Authorization issue when reading sheet - java

I want to read a table from Google Sheets from my android application.
I want to do that by Google Sheets APIs.
I declared the sheet as public, created API key and tried to send the GET service call:
https://sheets.googleapis.com/v4/spreadsheets/{My Sheet key}/values/responses:append?key={My API credential key}
I get 401 code.
Response:
Request is missing required authentication credential. Expected OAuth
2 access token, login cookie or other valid authentication credential.
See
https://developers.google.com/identity/sign-in/web/devconsole-project.
My code:
private static final String SHEET_URL = "https://sheets.googleapis.com/v4/spreadsheets/1d534sQ5xaNbr65wMM_qH2yjXo3EPrrp3o34z-Foledg/values/responses:append?key=AIzaSyDT88Nq6jhtaKH-vIVEuvGO1d9Sx8ewR0w";
public String GetConanimList() throws Exception {
URL url = new URL(SHEET_URL);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setDoOutput(true);
conn.setRequestMethod("GET");
conn.setRequestProperty("Content-Type", "application/json");
OutputStream os = conn.getOutputStream();
String jsonPayload = null;
//os.write(jsonPayload.getBytes());
os.flush();
os.close();
int statusCode = conn.getResponseCode();
System.out.println("Response from WA Gateway: \n");
System.out.println("Status Code: " + statusCode);
BufferedReader br = new BufferedReader(new InputStreamReader(
(statusCode == 200) ? conn.getInputStream() : conn.getErrorStream()
));
String output;
String response = "";
while ((output = br.readLine()) != null) {
response = response + output;
}
conn.disconnect();
return response;
}
What am I missing? Thanks.

