com.smartsheet.api.internal.oauth.OAuthFlowImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of smartsheet-sdk-java Show documentation
Show all versions of smartsheet-sdk-java Show documentation
Library for connecting to Smartsheet Services
package com.smartsheet.api.internal.oauth;
/*
* #[license]
* Smartsheet SDK for Java
* %%
* Copyright (C) 2014 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.
* %[license]
*/
import com.smartsheet.api.InvalidRequestException;
import com.smartsheet.api.internal.http.*;
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.*;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
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;
/**
* Constructor.
*
* Exceptions: -
*
* @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
* @throws UnsupportedEncodingException the unsupported encoding exception
*/
public String newAuthorizationURL(EnumSet scopes, String state) {
Util.throwIfNull(scopes);
if(state == null){state = "";}
// Build a map of parameters for the URL
HashMap 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 NoSuchAlgorithmException the no such algorithm exception
* @throws UnsupportedEncodingException the unsupported encoding exception
* @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;
try {
digest = md.digest(doHash.getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
//String hash = javax.xml.bind.DatatypeConverter.printHexBinary(digest);
String hash = org.apache.commons.codec.binary.Hex.encodeHexString(digest);
// create a Map of the parameters
HashMap 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 NoSuchAlgorithmException the no such algorithm exception
* @throws UnsupportedEncodingException the unsupported encoding exception
* @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;
try {
digest = md.digest(doHash.getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
//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);
}
}
// Another error by not getting a 200 result
else if(response.getStatusCode() != 200){
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;
}
}