diff --git a/java/google/registry/config/RegistryConfig.java b/java/google/registry/config/RegistryConfig.java index 6223340a3..e30c102c8 100644 --- a/java/google/registry/config/RegistryConfig.java +++ b/java/google/registry/config/RegistryConfig.java @@ -1237,14 +1237,6 @@ public final class RegistryConfig { return ImmutableList.copyOf(config.credentialOAuth.localCredentialOauthScopes); } - /** Provides the OAuth scopes required for access to App Engine Admin API. */ - @Provides - @Config("appEngineAdminApiCredentialOauthScopes") - public static ImmutableList provideAppEngineAdminApiCredentialOauthScopes( - RegistryConfigSettings config) { - return ImmutableList.copyOf(config.credentialOAuth.appEngineAdminApiCredentialOauthScopes); - } - /** * Returns the help path for the RDAP terms of service. * diff --git a/java/google/registry/config/RegistryConfigSettings.java b/java/google/registry/config/RegistryConfigSettings.java index 2ce28bacf..2b7c9b4fe 100644 --- a/java/google/registry/config/RegistryConfigSettings.java +++ b/java/google/registry/config/RegistryConfigSettings.java @@ -59,7 +59,6 @@ public class RegistryConfigSettings { public List defaultCredentialOauthScopes; public List delegatedCredentialOauthScopes; public List localCredentialOauthScopes; - public List appEngineAdminApiCredentialOauthScopes; } /** Configuration options for the G Suite account used by Nomulus. */ diff --git a/java/google/registry/config/files/default-config.yaml b/java/google/registry/config/files/default-config.yaml index c6e447d45..c884d225b 100644 --- a/java/google/registry/config/files/default-config.yaml +++ b/java/google/registry/config/files/default-config.yaml @@ -285,17 +285,15 @@ credentialOAuth: - 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. + # View and manage data in all Google Cloud APIs. + - https://www.googleapis.com/auth/cloud-platform + # Call App Engine APIs locally. - 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: # View and manage your applications deployed on Google App Engine - https://www.googleapis.com/auth/appengine.admin - icannReporting: # URL we PUT monthly ICANN transactions reports to. icannTransactionsReportingUploadUrl: https://ry-api.icann.org/report/registrar-transactions diff --git a/java/google/registry/config/files/nomulus-config-production-sample.yaml b/java/google/registry/config/files/nomulus-config-production-sample.yaml index 4d065acbc..341ccaeb2 100644 --- a/java/google/registry/config/files/nomulus-config-production-sample.yaml +++ b/java/google/registry/config/files/nomulus-config-production-sample.yaml @@ -44,6 +44,7 @@ icannReporting: oAuth: allowedOauthClientIds: - placeholder.apps.googleusercontent.com + - placeholder-for-proxy rde: reportUrlPrefix: https://ry-api.icann.org/report/registry-escrow-report @@ -69,3 +70,7 @@ keyring: activeKeyring: KMS kms: projectId: placeholder + +registryTool: + clientId: placeholder.apps.googleusercontent.com + clientSecret: placeholder diff --git a/java/google/registry/tools/AppEngineAdminApiModule.java b/java/google/registry/tools/AppEngineAdminApiModule.java index 8a67a7365..d7beac24c 100644 --- a/java/google/registry/tools/AppEngineAdminApiModule.java +++ b/java/google/registry/tools/AppEngineAdminApiModule.java @@ -14,13 +14,13 @@ package google.registry.tools; -import com.google.api.client.googleapis.extensions.appengine.auth.oauth2.AppIdentityCredential; +import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; import com.google.api.client.googleapis.util.Utils; import com.google.api.services.appengine.v1.Appengine; import dagger.Module; import dagger.Provides; -import google.registry.config.CredentialModule.AppEngineAdminApiCredential; import google.registry.config.RegistryConfig.Config; +import google.registry.tools.AuthModule.LocalCredential; import javax.inject.Singleton; /** Module providing the instance of {@link Appengine} to access App Engine Admin Api. */ @@ -30,10 +30,9 @@ public abstract class AppEngineAdminApiModule { @Provides @Singleton public static Appengine provideAppengine( - @AppEngineAdminApiCredential AppIdentityCredential appIdentityCredential, - @Config("projectId") String projectId) { + @LocalCredential GoogleCredential credential, @Config("projectId") String projectId) { return new Appengine.Builder( - Utils.getDefaultTransport(), Utils.getDefaultJsonFactory(), appIdentityCredential) + Utils.getDefaultTransport(), Utils.getDefaultJsonFactory(), credential) .setApplicationName(projectId) .build(); } diff --git a/java/google/registry/tools/AuthModule.java b/java/google/registry/tools/AuthModule.java index 1cb0fb89c..3d4138360 100644 --- a/java/google/registry/tools/AuthModule.java +++ b/java/google/registry/tools/AuthModule.java @@ -22,6 +22,7 @@ import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver; import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow; import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets; import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets.Details; +import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; import com.google.api.client.http.javanet.NetHttpTransport; import com.google.api.client.json.JsonFactory; import com.google.api.client.util.store.AbstractDataStoreFactory; @@ -32,8 +33,10 @@ 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.Binds; import dagger.Module; import dagger.Provides; +import google.registry.config.CredentialModule.DefaultCredential; import google.registry.config.RegistryConfig.Config; import java.io.ByteArrayInputStream; import java.io.File; @@ -54,8 +57,17 @@ public class AuthModule { private static final File DATA_STORE_DIR = new File(System.getProperty("user.home"), ".config/nomulus/credentials"); + @Module + abstract static class LocalCredentialModule { + @Binds + @DefaultCredential + abstract GoogleCredential provideLocalCredentialAsDefaultCredential( + @LocalCredential GoogleCredential credential); + } + @Provides - public static Credential provideCredential( + @StoredCredential + static Credential provideCredential( GoogleAuthorizationCodeFlow flow, @ClientScopeQualifier String clientScopeQualifier) { try { // Try to load the credentials, throw an exception if we fail. @@ -69,6 +81,17 @@ public class AuthModule { } } + @Provides + @LocalCredential + public static GoogleCredential provideLocalCredential( + @LocalCredentialStream Supplier credentialStream) { + try { + return GoogleCredential.fromStream(credentialStream.get()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + @Provides public static GoogleAuthorizationCodeFlow provideAuthorizationCodeFlow( JsonFactory jsonFactory, @@ -111,7 +134,7 @@ public class AuthModule { @Provides @LocalCredentialStream public static Supplier provideLocalCredentialStream( - GoogleClientSecrets clientSecrets, Credential credential) { + GoogleClientSecrets clientSecrets, @StoredCredential Credential credential) { String json = new Gson() .toJson( @@ -154,6 +177,24 @@ public class AuthModule { LoginRequiredException() {} } + /** + * Dagger qualifier for the {@link Credential} constructed from the data stored on disk. + * + *

