mirror of
https://github.com/google/nomulus.git
synced 2025-07-23 19:20:44 +02:00
Refactor OIDC-based auth mechanism (#2049)
This PR changes the two flavors of OIDC authentication mechanisms to verify the same audience. This allows the same token to pass both mechanisms. Previously the regular OIDC flavor uses the project id as its required audience, which does not work for local user credentials (such as ones used by the nomulus tool), which requires a valid OAuth client ID as audience when minting the token (project id is NOT a valid OAuth client ID). I considered allowing multiple audiences, but the result is not as clean as just using the same everywhere, because the fall-through logic would have generated a lot of noises for failed attempts. This PR also changes the client side to solely use OIDC token whenever possible, including the proxy, cloud scheduler and cloud tasks. The nomulus tool still uses OAuth access token by default because it requires USER level authentication, which in turn requires us to fill the User table with objects corresponding to the email address of everyone needing access to the tool. TESTED=verified each client is able to make authenticated calls on QA with or without IAP.
This commit is contained in:
parent
cf1a148208
commit
fdfbb9572d
56 changed files with 565 additions and 891 deletions
|
@ -564,14 +564,18 @@ task deployCloudSchedulerAndQueue {
|
||||||
def env = environment
|
def env = environment
|
||||||
if (!prodOrSandboxEnv) {
|
if (!prodOrSandboxEnv) {
|
||||||
exec {
|
exec {
|
||||||
|
workingDir "${rootDir}/release/builder/"
|
||||||
commandLine 'go', 'run',
|
commandLine 'go', 'run',
|
||||||
"${rootDir}/release/builder/deployCloudSchedulerAndQueue.go",
|
"./deployCloudSchedulerAndQueue.go",
|
||||||
|
"${rootDir}/core/src/main/java/google/registry/config/files/nomulus-config-${env}.yaml",
|
||||||
"${rootDir}/core/src/main/java/google/registry/env/${env}/default/WEB-INF/cloud-scheduler-tasks.xml",
|
"${rootDir}/core/src/main/java/google/registry/env/${env}/default/WEB-INF/cloud-scheduler-tasks.xml",
|
||||||
"domain-registry-${env}"
|
"domain-registry-${env}"
|
||||||
}
|
}
|
||||||
exec {
|
exec {
|
||||||
|
workingDir "${rootDir}/release/builder/"
|
||||||
commandLine 'go', 'run',
|
commandLine 'go', 'run',
|
||||||
"${rootDir}/release/builder/deployCloudSchedulerAndQueue.go",
|
"./deployCloudSchedulerAndQueue.go",
|
||||||
|
"${rootDir}/core/src/main/java/google/registry/config/files/nomulus-config-${env}.yaml",
|
||||||
"${rootDir}/core/src/main/java/google/registry/env/common/default/WEB-INF/cloud-tasks-queue.xml",
|
"${rootDir}/core/src/main/java/google/registry/env/common/default/WEB-INF/cloud-tasks-queue.xml",
|
||||||
"domain-registry-${env}"
|
"domain-registry-${env}"
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,8 +20,6 @@ import static google.registry.tools.ServiceConnection.getServer;
|
||||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||||
|
|
||||||
import com.google.api.gax.rpc.ApiException;
|
import com.google.api.gax.rpc.ApiException;
|
||||||
import com.google.cloud.tasks.v2.AppEngineHttpRequest;
|
|
||||||
import com.google.cloud.tasks.v2.AppEngineRouting;
|
|
||||||
import com.google.cloud.tasks.v2.CloudTasksClient;
|
import com.google.cloud.tasks.v2.CloudTasksClient;
|
||||||
import com.google.cloud.tasks.v2.HttpMethod;
|
import com.google.cloud.tasks.v2.HttpMethod;
|
||||||
import com.google.cloud.tasks.v2.HttpRequest;
|
import com.google.cloud.tasks.v2.HttpRequest;
|
||||||
|
@ -39,10 +37,12 @@ import com.google.common.net.MediaType;
|
||||||
import com.google.common.net.UrlEscapers;
|
import com.google.common.net.UrlEscapers;
|
||||||
import com.google.protobuf.ByteString;
|
import com.google.protobuf.ByteString;
|
||||||
import com.google.protobuf.util.Timestamps;
|
import com.google.protobuf.util.Timestamps;
|
||||||
|
import google.registry.config.CredentialModule.ApplicationDefaultCredential;
|
||||||
import google.registry.config.RegistryConfig.Config;
|
import google.registry.config.RegistryConfig.Config;
|
||||||
import google.registry.request.Action.Service;
|
import google.registry.request.Action.Service;
|
||||||
import google.registry.util.Clock;
|
import google.registry.util.Clock;
|
||||||
import google.registry.util.CollectionUtils;
|
import google.registry.util.CollectionUtils;
|
||||||
|
import google.registry.util.GoogleCredentialsBundle;
|
||||||
import google.registry.util.Retrier;
|
import google.registry.util.Retrier;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
@ -52,7 +52,6 @@ import java.util.Random;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import org.joda.time.Duration;
|
import org.joda.time.Duration;
|
||||||
|
|
||||||
|
@ -67,9 +66,8 @@ public class CloudTasksUtils implements Serializable {
|
||||||
private final Clock clock;
|
private final Clock clock;
|
||||||
private final String projectId;
|
private final String projectId;
|
||||||
private final String locationId;
|
private final String locationId;
|
||||||
// defaultServiceAccount and iapClientId are nullable because Optional isn't serializable
|
private final String oauthClientId;
|
||||||
@Nullable private final String defaultServiceAccount;
|
private final GoogleCredentialsBundle credential;
|
||||||
@Nullable private final String iapClientId;
|
|
||||||
private final SerializableCloudTasksClient client;
|
private final SerializableCloudTasksClient client;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
|
@ -78,15 +76,15 @@ public class CloudTasksUtils implements Serializable {
|
||||||
Clock clock,
|
Clock clock,
|
||||||
@Config("projectId") String projectId,
|
@Config("projectId") String projectId,
|
||||||
@Config("locationId") String locationId,
|
@Config("locationId") String locationId,
|
||||||
@Config("defaultServiceAccount") Optional<String> defaultServiceAccount,
|
@Config("oauthClientId") String oauthClientId,
|
||||||
@Config("iapClientId") Optional<String> iapClientId,
|
@ApplicationDefaultCredential GoogleCredentialsBundle credential,
|
||||||
SerializableCloudTasksClient client) {
|
SerializableCloudTasksClient client) {
|
||||||
this.retrier = retrier;
|
this.retrier = retrier;
|
||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
this.projectId = projectId;
|
this.projectId = projectId;
|
||||||
this.locationId = locationId;
|
this.locationId = locationId;
|
||||||
this.defaultServiceAccount = defaultServiceAccount.orElse(null);
|
this.oauthClientId = oauthClientId;
|
||||||
this.iapClientId = iapClientId.orElse(null);
|
this.credential = credential;
|
||||||
this.client = client;
|
this.client = client;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,7 +122,7 @@ public class CloudTasksUtils implements Serializable {
|
||||||
*
|
*
|
||||||
* @return the resulting path (unchanged for POST requests, with params added for GET requests)
|
* @return the resulting path (unchanged for POST requests, with params added for GET requests)
|
||||||
*/
|
*/
|
||||||
private String processRequestParameters(
|
private static String processRequestParameters(
|
||||||
String path,
|
String path,
|
||||||
HttpMethod method,
|
HttpMethod method,
|
||||||
Multimap<String, String> params,
|
Multimap<String, String> params,
|
||||||
|
@ -152,43 +150,20 @@ public class CloudTasksUtils implements Serializable {
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a {@link Task} that does not use AppEngine for submission.
|
|
||||||
*
|
|
||||||
* <p>This uses the standard Cloud Tasks auth format to create and send an OIDC ID token set to
|
|
||||||
* the default service account. That account must have permission to submit tasks to Cloud Tasks.
|
|
||||||
*/
|
|
||||||
private Task createNonAppEngineTask(
|
|
||||||
String path, HttpMethod method, Service service, Multimap<String, String> params) {
|
|
||||||
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder().setHttpMethod(method);
|
|
||||||
path =
|
|
||||||
processRequestParameters(
|
|
||||||
path, method, params, requestBuilder::putHeaders, requestBuilder::setBody);
|
|
||||||
OidcToken.Builder oidcTokenBuilder =
|
|
||||||
OidcToken.newBuilder().setServiceAccountEmail(defaultServiceAccount);
|
|
||||||
// If the service is using IAP, add that as the audience for the token so the request can be
|
|
||||||
// appropriately authed. Otherwise, use the project name.
|
|
||||||
if (iapClientId != null) {
|
|
||||||
oidcTokenBuilder.setAudience(iapClientId);
|
|
||||||
} else {
|
|
||||||
oidcTokenBuilder.setAudience(projectId);
|
|
||||||
}
|
|
||||||
requestBuilder.setOidcToken(oidcTokenBuilder.build());
|
|
||||||
String totalPath = String.format("%s%s", getServer(service), path);
|
|
||||||
requestBuilder.setUrl(totalPath);
|
|
||||||
return Task.newBuilder().setHttpRequest(requestBuilder.build()).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a {@link Task} to be enqueued.
|
* Create a {@link Task} to be enqueued.
|
||||||
*
|
*
|
||||||
|
* <p>This uses the standard Cloud Tasks auth format to create and send an OIDC ID token with the
|
||||||
|
* default service account as the principal. That account must have permission to submit tasks to
|
||||||
|
* Cloud Tasks.
|
||||||
|
*
|
||||||
* @param path the relative URI (staring with a slash and ending without one).
|
* @param path the relative URI (staring with a slash and ending without one).
|
||||||
* @param method the HTTP method to be used for the request, only GET and POST are supported.
|
* @param method the HTTP method to be used for the request, only GET and POST are supported.
|
||||||
* @param service the App Engine service to route the request to. Note that with App Engine Task
|
* @param service the App Engine service to route the request to. Note that with App Engine Task
|
||||||
* Queue API if no service is specified, the service which enqueues the task will be used to
|
* Queue API if no service is specified, the service which enqueues the task will be used to
|
||||||
* process the task. Cloud Tasks API does not support this feature so the service will always
|
* process the task. Cloud Tasks API does not support this feature so the service will always
|
||||||
* needs to be explicitly specified.
|
* needs to be explicitly specified.
|
||||||
* @param params a multi-map of URL query parameters. Duplicate keys are saved as is, and it is up
|
* @param params a multimap of URL query parameters. Duplicate keys are saved as is, and it is up
|
||||||
* to the server to process the duplicate keys.
|
* to the server to process the duplicate keys.
|
||||||
* @return the enqueued task.
|
* @return the enqueued task.
|
||||||
* @see <a
|
* @see <a
|
||||||
|
@ -204,21 +179,18 @@ public class CloudTasksUtils implements Serializable {
|
||||||
method.equals(HttpMethod.GET) || method.equals(HttpMethod.POST),
|
method.equals(HttpMethod.GET) || method.equals(HttpMethod.POST),
|
||||||
"HTTP method %s is used. Only GET and POST are allowed.",
|
"HTTP method %s is used. Only GET and POST are allowed.",
|
||||||
method);
|
method);
|
||||||
// If the default service account is configured, send a standard non-AppEngine HTTP request
|
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder().setHttpMethod(method);
|
||||||
if (defaultServiceAccount != null) {
|
path =
|
||||||
return createNonAppEngineTask(path, method, service, params);
|
processRequestParameters(
|
||||||
} else {
|
path, method, params, requestBuilder::putHeaders, requestBuilder::setBody);
|
||||||
AppEngineHttpRequest.Builder requestBuilder =
|
OidcToken.Builder oidcTokenBuilder =
|
||||||
AppEngineHttpRequest.newBuilder()
|
OidcToken.newBuilder()
|
||||||
.setHttpMethod(method)
|
.setServiceAccountEmail(credential.serviceAccount())
|
||||||
.setAppEngineRouting(
|
.setAudience(oauthClientId);
|
||||||
AppEngineRouting.newBuilder().setService(service.toString()).build());
|
requestBuilder.setOidcToken(oidcTokenBuilder.build());
|
||||||
path =
|
String totalPath = String.format("%s%s", getServer(service), path);
|
||||||
processRequestParameters(
|
requestBuilder.setUrl(totalPath);
|
||||||
path, method, params, requestBuilder::putHeaders, requestBuilder::setBody);
|
return Task.newBuilder().setHttpRequest(requestBuilder.build()).build();
|
||||||
requestBuilder.setRelativeUri(path);
|
|
||||||
return Task.newBuilder().setAppEngineHttpRequest(requestBuilder.build()).build();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -230,7 +202,7 @@ public class CloudTasksUtils implements Serializable {
|
||||||
* Queue API if no service is specified, the service which enqueues the task will be used to
|
* Queue API if no service is specified, the service which enqueues the task will be used to
|
||||||
* process the task. Cloud Tasks API does not support this feature so the service will always
|
* process the task. Cloud Tasks API does not support this feature so the service will always
|
||||||
* needs to be explicitly specified.
|
* needs to be explicitly specified.
|
||||||
* @param params a multi-map of URL query parameters. Duplicate keys are saved as is, and it is up
|
* @param params a multimap of URL query parameters. Duplicate keys are saved as is, and it is up
|
||||||
* to the server to process the duplicate keys.
|
* to the server to process the duplicate keys.
|
||||||
* @param jitterSeconds the number of seconds that a task is randomly delayed up to.
|
* @param jitterSeconds the number of seconds that a task is randomly delayed up to.
|
||||||
* @return the enqueued task.
|
* @return the enqueued task.
|
||||||
|
@ -264,7 +236,7 @@ public class CloudTasksUtils implements Serializable {
|
||||||
* Queue API if no service is specified, the service which enqueues the task will be used to
|
* Queue API if no service is specified, the service which enqueues the task will be used to
|
||||||
* process the task. Cloud Tasks API does not support this feature so the service will always
|
* process the task. Cloud Tasks API does not support this feature so the service will always
|
||||||
* needs to be explicitly specified.
|
* needs to be explicitly specified.
|
||||||
* @param params a multi-map of URL query parameters. Duplicate keys are saved as is, and it is up
|
* @param params a multimap of URL query parameters. Duplicate keys are saved as is, and it is up
|
||||||
* to the server to process the duplicate keys.
|
* to the server to process the duplicate keys.
|
||||||
* @param delay the amount of time that a task needs to delayed for.
|
* @param delay the amount of time that a task needs to delayed for.
|
||||||
* @return the enqueued task.
|
* @return the enqueued task.
|
||||||
|
@ -330,6 +302,9 @@ public class CloudTasksUtils implements Serializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract static class SerializableCloudTasksClient implements Serializable {
|
public abstract static class SerializableCloudTasksClient implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 7872861868968535498L;
|
||||||
|
|
||||||
public abstract Task enqueue(String projectId, String locationId, String queueName, Task task);
|
public abstract Task enqueue(String projectId, String locationId, String queueName, Task task);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -102,6 +102,8 @@ public final class RegistryConfig {
|
||||||
@Module
|
@Module
|
||||||
public static final class ConfigModule {
|
public static final class ConfigModule {
|
||||||
|
|
||||||
|
private ConfigModule() {}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Config("projectId")
|
@Config("projectId")
|
||||||
public static String provideProjectId(RegistryConfigSettings config) {
|
public static String provideProjectId(RegistryConfigSettings config) {
|
||||||
|
@ -120,17 +122,6 @@ public final class RegistryConfig {
|
||||||
return config.gcpProject.locationId;
|
return config.gcpProject.locationId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Config("serviceAccountEmails")
|
|
||||||
public static ImmutableList<String> provideServiceAccountEmails(RegistryConfigSettings config) {
|
|
||||||
return ImmutableList.copyOf(config.gcpProject.serviceAccountEmails);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Config("defaultServiceAccount")
|
|
||||||
public static Optional<String> provideDefaultServiceAccount(RegistryConfigSettings config) {
|
|
||||||
return Optional.ofNullable(config.gcpProject.defaultServiceAccount);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The filename of the logo to be displayed in the header of the registrar console.
|
* The filename of the logo to be displayed in the header of the registrar console.
|
||||||
|
@ -257,7 +248,7 @@ public final class RegistryConfig {
|
||||||
@Provides
|
@Provides
|
||||||
@Config("databaseRetention")
|
@Config("databaseRetention")
|
||||||
public static Duration provideDatabaseRetention() {
|
public static Duration provideDatabaseRetention() {
|
||||||
return RegistryConfig.getDatabaseRetention();
|
return getDatabaseRetention();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -304,7 +295,7 @@ public final class RegistryConfig {
|
||||||
* The maximum number of domain and host updates to batch together to send to
|
* The maximum number of domain and host updates to batch together to send to
|
||||||
* PublishDnsUpdatesAction, to avoid exceeding HTTP request timeout limits.
|
* PublishDnsUpdatesAction, to avoid exceeding HTTP request timeout limits.
|
||||||
*
|
*
|
||||||
* @see google.registry.dns.ReadDnsRefreshRequestsAction
|
* @see ReadDnsRefreshRequestsAction
|
||||||
*/
|
*/
|
||||||
@Provides
|
@Provides
|
||||||
@Config("dnsTldUpdateBatchSize")
|
@Config("dnsTldUpdateBatchSize")
|
||||||
|
@ -1144,7 +1135,7 @@ public final class RegistryConfig {
|
||||||
@Provides
|
@Provides
|
||||||
@Config("availableOauthScopes")
|
@Config("availableOauthScopes")
|
||||||
public static ImmutableSet<String> provideAvailableOauthScopes(RegistryConfigSettings config) {
|
public static ImmutableSet<String> provideAvailableOauthScopes(RegistryConfigSettings config) {
|
||||||
return ImmutableSet.copyOf(config.oAuth.availableOauthScopes);
|
return ImmutableSet.copyOf(config.auth.availableOauthScopes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1157,27 +1148,38 @@ public final class RegistryConfig {
|
||||||
* API, which requires at least one of:
|
* API, which requires at least one of:
|
||||||
*
|
*
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>https://www.googleapis.com/auth/appengine.apis
|
* <li>{@code https://www.googleapis.com/auth/appengine.apis}
|
||||||
* <li>https://www.googleapis.com/auth/cloud-platform
|
* <li>{@code https://www.googleapis.com/auth/cloud-platform}
|
||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
@Provides
|
@Provides
|
||||||
@Config("requiredOauthScopes")
|
@Config("requiredOauthScopes")
|
||||||
public static ImmutableSet<String> provideRequiredOauthScopes(RegistryConfigSettings config) {
|
public static ImmutableSet<String> provideRequiredOauthScopes(RegistryConfigSettings config) {
|
||||||
return ImmutableSet.copyOf(config.oAuth.requiredOauthScopes);
|
return ImmutableSet.copyOf(config.auth.requiredOauthScopes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides service account email addresses allowed to authenticate with the app at {@link
|
||||||
|
* google.registry.request.auth.AuthSettings.AuthLevel#APP} level.
|
||||||
|
*/
|
||||||
|
@Provides
|
||||||
|
@Config("allowedServiceAccountEmails")
|
||||||
|
public static ImmutableSet<String> provideAllowedServiceAccountEmails(
|
||||||
|
RegistryConfigSettings config) {
|
||||||
|
return ImmutableSet.copyOf(config.auth.allowedServiceAccountEmails);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Provides the allowed OAuth client IDs (could be multibinding). */
|
/** Provides the allowed OAuth client IDs (could be multibinding). */
|
||||||
@Provides
|
@Provides
|
||||||
@Config("allowedOauthClientIds")
|
@Config("allowedOauthClientIds")
|
||||||
public static ImmutableSet<String> provideAllowedOauthClientIds(RegistryConfigSettings config) {
|
public static ImmutableSet<String> provideAllowedOauthClientIds(RegistryConfigSettings config) {
|
||||||
return ImmutableSet.copyOf(config.oAuth.allowedOauthClientIds);
|
return ImmutableSet.copyOf(config.auth.allowedOauthClientIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Config("iapClientId")
|
@Config("oauthClientId")
|
||||||
public static Optional<String> provideIapClientId(RegistryConfigSettings config) {
|
public static String provideOauthClientId(RegistryConfigSettings config) {
|
||||||
return Optional.ofNullable(config.oAuth.iapClientId);
|
return config.auth.oauthClientId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1253,7 +1255,7 @@ public final class RegistryConfig {
|
||||||
toImmutableSortedMap(
|
toImmutableSortedMap(
|
||||||
naturalOrder(),
|
naturalOrder(),
|
||||||
e ->
|
e ->
|
||||||
e.getKey().equals("START_OF_TIME")
|
"START_OF_TIME".equals(e.getKey())
|
||||||
? START_OF_TIME
|
? START_OF_TIME
|
||||||
: DateTime.parse(e.getKey()),
|
: DateTime.parse(e.getKey()),
|
||||||
Entry::getValue));
|
Entry::getValue));
|
||||||
|
@ -1374,6 +1376,12 @@ public final class RegistryConfig {
|
||||||
public static String providePackageDomainLimitUpgradeEmailBody(RegistryConfigSettings config) {
|
public static String providePackageDomainLimitUpgradeEmailBody(RegistryConfigSettings config) {
|
||||||
return config.packageMonitoring.packageDomainLimitUpgradeEmailBody;
|
return config.packageMonitoring.packageDomainLimitUpgradeEmailBody;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String formatComments(String text) {
|
||||||
|
return Splitter.on('\n').omitEmptyStrings().trimResults().splitToList(text).stream()
|
||||||
|
.map(s -> "# " + s)
|
||||||
|
.collect(Collectors.joining("\n"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the App Engine project ID, which is based off the environment name. */
|
/** Returns the App Engine project ID, which is based off the environment name. */
|
||||||
|
@ -1539,9 +1547,9 @@ public final class RegistryConfig {
|
||||||
* one single INSERT statement which can dramatically increase speed in situations with many
|
* one single INSERT statement which can dramatically increase speed in situations with many
|
||||||
* inserts.
|
* inserts.
|
||||||
*
|
*
|
||||||
* <p>Hibernate docs, i.e.
|
* <p><a
|
||||||
* https://docs.jboss.org/hibernate/orm/5.6/userguide/html_single/Hibernate_User_Guide.html,
|
* href="https://docs.jboss.org/hibernate/orm/5.6/userguide/html_single/Hibernate_User_Guide.html">Hibernate
|
||||||
* recommend between 10 and 50.
|
* User Guide</a> recommends between 10 and 50.
|
||||||
*/
|
*/
|
||||||
public static int getHibernateJdbcBatchSize() {
|
public static int getHibernateJdbcBatchSize() {
|
||||||
return CONFIG_SETTINGS.get().hibernate.jdbcBatchSize;
|
return CONFIG_SETTINGS.get().hibernate.jdbcBatchSize;
|
||||||
|
@ -1578,11 +1586,7 @@ public final class RegistryConfig {
|
||||||
public static final Supplier<RegistryConfigSettings> CONFIG_SETTINGS =
|
public static final Supplier<RegistryConfigSettings> CONFIG_SETTINGS =
|
||||||
memoize(RegistryConfig::getConfigSettings);
|
memoize(RegistryConfig::getConfigSettings);
|
||||||
|
|
||||||
private static String formatComments(String text) {
|
|
||||||
return Splitter.on('\n').omitEmptyStrings().trimResults().splitToList(text).stream()
|
|
||||||
.map(s -> "# " + s)
|
|
||||||
.collect(Collectors.joining("\n"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static InternetAddress parseEmailAddress(String email) {
|
private static InternetAddress parseEmailAddress(String email) {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -23,7 +23,7 @@ public class RegistryConfigSettings {
|
||||||
|
|
||||||
public GcpProject gcpProject;
|
public GcpProject gcpProject;
|
||||||
public GSuite gSuite;
|
public GSuite gSuite;
|
||||||
public OAuth oAuth;
|
public Auth auth;
|
||||||
public CredentialOAuth credentialOAuth;
|
public CredentialOAuth credentialOAuth;
|
||||||
public RegistryPolicy registryPolicy;
|
public RegistryPolicy registryPolicy;
|
||||||
public Hibernate hibernate;
|
public Hibernate hibernate;
|
||||||
|
@ -54,16 +54,15 @@ public class RegistryConfigSettings {
|
||||||
public String backendServiceUrl;
|
public String backendServiceUrl;
|
||||||
public String toolsServiceUrl;
|
public String toolsServiceUrl;
|
||||||
public String pubapiServiceUrl;
|
public String pubapiServiceUrl;
|
||||||
public List<String> serviceAccountEmails;
|
|
||||||
public String defaultServiceAccount;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Configuration options for OAuth settings for authenticating users. */
|
/** Configuration options for authenticating users. */
|
||||||
public static class OAuth {
|
public static class Auth {
|
||||||
public List<String> availableOauthScopes;
|
public List<String> availableOauthScopes;
|
||||||
public List<String> requiredOauthScopes;
|
public List<String> requiredOauthScopes;
|
||||||
public List<String> allowedOauthClientIds;
|
public List<String> allowedOauthClientIds;
|
||||||
public String iapClientId;
|
public List<String> allowedServiceAccountEmails;
|
||||||
|
public String oauthClientId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Configuration options for accessing Google APIs. */
|
/** Configuration options for accessing Google APIs. */
|
||||||
|
|
|
@ -18,18 +18,10 @@ gcpProject:
|
||||||
# whether to use local/test credentials when connecting to the servers
|
# whether to use local/test credentials when connecting to the servers
|
||||||
isLocal: true
|
isLocal: true
|
||||||
# URLs of the services for the project.
|
# URLs of the services for the project.
|
||||||
defaultServiceUrl: https://localhost
|
defaultServiceUrl: https://default.example.com
|
||||||
backendServiceUrl: https://localhost
|
backendServiceUrl: https://backend.example.com
|
||||||
toolsServiceUrl: https://localhost
|
toolsServiceUrl: https://tools.example.com
|
||||||
pubapiServiceUrl: https://localhost
|
pubapiServiceUrl: https://pubapi.example.com
|
||||||
# Service accounts eligible for authorization (e.g. default service account,
|
|
||||||
# account used by Cloud Scheduler) to send authenticated requests.
|
|
||||||
serviceAccountEmails:
|
|
||||||
- default-service-account-email@email.com
|
|
||||||
- cloud-scheduler-email@email.com
|
|
||||||
# The default service account with which the service is running. For example,
|
|
||||||
# on GAE this would be {project-id}@appspot.gserviceaccount.com
|
|
||||||
defaultServiceAccount: null
|
|
||||||
|
|
||||||
gSuite:
|
gSuite:
|
||||||
# Publicly accessible domain name of the running G Suite instance.
|
# Publicly accessible domain name of the running G Suite instance.
|
||||||
|
@ -295,24 +287,41 @@ caching:
|
||||||
# long duration is acceptable because claims lists don't change frequently.
|
# long duration is acceptable because claims lists don't change frequently.
|
||||||
claimsListCachingSeconds: 21600 # six hours
|
claimsListCachingSeconds: 21600 # six hours
|
||||||
|
|
||||||
oAuth:
|
# Note: Only allowedServiceAccountEmails and oauthClientId should be configured.
|
||||||
|
# Other fields are related to OAuth-based authentication and will be removed.
|
||||||
|
auth:
|
||||||
|
# Deprecated: Use OIDC-based auth instead. This field is for OAuth-based auth.
|
||||||
# OAuth scopes to detect on access tokens. Superset of requiredOauthScopes.
|
# OAuth scopes to detect on access tokens. Superset of requiredOauthScopes.
|
||||||
availableOauthScopes:
|
availableOauthScopes:
|
||||||
- https://www.googleapis.com/auth/userinfo.email
|
- https://www.googleapis.com/auth/userinfo.email
|
||||||
|
|
||||||
|
# Deprecated: Use OIDC-based auth instead. This field is for OAuth-based auth.
|
||||||
# OAuth scopes required for authenticating. Subset of availableOauthScopes.
|
# OAuth scopes required for authenticating. Subset of availableOauthScopes.
|
||||||
requiredOauthScopes:
|
requiredOauthScopes:
|
||||||
- https://www.googleapis.com/auth/userinfo.email
|
- https://www.googleapis.com/auth/userinfo.email
|
||||||
|
|
||||||
|
# Deprecated: Use OIDC-based auth instead. This field is for OAuth-based auth.
|
||||||
# OAuth client IDs that are allowed to authenticate and communicate with
|
# OAuth client IDs that are allowed to authenticate and communicate with
|
||||||
# backend services, e. g. nomulus tool, EPP proxy, etc. The client_id value
|
# backend services, e.g. nomulus tool, EPP proxy, etc. The value in
|
||||||
# used in registryTool.clientId field for associated tooling should be included
|
# registryTool.clientId field should be included in this list. Client IDs are
|
||||||
# in this list. Client IDs are typically of the format
|
# typically of the format
|
||||||
# numbers-alphanumerics.apps.googleusercontent.com
|
# numbers-alphanumerics.apps.googleusercontent.com
|
||||||
allowedOauthClientIds: []
|
allowedOauthClientIds: []
|
||||||
# GCP Identity-Aware Proxy client ID, if set up (note: this requires manual setup
|
|
||||||
# of User objects in the database for Nomulus tool users)
|
# Service accounts (e.g. default service account, account used by Cloud
|
||||||
iapClientId: null
|
# Scheduler) allowed to send authenticated requests.
|
||||||
|
allowedServiceAccountEmails:
|
||||||
|
- default-service-account-email@email.com
|
||||||
|
- cloud-scheduler-email@email.com
|
||||||
|
|
||||||
|
# OAuth 2.0 client ID that will be used as the audience in OIDC ID tokens sent
|
||||||
|
# from clients (e.g. proxy, nomulus tool, cloud tasks) for authentication. The
|
||||||
|
# same ID is the only one accepted by the regular OIDC or IAP authentication
|
||||||
|
# mechanisms. In most cases we should use the client ID created for IAP here,
|
||||||
|
# as it allows requests bearing a token with this audience to be accepted by
|
||||||
|
# both IAP or regular OIDC. The clientId value in proxy config file should be
|
||||||
|
# the same as this one.
|
||||||
|
oauthClientId: iap-oauth-clientid
|
||||||
|
|
||||||
credentialOAuth:
|
credentialOAuth:
|
||||||
# OAuth scopes required for accessing Google APIs using the default
|
# OAuth scopes required for accessing Google APIs using the default
|
||||||
|
|
|
@ -140,25 +140,13 @@ public final class TldFanoutAction implements Runnable {
|
||||||
for (String tld : tlds) {
|
for (String tld : tlds) {
|
||||||
Task task = createTask(tld, flowThruParams);
|
Task task = createTask(tld, flowThruParams);
|
||||||
Task createdTask = cloudTasksUtils.enqueue(queue, task);
|
Task createdTask = cloudTasksUtils.enqueue(queue, task);
|
||||||
if (createdTask.hasAppEngineHttpRequest()) {
|
outputPayload.append(
|
||||||
outputPayload.append(
|
String.format(
|
||||||
String.format(
|
"- Task: '%s', tld: '%s', endpoint: '%s'\n",
|
||||||
"- Task: '%s', tld: '%s', endpoint: '%s'\n",
|
createdTask.getName(), tld, createdTask.getHttpRequest().getUrl()));
|
||||||
createdTask.getName(),
|
logger.atInfo().log(
|
||||||
tld,
|
"Task: '%s', tld: '%s', endpoint: '%s'.",
|
||||||
createdTask.getAppEngineHttpRequest().getRelativeUri()));
|
createdTask.getName(), tld, createdTask.getHttpRequest().getUrl());
|
||||||
logger.atInfo().log(
|
|
||||||
"Task: '%s', tld: '%s', endpoint: '%s'.",
|
|
||||||
createdTask.getName(), tld, createdTask.getAppEngineHttpRequest().getRelativeUri());
|
|
||||||
} else {
|
|
||||||
outputPayload.append(
|
|
||||||
String.format(
|
|
||||||
"- Task: '%s', tld: '%s', endpoint: '%s'\n",
|
|
||||||
createdTask.getName(), tld, createdTask.getHttpRequest().getUrl()));
|
|
||||||
logger.atInfo().log(
|
|
||||||
"Task: '%s', tld: '%s', endpoint: '%s'.",
|
|
||||||
createdTask.getName(), tld, createdTask.getHttpRequest().getUrl());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
response.setContentType(PLAIN_TEXT_UTF_8);
|
response.setContentType(PLAIN_TEXT_UTF_8);
|
||||||
response.setPayload(outputPayload.toString());
|
response.setPayload(outputPayload.toString());
|
||||||
|
|
|
@ -44,7 +44,7 @@ public class AuthModule {
|
||||||
// See: https://cloud.google.com/iap/docs/signed-headers-howto#verifying_the_jwt_payload
|
// See: https://cloud.google.com/iap/docs/signed-headers-howto#verifying_the_jwt_payload
|
||||||
private static final String IAP_AUDIENCE_FORMAT = "/projects/%d/apps/%s";
|
private static final String IAP_AUDIENCE_FORMAT = "/projects/%d/apps/%s";
|
||||||
private static final String IAP_ISSUER_URL = "https://cloud.google.com/iap";
|
private static final String IAP_ISSUER_URL = "https://cloud.google.com/iap";
|
||||||
private static final String SA_ISSUER_URL = "https://accounts.google.com";
|
private static final String REGULAR_ISSUER_URL = "https://accounts.google.com";
|
||||||
|
|
||||||
/** Provides the custom authentication mechanisms (including OAuth and OIDC). */
|
/** Provides the custom authentication mechanisms (including OAuth and OIDC). */
|
||||||
@Provides
|
@Provides
|
||||||
|
@ -82,8 +82,8 @@ public class AuthModule {
|
||||||
@Provides
|
@Provides
|
||||||
@RegularOidc
|
@RegularOidc
|
||||||
@Singleton
|
@Singleton
|
||||||
TokenVerifier provideRegularTokenVerifier(@Config("projectId") String projectId) {
|
TokenVerifier provideRegularTokenVerifier(@Config("oauthClientId") String clientId) {
|
||||||
return TokenVerifier.newBuilder().setAudience(projectId).setIssuer(SA_ISSUER_URL).build();
|
return TokenVerifier.newBuilder().setAudience(clientId).setIssuer(REGULAR_ISSUER_URL).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|
|
@ -32,7 +32,7 @@ import javax.servlet.http.HttpServletRequest;
|
||||||
/**
|
/**
|
||||||
* OAuth authentication mechanism, using the OAuthService interface.
|
* OAuth authentication mechanism, using the OAuthService interface.
|
||||||
*
|
*
|
||||||
* Only OAuth version 2 is supported.
|
* <p>Only OAuth version 2 is supported.
|
||||||
*/
|
*/
|
||||||
public class OAuthAuthenticationMechanism implements AuthenticationMechanism {
|
public class OAuthAuthenticationMechanism implements AuthenticationMechanism {
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ import static google.registry.request.auth.AuthSettings.AuthLevel.APP;
|
||||||
import com.google.api.client.json.webtoken.JsonWebSignature;
|
import com.google.api.client.json.webtoken.JsonWebSignature;
|
||||||
import com.google.auth.oauth2.TokenVerifier;
|
import com.google.auth.oauth2.TokenVerifier;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.flogger.FluentLogger;
|
import com.google.common.flogger.FluentLogger;
|
||||||
import google.registry.config.RegistryConfig.Config;
|
import google.registry.config.RegistryConfig.Config;
|
||||||
import google.registry.config.RegistryEnvironment;
|
import google.registry.config.RegistryEnvironment;
|
||||||
|
@ -57,10 +57,10 @@ public abstract class OidcTokenAuthenticationMechanism implements Authentication
|
||||||
|
|
||||||
protected final TokenExtractor tokenExtractor;
|
protected final TokenExtractor tokenExtractor;
|
||||||
|
|
||||||
private final ImmutableList<String> serviceAccountEmails;
|
private final ImmutableSet<String> serviceAccountEmails;
|
||||||
|
|
||||||
protected OidcTokenAuthenticationMechanism(
|
protected OidcTokenAuthenticationMechanism(
|
||||||
ImmutableList<String> serviceAccountEmails,
|
ImmutableSet<String> serviceAccountEmails,
|
||||||
TokenVerifier tokenVerifier,
|
TokenVerifier tokenVerifier,
|
||||||
TokenExtractor tokenExtractor) {
|
TokenExtractor tokenExtractor) {
|
||||||
this.serviceAccountEmails = serviceAccountEmails;
|
this.serviceAccountEmails = serviceAccountEmails;
|
||||||
|
@ -83,7 +83,11 @@ public abstract class OidcTokenAuthenticationMechanism implements Authentication
|
||||||
try {
|
try {
|
||||||
token = tokenVerifier.verify(rawIdToken);
|
token = tokenVerifier.verify(rawIdToken);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.atInfo().withCause(e).log("Error when verifying access token");
|
logger.atInfo().withCause(e).log(
|
||||||
|
"Failed OIDC verification attempt:\n%s",
|
||||||
|
RegistryEnvironment.get().equals(RegistryEnvironment.PRODUCTION)
|
||||||
|
? "Raw token redacted in prod"
|
||||||
|
: rawIdToken);
|
||||||
return AuthResult.NOT_AUTHENTICATED;
|
return AuthResult.NOT_AUTHENTICATED;
|
||||||
}
|
}
|
||||||
String email = (String) token.getPayload().get("email");
|
String email = (String) token.getPayload().get("email");
|
||||||
|
@ -95,6 +99,7 @@ public abstract class OidcTokenAuthenticationMechanism implements Authentication
|
||||||
if (maybeUser.isPresent()) {
|
if (maybeUser.isPresent()) {
|
||||||
return AuthResult.create(AuthLevel.USER, UserAuthInfo.create(maybeUser.get()));
|
return AuthResult.create(AuthLevel.USER, UserAuthInfo.create(maybeUser.get()));
|
||||||
}
|
}
|
||||||
|
// TODO: implement caching so we don't have to look up the database for every request.
|
||||||
logger.atInfo().log("No end user found for email address %s", email);
|
logger.atInfo().log("No end user found for email address %s", email);
|
||||||
if (serviceAccountEmails.stream().anyMatch(e -> e.equals(email))) {
|
if (serviceAccountEmails.stream().anyMatch(e -> e.equals(email))) {
|
||||||
return AuthResult.create(APP);
|
return AuthResult.create(APP);
|
||||||
|
@ -136,7 +141,7 @@ public abstract class OidcTokenAuthenticationMechanism implements Authentication
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
protected IapOidcAuthenticationMechanism(
|
protected IapOidcAuthenticationMechanism(
|
||||||
@Config("serviceAccountEmails") ImmutableList<String> serviceAccountEmails,
|
@Config("allowedServiceAccountEmails") ImmutableSet<String> serviceAccountEmails,
|
||||||
@IapOidc TokenVerifier tokenVerifier,
|
@IapOidc TokenVerifier tokenVerifier,
|
||||||
@IapOidc TokenExtractor tokenExtractor) {
|
@IapOidc TokenExtractor tokenExtractor) {
|
||||||
super(serviceAccountEmails, tokenVerifier, tokenExtractor);
|
super(serviceAccountEmails, tokenVerifier, tokenExtractor);
|
||||||
|
@ -164,7 +169,7 @@ public abstract class OidcTokenAuthenticationMechanism implements Authentication
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
protected RegularOidcAuthenticationMechanism(
|
protected RegularOidcAuthenticationMechanism(
|
||||||
@Config("serviceAccountEmails") ImmutableList<String> serviceAccountEmails,
|
@Config("allowedServiceAccountEmails") ImmutableSet<String> serviceAccountEmails,
|
||||||
@RegularOidc TokenVerifier tokenVerifier,
|
@RegularOidc TokenVerifier tokenVerifier,
|
||||||
@RegularOidc TokenExtractor tokenExtractor) {
|
@RegularOidc TokenExtractor tokenExtractor) {
|
||||||
super(serviceAccountEmails, tokenVerifier, tokenExtractor);
|
super(serviceAccountEmails, tokenVerifier, tokenExtractor);
|
||||||
|
|
|
@ -14,23 +14,17 @@
|
||||||
|
|
||||||
package google.registry.tools;
|
package google.registry.tools;
|
||||||
|
|
||||||
import com.google.api.client.http.GenericUrl;
|
import static com.google.common.net.HttpHeaders.PROXY_AUTHORIZATION;
|
||||||
import com.google.api.client.http.HttpRequest;
|
|
||||||
import com.google.api.client.http.HttpRequestFactory;
|
import com.google.api.client.http.HttpRequestFactory;
|
||||||
import com.google.api.client.http.HttpResponse;
|
|
||||||
import com.google.api.client.http.UrlEncodedContent;
|
|
||||||
import com.google.api.client.http.javanet.NetHttpTransport;
|
import com.google.api.client.http.javanet.NetHttpTransport;
|
||||||
import com.google.api.client.util.GenericData;
|
|
||||||
import com.google.auth.oauth2.UserCredentials;
|
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
import google.registry.config.CredentialModule.ApplicationDefaultCredential;
|
import google.registry.config.CredentialModule.ApplicationDefaultCredential;
|
||||||
import google.registry.config.RegistryConfig;
|
import google.registry.config.RegistryConfig;
|
||||||
import google.registry.config.RegistryConfig.Config;
|
import google.registry.config.RegistryConfig.Config;
|
||||||
import google.registry.util.GoogleCredentialsBundle;
|
import google.registry.util.GoogleCredentialsBundle;
|
||||||
import java.io.IOException;
|
import google.registry.util.OidcTokenUtils;
|
||||||
import java.net.URI;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Module for providing the HttpRequestFactory.
|
* Module for providing the HttpRequestFactory.
|
||||||
|
@ -39,25 +33,16 @@ import java.util.Optional;
|
||||||
* connections in that they don't require OAuth2 credentials, but instead require a special cookie.
|
* connections in that they don't require OAuth2 credentials, but instead require a special cookie.
|
||||||
*/
|
*/
|
||||||
@Module
|
@Module
|
||||||
class RequestFactoryModule {
|
final class RequestFactoryModule {
|
||||||
|
|
||||||
static final int REQUEST_TIMEOUT_MS = 10 * 60 * 1000;
|
static final int REQUEST_TIMEOUT_MS = 10 * 60 * 1000;
|
||||||
|
|
||||||
/**
|
private RequestFactoryModule() {}
|
||||||
* Server to use if we want to manually request an IAP ID token
|
|
||||||
*
|
|
||||||
* <p>If we need to have an IAP-enabled audience, we can use the existing refresh token and the
|
|
||||||
* IAP client ID audience to request an IAP-enabled ID token. This token is read and used by
|
|
||||||
* {@link IapHeaderAuthenticationMechanismMechanism}, and it requires that the user have a {@link
|
|
||||||
* google.registry.model.console.User} object present in the database.
|
|
||||||
*/
|
|
||||||
private static final GenericUrl TOKEN_SERVER_URL =
|
|
||||||
new GenericUrl(URI.create("https://oauth2.googleapis.com/token"));
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
static HttpRequestFactory provideHttpRequestFactory(
|
static HttpRequestFactory provideHttpRequestFactory(
|
||||||
@ApplicationDefaultCredential GoogleCredentialsBundle credentialsBundle,
|
@ApplicationDefaultCredential GoogleCredentialsBundle credentialsBundle,
|
||||||
@Config("iapClientId") Optional<String> iapClientId) {
|
@Config("oauthClientId") String oauthClientId) {
|
||||||
if (RegistryConfig.areServersLocal()) {
|
if (RegistryConfig.areServersLocal()) {
|
||||||
return new NetHttpTransport()
|
return new NetHttpTransport()
|
||||||
.createRequestFactory(
|
.createRequestFactory(
|
||||||
|
@ -71,14 +56,13 @@ class RequestFactoryModule {
|
||||||
request -> {
|
request -> {
|
||||||
// Use the standard credential initializer to set the Authorization header
|
// Use the standard credential initializer to set the Authorization header
|
||||||
credentialsBundle.getHttpRequestInitializer().initialize(request);
|
credentialsBundle.getHttpRequestInitializer().initialize(request);
|
||||||
// If using IAP, use the refresh token to acquire an IAP-enabled ID token and use
|
// Set OIDC token as the alternative bearer token.
|
||||||
// that for authentication.
|
request
|
||||||
if (iapClientId.isPresent()) {
|
.getHeaders()
|
||||||
String idToken = getIdToken(credentialsBundle, iapClientId.get());
|
.set(
|
||||||
// Set the Proxy-Authentication header so that IAP can read from it, see
|
PROXY_AUTHORIZATION,
|
||||||
// https://cloud.google.com/iap/docs/authentication-howto#authenticating_from_proxy-authorization_header
|
"Bearer "
|
||||||
request.getHeaders().set("Proxy-Authorization", "Bearer " + idToken);
|
+ OidcTokenUtils.createOidcToken(credentialsBundle, oauthClientId));
|
||||||
}
|
|
||||||
// GAE request times out after 10 min, so here we set the timeout to 10 min. This is
|
// GAE request times out after 10 min, so here we set the timeout to 10 min. This is
|
||||||
// needed to support some nomulus commands like updating premium lists that take
|
// needed to support some nomulus commands like updating premium lists that take
|
||||||
// a lot of time to complete.
|
// a lot of time to complete.
|
||||||
|
@ -89,32 +73,4 @@ class RequestFactoryModule {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Uses the saved desktop-app refresh token to acquire an IAP ID token.
|
|
||||||
*
|
|
||||||
* <p>This is lifted mostly from the Google Auth Library's {@link UserCredentials}
|
|
||||||
* "doRefreshAccessToken" method (which is private and thus inaccessible) while adding in the
|
|
||||||
* audience of the IAP client ID. That addition of the audience is what allows us to satisfy IAP
|
|
||||||
* auth. See
|
|
||||||
* https://cloud.google.com/iap/docs/authentication-howto#authenticating_from_a_desktop_app for
|
|
||||||
* more details.
|
|
||||||
*/
|
|
||||||
private static String getIdToken(GoogleCredentialsBundle credentialsBundle, String iapClientId)
|
|
||||||
throws IOException {
|
|
||||||
UserCredentials credentials = (UserCredentials) credentialsBundle.getGoogleCredentials();
|
|
||||||
GenericData tokenRequest = new GenericData();
|
|
||||||
tokenRequest.set("client_id", credentials.getClientId());
|
|
||||||
tokenRequest.set("client_secret", credentials.getClientSecret());
|
|
||||||
tokenRequest.set("refresh_token", credentials.getRefreshToken());
|
|
||||||
tokenRequest.set("audience", iapClientId);
|
|
||||||
tokenRequest.set("grant_type", "refresh_token");
|
|
||||||
UrlEncodedContent content = new UrlEncodedContent(tokenRequest);
|
|
||||||
|
|
||||||
HttpRequestFactory requestFactory = credentialsBundle.getHttpTransport().createRequestFactory();
|
|
||||||
HttpRequest request = requestFactory.buildPostRequest(TOKEN_SERVER_URL, content);
|
|
||||||
request.setParser(credentialsBundle.getJsonFactory().createJsonObjectParser());
|
|
||||||
HttpResponse response = request.execute();
|
|
||||||
return response.parseAs(GenericData.class).get("id_token").toString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,7 +73,7 @@ public class AsyncTaskEnqueuerTest {
|
||||||
cloudTasksHelper.assertTasksEnqueued(
|
cloudTasksHelper.assertTasksEnqueued(
|
||||||
QUEUE_ASYNC_ACTIONS,
|
QUEUE_ASYNC_ACTIONS,
|
||||||
new CloudTasksHelper.TaskMatcher()
|
new CloudTasksHelper.TaskMatcher()
|
||||||
.url(ResaveEntityAction.PATH)
|
.path(ResaveEntityAction.PATH)
|
||||||
.method(HttpMethod.POST)
|
.method(HttpMethod.POST)
|
||||||
.service("backend")
|
.service("backend")
|
||||||
.header("content-type", "application/x-www-form-urlencoded")
|
.header("content-type", "application/x-www-form-urlencoded")
|
||||||
|
@ -93,7 +93,7 @@ public class AsyncTaskEnqueuerTest {
|
||||||
cloudTasksHelper.assertTasksEnqueued(
|
cloudTasksHelper.assertTasksEnqueued(
|
||||||
QUEUE_ASYNC_ACTIONS,
|
QUEUE_ASYNC_ACTIONS,
|
||||||
new TaskMatcher()
|
new TaskMatcher()
|
||||||
.url(ResaveEntityAction.PATH)
|
.path(ResaveEntityAction.PATH)
|
||||||
.method(HttpMethod.POST)
|
.method(HttpMethod.POST)
|
||||||
.service("backend")
|
.service("backend")
|
||||||
.header("content-type", "application/x-www-form-urlencoded")
|
.header("content-type", "application/x-www-form-urlencoded")
|
||||||
|
|
|
@ -30,6 +30,7 @@ import com.google.common.collect.ImmutableMultimap;
|
||||||
import com.google.common.collect.LinkedListMultimap;
|
import com.google.common.collect.LinkedListMultimap;
|
||||||
import google.registry.batch.CloudTasksUtils.SerializableCloudTasksClient;
|
import google.registry.batch.CloudTasksUtils.SerializableCloudTasksClient;
|
||||||
import google.registry.request.Action.Service;
|
import google.registry.request.Action.Service;
|
||||||
|
import google.registry.testing.CloudTasksHelper.FakeGoogleCredentialsBundle;
|
||||||
import google.registry.testing.FakeClock;
|
import google.registry.testing.FakeClock;
|
||||||
import google.registry.testing.FakeSleeper;
|
import google.registry.testing.FakeSleeper;
|
||||||
import google.registry.util.Retrier;
|
import google.registry.util.Retrier;
|
||||||
|
@ -47,14 +48,14 @@ public class CloudTasksUtilsTest {
|
||||||
private final LinkedListMultimap<String, String> params = LinkedListMultimap.create();
|
private final LinkedListMultimap<String, String> params = LinkedListMultimap.create();
|
||||||
private final SerializableCloudTasksClient mockClient = mock(SerializableCloudTasksClient.class);
|
private final SerializableCloudTasksClient mockClient = mock(SerializableCloudTasksClient.class);
|
||||||
private final FakeClock clock = new FakeClock(DateTime.parse("2021-11-08"));
|
private final FakeClock clock = new FakeClock(DateTime.parse("2021-11-08"));
|
||||||
private CloudTasksUtils cloudTasksUtils =
|
private final CloudTasksUtils cloudTasksUtils =
|
||||||
new CloudTasksUtils(
|
new CloudTasksUtils(
|
||||||
new Retrier(new FakeSleeper(clock), 1),
|
new Retrier(new FakeSleeper(clock), 1),
|
||||||
clock,
|
clock,
|
||||||
"project",
|
"project",
|
||||||
"location",
|
"location",
|
||||||
Optional.empty(),
|
"clientId",
|
||||||
Optional.empty(),
|
FakeGoogleCredentialsBundle.create(),
|
||||||
mockClient);
|
mockClient);
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
|
@ -66,208 +67,6 @@ public class CloudTasksUtilsTest {
|
||||||
.thenAnswer(invocation -> invocation.getArgument(3));
|
.thenAnswer(invocation -> invocation.getArgument(3));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
void testSuccess_createGetTasks() {
|
|
||||||
Task task = cloudTasksUtils.createGetTask("/the/path", Service.BACKEND, params);
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.GET);
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getRelativeUri())
|
|
||||||
.isEqualTo("/the/path?key1=val1&key2=val2&key1=val3");
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getAppEngineRouting().getService())
|
|
||||||
.isEqualTo(Service.BACKEND.toString());
|
|
||||||
assertThat(task.getScheduleTime().getSeconds()).isEqualTo(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testSuccess_createPostTasks() {
|
|
||||||
Task task = cloudTasksUtils.createPostTask("/the/path", Service.BACKEND, params);
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.POST);
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getRelativeUri()).isEqualTo("/the/path");
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getAppEngineRouting().getService())
|
|
||||||
.isEqualTo(Service.BACKEND.toString());
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getHeadersMap().get("Content-Type"))
|
|
||||||
.isEqualTo("application/x-www-form-urlencoded");
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getBody().toString(StandardCharsets.UTF_8))
|
|
||||||
.isEqualTo("key1=val1&key2=val2&key1=val3");
|
|
||||||
assertThat(task.getScheduleTime().getSeconds()).isEqualTo(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testSuccess_createGetTasks_withNullParams() {
|
|
||||||
Task task = cloudTasksUtils.createGetTask("/the/path", Service.BACKEND, null);
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.GET);
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getRelativeUri()).isEqualTo("/the/path");
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getAppEngineRouting().getService())
|
|
||||||
.isEqualTo(Service.BACKEND.toString());
|
|
||||||
assertThat(task.getScheduleTime().getSeconds()).isEqualTo(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testSuccess_createPostTasks_withNullParams() {
|
|
||||||
Task task = cloudTasksUtils.createPostTask("/the/path", Service.BACKEND, null);
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.POST);
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getRelativeUri()).isEqualTo("/the/path");
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getAppEngineRouting().getService())
|
|
||||||
.isEqualTo(Service.BACKEND.toString());
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getBody().toString(StandardCharsets.UTF_8)).isEmpty();
|
|
||||||
assertThat(task.getScheduleTime().getSeconds()).isEqualTo(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testSuccess_createGetTasks_withEmptyParams() {
|
|
||||||
Task task = cloudTasksUtils.createGetTask("/the/path", Service.BACKEND, ImmutableMultimap.of());
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.GET);
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getRelativeUri()).isEqualTo("/the/path");
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getAppEngineRouting().getService())
|
|
||||||
.isEqualTo(Service.BACKEND.toString());
|
|
||||||
assertThat(task.getScheduleTime().getSeconds()).isEqualTo(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testSuccess_createPostTasks_withEmptyParams() {
|
|
||||||
Task task =
|
|
||||||
cloudTasksUtils.createPostTask("/the/path", Service.BACKEND, ImmutableMultimap.of());
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.POST);
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getRelativeUri()).isEqualTo("/the/path");
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getAppEngineRouting().getService())
|
|
||||||
.isEqualTo(Service.BACKEND.toString());
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getBody().toString(StandardCharsets.UTF_8)).isEmpty();
|
|
||||||
assertThat(task.getScheduleTime().getSeconds()).isEqualTo(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("ProtoTimestampGetSecondsGetNano")
|
|
||||||
@Test
|
|
||||||
void testSuccess_createGetTasks_withJitterSeconds() {
|
|
||||||
Task task =
|
|
||||||
cloudTasksUtils.createGetTaskWithJitter(
|
|
||||||
"/the/path", Service.BACKEND, params, Optional.of(100));
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.GET);
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getRelativeUri())
|
|
||||||
.isEqualTo("/the/path?key1=val1&key2=val2&key1=val3");
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getAppEngineRouting().getService())
|
|
||||||
.isEqualTo(Service.BACKEND.toString());
|
|
||||||
|
|
||||||
Instant scheduleTime = Instant.ofEpochSecond(task.getScheduleTime().getSeconds());
|
|
||||||
Instant lowerBoundTime = Instant.ofEpochMilli(clock.nowUtc().getMillis());
|
|
||||||
Instant upperBound = Instant.ofEpochMilli(clock.nowUtc().plusSeconds(100).getMillis());
|
|
||||||
|
|
||||||
assertThat(scheduleTime.isBefore(lowerBoundTime)).isFalse();
|
|
||||||
assertThat(upperBound.isBefore(scheduleTime)).isFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("ProtoTimestampGetSecondsGetNano")
|
|
||||||
@Test
|
|
||||||
void testSuccess_createPostTasks_withJitterSeconds() {
|
|
||||||
Task task =
|
|
||||||
cloudTasksUtils.createPostTaskWithJitter(
|
|
||||||
"/the/path", Service.BACKEND, params, Optional.of(1));
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.POST);
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getRelativeUri()).isEqualTo("/the/path");
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getAppEngineRouting().getService())
|
|
||||||
.isEqualTo(Service.BACKEND.toString());
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getHeadersMap().get("Content-Type"))
|
|
||||||
.isEqualTo("application/x-www-form-urlencoded");
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getBody().toString(StandardCharsets.UTF_8))
|
|
||||||
.isEqualTo("key1=val1&key2=val2&key1=val3");
|
|
||||||
assertThat(task.getScheduleTime().getSeconds()).isNotEqualTo(0);
|
|
||||||
|
|
||||||
Instant scheduleTime = Instant.ofEpochSecond(task.getScheduleTime().getSeconds());
|
|
||||||
Instant lowerBoundTime = Instant.ofEpochMilli(clock.nowUtc().getMillis());
|
|
||||||
Instant upperBound = Instant.ofEpochMilli(clock.nowUtc().plusSeconds(1).getMillis());
|
|
||||||
|
|
||||||
assertThat(scheduleTime.isBefore(lowerBoundTime)).isFalse();
|
|
||||||
assertThat(upperBound.isBefore(scheduleTime)).isFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testSuccess_createPostTasks_withEmptyJitterSeconds() {
|
|
||||||
Task task =
|
|
||||||
cloudTasksUtils.createPostTaskWithJitter(
|
|
||||||
"/the/path", Service.BACKEND, params, Optional.empty());
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.POST);
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getRelativeUri()).isEqualTo("/the/path");
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getAppEngineRouting().getService())
|
|
||||||
.isEqualTo(Service.BACKEND.toString());
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getHeadersMap().get("Content-Type"))
|
|
||||||
.isEqualTo("application/x-www-form-urlencoded");
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getBody().toString(StandardCharsets.UTF_8))
|
|
||||||
.isEqualTo("key1=val1&key2=val2&key1=val3");
|
|
||||||
assertThat(task.getScheduleTime().getSeconds()).isEqualTo(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testSuccess_createGetTasks_withEmptyJitterSeconds() {
|
|
||||||
Task task =
|
|
||||||
cloudTasksUtils.createGetTaskWithJitter(
|
|
||||||
"/the/path", Service.BACKEND, params, Optional.empty());
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.GET);
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getRelativeUri())
|
|
||||||
.isEqualTo("/the/path?key1=val1&key2=val2&key1=val3");
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getAppEngineRouting().getService())
|
|
||||||
.isEqualTo(Service.BACKEND.toString());
|
|
||||||
assertThat(task.getScheduleTime().getSeconds()).isEqualTo(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testSuccess_createPostTasks_withZeroJitterSeconds() {
|
|
||||||
Task task =
|
|
||||||
cloudTasksUtils.createPostTaskWithJitter(
|
|
||||||
"/the/path", Service.BACKEND, params, Optional.of(0));
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.POST);
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getRelativeUri()).isEqualTo("/the/path");
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getAppEngineRouting().getService())
|
|
||||||
.isEqualTo(Service.BACKEND.toString());
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getHeadersMap().get("Content-Type"))
|
|
||||||
.isEqualTo("application/x-www-form-urlencoded");
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getBody().toString(StandardCharsets.UTF_8))
|
|
||||||
.isEqualTo("key1=val1&key2=val2&key1=val3");
|
|
||||||
assertThat(task.getScheduleTime().getSeconds()).isEqualTo(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testSuccess_createGetTasks_withZeroJitterSeconds() {
|
|
||||||
Task task =
|
|
||||||
cloudTasksUtils.createGetTaskWithJitter(
|
|
||||||
"/the/path", Service.BACKEND, params, Optional.of(0));
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.GET);
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getRelativeUri())
|
|
||||||
.isEqualTo("/the/path?key1=val1&key2=val2&key1=val3");
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getAppEngineRouting().getService())
|
|
||||||
.isEqualTo(Service.BACKEND.toString());
|
|
||||||
assertThat(task.getScheduleTime().getSeconds()).isEqualTo(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testSuccess_createGetTasks_withDelay() {
|
|
||||||
Task task =
|
|
||||||
cloudTasksUtils.createGetTaskWithDelay(
|
|
||||||
"/the/path", Service.BACKEND, params, Duration.standardMinutes(10));
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.GET);
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getRelativeUri())
|
|
||||||
.isEqualTo("/the/path?key1=val1&key2=val2&key1=val3");
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getAppEngineRouting().getService())
|
|
||||||
.isEqualTo(Service.BACKEND.toString());
|
|
||||||
assertThat(Instant.ofEpochSecond(task.getScheduleTime().getSeconds()))
|
|
||||||
.isEqualTo(Instant.ofEpochMilli(clock.nowUtc().plusMinutes(10).getMillis()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testSuccess_createPostTasks_withDelay() {
|
|
||||||
Task task =
|
|
||||||
cloudTasksUtils.createPostTaskWithDelay(
|
|
||||||
"/the/path", Service.BACKEND, params, Duration.standardMinutes(10));
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.POST);
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getRelativeUri()).isEqualTo("/the/path");
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getAppEngineRouting().getService())
|
|
||||||
.isEqualTo(Service.BACKEND.toString());
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getHeadersMap().get("Content-Type"))
|
|
||||||
.isEqualTo("application/x-www-form-urlencoded");
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getBody().toString(StandardCharsets.UTF_8))
|
|
||||||
.isEqualTo("key1=val1&key2=val2&key1=val3");
|
|
||||||
assertThat(task.getScheduleTime().getSeconds()).isNotEqualTo(0);
|
|
||||||
assertThat(Instant.ofEpochSecond(task.getScheduleTime().getSeconds()))
|
|
||||||
.isEqualTo(Instant.ofEpochMilli(clock.nowUtc().plusMinutes(10).getMillis()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testFailure_createGetTasks_withNegativeDelay() {
|
void testFailure_createGetTasks_withNegativeDelay() {
|
||||||
IllegalArgumentException thrown =
|
IllegalArgumentException thrown =
|
||||||
|
@ -290,34 +89,6 @@ public class CloudTasksUtilsTest {
|
||||||
assertThat(thrown).hasMessageThat().isEqualTo("Negative duration is not supported.");
|
assertThat(thrown).hasMessageThat().isEqualTo("Negative duration is not supported.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
void testSuccess_createPostTasks_withZeroDelay() {
|
|
||||||
Task task =
|
|
||||||
cloudTasksUtils.createPostTaskWithDelay(
|
|
||||||
"/the/path", Service.BACKEND, params, Duration.ZERO);
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.POST);
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getRelativeUri()).isEqualTo("/the/path");
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getAppEngineRouting().getService())
|
|
||||||
.isEqualTo(Service.BACKEND.toString());
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getHeadersMap().get("Content-Type"))
|
|
||||||
.isEqualTo("application/x-www-form-urlencoded");
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getBody().toString(StandardCharsets.UTF_8))
|
|
||||||
.isEqualTo("key1=val1&key2=val2&key1=val3");
|
|
||||||
assertThat(task.getScheduleTime().getSeconds()).isEqualTo(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testSuccess_createGetTasks_withZeroDelay() {
|
|
||||||
Task task =
|
|
||||||
cloudTasksUtils.createGetTaskWithDelay("/the/path", Service.BACKEND, params, Duration.ZERO);
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.GET);
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getRelativeUri())
|
|
||||||
.isEqualTo("/the/path?key1=val1&key2=val2&key1=val3");
|
|
||||||
assertThat(task.getAppEngineHttpRequest().getAppEngineRouting().getService())
|
|
||||||
.isEqualTo(Service.BACKEND.toString());
|
|
||||||
assertThat(task.getScheduleTime().getSeconds()).isEqualTo(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testFailure_illegalPath() {
|
void testFailure_illegalPath() {
|
||||||
assertThrows(
|
assertThrows(
|
||||||
|
@ -357,22 +128,20 @@ public class CloudTasksUtilsTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSuccess_nonAppEngine_createGetTasks() {
|
void testSuccess_createGetTasks() {
|
||||||
createOidcTasksUtils();
|
|
||||||
Task task = cloudTasksUtils.createGetTask("/the/path", Service.BACKEND, params);
|
Task task = cloudTasksUtils.createGetTask("/the/path", Service.BACKEND, params);
|
||||||
assertThat(task.getHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.GET);
|
assertThat(task.getHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.GET);
|
||||||
assertThat(task.getHttpRequest().getUrl())
|
assertThat(task.getHttpRequest().getUrl())
|
||||||
.isEqualTo("https://localhost/the/path?key1=val1&key2=val2&key1=val3");
|
.isEqualTo("https://backend.example.com/the/path?key1=val1&key2=val2&key1=val3");
|
||||||
verifyOidcToken(task);
|
verifyOidcToken(task);
|
||||||
assertThat(task.getScheduleTime().getSeconds()).isEqualTo(0);
|
assertThat(task.getScheduleTime().getSeconds()).isEqualTo(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSuccess_nonAppEngine_createPostTasks() {
|
void testSuccess_createPostTasks() {
|
||||||
createOidcTasksUtils();
|
|
||||||
Task task = cloudTasksUtils.createPostTask("/the/path", Service.BACKEND, params);
|
Task task = cloudTasksUtils.createPostTask("/the/path", Service.BACKEND, params);
|
||||||
assertThat(task.getHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.POST);
|
assertThat(task.getHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.POST);
|
||||||
assertThat(task.getHttpRequest().getUrl()).isEqualTo("https://localhost/the/path");
|
assertThat(task.getHttpRequest().getUrl()).isEqualTo("https://backend.example.com/the/path");
|
||||||
assertThat(task.getHttpRequest().getHeadersMap().get("Content-Type"))
|
assertThat(task.getHttpRequest().getHeadersMap().get("Content-Type"))
|
||||||
.isEqualTo("application/x-www-form-urlencoded");
|
.isEqualTo("application/x-www-form-urlencoded");
|
||||||
assertThat(task.getHttpRequest().getBody().toString(StandardCharsets.UTF_8))
|
assertThat(task.getHttpRequest().getBody().toString(StandardCharsets.UTF_8))
|
||||||
|
@ -382,43 +151,39 @@ public class CloudTasksUtilsTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSuccess_nonAppEngine_createGetTasks_withNullParams() {
|
void testSuccess_createGetTasks_withNullParams() {
|
||||||
createOidcTasksUtils();
|
|
||||||
Task task = cloudTasksUtils.createGetTask("/the/path", Service.BACKEND, null);
|
Task task = cloudTasksUtils.createGetTask("/the/path", Service.BACKEND, null);
|
||||||
assertThat(task.getHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.GET);
|
assertThat(task.getHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.GET);
|
||||||
assertThat(task.getHttpRequest().getUrl()).isEqualTo("https://localhost/the/path");
|
assertThat(task.getHttpRequest().getUrl()).isEqualTo("https://backend.example.com/the/path");
|
||||||
verifyOidcToken(task);
|
verifyOidcToken(task);
|
||||||
assertThat(task.getScheduleTime().getSeconds()).isEqualTo(0);
|
assertThat(task.getScheduleTime().getSeconds()).isEqualTo(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSuccess_nonAppEngine_createPostTasks_withNullParams() {
|
void testSuccess_createPostTasks_withNullParams() {
|
||||||
createOidcTasksUtils();
|
|
||||||
Task task = cloudTasksUtils.createPostTask("/the/path", Service.BACKEND, null);
|
Task task = cloudTasksUtils.createPostTask("/the/path", Service.BACKEND, null);
|
||||||
assertThat(task.getHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.POST);
|
assertThat(task.getHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.POST);
|
||||||
assertThat(task.getHttpRequest().getUrl()).isEqualTo("https://localhost/the/path");
|
assertThat(task.getHttpRequest().getUrl()).isEqualTo("https://backend.example.com/the/path");
|
||||||
assertThat(task.getHttpRequest().getBody().toString(StandardCharsets.UTF_8)).isEmpty();
|
assertThat(task.getHttpRequest().getBody().toString(StandardCharsets.UTF_8)).isEmpty();
|
||||||
verifyOidcToken(task);
|
verifyOidcToken(task);
|
||||||
assertThat(task.getScheduleTime().getSeconds()).isEqualTo(0);
|
assertThat(task.getScheduleTime().getSeconds()).isEqualTo(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSuccess_nonAppEngine_createGetTasks_withEmptyParams() {
|
void testSuccess_createGetTasks_withEmptyParams() {
|
||||||
createOidcTasksUtils();
|
|
||||||
Task task = cloudTasksUtils.createGetTask("/the/path", Service.BACKEND, ImmutableMultimap.of());
|
Task task = cloudTasksUtils.createGetTask("/the/path", Service.BACKEND, ImmutableMultimap.of());
|
||||||
assertThat(task.getHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.GET);
|
assertThat(task.getHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.GET);
|
||||||
assertThat(task.getHttpRequest().getUrl()).isEqualTo("https://localhost/the/path");
|
assertThat(task.getHttpRequest().getUrl()).isEqualTo("https://backend.example.com/the/path");
|
||||||
verifyOidcToken(task);
|
verifyOidcToken(task);
|
||||||
assertThat(task.getScheduleTime().getSeconds()).isEqualTo(0);
|
assertThat(task.getScheduleTime().getSeconds()).isEqualTo(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSuccess_nonAppEngine_createPostTasks_withEmptyParams() {
|
void testSuccess_createPostTasks_withEmptyParams() {
|
||||||
createOidcTasksUtils();
|
|
||||||
Task task =
|
Task task =
|
||||||
cloudTasksUtils.createPostTask("/the/path", Service.BACKEND, ImmutableMultimap.of());
|
cloudTasksUtils.createPostTask("/the/path", Service.BACKEND, ImmutableMultimap.of());
|
||||||
assertThat(task.getHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.POST);
|
assertThat(task.getHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.POST);
|
||||||
assertThat(task.getHttpRequest().getUrl()).isEqualTo("https://localhost/the/path");
|
assertThat(task.getHttpRequest().getUrl()).isEqualTo("https://backend.example.com/the/path");
|
||||||
assertThat(task.getHttpRequest().getBody().toString(StandardCharsets.UTF_8)).isEmpty();
|
assertThat(task.getHttpRequest().getBody().toString(StandardCharsets.UTF_8)).isEmpty();
|
||||||
verifyOidcToken(task);
|
verifyOidcToken(task);
|
||||||
assertThat(task.getScheduleTime().getSeconds()).isEqualTo(0);
|
assertThat(task.getScheduleTime().getSeconds()).isEqualTo(0);
|
||||||
|
@ -426,14 +191,13 @@ public class CloudTasksUtilsTest {
|
||||||
|
|
||||||
@SuppressWarnings("ProtoTimestampGetSecondsGetNano")
|
@SuppressWarnings("ProtoTimestampGetSecondsGetNano")
|
||||||
@Test
|
@Test
|
||||||
void testSuccess_nonAppEngine_createGetTasks_withJitterSeconds() {
|
void testSuccess_createGetTasks_withJitterSeconds() {
|
||||||
createOidcTasksUtils();
|
|
||||||
Task task =
|
Task task =
|
||||||
cloudTasksUtils.createGetTaskWithJitter(
|
cloudTasksUtils.createGetTaskWithJitter(
|
||||||
"/the/path", Service.BACKEND, params, Optional.of(100));
|
"/the/path", Service.BACKEND, params, Optional.of(100));
|
||||||
assertThat(task.getHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.GET);
|
assertThat(task.getHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.GET);
|
||||||
assertThat(task.getHttpRequest().getUrl())
|
assertThat(task.getHttpRequest().getUrl())
|
||||||
.isEqualTo("https://localhost/the/path?key1=val1&key2=val2&key1=val3");
|
.isEqualTo("https://backend.example.com/the/path?key1=val1&key2=val2&key1=val3");
|
||||||
verifyOidcToken(task);
|
verifyOidcToken(task);
|
||||||
|
|
||||||
Instant scheduleTime = Instant.ofEpochSecond(task.getScheduleTime().getSeconds());
|
Instant scheduleTime = Instant.ofEpochSecond(task.getScheduleTime().getSeconds());
|
||||||
|
@ -446,13 +210,12 @@ public class CloudTasksUtilsTest {
|
||||||
|
|
||||||
@SuppressWarnings("ProtoTimestampGetSecondsGetNano")
|
@SuppressWarnings("ProtoTimestampGetSecondsGetNano")
|
||||||
@Test
|
@Test
|
||||||
void testSuccess_nonAppEngine_createPostTasks_withJitterSeconds() {
|
void testSuccess_createPostTasks_withJitterSeconds() {
|
||||||
createOidcTasksUtils();
|
|
||||||
Task task =
|
Task task =
|
||||||
cloudTasksUtils.createPostTaskWithJitter(
|
cloudTasksUtils.createPostTaskWithJitter(
|
||||||
"/the/path", Service.BACKEND, params, Optional.of(1));
|
"/the/path", Service.BACKEND, params, Optional.of(1));
|
||||||
assertThat(task.getHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.POST);
|
assertThat(task.getHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.POST);
|
||||||
assertThat(task.getHttpRequest().getUrl()).isEqualTo("https://localhost/the/path");
|
assertThat(task.getHttpRequest().getUrl()).isEqualTo("https://backend.example.com/the/path");
|
||||||
assertThat(task.getHttpRequest().getHeadersMap().get("Content-Type"))
|
assertThat(task.getHttpRequest().getHeadersMap().get("Content-Type"))
|
||||||
.isEqualTo("application/x-www-form-urlencoded");
|
.isEqualTo("application/x-www-form-urlencoded");
|
||||||
assertThat(task.getHttpRequest().getBody().toString(StandardCharsets.UTF_8))
|
assertThat(task.getHttpRequest().getBody().toString(StandardCharsets.UTF_8))
|
||||||
|
@ -469,13 +232,12 @@ public class CloudTasksUtilsTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSuccess_nonAppEngine_createPostTasks_withEmptyJitterSeconds() {
|
void testSuccess_createPostTasks_withEmptyJitterSeconds() {
|
||||||
createOidcTasksUtils();
|
|
||||||
Task task =
|
Task task =
|
||||||
cloudTasksUtils.createPostTaskWithJitter(
|
cloudTasksUtils.createPostTaskWithJitter(
|
||||||
"/the/path", Service.BACKEND, params, Optional.empty());
|
"/the/path", Service.BACKEND, params, Optional.empty());
|
||||||
assertThat(task.getHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.POST);
|
assertThat(task.getHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.POST);
|
||||||
assertThat(task.getHttpRequest().getUrl()).isEqualTo("https://localhost/the/path");
|
assertThat(task.getHttpRequest().getUrl()).isEqualTo("https://backend.example.com/the/path");
|
||||||
assertThat(task.getHttpRequest().getHeadersMap().get("Content-Type"))
|
assertThat(task.getHttpRequest().getHeadersMap().get("Content-Type"))
|
||||||
.isEqualTo("application/x-www-form-urlencoded");
|
.isEqualTo("application/x-www-form-urlencoded");
|
||||||
assertThat(task.getHttpRequest().getBody().toString(StandardCharsets.UTF_8))
|
assertThat(task.getHttpRequest().getBody().toString(StandardCharsets.UTF_8))
|
||||||
|
@ -485,26 +247,24 @@ public class CloudTasksUtilsTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSuccess_nonAppEngine_createGetTasks_withEmptyJitterSeconds() {
|
void testSuccess_createGetTasks_withEmptyJitterSeconds() {
|
||||||
createOidcTasksUtils();
|
|
||||||
Task task =
|
Task task =
|
||||||
cloudTasksUtils.createGetTaskWithJitter(
|
cloudTasksUtils.createGetTaskWithJitter(
|
||||||
"/the/path", Service.BACKEND, params, Optional.empty());
|
"/the/path", Service.BACKEND, params, Optional.empty());
|
||||||
assertThat(task.getHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.GET);
|
assertThat(task.getHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.GET);
|
||||||
assertThat(task.getHttpRequest().getUrl())
|
assertThat(task.getHttpRequest().getUrl())
|
||||||
.isEqualTo("https://localhost/the/path?key1=val1&key2=val2&key1=val3");
|
.isEqualTo("https://backend.example.com/the/path?key1=val1&key2=val2&key1=val3");
|
||||||
verifyOidcToken(task);
|
verifyOidcToken(task);
|
||||||
assertThat(task.getScheduleTime().getSeconds()).isEqualTo(0);
|
assertThat(task.getScheduleTime().getSeconds()).isEqualTo(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSuccess_nonAppEngine_createPostTasks_withZeroJitterSeconds() {
|
void testSuccess_createPostTasks_withZeroJitterSeconds() {
|
||||||
createOidcTasksUtils();
|
|
||||||
Task task =
|
Task task =
|
||||||
cloudTasksUtils.createPostTaskWithJitter(
|
cloudTasksUtils.createPostTaskWithJitter(
|
||||||
"/the/path", Service.BACKEND, params, Optional.of(0));
|
"/the/path", Service.BACKEND, params, Optional.of(0));
|
||||||
assertThat(task.getHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.POST);
|
assertThat(task.getHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.POST);
|
||||||
assertThat(task.getHttpRequest().getUrl()).isEqualTo("https://localhost/the/path");
|
assertThat(task.getHttpRequest().getUrl()).isEqualTo("https://backend.example.com/the/path");
|
||||||
assertThat(task.getHttpRequest().getHeadersMap().get("Content-Type"))
|
assertThat(task.getHttpRequest().getHeadersMap().get("Content-Type"))
|
||||||
.isEqualTo("application/x-www-form-urlencoded");
|
.isEqualTo("application/x-www-form-urlencoded");
|
||||||
assertThat(task.getHttpRequest().getBody().toString(StandardCharsets.UTF_8))
|
assertThat(task.getHttpRequest().getBody().toString(StandardCharsets.UTF_8))
|
||||||
|
@ -514,40 +274,37 @@ public class CloudTasksUtilsTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSuccess_nonAppEngine_createGetTasks_withZeroJitterSeconds() {
|
void testSuccess_createGetTasks_withZeroJitterSeconds() {
|
||||||
createOidcTasksUtils();
|
|
||||||
Task task =
|
Task task =
|
||||||
cloudTasksUtils.createGetTaskWithJitter(
|
cloudTasksUtils.createGetTaskWithJitter(
|
||||||
"/the/path", Service.BACKEND, params, Optional.of(0));
|
"/the/path", Service.BACKEND, params, Optional.of(0));
|
||||||
assertThat(task.getHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.GET);
|
assertThat(task.getHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.GET);
|
||||||
assertThat(task.getHttpRequest().getUrl())
|
assertThat(task.getHttpRequest().getUrl())
|
||||||
.isEqualTo("https://localhost/the/path?key1=val1&key2=val2&key1=val3");
|
.isEqualTo("https://backend.example.com/the/path?key1=val1&key2=val2&key1=val3");
|
||||||
verifyOidcToken(task);
|
verifyOidcToken(task);
|
||||||
assertThat(task.getScheduleTime().getSeconds()).isEqualTo(0);
|
assertThat(task.getScheduleTime().getSeconds()).isEqualTo(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSuccess_nonAppEngine_createGetTasks_withDelay() {
|
void testSuccess_createGetTasks_withDelay() {
|
||||||
createOidcTasksUtils();
|
|
||||||
Task task =
|
Task task =
|
||||||
cloudTasksUtils.createGetTaskWithDelay(
|
cloudTasksUtils.createGetTaskWithDelay(
|
||||||
"/the/path", Service.BACKEND, params, Duration.standardMinutes(10));
|
"/the/path", Service.BACKEND, params, Duration.standardMinutes(10));
|
||||||
assertThat(task.getHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.GET);
|
assertThat(task.getHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.GET);
|
||||||
assertThat(task.getHttpRequest().getUrl())
|
assertThat(task.getHttpRequest().getUrl())
|
||||||
.isEqualTo("https://localhost/the/path?key1=val1&key2=val2&key1=val3");
|
.isEqualTo("https://backend.example.com/the/path?key1=val1&key2=val2&key1=val3");
|
||||||
verifyOidcToken(task);
|
verifyOidcToken(task);
|
||||||
assertThat(Instant.ofEpochSecond(task.getScheduleTime().getSeconds()))
|
assertThat(Instant.ofEpochSecond(task.getScheduleTime().getSeconds()))
|
||||||
.isEqualTo(Instant.ofEpochMilli(clock.nowUtc().plusMinutes(10).getMillis()));
|
.isEqualTo(Instant.ofEpochMilli(clock.nowUtc().plusMinutes(10).getMillis()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSuccess_nonAppEngine_createPostTasks_withDelay() {
|
void testSuccess_createPostTasks_withDelay() {
|
||||||
createOidcTasksUtils();
|
|
||||||
Task task =
|
Task task =
|
||||||
cloudTasksUtils.createPostTaskWithDelay(
|
cloudTasksUtils.createPostTaskWithDelay(
|
||||||
"/the/path", Service.BACKEND, params, Duration.standardMinutes(10));
|
"/the/path", Service.BACKEND, params, Duration.standardMinutes(10));
|
||||||
assertThat(task.getHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.POST);
|
assertThat(task.getHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.POST);
|
||||||
assertThat(task.getHttpRequest().getUrl()).isEqualTo("https://localhost/the/path");
|
assertThat(task.getHttpRequest().getUrl()).isEqualTo("https://backend.example.com/the/path");
|
||||||
assertThat(task.getHttpRequest().getHeadersMap().get("Content-Type"))
|
assertThat(task.getHttpRequest().getHeadersMap().get("Content-Type"))
|
||||||
.isEqualTo("application/x-www-form-urlencoded");
|
.isEqualTo("application/x-www-form-urlencoded");
|
||||||
assertThat(task.getHttpRequest().getBody().toString(StandardCharsets.UTF_8))
|
assertThat(task.getHttpRequest().getBody().toString(StandardCharsets.UTF_8))
|
||||||
|
@ -559,13 +316,12 @@ public class CloudTasksUtilsTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSuccess_nonAppEngine_createPostTasks_withZeroDelay() {
|
void testSuccess_createPostTasks_withZeroDelay() {
|
||||||
createOidcTasksUtils();
|
|
||||||
Task task =
|
Task task =
|
||||||
cloudTasksUtils.createPostTaskWithDelay(
|
cloudTasksUtils.createPostTaskWithDelay(
|
||||||
"/the/path", Service.BACKEND, params, Duration.ZERO);
|
"/the/path", Service.BACKEND, params, Duration.ZERO);
|
||||||
assertThat(task.getHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.POST);
|
assertThat(task.getHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.POST);
|
||||||
assertThat(task.getHttpRequest().getUrl()).isEqualTo("https://localhost/the/path");
|
assertThat(task.getHttpRequest().getUrl()).isEqualTo("https://backend.example.com/the/path");
|
||||||
assertThat(task.getHttpRequest().getHeadersMap().get("Content-Type"))
|
assertThat(task.getHttpRequest().getHeadersMap().get("Content-Type"))
|
||||||
.isEqualTo("application/x-www-form-urlencoded");
|
.isEqualTo("application/x-www-form-urlencoded");
|
||||||
assertThat(task.getHttpRequest().getBody().toString(StandardCharsets.UTF_8))
|
assertThat(task.getHttpRequest().getBody().toString(StandardCharsets.UTF_8))
|
||||||
|
@ -575,35 +331,22 @@ public class CloudTasksUtilsTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSuccess_nonAppEngine_createGetTasks_withZeroDelay() {
|
void testSuccess_createGetTasks_withZeroDelay() {
|
||||||
createOidcTasksUtils();
|
|
||||||
Task task =
|
Task task =
|
||||||
cloudTasksUtils.createGetTaskWithDelay("/the/path", Service.BACKEND, params, Duration.ZERO);
|
cloudTasksUtils.createGetTaskWithDelay("/the/path", Service.BACKEND, params, Duration.ZERO);
|
||||||
assertThat(task.getHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.GET);
|
assertThat(task.getHttpRequest().getHttpMethod()).isEqualTo(HttpMethod.GET);
|
||||||
assertThat(task.getHttpRequest().getUrl())
|
assertThat(task.getHttpRequest().getUrl())
|
||||||
.isEqualTo("https://localhost/the/path?key1=val1&key2=val2&key1=val3");
|
.isEqualTo("https://backend.example.com/the/path?key1=val1&key2=val2&key1=val3");
|
||||||
verifyOidcToken(task);
|
verifyOidcToken(task);
|
||||||
assertThat(task.getScheduleTime().getSeconds()).isEqualTo(0);
|
assertThat(task.getScheduleTime().getSeconds()).isEqualTo(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createOidcTasksUtils() {
|
private static void verifyOidcToken(Task task) {
|
||||||
cloudTasksUtils =
|
|
||||||
new CloudTasksUtils(
|
|
||||||
new Retrier(new FakeSleeper(clock), 1),
|
|
||||||
clock,
|
|
||||||
"project",
|
|
||||||
"location",
|
|
||||||
Optional.of("defaultServiceAccount"),
|
|
||||||
Optional.of("iapClientId"),
|
|
||||||
mockClient);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void verifyOidcToken(Task task) {
|
|
||||||
assertThat(task.getHttpRequest().getOidcToken())
|
assertThat(task.getHttpRequest().getOidcToken())
|
||||||
.isEqualTo(
|
.isEqualTo(
|
||||||
OidcToken.newBuilder()
|
OidcToken.newBuilder()
|
||||||
.setServiceAccountEmail("defaultServiceAccount")
|
.setServiceAccountEmail("service@account.com")
|
||||||
.setAudience("iapClientId")
|
.setAudience("clientId")
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -305,7 +305,7 @@ public class RelockDomainActionTest {
|
||||||
cloudTasksHelper.assertTasksEnqueued(
|
cloudTasksHelper.assertTasksEnqueued(
|
||||||
QUEUE_ASYNC_ACTIONS,
|
QUEUE_ASYNC_ACTIONS,
|
||||||
new TaskMatcher()
|
new TaskMatcher()
|
||||||
.url(RelockDomainAction.PATH)
|
.path(RelockDomainAction.PATH)
|
||||||
.method(HttpMethod.POST)
|
.method(HttpMethod.POST)
|
||||||
.param(
|
.param(
|
||||||
RelockDomainAction.OLD_UNLOCK_REVISION_ID_PARAM,
|
RelockDomainAction.OLD_UNLOCK_REVISION_ID_PARAM,
|
||||||
|
|
|
@ -136,7 +136,7 @@ public class ResaveEntityActionTest {
|
||||||
cloudTasksHelper.assertTasksEnqueued(
|
cloudTasksHelper.assertTasksEnqueued(
|
||||||
QUEUE_ASYNC_ACTIONS,
|
QUEUE_ASYNC_ACTIONS,
|
||||||
new TaskMatcher()
|
new TaskMatcher()
|
||||||
.url(ResaveEntityAction.PATH)
|
.path(ResaveEntityAction.PATH)
|
||||||
.method(HttpMethod.POST)
|
.method(HttpMethod.POST)
|
||||||
.service("backend")
|
.service("backend")
|
||||||
.header("content-type", "application/x-www-form-urlencoded")
|
.header("content-type", "application/x-www-form-urlencoded")
|
||||||
|
|
|
@ -464,7 +464,7 @@ public class RdePipelineTest {
|
||||||
cloudTasksHelper.assertTasksEnqueued(
|
cloudTasksHelper.assertTasksEnqueued(
|
||||||
"brda",
|
"brda",
|
||||||
new TaskMatcher()
|
new TaskMatcher()
|
||||||
.url("/_dr/task/brdaCopy")
|
.path("/_dr/task/brdaCopy")
|
||||||
.service("backend")
|
.service("backend")
|
||||||
.param("tld", "soy")
|
.param("tld", "soy")
|
||||||
.param("watermark", now.toString())
|
.param("watermark", now.toString())
|
||||||
|
@ -472,7 +472,7 @@ public class RdePipelineTest {
|
||||||
cloudTasksHelper.assertTasksEnqueued(
|
cloudTasksHelper.assertTasksEnqueued(
|
||||||
"rde-upload",
|
"rde-upload",
|
||||||
new TaskMatcher()
|
new TaskMatcher()
|
||||||
.url("/_dr/task/rdeUpload")
|
.path("/_dr/task/rdeUpload")
|
||||||
.service("backend")
|
.service("backend")
|
||||||
.param("tld", "soy")
|
.param("tld", "soy")
|
||||||
.param("prefix", "rde-job/"));
|
.param("prefix", "rde-job/"));
|
||||||
|
|
|
@ -91,14 +91,14 @@ class TldFanoutActionTest {
|
||||||
.map(
|
.map(
|
||||||
namespace ->
|
namespace ->
|
||||||
new TaskMatcher()
|
new TaskMatcher()
|
||||||
.url(ENDPOINT)
|
.path(ENDPOINT)
|
||||||
.header("content-type", "application/x-www-form-urlencoded")
|
.header("content-type", "application/x-www-form-urlencoded")
|
||||||
.param("tld", namespace))
|
.param("tld", namespace))
|
||||||
.collect(toImmutableList()));
|
.collect(toImmutableList()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertTaskWithoutTld() {
|
private void assertTaskWithoutTld() {
|
||||||
cloudTasksHelper.assertTasksEnqueued(QUEUE, new TaskMatcher().url(ENDPOINT));
|
cloudTasksHelper.assertTasksEnqueued(QUEUE, new TaskMatcher().path(ENDPOINT));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -211,7 +211,7 @@ class TldFanoutActionTest {
|
||||||
void testSuccess_additionalArgsFlowThroughToPostParams() {
|
void testSuccess_additionalArgsFlowThroughToPostParams() {
|
||||||
run(getParamsMap("forEachTestTld", "", "newkey", "newval"));
|
run(getParamsMap("forEachTestTld", "", "newkey", "newval"));
|
||||||
cloudTasksHelper.assertTasksEnqueued(
|
cloudTasksHelper.assertTasksEnqueued(
|
||||||
QUEUE, new TaskMatcher().url("/the/servlet").param("newkey", "newval"));
|
QUEUE, new TaskMatcher().path("/the/servlet").param("newkey", "newval"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -224,9 +224,9 @@ class TldFanoutActionTest {
|
||||||
String expectedResponse =
|
String expectedResponse =
|
||||||
String.format(
|
String.format(
|
||||||
"OK: Launched the following 3 tasks in queue the-queue\n"
|
"OK: Launched the following 3 tasks in queue the-queue\n"
|
||||||
+ "- Task: '%s', tld: 'com', endpoint: '/the/servlet'\n"
|
+ "- Task: '%s', tld: 'com', endpoint: 'https://backend.example.com/the/servlet'\n"
|
||||||
+ "- Task: '%s', tld: 'net', endpoint: '/the/servlet'\n"
|
+ "- Task: '%s', tld: 'net', endpoint: 'https://backend.example.com/the/servlet'\n"
|
||||||
+ "- Task: '%s', tld: 'org', endpoint: '/the/servlet'\n",
|
+ "- Task: '%s', tld: 'org', endpoint: 'https://backend.example.com/the/servlet'\n",
|
||||||
taskList.get(0).getName(), taskList.get(1).getName(), taskList.get(2).getName());
|
taskList.get(0).getName(), taskList.get(1).getName(), taskList.get(2).getName());
|
||||||
assertThat(response.getPayload()).isEqualTo(expectedResponse);
|
assertThat(response.getPayload()).isEqualTo(expectedResponse);
|
||||||
}
|
}
|
||||||
|
@ -241,7 +241,7 @@ class TldFanoutActionTest {
|
||||||
String expectedResponse =
|
String expectedResponse =
|
||||||
String.format(
|
String.format(
|
||||||
"OK: Launched the following 1 tasks in queue the-queue\n"
|
"OK: Launched the following 1 tasks in queue the-queue\n"
|
||||||
+ "- Task: '%s', tld: '', endpoint: '/the/servlet'\n",
|
+ "- Task: '%s', tld: '', endpoint: 'https://backend.example.com/the/servlet'\n",
|
||||||
taskList.get(0).getName());
|
taskList.get(0).getName());
|
||||||
assertThat(response.getPayload()).isEqualTo(expectedResponse);
|
assertThat(response.getPayload()).isEqualTo(expectedResponse);
|
||||||
}
|
}
|
||||||
|
|
|
@ -294,7 +294,7 @@ public class PublishDnsUpdatesActionTest {
|
||||||
cloudTasksHelper.assertTasksEnqueued(
|
cloudTasksHelper.assertTasksEnqueued(
|
||||||
DNS_PUBLISH_PUSH_QUEUE_NAME,
|
DNS_PUBLISH_PUSH_QUEUE_NAME,
|
||||||
new TaskMatcher()
|
new TaskMatcher()
|
||||||
.url(PublishDnsUpdatesAction.PATH)
|
.path(PublishDnsUpdatesAction.PATH)
|
||||||
.param(PARAM_TLD, "xn--q9jyb4c")
|
.param(PARAM_TLD, "xn--q9jyb4c")
|
||||||
.param(PARAM_DNS_WRITER, "correctWriter")
|
.param(PARAM_DNS_WRITER, "correctWriter")
|
||||||
.param(PARAM_LOCK_INDEX, "1")
|
.param(PARAM_LOCK_INDEX, "1")
|
||||||
|
@ -305,7 +305,7 @@ public class PublishDnsUpdatesActionTest {
|
||||||
.param(PARAM_HOSTS, "")
|
.param(PARAM_HOSTS, "")
|
||||||
.header("content-type", "application/x-www-form-urlencoded"),
|
.header("content-type", "application/x-www-form-urlencoded"),
|
||||||
new TaskMatcher()
|
new TaskMatcher()
|
||||||
.url(PublishDnsUpdatesAction.PATH)
|
.path(PublishDnsUpdatesAction.PATH)
|
||||||
.param(PARAM_TLD, "xn--q9jyb4c")
|
.param(PARAM_TLD, "xn--q9jyb4c")
|
||||||
.param(PARAM_DNS_WRITER, "correctWriter")
|
.param(PARAM_DNS_WRITER, "correctWriter")
|
||||||
.param(PARAM_LOCK_INDEX, "1")
|
.param(PARAM_LOCK_INDEX, "1")
|
||||||
|
@ -333,7 +333,7 @@ public class PublishDnsUpdatesActionTest {
|
||||||
cloudTasksHelper.assertTasksEnqueued(
|
cloudTasksHelper.assertTasksEnqueued(
|
||||||
DNS_PUBLISH_PUSH_QUEUE_NAME,
|
DNS_PUBLISH_PUSH_QUEUE_NAME,
|
||||||
new TaskMatcher()
|
new TaskMatcher()
|
||||||
.url(PublishDnsUpdatesAction.PATH)
|
.path(PublishDnsUpdatesAction.PATH)
|
||||||
.param(PARAM_TLD, "xn--q9jyb4c")
|
.param(PARAM_TLD, "xn--q9jyb4c")
|
||||||
.param(PARAM_DNS_WRITER, "correctWriter")
|
.param(PARAM_DNS_WRITER, "correctWriter")
|
||||||
.param(PARAM_LOCK_INDEX, "1")
|
.param(PARAM_LOCK_INDEX, "1")
|
||||||
|
@ -344,7 +344,7 @@ public class PublishDnsUpdatesActionTest {
|
||||||
.param(PARAM_HOSTS, "")
|
.param(PARAM_HOSTS, "")
|
||||||
.header("content-type", "application/x-www-form-urlencoded"),
|
.header("content-type", "application/x-www-form-urlencoded"),
|
||||||
new TaskMatcher()
|
new TaskMatcher()
|
||||||
.url(PublishDnsUpdatesAction.PATH)
|
.path(PublishDnsUpdatesAction.PATH)
|
||||||
.param(PARAM_TLD, "xn--q9jyb4c")
|
.param(PARAM_TLD, "xn--q9jyb4c")
|
||||||
.param(PARAM_DNS_WRITER, "correctWriter")
|
.param(PARAM_DNS_WRITER, "correctWriter")
|
||||||
.param(PARAM_LOCK_INDEX, "1")
|
.param(PARAM_LOCK_INDEX, "1")
|
||||||
|
@ -370,7 +370,7 @@ public class PublishDnsUpdatesActionTest {
|
||||||
cloudTasksHelper.assertTasksEnqueued(
|
cloudTasksHelper.assertTasksEnqueued(
|
||||||
DNS_PUBLISH_PUSH_QUEUE_NAME,
|
DNS_PUBLISH_PUSH_QUEUE_NAME,
|
||||||
new TaskMatcher()
|
new TaskMatcher()
|
||||||
.url(PublishDnsUpdatesAction.PATH)
|
.path(PublishDnsUpdatesAction.PATH)
|
||||||
.param(PARAM_TLD, "xn--q9jyb4c")
|
.param(PARAM_TLD, "xn--q9jyb4c")
|
||||||
.param(PARAM_DNS_WRITER, "correctWriter")
|
.param(PARAM_DNS_WRITER, "correctWriter")
|
||||||
.param(PARAM_LOCK_INDEX, "1")
|
.param(PARAM_LOCK_INDEX, "1")
|
||||||
|
@ -381,7 +381,7 @@ public class PublishDnsUpdatesActionTest {
|
||||||
.param(PARAM_HOSTS, "")
|
.param(PARAM_HOSTS, "")
|
||||||
.header("content-type", "application/x-www-form-urlencoded"),
|
.header("content-type", "application/x-www-form-urlencoded"),
|
||||||
new TaskMatcher()
|
new TaskMatcher()
|
||||||
.url(PublishDnsUpdatesAction.PATH)
|
.path(PublishDnsUpdatesAction.PATH)
|
||||||
.param(PARAM_TLD, "xn--q9jyb4c")
|
.param(PARAM_TLD, "xn--q9jyb4c")
|
||||||
.param(PARAM_DNS_WRITER, "correctWriter")
|
.param(PARAM_DNS_WRITER, "correctWriter")
|
||||||
.param(PARAM_LOCK_INDEX, "1")
|
.param(PARAM_LOCK_INDEX, "1")
|
||||||
|
|
|
@ -225,7 +225,7 @@ public class ReadDnsRefreshRequestsActionTest {
|
||||||
cloudTasksHelper.assertTasksEnqueued(
|
cloudTasksHelper.assertTasksEnqueued(
|
||||||
"dns-publish",
|
"dns-publish",
|
||||||
new TaskMatcher()
|
new TaskMatcher()
|
||||||
.url("/_dr/task/publishDnsUpdates")
|
.path("/_dr/task/publishDnsUpdates")
|
||||||
.service("BACKEND")
|
.service("BACKEND")
|
||||||
.param("tld", "tld")
|
.param("tld", "tld")
|
||||||
.param("dnsWriter", "FooWriter")
|
.param("dnsWriter", "FooWriter")
|
||||||
|
@ -236,7 +236,7 @@ public class ReadDnsRefreshRequestsActionTest {
|
||||||
.param("domains", "domain.tld,future.tld")
|
.param("domains", "domain.tld,future.tld")
|
||||||
.param("hosts", "ns1.domain.tld"),
|
.param("hosts", "ns1.domain.tld"),
|
||||||
new TaskMatcher()
|
new TaskMatcher()
|
||||||
.url("/_dr/task/publishDnsUpdates")
|
.path("/_dr/task/publishDnsUpdates")
|
||||||
.service("BACKEND")
|
.service("BACKEND")
|
||||||
.param("tld", "tld")
|
.param("tld", "tld")
|
||||||
.param("dnsWriter", "BarWriter")
|
.param("dnsWriter", "BarWriter")
|
||||||
|
|
|
@ -304,7 +304,7 @@ class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow, Domain
|
||||||
cloudTasksHelper.assertTasksEnqueued(
|
cloudTasksHelper.assertTasksEnqueued(
|
||||||
QUEUE_ASYNC_ACTIONS,
|
QUEUE_ASYNC_ACTIONS,
|
||||||
new TaskMatcher()
|
new TaskMatcher()
|
||||||
.url(ResaveEntityAction.PATH)
|
.path(ResaveEntityAction.PATH)
|
||||||
.method(HttpMethod.POST)
|
.method(HttpMethod.POST)
|
||||||
.service("backend")
|
.service("backend")
|
||||||
.header("content-type", "application/x-www-form-urlencoded")
|
.header("content-type", "application/x-www-form-urlencoded")
|
||||||
|
|
|
@ -520,7 +520,7 @@ class DomainTransferRequestFlowTest
|
||||||
cloudTasksHelper.assertTasksEnqueued(
|
cloudTasksHelper.assertTasksEnqueued(
|
||||||
QUEUE_ASYNC_ACTIONS,
|
QUEUE_ASYNC_ACTIONS,
|
||||||
new TaskMatcher()
|
new TaskMatcher()
|
||||||
.url(ResaveEntityAction.PATH)
|
.path(ResaveEntityAction.PATH)
|
||||||
.method(HttpMethod.POST)
|
.method(HttpMethod.POST)
|
||||||
.service("backend")
|
.service("backend")
|
||||||
.header("content-type", "application/x-www-form-urlencoded")
|
.header("content-type", "application/x-www-form-urlencoded")
|
||||||
|
|
|
@ -207,7 +207,7 @@ class HostUpdateFlowTest extends ResourceFlowTestCase<HostUpdateFlow, Host> {
|
||||||
cloudTasksHelper.assertTasksEnqueued(
|
cloudTasksHelper.assertTasksEnqueued(
|
||||||
QUEUE_HOST_RENAME,
|
QUEUE_HOST_RENAME,
|
||||||
new TaskMatcher()
|
new TaskMatcher()
|
||||||
.url(RefreshDnsOnHostRenameAction.PATH)
|
.path(RefreshDnsOnHostRenameAction.PATH)
|
||||||
.method(HttpMethod.POST)
|
.method(HttpMethod.POST)
|
||||||
.service("backend")
|
.service("backend")
|
||||||
.param(PARAM_HOST_KEY, renamedHost.createVKey().stringify()));
|
.param(PARAM_HOST_KEY, renamedHost.createVKey().stringify()));
|
||||||
|
|
|
@ -227,7 +227,7 @@ public class RdeUploadActionTest {
|
||||||
action, Tld.get("lol"), standardSeconds(23), CursorType.RDE_UPLOAD, standardDays(1));
|
action, Tld.get("lol"), standardSeconds(23), CursorType.RDE_UPLOAD, standardDays(1));
|
||||||
cloudTasksHelper.assertTasksEnqueued(
|
cloudTasksHelper.assertTasksEnqueued(
|
||||||
"rde-report",
|
"rde-report",
|
||||||
new TaskMatcher().url(RdeReportAction.PATH).param(RequestParameters.PARAM_TLD, "lol"));
|
new TaskMatcher().path(RdeReportAction.PATH).param(RequestParameters.PARAM_TLD, "lol"));
|
||||||
verifyNoMoreInteractions(runner);
|
verifyNoMoreInteractions(runner);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -244,7 +244,7 @@ public class RdeUploadActionTest {
|
||||||
cloudTasksHelper.assertTasksEnqueued(
|
cloudTasksHelper.assertTasksEnqueued(
|
||||||
"rde-report",
|
"rde-report",
|
||||||
new TaskMatcher()
|
new TaskMatcher()
|
||||||
.url(RdeReportAction.PATH)
|
.path(RdeReportAction.PATH)
|
||||||
.param(RequestParameters.PARAM_TLD, "lol")
|
.param(RequestParameters.PARAM_TLD, "lol")
|
||||||
.param(RdeModule.PARAM_PREFIX, "job-name/"));
|
.param(RdeModule.PARAM_PREFIX, "job-name/"));
|
||||||
verifyNoMoreInteractions(runner);
|
verifyNoMoreInteractions(runner);
|
||||||
|
|
|
@ -74,7 +74,7 @@ class GenerateInvoicesActionTest extends BeamActionTestBase {
|
||||||
cloudTasksHelper.assertTasksEnqueued(
|
cloudTasksHelper.assertTasksEnqueued(
|
||||||
"beam-reporting",
|
"beam-reporting",
|
||||||
new TaskMatcher()
|
new TaskMatcher()
|
||||||
.url("/_dr/task/publishInvoices")
|
.path("/_dr/task/publishInvoices")
|
||||||
.method(HttpMethod.POST)
|
.method(HttpMethod.POST)
|
||||||
.param("jobId", "jobid")
|
.param("jobId", "jobid")
|
||||||
.param("yearMonth", "2017-10")
|
.param("yearMonth", "2017-10")
|
||||||
|
|
|
@ -83,7 +83,7 @@ class PublishInvoicesActionTest {
|
||||||
verify(emailUtils).emailOverallInvoice();
|
verify(emailUtils).emailOverallInvoice();
|
||||||
TaskMatcher matcher =
|
TaskMatcher matcher =
|
||||||
new TaskMatcher()
|
new TaskMatcher()
|
||||||
.url("/_dr/task/copyDetailReports")
|
.path("/_dr/task/copyDetailReports")
|
||||||
.method(HttpMethod.POST)
|
.method(HttpMethod.POST)
|
||||||
.param("yearMonth", "2017-10");
|
.param("yearMonth", "2017-10");
|
||||||
cloudTasksHelper.assertTasksEnqueued("retryable-cron-tasks", matcher);
|
cloudTasksHelper.assertTasksEnqueued("retryable-cron-tasks", matcher);
|
||||||
|
|
|
@ -78,7 +78,7 @@ class IcannReportingStagingActionTest {
|
||||||
cloudTasksHelper.assertTasksEnqueued(
|
cloudTasksHelper.assertTasksEnqueued(
|
||||||
"retryable-cron-tasks",
|
"retryable-cron-tasks",
|
||||||
new TaskMatcher()
|
new TaskMatcher()
|
||||||
.url("/_dr/task/icannReportingUpload")
|
.path("/_dr/task/icannReportingUpload")
|
||||||
.method(HttpMethod.POST)
|
.method(HttpMethod.POST)
|
||||||
.scheduleTime(clock.nowUtc().plus(Duration.standardMinutes(2))));
|
.scheduleTime(clock.nowUtc().plus(Duration.standardMinutes(2))));
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,7 +86,7 @@ class GenerateSpec11ReportActionTest extends BeamActionTestBase {
|
||||||
cloudTasksHelper.assertTasksEnqueued(
|
cloudTasksHelper.assertTasksEnqueued(
|
||||||
"beam-reporting",
|
"beam-reporting",
|
||||||
new TaskMatcher()
|
new TaskMatcher()
|
||||||
.url("/_dr/task/publishSpec11")
|
.path("/_dr/task/publishSpec11")
|
||||||
.method(HttpMethod.POST)
|
.method(HttpMethod.POST)
|
||||||
.param("jobId", "jobid")
|
.param("jobId", "jobid")
|
||||||
.param("date", "2018-06-11")
|
.param("date", "2018-06-11")
|
||||||
|
|
|
@ -29,7 +29,7 @@ import com.google.api.client.json.webtoken.JsonWebSignature;
|
||||||
import com.google.api.client.json.webtoken.JsonWebSignature.Header;
|
import com.google.api.client.json.webtoken.JsonWebSignature.Header;
|
||||||
import com.google.auth.oauth2.TokenVerifier;
|
import com.google.auth.oauth2.TokenVerifier;
|
||||||
import com.google.auth.oauth2.TokenVerifier.VerificationException;
|
import com.google.auth.oauth2.TokenVerifier.VerificationException;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import dagger.Component;
|
import dagger.Component;
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
|
@ -54,8 +54,8 @@ public class OidcTokenAuthenticationMechanismTest {
|
||||||
private static final String rawToken = "this-token";
|
private static final String rawToken = "this-token";
|
||||||
private static final String email = "user@email.test";
|
private static final String email = "user@email.test";
|
||||||
private static final String gaiaId = "gaia-id";
|
private static final String gaiaId = "gaia-id";
|
||||||
private static final ImmutableList<String> serviceAccounts =
|
private static final ImmutableSet<String> serviceAccounts =
|
||||||
ImmutableList.of("service@email.test", "email@service.goog");
|
ImmutableSet.of("service@email.test", "email@service.goog");
|
||||||
|
|
||||||
private final Payload payload = new Payload();
|
private final Payload payload = new Payload();
|
||||||
private final User user =
|
private final User user =
|
||||||
|
@ -222,9 +222,16 @@ public class OidcTokenAuthenticationMechanismTest {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
@Config("serviceAccountEmails")
|
@Config("allowedServiceAccountEmails")
|
||||||
ImmutableList<String> provideServiceAccountEmails() {
|
ImmutableSet<String> provideAllowedServiceAccountEmails() {
|
||||||
return serviceAccounts;
|
return serviceAccounts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
@Config("oauthClientId")
|
||||||
|
String provideOauthClientId() {
|
||||||
|
return "client-id";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,9 @@ import static google.registry.util.DiffUtils.prettyPrintEntityDeepDiff;
|
||||||
import static java.util.Arrays.asList;
|
import static java.util.Arrays.asList;
|
||||||
import static java.util.stream.Collectors.joining;
|
import static java.util.stream.Collectors.joining;
|
||||||
|
|
||||||
|
import com.google.auth.oauth2.GoogleCredentials;
|
||||||
import com.google.cloud.tasks.v2.HttpMethod;
|
import com.google.cloud.tasks.v2.HttpMethod;
|
||||||
|
import com.google.cloud.tasks.v2.HttpRequest;
|
||||||
import com.google.cloud.tasks.v2.Task;
|
import com.google.cloud.tasks.v2.Task;
|
||||||
import com.google.common.base.Ascii;
|
import com.google.common.base.Ascii;
|
||||||
import com.google.common.base.Joiner;
|
import com.google.common.base.Joiner;
|
||||||
|
@ -45,6 +47,7 @@ import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
import google.registry.batch.CloudTasksUtils;
|
import google.registry.batch.CloudTasksUtils;
|
||||||
import google.registry.model.ImmutableObject;
|
import google.registry.model.ImmutableObject;
|
||||||
|
import google.registry.util.GoogleCredentialsBundle;
|
||||||
import google.registry.util.Retrier;
|
import google.registry.util.Retrier;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
@ -58,12 +61,13 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ConcurrentMap;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
|
@ -104,8 +108,8 @@ public class CloudTasksHelper implements Serializable {
|
||||||
clock,
|
clock,
|
||||||
PROJECT_ID,
|
PROJECT_ID,
|
||||||
LOCATION_ID,
|
LOCATION_ID,
|
||||||
Optional.empty(),
|
"client.id",
|
||||||
Optional.empty(),
|
FakeGoogleCredentialsBundle.create(),
|
||||||
new FakeCloudTasksClient());
|
new FakeCloudTasksClient());
|
||||||
testTasks.put(instanceId, Multimaps.synchronizedListMultimap(LinkedListMultimap.create()));
|
testTasks.put(instanceId, Multimaps.synchronizedListMultimap(LinkedListMultimap.create()));
|
||||||
}
|
}
|
||||||
|
@ -219,18 +223,35 @@ public class CloudTasksHelper implements Serializable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class FakeGoogleCredentialsBundle extends GoogleCredentialsBundle {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = -3251343247195058893L;
|
||||||
|
|
||||||
|
private static final FakeGoogleCredentialsBundle INSTANCE = new FakeGoogleCredentialsBundle();
|
||||||
|
|
||||||
|
public static GoogleCredentialsBundle create() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private FakeGoogleCredentialsBundle() {
|
||||||
|
super(new GoogleCredentials(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String serviceAccount() {
|
||||||
|
return "service@account.com";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** An adapter to clean up a {@link Task} for ease of matching. */
|
/** An adapter to clean up a {@link Task} for ease of matching. */
|
||||||
private static class MatchableTask extends ImmutableObject {
|
private static class MatchableTask extends ImmutableObject {
|
||||||
|
|
||||||
|
private static final Pattern HOSTNAME_PATTERN =
|
||||||
|
Pattern.compile("(?<=https://)[a-z]+(?=\\.example\\.com)");
|
||||||
String taskName;
|
String taskName;
|
||||||
String service;
|
String service;
|
||||||
// App Engine TaskOption methods default to "POST". This isn't obvious from the code, so we
|
HttpMethod method;
|
||||||
// default it to POST here so that we don't accidentally create an entry with a GET method when
|
String path;
|
||||||
// converting to CloudTasksUtils, which requires that the method be specified explicitly.
|
|
||||||
// Should we ever actually want to do a GET, we'll likewise have to set this explicitly for the
|
|
||||||
// tests.
|
|
||||||
HttpMethod method = HttpMethod.POST;
|
|
||||||
String url;
|
|
||||||
Timestamp scheduleTime;
|
Timestamp scheduleTime;
|
||||||
Multimap<String, String> headers = ArrayListMultimap.create();
|
Multimap<String, String> headers = ArrayListMultimap.create();
|
||||||
Multimap<String, String> params = ArrayListMultimap.create();
|
Multimap<String, String> params = ArrayListMultimap.create();
|
||||||
|
@ -238,24 +259,24 @@ public class CloudTasksHelper implements Serializable {
|
||||||
MatchableTask() {}
|
MatchableTask() {}
|
||||||
|
|
||||||
MatchableTask(Task task) {
|
MatchableTask(Task task) {
|
||||||
|
HttpRequest request = task.getHttpRequest();
|
||||||
|
taskName = task.getName();
|
||||||
|
String url = request.getUrl();
|
||||||
|
// URI class provides helper method to extract query parameters.
|
||||||
URI uri;
|
URI uri;
|
||||||
try {
|
try {
|
||||||
// Construct a fake full URI for parsing purpose. The relative URI must start with a slash.
|
uri = new URI(url);
|
||||||
uri =
|
|
||||||
new URI(
|
|
||||||
String.format(
|
|
||||||
"https://nomulus.foo%s", task.getAppEngineHttpRequest().getRelativeUri()));
|
|
||||||
} catch (URISyntaxException e) {
|
} catch (URISyntaxException e) {
|
||||||
throw new IllegalArgumentException(e);
|
throw new IllegalArgumentException(e);
|
||||||
}
|
}
|
||||||
taskName = task.getName();
|
Matcher hostnameMatcher = HOSTNAME_PATTERN.matcher(url);
|
||||||
service =
|
assertThat(hostnameMatcher.find()).isTrue();
|
||||||
Ascii.toLowerCase(task.getAppEngineHttpRequest().getAppEngineRouting().getService());
|
service = Ascii.toLowerCase(hostnameMatcher.group());
|
||||||
method = task.getAppEngineHttpRequest().getHttpMethod();
|
path = url.substring(String.format("https://%s.example.com", service).length());
|
||||||
url = uri.getPath();
|
method = request.getHttpMethod();
|
||||||
scheduleTime = task.getScheduleTime();
|
scheduleTime = task.getScheduleTime();
|
||||||
ImmutableMultimap.Builder<String, String> headerBuilder = new ImmutableMultimap.Builder<>();
|
ImmutableMultimap.Builder<String, String> headerBuilder = new ImmutableMultimap.Builder<>();
|
||||||
task.getAppEngineHttpRequest()
|
request
|
||||||
.getHeadersMap()
|
.getHeadersMap()
|
||||||
.forEach(
|
.forEach(
|
||||||
(key, value) -> {
|
(key, value) -> {
|
||||||
|
@ -269,14 +290,13 @@ public class CloudTasksHelper implements Serializable {
|
||||||
// where parameters are not properly URL-encoded); it always does a best-effort parse.
|
// where parameters are not properly URL-encoded); it always does a best-effort parse.
|
||||||
if (method == HttpMethod.GET && uri.getQuery() != null) {
|
if (method == HttpMethod.GET && uri.getQuery() != null) {
|
||||||
paramBuilder.putAll(UriParameters.parse(uri.getQuery()));
|
paramBuilder.putAll(UriParameters.parse(uri.getQuery()));
|
||||||
} else if (method == HttpMethod.POST && !task.getAppEngineHttpRequest().getBody().isEmpty()) {
|
} else if (method == HttpMethod.POST && !request.getBody().isEmpty()) {
|
||||||
assertThat(
|
assertThat(
|
||||||
headers.containsEntry(
|
headers.containsEntry(
|
||||||
Ascii.toLowerCase(HttpHeaders.CONTENT_TYPE), MediaType.FORM_DATA.toString()))
|
Ascii.toLowerCase(HttpHeaders.CONTENT_TYPE), MediaType.FORM_DATA.toString()))
|
||||||
.isTrue();
|
.isTrue();
|
||||||
paramBuilder.putAll(
|
paramBuilder.putAll(
|
||||||
UriParameters.parse(
|
UriParameters.parse(request.getBody().toString(StandardCharsets.UTF_8)));
|
||||||
task.getAppEngineHttpRequest().getBody().toString(StandardCharsets.UTF_8)));
|
|
||||||
}
|
}
|
||||||
params = paramBuilder.build();
|
params = paramBuilder.build();
|
||||||
}
|
}
|
||||||
|
@ -286,7 +306,7 @@ public class CloudTasksHelper implements Serializable {
|
||||||
builder.put("taskName", taskName);
|
builder.put("taskName", taskName);
|
||||||
builder.put("method", method);
|
builder.put("method", method);
|
||||||
builder.put("service", service);
|
builder.put("service", service);
|
||||||
builder.put("url", url);
|
builder.put("path", path);
|
||||||
builder.put("headers", headers);
|
builder.put("headers", headers);
|
||||||
builder.put("params", params);
|
builder.put("params", params);
|
||||||
builder.put("scheduleTime", scheduleTime);
|
builder.put("scheduleTime", scheduleTime);
|
||||||
|
@ -310,8 +330,8 @@ public class CloudTasksHelper implements Serializable {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TaskMatcher url(String url) {
|
public TaskMatcher path(String path) {
|
||||||
expected.url = url;
|
expected.path = path;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -371,7 +391,7 @@ public class CloudTasksHelper implements Serializable {
|
||||||
public boolean test(@Nonnull Task task) {
|
public boolean test(@Nonnull Task task) {
|
||||||
MatchableTask actual = new MatchableTask(task);
|
MatchableTask actual = new MatchableTask(task);
|
||||||
return (expected.taskName == null || Objects.equals(expected.taskName, actual.taskName))
|
return (expected.taskName == null || Objects.equals(expected.taskName, actual.taskName))
|
||||||
&& (expected.url == null || Objects.equals(expected.url, actual.url))
|
&& (expected.path == null || Objects.equals(expected.path, actual.path))
|
||||||
&& (expected.method == null || Objects.equals(expected.method, actual.method))
|
&& (expected.method == null || Objects.equals(expected.method, actual.method))
|
||||||
&& (expected.service == null || Objects.equals(expected.service, actual.service))
|
&& (expected.service == null || Objects.equals(expected.service, actual.service))
|
||||||
&& (expected.scheduleTime == null
|
&& (expected.scheduleTime == null
|
||||||
|
|
|
@ -252,7 +252,7 @@ class NordnUploadActionTest {
|
||||||
cloudTasksHelper.assertTasksEnqueued(
|
cloudTasksHelper.assertTasksEnqueued(
|
||||||
NordnVerifyAction.QUEUE,
|
NordnVerifyAction.QUEUE,
|
||||||
new TaskMatcher()
|
new TaskMatcher()
|
||||||
.url(NordnVerifyAction.PATH)
|
.path(NordnVerifyAction.PATH)
|
||||||
.param(NordnVerifyAction.NORDN_URL_PARAM, LOCATION_URL)
|
.param(NordnVerifyAction.NORDN_URL_PARAM, LOCATION_URL)
|
||||||
.param(RequestParameters.PARAM_TLD, "tld")
|
.param(RequestParameters.PARAM_TLD, "tld")
|
||||||
.header(CONTENT_TYPE, FORM_DATA.toString()));
|
.header(CONTENT_TYPE, FORM_DATA.toString()));
|
||||||
|
|
|
@ -259,7 +259,7 @@ public final class DomainLockUtilsTest {
|
||||||
cloudTasksHelper.assertTasksEnqueued(
|
cloudTasksHelper.assertTasksEnqueued(
|
||||||
QUEUE_ASYNC_ACTIONS,
|
QUEUE_ASYNC_ACTIONS,
|
||||||
new TaskMatcher()
|
new TaskMatcher()
|
||||||
.url(RelockDomainAction.PATH)
|
.path(RelockDomainAction.PATH)
|
||||||
.method(HttpMethod.POST)
|
.method(HttpMethod.POST)
|
||||||
.service("backend")
|
.service("backend")
|
||||||
.param(
|
.param(
|
||||||
|
@ -481,7 +481,7 @@ public final class DomainLockUtilsTest {
|
||||||
cloudTasksHelper.assertTasksEnqueued(
|
cloudTasksHelper.assertTasksEnqueued(
|
||||||
QUEUE_ASYNC_ACTIONS,
|
QUEUE_ASYNC_ACTIONS,
|
||||||
new CloudTasksHelper.TaskMatcher()
|
new CloudTasksHelper.TaskMatcher()
|
||||||
.url(RelockDomainAction.PATH)
|
.path(RelockDomainAction.PATH)
|
||||||
.method(HttpMethod.POST)
|
.method(HttpMethod.POST)
|
||||||
.service("backend")
|
.service("backend")
|
||||||
.param(
|
.param(
|
||||||
|
|
|
@ -73,7 +73,7 @@ final class GcpProjectConnectionTest {
|
||||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||||
getStreamingContent().writeTo(output);
|
getStreamingContent().writeTo(output);
|
||||||
output.close();
|
output.close();
|
||||||
return new String(output.toByteArray(), UTF_8);
|
return output.toString(UTF_8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,7 +97,7 @@ final class GcpProjectConnectionTest {
|
||||||
.isEqualTo("MyContent");
|
.isEqualTo("MyContent");
|
||||||
assertThat(httpTransport.method).isEqualTo("GET");
|
assertThat(httpTransport.method).isEqualTo("GET");
|
||||||
assertThat(httpTransport.url)
|
assertThat(httpTransport.url)
|
||||||
.isEqualTo("https://localhost/my/path?query&key1=value1&key2=value2");
|
.isEqualTo("https://tools.example.com/my/path?query&key1=value1&key2=value2");
|
||||||
assertThat(lowLevelHttpRequest.headers).containsEntry("Cache-Control", "no-cache");
|
assertThat(lowLevelHttpRequest.headers).containsEntry("Cache-Control", "no-cache");
|
||||||
assertThat(lowLevelHttpRequest.headers).containsEntry("x-requested-with", "RegistryTool");
|
assertThat(lowLevelHttpRequest.headers).containsEntry("x-requested-with", "RegistryTool");
|
||||||
}
|
}
|
||||||
|
@ -113,7 +113,7 @@ final class GcpProjectConnectionTest {
|
||||||
.isEqualTo("MyContent");
|
.isEqualTo("MyContent");
|
||||||
assertThat(httpTransport.method).isEqualTo("POST");
|
assertThat(httpTransport.method).isEqualTo("POST");
|
||||||
assertThat(httpTransport.url)
|
assertThat(httpTransport.url)
|
||||||
.isEqualTo("https://localhost/my/path?query&key1=value1&key2=value2");
|
.isEqualTo("https://tools.example.com/my/path?query&key1=value1&key2=value2");
|
||||||
assertThat(lowLevelHttpRequest.getContentType()).isEqualTo("text/plain; charset=utf-8");
|
assertThat(lowLevelHttpRequest.getContentType()).isEqualTo("text/plain; charset=utf-8");
|
||||||
assertThat(lowLevelHttpRequest.getContentString()).isEqualTo("some data");
|
assertThat(lowLevelHttpRequest.getContentString()).isEqualTo("some data");
|
||||||
assertThat(lowLevelHttpRequest.headers).containsEntry("Cache-Control", "no-cache");
|
assertThat(lowLevelHttpRequest.headers).containsEntry("Cache-Control", "no-cache");
|
||||||
|
@ -130,7 +130,7 @@ final class GcpProjectConnectionTest {
|
||||||
"/my/path?query", ImmutableMap.of("string", "value1", "bool", true)))
|
"/my/path?query", ImmutableMap.of("string", "value1", "bool", true)))
|
||||||
.containsExactly("key", "value");
|
.containsExactly("key", "value");
|
||||||
assertThat(httpTransport.method).isEqualTo("POST");
|
assertThat(httpTransport.method).isEqualTo("POST");
|
||||||
assertThat(httpTransport.url).isEqualTo("https://localhost/my/path?query");
|
assertThat(httpTransport.url).isEqualTo("https://tools.example.com/my/path?query");
|
||||||
assertThat(lowLevelHttpRequest.getContentType()).isEqualTo("application/json; charset=utf-8");
|
assertThat(lowLevelHttpRequest.getContentType()).isEqualTo("application/json; charset=utf-8");
|
||||||
assertThat(lowLevelHttpRequest.getContentString())
|
assertThat(lowLevelHttpRequest.getContentString())
|
||||||
.isEqualTo("{\"string\":\"value1\",\"bool\":true}");
|
.isEqualTo("{\"string\":\"value1\",\"bool\":true}");
|
||||||
|
|
|
@ -187,7 +187,7 @@ public class GenerateEscrowDepositCommandTest
|
||||||
cloudTasksHelper.assertTasksEnqueued(
|
cloudTasksHelper.assertTasksEnqueued(
|
||||||
"rde-report",
|
"rde-report",
|
||||||
new TaskMatcher()
|
new TaskMatcher()
|
||||||
.url("/_dr/task/rdeStaging")
|
.path("/_dr/task/rdeStaging")
|
||||||
.param("mode", "THIN")
|
.param("mode", "THIN")
|
||||||
.param("lenient", "true")
|
.param("lenient", "true")
|
||||||
.param("watermarks", "2017-01-01T00:00:00.000Z")
|
.param("watermarks", "2017-01-01T00:00:00.000Z")
|
||||||
|
@ -204,7 +204,7 @@ public class GenerateEscrowDepositCommandTest
|
||||||
cloudTasksHelper.assertTasksEnqueued(
|
cloudTasksHelper.assertTasksEnqueued(
|
||||||
"rde-report",
|
"rde-report",
|
||||||
new TaskMatcher()
|
new TaskMatcher()
|
||||||
.url("/_dr/task/rdeStaging")
|
.path("/_dr/task/rdeStaging")
|
||||||
.param("mode", "THIN")
|
.param("mode", "THIN")
|
||||||
.param("lenient", "false")
|
.param("lenient", "false")
|
||||||
.param("watermarks", "2017-01-01T00:00:00.000Z")
|
.param("watermarks", "2017-01-01T00:00:00.000Z")
|
||||||
|
@ -221,7 +221,7 @@ public class GenerateEscrowDepositCommandTest
|
||||||
cloudTasksHelper.assertTasksEnqueued(
|
cloudTasksHelper.assertTasksEnqueued(
|
||||||
"rde-report",
|
"rde-report",
|
||||||
new TaskMatcher()
|
new TaskMatcher()
|
||||||
.url("/_dr/task/rdeStaging")
|
.path("/_dr/task/rdeStaging")
|
||||||
.param("lenient", "false")
|
.param("lenient", "false")
|
||||||
.param("mode", "THIN")
|
.param("mode", "THIN")
|
||||||
.param("watermarks", "2017-01-01T00:00:00.000Z")
|
.param("watermarks", "2017-01-01T00:00:00.000Z")
|
||||||
|
@ -237,7 +237,7 @@ public class GenerateEscrowDepositCommandTest
|
||||||
cloudTasksHelper.assertTasksEnqueued(
|
cloudTasksHelper.assertTasksEnqueued(
|
||||||
"rde-report",
|
"rde-report",
|
||||||
new TaskMatcher()
|
new TaskMatcher()
|
||||||
.url("/_dr/task/rdeStaging")
|
.path("/_dr/task/rdeStaging")
|
||||||
.param("mode", "FULL")
|
.param("mode", "FULL")
|
||||||
.param("lenient", "false")
|
.param("lenient", "false")
|
||||||
.param("watermarks", "2017-01-01T00:00:00.000Z")
|
.param("watermarks", "2017-01-01T00:00:00.000Z")
|
||||||
|
@ -259,7 +259,7 @@ public class GenerateEscrowDepositCommandTest
|
||||||
cloudTasksHelper.assertTasksEnqueued(
|
cloudTasksHelper.assertTasksEnqueued(
|
||||||
"rde-report",
|
"rde-report",
|
||||||
new TaskMatcher()
|
new TaskMatcher()
|
||||||
.url("/_dr/task/rdeStaging")
|
.path("/_dr/task/rdeStaging")
|
||||||
.param("mode", "THIN")
|
.param("mode", "THIN")
|
||||||
.param("lenient", "false")
|
.param("lenient", "false")
|
||||||
.param("watermarks", "2017-01-01T00:00:00.000Z,2017-01-02T00:00:00.000Z")
|
.param("watermarks", "2017-01-01T00:00:00.000Z,2017-01-02T00:00:00.000Z")
|
||||||
|
|
|
@ -35,7 +35,6 @@ import com.google.auth.oauth2.UserCredentials;
|
||||||
import google.registry.config.RegistryConfig;
|
import google.registry.config.RegistryConfig;
|
||||||
import google.registry.testing.SystemPropertyExtension;
|
import google.registry.testing.SystemPropertyExtension;
|
||||||
import google.registry.util.GoogleCredentialsBundle;
|
import google.registry.util.GoogleCredentialsBundle;
|
||||||
import java.util.Optional;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
@ -65,7 +64,7 @@ public class RequestFactoryModuleTest {
|
||||||
RegistryConfig.CONFIG_SETTINGS.get().gcpProject.isLocal = true;
|
RegistryConfig.CONFIG_SETTINGS.get().gcpProject.isLocal = true;
|
||||||
try {
|
try {
|
||||||
HttpRequestFactory factory =
|
HttpRequestFactory factory =
|
||||||
RequestFactoryModule.provideHttpRequestFactory(credentialsBundle, Optional.empty());
|
RequestFactoryModule.provideHttpRequestFactory(credentialsBundle, "client-id");
|
||||||
HttpRequestInitializer initializer = factory.getInitializer();
|
HttpRequestInitializer initializer = factory.getInitializer();
|
||||||
assertThat(initializer).isNotNull();
|
assertThat(initializer).isNotNull();
|
||||||
HttpRequest request = factory.buildGetRequest(new GenericUrl("http://localhost"));
|
HttpRequest request = factory.buildGetRequest(new GenericUrl("http://localhost"));
|
||||||
|
@ -79,29 +78,7 @@ public class RequestFactoryModuleTest {
|
||||||
@Test
|
@Test
|
||||||
void test_provideHttpRequestFactory_remote() throws Exception {
|
void test_provideHttpRequestFactory_remote() throws Exception {
|
||||||
when(credentialsBundle.getHttpRequestInitializer()).thenReturn(httpRequestInitializer);
|
when(credentialsBundle.getHttpRequestInitializer()).thenReturn(httpRequestInitializer);
|
||||||
// Make sure that example.com creates a request factory with the UNITTEST client id but no
|
// Mock the request/response to/from the OIDC server requesting an ID token
|
||||||
boolean origIsLocal = RegistryConfig.CONFIG_SETTINGS.get().gcpProject.isLocal;
|
|
||||||
RegistryConfig.CONFIG_SETTINGS.get().gcpProject.isLocal = false;
|
|
||||||
try {
|
|
||||||
HttpRequestFactory factory =
|
|
||||||
RequestFactoryModule.provideHttpRequestFactory(credentialsBundle, Optional.empty());
|
|
||||||
HttpRequestInitializer initializer = factory.getInitializer();
|
|
||||||
assertThat(initializer).isNotNull();
|
|
||||||
// HttpRequestFactory#buildGetRequest() calls initialize() once.
|
|
||||||
HttpRequest request = factory.buildGetRequest(new GenericUrl("http://localhost"));
|
|
||||||
verify(httpRequestInitializer).initialize(request);
|
|
||||||
assertThat(request.getConnectTimeout()).isEqualTo(REQUEST_TIMEOUT_MS);
|
|
||||||
assertThat(request.getReadTimeout()).isEqualTo(REQUEST_TIMEOUT_MS);
|
|
||||||
verifyNoMoreInteractions(httpRequestInitializer);
|
|
||||||
} finally {
|
|
||||||
RegistryConfig.CONFIG_SETTINGS.get().gcpProject.isLocal = origIsLocal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void test_provideHttpRequestFactory_remote_withIap() throws Exception {
|
|
||||||
when(credentialsBundle.getHttpRequestInitializer()).thenReturn(httpRequestInitializer);
|
|
||||||
// Mock the request/response to/from the IAP server requesting an ID token
|
|
||||||
UserCredentials mockUserCredentials = mock(UserCredentials.class);
|
UserCredentials mockUserCredentials = mock(UserCredentials.class);
|
||||||
when(credentialsBundle.getGoogleCredentials()).thenReturn(mockUserCredentials);
|
when(credentialsBundle.getGoogleCredentials()).thenReturn(mockUserCredentials);
|
||||||
HttpTransport mockTransport = mock(HttpTransport.class);
|
HttpTransport mockTransport = mock(HttpTransport.class);
|
||||||
|
@ -114,17 +91,16 @@ public class RequestFactoryModuleTest {
|
||||||
HttpResponse mockResponse = mock(HttpResponse.class);
|
HttpResponse mockResponse = mock(HttpResponse.class);
|
||||||
when(mockPostRequest.execute()).thenReturn(mockResponse);
|
when(mockPostRequest.execute()).thenReturn(mockResponse);
|
||||||
GenericData genericDataResponse = new GenericData();
|
GenericData genericDataResponse = new GenericData();
|
||||||
genericDataResponse.set("id_token", "iapIdToken");
|
genericDataResponse.set("id_token", "oidc.token");
|
||||||
when(mockResponse.parseAs(GenericData.class)).thenReturn(genericDataResponse);
|
when(mockResponse.parseAs(GenericData.class)).thenReturn(genericDataResponse);
|
||||||
|
|
||||||
boolean origIsLocal = RegistryConfig.CONFIG_SETTINGS.get().gcpProject.isLocal;
|
boolean origIsLocal = RegistryConfig.CONFIG_SETTINGS.get().gcpProject.isLocal;
|
||||||
RegistryConfig.CONFIG_SETTINGS.get().gcpProject.isLocal = false;
|
RegistryConfig.CONFIG_SETTINGS.get().gcpProject.isLocal = false;
|
||||||
try {
|
try {
|
||||||
HttpRequestFactory factory =
|
HttpRequestFactory factory =
|
||||||
RequestFactoryModule.provideHttpRequestFactory(
|
RequestFactoryModule.provideHttpRequestFactory(credentialsBundle, "clientId");
|
||||||
credentialsBundle, Optional.of("iapClientId"));
|
|
||||||
HttpRequest request = factory.buildGetRequest(new GenericUrl("http://localhost"));
|
HttpRequest request = factory.buildGetRequest(new GenericUrl("http://localhost"));
|
||||||
assertThat(request.getHeaders().get("Proxy-Authorization")).isEqualTo("Bearer iapIdToken");
|
assertThat(request.getHeaders().get("Proxy-Authorization")).isEqualTo("Bearer oidc.token");
|
||||||
assertThat(request.getConnectTimeout()).isEqualTo(REQUEST_TIMEOUT_MS);
|
assertThat(request.getConnectTimeout()).isEqualTo(REQUEST_TIMEOUT_MS);
|
||||||
assertThat(request.getReadTimeout()).isEqualTo(REQUEST_TIMEOUT_MS);
|
assertThat(request.getReadTimeout()).isEqualTo(REQUEST_TIMEOUT_MS);
|
||||||
verify(httpRequestInitializer).initialize(request);
|
verify(httpRequestInitializer).initialize(request);
|
||||||
|
|
|
@ -26,13 +26,13 @@ public class ServiceConnectionTest {
|
||||||
void testServerUrl_notCanary() {
|
void testServerUrl_notCanary() {
|
||||||
ServiceConnection connection = new ServiceConnection().withService(DEFAULT, false);
|
ServiceConnection connection = new ServiceConnection().withService(DEFAULT, false);
|
||||||
String serverUrl = connection.getServer().toString();
|
String serverUrl = connection.getServer().toString();
|
||||||
assertThat(serverUrl).isEqualTo("https://localhost"); // See default-config.yaml
|
assertThat(serverUrl).isEqualTo("https://default.example.com"); // See default-config.yaml
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testServerUrl_canary() {
|
void testServerUrl_canary() {
|
||||||
ServiceConnection connection = new ServiceConnection().withService(DEFAULT, true);
|
ServiceConnection connection = new ServiceConnection().withService(DEFAULT, true);
|
||||||
String serverUrl = connection.getServer().toString();
|
String serverUrl = connection.getServer().toString();
|
||||||
assertThat(serverUrl).isEqualTo("https://nomulus-dot-localhost");
|
assertThat(serverUrl).isEqualTo("https://nomulus-dot-default.example.com");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,7 +70,7 @@ class RegistrarSettingsActionTest extends RegistrarSettingsActionTestCase {
|
||||||
cloudTasksHelper.assertTasksEnqueued(
|
cloudTasksHelper.assertTasksEnqueued(
|
||||||
"sheet",
|
"sheet",
|
||||||
new TaskMatcher()
|
new TaskMatcher()
|
||||||
.url(SyncRegistrarsSheetAction.PATH)
|
.path(SyncRegistrarsSheetAction.PATH)
|
||||||
.service("Backend")
|
.service("Backend")
|
||||||
.method(HttpMethod.GET));
|
.method(HttpMethod.GET));
|
||||||
assertMetric(CLIENT_ID, "update", "[OWNER]", "SUCCESS");
|
assertMetric(CLIENT_ID, "update", "[OWNER]", "SUCCESS");
|
||||||
|
|
|
@ -16,7 +16,6 @@ package google.registry.proxy;
|
||||||
|
|
||||||
import static google.registry.util.ResourceUtils.readResourceBytes;
|
import static google.registry.util.ResourceUtils.readResourceBytes;
|
||||||
|
|
||||||
import com.google.auth.oauth2.GoogleCredentials;
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
|
@ -44,7 +43,6 @@ import io.netty.handler.timeout.ReadTimeoutHandler;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
@ -116,7 +114,7 @@ public final class EppProtocolModule {
|
||||||
config.epp.headerLengthBytes,
|
config.epp.headerLengthBytes,
|
||||||
// Adjustment applied to the header field value in order to obtain message length.
|
// Adjustment applied to the header field value in order to obtain message length.
|
||||||
-config.epp.headerLengthBytes,
|
-config.epp.headerLengthBytes,
|
||||||
// Initial bytes to strip (i. e. strip the length header).
|
// Initial bytes to strip (i.e. strip the length header).
|
||||||
config.epp.headerLengthBytes);
|
config.epp.headerLengthBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,18 +147,12 @@ public final class EppProtocolModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
static EppServiceHandler provideEppServiceHandler(
|
static EppServiceHandler provideEppServiceHandler(
|
||||||
Supplier<GoogleCredentials> refreshedCredentialsSupplier,
|
@Named("idToken") Supplier<String> idTokenSupplier,
|
||||||
@Named("iapClientId") Optional<String> iapClientId,
|
|
||||||
@Named("hello") byte[] helloBytes,
|
@Named("hello") byte[] helloBytes,
|
||||||
FrontendMetrics metrics,
|
FrontendMetrics metrics,
|
||||||
ProxyConfig config) {
|
ProxyConfig config) {
|
||||||
return new EppServiceHandler(
|
return new EppServiceHandler(
|
||||||
config.epp.relayHost,
|
config.epp.relayHost, config.epp.relayPath, idTokenSupplier, helloBytes, metrics);
|
||||||
config.epp.relayPath,
|
|
||||||
refreshedCredentialsSupplier,
|
|
||||||
iapClientId,
|
|
||||||
helloBytes,
|
|
||||||
metrics);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
|
|
|
@ -40,7 +40,7 @@ public class ProxyConfig {
|
||||||
private static final String CUSTOM_CONFIG_FORMATTER = "config/proxy-config-%s.yaml";
|
private static final String CUSTOM_CONFIG_FORMATTER = "config/proxy-config-%s.yaml";
|
||||||
|
|
||||||
public String projectId;
|
public String projectId;
|
||||||
public String iapClientId;
|
public String oauthClientId;
|
||||||
public List<String> gcpScopes;
|
public List<String> gcpScopes;
|
||||||
public int serverCertificateCacheSeconds;
|
public int serverCertificateCacheSeconds;
|
||||||
public Gcs gcs;
|
public Gcs gcs;
|
||||||
|
|
|
@ -28,6 +28,7 @@ import com.google.cloud.storage.BlobId;
|
||||||
import com.google.cloud.storage.Storage;
|
import com.google.cloud.storage.Storage;
|
||||||
import com.google.cloud.storage.StorageException;
|
import com.google.cloud.storage.StorageException;
|
||||||
import com.google.cloud.storage.StorageOptions;
|
import com.google.cloud.storage.StorageOptions;
|
||||||
|
import com.google.common.base.Suppliers;
|
||||||
import com.google.monitoring.metrics.MetricReporter;
|
import com.google.monitoring.metrics.MetricReporter;
|
||||||
import dagger.Component;
|
import dagger.Component;
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
|
@ -45,6 +46,7 @@ import google.registry.proxy.handler.ProxyProtocolHandler;
|
||||||
import google.registry.util.Clock;
|
import google.registry.util.Clock;
|
||||||
import google.registry.util.GoogleCredentialsBundle;
|
import google.registry.util.GoogleCredentialsBundle;
|
||||||
import google.registry.util.JdkLoggerConfig;
|
import google.registry.util.JdkLoggerConfig;
|
||||||
|
import google.registry.util.OidcTokenUtils;
|
||||||
import google.registry.util.SystemClock;
|
import google.registry.util.SystemClock;
|
||||||
import io.netty.handler.logging.LogLevel;
|
import io.netty.handler.logging.LogLevel;
|
||||||
import io.netty.handler.logging.LoggingHandler;
|
import io.netty.handler.logging.LoggingHandler;
|
||||||
|
@ -59,6 +61,7 @@ import java.util.Set;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import java.util.logging.ConsoleHandler;
|
import java.util.logging.ConsoleHandler;
|
||||||
import java.util.logging.Handler;
|
import java.util.logging.Handler;
|
||||||
|
@ -126,7 +129,8 @@ public class ProxyModule {
|
||||||
// corresponds to Level.FINE (JUL log level). It uses a JUL logger with the name
|
// corresponds to Level.FINE (JUL log level). It uses a JUL logger with the name
|
||||||
// "io.netty.handler.logging.LoggingHandler" to actually process the logs. This JUL logger is
|
// "io.netty.handler.logging.LoggingHandler" to actually process the logs. This JUL logger is
|
||||||
// set to Level.FINE if the --log parameter is passed, so that it does not filter out logs
|
// set to Level.FINE if the --log parameter is passed, so that it does not filter out logs
|
||||||
// that the LoggingHandler writes. Otherwise the logs are silently ignored because the default
|
// that the LoggingHandler writes. Otherwise, the logs are silently ignored because the
|
||||||
|
// default
|
||||||
// JUL logger level is Level.INFO.
|
// JUL logger level is Level.INFO.
|
||||||
JdkLoggerConfig.getConfig(LoggingHandler.class).setLevel(Level.FINE);
|
JdkLoggerConfig.getConfig(LoggingHandler.class).setLevel(Level.FINE);
|
||||||
// Log source IP information if --log parameter is passed. This is considered PII and should
|
// Log source IP information if --log parameter is passed. This is considered PII and should
|
||||||
|
@ -158,10 +162,10 @@ public class ProxyModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Named("iapClientId")
|
@Named("oauthClientId")
|
||||||
@Singleton
|
@Singleton
|
||||||
Optional<String> provideIapClientId(ProxyConfig config) {
|
String provideClientId(ProxyConfig config) {
|
||||||
return Optional.ofNullable(config.iapClientId);
|
return config.oauthClientId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
@ -242,6 +246,23 @@ public class ProxyModule {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides an OIDC token with the given OAuth client ID as audience used for authentication.
|
||||||
|
*
|
||||||
|
* <p>The token is cached for 1 hour to reduce repeated calls to the metadata server.
|
||||||
|
*
|
||||||
|
* @see <a href="https://cloud.google.com/docs/authentication/token-types#id-lifetime">ID token
|
||||||
|
* lifetime</a>
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
@Provides
|
||||||
|
@Named("idToken")
|
||||||
|
static Supplier<String> provideOidcToken(
|
||||||
|
GoogleCredentialsBundle credentialsBundle, @Named("oauthClientId") String clientId) {
|
||||||
|
return Suppliers.memoizeWithExpiration(
|
||||||
|
() -> OidcTokenUtils.createOidcToken(credentialsBundle, clientId), 1, TimeUnit.HOURS);
|
||||||
|
}
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
static CloudKMS provideCloudKms(GoogleCredentialsBundle credentialsBundle, ProxyConfig config) {
|
static CloudKMS provideCloudKms(GoogleCredentialsBundle credentialsBundle, ProxyConfig config) {
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
|
|
||||||
package google.registry.proxy;
|
package google.registry.proxy;
|
||||||
|
|
||||||
import com.google.auth.oauth2.GoogleCredentials;
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
|
@ -35,7 +34,6 @@ import google.registry.util.Clock;
|
||||||
import io.netty.channel.ChannelHandler;
|
import io.netty.channel.ChannelHandler;
|
||||||
import io.netty.handler.codec.LineBasedFrameDecoder;
|
import io.netty.handler.codec.LineBasedFrameDecoder;
|
||||||
import io.netty.handler.timeout.ReadTimeoutHandler;
|
import io.netty.handler.timeout.ReadTimeoutHandler;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
@ -46,7 +44,9 @@ import javax.inject.Singleton;
|
||||||
|
|
||||||
/** A module that provides the {@link FrontendProtocol} used for whois protocol. */
|
/** A module that provides the {@link FrontendProtocol} used for whois protocol. */
|
||||||
@Module
|
@Module
|
||||||
public class WhoisProtocolModule {
|
public final class WhoisProtocolModule {
|
||||||
|
|
||||||
|
private WhoisProtocolModule() {}
|
||||||
|
|
||||||
/** Dagger qualifier to provide whois protocol related handlers and other bindings. */
|
/** Dagger qualifier to provide whois protocol related handlers and other bindings. */
|
||||||
@Qualifier
|
@Qualifier
|
||||||
|
@ -93,15 +93,10 @@ public class WhoisProtocolModule {
|
||||||
@Provides
|
@Provides
|
||||||
static WhoisServiceHandler provideWhoisServiceHandler(
|
static WhoisServiceHandler provideWhoisServiceHandler(
|
||||||
ProxyConfig config,
|
ProxyConfig config,
|
||||||
Supplier<GoogleCredentials> refreshedCredentialsSupplier,
|
@Named("idToken") Supplier<String> idTokenSupplier,
|
||||||
@Named("iapClientId") Optional<String> iapClientId,
|
|
||||||
FrontendMetrics metrics) {
|
FrontendMetrics metrics) {
|
||||||
return new WhoisServiceHandler(
|
return new WhoisServiceHandler(
|
||||||
config.whois.relayHost,
|
config.whois.relayHost, config.whois.relayPath, idTokenSupplier, metrics);
|
||||||
config.whois.relayPath,
|
|
||||||
refreshedCredentialsSupplier,
|
|
||||||
iapClientId,
|
|
||||||
metrics);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|
|
@ -8,14 +8,17 @@
|
||||||
# GCP project ID
|
# GCP project ID
|
||||||
projectId: your-gcp-project-id
|
projectId: your-gcp-project-id
|
||||||
|
|
||||||
# IAP client ID, if IAP is enabled for this project
|
# OAuth client ID set as the audience of the OIDC token. This value must be the
|
||||||
iapClientId: null
|
# same as the auth.oauthClientId value in Nomulus config file, which usually is
|
||||||
|
# the IAP client ID, to allow the request to access IAP protected endpoints.
|
||||||
|
# Regular OIDC authentication mechanism also checks for this audience.
|
||||||
|
oauthClientId: iap-client-id
|
||||||
|
|
||||||
# OAuth scope that the GoogleCredential will be constructed with. This list
|
# OAuth scope that the GoogleCredential will be constructed with. This list
|
||||||
# should include all service scopes that the proxy depends on.
|
# should include all service scopes that the proxy depends on.
|
||||||
gcpScopes:
|
gcpScopes:
|
||||||
# The default OAuth scope granted to GCE instances. Local development instance
|
# The default OAuth scope granted to GCE instances. Local development instance
|
||||||
# needs this scope to mimic running on GCE. Currently it is used to access
|
# needs this scope to mimic running on GCE. Currently, it is used to access
|
||||||
# Cloud KMS and Stackdriver Monitoring APIs.
|
# Cloud KMS and Stackdriver Monitoring APIs.
|
||||||
- https://www.googleapis.com/auth/cloud-platform
|
- https://www.googleapis.com/auth/cloud-platform
|
||||||
|
|
||||||
|
@ -25,8 +28,8 @@ gcpScopes:
|
||||||
|
|
||||||
# Server certificate is cached for 30 minutes.
|
# Server certificate is cached for 30 minutes.
|
||||||
#
|
#
|
||||||
# Encrypted server server certificate and private keys are stored on GCS. They
|
# Encrypted server certificate and private keys are stored on GCS. They
|
||||||
# are cached and shared for all connections for 30 minutes. We not not cache
|
# are cached and shared for all connections for 30 minutes. We do not cache
|
||||||
# the certificate indefinitely because if we upload a new one to GCS, all
|
# the certificate indefinitely because if we upload a new one to GCS, all
|
||||||
# existing instances need to be killed if they cache the old one indefinitely.
|
# existing instances need to be killed if they cache the old one indefinitely.
|
||||||
serverCertificateCacheSeconds: 1800
|
serverCertificateCacheSeconds: 1800
|
||||||
|
@ -59,8 +62,8 @@ epp:
|
||||||
# The first 4 bytes in a message is the total length of message, in bytes.
|
# The first 4 bytes in a message is the total length of message, in bytes.
|
||||||
#
|
#
|
||||||
# We accept a message up to 1 GB, which should be plentiful, if not over the
|
# We accept a message up to 1 GB, which should be plentiful, if not over the
|
||||||
# top. In fact we should probably limit this to a more reasonable number, as a
|
# top. In fact, we should probably limit this to a more reasonable number, as
|
||||||
# 1 GB message will likely cause the proxy to go out of memory.
|
# a 1 GB message will likely cause the proxy to go out of memory.
|
||||||
#
|
#
|
||||||
# See also: RFC 5734 4 Data Unit Format
|
# See also: RFC 5734 4 Data Unit Format
|
||||||
# (https://tools.ietf.org/html/rfc5734#section-4).
|
# (https://tools.ietf.org/html/rfc5734#section-4).
|
||||||
|
@ -76,7 +79,7 @@ epp:
|
||||||
# Time after which an idle connection will be closed.
|
# Time after which an idle connection will be closed.
|
||||||
#
|
#
|
||||||
# The RFC gives registry discretionary power to set a timeout period. 1 hr
|
# The RFC gives registry discretionary power to set a timeout period. 1 hr
|
||||||
# should be reasonable enough for any registrar to login and submit their
|
# should be reasonable enough for any registrar to log in and submit their
|
||||||
# request.
|
# request.
|
||||||
readTimeoutSeconds: 3600
|
readTimeoutSeconds: 3600
|
||||||
|
|
||||||
|
@ -91,7 +94,7 @@ epp:
|
||||||
# Default quota for any userId not matched in customQuota.
|
# Default quota for any userId not matched in customQuota.
|
||||||
defaultQuota:
|
defaultQuota:
|
||||||
|
|
||||||
# List of identifiers, e. g. IP address, certificate hash.
|
# List of identifiers, e.g. IP address, certificate hash.
|
||||||
#
|
#
|
||||||
# userId for defaultQuota should always be an empty list. Any value
|
# userId for defaultQuota should always be an empty list. Any value
|
||||||
# in the list will be discarded.
|
# in the list will be discarded.
|
||||||
|
@ -129,7 +132,7 @@ whois:
|
||||||
# (http://www.freesoft.org/CIE/RFC/1035/9.htm).
|
# (http://www.freesoft.org/CIE/RFC/1035/9.htm).
|
||||||
maxMessageLengthBytes: 512
|
maxMessageLengthBytes: 512
|
||||||
|
|
||||||
# Whois protocol is transient, the client should not establish a long lasting
|
# Whois protocol is transient, the client should not establish a long-lasting
|
||||||
# idle connection.
|
# idle connection.
|
||||||
readTimeoutSeconds: 60
|
readTimeoutSeconds: 60
|
||||||
|
|
||||||
|
@ -144,7 +147,7 @@ whois:
|
||||||
# Default quota for any userId not matched in customQuota.
|
# Default quota for any userId not matched in customQuota.
|
||||||
defaultQuota:
|
defaultQuota:
|
||||||
|
|
||||||
# List of identifiers, e. g. IP address, certificate hash.
|
# List of identifiers, e.g. IP address, certificate hash.
|
||||||
#
|
#
|
||||||
# userId for defaultQuota should always be an empty list.
|
# userId for defaultQuota should always be an empty list.
|
||||||
userId: []
|
userId: []
|
||||||
|
|
|
@ -20,7 +20,6 @@ import static google.registry.networking.handler.SslServerInitializer.CLIENT_CER
|
||||||
import static google.registry.proxy.handler.ProxyProtocolHandler.REMOTE_ADDRESS_KEY;
|
import static google.registry.proxy.handler.ProxyProtocolHandler.REMOTE_ADDRESS_KEY;
|
||||||
import static google.registry.util.X509Utils.getCertificateHash;
|
import static google.registry.util.X509Utils.getCertificateHash;
|
||||||
|
|
||||||
import com.google.auth.oauth2.GoogleCredentials;
|
|
||||||
import com.google.common.flogger.FluentLogger;
|
import com.google.common.flogger.FluentLogger;
|
||||||
import google.registry.proxy.metric.FrontendMetrics;
|
import google.registry.proxy.metric.FrontendMetrics;
|
||||||
import google.registry.util.ProxyHttpHeaders;
|
import google.registry.util.ProxyHttpHeaders;
|
||||||
|
@ -37,7 +36,6 @@ import io.netty.handler.ssl.SslHandshakeCompletionEvent;
|
||||||
import io.netty.util.AttributeKey;
|
import io.netty.util.AttributeKey;
|
||||||
import io.netty.util.concurrent.Promise;
|
import io.netty.util.concurrent.Promise;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
/** Handler that processes EPP protocol logic. */
|
/** Handler that processes EPP protocol logic. */
|
||||||
|
@ -62,12 +60,11 @@ public class EppServiceHandler extends HttpsRelayServiceHandler {
|
||||||
public EppServiceHandler(
|
public EppServiceHandler(
|
||||||
String relayHost,
|
String relayHost,
|
||||||
String relayPath,
|
String relayPath,
|
||||||
Supplier<GoogleCredentials> refreshedCredentialsSupplier,
|
Supplier<String> idTokenSupplier,
|
||||||
Optional<String> iapClientId,
|
|
||||||
byte[] helloBytes,
|
byte[] helloBytes,
|
||||||
FrontendMetrics metrics) {
|
FrontendMetrics metrics) {
|
||||||
super(relayHost, relayPath, refreshedCredentialsSupplier, iapClientId, metrics);
|
super(relayHost, relayPath, idTokenSupplier, metrics);
|
||||||
this.helloBytes = helloBytes;
|
this.helloBytes = helloBytes.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -91,6 +88,7 @@ public class EppServiceHandler extends HttpsRelayServiceHandler {
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
@SuppressWarnings("unused")
|
||||||
Promise<X509Certificate> unusedPromise =
|
Promise<X509Certificate> unusedPromise =
|
||||||
ctx.channel()
|
ctx.channel()
|
||||||
.attr(CLIENT_CERTIFICATE_PROMISE_KEY)
|
.attr(CLIENT_CERTIFICATE_PROMISE_KEY)
|
||||||
|
@ -110,6 +108,7 @@ public class EppServiceHandler extends HttpsRelayServiceHandler {
|
||||||
logger.atWarning().withCause(promise.cause()).log(
|
logger.atWarning().withCause(promise.cause()).log(
|
||||||
"Cannot finish handshake for channel %s, remote IP %s",
|
"Cannot finish handshake for channel %s, remote IP %s",
|
||||||
ctx.channel(), ctx.channel().attr(REMOTE_ADDRESS_KEY).get());
|
ctx.channel(), ctx.channel().attr(REMOTE_ADDRESS_KEY).get());
|
||||||
|
@SuppressWarnings("unused")
|
||||||
ChannelFuture unusedFuture = ctx.close();
|
ChannelFuture unusedFuture = ctx.close();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -136,7 +135,7 @@ public class EppServiceHandler extends HttpsRelayServiceHandler {
|
||||||
checkArgument(msg instanceof HttpResponse);
|
checkArgument(msg instanceof HttpResponse);
|
||||||
HttpResponse response = (HttpResponse) msg;
|
HttpResponse response = (HttpResponse) msg;
|
||||||
String sessionAliveValue = response.headers().get(ProxyHttpHeaders.EPP_SESSION);
|
String sessionAliveValue = response.headers().get(ProxyHttpHeaders.EPP_SESSION);
|
||||||
if (sessionAliveValue != null && sessionAliveValue.equals("close")) {
|
if ("close".equals(sessionAliveValue)) {
|
||||||
promise.addListener(ChannelFutureListener.CLOSE);
|
promise.addListener(ChannelFutureListener.CLOSE);
|
||||||
}
|
}
|
||||||
super.write(ctx, msg, promise);
|
super.write(ctx, msg, promise);
|
||||||
|
|
|
@ -16,12 +16,7 @@ package google.registry.proxy.handler;
|
||||||
|
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
|
||||||
import com.google.auth.oauth2.GoogleCredentials;
|
|
||||||
import com.google.auth.oauth2.IdToken;
|
|
||||||
import com.google.auth.oauth2.IdTokenProvider;
|
|
||||||
import com.google.auth.oauth2.IdTokenProvider.Option;
|
|
||||||
import com.google.common.base.Throwables;
|
import com.google.common.base.Throwables;
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.flogger.FluentLogger;
|
import com.google.common.flogger.FluentLogger;
|
||||||
import google.registry.proxy.metric.FrontendMetrics;
|
import google.registry.proxy.metric.FrontendMetrics;
|
||||||
|
@ -42,11 +37,9 @@ import io.netty.handler.codec.http.cookie.ClientCookieDecoder;
|
||||||
import io.netty.handler.codec.http.cookie.ClientCookieEncoder;
|
import io.netty.handler.codec.http.cookie.ClientCookieEncoder;
|
||||||
import io.netty.handler.codec.http.cookie.Cookie;
|
import io.netty.handler.codec.http.cookie.Cookie;
|
||||||
import io.netty.handler.timeout.ReadTimeoutException;
|
import io.netty.handler.timeout.ReadTimeoutException;
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import javax.net.ssl.SSLHandshakeException;
|
import javax.net.ssl.SSLHandshakeException;
|
||||||
|
|
||||||
|
@ -63,8 +56,8 @@ import javax.net.ssl.SSLHandshakeException;
|
||||||
* of the next outbound handler in the channel pipeline, which eventually writes the response bytes
|
* of the next outbound handler in the channel pipeline, which eventually writes the response bytes
|
||||||
* to the remote peer of this channel.
|
* to the remote peer of this channel.
|
||||||
*
|
*
|
||||||
* <p>This handler is session aware and will store all the session cookies that the are contained in
|
* <p>This handler is session-aware and will store all the session cookies that are contained in the
|
||||||
* the HTTP response headers, which are added back to headers of subsequent HTTP requests.
|
* HTTP response headers, which are added back to headers of subsequent HTTP requests.
|
||||||
*/
|
*/
|
||||||
public abstract class HttpsRelayServiceHandler extends ByteToMessageCodec<FullHttpResponse> {
|
public abstract class HttpsRelayServiceHandler extends ByteToMessageCodec<FullHttpResponse> {
|
||||||
|
|
||||||
|
@ -79,21 +72,18 @@ public abstract class HttpsRelayServiceHandler extends ByteToMessageCodec<FullHt
|
||||||
private final Map<String, Cookie> cookieStore = new LinkedHashMap<>();
|
private final Map<String, Cookie> cookieStore = new LinkedHashMap<>();
|
||||||
private final String relayHost;
|
private final String relayHost;
|
||||||
private final String relayPath;
|
private final String relayPath;
|
||||||
private final Supplier<GoogleCredentials> refreshedCredentialsSupplier;
|
private final Supplier<String> idTokenSupplier;
|
||||||
private final Optional<String> iapClientId;
|
|
||||||
|
|
||||||
protected final FrontendMetrics metrics;
|
protected final FrontendMetrics metrics;
|
||||||
|
|
||||||
HttpsRelayServiceHandler(
|
HttpsRelayServiceHandler(
|
||||||
String relayHost,
|
String relayHost,
|
||||||
String relayPath,
|
String relayPath,
|
||||||
Supplier<GoogleCredentials> refreshedCredentialsSupplier,
|
Supplier<String> idTokenSupplier,
|
||||||
Optional<String> iapClientId,
|
|
||||||
FrontendMetrics metrics) {
|
FrontendMetrics metrics) {
|
||||||
this.relayHost = relayHost;
|
this.relayHost = relayHost;
|
||||||
this.relayPath = relayPath;
|
this.relayPath = relayPath;
|
||||||
this.refreshedCredentialsSupplier = refreshedCredentialsSupplier;
|
this.idTokenSupplier = idTokenSupplier;
|
||||||
this.iapClientId = iapClientId;
|
|
||||||
this.metrics = metrics;
|
this.metrics = metrics;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,30 +98,12 @@ public abstract class HttpsRelayServiceHandler extends ByteToMessageCodec<FullHt
|
||||||
protected FullHttpRequest decodeFullHttpRequest(ByteBuf byteBuf) {
|
protected FullHttpRequest decodeFullHttpRequest(ByteBuf byteBuf) {
|
||||||
FullHttpRequest request =
|
FullHttpRequest request =
|
||||||
new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, relayPath);
|
new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, relayPath);
|
||||||
GoogleCredentials credentials = refreshedCredentialsSupplier.get();
|
|
||||||
request
|
request
|
||||||
.headers()
|
.headers()
|
||||||
.set(HttpHeaderNames.USER_AGENT, "Proxy")
|
.set(HttpHeaderNames.USER_AGENT, "Proxy")
|
||||||
.set(HttpHeaderNames.HOST, relayHost)
|
.set(HttpHeaderNames.HOST, relayHost)
|
||||||
.set(
|
.set(HttpHeaderNames.AUTHORIZATION, "Bearer " + idTokenSupplier.get())
|
||||||
HttpHeaderNames.AUTHORIZATION, "Bearer " + credentials.getAccessToken().getTokenValue())
|
|
||||||
.setInt(HttpHeaderNames.CONTENT_LENGTH, byteBuf.readableBytes());
|
.setInt(HttpHeaderNames.CONTENT_LENGTH, byteBuf.readableBytes());
|
||||||
// Set the Proxy-Authorization header if using IAP
|
|
||||||
if (iapClientId.isPresent()) {
|
|
||||||
IdTokenProvider idTokenProvider = (IdTokenProvider) credentials;
|
|
||||||
try {
|
|
||||||
// Note: we use Option.FORMAT_FULL to make sure the JWT we receive contains the email
|
|
||||||
// address (as is required by IAP)
|
|
||||||
IdToken idToken =
|
|
||||||
idTokenProvider.idTokenWithAudience(
|
|
||||||
iapClientId.get(), ImmutableList.of(Option.FORMAT_FULL));
|
|
||||||
request
|
|
||||||
.headers()
|
|
||||||
.set(HttpHeaderNames.PROXY_AUTHORIZATION, "Bearer " + idToken.getTokenValue());
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.atSevere().withCause(e).log("Error when attempting to retrieve IAP ID token");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
request.content().writeBytes(byteBuf);
|
request.content().writeBytes(byteBuf);
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
@ -204,6 +176,7 @@ public abstract class HttpsRelayServiceHandler extends ByteToMessageCodec<FullHt
|
||||||
logger.atSevere().withCause(cause).log(
|
logger.atSevere().withCause(cause).log(
|
||||||
"Inbound exception caught for channel %s", ctx.channel());
|
"Inbound exception caught for channel %s", ctx.channel());
|
||||||
}
|
}
|
||||||
|
@SuppressWarnings("unused")
|
||||||
ChannelFuture unusedFuture = ctx.close();
|
ChannelFuture unusedFuture = ctx.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,6 +195,7 @@ public abstract class HttpsRelayServiceHandler extends ByteToMessageCodec<FullHt
|
||||||
logger.atSevere().withCause(channelFuture.cause()).log(
|
logger.atSevere().withCause(channelFuture.cause()).log(
|
||||||
"Outbound exception caught for channel %s", channelFuture.channel());
|
"Outbound exception caught for channel %s", channelFuture.channel());
|
||||||
}
|
}
|
||||||
|
@SuppressWarnings("unused")
|
||||||
ChannelFuture unusedFuture = channelFuture.channel().close();
|
ChannelFuture unusedFuture = channelFuture.channel().close();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -230,6 +204,9 @@ public abstract class HttpsRelayServiceHandler extends ByteToMessageCodec<FullHt
|
||||||
|
|
||||||
/** Exception thrown when the response status from GAE is not 200. */
|
/** Exception thrown when the response status from GAE is not 200. */
|
||||||
public static class NonOkHttpResponseException extends Exception {
|
public static class NonOkHttpResponseException extends Exception {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 5340993059579288708L;
|
||||||
|
|
||||||
NonOkHttpResponseException(FullHttpResponse response, Channel channel) {
|
NonOkHttpResponseException(FullHttpResponse response, Channel channel) {
|
||||||
super(
|
super(
|
||||||
String.format(
|
String.format(
|
||||||
|
|
|
@ -16,7 +16,6 @@ package google.registry.proxy.handler;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
|
||||||
import com.google.auth.oauth2.GoogleCredentials;
|
|
||||||
import google.registry.proxy.metric.FrontendMetrics;
|
import google.registry.proxy.metric.FrontendMetrics;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.channel.ChannelFutureListener;
|
import io.netty.channel.ChannelFutureListener;
|
||||||
|
@ -26,7 +25,6 @@ import io.netty.handler.codec.http.FullHttpRequest;
|
||||||
import io.netty.handler.codec.http.HttpHeaderNames;
|
import io.netty.handler.codec.http.HttpHeaderNames;
|
||||||
import io.netty.handler.codec.http.HttpHeaderValues;
|
import io.netty.handler.codec.http.HttpHeaderValues;
|
||||||
import io.netty.handler.codec.http.HttpResponse;
|
import io.netty.handler.codec.http.HttpResponse;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
/** Handler that processes WHOIS protocol logic. */
|
/** Handler that processes WHOIS protocol logic. */
|
||||||
|
@ -35,10 +33,9 @@ public final class WhoisServiceHandler extends HttpsRelayServiceHandler {
|
||||||
public WhoisServiceHandler(
|
public WhoisServiceHandler(
|
||||||
String relayHost,
|
String relayHost,
|
||||||
String relayPath,
|
String relayPath,
|
||||||
Supplier<GoogleCredentials> refreshedCredentialsSupplier,
|
Supplier<String> idTokenSupplier,
|
||||||
Optional<String> iapClientId,
|
|
||||||
FrontendMetrics metrics) {
|
FrontendMetrics metrics) {
|
||||||
super(relayHost, relayPath, refreshedCredentialsSupplier, iapClientId, metrics);
|
super(relayHost, relayPath, idTokenSupplier, metrics);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -24,7 +24,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
import com.google.common.base.Throwables;
|
import com.google.common.base.Throwables;
|
||||||
import google.registry.proxy.handler.HttpsRelayServiceHandler.NonOkHttpResponseException;
|
import google.registry.proxy.handler.HttpsRelayServiceHandler.NonOkHttpResponseException;
|
||||||
import google.registry.testing.FakeClock;
|
|
||||||
import google.registry.util.SelfSignedCaCertificate;
|
import google.registry.util.SelfSignedCaCertificate;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
|
@ -90,7 +89,7 @@ class EppProtocolModuleTest extends ProtocolModuleTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get a {@link ByteBuf} that represents the raw epp request with the given content. */
|
/** Get a {@link ByteBuf} that represents the raw epp request with the given content. */
|
||||||
private ByteBuf getByteBufFromContent(byte[] content) {
|
private static ByteBuf getByteBufFromContent(byte[] content) {
|
||||||
ByteBuf buffer = Unpooled.buffer();
|
ByteBuf buffer = Unpooled.buffer();
|
||||||
buffer.writeInt(content.length + HEADER_LENGTH);
|
buffer.writeInt(content.length + HEADER_LENGTH);
|
||||||
buffer.writeBytes(content);
|
buffer.writeBytes(content);
|
||||||
|
@ -102,18 +101,17 @@ class EppProtocolModuleTest extends ProtocolModuleTest {
|
||||||
new String(content, UTF_8),
|
new String(content, UTF_8),
|
||||||
PROXY_CONFIG.epp.relayHost,
|
PROXY_CONFIG.epp.relayHost,
|
||||||
PROXY_CONFIG.epp.relayPath,
|
PROXY_CONFIG.epp.relayPath,
|
||||||
TestModule.provideFakeCredentials().get(),
|
TestModule.provideFakeIdToken().get(),
|
||||||
getCertificateHash(certificate),
|
getCertificateHash(certificate),
|
||||||
CLIENT_ADDRESS,
|
CLIENT_ADDRESS,
|
||||||
TestModule.provideIapClientId(),
|
|
||||||
cookies);
|
cookies);
|
||||||
}
|
}
|
||||||
|
|
||||||
private FullHttpResponse makeEppHttpResponse(byte[] content, Cookie... cookies) {
|
private static FullHttpResponse makeEppHttpResponse(byte[] content, Cookie... cookies) {
|
||||||
return makeEppHttpResponse(content, HttpResponseStatus.OK, cookies);
|
return makeEppHttpResponse(content, HttpResponseStatus.OK, cookies);
|
||||||
}
|
}
|
||||||
|
|
||||||
private FullHttpResponse makeEppHttpResponse(
|
private static FullHttpResponse makeEppHttpResponse(
|
||||||
byte[] content, HttpResponseStatus status, Cookie... cookies) {
|
byte[] content, HttpResponseStatus status, Cookie... cookies) {
|
||||||
return TestUtils.makeEppHttpResponse(new String(content, UTF_8), status, cookies);
|
return TestUtils.makeEppHttpResponse(new String(content, UTF_8), status, cookies);
|
||||||
}
|
}
|
||||||
|
@ -121,7 +119,7 @@ class EppProtocolModuleTest extends ProtocolModuleTest {
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
@Override
|
@Override
|
||||||
void beforeEach() throws Exception {
|
void beforeEach() throws Exception {
|
||||||
testComponent = makeTestComponent(new FakeClock());
|
testComponent = makeTestComponent();
|
||||||
certificate = SelfSignedCaCertificate.create().cert();
|
certificate = SelfSignedCaCertificate.create().cert();
|
||||||
initializeChannel(
|
initializeChannel(
|
||||||
ch -> {
|
ch -> {
|
||||||
|
@ -129,6 +127,7 @@ class EppProtocolModuleTest extends ProtocolModuleTest {
|
||||||
ch.attr(CLIENT_CERTIFICATE_PROMISE_KEY).set(ch.eventLoop().newPromise());
|
ch.attr(CLIENT_CERTIFICATE_PROMISE_KEY).set(ch.eventLoop().newPromise());
|
||||||
addAllTestableHandlers(ch);
|
addAllTestableHandlers(ch);
|
||||||
});
|
});
|
||||||
|
@SuppressWarnings("unused")
|
||||||
Promise<X509Certificate> unusedPromise =
|
Promise<X509Certificate> unusedPromise =
|
||||||
channel.attr(CLIENT_CERTIFICATE_PROMISE_KEY).get().setSuccess(certificate);
|
channel.attr(CLIENT_CERTIFICATE_PROMISE_KEY).get().setSuccess(certificate);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,14 +17,7 @@ package google.registry.proxy;
|
||||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||||
import static google.registry.proxy.ProxyConfig.Environment.LOCAL;
|
import static google.registry.proxy.ProxyConfig.Environment.LOCAL;
|
||||||
import static google.registry.proxy.ProxyConfig.getProxyConfig;
|
import static google.registry.proxy.ProxyConfig.getProxyConfig;
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
import com.google.auth.oauth2.AccessToken;
|
|
||||||
import com.google.auth.oauth2.ComputeEngineCredentials;
|
|
||||||
import com.google.auth.oauth2.GoogleCredentials;
|
|
||||||
import com.google.auth.oauth2.IdToken;
|
|
||||||
import com.google.auth.oauth2.IdTokenProvider.Option;
|
|
||||||
import com.google.common.base.Suppliers;
|
import com.google.common.base.Suppliers;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
@ -59,9 +52,7 @@ import io.netty.channel.embedded.EmbeddedChannel;
|
||||||
import io.netty.handler.logging.LoggingHandler;
|
import io.netty.handler.logging.LoggingHandler;
|
||||||
import io.netty.handler.ssl.SslProvider;
|
import io.netty.handler.ssl.SslProvider;
|
||||||
import io.netty.handler.timeout.ReadTimeoutHandler;
|
import io.netty.handler.timeout.ReadTimeoutHandler;
|
||||||
import java.io.IOException;
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
@ -80,14 +71,14 @@ import org.junit.jupiter.api.BeforeEach;
|
||||||
* correctly performed by various handlers attached to its pipeline. Non-business essential handlers
|
* correctly performed by various handlers attached to its pipeline. Non-business essential handlers
|
||||||
* should be excluded.
|
* should be excluded.
|
||||||
*
|
*
|
||||||
* <p>Subclass should implement an no-arg constructor that calls constructors of this class,
|
* <p>Subclass should implement a no-arg constructor that calls constructors of this class,
|
||||||
* providing the method reference of the {@link TestComponent} method to call to obtain the list of
|
* providing the method reference of the {@link TestComponent} method to call to obtain the list of
|
||||||
* {@link ChannelHandler} providers for the {@link Protocol} to test, and optionally a set of {@link
|
* {@link ChannelHandler} providers for the {@link Protocol} to test, and optionally a set of {@link
|
||||||
* ChannelHandler} classes to exclude from testing.
|
* ChannelHandler} classes to exclude from testing.
|
||||||
*/
|
*/
|
||||||
public abstract class ProtocolModuleTest {
|
public abstract class ProtocolModuleTest {
|
||||||
|
|
||||||
static final ProxyConfig PROXY_CONFIG = getProxyConfig(Environment.LOCAL);
|
static final ProxyConfig PROXY_CONFIG = getProxyConfig(LOCAL);
|
||||||
|
|
||||||
TestComponent testComponent;
|
TestComponent testComponent;
|
||||||
|
|
||||||
|
@ -112,7 +103,7 @@ public abstract class ProtocolModuleTest {
|
||||||
FullHttpResponseRelayHandler.class,
|
FullHttpResponseRelayHandler.class,
|
||||||
// This handler is tested in its own unit tests. It is installed in web whois redirect
|
// This handler is tested in its own unit tests. It is installed in web whois redirect
|
||||||
// protocols. The end-to-end tests for the rest of the handlers in its pipeline need to
|
// protocols. The end-to-end tests for the rest of the handlers in its pipeline need to
|
||||||
// be able to emit incoming requests out of the channel for assertions. Therefore this
|
// be able to emit incoming requests out of the channel for assertions. Therefore, this
|
||||||
// handler is removed from the pipeline.
|
// handler is removed from the pipeline.
|
||||||
WebWhoisRedirectHandler.class,
|
WebWhoisRedirectHandler.class,
|
||||||
// The rest are not part of business logic and do not need to be tested, obviously.
|
// The rest are not part of business logic and do not need to be tested, obviously.
|
||||||
|
@ -150,7 +141,7 @@ public abstract class ProtocolModuleTest {
|
||||||
this(handlerProvidersMethod, DEFAULT_EXCLUDED_HANDLERS);
|
this(handlerProvidersMethod, DEFAULT_EXCLUDED_HANDLERS);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Excludes handler providers that are not of interested for testing. */
|
/** Excludes handler providers that are not of interest for testing. */
|
||||||
private ImmutableList<Provider<? extends ChannelHandler>> excludeHandlerProvidersForTesting(
|
private ImmutableList<Provider<? extends ChannelHandler>> excludeHandlerProvidersForTesting(
|
||||||
ImmutableList<Provider<? extends ChannelHandler>> handlerProviders) {
|
ImmutableList<Provider<? extends ChannelHandler>> handlerProviders) {
|
||||||
return handlerProviders.stream()
|
return handlerProviders.stream()
|
||||||
|
@ -177,7 +168,7 @@ public abstract class ProtocolModuleTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static TestComponent makeTestComponent(FakeClock fakeClock) {
|
static TestComponent makeTestComponent() {
|
||||||
return DaggerProtocolModuleTest_TestComponent.builder()
|
return DaggerProtocolModuleTest_TestComponent.builder()
|
||||||
.testModule(new TestModule(new FakeClock()))
|
.testModule(new TestModule(new FakeClock()))
|
||||||
.build();
|
.build();
|
||||||
|
@ -185,7 +176,7 @@ public abstract class ProtocolModuleTest {
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void beforeEach() throws Exception {
|
void beforeEach() throws Exception {
|
||||||
testComponent = makeTestComponent(new FakeClock());
|
testComponent = makeTestComponent();
|
||||||
initializeChannel(this::addAllTestableHandlers);
|
initializeChannel(this::addAllTestableHandlers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -244,10 +235,11 @@ public abstract class ProtocolModuleTest {
|
||||||
this.fakeClock = fakeClock;
|
this.fakeClock = fakeClock;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
@Named("iapClientId")
|
@Named("clientId")
|
||||||
public static Optional<String> provideIapClientId() {
|
public static String provideClientId() {
|
||||||
return Optional.of("iapClientId");
|
return "clientId";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
|
@ -264,19 +256,9 @@ public abstract class ProtocolModuleTest {
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
static Supplier<GoogleCredentials> provideFakeCredentials() {
|
@Named("idToken")
|
||||||
ComputeEngineCredentials mockCredentials = mock(ComputeEngineCredentials.class);
|
static Supplier<String> provideFakeIdToken() {
|
||||||
when(mockCredentials.getAccessToken()).thenReturn(new AccessToken("fake.test.token", null));
|
return Suppliers.ofInstance("fake.test.id.token");
|
||||||
IdToken mockIdToken = mock(IdToken.class);
|
|
||||||
when(mockIdToken.getTokenValue()).thenReturn("fake.test.id.token");
|
|
||||||
try {
|
|
||||||
when(mockCredentials.idTokenWithAudience(
|
|
||||||
"iapClientId", ImmutableList.of(Option.FORMAT_FULL)))
|
|
||||||
.thenReturn(mockIdToken);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
return Suppliers.ofInstance(mockCredentials);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
|
@ -306,7 +288,7 @@ public abstract class ProtocolModuleTest {
|
||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
static Environment provideEnvironment() {
|
static Environment provideEnvironment() {
|
||||||
return Environment.LOCAL;
|
return LOCAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
|
|
|
@ -17,10 +17,6 @@ package google.registry.proxy;
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static java.nio.charset.StandardCharsets.US_ASCII;
|
import static java.nio.charset.StandardCharsets.US_ASCII;
|
||||||
|
|
||||||
import com.google.auth.oauth2.GoogleCredentials;
|
|
||||||
import com.google.auth.oauth2.IdTokenProvider;
|
|
||||||
import com.google.auth.oauth2.IdTokenProvider.Option;
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import google.registry.util.ProxyHttpHeaders;
|
import google.registry.util.ProxyHttpHeaders;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
|
@ -39,10 +35,11 @@ import io.netty.handler.codec.http.cookie.ClientCookieEncoder;
|
||||||
import io.netty.handler.codec.http.cookie.Cookie;
|
import io.netty.handler.codec.http.cookie.Cookie;
|
||||||
import io.netty.handler.codec.http.cookie.ServerCookieEncoder;
|
import io.netty.handler.codec.http.cookie.ServerCookieEncoder;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
/** Utility class for various helper methods used in testing. */
|
/** Utility class for various helper methods used in testing. */
|
||||||
public class TestUtils {
|
public final class TestUtils {
|
||||||
|
|
||||||
|
private TestUtils() {}
|
||||||
|
|
||||||
public static FullHttpRequest makeHttpPostRequest(String content, String host, String path) {
|
public static FullHttpRequest makeHttpPostRequest(String content, String host, String path) {
|
||||||
ByteBuf buf = Unpooled.wrappedBuffer(content.getBytes(US_ASCII));
|
ByteBuf buf = Unpooled.wrappedBuffer(content.getBytes(US_ASCII));
|
||||||
|
@ -77,19 +74,13 @@ public class TestUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static FullHttpRequest makeWhoisHttpRequest(
|
public static FullHttpRequest makeWhoisHttpRequest(
|
||||||
String content,
|
String content, String host, String path, String idToken) throws IOException {
|
||||||
String host,
|
|
||||||
String path,
|
|
||||||
GoogleCredentials credentials,
|
|
||||||
Optional<String> iapClientId)
|
|
||||||
throws IOException {
|
|
||||||
FullHttpRequest request = makeHttpPostRequest(content, host, path);
|
FullHttpRequest request = makeHttpPostRequest(content, host, path);
|
||||||
request
|
request
|
||||||
.headers()
|
.headers()
|
||||||
.set("authorization", "Bearer " + credentials.getAccessToken().getTokenValue())
|
.set("authorization", "Bearer " + idToken)
|
||||||
.set(HttpHeaderNames.CONTENT_TYPE, "text/plain")
|
.set(HttpHeaderNames.CONTENT_TYPE, "text/plain")
|
||||||
.set("accept", "text/plain");
|
.set("accept", "text/plain");
|
||||||
maybeSetProxyAuthForIap(request, credentials, iapClientId);
|
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,21 +88,19 @@ public class TestUtils {
|
||||||
String content,
|
String content,
|
||||||
String host,
|
String host,
|
||||||
String path,
|
String path,
|
||||||
GoogleCredentials credentials,
|
String idToken,
|
||||||
String sslClientCertificateHash,
|
String sslClientCertificateHash,
|
||||||
String clientAddress,
|
String clientAddress,
|
||||||
Optional<String> iapClientId,
|
|
||||||
Cookie... cookies)
|
Cookie... cookies)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
FullHttpRequest request = makeHttpPostRequest(content, host, path);
|
FullHttpRequest request = makeHttpPostRequest(content, host, path);
|
||||||
request
|
request
|
||||||
.headers()
|
.headers()
|
||||||
.set("authorization", "Bearer " + credentials.getAccessToken().getTokenValue())
|
.set("authorization", "Bearer " + idToken)
|
||||||
.set(HttpHeaderNames.CONTENT_TYPE, "application/epp+xml")
|
.set(HttpHeaderNames.CONTENT_TYPE, "application/epp+xml")
|
||||||
.set("accept", "application/epp+xml")
|
.set("accept", "application/epp+xml")
|
||||||
.set(ProxyHttpHeaders.CERTIFICATE_HASH, sslClientCertificateHash)
|
.set(ProxyHttpHeaders.CERTIFICATE_HASH, sslClientCertificateHash)
|
||||||
.set(ProxyHttpHeaders.IP_ADDRESS, clientAddress);
|
.set(ProxyHttpHeaders.IP_ADDRESS, clientAddress);
|
||||||
maybeSetProxyAuthForIap(request, credentials, iapClientId);
|
|
||||||
if (cookies.length != 0) {
|
if (cookies.length != 0) {
|
||||||
request.headers().set("cookie", ClientCookieEncoder.STRICT.encode(cookies));
|
request.headers().set("cookie", ClientCookieEncoder.STRICT.encode(cookies));
|
||||||
}
|
}
|
||||||
|
@ -140,7 +129,7 @@ public class TestUtils {
|
||||||
* <p>This method is needed because an HTTP message decoded and aggregated from inbound {@link
|
* <p>This method is needed because an HTTP message decoded and aggregated from inbound {@link
|
||||||
* ByteBuf} is of a different class than the one written to the outbound {@link ByteBuf}, and The
|
* ByteBuf} is of a different class than the one written to the outbound {@link ByteBuf}, and The
|
||||||
* {@link ByteBuf} implementations that hold the content of the HTTP messages are different, even
|
* {@link ByteBuf} implementations that hold the content of the HTTP messages are different, even
|
||||||
* though the actual content, headers, etc are the same.
|
* though the actual content, headers, etc. are the same.
|
||||||
*
|
*
|
||||||
* <p>This method is not type-safe, msg1 & msg2 can be a request and a response, respectively. Do
|
* <p>This method is not type-safe, msg1 & msg2 can be a request and a response, respectively. Do
|
||||||
* not use this method directly.
|
* not use this method directly.
|
||||||
|
@ -161,16 +150,4 @@ public class TestUtils {
|
||||||
public static void assertHttpRequestEquivalent(HttpRequest req1, HttpRequest req2) {
|
public static void assertHttpRequestEquivalent(HttpRequest req1, HttpRequest req2) {
|
||||||
assertHttpMessageEquivalent(req1, req2);
|
assertHttpMessageEquivalent(req1, req2);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void maybeSetProxyAuthForIap(
|
|
||||||
FullHttpRequest request, GoogleCredentials credentials, Optional<String> iapClientId)
|
|
||||||
throws IOException {
|
|
||||||
if (iapClientId.isPresent()) {
|
|
||||||
String idTokenValue =
|
|
||||||
((IdTokenProvider) credentials)
|
|
||||||
.idTokenWithAudience(iapClientId.get(), ImmutableList.of(Option.FORMAT_FULL))
|
|
||||||
.getTokenValue();
|
|
||||||
request.headers().set("proxy-authorization", "Bearer " + idTokenValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,8 +53,7 @@ class WhoisProtocolModuleTest extends ProtocolModuleTest {
|
||||||
"test.tld",
|
"test.tld",
|
||||||
PROXY_CONFIG.whois.relayHost,
|
PROXY_CONFIG.whois.relayHost,
|
||||||
PROXY_CONFIG.whois.relayPath,
|
PROXY_CONFIG.whois.relayPath,
|
||||||
TestModule.provideFakeCredentials().get(),
|
TestModule.provideFakeIdToken().get());
|
||||||
TestModule.provideIapClientId());
|
|
||||||
assertThat(actualRequest).isEqualTo(expectedRequest);
|
assertThat(actualRequest).isEqualTo(expectedRequest);
|
||||||
assertThat(channel.isActive()).isTrue();
|
assertThat(channel.isActive()).isTrue();
|
||||||
// Nothing more to read.
|
// Nothing more to read.
|
||||||
|
@ -89,8 +88,7 @@ class WhoisProtocolModuleTest extends ProtocolModuleTest {
|
||||||
"test1.tld",
|
"test1.tld",
|
||||||
PROXY_CONFIG.whois.relayHost,
|
PROXY_CONFIG.whois.relayHost,
|
||||||
PROXY_CONFIG.whois.relayPath,
|
PROXY_CONFIG.whois.relayPath,
|
||||||
TestModule.provideFakeCredentials().get(),
|
TestModule.provideFakeIdToken().get());
|
||||||
TestModule.provideIapClientId());
|
|
||||||
assertThat(actualRequest1).isEqualTo(expectedRequest1);
|
assertThat(actualRequest1).isEqualTo(expectedRequest1);
|
||||||
// No more message at this point.
|
// No more message at this point.
|
||||||
assertThat((Object) channel.readInbound()).isNull();
|
assertThat((Object) channel.readInbound()).isNull();
|
||||||
|
@ -104,8 +102,7 @@ class WhoisProtocolModuleTest extends ProtocolModuleTest {
|
||||||
"test2.tld",
|
"test2.tld",
|
||||||
PROXY_CONFIG.whois.relayHost,
|
PROXY_CONFIG.whois.relayHost,
|
||||||
PROXY_CONFIG.whois.relayPath,
|
PROXY_CONFIG.whois.relayPath,
|
||||||
TestModule.provideFakeCredentials().get(),
|
TestModule.provideFakeIdToken().get());
|
||||||
TestModule.provideIapClientId());
|
|
||||||
assertThat(actualRequest2).isEqualTo(expectedRequest2);
|
assertThat(actualRequest2).isEqualTo(expectedRequest2);
|
||||||
// The third message is not complete yet.
|
// The third message is not complete yet.
|
||||||
assertThat(channel.isActive()).isTrue();
|
assertThat(channel.isActive()).isTrue();
|
||||||
|
|
|
@ -25,14 +25,8 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
import com.google.auth.oauth2.AccessToken;
|
|
||||||
import com.google.auth.oauth2.ComputeEngineCredentials;
|
|
||||||
import com.google.auth.oauth2.IdToken;
|
|
||||||
import com.google.auth.oauth2.IdTokenProvider.Option;
|
|
||||||
import com.google.common.base.Throwables;
|
import com.google.common.base.Throwables;
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import google.registry.proxy.TestUtils;
|
import google.registry.proxy.TestUtils;
|
||||||
import google.registry.proxy.handler.HttpsRelayServiceHandler.NonOkHttpResponseException;
|
import google.registry.proxy.handler.HttpsRelayServiceHandler.NonOkHttpResponseException;
|
||||||
import google.registry.proxy.metric.FrontendMetrics;
|
import google.registry.proxy.metric.FrontendMetrics;
|
||||||
|
@ -52,7 +46,6 @@ import io.netty.handler.codec.http.cookie.DefaultCookie;
|
||||||
import io.netty.util.concurrent.Promise;
|
import io.netty.util.concurrent.Promise;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.Optional;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
@ -69,27 +62,18 @@ class EppServiceHandlerTest {
|
||||||
private static final String RELAY_PATH = "/epp";
|
private static final String RELAY_PATH = "/epp";
|
||||||
private static final String CLIENT_ADDRESS = "epp.client.tld";
|
private static final String CLIENT_ADDRESS = "epp.client.tld";
|
||||||
private static final String PROTOCOL = "epp";
|
private static final String PROTOCOL = "epp";
|
||||||
private static final String IAP_CLIENT_ID = "iapClientId";
|
private static final String ID_TOKEN = "fake.id.token";
|
||||||
|
|
||||||
private static final ComputeEngineCredentials mockCredentials =
|
|
||||||
mock(ComputeEngineCredentials.class);
|
|
||||||
|
|
||||||
private X509Certificate clientCertificate;
|
private X509Certificate clientCertificate;
|
||||||
|
|
||||||
private final FrontendMetrics metrics = mock(FrontendMetrics.class);
|
private final FrontendMetrics metrics = mock(FrontendMetrics.class);
|
||||||
|
|
||||||
private final EppServiceHandler eppServiceHandler =
|
private final EppServiceHandler eppServiceHandler =
|
||||||
new EppServiceHandler(
|
new EppServiceHandler(RELAY_HOST, RELAY_PATH, () -> ID_TOKEN, HELLO.getBytes(UTF_8), metrics);
|
||||||
RELAY_HOST,
|
|
||||||
RELAY_PATH,
|
|
||||||
() -> mockCredentials,
|
|
||||||
Optional.of(IAP_CLIENT_ID),
|
|
||||||
HELLO.getBytes(UTF_8),
|
|
||||||
metrics);
|
|
||||||
|
|
||||||
private EmbeddedChannel channel;
|
private EmbeddedChannel channel;
|
||||||
|
|
||||||
private void setHandshakeSuccess(EmbeddedChannel channel, X509Certificate certificate) {
|
private static void setHandshakeSuccess(EmbeddedChannel channel, X509Certificate certificate) {
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
Promise<X509Certificate> unusedPromise =
|
Promise<X509Certificate> unusedPromise =
|
||||||
channel.attr(CLIENT_CERTIFICATE_PROMISE_KEY).get().setSuccess(certificate);
|
channel.attr(CLIENT_CERTIFICATE_PROMISE_KEY).get().setSuccess(certificate);
|
||||||
|
@ -99,7 +83,8 @@ class EppServiceHandlerTest {
|
||||||
setHandshakeSuccess(channel, clientCertificate);
|
setHandshakeSuccess(channel, clientCertificate);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setHandshakeFailure(EmbeddedChannel channel) {
|
private static void setHandshakeFailure(EmbeddedChannel channel) {
|
||||||
|
@SuppressWarnings("unused")
|
||||||
Promise<X509Certificate> unusedPromise =
|
Promise<X509Certificate> unusedPromise =
|
||||||
channel
|
channel
|
||||||
.attr(CLIENT_CERTIFICATE_PROMISE_KEY)
|
.attr(CLIENT_CERTIFICATE_PROMISE_KEY)
|
||||||
|
@ -116,25 +101,19 @@ class EppServiceHandlerTest {
|
||||||
content,
|
content,
|
||||||
RELAY_HOST,
|
RELAY_HOST,
|
||||||
RELAY_PATH,
|
RELAY_PATH,
|
||||||
mockCredentials,
|
ID_TOKEN,
|
||||||
getCertificateHash(clientCertificate),
|
getCertificateHash(clientCertificate),
|
||||||
CLIENT_ADDRESS,
|
CLIENT_ADDRESS,
|
||||||
Optional.of(IAP_CLIENT_ID),
|
|
||||||
cookies);
|
cookies);
|
||||||
}
|
}
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void beforeEach() throws Exception {
|
void beforeEach() throws Exception {
|
||||||
when(mockCredentials.getAccessToken()).thenReturn(new AccessToken("this.access.token", null));
|
|
||||||
IdToken mockIdToken = mock(IdToken.class);
|
|
||||||
when(mockIdToken.getTokenValue()).thenReturn("fake.test.id.token");
|
|
||||||
when(mockCredentials.idTokenWithAudience(IAP_CLIENT_ID, ImmutableList.of(Option.FORMAT_FULL)))
|
|
||||||
.thenReturn(mockIdToken);
|
|
||||||
clientCertificate = SelfSignedCaCertificate.create().cert();
|
clientCertificate = SelfSignedCaCertificate.create().cert();
|
||||||
channel = setUpNewChannel(eppServiceHandler);
|
channel = setUpNewChannel(eppServiceHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
private EmbeddedChannel setUpNewChannel(EppServiceHandler handler) {
|
private static EmbeddedChannel setUpNewChannel(EppServiceHandler handler) {
|
||||||
return new EmbeddedChannel(
|
return new EmbeddedChannel(
|
||||||
DefaultChannelId.newInstance(),
|
DefaultChannelId.newInstance(),
|
||||||
new ChannelInitializer<EmbeddedChannel>() {
|
new ChannelInitializer<EmbeddedChannel>() {
|
||||||
|
@ -165,12 +144,7 @@ class EppServiceHandlerTest {
|
||||||
// Set up the second channel.
|
// Set up the second channel.
|
||||||
EppServiceHandler eppServiceHandler2 =
|
EppServiceHandler eppServiceHandler2 =
|
||||||
new EppServiceHandler(
|
new EppServiceHandler(
|
||||||
RELAY_HOST,
|
RELAY_HOST, RELAY_PATH, () -> ID_TOKEN, HELLO.getBytes(UTF_8), metrics);
|
||||||
RELAY_PATH,
|
|
||||||
() -> mockCredentials,
|
|
||||||
Optional.empty(),
|
|
||||||
HELLO.getBytes(UTF_8),
|
|
||||||
metrics);
|
|
||||||
EmbeddedChannel channel2 = setUpNewChannel(eppServiceHandler2);
|
EmbeddedChannel channel2 = setUpNewChannel(eppServiceHandler2);
|
||||||
setHandshakeSuccess(channel2, clientCertificate);
|
setHandshakeSuccess(channel2, clientCertificate);
|
||||||
|
|
||||||
|
@ -190,12 +164,7 @@ class EppServiceHandlerTest {
|
||||||
// Set up the second channel.
|
// Set up the second channel.
|
||||||
EppServiceHandler eppServiceHandler2 =
|
EppServiceHandler eppServiceHandler2 =
|
||||||
new EppServiceHandler(
|
new EppServiceHandler(
|
||||||
RELAY_HOST,
|
RELAY_HOST, RELAY_PATH, () -> ID_TOKEN, HELLO.getBytes(UTF_8), metrics);
|
||||||
RELAY_PATH,
|
|
||||||
() -> mockCredentials,
|
|
||||||
Optional.empty(),
|
|
||||||
HELLO.getBytes(UTF_8),
|
|
||||||
metrics);
|
|
||||||
EmbeddedChannel channel2 = setUpNewChannel(eppServiceHandler2);
|
EmbeddedChannel channel2 = setUpNewChannel(eppServiceHandler2);
|
||||||
X509Certificate clientCertificate2 = SelfSignedCaCertificate.create().cert();
|
X509Certificate clientCertificate2 = SelfSignedCaCertificate.create().cert();
|
||||||
setHandshakeSuccess(channel2, clientCertificate2);
|
setHandshakeSuccess(channel2, clientCertificate2);
|
||||||
|
@ -358,38 +327,4 @@ class EppServiceHandlerTest {
|
||||||
assertThat((Object) channel.readOutbound()).isNull();
|
assertThat((Object) channel.readOutbound()).isNull();
|
||||||
assertThat(channel.isActive()).isTrue();
|
assertThat(channel.isActive()).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
void testSuccess_withoutIapClientId() throws Exception {
|
|
||||||
// Without an IAP client ID configured, we shouldn't include the proxy-authorization header
|
|
||||||
EppServiceHandler nonIapServiceHandler =
|
|
||||||
new EppServiceHandler(
|
|
||||||
RELAY_HOST,
|
|
||||||
RELAY_PATH,
|
|
||||||
() -> mockCredentials,
|
|
||||||
Optional.empty(),
|
|
||||||
HELLO.getBytes(UTF_8),
|
|
||||||
metrics);
|
|
||||||
channel = setUpNewChannel(nonIapServiceHandler);
|
|
||||||
|
|
||||||
setHandshakeSuccess();
|
|
||||||
// First inbound message is hello.
|
|
||||||
channel.readInbound();
|
|
||||||
String content = "<epp>stuff</epp>";
|
|
||||||
channel.writeInbound(Unpooled.wrappedBuffer(content.getBytes(UTF_8)));
|
|
||||||
FullHttpRequest request = channel.readInbound();
|
|
||||||
assertThat(request)
|
|
||||||
.isEqualTo(
|
|
||||||
TestUtils.makeEppHttpRequest(
|
|
||||||
content,
|
|
||||||
RELAY_HOST,
|
|
||||||
RELAY_PATH,
|
|
||||||
mockCredentials,
|
|
||||||
getCertificateHash(clientCertificate),
|
|
||||||
CLIENT_ADDRESS,
|
|
||||||
Optional.empty()));
|
|
||||||
// Nothing further to pass to the next handler.
|
|
||||||
assertThat((Object) channel.readInbound()).isNull();
|
|
||||||
assertThat(channel.isActive()).isTrue();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,14 +22,8 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
import com.google.auth.oauth2.AccessToken;
|
|
||||||
import com.google.auth.oauth2.ComputeEngineCredentials;
|
|
||||||
import com.google.auth.oauth2.IdToken;
|
|
||||||
import com.google.auth.oauth2.IdTokenProvider.Option;
|
|
||||||
import com.google.common.base.Throwables;
|
import com.google.common.base.Throwables;
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import google.registry.proxy.handler.HttpsRelayServiceHandler.NonOkHttpResponseException;
|
import google.registry.proxy.handler.HttpsRelayServiceHandler.NonOkHttpResponseException;
|
||||||
import google.registry.proxy.metric.FrontendMetrics;
|
import google.registry.proxy.metric.FrontendMetrics;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
|
@ -40,7 +34,6 @@ import io.netty.handler.codec.EncoderException;
|
||||||
import io.netty.handler.codec.http.FullHttpRequest;
|
import io.netty.handler.codec.http.FullHttpRequest;
|
||||||
import io.netty.handler.codec.http.FullHttpResponse;
|
import io.netty.handler.codec.http.FullHttpResponse;
|
||||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||||
import java.util.Optional;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
@ -52,24 +45,16 @@ class WhoisServiceHandlerTest {
|
||||||
private static final String QUERY_CONTENT = "test.tld";
|
private static final String QUERY_CONTENT = "test.tld";
|
||||||
private static final String PROTOCOL = "whois";
|
private static final String PROTOCOL = "whois";
|
||||||
private static final String CLIENT_HASH = "none";
|
private static final String CLIENT_HASH = "none";
|
||||||
private static final String IAP_CLIENT_ID = "iapClientId";
|
private static final String ID_TOKEN = "fake.id.token";
|
||||||
|
|
||||||
private static final ComputeEngineCredentials mockCredentials =
|
|
||||||
mock(ComputeEngineCredentials.class);
|
|
||||||
private final FrontendMetrics metrics = mock(FrontendMetrics.class);
|
private final FrontendMetrics metrics = mock(FrontendMetrics.class);
|
||||||
|
|
||||||
private final WhoisServiceHandler whoisServiceHandler =
|
private final WhoisServiceHandler whoisServiceHandler =
|
||||||
new WhoisServiceHandler(
|
new WhoisServiceHandler(RELAY_HOST, RELAY_PATH, () -> ID_TOKEN, metrics);
|
||||||
RELAY_HOST, RELAY_PATH, () -> mockCredentials, Optional.of(IAP_CLIENT_ID), metrics);
|
|
||||||
private EmbeddedChannel channel;
|
private EmbeddedChannel channel;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void beforeEach() throws Exception {
|
void beforeEach() throws Exception {
|
||||||
when(mockCredentials.getAccessToken()).thenReturn(new AccessToken("this.access.token", null));
|
|
||||||
IdToken mockIdToken = mock(IdToken.class);
|
|
||||||
when(mockIdToken.getTokenValue()).thenReturn("fake.test.id.token");
|
|
||||||
when(mockCredentials.idTokenWithAudience(IAP_CLIENT_ID, ImmutableList.of(Option.FORMAT_FULL)))
|
|
||||||
.thenReturn(mockIdToken);
|
|
||||||
// Need to reset metrics for each test method, since they are static fields on the class and
|
// Need to reset metrics for each test method, since they are static fields on the class and
|
||||||
// shared between each run.
|
// shared between each run.
|
||||||
channel = new EmbeddedChannel(whoisServiceHandler);
|
channel = new EmbeddedChannel(whoisServiceHandler);
|
||||||
|
@ -89,8 +74,7 @@ class WhoisServiceHandlerTest {
|
||||||
|
|
||||||
// Setup second channel.
|
// Setup second channel.
|
||||||
WhoisServiceHandler whoisServiceHandler2 =
|
WhoisServiceHandler whoisServiceHandler2 =
|
||||||
new WhoisServiceHandler(
|
new WhoisServiceHandler(RELAY_HOST, RELAY_PATH, () -> ID_TOKEN, metrics);
|
||||||
RELAY_HOST, RELAY_PATH, () -> mockCredentials, Optional.empty(), metrics);
|
|
||||||
EmbeddedChannel channel2 =
|
EmbeddedChannel channel2 =
|
||||||
// We need a new channel id so that it has a different hash code.
|
// We need a new channel id so that it has a different hash code.
|
||||||
// This only is needed for EmbeddedChannel because it has a dummy hash code implementation.
|
// This only is needed for EmbeddedChannel because it has a dummy hash code implementation.
|
||||||
|
@ -104,8 +88,7 @@ class WhoisServiceHandlerTest {
|
||||||
void testSuccess_fireInboundHttpRequest() throws Exception {
|
void testSuccess_fireInboundHttpRequest() throws Exception {
|
||||||
ByteBuf inputBuffer = Unpooled.wrappedBuffer(QUERY_CONTENT.getBytes(US_ASCII));
|
ByteBuf inputBuffer = Unpooled.wrappedBuffer(QUERY_CONTENT.getBytes(US_ASCII));
|
||||||
FullHttpRequest expectedRequest =
|
FullHttpRequest expectedRequest =
|
||||||
makeWhoisHttpRequest(
|
makeWhoisHttpRequest(QUERY_CONTENT, RELAY_HOST, RELAY_PATH, ID_TOKEN);
|
||||||
QUERY_CONTENT, RELAY_HOST, RELAY_PATH, mockCredentials, Optional.of(IAP_CLIENT_ID));
|
|
||||||
// Input data passed to next handler
|
// Input data passed to next handler
|
||||||
assertThat(channel.writeInbound(inputBuffer)).isTrue();
|
assertThat(channel.writeInbound(inputBuffer)).isTrue();
|
||||||
FullHttpRequest inputRequest = channel.readInbound();
|
FullHttpRequest inputRequest = channel.readInbound();
|
||||||
|
@ -128,27 +111,6 @@ class WhoisServiceHandlerTest {
|
||||||
assertThat(channel.isActive()).isFalse();
|
assertThat(channel.isActive()).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
void testSuccess_withoutIapClientId() throws Exception {
|
|
||||||
// Without an IAP client ID configured, we shouldn't include the proxy-authorization header
|
|
||||||
WhoisServiceHandler nonIapHandler =
|
|
||||||
new WhoisServiceHandler(
|
|
||||||
RELAY_HOST, RELAY_PATH, () -> mockCredentials, Optional.empty(), metrics);
|
|
||||||
channel = new EmbeddedChannel(nonIapHandler);
|
|
||||||
|
|
||||||
ByteBuf inputBuffer = Unpooled.wrappedBuffer(QUERY_CONTENT.getBytes(US_ASCII));
|
|
||||||
FullHttpRequest expectedRequest =
|
|
||||||
makeWhoisHttpRequest(
|
|
||||||
QUERY_CONTENT, RELAY_HOST, RELAY_PATH, mockCredentials, Optional.empty());
|
|
||||||
// Input data passed to next handler
|
|
||||||
assertThat(channel.writeInbound(inputBuffer)).isTrue();
|
|
||||||
FullHttpRequest inputRequest = channel.readInbound();
|
|
||||||
assertThat(inputRequest).isEqualTo(expectedRequest);
|
|
||||||
// The channel is still open, and nothing else is to be read from it.
|
|
||||||
assertThat((Object) channel.readInbound()).isNull();
|
|
||||||
assertThat(channel.isActive()).isTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testFailure_OutboundHttpResponseNotOK() {
|
void testFailure_OutboundHttpResponseNotOK() {
|
||||||
String outputString = "line1\r\nline2\r\n";
|
String outputString = "line1\r\nline2\r\n";
|
||||||
|
|
|
@ -23,8 +23,9 @@
|
||||||
|
|
||||||
FROM golang:1.19 as deployCloudSchedulerAndQueueBuilder
|
FROM golang:1.19 as deployCloudSchedulerAndQueueBuilder
|
||||||
WORKDIR /usr/src/deployCloudSchedulerAndQueue
|
WORKDIR /usr/src/deployCloudSchedulerAndQueue
|
||||||
RUN go mod init deployCloudSchedulerAndQueue
|
COPY deployCloudSchedulerAndQueue.go ./
|
||||||
COPY *.go ./
|
COPY go.sum ./
|
||||||
|
COPY go.mod ./
|
||||||
RUN go build -o /deployCloudSchedulerAndQueue
|
RUN go build -o /deployCloudSchedulerAndQueue
|
||||||
|
|
||||||
FROM marketplace.gcr.io/google/debian10
|
FROM marketplace.gcr.io/google/debian10
|
||||||
|
|
|
@ -26,10 +26,14 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var projectName string
|
var projectName string
|
||||||
|
|
||||||
|
var clientId string
|
||||||
|
|
||||||
const GcpLocation = "us-central1"
|
const GcpLocation = "us-central1"
|
||||||
|
|
||||||
type SyncManager[T Task | Queue] interface {
|
type SyncManager[T Task | Queue] interface {
|
||||||
|
@ -74,6 +78,12 @@ type TasksSyncManager struct {
|
||||||
ServiceAccountEmail string
|
ServiceAccountEmail string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type YamlEntries struct {
|
||||||
|
Auth struct {
|
||||||
|
OauthClientId string `yaml:"oauthClientId"`
|
||||||
|
} `yaml:"auth"`
|
||||||
|
}
|
||||||
|
|
||||||
type XmlEntries struct {
|
type XmlEntries struct {
|
||||||
XMLName xml.Name `xml:"entries"`
|
XMLName xml.Name `xml:"entries"`
|
||||||
Tasks []Task `xml:"task"`
|
Tasks []Task `xml:"task"`
|
||||||
|
@ -180,7 +190,7 @@ func (manager TasksSyncManager) getArgs(task Task, operationType string) []strin
|
||||||
"--description", description,
|
"--description", description,
|
||||||
"--http-method", "get",
|
"--http-method", "get",
|
||||||
"--oidc-service-account-email", getCloudSchedulerServiceAccountEmail(),
|
"--oidc-service-account-email", getCloudSchedulerServiceAccountEmail(),
|
||||||
"--oidc-token-audience", projectName,
|
"--oidc-token-audience", clientId,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -301,21 +311,37 @@ func getExistingEntries(cmd *exec.Cmd) ExistingEntries {
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if len(os.Args) < 3 || os.Args[1] == "" || os.Args[2] == "" {
|
if len(os.Args) < 4 || os.Args[1] == "" || os.Args[2] == "" || os.Args[3] == "" {
|
||||||
panic("Error - Invalid Parameters.\nRequired params: 1 - config file path; 2 - project name;")
|
panic("Error - Invalid Parameters.\n" +
|
||||||
|
"Required params: 1 - Nomulu config YAML path; 2 - config XML path; 3 - project name;")
|
||||||
}
|
}
|
||||||
// Config file path
|
// Nomulus YAML config file path, used to extract OAuth client ID.
|
||||||
configFileLocation := os.Args[1]
|
nomulusConfigFileLocation := os.Args[1]
|
||||||
|
// XML config file path
|
||||||
|
configFileLocation := os.Args[2]
|
||||||
// Project name where to submit the tasks
|
// Project name where to submit the tasks
|
||||||
projectName = os.Args[2]
|
projectName = os.Args[3]
|
||||||
|
|
||||||
log.Default().Println("Filepath " + configFileLocation)
|
log.Default().Println("YAML Filepath " + nomulusConfigFileLocation)
|
||||||
|
yamlFile, err := os.Open(nomulusConfigFileLocation)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer yamlFile.Close()
|
||||||
|
byteValue, _ := io.ReadAll(yamlFile)
|
||||||
|
var yamlEntries YamlEntries
|
||||||
|
if err := yaml.Unmarshal(byteValue, &yamlEntries); err != nil {
|
||||||
|
panic("Failed to parse YAML file entries: " + err.Error())
|
||||||
|
}
|
||||||
|
clientId = yamlEntries.Auth.OauthClientId
|
||||||
|
|
||||||
|
log.Default().Println("XML Filepath " + configFileLocation)
|
||||||
xmlFile, err := os.Open(configFileLocation)
|
xmlFile, err := os.Open(configFileLocation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
defer xmlFile.Close()
|
defer xmlFile.Close()
|
||||||
byteValue, _ := io.ReadAll(xmlFile)
|
byteValue, _ = io.ReadAll(xmlFile)
|
||||||
var xmlEntries XmlEntries
|
var xmlEntries XmlEntries
|
||||||
if err := xml.Unmarshal(byteValue, &xmlEntries); err != nil {
|
if err := xml.Unmarshal(byteValue, &xmlEntries); err != nil {
|
||||||
panic("Failed to parse xml file entries: " + err.Error())
|
panic("Failed to parse xml file entries: " + err.Error())
|
||||||
|
|
5
release/builder/go.mod
Normal file
5
release/builder/go.mod
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
module nomulus/release/builder
|
||||||
|
|
||||||
|
go 1.21
|
||||||
|
|
||||||
|
require gopkg.in/yaml.v3 v3.0.1
|
4
release/builder/go.sum
Normal file
4
release/builder/go.sum
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
@ -43,8 +43,9 @@ steps:
|
||||||
fi
|
fi
|
||||||
gsutil cp gs://$PROJECT_ID-deploy/${TAG_NAME}/${_ENV}.tar .
|
gsutil cp gs://$PROJECT_ID-deploy/${TAG_NAME}/${_ENV}.tar .
|
||||||
tar -xvf ${_ENV}.tar
|
tar -xvf ${_ENV}.tar
|
||||||
deployCloudSchedulerAndQueue default/WEB-INF/cloud-scheduler-tasks.xml $project_id
|
unzip default/WEB-INF/lib/core.jar
|
||||||
deployCloudSchedulerAndQueue default/WEB-INF/cloud-tasks-queue.xml $project_id
|
deployCloudSchedulerAndQueue google/registry/config/files/nomulus-config-${_ENV}.yaml default/WEB-INF/cloud-scheduler-tasks.xml $project_id
|
||||||
|
deployCloudSchedulerAndQueue google/registry/config/files/nomulus-config-${_ENV}.yaml default/WEB-INF/cloud-tasks-queue.xml $project_id
|
||||||
# Deploy the GAE config files.
|
# Deploy the GAE config files.
|
||||||
# First authorize the gcloud tool to use the credential json file, then
|
# First authorize the gcloud tool to use the credential json file, then
|
||||||
# download and unzip the tarball that contains the relevant config files
|
# download and unzip the tarball that contains the relevant config files
|
||||||
|
|
|
@ -20,6 +20,7 @@ import com.google.api.client.googleapis.util.Utils;
|
||||||
import com.google.api.client.http.HttpRequestInitializer;
|
import com.google.api.client.http.HttpRequestInitializer;
|
||||||
import com.google.api.client.http.HttpTransport;
|
import com.google.api.client.http.HttpTransport;
|
||||||
import com.google.api.client.json.JsonFactory;
|
import com.google.api.client.json.JsonFactory;
|
||||||
|
import com.google.auth.ServiceAccountSigner;
|
||||||
import com.google.auth.http.HttpCredentialsAdapter;
|
import com.google.auth.http.HttpCredentialsAdapter;
|
||||||
import com.google.auth.oauth2.GoogleCredentials;
|
import com.google.auth.oauth2.GoogleCredentials;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
@ -31,12 +32,13 @@ import java.io.Serializable;
|
||||||
*/
|
*/
|
||||||
public class GoogleCredentialsBundle implements Serializable {
|
public class GoogleCredentialsBundle implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = -7184513645423688942L;
|
||||||
private static final HttpTransport HTTP_TRANSPORT = Utils.getDefaultTransport();
|
private static final HttpTransport HTTP_TRANSPORT = Utils.getDefaultTransport();
|
||||||
private static final JsonFactory JSON_FACTORY = Utils.getDefaultJsonFactory();
|
private static final JsonFactory JSON_FACTORY = Utils.getDefaultJsonFactory();
|
||||||
|
|
||||||
private GoogleCredentials googleCredentials;
|
private final GoogleCredentials googleCredentials;
|
||||||
|
|
||||||
private GoogleCredentialsBundle(GoogleCredentials googleCredentials) {
|
protected GoogleCredentialsBundle(GoogleCredentials googleCredentials) {
|
||||||
checkNotNull(googleCredentials);
|
checkNotNull(googleCredentials);
|
||||||
this.googleCredentials = googleCredentials;
|
this.googleCredentials = googleCredentials;
|
||||||
}
|
}
|
||||||
|
@ -46,6 +48,21 @@ public class GoogleCredentialsBundle implements Serializable {
|
||||||
return new GoogleCredentialsBundle(credentials);
|
return new GoogleCredentialsBundle(credentials);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the service account email address of the underlying {@link} GoogleCredentials, if
|
||||||
|
* possible.
|
||||||
|
*/
|
||||||
|
public String serviceAccount() {
|
||||||
|
if (googleCredentials instanceof ServiceAccountSigner) {
|
||||||
|
return ((ServiceAccountSigner) googleCredentials).getAccount();
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException(
|
||||||
|
String.format(
|
||||||
|
"%s is a %s, not a service account.",
|
||||||
|
googleCredentials, googleCredentials.getClass().getSimpleName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Returns the same {@link GoogleCredentials} used to create this object. */
|
/** Returns the same {@link GoogleCredentials} used to create this object. */
|
||||||
public GoogleCredentials getGoogleCredentials() {
|
public GoogleCredentials getGoogleCredentials() {
|
||||||
return googleCredentials;
|
return googleCredentials;
|
||||||
|
|
98
util/src/main/java/google/registry/util/OidcTokenUtils.java
Normal file
98
util/src/main/java/google/registry/util/OidcTokenUtils.java
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
// Copyright 2023 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.util;
|
||||||
|
|
||||||
|
import static com.google.api.client.googleapis.auth.oauth2.GoogleOAuthConstants.TOKEN_SERVER_URL;
|
||||||
|
|
||||||
|
import com.google.api.client.http.GenericUrl;
|
||||||
|
import com.google.api.client.http.HttpRequest;
|
||||||
|
import com.google.api.client.http.HttpRequestFactory;
|
||||||
|
import com.google.api.client.http.HttpResponse;
|
||||||
|
import com.google.api.client.http.UrlEncodedContent;
|
||||||
|
import com.google.api.client.util.GenericData;
|
||||||
|
import com.google.auth.oauth2.GoogleCredentials;
|
||||||
|
import com.google.auth.oauth2.IdToken;
|
||||||
|
import com.google.auth.oauth2.IdTokenProvider;
|
||||||
|
import com.google.auth.oauth2.IdTokenProvider.Option;
|
||||||
|
import com.google.auth.oauth2.UserCredentials;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.flogger.FluentLogger;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
|
||||||
|
public final class OidcTokenUtils {
|
||||||
|
|
||||||
|
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||||
|
|
||||||
|
private OidcTokenUtils() {}
|
||||||
|
|
||||||
|
public static String createOidcToken(GoogleCredentialsBundle credentialsBundle, String clientId) {
|
||||||
|
GoogleCredentials credentials = credentialsBundle.getGoogleCredentials();
|
||||||
|
if (credentials instanceof UserCredentials) {
|
||||||
|
try {
|
||||||
|
return getIdTokenForUserCredential(credentialsBundle, clientId);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.atSevere().withCause(e).log(
|
||||||
|
"Cannot generate OIDC token for credential %s", credentials);
|
||||||
|
throw new RuntimeException("Cannot create OIDC token", e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
IdTokenProvider idTokenProvider = (IdTokenProvider) credentials;
|
||||||
|
// Note: we use Option.FORMAT_FULL to make sure the JWT we receive contains the email
|
||||||
|
// address (as is required by IAP)
|
||||||
|
try {
|
||||||
|
IdToken idToken =
|
||||||
|
idTokenProvider.idTokenWithAudience(clientId, ImmutableList.of(Option.FORMAT_FULL));
|
||||||
|
return idToken.getTokenValue();
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.atSevere().withCause(e).log(
|
||||||
|
"Cannot generate OIDC token for credential %s", credentials);
|
||||||
|
throw new RuntimeException("Cannot create OIDC token", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses the saved desktop-app refresh token to acquire a token with the given audience.
|
||||||
|
*
|
||||||
|
* <p>This is lifted mostly from the Google Auth Library's {@link UserCredentials}
|
||||||
|
* "doRefreshAccessToken" method (which is private and thus inaccessible) while adding in the
|
||||||
|
* audience of the IAP client ID. The "idTokenWithAudience" method of that class does not support
|
||||||
|
* setting custom audience, paradoxically.
|
||||||
|
*
|
||||||
|
* @see <a
|
||||||
|
* href="https://cloud.google.com/iap/docs/authentication-howto#authenticating_from_a_desktop_app">
|
||||||
|
* Authenticating from a desktop app</a>
|
||||||
|
*/
|
||||||
|
private static String getIdTokenForUserCredential(
|
||||||
|
GoogleCredentialsBundle credentialsBundle, String audience)
|
||||||
|
throws GeneralSecurityException, IOException {
|
||||||
|
UserCredentials credentials = (UserCredentials) credentialsBundle.getGoogleCredentials();
|
||||||
|
GenericData tokenRequest = new GenericData();
|
||||||
|
tokenRequest.set("client_id", credentials.getClientId());
|
||||||
|
tokenRequest.set("client_secret", credentials.getClientSecret());
|
||||||
|
tokenRequest.set("refresh_token", credentials.getRefreshToken());
|
||||||
|
tokenRequest.set("audience", audience);
|
||||||
|
tokenRequest.set("grant_type", "refresh_token");
|
||||||
|
UrlEncodedContent content = new UrlEncodedContent(tokenRequest);
|
||||||
|
|
||||||
|
HttpRequestFactory requestFactory = credentialsBundle.getHttpTransport().createRequestFactory();
|
||||||
|
HttpRequest request =
|
||||||
|
requestFactory.buildPostRequest(new GenericUrl(URI.create(TOKEN_SERVER_URL)), content);
|
||||||
|
request.setParser(credentialsBundle.getJsonFactory().createJsonObjectParser());
|
||||||
|
HttpResponse response = request.execute();
|
||||||
|
return response.parseAs(GenericData.class).get("id_token").toString();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue