All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.smartsheet.api.internal.oauth.OAuthFlowImpl Maven / Gradle / Ivy

There is a newer version: 3.2.1
Show newest version
/*
 * Copyright (C) 2023 Smartsheet
 *
 * 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.smartsheet.api.internal.oauth;

import com.smartsheet.api.InvalidRequestException;
import com.smartsheet.api.internal.http.HttpClient;
import com.smartsheet.api.internal.http.HttpClientException;
import com.smartsheet.api.internal.http.HttpMethod;
import com.smartsheet.api.internal.http.HttpRequest;
import com.smartsheet.api.internal.http.HttpResponse;
import com.smartsheet.api.internal.json.JSONSerializerException;
import com.smartsheet.api.internal.json.JsonSerializer;
import com.smartsheet.api.internal.util.QueryUtil;
import com.smartsheet.api.internal.util.Util;
import com.smartsheet.api.oauth.AccessDeniedException;
import com.smartsheet.api.oauth.AccessScope;
import com.smartsheet.api.oauth.AuthorizationResult;
import com.smartsheet.api.oauth.InvalidOAuthClientException;
import com.smartsheet.api.oauth.InvalidOAuthGrantException;
import com.smartsheet.api.oauth.InvalidScopeException;
import com.smartsheet.api.oauth.InvalidTokenRequestException;
import com.smartsheet.api.oauth.OAuthAuthorizationCodeException;
import com.smartsheet.api.oauth.OAuthFlow;
import com.smartsheet.api.oauth.OAuthTokenException;
import com.smartsheet.api.oauth.Token;
import com.smartsheet.api.oauth.UnsupportedOAuthGrantTypeException;
import com.smartsheet.api.oauth.UnsupportedResponseTypeException;

import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;

/**
 * Default implementation of OAuthFlow.
 * 

* Thread Safety: Implementation of this interface must be thread safe. */ public class OAuthFlowImpl implements OAuthFlow { /** * Represents the HttpClient. *

* It will be initialized in constructor and will not change afterwards. */ private HttpClient httpClient; /** * Represents the JsonSerializer. *

* It will be initialized in constructor and will not change afterwards. */ private JsonSerializer jsonSerializer; /** * Represents the Client ID. *

* It will be initialized in constructor and will not change afterwards. */ private String clientId; /** * Represents the Client Secret. *

* It will be initialized in constructor and will not change afterwards. */ private String clientSecret; /** * Represents the redirect URL. *

* It will be initialized in constructor and will not change afterwards. */ private String redirectURL; /** * Represents the authorization URL. *

* It will be initialized in constructor and will not change afterwards. */ private String authorizationURL; /** * Represents the token URL. *

* It will be initialized in constructor and will not change afterwards. */ private String tokenURL; private static final String CODE = "code"; private static final String CLIENT_ID = "client_id"; private static final String REDIRECT_URI = "redirect_uri"; private static final String ERROR = "error"; private static final String REFRESH_TOKEN = "refresh_token"; /** * Constructor. *

* @param clientId the client id * @param clientSecret the client secret * @param redirectURL the redirect url * @param authorizationURL the authorization url * @param tokenURL the token url * @param httpClient the http client * @param jsonSerializer the json serializer * @throws IllegalArgumentException If any argument is null, or empty string. */ public OAuthFlowImpl(String clientId, String clientSecret, String redirectURL, String authorizationURL, String tokenURL, HttpClient httpClient, JsonSerializer jsonSerializer) { Util.throwIfNull(clientId, clientSecret, redirectURL, authorizationURL, tokenURL, httpClient, jsonSerializer); Util.throwIfEmpty(clientId, clientSecret, redirectURL, authorizationURL, tokenURL); this.clientId = clientId; this.clientSecret = clientSecret; this.redirectURL = redirectURL; this.authorizationURL = authorizationURL; this.tokenURL = tokenURL; this.httpClient = httpClient; this.jsonSerializer = jsonSerializer; } /** * Generate a new authorization URL. *

* Exceptions: - IllegalArgumentException : if scopes is null/empty * * @param scopes the scopes * @param state an arbitrary string that will be returned to your app; intended to be used by you to ensure that * this redirect is indeed from an OAuth flow that you initiated * @return the authorization URL */ public String newAuthorizationURL(EnumSet scopes, String state) { Util.throwIfNull(scopes); if (state == null) { state = ""; } // Build a map of parameters for the URL Map params = new HashMap<>(); params.put("response_type", CODE); params.put(CLIENT_ID, clientId); params.put(REDIRECT_URI, redirectURL); params.put("state", state); StringBuilder scopeBuffer = new StringBuilder(); for (AccessScope scope : scopes) { scopeBuffer.append(scope.name() + ","); } params.put("scope", scopeBuffer.substring(0, scopeBuffer.length() - 1)); // Generate the URL with the parameters return QueryUtil.generateUrl(authorizationURL, params); } /** * Extract AuthorizationResult from the authorization response URL (i.e. the redirectURL with the response * parameters from Smartsheet OAuth server). *

* Exceptions: *

* - IllegalArgumentException : if authorizationResponseURL is null/empty, or a malformed URL *

* - AccessDeniedException : if the user has denied the authorization request *

* - UnsupportedResponseTypeException : if the response type isn't supported * (note that this won't really happen in current implementation) *

* - InvalidScopeException : if some of the specified scopes are invalid *

* - OAuthAuthorizationCodeException : if any other error occurred during the operation * * @param authorizationResponseURL the authorization response URL * @return the authorization result * @throws URISyntaxException the URI syntax exception * @throws OAuthAuthorizationCodeException the o auth authorization code exception */ public AuthorizationResult extractAuthorizationResult(String authorizationResponseURL) throws URISyntaxException, OAuthAuthorizationCodeException { Util.throwIfNull(authorizationResponseURL); Util.throwIfEmpty(authorizationResponseURL); // Get all of the parms from the URL URI uri = new URI(authorizationResponseURL); String query = uri.getQuery(); if (query == null) { throw new OAuthAuthorizationCodeException("There must be a query string in the response URL"); } Map map = new HashMap<>(); for (String param : query.split("&")) { int index = param.indexOf('='); map.put(param.substring(0, index), param.substring(index + 1)); } // Check for an error response in the URL and throw it. String error = map.get(ERROR); if (error != null && !error.isEmpty()) { if ("access_denied".equals(error)) { throw new AccessDeniedException("Access denied."); } else if ("unsupported_response_type".equals(error)) { throw new UnsupportedResponseTypeException("response_type must be set to \"code\"."); } else if ("invalid_scope".equals(error)) { throw new InvalidScopeException("One or more of the requested access scopes are invalid. " + "Please check the list of access scopes"); } else { throw new OAuthAuthorizationCodeException("An undefined error was returned of type: " + error); } } AuthorizationResult authorizationResult = new AuthorizationResult(); authorizationResult.setCode(map.get(CODE)); authorizationResult.setState(map.get("state")); Long expiresIn; try { expiresIn = Long.parseLong(map.get("expires_in")); } catch (NumberFormatException ex) { expiresIn = 0L; } authorizationResult.setExpiresInSeconds(expiresIn); return authorizationResult; } /** * Obtain a new token using AuthorizationResult. *

* Exceptions: * - IllegalArgumentException : if authorizationResult is null * - InvalidTokenRequestException : if the token request is invalid (note that this won't really happen in current implementation) * - InvalidOAuthClientException : if the client information is invalid * - InvalidOAuthGrantException : if the authorization code or refresh token is invalid or expired, the * redirect_uri does not match, or the hash value does not match the client secret and/or code * - UnsupportedOAuthGrantTypeException : if the grant type is invalid (note that this won't really happen in * current implementation) * - OAuthTokenException : if any other error occurred during the operation * * @param authorizationResult the authorization result * @return the token * @throws OAuthTokenException the o auth token exception * @throws JSONSerializerException the JSON serializer exception * @throws HttpClientException the http client exception * @throws URISyntaxException the URI syntax exception * @throws InvalidRequestException the invalid request exception */ public Token obtainNewToken(AuthorizationResult authorizationResult) throws OAuthTokenException, JSONSerializerException, HttpClientException, URISyntaxException, InvalidRequestException { if (authorizationResult == null) { throw new IllegalArgumentException(); } // Prepare the hash String doHash = clientSecret + "|" + authorizationResult.getCode(); MessageDigest md; try { md = MessageDigest.getInstance("SHA-256"); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("Your JVM does not support SHA-256, which is required for OAuth with Smartsheet.", e); } byte[] digest; digest = md.digest(doHash.getBytes(StandardCharsets.UTF_8)); //String hash = javax.xml.bind.DatatypeConverter.printHexBinary(digest); String hash = org.apache.commons.codec.binary.Hex.encodeHexString(digest); // create a Map of the parameters Map params = new HashMap<>(); params.put("grant_type", "authorization_code"); params.put(CLIENT_ID, clientId); params.put(CODE, authorizationResult.getCode()); params.put(REDIRECT_URI, redirectURL); params.put("hash", hash); // Generate the URL and then get the token return requestToken(QueryUtil.generateUrl(tokenURL, params)); } /** * Refresh token. *

* Exceptions: * - IllegalArgumentException : if token is null. * - InvalidTokenRequestException : if the token request is invalid * - InvalidOAuthClientException : if the client information is invalid * - InvalidOAuthGrantException : if the authorization code or refresh token is invalid or expired, * the redirect_uri does not match, or the hash value does not match the client secret and/or code * - UnsupportedOAuthGrantTypeException : if the grant type is invalid * - OAuthTokenException : if any other error occurred during the operation * * @param token the token to refresh * @return the refreshed token * @throws OAuthTokenException the o auth token exception * @throws JSONSerializerException the JSON serializer exception * @throws HttpClientException the http client exception * @throws URISyntaxException the URI syntax exception * @throws InvalidRequestException the invalid request exception */ public Token refreshToken(Token token) throws OAuthTokenException, JSONSerializerException, HttpClientException, URISyntaxException, InvalidRequestException { // Prepare the hash String doHash = clientSecret + "|" + token.getRefreshToken(); MessageDigest md; try { md = MessageDigest.getInstance("SHA-256"); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("Your JVM does not support SHA-256, which is required for OAuth with Smartsheet.", e); } byte[] digest; digest = md.digest(doHash.getBytes(StandardCharsets.UTF_8)); //String hash = javax.xml.bind.DatatypeConverter.printHexBinary(digest); String hash = org.apache.commons.codec.binary.Hex.encodeHexString(digest); // Create a map of the parameters Map params = new HashMap<>(); params.put("grant_type", REFRESH_TOKEN); params.put(CLIENT_ID, clientId); params.put(REFRESH_TOKEN, token.getRefreshToken()); params.put(REDIRECT_URI, redirectURL); params.put("hash", hash); // Generate the URL and get the token return requestToken(QueryUtil.generateUrl(tokenURL, params)); } /** * Request a token. *

* Exceptions: * - IllegalArgumentException : if url is null or empty * - InvalidTokenRequestException : if the token request is invalid * - InvalidOAuthClientException : if the client information is invalid * - InvalidOAuthGrantException : if the authorization code or refresh token is invalid or * expired, the redirect_uri does not match, or the hash value does not match the client secret and/or code * - UnsupportedOAuthGrantTypeException : if the grant type is invalid * - OAuthTokenException : if any other error occurred during the operation * * @param url the URL (with request parameters) from which the token will be requested * @return the token * @throws OAuthTokenException the o auth token exception * @throws JSONSerializerException the JSON serializer exception * @throws HttpClientException the http client exception * @throws URISyntaxException the URI syntax exception * @throws InvalidRequestException the invalid request exception */ private Token requestToken(String url) throws OAuthTokenException, JSONSerializerException, HttpClientException, URISyntaxException, InvalidRequestException { // Create the request and send it to get the response/token. HttpRequest request = new HttpRequest(); request.setUri(new URI(url)); request.setMethod(HttpMethod.POST); request.setHeaders(new HashMap<>()); request.getHeaders().put("Content-Type", "application/x-www-form-urlencoded"); HttpResponse response = httpClient.request(request); // Create a map of the response InputStream inputStream = response.getEntity().getContent(); Map map = jsonSerializer.deserializeMap(inputStream); httpClient.releaseConnection(); // Check for a error response and throw it. if (response.getStatusCode() != 200 && map.get(ERROR) != null) { String errorType = map.get(ERROR).toString(); String errorDescription = map.get("message") == null ? "" : (String) map.get("message"); if ("invalid_request".equals(errorType)) { throw new InvalidTokenRequestException(errorDescription); } else if ("invalid_client".equals(errorType)) { throw new InvalidOAuthClientException(errorDescription); } else if ("invalid_grant".equals(errorType)) { throw new InvalidOAuthGrantException(errorDescription); } else if ("unsupported_grant_type".equals(errorType)) { throw new UnsupportedOAuthGrantTypeException(errorDescription); } else { throw new OAuthTokenException(errorDescription); } } else if (response.getStatusCode() != 200) { // Another error by not getting a 200 result throw new OAuthTokenException("Token request failed with http error code: " + response.getStatusCode()); } // Create a token based on the response Token token = new Token(); Object tempObj = map.get("access_token"); token.setAccessToken(tempObj == null ? "" : (String) tempObj); tempObj = map.get("token_type"); token.setTokenType(tempObj == null ? "" : (String) tempObj); tempObj = map.get(REFRESH_TOKEN); token.setRefreshToken(tempObj == null ? "" : (String) tempObj); Long expiresIn; try { expiresIn = Long.parseLong(String.valueOf(map.get("expires_in"))); } catch (NumberFormatException nfe) { expiresIn = 0L; } token.setExpiresInSeconds(expiresIn); return token; } /** * Revoke access token. *

* Exceptions: * - IllegalArgumentException : if url is null or empty * - InvalidTokenRequestException : if the token request is invalid * - InvalidOAuthClientException : if the client information is invalid * - InvalidOAuthGrantException : if the authorization code or refresh token is invalid or * expired, the redirect_uri does not match, or the hash value does not match the client secret and/or code * - UnsupportedOAuthGrantTypeException : if the grant type is invalid * - OAuthTokenException : if any other error occurred during the operation * * @param token the access token to revoke access from * @throws OAuthTokenException the o auth token exception * @throws JSONSerializerException the JSON serializer exception * @throws HttpClientException the http client exception * @throws URISyntaxException the URI syntax exception * @throws InvalidRequestException the invalid request exception */ public void revokeAccessToken(Token token) throws OAuthTokenException, JSONSerializerException, HttpClientException, URISyntaxException, InvalidRequestException { HttpRequest request = new HttpRequest(); request.setUri(new URI(tokenURL)); request.setMethod(HttpMethod.DELETE); request.setHeaders(new HashMap<>()); request.getHeaders().put("Authorization", "Bearer " + token.getAccessToken()); HttpResponse response = httpClient.request(request); if (response.getStatusCode() != 200) { throw new OAuthTokenException("Token request failed with http error code: " + response.getStatusCode()); } httpClient.releaseConnection(); } /** * Gets the http client. * * @return the http client */ public HttpClient getHttpClient() { return httpClient; } /** * Sets the http client. * * @param httpClient the new http client */ public void setHttpClient(HttpClient httpClient) { this.httpClient = httpClient; } /** * Gets the json serializer. * * @return the json serializer */ public JsonSerializer getJsonSerializer() { return jsonSerializer; } /** * Sets the json serializer. * * @param jsonSerializer the new json serializer */ public void setJsonSerializer(JsonSerializer jsonSerializer) { this.jsonSerializer = jsonSerializer; } /** * Gets the client id. * * @return the client id */ public String getClientId() { return clientId; } /** * Sets the client id. * * @param clientId the new client id */ public void setClientId(String clientId) { this.clientId = clientId; } /** * Gets the client secret. * * @return the client secret */ public String getClientSecret() { return clientSecret; } /** * Sets the client secret. * * @param clientSecret the new client secret */ public void setClientSecret(String clientSecret) { this.clientSecret = clientSecret; } /** * Gets the redirect url. * * @return the redirect url */ public String getRedirectURL() { return redirectURL; } /** * Sets the redirect url. * * @param redirectURL the new redirect url */ public void setRedirectURL(String redirectURL) { this.redirectURL = redirectURL; } /** * Gets the authorization url. * * @return the authorization url */ public String getAuthorizationURL() { return authorizationURL; } /** * Sets the authorization url. * * @param authorizationURL the new authorization url */ public void setAuthorizationURL(String authorizationURL) { this.authorizationURL = authorizationURL; } /** * Gets the token url. * * @return the token url */ public String getTokenURL() { return tokenURL; } /** * Sets the token url. * * @param tokenURL the new token url */ public void setTokenURL(String tokenURL) { this.tokenURL = tokenURL; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy