Play Framework and Office 365 OAuth - java

I am working on writing a Play Framework application that interfaces with Azure Active Directory. I'm starting with simply pulling some events but I can't get past the initial token refresh request.
private static void getEventsFromOffice365(){
System.out.println("getting from O365");
Promise<String> promise = WS.url("https://login.microsoftonline.com/common/oauth2/token")
.setBody("grant_type=refresh_token&refresh_token=[refresh token]&scope=openid+offline_access+https%3A%2F%2Foutlook.office.com%2Fmail.read+https%3A%2F%2Foutlook.office.com%2Fcalendars.read+https%3A%2F%2Foutlook.office.com%2Fcontacts.read&redirect_uri=https%3A%2F%2Foauthplay.azurewebsites.net%2F&client_id=[client id]&client_secret=[client secret]")
.setContentType("application/x-www-form-urlencoded")
.post("")
.map(
new Function<WSResponse, String>() {
public String apply(WSResponse response) {
System.out.println("Done");
String result = response.getBody();
System.out.println("Result:" + result);
System.out.println("json:" + response.getStatus());
return result;
}
});
}
For some reason whenever I run this I get the following response from Microsoft.
{"error":"invalid_request","error_description":"AADSTS90014: The request body must contain the following parameter: 'grant_type'.\r\nTrace ID: 6a3c1620-6f4d-4c53-a077-cf1f842c0332\r\nCorrelation ID: 0caba711-d434-4ce9-b15e-a56e27ea5a0f\r\nTimestamp: 2015-11-02 23:31:17Z","error_codes":[90014],"timestamp":"2015-11-02 23:31:17Z","trace_id":"6a3c1620-6f4d-4c53-a077-cf1f842c0332","correlation_id":"0caba711-d434-4ce9-b15e-a56e27ea5a0f"}
As you can see I have the grant_type declared in the post body. Why am I getting this error and how can I solve it?

From the official documents,you could use the setQueryParameter
to pass your parameters. I suggest you can refer to code as following :
WSRequestHolder req=WS.url("https://login.microsoftonline.com/common/oauth2/token");
req.setQueryParameter("grant_type", refresh_token );
req.setQueryParameter("refresh_token ", refresh_token);
req.setQueryParameter("redirect_uri",REDIRECT_URI);
req.setQueryParameter("scope", scope);
req.setQueryParameter("client_secret ", client_secret);
req.setQueryParameter("client_id ", client_id);
req.setContentType("application/x-www-form-urlencoded")
req.map(
new Function<WSResponse, String>() {
public String apply(WSResponse response) {
System.out.println("Done");
String result = response.getBody();
System.out.println("Result:" + result);
System.out.println("json:" + response.getStatus());
return result;
}
});
At the same time, I suggest you can try to use execute method instead of post method like this :
. execute(“post”)
Any results, please let me know.

Related

Java Exception: Unauthorized: 401

I'm getting the error: Exception: org.springframework.web.client.HttpClientErrorException$Unauthorized: 401, when trying to connect to Jira through HttpHeader, and the credentials are configured in a configserver file, which would be this:
#Component
public class JiraHttpHeadersHelper {
#Value("${first.jira.auth.user}")
private String firstJiraAuthUser;
#Value("${first.jira.auth.psw}")
private String firstJiraAuthPsw;
public HttpHeaders jiraHeadersWithAuthentication() {
String plainCreds = firstJiraAuthUser + ":" + firstJiraAuthPsw;
System.out.println("Credenciales JiraServices: "+plainCreds);
byte[] base64CredsBytes = Base64.getEncoder().encode(plainCreds.getBytes());
String base64Creds = new String(base64CredsBytes);
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Basic" + base64Creds);
headers.setContentType(MediaType.APPLICATION_JSON);
System.out.println("Authorization JiraServices: "+headers);
return headers;
}
}
And the method where I command to call the file above and where I get the error on the line ResponseEntity<String> result = restTemplate.exchange(url, HttpMethod.GET,this.requestEnt, String.class);, would be this:
public ResponseEntity<String> getPriorityJira() {
//Request entity created
this.requestEnt = new HttpEntity(this.jiraHttpHeadersHelper.jiraHeadersWithAuthentication());
String jql = "priority";
String url = jiraBaseURL + jql;
try {
ResponseEntity<String> result = restTemplate.exchange(url, HttpMethod.GET,this.requestEnt, String.class);
System.out.println("HttpStatus"+HttpStatus.OK);
if (result.getStatusCode() == HttpStatus.OK) {
return result;
} else {
logger.error("Jira Generic User maybe blocked, status from API: " +result.getStatusCode() + ". Body: "+ result.getBody());
return new ResponseEntity<>(result.getBody(), result.getStatusCode());
}
} catch(HttpClientErrorException e) {
logger.error("Error getting priorityJira. Exception: "+ e);
return new ResponseEntity<>(e.getStatusCode());
}
}
In fact, when I run the debug and check the credentials, it brings them up without a problem. I've already searched, tried most of the links on this page and I can't find the solution.
Any help would be appreciated in this case, thanks in advance.
When you define your authorization header you concat your key with « Basic » without adding a white space.
headers.add("Authorization", "Basic" + base64Creds);
Instead of :
headers.add("Authorization", "Basic " + base64Creds);
Maybe it’s just that.
Edit :
The answer was to add StandardCharsets.UTF-8 to the String constructor.

Changing a 404 response for REST API to a 200 empty response

I have a Spring Boot application written in Java that is a REST API. This service (Svc A) calls a REST API service (Svc B) with is also a Spring Boot Application written in Java. Svc B returns a 404 status code when no data was found. I need to change this response to a 200 status code and return an empty response object. I am not sure if or how to do this.
I can catch the error and determine if the 404 is this no data found error. However, I don't know how to change the response to a 200 empty response.
I am using a FeignClient to call the service. This is the error code that catches the 404:
#Component
public class FeignErrorDecoder implements ErrorDecoder {
Logger logger = LoggerFactory.getLogger(this.getClass());
#Override
public Exception decode(String methodKey, Response response) {
Reader reader = null;
String messageText = null;
switch (response.status()){
case 400:
logger.error("Status code " + response.status() + ", methodKey = " + methodKey);
case 404:
{
logger.error("Error took place when using Feign client to send HTTP Request. Status code " + response.status() + ", methodKey = " + methodKey);
try {
reader = response.body().asReader();
//Easy way to read the stream and get a String object
String result = CharStreams.toString(reader);
logger.error("RESPONSE BODY: " + result);
ObjectMapper mapper = new ObjectMapper();
//just in case you missed an attribute in the Pojo
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
//init the Pojo
ExceptionMessage exceptionMessage = mapper.readValue(result,
ExceptionMessage.class);
messageText = exceptionMessage.getMessage();
logger.info("message: " + messageText);
} catch(IOException ex) {
logger.error(ex.getMessage());
}
finally {
try {
if (reader != null)
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return new ResponseStatusException(HttpStatus.valueOf(200), messageText);
}
default:
return new Exception(response.reason());
}
}
}
I can change the status code to a 200 and it returns a 200 but I need to the response to have an empty response object.
The above code will return this response body of an error response object:
{
"statusCd" : "200",
"message" : "The Location not found for given Location Number and Facility Type Code",
"detailDesc" : "The Location not found for given Location Number and Facility Type Code. Error Timestamp : 2020-01-31 18:19:13"
}
I need it to return a response body like this:
200 - Empty Response
{
"facilityNumber": "923",
"facilityTimeZone": null,
"facilityAbbr": null,
"scheduledOperations": []
}
In case 404 just try
return new ResponseStatusException(HttpStatus.valueOf(200));
For anyone that has to do something this crazy...here is my solution:
Removed the FeignErrorCode file.
Added an exception to ControllerAdvice class like this:
#ExceptionHandler(FeignException.class)
public ResponseEntity<?> handleFeignException(FeignException fe, WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails(new Date(), HttpStatus.valueOf(fe.status()), fe.getMessage(), request.getDescription(false));
String response = fe.contentUTF8();
if(response != null) {
ScheduledOperationsViewResponse scheduledOperationsViewResponse = new ScheduledOperationsViewResponse();
if (response.contains("Scheduled") || response.contains("Location")) {
HttpHeaders headers = new HttpHeaders();
scheduledOperationsViewResponse.setFacilityNumber(request.getParameter("facilityNumber"));
return new ResponseEntity<ScheduledOperationsViewResponse>(scheduledOperationsViewResponse, headers, HttpStatus.OK);
}
}
return new ResponseEntity<>(errorDetails, errorDetails.getStatus());
}

Why don't I receive POST vars from OkHttp3? (Android Studio)

I've answered my own question here. See the first code block where I use php://input to get the posted data.
I'm trying to send a post request from my app to a webserver, and I'm checking for the post vars using PHP:
if( isset( $_POST['name'] ) ){
echo json_encode(['status' => 1]);
}else if( $posted_data = (string) #file_get_contents('php://input') ) {
echo json_encode(['status' => 2]);
}else{
echo json_encode($_POST);
}
The request always returns and empty json encoded array.
I'm using the latest Android Studio, and the latest OkHttp, com.squareup.okhttp3:okhttp:3.4.1. For me, this is like the "Hello World" of OkHttp in Android Studio.
In MainActivity.java:
public void postSomething(View view) {
String url = "https://example.com/json_api_test.php";
String json = "{\"name\":\"cholula\"}";
OkHttpPostHandler handler = new OkHttpPostHandler();
String result = "";
try {
result = handler.execute(url, json).get();
} catch (Exception e) {
e.printStackTrace();
}
displayPostResponse(result + "\n");
}
My OkHttpPostHandler.java:
public class OkHttpPostHandler extends AsyncTask<String, Void, String> {
OkHttpClient client = new OkHttpClient();
public static final MediaType JSON
= MediaType.parse("application/json; charset=utf-8");
#Override
protected String doInBackground(String... params) {
RequestBody body = RequestBody.create(JSON, params[1]);
Request request = new Request.Builder()
.url(params[0])
.post(body)
.build();
try {
Response response = client.newCall(request).execute();
return response.body().string();
}
catch( Exception e ){
return "HTTP Request Error";
}
}
}
When I debug, I can see that the params[1] value is the expected json-like string, but that's the last time I see it.
I've tried forming the json in a number of ways, so I'm not sure if that's the problem. I just don't understand why I can't see the posted vars when the post request gets to the server.
How can I see the posted vars on the webserver? What am I doing wrong? I've only been using Android Studio and Java for less than a week, so I have no clue. I've really looked around the internet a lot for the answer, and so posting here is the last resort.
Thanks!
It turns out that one must use php://input to get the posted data.

Outlook OAuth2 access mails

I am following this post: Outlook RestGettingStarted. From my Java app I am trying to get AccessToken and RefreshToken. When I made Authorization code request, it ended into following error:
Sorry, but we’re having trouble signing you in. We received a bad
request.
Additional technical information: Correlation ID:
ed838d66-5f2e-4cfb-9223-a29082ecb26f Timestamp: 2015-08-20 10:20:09Z
AADSTS90011: The 'resource' request parameter is not supported.
NOTE: URL formation is correct as per documentation.
So, I removed "resource" query parameter from my code. And redirected authorize url in browser. On user consent I got authorization code. Using this code I got AccessToken. But when I try to connect with Outlook IMAP server it failed. Java ref Link for details: Java OAuth2
But it gives me error:
[AUTHENTICATIONFAILED] OAuth authentication failed.
NOTE: I added correct scope, and user email.
Then using obtained Access Token I made Mail Rest API call to get Messages from User Inbox. It ended into following error:
HTTP response:
{"error":{"code":"MailboxNotEnabledForRESTAPI","message":"REST API is
not yet supported for this mailbox."}}
Can anyone help me for following:
What is the exact cause for: "AADSTS90011: The 'resource' request parameter is not supported" after following Outlook dev docs.
How to resolve "MailboxNotEnabledForRESTAPI" error.
Is it possible to connect using java mail APIs to Outlook IMAP server with correct AccessToken ?
I ran into this recently, but don't remember which solved it. One main issue is in the documentation in that it is varying. It will tell you to attach "resource", but that is for something else like Azure.
Here is the code I used:
First request to send:
private static final String USER_OAUTH2_AUTHORIZE_URL = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize";
public String getOAuthDialog(Http.Request request) {
return USER_OAUTH2_AUTHORIZE_URL
+ "?client_id=" + config.getClientId()
+ "&redirect_uri=" + getOutlookLoginRedirect(request)
+ "&response_type=code"
+ "&scope=https%3A%2F%2Foutlook.office.com%2Fmail.send%20" +
"https%3A%2F%2Foutlook.office.com%2Fmail.readwrite%20" +
"offline_access%20openid%20email%20profile"
+ "&state=" + crypto.generateSignedToken();
}
Scope was the hardest thing to figure out. I found a lot of ones that did not work. And it wasn't clear that I needed to separate them with spaces.
Then they will send you a request to your redirect url that was supplied. It will contain a code which you need to exchange for the data you requested in the scope. The redirect url that is supplied needs to be the exact same. Also you need to register the redirect url on your application portal under the Platform->Add Platform->Redirect URI->Add Url
private static final String USER_ACCESS_TOKEN_URL = "https://login.microsoftonline.com/common/oauth2/v2.0/token";
private Map<String, String> sendOutlookUserOAuthRequest(Http.Request request, String code) {
WSClient ws = WS.client();
HttpParameters params = new HttpParameters();
params.put("client_id", config.getClientId(), true);
params.put("client_secret", config.getClientSecret(), true);
params.put("code", code, true);
params.put("redirect_uri", getOutlookLoginRedirect(request), true);
params.put("grant_type", "authorization_code");
String postParams = OAuthUtil.parametersToString(params);
WSRequest wsRequest = ws.url(USER_ACCESS_TOKEN_URL)
.setMethod("POST")
.setContentType("application/x-www-form-urlencoded")
.setBody(postParams);
WSResponse wsResponse = wsRequest.execute().get(10, TimeUnit.SECONDS);
Map<String, String> result = new HashMap<>();
if (wsResponse.getStatus() != HttpStatus.SC_OK) {
return result;
}
JsonNode node = wsResponse.asJson();
if (node.hasNonNull("access_token")) {
result.put("access_token", node.get("access_token").asText());
}
if (node.hasNonNull("refresh_token")) {
result.put("refresh_token", node.get("refresh_token").asText());
}
if (node.hasNonNull("id_token")) {
String[] tokenSplit = node.get("id_token").asText().split("\\.");
if (tokenSplit.length >= 2) {
try {
JSONObject jsonObject = new JSONObject(new String(Base64.getDecoder().decode(tokenSplit[1])));
if (jsonObject.has("name")) {
result.put("name", jsonObject.get("name").toString());
}
if (jsonObject.has("email")) {
result.put("outlookUid", jsonObject.get("email").toString());
} else if (jsonObject.has("preferred_username")) {
result.put("outlookUid", jsonObject.get("preferred_username").toString());
}
} catch (JSONException e) {
log.error("Error extracting outlookUid from id_token: ", e);
}
}
}
return result;
}
Another request that you might need is to update the refresh token:
private String getAccessTokenFromRefreshToken(User user) {
WSClient ws = WS.client();
HttpParameters params = new HttpParameters();
params.put("client_id", config.getClientId(), true);
params.put("client_secret", config.getClientSecret(), true);
params.put("grant_type", "refresh_token");
params.put("refresh_token", user.getOutlookRefreshToken());
String postParams = OAuthUtil.parametersToString(params);
WSRequest wsRequest = ws.url(USER_ACCESS_TOKEN_URL)
.setMethod("POST")
.setContentType("application/x-www-form-urlencoded")
.setBody(postParams);
WSResponse wsResponse = wsRequest.execute().get(10, TimeUnit.SECONDS);
if (wsResponse.getStatus() != HttpStatus.SC_OK) {
log.error("Failure to refresh outlook access token for user: " + user +
". Received status: " + wsResponse.getStatus() + " : " + wsResponse.getStatusText());
return null;
}
JsonNode node = wsResponse.asJson();
if (node.hasNonNull("access_token")) {
String accessToken = node.get("access_token").asText();
return accessToken;
} else {
log.error("Outlook refresh token failure, 'access_token' not present in response body: " + wsResponse.getBody());
return null;
}
}
One issue I ran into that took far longer than I would have hoped was in getting the clientId and clientSecret. This was because the language microsoft uses wasn't the most explicit. Client Id and application id are used interchangeably. The client secret is also the password that you create on the Application Portal, not to be confused with the Private Key that you can generate.
So you actually want the application_id and the password, although they refer to them as client_id and client_secret with no direct indication as to the lines drawn.
This is all assuming you have set up an application on the Outlook Application Portal. https://apps.dev.microsoft.com/
I hope this helps, although I assume you probably already solved this.
I faced the same problem with Java mail. You need to add service principals for your application on the Azure AD.
Find complete steps explained in Medium article Complete guide: Java Mail IMAP OAuth2.0 Connect Outlook | by Ritik Sharma | Dec, 2022.

Jersey converting from ClientResponse to Response

I'm currently using Jersey as a proxy REST api to call another RESTful web service. Some of the calls will be passed to and from with minimal processing in my server.
Is there a way to do this cleanly? I was thinking of using the Jersey Client to make the REST call, then converting the ClientResponse into a Response. Is this possible or is there a better way to do this?
Some example code:
#GET
#Path("/groups/{ownerID}")
#Produces("application/xml")
public String getDomainGroups(#PathParam("ownerID") String ownerID) {
WebResource r = client.resource(URL_BASE + "/" + URL_GET_GROUPS + "/" + ownerID);
String resp = r.get(String.class);
return resp;
}
This works if the response is always a success, but if there's a 404 on the other server, I'd have to check the response code. In other words, is there clean way to just return the response I got?
There is no convenience method as far as I am aware. You can do this:
public Response getDomainGroups(#PathParam("ownerID") String ownerID) {
WebResource r = client.resource(URL_BASE + "/" + URL_GET_GROUPS + "/" + ownerID);
ClientResponse resp = r.get(ClientResponse.class);
return clientResponseToResponse(resp);
}
public static Response clientResponseToResponse(ClientResponse r) {
// copy the status code
ResponseBuilder rb = Response.status(r.getStatus());
// copy all the headers
for (Entry<String, List<String>> entry : r.getHeaders().entrySet()) {
for (String value : entry.getValue()) {
rb.header(entry.getKey(), value);
}
}
// copy the entity
rb.entity(r.getEntityInputStream());
// return the response
return rb.build();
}
for me answer from Martin throw:
JsonMappingException: No serializer found for class sun.net.www.protocol.http.HttpURLConnection$HttpInputStream
Change from
rb.entity(r.getEntityInputStream());
to
rb.entity(r.getEntity(new GenericType<String>(){}));
helped.

Categories