This {@link Credential} should not be used in another module, hence the private qualifier. + * It's only use is to build a {@link GoogleCredential}, which is used in injection sites + * elsewhere. + */ + @Qualifier + @Documented + @Retention(RetentionPolicy.RUNTIME) + private @interface StoredCredential {} + + /** Dagger qualifier for the local credential used in the nomulus tool. */ + @Qualifier + @Documented + @Retention(RetentionPolicy.RUNTIME) + @interface LocalCredential {} + /** Dagger qualifier for the JSON stream used to create the local credential. */ @Qualifier @Documented diff --git a/java/google/registry/tools/DefaultRequestFactoryModule.java b/java/google/registry/tools/DefaultRequestFactoryModule.java deleted file mode 100644 index 4f75c894e..000000000 --- a/java/google/registry/tools/DefaultRequestFactoryModule.java +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2017 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 com.google.api.client.auth.oauth2.Credential; -import com.google.api.client.http.HttpRequestFactory; -import com.google.api.client.http.javanet.NetHttpTransport; -import dagger.Binds; -import dagger.Module; -import dagger.Provides; -import google.registry.config.RegistryConfig; -import javax.inject.Named; -import javax.inject.Provider; - -/** - * Module for providing the default HttpRequestFactory. - * - * - *

This module provides a standard NetHttpTransport-based HttpRequestFactory binding. - * The binding is qualified with the name named "default" and is not consumed directly. The - * RequestFactoryModule module binds the "default" HttpRequestFactory to the unqualified - * HttpRequestFactory, allowing users to override the actual, unqualified HttpRequestFactory - * binding by replacing RequestFactoryfModule with their own module, optionally providing - * the "default" factory in some circumstances. - * - *

