Java tutorial
/** * Copyright 2014 Microsoft Open Technologies Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.microsoftopentechnologies.intellij.helpers.aadauth; import com.google.common.base.Charsets; import com.google.common.io.CharStreams; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.project.Project; import com.microsoftopentechnologies.intellij.helpers.EncodingHelper; import com.microsoftopentechnologies.intellij.helpers.StringHelper; import com.microsoftopentechnologies.intellij.helpers.webserver.AADWebServer; import com.microsoftopentechnologies.intellij.helpers.webserver.ClosedCallback; import org.jetbrains.annotations.NotNull; import java.io.*; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.ProtocolException; import java.net.URL; import java.text.ParseException; import java.util.HashMap; import java.util.Map; import java.util.UUID; import java.util.concurrent.locks.ReentrantLock; public class AuthenticationContext { private String authority; private final String AUTHORIZE_ENDPOINT_TEMPLATE = "https://{host}/{tenant}/oauth2/authorize"; private final String TOKEN_ENDPOINT_TEMPLATE = "https://{host}/{tenant}/oauth2/token"; private AADWebServer webServer = null; private ReentrantLock authCodeLock = new ReentrantLock(); private boolean gotAuthCode = false; public AuthenticationContext(final String authority) throws IOException { this.authority = authority; } public void dispose() { if (webServer != null) { webServer.stop(); webServer = null; } } public ListenableFuture<AuthenticationResult> acquireTokenInteractiveAsync(final String tenantName, final String resource, final String clientId, final String redirectUri, final Project project, final String promptValue) throws IOException { return acquireTokenInteractiveAsync(tenantName, resource, clientId, redirectUri, project, "Sign in to your Microsoft account", promptValue); } public ListenableFuture<AuthenticationResult> acquireTokenInteractiveAsync(final String tenantName, final String resource, final String clientId, final String redirectUri, final Project project, final String windowTitle, final String promptValue) throws IOException { final SettableFuture<AuthenticationResult> future = SettableFuture.create(); // get the auth code ListenableFuture<String> authCodeFuture = acquireAuthCodeInteractiveAsync(tenantName, resource, clientId, redirectUri, project, windowTitle, promptValue); Futures.addCallback(authCodeFuture, new FutureCallback<String>() { @Override public void onSuccess(String code) { OutputStream output = null; BufferedReader reader = null; try { // if code is null then the user cancelled the auth if (code == null) { future.set(null); return; } URL adAuthEndpointUrl = new URL( TOKEN_ENDPOINT_TEMPLATE.replace("{host}", authority).replace("{tenant}", tenantName)); // build the a/d auth params Map<String, String> params = new HashMap<String, String>(); params.put(OAuthParameter.clientId, clientId); params.put(OAuthParameter.code, code); params.put(OAuthParameter.grantType, OAuthGrantType.AuthorizationCode); params.put(OAuthParameter.redirectUri, redirectUri); params.put(OAuthParameter.resource, resource); byte[] requestData = EncodingHelper.toQueryString(params).getBytes(Charsets.UTF_8); // make a POST request to the endpoint with this data HttpURLConnection connection = (HttpURLConnection) adAuthEndpointUrl.openConnection(); connection.setRequestMethod("POST"); connection.setDoOutput(true); connection.setDoInput(true); connection.setUseCaches(false); connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=" + Charsets.UTF_8.name()); connection.setRequestProperty("Content-Length", Integer.toString(requestData.length)); output = connection.getOutputStream(); output.write(requestData); output.close(); output = null; // read the response int statusCode = connection.getResponseCode(); if (statusCode != HttpURLConnection.HTTP_OK) { // TODO: Is IOException the right exception type to raise? String err = CharStreams.toString(new InputStreamReader(connection.getErrorStream())); future.setException(new IOException("AD Auth token endpoint returned HTTP status code " + Integer.toString(statusCode) + ". Error info: " + err)); return; } reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); StringBuilder sb = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { sb.append(line); } reader.close(); reader = null; // parse the JSON String response = sb.toString(); JsonParser parser = new JsonParser(); JsonObject root = (JsonObject) parser.parse(response); // construct the authentication result object AuthenticationResult result = new AuthenticationResult( getJsonStringProp(root, OAuthReservedClaim.TokenType), getJsonStringProp(root, OAuthReservedClaim.AccessToken), getJsonStringProp(root, OAuthReservedClaim.RefreshToken), getJsonLongProp(root, OAuthReservedClaim.ExpiresOn), UserInfo.parse(getJsonStringProp(root, OAuthReservedClaim.IdToken))); future.set(result); } catch (MalformedURLException e) { future.setException(e); } catch (ProtocolException e) { future.setException(e); } catch (IOException e) { future.setException(e); } catch (ParseException e) { future.setException(e); } finally { try { if (output != null) { output.close(); } if (reader != null) { reader.close(); } } catch (IOException ignored) { } } } @Override public void onFailure(@NotNull Throwable throwable) { future.setException(throwable); } }); return future; } public AuthenticationResult acquireTokenByRefreshToken(AuthenticationResult authenticationResult, String tenantName, String resource, String clientId) throws IOException { URL adAuthEndpointUrl = new URL( TOKEN_ENDPOINT_TEMPLATE.replace("{host}", authority).replace("{tenant}", tenantName)); // build the a/d auth params Map<String, String> params = new HashMap<String, String>(); params.put(OAuthParameter.clientId, clientId); params.put(OAuthParameter.grantType, OAuthGrantType.RefreshToken); params.put(OAuthParameter.refreshToken, authenticationResult.getRefreshToken()); if (resource != null) { params.put(OAuthParameter.resource, resource); } byte[] requestData = EncodingHelper.toQueryString(params).getBytes(Charsets.UTF_8); // make a POST request to the endpoint with this data HttpURLConnection connection = (HttpURLConnection) adAuthEndpointUrl.openConnection(); connection.setRequestMethod("POST"); connection.setDoOutput(true); connection.setDoInput(true); connection.setUseCaches(false); connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=" + Charsets.UTF_8.name()); connection.setRequestProperty("Content-Length", Integer.toString(requestData.length)); OutputStream output = connection.getOutputStream(); output.write(requestData); output.close(); output = null; // read the response int statusCode = connection.getResponseCode(); if (statusCode != HttpURLConnection.HTTP_OK) { // TODO: Is IOException the right exception type to raise? throw new IOException( "AD Auth token endpoint returned HTTP status code " + Integer.toString(statusCode)); } BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); StringBuilder sb = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { sb.append(line); } reader.close(); reader = null; // parse the JSON String response = sb.toString(); JsonParser parser = new JsonParser(); JsonObject root = (JsonObject) parser.parse(response); // update the authentication result object return new AuthenticationResult(getJsonStringProp(root, OAuthReservedClaim.TokenType), getJsonStringProp(root, OAuthReservedClaim.AccessToken), getJsonStringProp(root, OAuthReservedClaim.RefreshToken), getJsonLongProp(root, OAuthReservedClaim.ExpiresOn), authenticationResult.getUserInfo()); } private String getJsonStringProp(JsonObject obj, String propName) { JsonElement element = obj.get(propName); if (element != null) { return element.getAsString(); } return ""; } private long getJsonLongProp(JsonObject obj, String propName) { JsonElement element = obj.get(propName); if (element != null) { return element.getAsLong(); } return Long.MIN_VALUE; } private ListenableFuture<String> acquireAuthCodeInteractiveAsync(final String tenantName, final String resource, final String clientId, final String redirectUri, final Project project, final String windowTitle, final String promptValue) throws IOException { final SettableFuture<String> future = SettableFuture.create(); try { String correlationId = UUID.randomUUID().toString(); // build the a/d auth URI params Map<String, String> params = new HashMap<String, String>(); params.put(OAuthParameter.resource, resource); params.put(OAuthParameter.clientId, clientId); params.put(OAuthParameter.responseType, OAuthResponseType.code); params.put(OAuthParameter.redirectUri, redirectUri); params.put(OAuthParameter.correlationId, correlationId); params.put(OAuthParameter.prompt, promptValue); params.put("site_id", "500879"); params.put("display", "popup"); String query = null; query = EncodingHelper.toQueryString(params); // build the actual URI String adUri = AUTHORIZE_ENDPOINT_TEMPLATE.replace("{host}", authority).replace("{tenant}", tenantName); adUri = adUri + "?" + query; // initialize and start up web server if (webServer == null) { webServer = new AADWebServer(); webServer.start(); } webServer.setAuthCodeCallback(new AuthCodeCallback() { @Override public void onAuthCodeReceived(String code, Map<String, String> params) { authCodeLock.lock(); try { gotAuthCode = true; if (StringHelper.isNullOrWhiteSpace(code)) { String msg = "An error occurred during authentication. 'code' is null/empty."; if (params.containsKey("error")) { msg += "\nError code: " + params.get("error"); } if (params.containsKey("error_description")) { msg += "\nDescription: " + params.get("error_description"); } future.setException(new IllegalArgumentException(msg)); } else { future.set(code); } } finally { authCodeLock.unlock(); } } }); webServer.setClosedCallback(new ClosedCallback() { @Override public void onClosed() { authCodeLock.lock(); try { if (!gotAuthCode) { future.set(null); } } finally { authCodeLock.unlock(); } } }); // start the browser final String finalAdUri = adUri; ApplicationManager.getApplication().invokeAndWait(new Runnable() { @Override public void run() { BrowserLauncher browserLauncher = null; try { browserLauncher = new BrowserLauncher(finalAdUri, redirectUri, webServer.getBaseURL().toString(), windowTitle, project); browserLauncher.browse(); } catch (MalformedURLException ignored) { } } }, ModalityState.any()); } catch (UnsupportedEncodingException e) { future.setException(e); } catch (MalformedURLException e) { future.setException(e); } return future; } }