package com.vaporwarecorp.mirror.component.oauth; import com.google.api.client.auth.oauth2.AuthorizationCodeFlow; import com.google.api.client.auth.oauth2.BearerToken; import com.google.api.client.auth.oauth2.BrowserClientRequestUrl; 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.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.vaporwarecorp.mirror.component.oauth.implicit.ImplicitResponseUrl; import java.io.IOException; import java.util.Collection; /** * 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 #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 { // ------------------------------ FIELDS ------------------------------ /** * Credential created listener or {@code null} for none. */ private final CredentialCreatedListener credentialCreatedListener; // --------------------------- CONSTRUCTORS --------------------------- AuthorizationFlow(Builder builder) { super(builder); credentialCreatedListener = builder.getGeneralCredentialCreatedListener(); } // -------------------------- OTHER METHODS -------------------------- /** * Creates a new credential for the given user ID based on the given token * response and store in the credential store. * * @param implicitResponse 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 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> * <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()); } /** * 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(); } // -------------------------- INNER CLASSES -------------------------- /** * 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; /** * @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); } @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); } @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); } } /** * Listener for a created credential after a successful token response in * {@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; } }