Store credentials under scope-qualified name

Store the auth credentials under a name qualified by the set of OAuth scopes
as well as the client id.  This is implemented as the base64 encoded SHA1 hash
of the concatenation of client id and sorted auth scopes.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=148127911
This commit is contained in:
mmuller 2017-02-21 12:17:51 -08:00 committed by Ben McIlwain
parent b3b4bba9aa
commit 68bac57da5
2 changed files with 62 additions and 15 deletions

View file

@ -29,15 +29,20 @@ import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.client.util.store.AbstractDataStoreFactory;
import com.google.api.client.util.store.FileDataStoreFactory;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Ordering;
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import google.registry.config.RegistryConfig.Config;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.annotation.Documented;
import java.util.Collections;
import java.util.Collection;
import javax.inject.Named;
import javax.inject.Provider;
import javax.inject.Qualifier;
@ -60,10 +65,6 @@ import javax.inject.Singleton;
@Module
class DefaultRequestFactoryModule {
// TODO(mmuller): Use @Config("requiredOauthScopes")
private static final String DEFAULT_SCOPE =
"https://www.googleapis.com/auth/userinfo.email";
private static final File DATA_STORE_DIR =
new File(System.getProperty("user.home"), ".config/nomulus/credentials");
@ -124,8 +125,8 @@ class DefaultRequestFactoryModule {
* Module for providing HttpRequestFactory.
*
* <p>Localhost connections go to the App Engine dev server. The dev server differs from most HTTP
* connections in that the types whose annotations affect the use of annotaty don't require
* OAuth2 credentials, but instead require a special cookie.
* connections in that it doesn't require OAuth2 credentials, but instead requires a special
* cookie.
*/
@Module
abstract static class RequestFactoryModule {
@ -155,25 +156,23 @@ class DefaultRequestFactoryModule {
static class AuthorizerModule {
@Provides
public Authorizer provideAuthorizer(
final JsonFactory jsonFactory, final AbstractDataStoreFactory dataStoreFactory) {
final JsonFactory jsonFactory,
final AbstractDataStoreFactory dataStoreFactory,
@Config("requiredOauthScopes") final ImmutableSet<String> requiredOauthScopes) {
return new Authorizer() {
@Override
public Credential authorize(GoogleClientSecrets clientSecrets) {
try {
// Run a new auth flow.
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
new NetHttpTransport(), jsonFactory, clientSecrets,
Collections.singleton(DEFAULT_SCOPE))
new NetHttpTransport(), jsonFactory, clientSecrets, requiredOauthScopes)
.setDataStoreFactory(dataStoreFactory)
.build();
// TODO(mmuller): "credentials" directory needs to be qualified with the scopes and
// client id.
// We pass client id to the authorize method so we can safely persist credentials for
// multiple client ids.
return new AuthorizationCodeInstalledApp(flow, new LocalServerReceiver())
.authorize(clientSecrets.getDetails().getClientId());
.authorize(createClientScopeQualifier(
clientSecrets.getDetails().getClientId(), requiredOauthScopes));
} catch (Exception ex) {
throw new RuntimeException(ex);
}
@ -182,6 +181,15 @@ class DefaultRequestFactoryModule {
}
}
/**
* Create a unique identifier for a given client id and collection of scopes, to be used as an
* identifier for a credential.
*/
@VisibleForTesting
static String createClientScopeQualifier(String clientId, Collection<String> scopes) {
return clientId + " " + Joiner.on(" ").join(Ordering.natural().sortedCopy(scopes));
}
/**
* Interface that encapsulates the authorization logic to produce a credential for the user,
* allowing us to override the behavior for unit tests.