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

com.google.api.client.auth.oauth2.AuthorizationCodeFlow Maven / Gradle / Ivy

Go to download

Google OAuth Client Library for Java. Functionality that works on all supported Java platforms, including Java 5 (or higher) desktop (SE) and web (EE), Android, and Google App Engine.

There is a newer version: 1.36.0
Show newest version
/*
 * Copyright (c) 2012 Google Inc.
 *
 * 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.google.api.client.auth.oauth2;

import com.google.api.client.auth.oauth2.Credential.AccessMethod;
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.http.UrlEncodedContent;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.util.Base64;
import com.google.api.client.util.Beta;
import com.google.api.client.util.Data;
import com.google.api.client.util.Clock;
import com.google.api.client.util.Joiner;
import com.google.api.client.util.Lists;
import com.google.api.client.util.Preconditions;
import com.google.api.client.util.store.DataStore;
import com.google.api.client.util.store.DataStoreFactory;

import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;

import static com.google.api.client.util.Strings.isNullOrEmpty;

/**
 * Thread-safe OAuth 2.0 authorization code flow that manages and persists end-user credentials.
 *
 * 

* 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. *

* *

* 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 #newAuthorizationUrl()} and * direct the end-user's browser to an authorization page. 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)}. Finally, use * {@link #createAndStoreCredential(TokenResponse, String)} to store and obtain a credential for * accessing protected resources. *

* * @since 1.7 * @author Yaniv Inbar */ public class AuthorizationCodeFlow { /** * Method of presenting the access token to the resource server (for example * {@link BearerToken#authorizationHeaderAccessMethod}). */ private final AccessMethod method; /** HTTP transport. */ private final HttpTransport transport; /** JSON factory. */ private final JsonFactory jsonFactory; /** Token server encoded URL. */ private final String tokenServerEncodedUrl; /** * Client authentication or {@code null} for none (see * {@link TokenRequest#setClientAuthentication(HttpExecuteInterceptor)}). */ private final HttpExecuteInterceptor clientAuthentication; /** Client identifier. */ private final String clientId; /** Authorization server encoded URL. */ private final String authorizationServerEncodedUrl; /** The Proof Key for Code Exchange (PKCE) or {@code null} if this flow should not use PKCE. */ private final PKCE pkce; /** Credential persistence store or {@code null} for none. */ @Beta @Deprecated private final CredentialStore credentialStore; /** Stored credential data store or {@code null} for none. */ @Beta private final DataStore credentialDataStore; /** HTTP request initializer or {@code null} for none. */ private final HttpRequestInitializer requestInitializer; /** Clock passed along to Credential. */ private final Clock clock; /** Collection of scopes. */ private final Collection scopes; /** Credential created listener or {@code null} for none. */ private final CredentialCreatedListener credentialCreatedListener; /** Refresh listeners provided by the client. */ private final Collection refreshListeners; /** * @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 * * @since 1.14 */ public AuthorizationCodeFlow(AccessMethod method, HttpTransport transport, JsonFactory jsonFactory, GenericUrl tokenServerUrl, HttpExecuteInterceptor clientAuthentication, String clientId, String authorizationServerEncodedUrl) { this(new Builder(method, transport, jsonFactory, tokenServerUrl, clientAuthentication, clientId, authorizationServerEncodedUrl)); } /** * @param builder authorization code flow builder * * @since 1.14 */ protected AuthorizationCodeFlow(Builder builder) { method = Preconditions.checkNotNull(builder.method); transport = Preconditions.checkNotNull(builder.transport); jsonFactory = Preconditions.checkNotNull(builder.jsonFactory); tokenServerEncodedUrl = Preconditions.checkNotNull(builder.tokenServerUrl).build(); clientAuthentication = builder.clientAuthentication; clientId = Preconditions.checkNotNull(builder.clientId); authorizationServerEncodedUrl = Preconditions.checkNotNull(builder.authorizationServerEncodedUrl); requestInitializer = builder.requestInitializer; credentialStore = builder.credentialStore; credentialDataStore = builder.credentialDataStore; scopes = Collections.unmodifiableCollection(builder.scopes); clock = Preconditions.checkNotNull(builder.clock); credentialCreatedListener = builder.credentialCreatedListener; refreshListeners = Collections.unmodifiableCollection(builder.refreshListeners); pkce = builder.pkce; } /** * Returns a new instance of an authorization code request URL. * *

* 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: *

* *
  private AuthorizationCodeFlow flow;

  public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
    String url = flow.newAuthorizationUrl().setState("xyz")
        .setRedirectUri("https://client.example.com/rd").build();
    response.sendRedirect(url);
  }
   * 
*/ public AuthorizationCodeRequestUrl newAuthorizationUrl() { AuthorizationCodeRequestUrl url = new AuthorizationCodeRequestUrl(authorizationServerEncodedUrl, clientId); url.setScopes(scopes); if (pkce != null) { url.setCodeChallenge(pkce.getChallenge()); url.setCodeChallengeMethod(pkce.getChallengeMethod()); } return url; } /** * Returns a new instance of an authorization code token request based on the given authorization * code. * *

* This is used to make a request for an access token using the authorization code. It uses * {@link #getTransport()}, {@link #getJsonFactory()}, {@link #getTokenServerEncodedUrl()}, * {@link #getClientAuthentication()}, {@link #getRequestInitializer()}, and {@link #getScopes()}. *

* *
  static TokenResponse requestAccessToken(AuthorizationCodeFlow flow, String code)
      throws IOException, TokenResponseException {
    return flow.newTokenRequest(code).setRedirectUri("https://client.example.com/rd").execute();
  }
   * 
* * @param authorizationCode authorization code. */ public AuthorizationCodeTokenRequest newTokenRequest(String authorizationCode) { HttpExecuteInterceptor pkceClientAuthenticationWrapper = new HttpExecuteInterceptor() { @Override public void intercept(HttpRequest request) throws IOException { clientAuthentication.intercept(request); if (pkce != null) { Map data = Data.mapOf(UrlEncodedContent.getContent(request).getData()); data.put("code_verifier", pkce.getVerifier()); } } }; return new AuthorizationCodeTokenRequest(transport, jsonFactory, new GenericUrl(tokenServerEncodedUrl), authorizationCode).setClientAuthentication( pkceClientAuthenticationWrapper).setRequestInitializer(requestInitializer).setScopes(scopes); } /** * Creates a new credential for the given user ID based on the given token response * and stores it in the credential store. * * @param response token response * @param userId user ID or {@code null} if not using a persisted credential store * @return newly created credential */ @SuppressWarnings("deprecation") public Credential createAndStoreCredential(TokenResponse response, String userId) throws IOException { Credential credential = newCredential(userId).setFromTokenResponse(response); if (credentialStore != null) { credentialStore.store(userId, credential); } if (credentialDataStore != null) { credentialDataStore.set(userId, new StoredCredential(credential)); } if (credentialCreatedListener != null) { credentialCreatedListener.onCredentialCreated(credential, response); } return credential; } /** * Loads the 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 credential found in the credential store of the given user ID or {@code null} for none * found */ @SuppressWarnings("deprecation") public Credential loadCredential(String userId) throws IOException { // No requests need to be performed when userId is not specified. if (isNullOrEmpty(userId)) { return null; } if (credentialDataStore == null && credentialStore == null) { return null; } Credential credential = newCredential(userId); if (credentialDataStore != null) { StoredCredential stored = credentialDataStore.get(userId); if (stored == null) { return null; } credential.setAccessToken(stored.getAccessToken()); credential.setRefreshToken(stored.getRefreshToken()); credential.setExpirationTimeMilliseconds(stored.getExpirationTimeMilliseconds()); } else if (!credentialStore.load(userId, credential)) { return null; } return credential; } /** * Returns a new credential instance based on the given user ID. * * @param userId user ID or {@code null} if not using a persisted credential store */ @SuppressWarnings("deprecation") private Credential newCredential(String userId) { Credential.Builder builder = new Credential.Builder(method).setTransport(transport) .setJsonFactory(jsonFactory) .setTokenServerEncodedUrl(tokenServerEncodedUrl) .setClientAuthentication(clientAuthentication) .setRequestInitializer(requestInitializer) .setClock(clock); if (credentialDataStore != null) { builder.addRefreshListener( new DataStoreCredentialRefreshListener(userId, credentialDataStore)); } else if (credentialStore != null) { builder.addRefreshListener(new CredentialStoreRefreshListener(userId, credentialStore)); } builder.getRefreshListeners().addAll(refreshListeners); return builder.build(); } /** * Returns the method of presenting the access token to the resource server (for example * {@link BearerToken#authorizationHeaderAccessMethod}). */ public final AccessMethod getMethod() { return method; } /** Returns the HTTP transport. */ public final HttpTransport getTransport() { return transport; } /** Returns the JSON factory. */ public final JsonFactory getJsonFactory() { return jsonFactory; } /** Returns the token server encoded URL. */ public final String getTokenServerEncodedUrl() { return tokenServerEncodedUrl; } /** * Returns the client authentication or {@code null} for none (see * {@link TokenRequest#setClientAuthentication(HttpExecuteInterceptor)}). */ public final HttpExecuteInterceptor getClientAuthentication() { return clientAuthentication; } /** Returns the client identifier. */ public final String getClientId() { return clientId; } /** Returns the authorization server encoded URL. */ public final String getAuthorizationServerEncodedUrl() { return authorizationServerEncodedUrl; } /** * {@link Beta}
* Returns the credential persistence store or {@code null} for none. * @deprecated (to be removed in the future) Use {@link #getCredentialDataStore()} instead. */ @Beta @Deprecated public final CredentialStore getCredentialStore() { return credentialStore; } /** * {@link Beta}
* Returns the stored credential data store or {@code null} for none. * * @since 1.16 */ @Beta public final DataStore getCredentialDataStore() { return credentialDataStore; } /** Returns the HTTP request initializer or {@code null} for none. */ public final HttpRequestInitializer getRequestInitializer() { return requestInitializer; } /** * Returns the space-separated list of scopes. * * @since 1.15 */ public final String getScopesAsString() { return Joiner.on(' ').join(scopes); } /** Returns the a collection of scopes. */ public final Collection getScopes() { return scopes; } /** * Returns the clock which will be passed along to the Credential. * @since 1.9 */ public final Clock getClock() { return clock; } /** * Returns the unmodifiable list of listeners for refresh token results. * * @since 1.15 */ public final Collection getRefreshListeners() { return refreshListeners; } /** * Listener for a created credential after a successful token response in * {@link #createAndStoreCredential}. * * @since 1.14 */ public interface CredentialCreatedListener { /** * Notifies of a created credential after a successful token response in * {@link #createAndStoreCredential}. * *

* Typical use is to parse additional fields from the credential created, such as an ID token. *

* * @param credential created credential * @param tokenResponse successful token response */ void onCredentialCreated(Credential credential, TokenResponse tokenResponse) throws IOException; } /** * An implementation of Proof Key for Code Exchange * which, according to the OAuth 2.0 for Native Apps RFC, * is mandatory for public native apps. */ private static class PKCE { private final String verifier; private String challenge; private String challengeMethod; public PKCE() { verifier = generateVerifier(); generateChallenge(verifier); } private static String generateVerifier() { SecureRandom sr = new SecureRandom(); byte[] code = new byte[32]; sr.nextBytes(code); return Base64.encodeBase64URLSafeString(code); } /** * Create the PKCE code verifier. It uses the S256 method but * falls back to using the 'plain' method in the unlikely case * that the SHA-256 MessageDigest algorithm implementation can't be * loaded. */ private void generateChallenge(String verifier) { try { byte[] bytes = verifier.getBytes(); MessageDigest md = MessageDigest.getInstance("SHA-256"); md.update(bytes, 0, bytes.length); byte[] digest = md.digest(); challenge = Base64.encodeBase64URLSafeString(digest); challengeMethod = "S256"; } catch (NoSuchAlgorithmException e) { challenge = verifier; challengeMethod = "plain"; } } public String getVerifier() { return verifier; } public String getChallenge() { return challenge; } public String getChallengeMethod() { return challengeMethod; } } /** * Authorization code flow builder. * *

* Implementation is not thread-safe. *

*/ public static class Builder { /** * Method of presenting the access token to the resource server (for example * {@link BearerToken#authorizationHeaderAccessMethod}). */ AccessMethod method; /** HTTP transport. */ HttpTransport transport; /** JSON factory. */ JsonFactory jsonFactory; /** Token server URL. */ GenericUrl tokenServerUrl; /** * Client authentication or {@code null} for none (see * {@link TokenRequest#setClientAuthentication(HttpExecuteInterceptor)}). */ HttpExecuteInterceptor clientAuthentication; /** Client identifier. */ String clientId; /** Authorization server encoded URL. */ String authorizationServerEncodedUrl; PKCE pkce; /** Credential persistence store or {@code null} for none. */ @Deprecated @Beta CredentialStore credentialStore; /** Stored credential data store or {@code null} for none. */ @Beta DataStore credentialDataStore; /** HTTP request initializer or {@code null} for none. */ HttpRequestInitializer requestInitializer; /** Collection of scopes. */ Collection scopes = Lists.newArrayList(); /** Clock passed along to the Credential. */ Clock clock = Clock.SYSTEM; /** Credential created listener or {@code null} for none. */ CredentialCreatedListener credentialCreatedListener; /** Refresh listeners provided by the client. */ Collection refreshListeners = Lists.newArrayList(); /** * @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) { setMethod(method); setTransport(transport); setJsonFactory(jsonFactory); setTokenServerUrl(tokenServerUrl); setClientAuthentication(clientAuthentication); setClientId(clientId); setAuthorizationServerEncodedUrl(authorizationServerEncodedUrl); } /** Returns a new instance of an authorization code flow based on this builder. */ public AuthorizationCodeFlow build() { return new AuthorizationCodeFlow(this); } /** * Returns the method of presenting the access token to the resource server (for example * {@link BearerToken#authorizationHeaderAccessMethod}). */ public final AccessMethod getMethod() { return method; } /** * Sets the method of presenting the access token to the resource server (for example * {@link BearerToken#authorizationHeaderAccessMethod}). * *

* Overriding is only supported for the purpose of calling the super implementation and changing * the return type, but nothing else. *

* @since 1.11 */ public Builder setMethod(AccessMethod method) { this.method = Preconditions.checkNotNull(method); return this; } /** Returns the HTTP transport. */ public final HttpTransport getTransport() { return transport; } /** * Sets the HTTP transport. * *

* Overriding is only supported for the purpose of calling the super implementation and changing * the return type, but nothing else. *

* @since 1.11 */ public Builder setTransport(HttpTransport transport) { this.transport = Preconditions.checkNotNull(transport); return this; } /** Returns the JSON factory. */ public final JsonFactory getJsonFactory() { return jsonFactory; } /** * Sets the JSON factory. * *

* Overriding is only supported for the purpose of calling the super implementation and changing * the return type, but nothing else. *

* @since 1.11 */ public Builder setJsonFactory(JsonFactory jsonFactory) { this.jsonFactory = Preconditions.checkNotNull(jsonFactory); return this; } /** Returns the token server URL. */ public final GenericUrl getTokenServerUrl() { return tokenServerUrl; } /** * Sets the token server URL. * *

* Overriding is only supported for the purpose of calling the super implementation and changing * the return type, but nothing else. *

* @since 1.11 */ public Builder setTokenServerUrl(GenericUrl tokenServerUrl) { this.tokenServerUrl = Preconditions.checkNotNull(tokenServerUrl); return this; } /** * Returns the client authentication or {@code null} for none (see * {@link TokenRequest#setClientAuthentication(HttpExecuteInterceptor)}). */ public final HttpExecuteInterceptor getClientAuthentication() { return clientAuthentication; } /** * Sets the client authentication or {@code null} for none (see * {@link TokenRequest#setClientAuthentication(HttpExecuteInterceptor)}). * *

* Overriding is only supported for the purpose of calling the super implementation and changing * the return type, but nothing else. *

* @since 1.11 */ public Builder setClientAuthentication(HttpExecuteInterceptor clientAuthentication) { this.clientAuthentication = clientAuthentication; return this; } /** Returns the client identifier. */ public final String getClientId() { return clientId; } /** * Sets the client identifier. * *

* Overriding is only supported for the purpose of calling the super implementation and changing * the return type, but nothing else. *

* @since 1.11 */ public Builder setClientId(String clientId) { this.clientId = Preconditions.checkNotNull(clientId); return this; } /** Returns the authorization server encoded URL. */ public final String getAuthorizationServerEncodedUrl() { return authorizationServerEncodedUrl; } /** * Sets the authorization server encoded URL. * *

* Overriding is only supported for the purpose of calling the super implementation and changing * the return type, but nothing else. *

* @since 1.11 */ public Builder setAuthorizationServerEncodedUrl(String authorizationServerEncodedUrl) { this.authorizationServerEncodedUrl = Preconditions.checkNotNull(authorizationServerEncodedUrl); return this; } /** * {@link Beta}
* Returns the credential persistence store or {@code null} for none. * @deprecated (to be removed in the future) Use {@link #getCredentialDataStore()} instead. */ @Beta @Deprecated public final CredentialStore getCredentialStore() { return credentialStore; } /** * {@link Beta}
* Returns the stored credential data store or {@code null} for none. * * @since 1.16 */ @Beta public final DataStore getCredentialDataStore() { return credentialDataStore; } /** * Returns the clock passed along to the Credential or {@link Clock#SYSTEM} when system default * is used. * @since 1.9 */ public final Clock getClock() { return clock; } /** * Sets the clock to pass to the Credential. * *

* The default value for this parameter is {@link Clock#SYSTEM} *

* *

* Overriding is only supported for the purpose of calling the super implementation and changing * the return type, but nothing else. *

* @since 1.9 */ public Builder setClock(Clock clock) { this.clock = Preconditions.checkNotNull(clock); return this; } /** * {@link Beta}
* Sets the credential persistence store or {@code null} for none. * *

* Warning: not compatible with {@link #setDataStoreFactory} or {@link #setCredentialDataStore}, * and if either of those is called before this method is called, this method will throw an * {@link IllegalArgumentException}. *

* *

* Overriding is only supported for the purpose of calling the super implementation and changing * the return type, but nothing else. *

* * @deprecated (to be removed in the future) Use * {@link #setDataStoreFactory(DataStoreFactory)} or * {@link #setCredentialDataStore(DataStore)} instead. */ @Beta @Deprecated public Builder setCredentialStore(CredentialStore credentialStore) { Preconditions.checkArgument(credentialDataStore == null); this.credentialStore = credentialStore; return this; } /** * {@link Beta}
* Sets the data store factory or {@code null} for none. * *

* Warning: not compatible with {@link #setCredentialStore}, and if it is called before this * method is called, this method will throw an {@link IllegalArgumentException}. *

* *

* Overriding is only supported for the purpose of calling the super implementation and changing * the return type, but nothing else. *

* * @since 1.16 */ @Beta public Builder setDataStoreFactory(DataStoreFactory dataStoreFactory) throws IOException { return setCredentialDataStore(StoredCredential.getDefaultDataStore(dataStoreFactory)); } /** * {@link Beta}
* Sets the stored credential data store or {@code null} for none. * *

* Warning: not compatible with {@link #setCredentialStore}, and if it is called before this * method is called, this method will throw an {@link IllegalArgumentException}. *

* *

* Overriding is only supported for the purpose of calling the super implementation and changing * the return type, but nothing else. *

* * @since 1.16 */ @Beta public Builder setCredentialDataStore(DataStore credentialDataStore) { Preconditions.checkArgument(credentialStore == null); this.credentialDataStore = credentialDataStore; return this; } /** Returns the HTTP request initializer or {@code null} for none. */ public final HttpRequestInitializer getRequestInitializer() { return requestInitializer; } /** * Sets the HTTP request initializer or {@code null} for none. * *

* Overriding is only supported for the purpose of calling the super implementation and changing * the return type, but nothing else. *

*/ public Builder setRequestInitializer(HttpRequestInitializer requestInitializer) { this.requestInitializer = requestInitializer; return this; } /** * Enables Proof Key for Code Exchange (PKCE) for this Athorization Code Flow. * @since 1.31 */ @Beta public Builder enablePKCE() { this.pkce = new PKCE(); return this; } /** * Sets the collection of scopes. * *

* Overriding is only supported for the purpose of calling the super implementation and changing * the return type, but nothing else. *

* * @param scopes collection of scopes * @since 1.15 */ public Builder setScopes(Collection scopes) { this.scopes = Preconditions.checkNotNull(scopes); return this; } /** Returns a collection of scopes. */ public final Collection getScopes() { return scopes; } /** * Sets the credential created listener or {@code null} for none. * *

* Overriding is only supported for the purpose of calling the super implementation and changing * the return type, but nothing else. *

* * @since 1.14 */ public Builder setCredentialCreatedListener( CredentialCreatedListener credentialCreatedListener) { this.credentialCreatedListener = credentialCreatedListener; return this; } /** * Adds a listener for refresh token results. * *

* Overriding is only supported for the purpose of calling the super implementation and changing * the return type, but nothing else. *

* * @param refreshListener refresh listener * @since 1.15 */ public Builder addRefreshListener(CredentialRefreshListener refreshListener) { refreshListeners.add(Preconditions.checkNotNull(refreshListener)); return this; } /** * Returns the listeners for refresh token results. * * @since 1.15 */ public final Collection getRefreshListeners() { return refreshListeners; } /** * Sets the listeners for refresh token results. * *

* Overriding is only supported for the purpose of calling the super implementation and changing * the return type, but nothing else. *

* * @since 1.15 */ public Builder setRefreshListeners(Collection refreshListeners) { this.refreshListeners = Preconditions.checkNotNull(refreshListeners); return this; } /** * Returns the credential created listener or {@code null} for none. * * @since 1.14 */ public final CredentialCreatedListener getCredentialCreatedListener() { return credentialCreatedListener; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy