package com.wuman.android.auth; import com.google.api.client.auth.oauth.OAuthAuthorizeTemporaryTokenUrl; import com.google.api.client.auth.oauth.OAuthCredentialsResponse; import com.google.api.client.auth.oauth.OAuthGetAccessToken; import com.google.api.client.auth.oauth.OAuthGetTemporaryToken; import com.google.api.client.auth.oauth.OAuthHmacSigner; import com.google.api.client.auth.oauth2.AuthorizationCodeFlow; import com.google.api.client.auth.oauth2.AuthorizationCodeRequestUrl; import com.google.api.client.auth.oauth2.AuthorizationCodeTokenRequest; import com.google.api.client.auth.oauth2.BearerToken; import com.google.api.client.auth.oauth2.BrowserClientRequestUrl; import com.google.api.client.auth.oauth2.ClientParametersAuthentication; import com.google.api.client.auth.oauth2.Credential; import com.google.api.client.auth.oauth2.Credential.AccessMethod; import com.google.api.client.auth.oauth2.CredentialRefreshListener; import com.google.api.client.auth.oauth2.CredentialStore; import com.google.api.client.auth.oauth2.CredentialStoreRefreshListener; import com.google.api.client.auth.oauth2.TokenRequest; import com.google.api.client.auth.oauth2.TokenResponse; import com.google.api.client.http.GenericUrl; import com.google.api.client.http.HttpExecuteInterceptor; import com.google.api.client.http.HttpRequest; import com.google.api.client.http.HttpRequestInitializer; import com.google.api.client.http.HttpTransport; import com.google.api.client.json.JsonFactory; import com.google.api.client.util.Beta; import com.google.api.client.util.Clock; import com.wuman.android.auth.oauth.OAuthHmacCredential; import com.wuman.android.auth.oauth2.explicit.LenientAuthorizationCodeTokenRequest; import com.wuman.android.auth.oauth2.implicit.ImplicitResponseUrl; import java.io.IOException; import java.util.Collection; import java.util.logging.Logger; /** * Thread-safe OAuth 1.0a and 2.0 authorization flow that manages and persists * end-user credentials. Both explicit authorization and implicit authorization * of OAuth 2.0 are supported. * <p> * This is designed to simplify the flow in which an end-user authorizes the * application to access their protected data, and then the application has * access to their data based on an access token and a refresh token to refresh * that access token when it expires. * </p> * <p> * The first step is to call {@link #loadCredential(String)} based on the known * user ID to check if the end-user's credentials are already known. If not, * call {@link #newExplicitAuthorizationUrl()} (or * {@link #newImplicitAuthorizationUrl()}) and direct the end-user's browser to * an authorization page. If explicit authorization is used, the web browser * will then redirect to the redirect URL with a {@code "code"} query parameter * which can then be used to request an access token using * {@link #newTokenRequest(String)}; If implicit authorization is used, the web * browser will then redirect to the redirect URL with a {@code "access_token"} * fragment. The implicit redirect URL is returned as * {@link ImplicitResponseUrl}. Finally, use * {@link #createAndStoreCredential(TokenResponse, String)} or * {@link #createAndStoreCredential(ImplicitResponseUrl, String)} to store and * obtain a credential for accessing protected resources. * </p> * * @author David Wu */ public class AuthorizationFlow extends AuthorizationCodeFlow { static final Logger LOGGER = Logger.getLogger(OAuthConstants.TAG); /** Credential created listener or {@code null} for none. */ private final CredentialCreatedListener credentialCreatedListener; /** Temporary token request URL */ private String temporaryTokenRequestUrl; /** * Listener for a created credential after a successful token response in * {@link AuthorizationFlow#createAndStoreCredential(OAuthCredentialsResponse, String)} * , * {@link AuthorizationFlow#createAndStoreCredential(TokenResponse, String)} * or * {@link AuthorizationFlow#createAndStoreCredential(ImplicitResponseUrl, String)} * . . */ public interface CredentialCreatedListener extends com.google.api.client.auth.oauth2.AuthorizationCodeFlow.CredentialCreatedListener { /** * Notifies of a created credential after a successful token response in * {@link AuthorizationFlow#createAndStoreCredential(ImplicitResponseUrl, String)} * . * <p> * Typical use is to parse additional fields from the credential * created, such as an ID token. * </p> * * @param credential created credential * @param implicitResponse successful implicit response URL */ void onCredentialCreated(Credential credential, ImplicitResponseUrl implicitResponse) throws IOException; /** * Notifies of a created credential after a successful token response in * {@link AuthorizationFlow#createAndStoreCredential(OAuthCredentialsResponse, String)} * * @param credential * @param oauth10aResponse * @throws IOException */ void onCredentialCreated(Credential credential, OAuthCredentialsResponse oauth10aResponse) throws IOException; } AuthorizationFlow(Builder builder) { super(builder); credentialCreatedListener = builder.getGeneralCredentialCreatedListener(); temporaryTokenRequestUrl = builder.getTemporaryTokenRequestUrl(); } /** * Returns the Request Token URL in OAuth 1.0a. * * @return */ public final String getTemporaryTokenRequestUrl() { return temporaryTokenRequestUrl; } /** * Loads the OAuth 1.0a credential of the given user ID from the credential * store. * * @param userId user ID or {@code null} if not using a persisted credential * store * @return OAuth 1.0a credential found in the credential store of the given * user ID or {@code null} for none found */ public OAuthHmacCredential load10aCredential(String userId) throws IOException { if (getCredentialStore() == null) { return null; } OAuthHmacCredential credential = new10aCredential(userId); if (!getCredentialStore().load(userId, credential)) { return null; } return credential; } /** * Returns the response of a Request Token request as defined in <a * href="http://oauth.net/core/1.0a/#auth_step1">Obtaining an Unauthorized * Request Token</a>. * * @param redirectUri the {@code oauth_callback} as defined in <a * href="http://oauth.net/core/1.0a/#rfc.section.6.1.1">Consumer * Obtains a Request Token</a> * @return * @throws IOException */ public OAuthCredentialsResponse new10aTemporaryTokenRequest(String redirectUri) throws IOException { OAuthGetTemporaryToken temporaryToken = new OAuthGetTemporaryToken(getTemporaryTokenRequestUrl()); OAuthHmacSigner signer = new OAuthHmacSigner(); ClientParametersAuthentication clientAuthentication = (ClientParametersAuthentication) getClientAuthentication(); signer.clientSharedSecret = clientAuthentication.getClientSecret(); temporaryToken.signer = signer; temporaryToken.consumerKey = clientAuthentication.getClientId(); temporaryToken.callback = redirectUri; temporaryToken.transport = getTransport(); return temporaryToken.execute(); } /** * Returns a new instance of a temporary token authorization request URL as * defined in <a * href="http://oauth.net/core/1.0a/#rfc.section.6.2.1">Consumer Directs the * User to the Service Provider</a>. * * @param temporaryToken * @return */ public OAuthAuthorizeTemporaryTokenUrl new10aAuthorizationUrl(String temporaryToken) { OAuthAuthorizeTemporaryTokenUrl authorizationUrl = new OAuthAuthorizeTemporaryTokenUrl(getAuthorizationServerEncodedUrl()); authorizationUrl.temporaryToken = temporaryToken; return authorizationUrl; } /** * Returns a new instance of a token request based on the given verifier * code. This step is defined in <a * href="http://oauth.net/core/1.0a/#auth_step3">Obtaining an Access * Token</a>. * * @param temporaryCredentials * @param verifierCode * @return */ public OAuthGetAccessToken new10aTokenRequest(OAuthCredentialsResponse temporaryCredentials, String verifierCode) { OAuthGetAccessToken request = new OAuthGetAccessToken(getTokenServerEncodedUrl()); request.temporaryToken = temporaryCredentials.token; request.transport = getTransport(); OAuthHmacSigner signer = new OAuthHmacSigner(); ClientParametersAuthentication clientAuthentication = (ClientParametersAuthentication) getClientAuthentication(); signer.clientSharedSecret = clientAuthentication.getClientSecret(); signer.tokenSharedSecret = temporaryCredentials.tokenSecret; request.signer = signer; request.consumerKey = clientAuthentication.getClientId(); request.verifier = verifierCode; return request; } @Override public AuthorizationCodeTokenRequest newTokenRequest(String authorizationCode) { return new LenientAuthorizationCodeTokenRequest(getTransport(), getJsonFactory(), new GenericUrl(getTokenServerEncodedUrl()), authorizationCode) .setClientAuthentication(getClientAuthentication()) .setScopes(getScopes()) .setRequestInitializer( new HttpRequestInitializer() { @Override public void initialize(HttpRequest request) throws IOException { HttpRequestInitializer requestInitializer = getRequestInitializer(); // If HttpRequestInitializer is set, initialize it as before if (requestInitializer != null) { requestInitializer.initialize(request); } // Also set JSON accept header request.getHeaders().setAccept("application/json"); } }); } /** * Returns a new instance of an explicit authorization code request URL. * <p> * This is a builder for an authorization web page to allow the end user to * authorize the application to access their protected resources and that * returns an authorization code. It uses the * {@link #getAuthorizationServerEncodedUrl()}, {@link #getClientId()}, and * {@link #getScopes()}. Sample usage: * </p> * * <pre> * private AuthorizationFlow flow; * * public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { * String url = flow.newExplicitAuthorizationUrl().setState("xyz") * .setRedirectUri("https://client.example.com/rd").build(); * response.sendRedirect(url); * } * </pre> */ public AuthorizationCodeRequestUrl newExplicitAuthorizationUrl() { return newAuthorizationUrl(); } /** * Returns a new instance of an implicit authorization request URL. * <p> * This is a builder for an authorization web page to allow the end user to * authorize the application to access their protected resources and that * returns an access token. It uses the * {@link #getAuthorizationServerEncodedUrl()}, {@link #getClientId()}, and * {@link #getScopes()}. Sample usage: * </p> * * <pre> * private AuthorizationFlow flow; * * public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { * String url = flow.newImplicitAuthorizationUrl().setState("xyz") * .setRedirectUri("https://client.example.com/rd").build(); * response.sendRedirect(url); * } * </pre> */ public BrowserClientRequestUrl newImplicitAuthorizationUrl() { return new BrowserClientRequestUrl(getAuthorizationServerEncodedUrl(), getClientId()) .setScopes(getScopes()); } /** * Creates a new credential for the given user ID based on the given token * response and store in the credential store. * * @param response OAuth 1.0a authorization token response * @param userId user ID or {@code null} if not using a persisted credential * store * @return newly created credential * @throws IOException */ public OAuthHmacCredential createAndStoreCredential(OAuthCredentialsResponse response, String userId) throws IOException { OAuthHmacCredential credential = new10aCredential(userId) .setAccessToken(response.token) .setTokenSharedSecret(response.tokenSecret); CredentialStore credentialStore = getCredentialStore(); if (credentialStore != null) { credentialStore.store(userId, credential); } if (credentialCreatedListener != null) { credentialCreatedListener.onCredentialCreated(credential, response); } return credential; } /** * Creates a new credential for the given user ID based on the given token * response and store in the credential store. * * @param response implicit authorization token response * @param userId user ID or {@code null} if not using a persisted credential * store * @return newly created credential * @throws IOException */ public Credential createAndStoreCredential(ImplicitResponseUrl implicitResponse, String userId) throws IOException { Credential credential = newCredential(userId) .setAccessToken(implicitResponse.getAccessToken()) .setExpiresInSeconds(implicitResponse.getExpiresInSeconds()); CredentialStore credentialStore = getCredentialStore(); if (credentialStore != null) { credentialStore.store(userId, credential); } if (credentialCreatedListener != null) { credentialCreatedListener.onCredentialCreated(credential, implicitResponse); } return credential; } /** * Returns a new OAuth 1.0a credential instance based on the given user ID. * * @param userId user ID or {@code null} if not using a persisted credential * store */ private OAuthHmacCredential new10aCredential(String userId) { ClientParametersAuthentication clientAuthentication = (ClientParametersAuthentication) getClientAuthentication(); OAuthHmacCredential.Builder builder = new OAuthHmacCredential.Builder(getMethod(), clientAuthentication.getClientId(), clientAuthentication.getClientSecret()) .setTransport(getTransport()) .setJsonFactory(getJsonFactory()) .setTokenServerEncodedUrl(getTokenServerEncodedUrl()) .setClientAuthentication(getClientAuthentication()) .setRequestInitializer(getRequestInitializer()) .setClock(getClock()); if (getCredentialStore() != null) { builder.addRefreshListener( new CredentialStoreRefreshListener(userId, getCredentialStore())); } builder.getRefreshListeners().addAll(getRefreshListeners()); return builder.build(); } /** * Returns a new OAuth 2.0 credential instance based on the given user ID. * * @param userId user ID or {@code null} if not using a persisted credential * store */ private Credential newCredential(String userId) { Credential.Builder builder = new Credential.Builder(getMethod()) .setTransport(getTransport()) .setJsonFactory(getJsonFactory()) .setTokenServerEncodedUrl(getTokenServerEncodedUrl()) .setClientAuthentication(getClientAuthentication()) .setRequestInitializer(getRequestInitializer()) .setClock(getClock()); if (getCredentialStore() != null) { builder.addRefreshListener( new CredentialStoreRefreshListener(userId, getCredentialStore())); } builder.getRefreshListeners().addAll(getRefreshListeners()); return builder.build(); } /** * Authorization flow builder. * <p> * Implementation is not thread-safe. * </p> */ public static class Builder extends com.google.api.client.auth.oauth2.AuthorizationCodeFlow.Builder { /** Credential created listener or {@code null} for none. */ CredentialCreatedListener credentialCreatedListener; /** Temporary token request URL */ String temporaryTokenRequestUrl; /** * @param method method of presenting the access token to the resource * server (for example * {@link BearerToken#authorizationHeaderAccessMethod}) * @param transport HTTP transport * @param jsonFactory JSON factory * @param tokenServerUrl token server URL * @param clientAuthentication client authentication or {@code null} for * none (see * {@link TokenRequest#setClientAuthentication(HttpExecuteInterceptor)} * ) * @param clientId client identifier * @param authorizationServerEncodedUrl authorization server encoded URL */ public Builder(AccessMethod method, HttpTransport transport, JsonFactory jsonFactory, GenericUrl tokenServerUrl, HttpExecuteInterceptor clientAuthentication, String clientId, String authorizationServerEncodedUrl) { super(method, transport, jsonFactory, tokenServerUrl, clientAuthentication, clientId, authorizationServerEncodedUrl); } /** * Returns a new instance of an authorization flow based on this * builder. */ public AuthorizationFlow build() { return new AuthorizationFlow(this); } /** * Sets the temporary token request URL. * * @param temporaryTokenRequestUrl * @return */ public Builder setTemporaryTokenRequestUrl(String temporaryTokenRequestUrl) { this.temporaryTokenRequestUrl = temporaryTokenRequestUrl; return this; } /** * Returns the temporary token request URL. * * @return */ public String getTemporaryTokenRequestUrl() { return temporaryTokenRequestUrl; } @Override public Builder setMethod(AccessMethod method) { return (Builder) super.setMethod(method); } @Override public Builder setTransport(HttpTransport transport) { return (Builder) super.setTransport(transport); } @Override public Builder setJsonFactory(JsonFactory jsonFactory) { return (Builder) super.setJsonFactory(jsonFactory); } @Override public Builder setTokenServerUrl(GenericUrl tokenServerUrl) { return (Builder) super.setTokenServerUrl(tokenServerUrl); } @Override public Builder setClientAuthentication(HttpExecuteInterceptor clientAuthentication) { return (Builder) super.setClientAuthentication(clientAuthentication); } @Override public Builder setClientId(String clientId) { return (Builder) super.setClientId(clientId); } @Override public Builder setAuthorizationServerEncodedUrl(String authorizationServerEncodedUrl) { return (Builder) super.setAuthorizationServerEncodedUrl(authorizationServerEncodedUrl); } @Override public Builder setClock(Clock clock) { return (Builder) super.setClock(clock); } @Beta @Override public Builder setCredentialStore(CredentialStore credentialStore) { return (Builder) super.setCredentialStore(credentialStore); } @Override public Builder setRequestInitializer(HttpRequestInitializer requestInitializer) { return (Builder) super.setRequestInitializer(requestInitializer); } @Beta @Deprecated @Override public Builder setScopes(Iterable<String> scopes) { return (Builder) super.setScopes(scopes); } @Beta @Deprecated @Override public Builder setScopes(String... scopes) { return (Builder) super.setScopes(scopes); } @Override public Builder setScopes(Collection<String> scopes) { return (Builder) super.setScopes(scopes); } /** * Sets the credential created listener or {@code null} for none. * * <p> * Overriding is only supported for the purpose of calling the super * implementation and changing the return type, but nothing else. * </p> */ public Builder setCredentialCreatedListener( CredentialCreatedListener credentialCreatedListener) { this.credentialCreatedListener = credentialCreatedListener; return (Builder) super.setCredentialCreatedListener(credentialCreatedListener); } /** * Returns the credential created listener or {@code null} for none. */ public final CredentialCreatedListener getGeneralCredentialCreatedListener() { return credentialCreatedListener; } @Override public Builder addRefreshListener(CredentialRefreshListener refreshListener) { return (Builder) super.addRefreshListener(refreshListener); } @Override public Builder setRefreshListeners(Collection<CredentialRefreshListener> refreshListeners) { return (Builder) super.setRefreshListeners(refreshListeners); } } }