To use Google Sheets API, you need to authorize your requests.
There are two ways to identify your application: using an OAuth 2.0
token (which also authorizes the request) and/or using the
application's API key. Here's how to determine which of those
options to use:
If the request requires authorization (such as a request for an individual's private data), then the application must provide an OAuth 2.0 token with the request. The application may also provide the API key, but it doesn't have to.
If the request doesn't require authorization (such as a request for public data), then the application must provide either the API key or an OAuth 2.0 token, or both—whatever option is most convenient for you.
You can also refer to the quickstart projects available as a guide on how to correctly implement this.

Related

MSAL with Graph API

I am trying to build a Java code to create users in AAD using MSAL and MS Graph API. Below is the code that I am using to create the user. I am able to retrieve the token successfully, however getting exception while trying to POST the request. What am I doing wrong?
public static void main(String[] args) throws Exception {
Map<String,Object> params = new LinkedHashMap<>();
params.put("givenName", "Test");
params.put("displayName", "ABC");
params.put("accountEnabled", true);
params.put("mailNickname","abc");
params.put("userPrincipalName","jcooper#demo.onmicrosoft.com");
StringBuilder postData = new StringBuilder();
for (Map.Entry<String,Object> param : params.entrySet()) {
if (postData.length() != 0) postData.append('&');
postData.append(URLEncoder.encode(param.getKey(), "UTF-8"));
postData.append('=');
postData.append(URLEncoder.encode(String.valueOf(param.getValue()), "UTF-8"));
}
byte[] postDataBytes = postData.toString().getBytes("UTF-8");
int length =postDataBytes.length;
URL url = new URL("https://graph.microsoft.com/v1.0/users");
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type","application/json");
conn.setRequestProperty("Authorization", "Bearer "+accessToken);
conn.setDoInput(true);
conn.setDoOutput(true);
conn.setInstanceFollowRedirects(false);
conn.setRequestProperty("Content-Length",Integer.toString(length));
conn.connect();
conn.getInputStream();
try (var wr = new DataOutputStream(conn.getOutputStream())) {
wr.write(postDataBytes);
}
StringBuilder content;
System.out.println(postDataBytes+" "+postData);
try (var br = new BufferedReader(
new InputStreamReader(conn.getInputStream()))) {
String line;
content = new StringBuilder();
while ((line = br.readLine()) != null) {
content.append(line);
content.append(System.lineSeparator());
}
}
System.out.println(content.toString());
}
Exception : Exception in thread "main" java.io.IOException: Server returned HTTP response code: 411 for URL: https://graph.microsoft.com/v1.0/users
According to some test, I met the same issue with yours. It seems the code is correct but do not know why it still show 411 error. It may be caused by the graph api can just accept json request body but you convert the request body to application/x-www-form-urlencoded in your first part of code(I'm not sure because I test the code with json request body but still show 411).
Since you mentioned use MSAL to get access token, you can also continue to use MSAL to create the user. Please refer to this example:
GraphServiceClient graphClient = GraphServiceClient.builder().authenticationProvider( authProvider ).buildClient();
User user = new User();
user.accountEnabled = true;
user.displayName = "Adele Vance";
user.mailNickname = "AdeleV";
user.userPrincipalName = "AdeleV#contoso.onmicrosoft.com";
PasswordProfile passwordProfile = new PasswordProfile();
passwordProfile.forceChangePasswordNextSignIn = true;
passwordProfile.password = "xWwvJ]6NMw+bWH-d";
user.passwordProfile = passwordProfile;
graphClient.users()
.buildRequest()
.post(user);
For accessing Microsoft Graph from a desktop app, I'd use the InteractiveBrowserCredentialBuilder() with the TokenCredentialAuthProvider that comes with GraphSDK to get the Graph token. Check out the great sample code here. All you'd need to do to customize this is to change the last line and set the scopes differently based on what Graph API you need to call. There's a link on the bottom of that page that'll teach you to register your app properly.
The simplest way in a web app is to use Azure AD Spring Boot Starter to get an access token for a logged-in user, and use GraphSDK to call Graph in a Spring 5 web app. See this sample that demonstrates this along with full instructions (Relevant Graph code is is in SampleController.java and Utilities.java)

Microsoft Graph: Requesting an Extension returns http 400 bad request

I added an open extension to an event in a calendar and am trying to read it back.
Here is the url:
https://graph.microsoft.com/v1.0/users/{userid}/calendars/{calendarId}=/events?$expand=Extensions($filter=Id eq 'c.i.m.p.server.entities.outlook.Event')
I cannot get this to work in a Java program. The following combinations do work:
It works my Java program if I remove the $expand... parameter. I can also ask for certain fields, that works too.
The request works in Postman (I just have to set the token)
The request works in Graph Explorer when I log in as the owner of the calendar
Here is the extension (inside one of the events) when I use Postman to read the event. It is the last item in the event:
"extensions#odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('{userid}')/calendars('{calendarId}')/events('{eventId})/extensions",
"extensions": [
{
"#odata.type": "#microsoft.graph.openTypeExtension",
"id": "Microsoft.OutlookServices.OpenTypeExtension.c.i.m.p.server.entities.outlook.Event",
"extensionName": "c.i.m.p.server.entities.outlook.Event",
"adherentId": "12346",
"timeSlotID": "346463"
}
]
Here is the Java code (Java 8, using java.io and java.net libraries):
private static void doSomething(String _accessToken) throws IOException {
String urlString = "https://graph.microsoft.com/v1.0/users/{userId}/calendars/{calendarId}/events?$expand=Extensions($filter=Id eq 'c.i.m.p.server.entities.outlook.Event')";
URL url = new URL(urlString);
Proxy webProxy
= new Proxy(Proxy.Type.HTTP, new InetSocketAddress({proxy-address}, {port}));
HttpURLConnection connection = (HttpURLConnection) url.openConnection(webProxy);
// Set the appropriate header fields in the request header.
connection.setRequestProperty("Authorization", "Bearer " + _accessToken);
connection.setRequestProperty("Accept", "application/json");
connection.setDoOutput(true);
connection.setReadTimeout(5000);
connection.setRequestMethod(HttpMethod.GET);
try {
connection.connect();
int responseCode = connection.getResponseCode();
System.out.println("execute(), response code = " + responseCode);
String responseMessage = connection.getResponseMessage();
System.out.println("execute(), response Message = " + responseMessage);
String responseString = null;
try {
InputStream ins = connection.getInputStream();
BufferedReader br=new BufferedReader(new InputStreamReader(ins));
StringBuffer sb=new StringBuffer();
String line;
while ((line=br.readLine()) != null) {
sb.append(line);
}
responseString = sb.toString();
} catch (Exception e) {
System.out.println("Could not get input stream from response, error is " + e.toString());
}
System.out.println("execute(), httpResult = " + responseString);
} catch (IOException e) {
System.out.println(".execute(), IOException : " + e.toString());
} finally {
connection.disconnect();
}
}
How do I fix this? Thanks!
400 means bad request. It could be because of url encoding. Url encode the query string.
Something like
String query = "Extensions($filter=Id eq 'c.i.m.p.server.entities.outlook.Event'";
String url = "https://graph.microsoft.com/v1.0/users/{userId}/calendars/{calendarId}/events?
$expand=" + URLEncoder.encode(query, StandardCharsets.UTF_8.name());
Alternatively you could use graph service java api based on your need which will help abstract all the interactions for you or you could use any of the rest clients available.
First of all, you should provide more info on the error - Stacktrace and error message. But 400 code indicates that was a user mistake, meaning that you are sending an invalid request. Since you say that postman request works then compare all the headers that are sent by postman and see if your code misses some hearer. As for the code, instead of coding your own Http client functionality I would suggest using 3d party Http client. Here are a few suggestions:
Apache Http client - very popular and well known 3d party Http Client
OK Http client - Open-source Http client. Here is tutorial
MgntUtils Http client - very simple 3d party HttpClient: Provided in MgntUtils Open source library (written by me). Very simple in use. Take a look at Javadoc. Library itself provided as Maven artifacts and on Git (including source code and Javadoc).

Authentication requests using HTTPS to an API in Java

I am looking to use an API called HookTheory. I am making the HTTPS calls in Java, and since its my first time using HTTP or HTTPS clients I am running into a few road blocks.
Here are the document's details on User Authentication:
You authenticate to the Hooktheory API by providing an HTTP Bearer Token, according to the OAuth 2 protocol. Your HTTP Bearer Token is retrieved through the API with your www.hooktheory.com username and password. To retrieve your HTTP Bearer Token, make the following request:
POST users/auth
The body of the request must contain your www.hooktheory.com username and password:
{
"username": "Hooktheory",
"password": "0123456789"
}
The response will contain three fields, as shown below:
{
"id": 1234,
"username": "Hooktheory",
"activkey": "aoa6jjacz34kcta3aomeqwuz89"
}
The "activkey" property contains your HTTP Bearer Token; include it as an authorization header in all future requests to the API.
Can someone explain in better detail how I would go about doing this in Java?
My starter code is the following:
String url = "https://api.hooktheory.com/v1/users/auth";
URL obj = new URL(url);
HttpsURLConnection con = (HttpsURLConnection) obj.openConnection();
//add request header
con.setRequestMethod("POST");
con.setRequestProperty("username", username);
con.setRequestProperty("password", password);
String urlParameters = "sn=C02G8416DRJM&cn=&locale=&caller=&num=12345";
// Send post request
con.setDoOutput(true);
DataOutputStream wr = new DataOutputStream(con.getOutputStream());
wr.writeBytes(urlParameters);
wr.flush();
wr.close();
int responseCode = con.getResponseCode();
System.out.println("\nSending 'POST' request to URL : " + url);
System.out.println("Post parameters : " + urlParameters);
System.out.println("Response Code : " + responseCode);
BufferedReader in = new BufferedReader(
new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
//print result
System.out.println(response.toString());
This code is basically a copy of the code here.
I get a 401 error (authentication) when I run the post code above. I'm guessing its because I haven't formatted the get/post requests correctly.
Can somebody point me in the right direction?
You've set username and password as request properties, which are HTTP headers. Per the HookTheory docs, you need to send these on the request body.
Specifically, you need to send the JSON request on the body like this:
DataOutputStream wr = new DataOutputStream(con.getOutputStream());
String requestBody = String.format("{ \"username\": \"%s\", \"password\": \"%s\" }", username, password);
wr.writeBytes(requestBody);
Also, per their docs you should indicate that this is JSON by setting the Accept and Content-Type headers. This is done prior to sending the post request data through calls like this:
con.setRequestProperty("Accept", "application/json");
con.setRequestProperty("Content-Type", "application/json");
So the entire block looks like:
String url = "https://api.hooktheory.com/v1/users/auth";
URL obj = new URL(url);
HttpsURLConnection con = (HttpsURLConnection) obj.openConnection();
con.setRequestMethod("POST");
con.setRequestProperty("Accept", "application/json");
con.setRequestProperty("Content-Type", "application/json");
// Send post request
con.setDoOutput(true);
DataOutputStream wr = new DataOutputStream(con.getOutputStream());
String requestBody = String.format("{ \"username\": \"%s\", \"password\": \"%s\" }", username, password);
wr.writeBytes(requestBody);
wr.flush();
wr.close();
code 401 clearly specifies something wrong with authentication. stack trace shows unauthenticated on server side.
10.4.2 401 Unauthorized
The request requires user authentication. The response MUST include a WWW-Authenticate header field (section 14.47) containing a challenge applicable to the requested resource. The client MAY repeat the request with a suitable Authorization header field (section 14.8). If the request already included Authorization credentials, then the 401 response indicates that authorization has been refused for those credentials. If the 401 response contains the same challenge as the prior response, and the user agent has already attempted authentication at least once, then the user SHOULD be presented the entity that was given in the response, since that entity might include relevant diagnostic information. HTTP access authentication is explained in "HTTP Authentication: Basic and Digest Access Authentication"

RestEasy client to secured service

We have deployed RestEasy services, as part of application, and secured them with filter (a class that implements javax.servlet.Filter). Filter checks if object of the User class exists in the session. The user object is inserted into session when user logs in. Everything works fine.
However, we need to create standalone app that accesses these services. We decided to use RestEasy client API. We can access these services if the filter is turned off, but how do we perform this logging in and session manipulation from RestEasy client API.
Let's assume your standalone client was written in Java.
Then a sample code would look like this.
First we need to authenticate the user by calling your auth service and obtain a valid session ID. Let's assume the obtained session id is equivalent to "f47cdba8-d066-48e8-ad29-8c4222906bc7". Then we can call the REST service with the valid session ID as a HTTP header parameter.
String url = "http://localhost:8081/AuthService/";
URL obj = new URL(url);
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
// optional default is GET
con.setRequestMethod("GET");
//add request header
con.setRequestProperty("User-Agent", USER_AGENT);
con.setRequestProperty("Authorization", "token=f47cdba8-d066-48e8-ad29-8c4222906bc7");
int responseCode = con.getResponseCode();
System.out.println("\nSending 'GET' request to URL : " + url);
System.out.println("Response Code : " + responseCode);
BufferedReader in = new BufferedReader(
new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
//print result
System.out.println(response.toString());
At the back end you need to process this HTTP header parameter based Session ID and validate the oncoming request before serving it.
Hope this helps.

Cannot add user to database/_security in CouchDB using HTTP PUT

The problem
In my Android app, I am trying to add a user to the /_security document of my CouchDB database via HTTP PUT. If I am trying to do this by authenticating my admin user using Cookie-authentication or by simply inserting the admin data into the url like in the following, I receive an error.
URL on which the PUT is directed (if not using Cookie-authentication):
http://admin_name:admin_password#url:port/databasename/_security
Error I receive in both cases:
Authentication error: Unable to respond to any of these
challenges: {} {"error":"unauthorized","reason":"You are not a db or
server admin."}
If I am doing this via command-line using curl, the user is inserted without any problems:
~$ curl -X PUT http://admin:pw#ip:port/databasename/_security -d '{"admins":{"names":[],"roles":[]},"members":{"names":["participant_1"],"roles":[]}}'
> {"ok":true}
My aproach
As soon as I authenticate using the "Authorization" option in one of my HTTP PUT's headers, athentication is no problem anymore.
private boolean putJSONWithAuthentication(String userName, String password, String json, String url) {
// url = http://url:port/databasename/_security
// json = {"admins":{"names":[],"roles":[]},"members":{"names":["participant_1"],"roles":[]}}
HttpClient client = new DefaultHttpClient();
HttpPut put = new HttpPut(url);
String authenticationData = userName+":"+password;
String encoding = Base64.encodeToString(authenticationData.getBytes(Charset.forName("utf-8")), Base64.DEFAULT);
try {
StringEntity stringEntity = new StringEntity(json,"utf-8");
put.setEntity(stringEntity);
put.setHeader("Content-type", "application/json; charset=utf-8");
put.setHeader("Accept", "application/json");
put.setHeader("Authorization", "Basic " + encoding);
HttpResponse response = client.execute(put);
BufferedReader rd = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
String line = "";
while ((line = rd.readLine()) != null) {
System.out.println(line);
}
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
However, I am receiving this error:
> {"error":"bad_request","reason":"invalid_json"}
If I am inserting my user-JSON as a usual document using the above method, e.g., to http://url:port/databasename/new_document, the JSON is inserted without any errors. Consequently, I guess the JSON string should be formatted correctly.
Thus, my questions is, what am I missing here? It seems like I cannot authenticate and put data in the request body at once. How do I correctly insert a user to the /_security document of a database from code?
Instead of creating the basic auth header manually, can you try create it using UsernamePasswordCredentials, e.g.
HttpPut put = new HttpPut(url);
UsernamePasswordCredentials creds = new UsernamePasswordCredentials(userName, password);
put.addHeader( BasicScheme.authenticate(creds,"US-ASCII",false) );
...
HttpResponse response = client.execute(put);

Categories