Localhost connections go to the App Engine dev server. The dev server differs from most HTTP - * connections in that they don't require OAuth2 credentials, but instead require a special cookie. - */ -@Module -class DefaultRequestFactoryModule { - - @Provides - @Named("default") - public HttpRequestFactory provideHttpRequestFactory( - Provider credentialProvider) { - if (RegistryConfig.areServersLocal()) { - return new NetHttpTransport() - .createRequestFactory( - request -> request - .getHeaders() - .setCookie("dev_appserver_login=test@example.com:true:1858047912411")); - } else { - return new NetHttpTransport().createRequestFactory(credentialProvider.get()); - } - } - - /** - * Module for providing HttpRequestFactory. - * - *

Localhost connections go to the App Engine dev server. The dev server differs from most HTTP - * connections in that it doesn't require OAuth2 credentials, but instead requires a special - * cookie. - */ - @Module - abstract static class RequestFactoryModule { - - @Binds - public abstract HttpRequestFactory provideHttpRequestFactory( - @Named("default") HttpRequestFactory requestFactory); - } -} diff --git a/java/google/registry/tools/RegistryCli.java b/java/google/registry/tools/RegistryCli.java index ccf200d37..f3eef71ac 100644 --- a/java/google/registry/tools/RegistryCli.java +++ b/java/google/registry/tools/RegistryCli.java @@ -14,9 +14,6 @@ package google.registry.tools; -import com.google.appengine.tools.remoteapi.RemoteApiInstaller; -import com.google.appengine.tools.remoteapi.RemoteApiOptions; - import static com.google.common.base.Preconditions.checkState; import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.tools.Injector.injectReflectively; @@ -26,10 +23,14 @@ import com.beust.jcommander.Parameter; import com.beust.jcommander.ParameterException; import com.beust.jcommander.Parameters; import com.beust.jcommander.ParametersDelegate; +import com.google.appengine.tools.remoteapi.RemoteApiInstaller; +import com.google.appengine.tools.remoteapi.RemoteApiOptions; +import com.google.common.base.Throwables; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import google.registry.config.RegistryConfig; import google.registry.model.ofy.ObjectifyService; +import google.registry.tools.AuthModule.LoginRequiredException; import google.registry.tools.params.ParameterFactory; import java.net.URL; import java.security.Security; @@ -55,7 +56,6 @@ final class RegistryCli implements AutoCloseable, CommandRunner { description = "Returns all command names.") private boolean showAllCommands; - // Do not make this final - compile-time constant inlining may interfere with JCommander. @ParametersDelegate private LoggingParameters loggingParams = new LoggingParameters(); @@ -80,8 +80,7 @@ final class RegistryCli implements AutoCloseable, CommandRunner { Security.addProvider(new BouncyCastleProvider()); - component = DaggerRegistryToolComponent.builder() - .build(); + component = DaggerRegistryToolComponent.create(); } // The > wildcard looks a little funny, but is needed so that @@ -156,18 +155,32 @@ final class RegistryCli implements AutoCloseable, CommandRunner { try { runCommand(command); - } catch (AuthModule.LoginRequiredException ex) { - System.err.println("==================================================================="); - System.err.println("You must login using 'nomulus login' prior to running this command."); - System.err.println("==================================================================="); + } catch (RuntimeException ex) { + if (Throwables.getRootCause(ex) instanceof LoginRequiredException) { + System.err.println("==================================================================="); + System.err.println("You must login using 'nomulus login' prior to running this command."); + System.err.println("==================================================================="); + } else { + throw ex; + } } } @Override public void close() { if (installer != null) { - installer.uninstall(); - installer = null; + try { + installer.uninstall(); + installer = null; + } catch (IllegalArgumentException e) { + // There is no point throwing the error if the API is already uninstalled, which is most + // likely caused by something wrong when installing the API. That something (e. g. no + // credential found) must have already thrown an error message earlier (e. g. must run + // "nomulus login" first). This error message here is non-actionable. + if (!e.getMessage().equals("remote API is already uninstalled")) { + throw e; + } + } } } @@ -197,8 +210,8 @@ final class RegistryCli implements AutoCloseable, CommandRunner { // Use dev credentials for localhost. options.useDevelopmentServerCredential(); } else { - RemoteApiOptionsUtil.useGoogleCredentialStream(options, component - .googleCredentialStream().get()); + 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 82fd2a305..bad763893 100644 --- a/java/google/registry/tools/RegistryToolComponent.java +++ b/java/google/registry/tools/RegistryToolComponent.java @@ -17,7 +17,6 @@ package google.registry.tools; import com.google.common.base.Supplier; import dagger.Component; import google.registry.bigquery.BigqueryModule; -import google.registry.config.CredentialModule; import google.registry.config.RegistryConfig.ConfigModule; import google.registry.dns.writer.VoidDnsWriterModule; import google.registry.dns.writer.clouddns.CloudDnsWriterModule; @@ -32,6 +31,7 @@ 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.LocalCredentialModule; import google.registry.tools.AuthModule.LocalCredentialStream; import google.registry.util.AppEngineServiceUtilsImpl.AppEngineServiceUtilsModule; import google.registry.util.SystemClock.SystemClockModule; @@ -51,22 +51,20 @@ import javax.inject.Singleton; modules = { AppEngineAdminApiModule.class, AppEngineServiceUtilsModule.class, - // TODO(b/36866706): Find a way to replace this with a command-line friendly version AuthModule.class, BigqueryModule.class, ConfigModule.class, - CredentialModule.class, + CloudDnsWriterModule.class, DatastoreServiceModule.class, DummyKeyringModule.class, - CloudDnsWriterModule.class, - DefaultRequestFactoryModule.class, - DefaultRequestFactoryModule.RequestFactoryModule.class, DnsUpdateWriterModule.class, Jackson2Module.class, KeyModule.class, KeyringModule.class, KmsModule.class, + LocalCredentialModule.class, RdeModule.class, + RequestFactoryModule.class, SystemClockModule.class, SystemSleeperModule.class, URLFetchServiceModule.class, @@ -117,3 +115,4 @@ interface RegistryToolComponent { @LocalCredentialStream Supplier googleCredentialStream(); } + diff --git a/java/google/registry/tools/RequestFactoryModule.java b/java/google/registry/tools/RequestFactoryModule.java new file mode 100644 index 000000000..dbc6674ac --- /dev/null +++ b/java/google/registry/tools/RequestFactoryModule.java @@ -0,0 +1,48 @@ +// Copyright 2017 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 com.google.api.client.googleapis.auth.oauth2.GoogleCredential; +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.RegistryConfig; +import google.registry.tools.AuthModule.LocalCredential; + +/** + * Module for providing the HttpRequestFactory. + * + *

