diff --git a/gradle/core/build.gradle b/gradle/core/build.gradle index 79d0cd66e..59e1e7187 100644 --- a/gradle/core/build.gradle +++ b/gradle/core/build.gradle @@ -117,6 +117,7 @@ dependencies { maybeRuntime 'com.google.auth:google-auth-library-oauth2-http:0.7.1' maybeRuntime 'com.google.auto:auto-common:0.8' maybeRuntime 'com.google.auto.factory:auto-factory:1.0-beta3' + compile 'com.google.code.gson:gson:2.8.5' compile 'com.google.auto.value:auto-value-annotations:1.6.2' maybeRuntime 'com.google.cloud.bigdataoss:gcsio:1.4.5' maybeRuntime 'com.google.cloud.bigdataoss:util:1.4.5' diff --git a/java/google/registry/config/RegistryConfig.java b/java/google/registry/config/RegistryConfig.java index 851d24840..2a79e6ca6 100644 --- a/java/google/registry/config/RegistryConfig.java +++ b/java/google/registry/config/RegistryConfig.java @@ -1229,6 +1229,14 @@ public final class RegistryConfig { return ImmutableList.copyOf(config.credentialOAuth.delegatedCredentialOauthScopes); } + /** Provides the OAuth scopes required for credentials created locally for the nomulus tool. */ + @Provides + @Config("localCredentialOauthScopes") + public static ImmutableList provideLocalCredentialOauthScopes( + RegistryConfigSettings config) { + return ImmutableList.copyOf(config.credentialOAuth.localCredentialOauthScopes); + } + /** Provides the OAuth scopes required for access to App Engine Admin API. */ @Provides @Config("appEngineAdminApiCredentialOauthScopes") diff --git a/java/google/registry/config/RegistryConfigSettings.java b/java/google/registry/config/RegistryConfigSettings.java index ab1b2c790..9d2b50096 100644 --- a/java/google/registry/config/RegistryConfigSettings.java +++ b/java/google/registry/config/RegistryConfigSettings.java @@ -58,6 +58,7 @@ public class RegistryConfigSettings { public static class CredentialOAuth { public List defaultCredentialOauthScopes; public List delegatedCredentialOauthScopes; + public List localCredentialOauthScopes; public List appEngineAdminApiCredentialOauthScopes; } diff --git a/java/google/registry/config/files/default-config.yaml b/java/google/registry/config/files/default-config.yaml index 798d5e24a..e0cee2e69 100644 --- a/java/google/registry/config/files/default-config.yaml +++ b/java/google/registry/config/files/default-config.yaml @@ -283,6 +283,12 @@ credentialOAuth: - https://www.googleapis.com/auth/admin.directory.group # View and manage group settings in Group Settings API. - https://www.googleapis.com/auth/apps.groups.settings + # OAuth scopes required to create a credential locally in for the nomulus tool. + localCredentialOauthScopes: + # Call App Engine APIs. + - https://www.googleapis.com/auth/appengine.apis + # View your email address. + - https://www.googleapis.com/auth/userinfo.email # OAuth scopes required for accessing App Engine Admin API using the # AppIdentityCredential. appEngineAdminApiCredentialOauthScopes: diff --git a/java/google/registry/repositories.bzl b/java/google/registry/repositories.bzl index 401631c51..7f0a25c47 100644 --- a/java/google/registry/repositories.bzl +++ b/java/google/registry/repositories.bzl @@ -59,6 +59,7 @@ def domain_registry_repositories( omit_com_google_auto_factory = False, omit_com_google_auto_service = False, omit_com_google_auto_value = False, + omit_com_google_code_gson = False, omit_com_google_cloud_bigdataoss_gcsio = False, omit_com_google_cloud_bigdataoss_util = False, omit_com_google_code_findbugs_jsr305 = False, @@ -242,6 +243,8 @@ def domain_registry_repositories( com_google_auto_service() if not omit_com_google_auto_value: com_google_auto_value() + if not omit_com_google_code_gson: + com_google_code_gson() if not omit_com_google_cloud_bigdataoss_gcsio: com_google_cloud_bigdataoss_gcsio() if not omit_com_google_cloud_bigdataoss_util: @@ -1115,6 +1118,17 @@ def com_google_auto_value(): ]), ) +def com_google_code_gson(): + java_import_external( + name = "com_google_code_gson", + licenses = ["notice"], # Apache 2.0 + jar_sha256 = "233a0149fc365c9f6edbd683cfe266b19bdc773be98eabdaf6b3c924b48e7d81", + jar_urls = [ + "http://repo1.maven.org/maven2/com/google/code/gson/gson/2.8.5/gson-2.8.5.jar", + "http://maven.ibiblio.org/maven2/com/google/code/gson/gson/2.8.5/gson-2.8.5.jar", + ], + ) + def com_google_cloud_bigdataoss_gcsio(): java_import_external( name = "com_google_cloud_bigdataoss_gcsio", diff --git a/java/google/registry/tools/AuthModule.java b/java/google/registry/tools/AuthModule.java index 114102d75..19595b74a 100644 --- a/java/google/registry/tools/AuthModule.java +++ b/java/google/registry/tools/AuthModule.java @@ -26,11 +26,15 @@ import com.google.api.client.json.JsonFactory; import com.google.api.client.util.store.AbstractDataStoreFactory; import com.google.api.client.util.store.FileDataStoreFactory; import com.google.common.base.Joiner; -import com.google.common.collect.ImmutableSet; +import com.google.common.base.Supplier; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.Ordering; +import com.google.gson.Gson; import dagger.Module; import dagger.Provides; import google.registry.config.RegistryConfig.Config; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -51,9 +55,8 @@ public class AuthModule { new File(System.getProperty("user.home"), ".config/nomulus/credentials"); @Provides - public Credential provideCredential( - GoogleAuthorizationCodeFlow flow, - @ClientScopeQualifier String clientScopeQualifier) { + public static Credential provideCredential( + GoogleAuthorizationCodeFlow flow, @ClientScopeQualifier String clientScopeQualifier) { try { // Try to load the credentials, throw an exception if we fail. Credential credential = flow.loadCredential(clientScopeQualifier); @@ -67,10 +70,10 @@ public class AuthModule { } @Provides - GoogleAuthorizationCodeFlow provideAuthorizationCodeFlow( + public static GoogleAuthorizationCodeFlow provideAuthorizationCodeFlow( JsonFactory jsonFactory, GoogleClientSecrets clientSecrets, - @Config("requiredOauthScopes") ImmutableSet requiredOauthScopes, + @Config("localCredentialOauthScopes") ImmutableList requiredOauthScopes, AbstractDataStoreFactory dataStoreFactory) { try { return new GoogleAuthorizationCodeFlow.Builder( @@ -83,17 +86,17 @@ public class AuthModule { } @Provides - AuthorizationCodeInstalledApp provideAuthorizationCodeInstalledApp( + public static AuthorizationCodeInstalledApp provideAuthorizationCodeInstalledApp( GoogleAuthorizationCodeFlow flow) { return new AuthorizationCodeInstalledApp(flow, new LocalServerReceiver()); } @Provides - GoogleClientSecrets provideClientSecrets( + public static GoogleClientSecrets provideClientSecrets( @Config("clientSecretFilename") String clientSecretFilename, JsonFactory jsonFactory) { try { // Load the client secrets file. - InputStream secretResourceStream = getClass().getResourceAsStream(clientSecretFilename); + InputStream secretResourceStream = AuthModule.class.getResourceAsStream(clientSecretFilename); if (secretResourceStream == null) { throw new RuntimeException("No client secret file found: " + clientSecretFilename); } @@ -105,19 +108,39 @@ public class AuthModule { } @Provides - @OAuthClientId String provideClientId(GoogleClientSecrets clientSecrets) { + @LocalCredentialStream + public static Supplier provideLocalCredentialStream( + GoogleClientSecrets clientSecrets, Credential credential) { + String json = + new Gson() + .toJson( + ImmutableMap.builder() + .put("type", "authorized_user") + .put("client_id", clientSecrets.getDetails().getClientId()) + .put("client_secret", clientSecrets.getDetails().getClientSecret()) + .put("refresh_token", credential.getRefreshToken()) + .build()); + // A supplier is provided so that each binding gets a fresh stream, to avoid contention. + return () -> new ByteArrayInputStream(json.getBytes(UTF_8)); + } + + @Provides + @OAuthClientId + static String provideClientId(GoogleClientSecrets clientSecrets) { return clientSecrets.getDetails().getClientId(); } @Provides - @ClientScopeQualifier String provideClientScopeQualifier( - @OAuthClientId String clientId, @Config("requiredOauthScopes") ImmutableSet scopes) { + @ClientScopeQualifier + static String provideClientScopeQualifier( + @OAuthClientId String clientId, + @Config("localCredentialOauthScopes") ImmutableList scopes) { return clientId + " " + Joiner.on(" ").join(Ordering.natural().sortedCopy(scopes)); } @Provides @Singleton - public AbstractDataStoreFactory provideDataStoreFactory() { + public static AbstractDataStoreFactory provideDataStoreFactory() { try { return new FileDataStoreFactory(DATA_STORE_DIR); } catch (IOException ex) { @@ -125,31 +148,26 @@ public class AuthModule { } } - /** Wrapper class to hold the login() function. */ - public static class Authorizer { - /** Initiate the login flow. */ - public static void login( - GoogleAuthorizationCodeFlow flow, - @ClientScopeQualifier String clientScopeQualifier) throws IOException { - new AuthorizationCodeInstalledApp(flow, new LocalServerReceiver()) - .authorize(clientScopeQualifier); - } + /** Raised when we need a user login. */ + static class LoginRequiredException extends RuntimeException { + LoginRequiredException() {} } - /** Raised when we need a user login. */ - public static class LoginRequiredException extends RuntimeException { - public LoginRequiredException() {} - } + /** Dagger qualifier for the JSON stream used to create the local credential. */ + @Qualifier + @Documented + @Retention(RetentionPolicy.RUNTIME) + @interface LocalCredentialStream {} /** Dagger qualifier for the credential qualifier consisting of client and scopes. */ @Qualifier @Documented @Retention(RetentionPolicy.RUNTIME) - public @interface ClientScopeQualifier {} + @interface ClientScopeQualifier {} /** Dagger qualifier for the OAuth2 client id. */ @Qualifier @Documented @Retention(RetentionPolicy.RUNTIME) - public @interface OAuthClientId {} + @interface OAuthClientId {} } diff --git a/java/google/registry/tools/BUILD b/java/google/registry/tools/BUILD index d8e5402a2..3a4a2c12a 100644 --- a/java/google/registry/tools/BUILD +++ b/java/google/registry/tools/BUILD @@ -83,6 +83,7 @@ java_library( "@com_google_appengine_remote_api//:link", "@com_google_auto_value", "@com_google_code_findbugs_jsr305", + "@com_google_code_gson", "@com_google_dagger", "@com_google_flogger", "@com_google_flogger_system_backend", diff --git a/java/google/registry/tools/RegistryCli.java b/java/google/registry/tools/RegistryCli.java index f9dbe7f1d..ccf200d37 100644 --- a/java/google/registry/tools/RegistryCli.java +++ b/java/google/registry/tools/RegistryCli.java @@ -197,7 +197,8 @@ final class RegistryCli implements AutoCloseable, CommandRunner { // Use dev credentials for localhost. options.useDevelopmentServerCredential(); } else { - options.useApplicationDefaultCredential(); + RemoteApiOptionsUtil.useGoogleCredentialStream(options, component + .googleCredentialStream().get()); } installer.install(options); } diff --git a/java/google/registry/tools/RegistryToolComponent.java b/java/google/registry/tools/RegistryToolComponent.java index 44d728fd8..82fd2a305 100644 --- a/java/google/registry/tools/RegistryToolComponent.java +++ b/java/google/registry/tools/RegistryToolComponent.java @@ -14,6 +14,7 @@ package google.registry.tools; +import com.google.common.base.Supplier; import dagger.Component; import google.registry.bigquery.BigqueryModule; import google.registry.config.CredentialModule; @@ -31,10 +32,12 @@ import google.registry.request.Modules.Jackson2Module; import google.registry.request.Modules.URLFetchServiceModule; import google.registry.request.Modules.UrlFetchTransportModule; import google.registry.request.Modules.UserServiceModule; +import google.registry.tools.AuthModule.LocalCredentialStream; import google.registry.util.AppEngineServiceUtilsImpl.AppEngineServiceUtilsModule; import google.registry.util.SystemClock.SystemClockModule; import google.registry.util.SystemSleeper.SystemSleeperModule; import google.registry.whois.WhoisModule; +import java.io.InputStream; import javax.inject.Singleton; /** @@ -110,4 +113,7 @@ interface RegistryToolComponent { void inject(WhoisQueryCommand command); AppEngineConnection appEngineConnection(); + + @LocalCredentialStream + Supplier googleCredentialStream(); } diff --git a/java/google/registry/tools/RemoteApiOptionsUtil.java b/java/google/registry/tools/RemoteApiOptionsUtil.java new file mode 100644 index 000000000..f6fe1f2a8 --- /dev/null +++ b/java/google/registry/tools/RemoteApiOptionsUtil.java @@ -0,0 +1,43 @@ +// Copyright 2018 The Nomulus Authors. All Rights Reserved. +// +// 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 google.registry.tools; + +import static com.google.common.base.Preconditions.checkState; + +import com.google.appengine.tools.remoteapi.RemoteApiOptions; +import java.io.InputStream; +import java.lang.reflect.Method; + +/** + * Provides a method to access {@link RemoteApiOptions#useGoogleCredentialStream(InputStream)}, + * which is a package private method. + * + *

This is obviously a hack, but until that method is exposed, we have to do this to set up the + * {@link RemoteApiOptions} with a JSON representing a user credential. + */ +public class RemoteApiOptionsUtil { + static RemoteApiOptions useGoogleCredentialStream(RemoteApiOptions options, InputStream stream) + throws Exception { + Method method = + options.getClass().getDeclaredMethod("useGoogleCredentialStream", InputStream.class); + checkState( + !method.isAccessible(), + "RemoteApiOptoins#useGoogleCredentialStream(InputStream) is accessible." + + " Stop using RemoteApiOptionsUtil."); + method.setAccessible(true); + method.invoke(options, stream); + return options; + } +} diff --git a/java/google/registry/tools/resources/client_secret_UNITTEST.json b/java/google/registry/tools/resources/client_secret_UNITTEST.json index 4df327a21..b0a65e565 100644 --- a/java/google/registry/tools/resources/client_secret_UNITTEST.json +++ b/java/google/registry/tools/resources/client_secret_UNITTEST.json @@ -5,7 +5,7 @@ "auth_uri":"https://accounts.google.com/o/oauth2/auth", "token_uri":"https://accounts.google.com/o/oauth2/token", "auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs", - "client_secret":"TBj4EcP5c0609ojiy2DIG6wE", + "client_secret":"UNITTEST-CLIENT-SECRET", "redirect_uris":["urn:ietf:wg:oauth:2.0:oob","http://localhost"] } } diff --git a/javatests/google/registry/tools/AuthModuleTest.java b/javatests/google/registry/tools/AuthModuleTest.java index 2ce24cc1b..d2f517ad5 100644 --- a/javatests/google/registry/tools/AuthModuleTest.java +++ b/javatests/google/registry/tools/AuthModuleTest.java @@ -16,37 +16,63 @@ package google.registry.tools; import static com.google.common.truth.Truth.assertThat; import static google.registry.testing.JUnitBackports.assertThrows; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import com.google.api.client.auth.oauth2.ClientParametersAuthentication; import com.google.api.client.auth.oauth2.Credential; import com.google.api.client.auth.oauth2.StoredCredential; import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets; +import com.google.api.client.http.GenericUrl; import com.google.api.client.http.HttpRequest; +import com.google.api.client.http.javanet.NetHttpTransport; import com.google.api.client.json.jackson2.JacksonFactory; import com.google.api.client.util.store.AbstractDataStoreFactory; import com.google.api.client.util.store.DataStore; -import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableList; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.io.Serializable; +import java.util.Map; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +/** Unit tests for {@link AuthModule}. */ @RunWith(JUnit4.class) public class AuthModuleTest { private static final String TEST_CLIENT_SECRET_FILENAME = "/google/registry/tools/resources/client_secret_UNITTEST.json"; - private static final Credential FAKE_CREDENTIAL = new Credential( - new Credential.AccessMethod() { - @Override - public void intercept(HttpRequest request, String accessToken) {} + private static final String CLIENT_ID = "UNITTEST-CLIENT-ID"; + private static final String CLIENT_SECRET = "UNITTEST-CLIENT-SECRET"; + private static final String ACCESS_TOKEN = "FakeAccessToken"; + private static final String REFRESH_TOKEN = "FakeReFreshToken"; - @Override - public String getAccessTokenFromRequest(HttpRequest request) { - return "MockAccessToken"; - } - }); + private final Credential fakeCredential = + new Credential.Builder( + new Credential.AccessMethod() { + @Override + public void intercept(HttpRequest request, String accessToken) {} + + @Override + public String getAccessTokenFromRequest(HttpRequest request) { + return ACCESS_TOKEN; + } + }) + // We need to set the following fields because they are checked when + // Credential#setRefreshToken is called. However they are not actually persisted in the + // DataStore and not actually used in tests. + .setJsonFactory(new JacksonFactory()) + .setTransport(new NetHttpTransport()) + .setTokenServerUrl(new GenericUrl("https://accounts.google.com/o/oauth2/token")) + .setClientAuthentication(new ClientParametersAuthentication(CLIENT_ID, CLIENT_SECRET)) + .build(); @SuppressWarnings("unchecked") DataStore dataStore = mock(DataStore.class); @@ -59,11 +85,18 @@ public class AuthModuleTest { return result; } } + + @Before + public void setUp() throws Exception { + fakeCredential.setRefreshToken(REFRESH_TOKEN); + when(dataStore.get(CLIENT_ID + " scope1")) + .thenReturn(new StoredCredential(fakeCredential)); + } + @Test public void test_clientScopeQualifier() { - AuthModule authModule = new AuthModule(); String simpleQualifier = - authModule.provideClientScopeQualifier("client-id", ImmutableSet.of("foo", "bar")); + AuthModule.provideClientScopeQualifier("client-id", ImmutableList.of("foo", "bar")); // If we change the way we encode client id and scopes, this assertion will break. That's // probably ok and you can just change the text. The things you have to be aware of are: @@ -73,59 +106,81 @@ public class AuthModuleTest { assertThat(simpleQualifier).isEqualTo("client-id bar foo"); // Verify order independence. - assertThat(simpleQualifier).isEqualTo( - authModule.provideClientScopeQualifier("client-id", ImmutableSet.of("bar", "foo"))); + assertThat(simpleQualifier) + .isEqualTo( + AuthModule.provideClientScopeQualifier("client-id", ImmutableList.of("bar", "foo"))); // Verify changing client id produces a different value. - assertThat(simpleQualifier).isNotEqualTo( - authModule.provideClientScopeQualifier("new-client", ImmutableSet.of("bar", "foo"))); + assertThat(simpleQualifier) + .isNotEqualTo( + AuthModule.provideClientScopeQualifier("new-client", ImmutableList.of("bar", "foo"))); // Verify that adding/deleting/modifying scopes produces a different value. - assertThat(simpleQualifier).isNotEqualTo( - authModule.provideClientScopeQualifier("client id", ImmutableSet.of("bar", "foo", "baz"))); - assertThat(simpleQualifier).isNotEqualTo( - authModule.provideClientScopeQualifier("client id", ImmutableSet.of("barx", "foo"))); - assertThat(simpleQualifier).isNotEqualTo( - authModule.provideClientScopeQualifier("client id", ImmutableSet.of("bar", "foox"))); - assertThat(simpleQualifier).isNotEqualTo( - authModule.provideClientScopeQualifier("client id", ImmutableSet.of("bar"))); + assertThat(simpleQualifier) + .isNotEqualTo( + AuthModule.provideClientScopeQualifier( + "client id", ImmutableList.of("bar", "foo", "baz"))); + assertThat(simpleQualifier) + .isNotEqualTo( + AuthModule.provideClientScopeQualifier("client id", ImmutableList.of("barx", "foo"))); + assertThat(simpleQualifier) + .isNotEqualTo( + AuthModule.provideClientScopeQualifier("client id", ImmutableList.of("bar", "foox"))); + assertThat(simpleQualifier) + .isNotEqualTo(AuthModule.provideClientScopeQualifier("client id", ImmutableList.of("bar"))); // Verify that delimiting works. - assertThat(simpleQualifier).isNotEqualTo( - authModule.provideClientScopeQualifier("client-id", ImmutableSet.of("barf", "oo"))); - assertThat(simpleQualifier).isNotEqualTo( - authModule.provideClientScopeQualifier("client-idb", ImmutableSet.of("ar", "foo"))); + assertThat(simpleQualifier) + .isNotEqualTo( + AuthModule.provideClientScopeQualifier("client-id", ImmutableList.of("barf", "oo"))); + assertThat(simpleQualifier) + .isNotEqualTo( + AuthModule.provideClientScopeQualifier("client-idb", ImmutableList.of("ar", "foo"))); } private Credential getCredential() { // Reconstruct the entire dependency graph, injecting FakeDatastoreFactory and credential // parameters. - AuthModule authModule = new AuthModule(); JacksonFactory jsonFactory = new JacksonFactory(); - GoogleClientSecrets clientSecrets = - authModule.provideClientSecrets(TEST_CLIENT_SECRET_FILENAME, jsonFactory); - ImmutableSet scopes = ImmutableSet.of("scope1"); - return authModule.provideCredential( - authModule.provideAuthorizationCodeFlow( + GoogleClientSecrets clientSecrets = getSecrets(); + ImmutableList scopes = ImmutableList.of("scope1"); + return AuthModule.provideCredential( + AuthModule.provideAuthorizationCodeFlow( jsonFactory, clientSecrets, scopes, new FakeDataStoreFactory()), - authModule.provideClientScopeQualifier(authModule.provideClientId(clientSecrets), scopes)); + AuthModule.provideClientScopeQualifier(AuthModule.provideClientId(clientSecrets), scopes)); + } + + private GoogleClientSecrets getSecrets() { + return AuthModule.provideClientSecrets(TEST_CLIENT_SECRET_FILENAME, new JacksonFactory()); } @Test - public void test_provideCredential() throws Exception { - when(dataStore.get("UNITTEST-CLIENT-ID scope1")).thenReturn( - new StoredCredential(FAKE_CREDENTIAL)); + public void test_provideLocalCredentialStream() { + InputStream jsonStream = + AuthModule.provideLocalCredentialStream(getSecrets(), getCredential()).get(); + Map jsonMap = + new Gson() + .fromJson( + new InputStreamReader(jsonStream, UTF_8), + new TypeToken>() {}.getType()); + assertThat(jsonMap.get("type")).isEqualTo("authorized_user"); + assertThat(jsonMap.get("client_secret")).isEqualTo(CLIENT_SECRET); + assertThat(jsonMap.get("client_id")).isEqualTo(CLIENT_ID); + assertThat(jsonMap.get("refresh_token")).isEqualTo(REFRESH_TOKEN); + } + + @Test + public void test_provideCredential() { Credential cred = getCredential(); - assertThat(cred.getAccessToken()).isEqualTo(FAKE_CREDENTIAL.getAccessToken()); - assertThat(cred.getRefreshToken()).isEqualTo(FAKE_CREDENTIAL.getRefreshToken()); + assertThat(cred.getAccessToken()).isEqualTo(fakeCredential.getAccessToken()); + assertThat(cred.getRefreshToken()).isEqualTo(fakeCredential.getRefreshToken()); assertThat(cred.getExpirationTimeMilliseconds()).isEqualTo( - FAKE_CREDENTIAL.getExpirationTimeMilliseconds()); + fakeCredential.getExpirationTimeMilliseconds()); } @Test - public void test_provideCredential_notStored() { - // Doing this without the mock setup should cause us to throw an exception because the - // credential has not been stored. + public void test_provideCredential_notStored() throws IOException { + when(dataStore.get(CLIENT_ID + " scope1")).thenReturn(null); assertThrows(AuthModule.LoginRequiredException.class, this::getCredential); } } diff --git a/javatests/google/registry/tools/BUILD b/javatests/google/registry/tools/BUILD index 8cc9724e2..ccb01b431 100644 --- a/javatests/google/registry/tools/BUILD +++ b/javatests/google/registry/tools/BUILD @@ -45,6 +45,7 @@ java_library( "@com_google_appengine_remote_api", "@com_google_auto_value", "@com_google_code_findbugs_jsr305", + "@com_google_code_gson", "@com_google_guava", "@com_google_http_client", "@com_google_http_client_jackson2",