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

com.rt.storage.auth.oauth2.UserAuthorizer Maven / Gradle / Ivy

package com.rt.storage.auth.oauth2;

import com.rt.storage.api.client.http.GenericUrl;
import com.rt.storage.api.client.http.HttpRequest;
import com.rt.storage.api.client.http.HttpRequestFactory;
import com.rt.storage.api.client.http.HttpResponse;
import com.rt.storage.api.client.http.UrlEncodedContent;
import com.rt.storage.api.client.json.GenericJson;
import com.rt.storage.api.client.json.JsonObjectParser;
import com.rt.storage.api.client.util.GenericData;
import com.rt.storage.api.client.util.Joiner;
import com.rt.storage.api.client.util.Preconditions;
import com.rt.storage.auth.http.HttpTransportFactory;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.util.Collection;
import java.util.Date;

/** Handles an interactive 3-Legged-OAuth2 (3LO) user consent authorization. */
public class UserAuthorizer {

  static final URI DEFAULT_CALLBACK_URI = URI.create("/oauth2callback");

  private final String TOKEN_STORE_ERROR = "Error parsing stored token data.";
  private final String FETCH_TOKEN_ERROR = "Error reading result of Token API:";

  private final ClientId clientId;
  private final Collection scopes;
  private final TokenStore tokenStore;
  private final URI callbackUri;

  private final HttpTransportFactory transportFactory;
  private final URI tokenServerUri;
  private final URI userAuthUri;

  /**
   * Constructor with all parameters.
   *
   * @param clientId Client ID to identify the OAuth2 consent prompt
   * @param scopes OAuth2 scopes defining the user consent
   * @param tokenStore Implementation of a component for long term storage of tokens
   * @param callbackUri URI for implementation of the OAuth2 web callback
   * @param transportFactory HTTP transport factory, creates the transport used to get access
   *     tokens.
   * @param tokenServerUri URI of the end point that provides tokens
   * @param userAuthUri URI of the Web UI for user consent
   */
  private UserAuthorizer(
      ClientId clientId,
      Collection scopes,
      TokenStore tokenStore,
      URI callbackUri,
      HttpTransportFactory transportFactory,
      URI tokenServerUri,
      URI userAuthUri) {
    this.clientId = Preconditions.checkNotNull(clientId);
    this.scopes = ImmutableList.copyOf(Preconditions.checkNotNull(scopes));
    this.callbackUri = (callbackUri == null) ? DEFAULT_CALLBACK_URI : callbackUri;
    this.transportFactory =
        (transportFactory == null) ? OAuth2Utils.HTTP_TRANSPORT_FACTORY : transportFactory;
    this.tokenServerUri = (tokenServerUri == null) ? OAuth2Utils.TOKEN_SERVER_URI : tokenServerUri;
    this.userAuthUri = (userAuthUri == null) ? OAuth2Utils.USER_AUTH_URI : userAuthUri;
    this.tokenStore = (tokenStore == null) ? new MemoryTokensStorage() : tokenStore;
  }

  /**
   * Returns the Client ID user to identify the OAuth2 consent prompt.
   *
   * @return The Client ID.
   */
  public ClientId getClientId() {
    return clientId;
  }

  /**
   * Returns the scopes defining the user consent.
   *
   * @return The collection of scopes defining the user consent.
   */
  public Collection getScopes() {
    return scopes;
  }

  /**
   * Returns the URI for implementation of the OAuth2 web callback.
   *
   * @return The URI for the OAuth2 web callback.
   */
  public URI getCallbackUri() {
    return callbackUri;
  }