Localhost connections go to the App Engine dev server. The dev server differs from most HTTP + * connections in that they don't require OAuth2 credentials, but instead require a special cookie. + */ +@Module +class RequestFactoryModule { + + @Provides + static HttpRequestFactory provideHttpRequestFactory( + @LocalCredential GoogleCredential credential) { + if (RegistryConfig.areServersLocal()) { + return new NetHttpTransport() + .createRequestFactory( + request -> + request + .getHeaders() + .setCookie("dev_appserver_login=test@example.com:true:1858047912411")); + } else { + return new NetHttpTransport().createRequestFactory(credential); + } + } +} diff --git a/java/google/registry/tools/UserIdProvider.java b/java/google/registry/tools/UserIdProvider.java deleted file mode 100644 index c170180d5..000000000 --- a/java/google/registry/tools/UserIdProvider.java +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2017 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; - - -/** Static methods to get the current user id. */ -class UserIdProvider { - - static String getTestUserId() { - return "test@example.com"; // Predefined default user for the development server. - } - - /** Pick up the username from an appropriate source. */ - static String getProdUserId() { - // TODO(b/28219927): fix tool authentication to use actual user credentials. - // For the time being, use the empty string so that for testing, requests without credentials - // can still pass the server-side XSRF token check (which will represent no user as ""). - return ""; - } -} diff --git a/javatests/google/registry/tools/DefaultRequestFactoryModuleTest.java b/javatests/google/registry/tools/RequestFactoryModuleTest.java similarity index 67% rename from javatests/google/registry/tools/DefaultRequestFactoryModuleTest.java rename to javatests/google/registry/tools/RequestFactoryModuleTest.java index 45978e36a..e5391c054 100644 --- a/javatests/google/registry/tools/DefaultRequestFactoryModuleTest.java +++ b/javatests/google/registry/tools/RequestFactoryModuleTest.java @@ -15,9 +15,9 @@ package google.registry.tools; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.mock; -import com.google.api.client.auth.oauth2.Credential; -import com.google.api.client.http.HttpRequest; +import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; import com.google.api.client.http.HttpRequestFactory; import com.google.api.client.http.HttpRequestInitializer; import google.registry.config.RegistryConfig; @@ -29,23 +29,12 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @RunWith(JUnit4.class) -public class DefaultRequestFactoryModuleTest { +public class RequestFactoryModuleTest { - private static final Credential FAKE_CREDENTIAL = new Credential( - new Credential.AccessMethod() { - @Override - public void intercept(HttpRequest request, String accessToken) {} - - @Override - public String getAccessTokenFromRequest(HttpRequest request) { - return "MockAccessToken"; - } - }); + private final GoogleCredential googleCredential = mock(GoogleCredential.class); @Rule public final SystemPropertyRule systemPropertyRule = new SystemPropertyRule(); - DefaultRequestFactoryModule module = new DefaultRequestFactoryModule(); - @Before public void setUp() { RegistryToolEnvironment.UNITTEST.setup(systemPropertyRule); @@ -55,17 +44,17 @@ public class DefaultRequestFactoryModuleTest { public void test_provideHttpRequestFactory_localhost() { // Make sure that localhost creates a request factory with an initializer. RegistryConfig.CONFIG_SETTINGS.get().appEngine.isLocal = true; - HttpRequestFactory factory = module.provideHttpRequestFactory(() -> FAKE_CREDENTIAL); + HttpRequestFactory factory = RequestFactoryModule.provideHttpRequestFactory(googleCredential); HttpRequestInitializer initializer = factory.getInitializer(); assertThat(initializer).isNotNull(); - assertThat(initializer).isNotSameAs(FAKE_CREDENTIAL); + assertThat(initializer).isNotSameAs(googleCredential); } @Test public void test_provideHttpRequestFactory_remote() { // Make sure that example.com creates a request factory with the UNITTEST client id but no RegistryConfig.CONFIG_SETTINGS.get().appEngine.isLocal = false; - HttpRequestFactory factory = module.provideHttpRequestFactory(() -> FAKE_CREDENTIAL); - assertThat(factory.getInitializer()).isSameAs(FAKE_CREDENTIAL); + HttpRequestFactory factory = RequestFactoryModule.provideHttpRequestFactory(googleCredential); + assertThat(factory.getInitializer()).isSameAs(googleCredential); } }