diff --git a/java/google/registry/tools/AuthModule.java b/java/google/registry/tools/AuthModule.java index 16f550cff..61286a187 100644 --- a/java/google/registry/tools/AuthModule.java +++ b/java/google/registry/tools/AuthModule.java @@ -33,6 +33,7 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.Ordering; import com.google.gson.Gson; import dagger.Binds; +import dagger.Lazy; import dagger.Module; import dagger.Provides; import google.registry.config.CredentialModule.DefaultCredential; @@ -45,12 +46,14 @@ import java.io.IOException; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.nio.file.Files; +import java.nio.file.Paths; +import javax.annotation.Nullable; +import javax.inject.Named; import javax.inject.Qualifier; import javax.inject.Singleton; -/** - * Module providing the dependency graph for authorization credentials. - */ +/** Module providing the dependency graph for authorization credentials. */ @Module public class AuthModule { @@ -84,9 +87,15 @@ public class AuthModule { @Provides @LocalCredential public static GoogleCredential provideLocalCredential( - @LocalCredentialJson String credentialJson) { + @LocalCredentialJson String credentialJson, + @Config("localCredentialOauthScopes") ImmutableList scopes) { try { - return GoogleCredential.fromStream(new ByteArrayInputStream(credentialJson.getBytes(UTF_8))); + GoogleCredential credential = + GoogleCredential.fromStream(new ByteArrayInputStream(credentialJson.getBytes(UTF_8))); + if (credential.createScopedRequired()) { + credential = credential.createScoped(scopes); + } + return credential; } catch (IOException e) { throw new RuntimeException(e); } @@ -133,15 +142,25 @@ public class AuthModule { @Provides @LocalCredentialJson public static String provideLocalCredentialJson( - GoogleClientSecrets clientSecrets, @StoredCredential Credential credential) { - return 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()); + Lazy clientSecrets, + @StoredCredential Lazy credential, + @Nullable @Named("credentialFileName") String credentialFilename) { + try { + if (credentialFilename != null) { + return new String(Files.readAllBytes(Paths.get(credentialFilename)), UTF_8); + } else { + return new Gson() + .toJson( + ImmutableMap.builder() + .put("type", "authorized_user") + .put("client_id", clientSecrets.get().getDetails().getClientId()) + .put("client_secret", clientSecrets.get().getDetails().getClientSecret()) + .put("refresh_token", credential.get().getRefreshToken()) + .build()); + } + } catch (IOException e) { + throw new RuntimeException(e); + } } @Provides diff --git a/java/google/registry/tools/RegistryCli.java b/java/google/registry/tools/RegistryCli.java index 412d0f472..7baa09e1e 100644 --- a/java/google/registry/tools/RegistryCli.java +++ b/java/google/registry/tools/RegistryCli.java @@ -58,6 +58,13 @@ final class RegistryCli implements AutoCloseable, CommandRunner { description = "Returns all command names.") private boolean showAllCommands; + @Parameter( + names = {"--credential"}, + description = + "Name of a JSON file containing credential information used by the tool. " + + "If not set, credentials saved by running `nomulus login' will be used.") + private String credentialJson = null; + // Do not make this final - compile-time constant inlining may interfere with JCommander. @ParametersDelegate private LoggingParameters loggingParams = new LoggingParameters(); @@ -81,8 +88,6 @@ final class RegistryCli implements AutoCloseable, CommandRunner { this.commands = commands; Security.addProvider(new BouncyCastleProvider()); - - component = DaggerRegistryToolComponent.create(); } // The > wildcard looks a little funny, but is needed so that @@ -146,6 +151,9 @@ final class RegistryCli implements AutoCloseable, CommandRunner { checkState(RegistryToolEnvironment.get() == environment, "RegistryToolEnvironment argument pre-processing kludge failed."); + component = + DaggerRegistryToolComponent.builder().credentialFilename(credentialJson).build(); + // JCommander stores sub-commands as nested JCommander objects containing a list of user objects // to be populated. Extract the subcommand by getting the JCommander wrapper and then // retrieving the first (and, by virtue of our usage, only) object from it. diff --git a/java/google/registry/tools/RegistryToolComponent.java b/java/google/registry/tools/RegistryToolComponent.java index f892a3be6..606cd36b9 100644 --- a/java/google/registry/tools/RegistryToolComponent.java +++ b/java/google/registry/tools/RegistryToolComponent.java @@ -14,6 +14,7 @@ package google.registry.tools; +import dagger.BindsInstance; import dagger.Component; import google.registry.bigquery.BigqueryModule; import google.registry.config.CredentialModule.LocalCredentialJson; @@ -36,6 +37,8 @@ import google.registry.util.AppEngineServiceUtilsImpl.AppEngineServiceUtilsModul import google.registry.util.SystemClock.SystemClockModule; import google.registry.util.SystemSleeper.SystemSleeperModule; import google.registry.whois.WhoisModule; +import javax.annotation.Nullable; +import javax.inject.Named; import javax.inject.Singleton; /** @@ -113,5 +116,12 @@ interface RegistryToolComponent { @LocalCredentialJson String googleCredentialJson(); -} + @Component.Builder + interface Builder { + @BindsInstance + Builder credentialFilename(@Nullable @Named("credentialFileName") String credentialFilename); + + RegistryToolComponent build(); + } +} diff --git a/java/google/registry/tools/RequestFactoryModule.java b/java/google/registry/tools/RequestFactoryModule.java index ac555050c..f338e4f7f 100644 --- a/java/google/registry/tools/RequestFactoryModule.java +++ b/java/google/registry/tools/RequestFactoryModule.java @@ -19,7 +19,7 @@ import com.google.api.client.http.HttpRequestFactory; import com.google.api.client.http.javanet.NetHttpTransport; import dagger.Module; import dagger.Provides; -import google.registry.config.CredentialModule.LocalCredential; +import google.registry.config.CredentialModule.DefaultCredential; import google.registry.config.RegistryConfig; /** @@ -35,7 +35,7 @@ class RequestFactoryModule { @Provides static HttpRequestFactory provideHttpRequestFactory( - @LocalCredential GoogleCredential credential) { + @DefaultCredential GoogleCredential credential) { if (RegistryConfig.areServersLocal()) { return new NetHttpTransport() .createRequestFactory( diff --git a/javatests/google/registry/tools/AuthModuleTest.java b/javatests/google/registry/tools/AuthModuleTest.java index b58763500..290c1a7a5 100644 --- a/javatests/google/registry/tools/AuthModuleTest.java +++ b/javatests/google/registry/tools/AuthModuleTest.java @@ -16,6 +16,7 @@ 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; @@ -32,11 +33,15 @@ import com.google.api.client.util.store.DataStore; import com.google.common.collect.ImmutableList; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; +import java.io.File; import java.io.IOException; import java.io.Serializable; +import java.nio.file.Files; import java.util.Map; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -49,6 +54,9 @@ public class AuthModuleTest { private static final String ACCESS_TOKEN = "FakeAccessToken"; private static final String REFRESH_TOKEN = "FakeReFreshToken"; + @Rule + public final TemporaryFolder folder = new TemporaryFolder(); + private final Credential fakeCredential = new Credential.Builder( new Credential.AccessMethod() { @@ -154,7 +162,8 @@ public class AuthModuleTest { @Test public void test_provideLocalCredentialJson() { - String credentialJson = AuthModule.provideLocalCredentialJson(getSecrets(), getCredential()); + String credentialJson = + AuthModule.provideLocalCredentialJson(this::getSecrets, this::getCredential, null); Map jsonMap = new Gson().fromJson(credentialJson, new TypeToken>() {}.getType()); assertThat(jsonMap.get("type")).isEqualTo("authorized_user"); @@ -163,6 +172,16 @@ public class AuthModuleTest { assertThat(jsonMap.get("refresh_token")).isEqualTo(REFRESH_TOKEN); } + @Test + public void test_provideExternalCredentialJson() throws Exception { + File credentialFile = folder.newFile("credential.json"); + Files.write(credentialFile.toPath(), "{some_field: some_value}".getBytes(UTF_8)); + String credentialJson = + AuthModule.provideLocalCredentialJson( + this::getSecrets, this::getCredential, credentialFile.getCanonicalPath()); + assertThat(credentialJson).isEqualTo("{some_field: some_value}"); + } + @Test public void test_provideCredential() { Credential cred = getCredential();