  /**
   * Returns the URI for implementation of the OAuth2 web callback, optionally relative to the
   * specified URI.
   *
   * 

The callback URI is often relative to enable an application to be tested from more than one * place so this can be used to resolve it relative to another URI. * * @param baseUri The URI to resolve the callback URI relative to. * @return The resolved URI. */ public URI getCallbackUri(URI baseUri) { if (callbackUri.isAbsolute()) { return callbackUri; } if (baseUri == null || !baseUri.isAbsolute()) { throw new IllegalStateException( "If the callback URI is relative, the baseUri passed must" + " be an absolute URI"); } return baseUri.resolve(callbackUri); } /** * Returns the implementation of a component for long term storage of tokens. * * @return The token storage implementation for long term storage of tokens. */ public TokenStore getTokenStore() { return tokenStore; } /** * Return an URL that performs the authorization consent prompt web UI. * * @param userId Application's identifier for the end user. * @param state State that is passed on to the OAuth2 callback URI after the consent. * @param baseUri The URI to resolve the OAuth2 callback URI relative to. * @return The URL that can be navigated or redirected to. */ public URL getAuthorizationUrl(String userId, String state, URI baseUri) { URI resolvedCallbackUri = getCallbackUri(baseUri); String scopesString = Joiner.on(' ').join(scopes); GenericUrl url = new GenericUrl(userAuthUri); url.put("response_type", "code"); url.put("client_id", clientId.getClientId()); url.put("redirect_uri", resolvedCallbackUri); url.put("scope", scopesString); if (state != null) { url.put("state", state); } url.put("access_type", "offline"); url.put("approval_prompt", "force"); if (userId != null) { url.put("login_hint", userId); } url.put("include_granted_scopes", true); return url.toURL(); } /** * Attempts to retrieve credentials for the approved end user consent. * * @param userId Application's identifier for the end user. * @return The loaded credentials or null if there are no valid approved credentials. * @throws IOException If there is error retrieving or loading the credentials. */ public UserCredentials getCredentials(String userId) throws IOException { Preconditions.checkNotNull(userId); if (tokenStore == null) { throw new IllegalStateException("Method cannot be called if token store is not specified."); } String tokenData = tokenStore.load(userId); if (tokenData == null) { return null; } GenericJson tokenJson = OAuth2Utils.parseJson(tokenData); String accessTokenValue = OAuth2Utils.validateString(tokenJson, "access_token", TOKEN_STORE_ERROR); Long expirationMillis = OAuth2Utils.validateLong(tokenJson, "expiration_time_millis", TOKEN_STORE_ERROR); Date expirationTime = new Date(expirationMillis); AccessToken accessToken = new AccessToken(accessTokenValue, expirationTime); String refreshToken = OAuth2Utils.validateOptionalString(tokenJson, "refresh_token", TOKEN_STORE_ERROR); UserCredentials credentials = UserCredentials.newBuilder() .setClientId(clientId.getClientId()) .setClientSecret(clientId.getClientSecret()) .setRefreshToken(refreshToken) .setAccessToken(accessToken) .setHttpTransportFactory(transportFactory) .setTokenServerUri(tokenServerUri) .build(); monitorCredentials(userId, credentials); return credentials; } /** * Returns a UserCredentials instance by exchanging an OAuth2 authorization code for tokens. * * @param code Code returned from OAuth2 consent prompt. * @param baseUri The URI to resolve the OAuth2 callback URI relative to. * @return the UserCredentials instance created from the authorization code. * @throws IOException An error from the server API call to get the tokens. */ public UserCredentials getCredentialsFromCode(String code, URI baseUri) throws IOException { Preconditions.checkNotNull(code); URI resolvedCallbackUri = getCallbackUri(baseUri); GenericData tokenData = new GenericData(); tokenData.put("code", code); tokenData.put("client_id", clientId.getClientId()); tokenData.put("client_secret", clientId.getClientSecret()); tokenData.put("redirect_uri", resolvedCallbackUri); tokenData.put("grant_type", "authorization_code"); UrlEncodedContent tokenContent = new UrlEncodedContent(tokenData); HttpRequestFactory requestFactory = transportFactory.create().createRequestFactory(); HttpRequest tokenRequest = requestFactory.buildPostRequest(new GenericUrl(tokenServerUri), tokenContent); tokenRequest.setParser(new JsonObjectParser(OAuth2Utils.JSON_FACTORY)); HttpResponse tokenResponse = tokenRequest.execute(); GenericJson parsedTokens = tokenResponse.parseAs(GenericJson.class); String accessTokenValue = OAuth2Utils.validateString(parsedTokens, "access_token", FETCH_TOKEN_ERROR); int expiresInSecs = OAuth2Utils.validateInt32(parsedTokens, "expires_in", FETCH_TOKEN_ERROR); Date expirationTime = new Date(new Date().getTime() + expiresInSecs * 1000); AccessToken accessToken = new AccessToken(accessTokenValue, expirationTime); String refreshToken = OAuth2Utils.validateOptionalString(parsedTokens, "refresh_token", FETCH_TOKEN_ERROR); return UserCredentials.newBuilder() .setClientId(clientId.getClientId()) .setClientSecret(clientId.getClientSecret()) .setRefreshToken(refreshToken) .setAccessToken(accessToken) .setHttpTransportFactory(transportFactory) .setTokenServerUri(tokenServerUri) .build(); } /** * Exchanges an authorization code for tokens and stores them. * * @param userId Application's identifier for the end user. * @param code Code returned from OAuth2 consent prompt. * @param baseUri The URI to resolve the OAuth2 callback URI relative to. * @return UserCredentials instance created from the authorization code. * @throws IOException An error from the server API call to get the tokens or store the tokens. */ public UserCredentials getAndStoreCredentialsFromCode(String userId, String code, URI baseUri) throws IOException { Preconditions.checkNotNull(userId); Preconditions.checkNotNull(code); UserCredentials credentials = getCredentialsFromCode(code, baseUri); storeCredentials(userId, credentials); monitorCredentials(userId, credentials); return credentials; } /** * Revokes the authorization for tokens stored for the user. * * @param userId Application's identifier for the end user. * @throws IOException An error calling the revoke API or deleting the state. */ public void revokeAuthorization(String userId) throws IOException { Preconditions.checkNotNull(userId); if (tokenStore == null) { throw new IllegalStateException("Method cannot be called if token store is not specified."); } String tokenData = tokenStore.load(userId); if (tokenData == null) { return; } IOException deleteTokenException = null; try { // Delete the stored version first. If token reversion fails it is less harmful to have an // non revoked token to hold on to a potentially revoked token. tokenStore.delete(userId); } catch (IOException e) { deleteTokenException = e; } GenericJson tokenJson = OAuth2Utils.parseJson(tokenData); String accessTokenValue = OAuth2Utils.validateOptionalString(tokenJson, "access_token", TOKEN_STORE_ERROR); String refreshToken = OAuth2Utils.validateOptionalString(tokenJson, "refresh_token", TOKEN_STORE_ERROR); // If both tokens are present, either can be used String revokeToken = (refreshToken != null) ? refreshToken : accessTokenValue; GenericUrl revokeUrl = new GenericUrl(OAuth2Utils.TOKEN_REVOKE_URI); revokeUrl.put("token", revokeToken); HttpRequestFactory requestFactory = transportFactory.create().createRequestFactory(); HttpRequest tokenRequest = requestFactory.buildGetRequest(revokeUrl); tokenRequest.execute(); if (deleteTokenException != null) { throw deleteTokenException; } } /** * Puts the end user credentials in long term storage. * * @param userId Application's identifier for the end user. * @param credentials UserCredentials instance for the authorized consent. * @throws IOException An error storing the credentials. */ public void storeCredentials(String userId, UserCredentials credentials) throws IOException { if (tokenStore == null) { throw new IllegalStateException("Cannot store tokens if tokenStore is not specified."); } AccessToken accessToken = credentials.getAccessToken(); String acessTokenValue = null; Date expiresBy = null; if (accessToken != null) { acessTokenValue = accessToken.getTokenValue(); expiresBy = accessToken.getExpirationTime(); } String refreshToken = credentials.getRefreshToken(); GenericJson tokenStateJson = new GenericJson(); tokenStateJson.setFactory(OAuth2Utils.JSON_FACTORY); tokenStateJson.put("access_token", acessTokenValue); tokenStateJson.put("expiration_time_millis", expiresBy.getTime()); if (refreshToken != null) { tokenStateJson.put("refresh_token", refreshToken); } String tokenState = tokenStateJson.toString(); tokenStore.store(userId, tokenState); } /** * Adds a listen to rewrite the credentials when the tokens are refreshed. * * @param userId Application's identifier for the end user. * @param credentials UserCredentials instance to listen to. */ protected void monitorCredentials(String userId, UserCredentials credentials) { credentials.addChangeListener(new UserCredentialsListener(userId)); } /** * Implementation of listener used by monitorCredentials to rewrite the credentials when the * tokens are refreshed. */ private class UserCredentialsListener implements OAuth2Credentials.CredentialsChangedListener { private final String userId; /** Construct new listener. */ public UserCredentialsListener(String userId) { this.userId = userId; } /** Handle change event by rewriting to token store. */ @Override public void onChanged(OAuth2Credentials credentials) throws IOException { UserCredentials userCredentials = (UserCredentials) credentials; storeCredentials(userId, userCredentials); } } public static Builder newBuilder() { return new Builder(); } public Builder toBuilder() { return new Builder(this); } public static class Builder { private ClientId clientId; private TokenStore tokenStore; private URI callbackUri; private URI tokenServerUri; private URI userAuthUri; private Collection scopes; private HttpTransportFactory transportFactory; protected Builder() {} protected Builder(UserAuthorizer authorizer) { this.clientId = authorizer.clientId; this.scopes = authorizer.scopes; this.transportFactory = authorizer.transportFactory; this.tokenServerUri = authorizer.tokenServerUri; this.tokenStore = authorizer.tokenStore; this.callbackUri = authorizer.callbackUri; this.userAuthUri = authorizer.userAuthUri; } public Builder setClientId(ClientId clientId) { this.clientId = clientId; return this; } public Builder setTokenStore(TokenStore tokenStore) { this.tokenStore = tokenStore; return this; } public Builder setScopes(Collection scopes) { this.scopes = scopes; return this; } public Builder setTokenServerUri(URI tokenServerUri) { this.tokenServerUri = tokenServerUri; return this; } public Builder setCallbackUri(URI callbackUri) { this.callbackUri = callbackUri; return this; } public Builder setUserAuthUri(URI userAuthUri) { this.userAuthUri = userAuthUri; return this; } public Builder setHttpTransportFactory(HttpTransportFactory transportFactory) { this.transportFactory = transportFactory; return this; } public ClientId getClientId() { return clientId; } public TokenStore getTokenStore() { return tokenStore; } public Collection getScopes() { return scopes; } public URI getTokenServerUri() { return tokenServerUri; } public URI getCallbackUri() { return callbackUri; } public URI getUserAuthUri() { return userAuthUri; } public HttpTransportFactory getHttpTransportFactory() { return transportFactory; } public UserAuthorizer build() { return new UserAuthorizer( clientId, scopes, tokenStore, callbackUri, transportFactory, tokenServerUri, userAuthUri); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy