Add the ability to provide credential JSON file to the nomulus tool

This allows us to run nomulus tool programmatically on environments that do not
allow the 3-legged OAuth authentication flow.

The provided JSON file corresponds to a service account, which must have
GAE admin permission and whose client ID must be whitelisted in the config
file.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=226008337
This commit is contained in:
jianglai 2018-12-18 09:25:06 -08:00 committed by Michael Muller
parent 40b05ffb3c
commit 27b6231053
5 changed files with 76 additions and 20 deletions

View file

@ -33,6 +33,7 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Ordering; import com.google.common.collect.Ordering;
import com.google.gson.Gson; import com.google.gson.Gson;
import dagger.Binds; import dagger.Binds;
import dagger.Lazy;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
import google.registry.config.CredentialModule.DefaultCredential; import google.registry.config.CredentialModule.DefaultCredential;
@ -45,12 +46,14 @@ import java.io.IOException;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; 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.Qualifier;
import javax.inject.Singleton; import javax.inject.Singleton;
/** /** Module providing the dependency graph for authorization credentials. */
* Module providing the dependency graph for authorization credentials.
*/
@Module @Module
public class AuthModule { public class AuthModule {
@ -84,9 +87,15 @@ public class AuthModule {
@Provides @Provides
@LocalCredential @LocalCredential
public static GoogleCredential provideLocalCredential( public static GoogleCredential provideLocalCredential(
@LocalCredentialJson String credentialJson) { @LocalCredentialJson String credentialJson,
@Config("localCredentialOauthScopes") ImmutableList<String> scopes) {
try { 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) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@ -133,15 +142,25 @@ public class AuthModule {
@Provides @Provides
@LocalCredentialJson @LocalCredentialJson
public static String provideLocalCredentialJson( public static String provideLocalCredentialJson(
GoogleClientSecrets clientSecrets, @StoredCredential Credential credential) { Lazy<GoogleClientSecrets> clientSecrets,
return new Gson() @StoredCredential Lazy<Credential> credential,
.toJson( @Nullable @Named("credentialFileName") String credentialFilename) {
ImmutableMap.<String, String>builder() try {
.put("type", "authorized_user") if (credentialFilename != null) {
.put("client_id", clientSecrets.getDetails().getClientId()) return new String(Files.readAllBytes(Paths.get(credentialFilename)), UTF_8);
.put("client_secret", clientSecrets.getDetails().getClientSecret()) } else {
.put("refresh_token", credential.getRefreshToken()) return new Gson()
.build()); .toJson(
ImmutableMap.<String, String>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 @Provides

View file

@ -58,6 +58,13 @@ final class RegistryCli implements AutoCloseable, CommandRunner {
description = "Returns all command names.") description = "Returns all command names.")
private boolean showAllCommands; 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. // Do not make this final - compile-time constant inlining may interfere with JCommander.
@ParametersDelegate @ParametersDelegate
private LoggingParameters loggingParams = new LoggingParameters(); private LoggingParameters loggingParams = new LoggingParameters();
@ -81,8 +88,6 @@ final class RegistryCli implements AutoCloseable, CommandRunner {
this.commands = commands; this.commands = commands;
Security.addProvider(new BouncyCastleProvider()); Security.addProvider(new BouncyCastleProvider());
component = DaggerRegistryToolComponent.create();
} }
// The <? extends Class<? extends Command>> wildcard looks a little funny, but is needed so that // The <? extends Class<? extends Command>> wildcard looks a little funny, but is needed so that
@ -146,6 +151,9 @@ final class RegistryCli implements AutoCloseable, CommandRunner {
checkState(RegistryToolEnvironment.get() == environment, checkState(RegistryToolEnvironment.get() == environment,
"RegistryToolEnvironment argument pre-processing kludge failed."); "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 // 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 // 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. // retrieving the first (and, by virtue of our usage, only) object from it.

View file

@ -14,6 +14,7 @@
package google.registry.tools; package google.registry.tools;
import dagger.BindsInstance;
import dagger.Component; import dagger.Component;
import google.registry.bigquery.BigqueryModule; import google.registry.bigquery.BigqueryModule;
import google.registry.config.CredentialModule.LocalCredentialJson; 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.SystemClock.SystemClockModule;
import google.registry.util.SystemSleeper.SystemSleeperModule; import google.registry.util.SystemSleeper.SystemSleeperModule;
import google.registry.whois.WhoisModule; import google.registry.whois.WhoisModule;
import javax.annotation.Nullable;
import javax.inject.Named;
import javax.inject.Singleton; import javax.inject.Singleton;
/** /**
@ -113,5 +116,12 @@ interface RegistryToolComponent {
@LocalCredentialJson @LocalCredentialJson
String googleCredentialJson(); String googleCredentialJson();
}
@Component.Builder
interface Builder {
@BindsInstance
Builder credentialFilename(@Nullable @Named("credentialFileName") String credentialFilename);
RegistryToolComponent build();
}
}

View file

@ -19,7 +19,7 @@ import com.google.api.client.http.HttpRequestFactory;
import com.google.api.client.http.javanet.NetHttpTransport; import com.google.api.client.http.javanet.NetHttpTransport;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
import google.registry.config.CredentialModule.LocalCredential; import google.registry.config.CredentialModule.DefaultCredential;
import google.registry.config.RegistryConfig; import google.registry.config.RegistryConfig;
/** /**
@ -35,7 +35,7 @@ class RequestFactoryModule {
@Provides @Provides
static HttpRequestFactory provideHttpRequestFactory( static HttpRequestFactory provideHttpRequestFactory(
@LocalCredential GoogleCredential credential) { @DefaultCredential GoogleCredential credential) {
if (RegistryConfig.areServersLocal()) { if (RegistryConfig.areServersLocal()) {
return new NetHttpTransport() return new NetHttpTransport()
.createRequestFactory( .createRequestFactory(

View file

@ -16,6 +16,7 @@ package google.registry.tools;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static google.registry.testing.JUnitBackports.assertThrows; 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.mock;
import static org.mockito.Mockito.when; 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.common.collect.ImmutableList;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.Serializable; import java.io.Serializable;
import java.nio.file.Files;
import java.util.Map; import java.util.Map;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.junit.runners.JUnit4; import org.junit.runners.JUnit4;
@ -49,6 +54,9 @@ public class AuthModuleTest {
private static final String ACCESS_TOKEN = "FakeAccessToken"; private static final String ACCESS_TOKEN = "FakeAccessToken";
private static final String REFRESH_TOKEN = "FakeReFreshToken"; private static final String REFRESH_TOKEN = "FakeReFreshToken";
@Rule
public final TemporaryFolder folder = new TemporaryFolder();
private final Credential fakeCredential = private final Credential fakeCredential =
new Credential.Builder( new Credential.Builder(
new Credential.AccessMethod() { new Credential.AccessMethod() {
@ -154,7 +162,8 @@ public class AuthModuleTest {
@Test @Test
public void test_provideLocalCredentialJson() { public void test_provideLocalCredentialJson() {
String credentialJson = AuthModule.provideLocalCredentialJson(getSecrets(), getCredential()); String credentialJson =
AuthModule.provideLocalCredentialJson(this::getSecrets, this::getCredential, null);
Map<String, String> jsonMap = Map<String, String> jsonMap =
new Gson().fromJson(credentialJson, new TypeToken<Map<String, String>>() {}.getType()); new Gson().fromJson(credentialJson, new TypeToken<Map<String, String>>() {}.getType());
assertThat(jsonMap.get("type")).isEqualTo("authorized_user"); assertThat(jsonMap.get("type")).isEqualTo("authorized_user");
@ -163,6 +172,16 @@ public class AuthModuleTest {
assertThat(jsonMap.get("refresh_token")).isEqualTo(REFRESH_TOKEN); 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 @Test
public void test_provideCredential() { public void test_provideCredential() {
Credential cred = getCredential(); Credential cred = getCredential();