mirror of
https://github.com/google/nomulus
synced 2026-05-19 14:21:48 +00:00
Compare commits
17 Commits
nomulus-20
...
nomulus-20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d98d65eee5 | ||
|
|
28e72bd0d0 | ||
|
|
0777be3d6c | ||
|
|
f9cd167ae4 | ||
|
|
eed1886121 | ||
|
|
7149fd3307 | ||
|
|
0dc7ab99d7 | ||
|
|
76d4dfbb04 | ||
|
|
8547ad7941 | ||
|
|
b1266c95e8 | ||
|
|
bc9aab6790 | ||
|
|
6cb669a5a7 | ||
|
|
0f92e98028 | ||
|
|
5f0526c07a | ||
|
|
759aaddb5f | ||
|
|
816180f3b3 | ||
|
|
bf66b374c6 |
@@ -84,10 +84,10 @@ tasks.build.dependsOn(tasks.checkLicense)
|
||||
// Paths to main and test sources.
|
||||
ext.projectRootDir = "${rootDir}"
|
||||
|
||||
// Tasks to deploy/stage all App Engine services
|
||||
// Tasks to deploy/stage all services
|
||||
task deploy {
|
||||
group = 'deployment'
|
||||
description = 'Deploys all services to App Engine.'
|
||||
description = 'Deploys all services.'
|
||||
}
|
||||
|
||||
task stage {
|
||||
|
||||
@@ -33,8 +33,8 @@ public abstract class DateTimeUtils {
|
||||
/**
|
||||
* A date in the far future that we can treat as infinity.
|
||||
*
|
||||
* <p>This value is (2^63-1)/1000 rounded down. AppEngine stores dates as 64 bit microseconds, but
|
||||
* Java uses milliseconds, so this is the largest representable date that will survive a
|
||||
* <p>This value is (2^63-1)/1000 rounded down. Postgres can store dates as 64 bit microseconds,
|
||||
* but Java uses milliseconds, so this is the largest representable date that will survive a
|
||||
* round-trip through the database.
|
||||
*/
|
||||
public static final DateTime END_OF_TIME = new DateTime(Long.MAX_VALUE / 1000, DateTimeZone.UTC);
|
||||
|
||||
@@ -104,7 +104,7 @@ PROPERTIES = [
|
||||
Property('testFilter',
|
||||
'Comma separated list of test patterns, if specified run only '
|
||||
'these.'),
|
||||
Property('environment', 'GAE Environment for deployment and staging.'),
|
||||
Property('environment', 'Environment for deployment and staging.'),
|
||||
|
||||
# Cloud SQL properties
|
||||
Property('dbServer',
|
||||
|
||||
@@ -9,7 +9,7 @@ expected to change.
|
||||
|
||||
## Deployment
|
||||
|
||||
Webapp is deployed with the nomulus default service war to Google App Engine.
|
||||
The webapp is deployed with the nomulus default service war to GKE.
|
||||
During nomulus default service war build task, gradle script triggers the
|
||||
following:
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ configurations {
|
||||
// for details.
|
||||
nomulus_test
|
||||
|
||||
// Exclude non-canonical servlet-api jars. Our AppEngine deployment uses
|
||||
// Exclude non-canonical servlet-api jars. Our deployment uses
|
||||
// javax.servlet:servlet-api:2.5
|
||||
// For reasons we do not understand, marking the following dependencies as
|
||||
// compileOnly instead of compile does not exclude them from runtimeClasspath.
|
||||
|
||||
@@ -28,13 +28,20 @@ import static google.registry.request.RequestParameters.extractRequiredDatetimeP
|
||||
import static google.registry.request.RequestParameters.extractRequiredParameter;
|
||||
import static google.registry.request.RequestParameters.extractSetOfDatetimeParameters;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.util.concurrent.RateLimiter;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import google.registry.request.HttpException.BadRequestException;
|
||||
import google.registry.request.OptionalJsonPayload;
|
||||
import google.registry.request.Parameter;
|
||||
import jakarta.inject.Named;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
@@ -44,6 +51,8 @@ public class BatchModule {
|
||||
|
||||
public static final String PARAM_FAST = "fast";
|
||||
|
||||
static final int DEFAULT_MAX_QPS = 10;
|
||||
|
||||
@Provides
|
||||
@Parameter("url")
|
||||
static String provideUrl(HttpServletRequest req) {
|
||||
@@ -140,8 +149,6 @@ public class BatchModule {
|
||||
return extractBooleanParameter(req, PARAM_FAST);
|
||||
}
|
||||
|
||||
private static final int DEFAULT_MAX_QPS = 10;
|
||||
|
||||
@Provides
|
||||
@Parameter("maxQps")
|
||||
static int provideMaxQps(HttpServletRequest req) {
|
||||
@@ -149,8 +156,42 @@ public class BatchModule {
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Named("removeAllDomainContacts")
|
||||
static RateLimiter provideRemoveAllDomainContactsRateLimiter(@Parameter("maxQps") int maxQps) {
|
||||
@Named("standardRateLimiter")
|
||||
static RateLimiter provideStandardRateLimiter(@Parameter("maxQps") int maxQps) {
|
||||
return RateLimiter.create(maxQps);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Parameter("gainingRegistrarId")
|
||||
static String provideGainingRegistrarId(HttpServletRequest req) {
|
||||
return extractRequiredParameter(req, "gainingRegistrarId");
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Parameter("losingRegistrarId")
|
||||
static String provideLosingRegistrarId(HttpServletRequest req) {
|
||||
return extractRequiredParameter(req, "losingRegistrarId");
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Parameter("bulkTransferDomainNames")
|
||||
static ImmutableList<String> provideBulkTransferDomainNames(
|
||||
Gson gson, @OptionalJsonPayload Optional<JsonElement> optionalJsonElement) {
|
||||
return optionalJsonElement
|
||||
.map(je -> ImmutableList.copyOf(gson.fromJson(je, new TypeToken<List<String>>() {})))
|
||||
.orElseThrow(
|
||||
() -> new BadRequestException("Missing POST body of bulk transfer domain names"));
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Parameter("requestedByRegistrar")
|
||||
static boolean provideRequestedByRegistrar(HttpServletRequest req) {
|
||||
return extractBooleanParameter(req, "requestedByRegistrar");
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Parameter("reason")
|
||||
static String provideReason(HttpServletRequest req) {
|
||||
return extractRequiredParameter(req, "reason");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
// Copyright 2025 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.batch;
|
||||
|
||||
import static com.google.common.net.MediaType.PLAIN_TEXT_UTF_8;
|
||||
import static google.registry.flows.FlowUtils.marshalWithLenientRetry;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_NO_CONTENT;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
|
||||
import static java.nio.charset.StandardCharsets.US_ASCII;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.util.concurrent.RateLimiter;
|
||||
import google.registry.flows.EppController;
|
||||
import google.registry.flows.EppRequestSource;
|
||||
import google.registry.flows.PasswordOnlyTransportCredentials;
|
||||
import google.registry.flows.StatelessRequestSessionMetadata;
|
||||
import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.eppcommon.ProtocolDefinition;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.eppoutput.EppOutput;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Parameter;
|
||||
import google.registry.request.Response;
|
||||
import google.registry.request.auth.Auth;
|
||||
import google.registry.request.lock.LockHandler;
|
||||
import google.registry.util.DateTimeUtils;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.logging.Level;
|
||||
import org.joda.time.Duration;
|
||||
|
||||
/**
|
||||
* An action that transfers a set of domains from one registrar to another.
|
||||
*
|
||||
* <p>This should be used as part of the BTAPPA (Bulk Transfer After a Partial Portfolio
|
||||
* Acquisition) process in order to transfer a (possibly large) list of domains from one registrar
|
||||
* to another, though it may be used in other situations as well.
|
||||
*
|
||||
* <p>This runs as a single-threaded idempotent action that runs a superuser domain transfer on each
|
||||
* domain to process. We go through the standard EPP process to make sure that we have an accurate
|
||||
* historical representation of events (rather than force-modifying the domains in place).
|
||||
*
|
||||
* <p>The body of the HTTP post request should be a JSON list of the domains to be transferred.
|
||||
* Because the list of domains to process can be quite large, this action should be called by a tool
|
||||
* that batches the list of domains into reasonable sizes if necessary.
|
||||
*
|
||||
* <p>Consider passing in an "maxQps" parameter based on the number of domains being transferred,
|
||||
* otherwise the default is {@link BatchModule#DEFAULT_MAX_QPS}.
|
||||
*/
|
||||
@Action(
|
||||
service = Action.Service.BACKEND,
|
||||
path = BulkDomainTransferAction.PATH,
|
||||
method = Action.Method.POST,
|
||||
auth = Auth.AUTH_ADMIN)
|
||||
public class BulkDomainTransferAction implements Runnable {
|
||||
|
||||
public static final String PATH = "/_dr/task/bulkDomainTransfer";
|
||||
|
||||
private static final String SUPERUSER_TRANSFER_XML_FORMAT =
|
||||
"""
|
||||
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
|
||||
<command>
|
||||
<transfer op="request">
|
||||
<domain:transfer xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
|
||||
<domain:name>%DOMAIN_NAME%</domain:name>
|
||||
</domain:transfer>
|
||||
</transfer>
|
||||
<extension>
|
||||
<superuser:domainTransferRequest xmlns:superuser="urn:google:params:xml:ns:superuser-1.0">
|
||||
<superuser:renewalPeriod unit="y">0</superuser:renewalPeriod>
|
||||
<superuser:automaticTransferLength>0</superuser:automaticTransferLength>
|
||||
</superuser:domainTransferRequest>
|
||||
<metadata:metadata xmlns:metadata="urn:google:params:xml:ns:metadata-1.0">
|
||||
<metadata:reason>%REASON%</metadata:reason>
|
||||
<metadata:requestedByRegistrar>%REQUESTED_BY_REGISTRAR%</metadata:requestedByRegistrar>
|
||||
</metadata:metadata>
|
||||
</extension>
|
||||
<clTRID>BulkDomainTransferAction</clTRID>
|
||||
</command>
|
||||
</epp>
|
||||
""";
|
||||
|
||||
private static final String LOCK_NAME = "Domain bulk transfer";
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
private final EppController eppController;
|
||||
private final LockHandler lockHandler;
|
||||
private final RateLimiter rateLimiter;
|
||||
private final ImmutableList<String> bulkTransferDomainNames;
|
||||
private final String gainingRegistrarId;
|
||||
private final String losingRegistrarId;
|
||||
private final boolean requestedByRegistrar;
|
||||
private final String reason;
|
||||
private final Response response;
|
||||
|
||||
private int successes = 0;
|
||||
private int alreadyTransferred = 0;
|
||||
private int pendingDelete = 0;
|
||||
private int missingDomains = 0;
|
||||
private int errors = 0;
|
||||
|
||||
@Inject
|
||||
BulkDomainTransferAction(
|
||||
EppController eppController,
|
||||
LockHandler lockHandler,
|
||||
@Named("standardRateLimiter") RateLimiter rateLimiter,
|
||||
@Parameter("bulkTransferDomainNames") ImmutableList<String> bulkTransferDomainNames,
|
||||
@Parameter("gainingRegistrarId") String gainingRegistrarId,
|
||||
@Parameter("losingRegistrarId") String losingRegistrarId,
|
||||
@Parameter("requestedByRegistrar") boolean requestedByRegistrar,
|
||||
@Parameter("reason") String reason,
|
||||
Response response) {
|
||||
this.eppController = eppController;
|
||||
this.lockHandler = lockHandler;
|
||||
this.rateLimiter = rateLimiter;
|
||||
this.bulkTransferDomainNames = bulkTransferDomainNames;
|
||||
this.gainingRegistrarId = gainingRegistrarId;
|
||||
this.losingRegistrarId = losingRegistrarId;
|
||||
this.requestedByRegistrar = requestedByRegistrar;
|
||||
this.reason = reason;
|
||||
this.response = response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
response.setContentType(PLAIN_TEXT_UTF_8);
|
||||
Callable<Void> runner =
|
||||
() -> {
|
||||
try {
|
||||
runLocked();
|
||||
response.setStatus(SC_OK);
|
||||
} catch (Exception e) {
|
||||
logger.atSevere().withCause(e).log("Errored out during execution.");
|
||||
response.setStatus(SC_INTERNAL_SERVER_ERROR);
|
||||
response.setPayload(String.format("Errored out with cause: %s", e));
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
if (!lockHandler.executeWithLocks(runner, null, Duration.standardHours(1), LOCK_NAME)) {
|
||||
// Send a 200-series status code to prevent this conflicting action from retrying.
|
||||
response.setStatus(SC_NO_CONTENT);
|
||||
response.setPayload("Could not acquire lock; already running?");
|
||||
}
|
||||
}
|
||||
|
||||
private void runLocked() {
|
||||
logger.atInfo().log("Attempting to transfer %d domains.", bulkTransferDomainNames.size());
|
||||
for (String domainName : bulkTransferDomainNames) {
|
||||
rateLimiter.acquire();
|
||||
tm().transact(() -> runTransferFlowInTransaction(domainName));
|
||||
}
|
||||
|
||||
String msg =
|
||||
String.format(
|
||||
"Finished; %d domains were successfully transferred, %d were previously transferred, %s"
|
||||
+ " were missing domains, %s are pending delete, and %d errored out.",
|
||||
successes, alreadyTransferred, missingDomains, pendingDelete, errors);
|
||||
logger.at(errors + missingDomains == 0 ? Level.INFO : Level.WARNING).log(msg);
|
||||
response.setPayload(msg);
|
||||
}
|
||||
|
||||
private void runTransferFlowInTransaction(String domainName) {
|
||||
if (shouldSkipDomain(domainName)) {
|
||||
return;
|
||||
}
|
||||
String xml =
|
||||
SUPERUSER_TRANSFER_XML_FORMAT
|
||||
.replace("%DOMAIN_NAME%", domainName)
|
||||
.replace("%REASON%", reason)
|
||||
.replace("%REQUESTED_BY_REGISTRAR%", String.valueOf(requestedByRegistrar));
|
||||
EppOutput output =
|
||||
eppController.handleEppCommand(
|
||||
new StatelessRequestSessionMetadata(
|
||||
gainingRegistrarId, ProtocolDefinition.getVisibleServiceExtensionUris()),
|
||||
new PasswordOnlyTransportCredentials(),
|
||||
EppRequestSource.TOOL,
|
||||
false,
|
||||
true,
|
||||
xml.getBytes(US_ASCII));
|
||||
if (output.isSuccess()) {
|
||||
logger.atInfo().log("Successfully transferred domain '%s'.", domainName);
|
||||
successes++;
|
||||
} else {
|
||||
logger.atWarning().log(
|
||||
"Failed transferring domain '%s' with error '%s'.",
|
||||
domainName, new String(marshalWithLenientRetry(output), US_ASCII));
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldSkipDomain(String domainName) {
|
||||
Optional<Domain> maybeDomain =
|
||||
ForeignKeyUtils.loadResource(Domain.class, domainName, tm().getTransactionTime());
|
||||
if (maybeDomain.isEmpty()) {
|
||||
logger.atWarning().log("Domain '%s' was already deleted", domainName);
|
||||
missingDomains++;
|
||||
return true;
|
||||
}
|
||||
Domain domain = maybeDomain.get();
|
||||
String currentRegistrarId = domain.getCurrentSponsorRegistrarId();
|
||||
if (currentRegistrarId.equals(gainingRegistrarId)) {
|
||||
logger.atInfo().log("Domain '%s' was already transferred", domainName);
|
||||
alreadyTransferred++;
|
||||
return true;
|
||||
}
|
||||
if (!currentRegistrarId.equals(losingRegistrarId)) {
|
||||
logger.atWarning().log(
|
||||
"Domain '%s' had unexpected registrar '%s'", domainName, currentRegistrarId);
|
||||
errors++;
|
||||
return true;
|
||||
}
|
||||
if (domain.getStatusValues().contains(StatusValue.PENDING_DELETE)
|
||||
|| !domain.getDeletionTime().equals(DateTimeUtils.END_OF_TIME)) {
|
||||
logger.atWarning().log("Domain '%s' is in PENDING_DELETE", domainName);
|
||||
pendingDelete++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,6 @@ import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.Parameter;
|
||||
import google.registry.request.Response;
|
||||
import google.registry.request.UrlConnectionService;
|
||||
@@ -43,7 +42,7 @@ import javax.net.ssl.HttpsURLConnection;
|
||||
* --service BACKEND -X POST -u '/_dr/task/executeCannedScript}'}
|
||||
*/
|
||||
@Action(
|
||||
service = GaeService.BACKEND,
|
||||
service = Action.Service.BACKEND,
|
||||
path = "/_dr/task/executeCannedScript",
|
||||
method = {POST, GET},
|
||||
automaticallyPrintOk = true,
|
||||
|
||||
@@ -27,7 +27,6 @@ import google.registry.model.domain.token.AllocationToken;
|
||||
import google.registry.model.domain.token.BulkPricingPackage;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.auth.Auth;
|
||||
import google.registry.ui.server.SendEmailUtils;
|
||||
import google.registry.util.Clock;
|
||||
@@ -39,7 +38,10 @@ import org.joda.time.Days;
|
||||
* An action that checks all {@link BulkPricingPackage} objects for compliance with their max create
|
||||
* limit.
|
||||
*/
|
||||
@Action(service = GaeService.BACKEND, path = CheckBulkComplianceAction.PATH, auth = Auth.AUTH_ADMIN)
|
||||
@Action(
|
||||
service = Action.Service.BACKEND,
|
||||
path = CheckBulkComplianceAction.PATH,
|
||||
auth = Auth.AUTH_ADMIN)
|
||||
public class CheckBulkComplianceAction implements Runnable {
|
||||
|
||||
public static final String PATH = "/_dr/task/checkBulkCompliance";
|
||||
|
||||
@@ -43,7 +43,6 @@ import google.registry.config.CredentialModule.ApplicationDefaultCredential;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.Method;
|
||||
import google.registry.request.Action.Service;
|
||||
import google.registry.util.Clock;
|
||||
import google.registry.util.CollectionUtils;
|
||||
import google.registry.util.GoogleCredentialsBundle;
|
||||
@@ -56,8 +55,6 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
import org.joda.time.Duration;
|
||||
|
||||
@@ -119,19 +116,13 @@ public class CloudTasksUtils implements Serializable {
|
||||
* <p>For GET requests we add them on to the URL, and for POST requests we add them in the body of
|
||||
* the request.
|
||||
*
|
||||
* <p>The parameters {@code putHeadersFunction} and {@code setBodyFunction} are used so that this
|
||||
* method can be called with either an AppEngine HTTP request or a standard non-AppEngine HTTP
|
||||
* request. The two objects do not have the same methods, but both have ways of setting headers /
|
||||
* body.
|
||||
*
|
||||
* @return the resulting path (unchanged for POST requests, with params added for GET requests)
|
||||
*/
|
||||
private static String processRequestParameters(
|
||||
String path,
|
||||
Method method,
|
||||
Multimap<String, String> params,
|
||||
BiConsumer<String, String> putHeadersFunction,
|
||||
Consumer<ByteString> setBodyFunction) {
|
||||
HttpRequest.Builder requestBuilder) {
|
||||
if (CollectionUtils.isNullOrEmpty(params)) {
|
||||
return path;
|
||||
}
|
||||
@@ -149,8 +140,8 @@ public class CloudTasksUtils implements Serializable {
|
||||
if (method.equals(Method.GET)) {
|
||||
return String.format("%s?%s", path, encodedParams);
|
||||
}
|
||||
putHeadersFunction.accept(HttpHeaders.CONTENT_TYPE, MediaType.FORM_DATA.toString());
|
||||
setBodyFunction.accept(ByteString.copyFrom(encodedParams, StandardCharsets.UTF_8));
|
||||
requestBuilder.putHeaders(HttpHeaders.CONTENT_TYPE, MediaType.FORM_DATA.toString());
|
||||
requestBuilder.setBody(ByteString.copyFrom(encodedParams, StandardCharsets.UTF_8));
|
||||
return path;
|
||||
}
|
||||
|
||||
@@ -161,29 +152,26 @@ public class CloudTasksUtils implements Serializable {
|
||||
* default service account as the principal. That account must have permission to submit tasks to
|
||||
* Cloud Tasks.
|
||||
*
|
||||
* <p>The caller of this method is responsible for passing in the appropriate service based on the
|
||||
* runtime (GAE/GKE). Use the overload that takes an action class if possible.
|
||||
* <p>The caller of this method is responsible for passing in the appropriate service. Use the
|
||||
* overload that takes an action class if possible.
|
||||
*
|
||||
* @param path the relative URI (staring with a slash and ending without one).
|
||||
* @param method the HTTP method to be used for the request.
|
||||
* @param service the GAE/GKE service to route the request to.
|
||||
* @param service the service to route the request to.
|
||||
* @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.
|
||||
* @return the enqueued task.
|
||||
* @see <a
|
||||
* href=ttps://cloud.google.com/appengine/docs/standard/java/taskqueue/push/creating-tasks#target>Specifyinig
|
||||
* the worker service</a>
|
||||
* @see <a href=https://docs.cloud.google.com/tasks/docs/creating-http-target-tasks#java>Creating
|
||||
* HTTP target tasks</a>
|
||||
*/
|
||||
protected Task createTask(
|
||||
String path, Method method, Service service, Multimap<String, String> params) {
|
||||
String path, Method method, Action.Service service, Multimap<String, String> params) {
|
||||
checkArgument(
|
||||
path != null && !path.isEmpty() && path.charAt(0) == '/',
|
||||
"The path must start with a '/'.");
|
||||
HttpRequest.Builder requestBuilder =
|
||||
HttpRequest.newBuilder().setHttpMethod(HttpMethod.valueOf(method.name()));
|
||||
path =
|
||||
processRequestParameters(
|
||||
path, method, params, requestBuilder::putHeaders, requestBuilder::setBody);
|
||||
path = processRequestParameters(path, method, params, requestBuilder);
|
||||
OidcToken.Builder oidcTokenBuilder =
|
||||
OidcToken.newBuilder()
|
||||
.setServiceAccountEmail(credential.serviceAccount())
|
||||
@@ -205,16 +193,15 @@ public class CloudTasksUtils implements Serializable {
|
||||
* Cloud Tasks.
|
||||
*
|
||||
* <p>Prefer this overload over the one where the path and service are explicitly defined, as this
|
||||
* class will automatically determine the service to use based on the action and the runtime.
|
||||
* class will automatically determine the service to use based on the action.
|
||||
*
|
||||
* @param actionClazz the action class to run, must be annotated with {@link Action}.
|
||||
* @param method the HTTP method to be used for the request.
|
||||
* @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.
|
||||
* @return the enqueued task.
|
||||
* @see <a
|
||||
* href=ttps://cloud.google.com/appengine/docs/standard/java/taskqueue/push/creating-tasks#target>Specifyinig
|
||||
* the worker service</a>
|
||||
* @see <a href=https://docs.cloud.google.com/tasks/docs/creating-http-target-tasks#java>Creating
|
||||
* HTTP target tasks</a>
|
||||
*/
|
||||
public Task createTask(
|
||||
Class<? extends Runnable> actionClazz, Method method, Multimap<String, String> params) {
|
||||
@@ -231,32 +218,29 @@ public class CloudTasksUtils implements Serializable {
|
||||
method,
|
||||
actionClazz.getSimpleName(),
|
||||
allowedMethods);
|
||||
Service service =
|
||||
RegistryEnvironment.isOnJetty() ? Action.ServiceGetter.get(action) : action.service();
|
||||
return createTask(path, method, service, params);
|
||||
return createTask(path, method, action.service(), params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link Task} to be enqueued with a random delay up to {@code jitterSeconds}.
|
||||
*
|
||||
* <p>The caller of this method is responsible for passing in the appropriate service based on the
|
||||
* runtime (GAE/GKE). Use the overload that takes an action class if possible.
|
||||
* <p>The caller of this method is responsible for passing in the appropriate service. Use the
|
||||
* overload that takes an action class if possible.
|
||||
*
|
||||
* @param path the relative URI (staring with a slash and ending without one).
|
||||
* @param method the HTTP method to be used for the request.
|
||||
* @param service the GAE/GKE service to route the request to.
|
||||
* @param service the service to route the request to.
|
||||
* @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.
|
||||
* @param jitterSeconds the number of seconds that a task is randomly delayed up to.
|
||||
* @return the enqueued task.
|
||||
* @see <a
|
||||
* href=ttps://cloud.google.com/appengine/docs/standard/java/taskqueue/push/creating-tasks#target>Specifyinig
|
||||
* the worker service</a>
|
||||
* @see <a href=https://docs.cloud.google.com/tasks/docs/creating-http-target-tasks#java>Creating
|
||||
* HTTP target tasks</a>
|
||||
*/
|
||||
public Task createTaskWithJitter(
|
||||
String path,
|
||||
Method method,
|
||||
Service service,
|
||||
Action.Service service,
|
||||
Multimap<String, String> params,
|
||||
Optional<Integer> jitterSeconds) {
|
||||
if (jitterSeconds.isEmpty() || jitterSeconds.get() <= 0) {
|
||||
@@ -274,7 +258,7 @@ public class CloudTasksUtils implements Serializable {
|
||||
* Create a {@link Task} to be enqueued with a random delay up to {@code jitterSeconds}.
|
||||
*
|
||||
* <p>Prefer this overload over the one where the path and service are explicitly defined, as this
|
||||
* class will automatically determine the service to use based on the action and the runtime.
|
||||
* class will automatically determine the service to use based on the action.
|
||||
*
|
||||
* @param actionClazz the action class to run, must be annotated with {@link Action}.
|
||||
* @param method the HTTP method to be used for the request.
|
||||
@@ -282,9 +266,8 @@ public class CloudTasksUtils implements Serializable {
|
||||
* to the server to process the duplicate keys.
|
||||
* @param jitterSeconds the number of seconds that a task is randomly delayed up to.
|
||||
* @return the enqueued task.
|
||||
* @see <a
|
||||
* href=ttps://cloud.google.com/appengine/docs/standard/java/taskqueue/push/creating-tasks#target>Specifyinig
|
||||
* the worker service</a>
|
||||
* @see <a href=https://docs.cloud.google.com/tasks/docs/creating-http-target-tasks#java>Creating
|
||||
* HTTP target tasks</a>
|
||||
*/
|
||||
public Task createTaskWithJitter(
|
||||
Class<? extends Runnable> actionClazz,
|
||||
@@ -297,9 +280,7 @@ public class CloudTasksUtils implements Serializable {
|
||||
"Action class %s is not annotated with @Action",
|
||||
actionClazz.getSimpleName());
|
||||
String path = action.path();
|
||||
Service service =
|
||||
RegistryEnvironment.isOnJetty() ? Action.ServiceGetter.get(action) : action.service();
|
||||
return createTaskWithJitter(path, method, service, params, jitterSeconds);
|
||||
return createTaskWithJitter(path, method, action.service(), params, jitterSeconds);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -307,19 +288,18 @@ public class CloudTasksUtils implements Serializable {
|
||||
*
|
||||
* @param path the relative URI (staring with a slash and ending without one).
|
||||
* @param method the HTTP method to be used for the request.
|
||||
* @param service the GAE/GKE service to route the request to.
|
||||
* @param service the service to route the request to.
|
||||
* @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.
|
||||
* @param delay the amount of time that a task needs to be delayed for.
|
||||
* @return the enqueued task.
|
||||
* @see <a
|
||||
* href=ttps://cloud.google.com/appengine/docs/standard/java/taskqueue/push/creating-tasks#target>Specifyinig
|
||||
* the worker service</a>
|
||||
* @see <a href=https://docs.cloud.google.com/tasks/docs/creating-http-target-tasks#java>Creating
|
||||
* HTTP target tasks</a>
|
||||
*/
|
||||
private Task createTaskWithDelay(
|
||||
String path,
|
||||
Method method,
|
||||
Service service,
|
||||
Action.Service service,
|
||||
Multimap<String, String> params,
|
||||
Duration delay) {
|
||||
if (delay.isEqual(Duration.ZERO)) {
|
||||
@@ -335,7 +315,7 @@ public class CloudTasksUtils implements Serializable {
|
||||
* Create a {@link Task} to be enqueued with delay of {@code duration}.
|
||||
*
|
||||
* <p>Prefer this overload over the one where the path and service are explicitly defined, as this
|
||||
* class will automatically determine the service to use based on the action and the runtime.
|
||||
* class will automatically determine the service to use based on the action.
|
||||
*
|
||||
* @param actionClazz the action class to run, must be annotated with {@link Action}.
|
||||
* @param method the HTTP method to be used for the request.
|
||||
@@ -343,9 +323,8 @@ public class CloudTasksUtils implements Serializable {
|
||||
* to the server to process the duplicate keys.
|
||||
* @param delay the amount of time that a task needs to be delayed for.
|
||||
* @return the enqueued task.
|
||||
* @see <a
|
||||
* href=ttps://cloud.google.com/appengine/docs/standard/java/taskqueue/push/creating-tasks#target>Specifyinig
|
||||
* the worker service</a>
|
||||
* @see <a href=https://docs.cloud.google.com/tasks/docs/creating-http-target-tasks#java>Creating
|
||||
* HTTP target tasks</a>
|
||||
*/
|
||||
public Task createTaskWithDelay(
|
||||
Class<? extends Runnable> actionClazz,
|
||||
@@ -354,9 +333,7 @@ public class CloudTasksUtils implements Serializable {
|
||||
Duration delay) {
|
||||
Action action = getAction(actionClazz);
|
||||
String path = action.path();
|
||||
Service service =
|
||||
RegistryEnvironment.isOnJetty() ? Action.ServiceGetter.get(action) : action.service();
|
||||
return createTaskWithDelay(path, method, service, params, delay);
|
||||
return createTaskWithDelay(path, method, action.service(), params, delay);
|
||||
}
|
||||
|
||||
private static Action getAction(Class<? extends Runnable> actionClazz) {
|
||||
|
||||
@@ -37,7 +37,6 @@ import google.registry.model.eppcommon.ProtocolDefinition;
|
||||
import google.registry.model.eppoutput.EppOutput;
|
||||
import google.registry.persistence.transaction.QueryComposer.Comparator;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.Response;
|
||||
import google.registry.request.auth.Auth;
|
||||
import google.registry.request.lock.LockHandler;
|
||||
@@ -68,7 +67,7 @@ import org.joda.time.Duration;
|
||||
* this action runs, thus alerting us that human action is needed to correctly process the delete.
|
||||
*/
|
||||
@Action(
|
||||
service = GaeService.BACKEND,
|
||||
service = Action.Service.BACKEND,
|
||||
path = DeleteExpiredDomainsAction.PATH,
|
||||
auth = Auth.AUTH_ADMIN)
|
||||
public class DeleteExpiredDomainsAction implements Runnable {
|
||||
|
||||
@@ -37,7 +37,6 @@ import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.reporting.HistoryEntryDao;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.Parameter;
|
||||
import google.registry.request.auth.Auth;
|
||||
import google.registry.util.Clock;
|
||||
@@ -55,7 +54,7 @@ import jakarta.inject.Inject;
|
||||
* production.
|
||||
*/
|
||||
@Action(
|
||||
service = GaeService.BACKEND,
|
||||
service = Action.Service.BACKEND,
|
||||
path = "/_dr/task/deleteLoadTestData",
|
||||
method = POST,
|
||||
auth = Auth.AUTH_ADMIN)
|
||||
|
||||
@@ -42,7 +42,6 @@ import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.tld.Tld.TldType;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.Parameter;
|
||||
import google.registry.request.auth.Auth;
|
||||
import google.registry.util.RegistryEnvironment;
|
||||
@@ -59,7 +58,7 @@ import org.joda.time.Duration;
|
||||
* billing events, along with their ForeignKeyDomainIndex entities.
|
||||
*/
|
||||
@Action(
|
||||
service = GaeService.BACKEND,
|
||||
service = Action.Service.BACKEND,
|
||||
path = "/_dr/task/deleteProberData",
|
||||
method = POST,
|
||||
auth = Auth.AUTH_ADMIN)
|
||||
|
||||
@@ -35,7 +35,6 @@ import google.registry.model.billing.BillingEvent;
|
||||
import google.registry.model.billing.BillingRecurrence;
|
||||
import google.registry.model.common.Cursor;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.Parameter;
|
||||
import google.registry.request.Response;
|
||||
import google.registry.request.auth.Auth;
|
||||
@@ -51,7 +50,7 @@ import org.joda.time.DateTime;
|
||||
* BillingRecurrence} billing events into synthetic {@link BillingEvent} events.
|
||||
*/
|
||||
@Action(
|
||||
service = GaeService.BACKEND,
|
||||
service = Action.Service.BACKEND,
|
||||
path = "/_dr/task/expandBillingRecurrences",
|
||||
auth = Auth.AUTH_ADMIN)
|
||||
public class ExpandBillingRecurrencesAction implements Runnable {
|
||||
|
||||
@@ -32,7 +32,6 @@ import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.tld.RegistryLockDao;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.Parameter;
|
||||
import google.registry.request.Response;
|
||||
import google.registry.request.auth.Auth;
|
||||
@@ -47,7 +46,7 @@ import org.joda.time.Duration;
|
||||
|
||||
/** Task that re-locks a previously-Registry-Locked domain after a predetermined period of time. */
|
||||
@Action(
|
||||
service = GaeService.BACKEND,
|
||||
service = Action.Service.BACKEND,
|
||||
path = RelockDomainAction.PATH,
|
||||
method = POST,
|
||||
automaticallyPrintOk = true,
|
||||
@@ -113,11 +112,11 @@ public class RelockDomainAction implements Runnable {
|
||||
public void run() {
|
||||
/* We wish to manually control our retry behavior, in order to limit the number of retries
|
||||
* and/or notify registrars / support only after a certain number of retries, or only
|
||||
* with a certain type of failure. AppEngine will automatically retry on any non-2xx status
|
||||
* with a certain type of failure. Cloud Tasks will automatically retry on any non-2xx status
|
||||
* code, so return SC_NO_CONTENT (204) by default to avoid this auto-retry.
|
||||
*
|
||||
* See https://cloud.google.com/appengine/docs/standard/java/taskqueue/push/retrying-tasks
|
||||
* for more details on retry behavior. */
|
||||
* See https://docs.cloud.google.com/tasks/docs/configuring-queues#retry for more details on
|
||||
* retry behavior. */
|
||||
response.setStatus(SC_NO_CONTENT);
|
||||
response.setContentType(MediaType.PLAIN_TEXT_UTF_8);
|
||||
tm().transact(this::relockDomain);
|
||||
|
||||
@@ -44,7 +44,6 @@ import google.registry.model.eppcommon.ProtocolDefinition;
|
||||
import google.registry.model.eppoutput.EppOutput;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.Response;
|
||||
import google.registry.request.auth.Auth;
|
||||
import google.registry.request.lock.LockHandler;
|
||||
@@ -67,7 +66,7 @@ import org.joda.time.Duration;
|
||||
* leaving behind a record recording that update.
|
||||
*/
|
||||
@Action(
|
||||
service = GaeService.BACKEND,
|
||||
service = Action.Service.BACKEND,
|
||||
path = RemoveAllDomainContactsAction.PATH,
|
||||
method = Action.Method.POST,
|
||||
auth = Auth.AUTH_ADMIN)
|
||||
@@ -94,7 +93,7 @@ public class RemoveAllDomainContactsAction implements Runnable {
|
||||
EppController eppController,
|
||||
@Config("registryAdminClientId") String registryAdminClientId,
|
||||
LockHandler lockHandler,
|
||||
@Named("removeAllDomainContacts") RateLimiter rateLimiter,
|
||||
@Named("standardRateLimiter") RateLimiter rateLimiter,
|
||||
Response response) {
|
||||
this.eppController = eppController;
|
||||
this.registryAdminClientId = registryAdminClientId;
|
||||
|
||||
@@ -28,7 +28,6 @@ import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.net.MediaType;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.Parameter;
|
||||
import google.registry.request.Response;
|
||||
import google.registry.request.auth.Auth;
|
||||
@@ -54,7 +53,7 @@ import jakarta.inject.Inject;
|
||||
* <p>This runs the {@link google.registry.beam.resave.ResaveAllEppResourcesPipeline}.
|
||||
*/
|
||||
@Action(
|
||||
service = GaeService.BACKEND,
|
||||
service = Action.Service.BACKEND,
|
||||
path = ResaveAllEppResourcesPipelineAction.PATH,
|
||||
auth = Auth.AUTH_ADMIN)
|
||||
public class ResaveAllEppResourcesPipelineAction implements Runnable {
|
||||
|
||||
@@ -25,7 +25,6 @@ import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.Action.Method;
|
||||
import google.registry.request.Parameter;
|
||||
import google.registry.request.Response;
|
||||
@@ -40,7 +39,7 @@ import org.joda.time.DateTime;
|
||||
* <p>{@link EppResource}s will be projected forward to the current time.
|
||||
*/
|
||||
@Action(
|
||||
service = GaeService.BACKEND,
|
||||
service = Action.Service.BACKEND,
|
||||
path = ResaveEntityAction.PATH,
|
||||
auth = Auth.AUTH_ADMIN,
|
||||
method = Method.POST)
|
||||
|
||||
@@ -35,7 +35,6 @@ import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarPoc;
|
||||
import google.registry.model.registrar.RegistrarPoc.Type;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.Response;
|
||||
import google.registry.request.auth.Auth;
|
||||
import google.registry.util.EmailMessage;
|
||||
@@ -50,7 +49,7 @@ import org.joda.time.format.DateTimeFormatter;
|
||||
|
||||
/** An action that sends notification emails to registrars whose certificates are expiring soon. */
|
||||
@Action(
|
||||
service = GaeService.BACKEND,
|
||||
service = Action.Service.BACKEND,
|
||||
path = SendExpiringCertificateNotificationEmailAction.PATH,
|
||||
auth = Auth.AUTH_ADMIN)
|
||||
public class SendExpiringCertificateNotificationEmailAction implements Runnable {
|
||||
|
||||
@@ -30,7 +30,6 @@ import google.registry.beam.wipeout.WipeOutContactHistoryPiiPipeline;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.model.contact.ContactHistory;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.Parameter;
|
||||
import google.registry.request.Response;
|
||||
import google.registry.request.auth.Auth;
|
||||
@@ -49,7 +48,7 @@ import org.joda.time.DateTime;
|
||||
* time.
|
||||
*/
|
||||
@Action(
|
||||
service = GaeService.BACKEND,
|
||||
service = Action.Service.BACKEND,
|
||||
path = WipeOutContactHistoryPiiAction.PATH,
|
||||
auth = Auth.AUTH_ADMIN)
|
||||
public class WipeOutContactHistoryPiiAction implements Runnable {
|
||||
|
||||
@@ -40,8 +40,6 @@ public class RegistryPipelineWorkerInitializer implements JvmInitializer {
|
||||
|
||||
@Override
|
||||
public void beforeProcessing(PipelineOptions options) {
|
||||
// TODO(b/416299900): remove next line after GAE is removed.
|
||||
System.setProperty("google.registry.jetty", "true");
|
||||
RegistryPipelineOptions registryOptions = options.as(RegistryPipelineOptions.class);
|
||||
RegistryEnvironment environment = registryOptions.getRegistryEnvironment();
|
||||
if (environment == null || environment.equals(RegistryEnvironment.UNITTEST)) {
|
||||
|
||||
@@ -279,20 +279,6 @@ public class BigqueryConnection implements AutoCloseable {
|
||||
private TableReference getTableReference() {
|
||||
return table.getTableReference().clone();
|
||||
}
|
||||
|
||||
/** Returns a string representation of the TableReference for the wrapped table. */
|
||||
public String getStringReference() {
|
||||
return tableReferenceToString(table.getTableReference());
|
||||
}
|
||||
|
||||
/** Returns a string representation of the given TableReference. */
|
||||
private static String tableReferenceToString(TableReference tableRef) {
|
||||
return String.format(
|
||||
"%s:%s.%s",
|
||||
tableRef.getProjectId(),
|
||||
tableRef.getDatasetId(),
|
||||
tableRef.getTableId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -398,29 +384,12 @@ public class BigqueryConnection implements AutoCloseable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts an asynchronous query job to dump the results of the specified query into a local
|
||||
* ImmutableTable object, row-keyed by the row number (indexed from 1), column-keyed by the
|
||||
* TableFieldSchema for that column, and with the value object as the cell value. Note that null
|
||||
* values will not actually be null, but they can be checked for using Data.isNull().
|
||||
* Dumps the results of the specified query into a local ImmutableTable object, row-keyed by the
|
||||
* row number (indexed from 1), column-keyed by the TableFieldSchema for that column, and with the
|
||||
* value object as the cell value.
|
||||
*
|
||||
* <p>Returns a ListenableFuture that holds the ImmutableTable on success.
|
||||
*/
|
||||
public ListenableFuture<ImmutableTable<Integer, TableFieldSchema, Object>>
|
||||
queryToLocalTable(String querySql) {
|
||||
Job job = new Job()
|
||||
.setConfiguration(new JobConfiguration()
|
||||
.setQuery(new JobConfigurationQuery()
|
||||
.setQuery(querySql)
|
||||
.setDefaultDataset(getDataset())));
|
||||
return transform(runJobToCompletion(job), this::getQueryResults, directExecutor());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the result of calling queryToLocalTable, but synchronously to avoid spawning new
|
||||
* background threads, which App Engine doesn't support.
|
||||
*
|
||||
* @see <a href="https://cloud.google.com/appengine/docs/standard/java/runtime#Threads">App Engine
|
||||
* Runtime</a>
|
||||
* <p>Note that null values will not actually be null, but they can be checked for using
|
||||
* Data.isNull()
|
||||
*/
|
||||
public ImmutableTable<Integer, TableFieldSchema, Object> queryToLocalTableSync(String querySql) {
|
||||
Job job = new Job()
|
||||
@@ -634,10 +603,6 @@ public class BigqueryConnection implements AutoCloseable {
|
||||
});
|
||||
}
|
||||
|
||||
private ListenableFuture<Job> runJobToCompletion(final Job job) {
|
||||
return service.submit(() -> runJob(job, null));
|
||||
}
|
||||
|
||||
/** Helper that returns true if a dataset with this name exists. */
|
||||
public boolean checkDatasetExists(String datasetName) throws IOException {
|
||||
try {
|
||||
@@ -676,14 +641,6 @@ public class BigqueryConnection implements AutoCloseable {
|
||||
.setDatasetId(getDatasetId());
|
||||
}
|
||||
|
||||
/** Returns table reference with the projectId and datasetId filled out for you. */
|
||||
public TableReference getTable(String tableName) {
|
||||
return new TableReference()
|
||||
.setProjectId(getProjectId())
|
||||
.setDatasetId(getDatasetId())
|
||||
.setTableId(tableName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper that creates a dataset with this name if it doesn't already exist, and returns true if
|
||||
* creation took place.
|
||||
|
||||
@@ -71,9 +71,7 @@ class BsaDiffCreator {
|
||||
Optional<String> previousJobName = schedule.latestCompleted().map(CompletedJob::jobName);
|
||||
/*
|
||||
* Memory usage is a concern when creating a diff, when the newest download needs to be held in
|
||||
* memory in its entirety. The top-grade AppEngine VM has 3GB of memory, leaving less than 1.5GB
|
||||
* to application memory footprint after subtracting overheads due to copying garbage collection
|
||||
* and non-heap data etc. Assuming 400K labels, each of which on average included in 5 orders,
|
||||
* memory in its entirety. Assuming 400K labels, each of which on average included in 5 orders,
|
||||
* the memory footprint is at least 300MB when loaded into a Hashset-backed Multimap (64-bit
|
||||
* JVM, with 12-byte object header, 16-byte array header, and 16-byte alignment).
|
||||
*
|
||||
|
||||
@@ -41,7 +41,6 @@ import google.registry.bsa.persistence.DownloadScheduler;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.model.tld.Tlds;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.Response;
|
||||
import google.registry.request.auth.Auth;
|
||||
import google.registry.util.Clock;
|
||||
@@ -51,7 +50,7 @@ import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Action(
|
||||
service = GaeService.BSA,
|
||||
service = Action.Service.BACKEND,
|
||||
path = BsaDownloadAction.PATH,
|
||||
method = {GET, POST},
|
||||
auth = Auth.AUTH_ADMIN)
|
||||
|
||||
@@ -31,7 +31,6 @@ import google.registry.bsa.persistence.RefreshScheduler;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.model.tld.Tlds;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.Response;
|
||||
import google.registry.request.auth.Auth;
|
||||
import google.registry.util.BatchedStreams;
|
||||
@@ -42,7 +41,7 @@ import java.util.stream.Stream;
|
||||
import org.joda.time.Duration;
|
||||
|
||||
@Action(
|
||||
service = GaeService.BSA,
|
||||
service = Action.Service.BACKEND,
|
||||
path = BsaRefreshAction.PATH,
|
||||
method = {GET, POST},
|
||||
auth = Auth.AUTH_ADMIN)
|
||||
|
||||
@@ -53,7 +53,6 @@ import google.registry.model.domain.Domain;
|
||||
import google.registry.model.tld.Tld;
|
||||
import google.registry.model.tld.Tld.TldType;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.Response;
|
||||
import google.registry.request.auth.Auth;
|
||||
import google.registry.util.Clock;
|
||||
@@ -66,7 +65,7 @@ import org.joda.time.Duration;
|
||||
|
||||
/** Validates the BSA data in the database against the most recent block lists. */
|
||||
@Action(
|
||||
service = GaeService.BSA,
|
||||
service = Action.Service.BACKEND,
|
||||
path = BsaValidateAction.PATH,
|
||||
method = {GET, POST},
|
||||
auth = Auth.AUTH_ADMIN)
|
||||
|
||||
@@ -42,7 +42,6 @@ import google.registry.model.tld.Tld;
|
||||
import google.registry.model.tld.Tld.TldType;
|
||||
import google.registry.model.tld.label.ReservedList;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.auth.Auth;
|
||||
import google.registry.util.Clock;
|
||||
import jakarta.inject.Inject;
|
||||
@@ -78,7 +77,7 @@ import org.joda.time.DateTime;
|
||||
* <p>The file is also uploaded to GCS to preserve it as a record for ourselves.
|
||||
*/
|
||||
@Action(
|
||||
service = GaeService.BSA,
|
||||
service = Action.Service.BACKEND,
|
||||
path = "/_dr/task/uploadBsaUnavailableNames",
|
||||
method = {GET, POST},
|
||||
auth = Auth.AUTH_ADMIN)
|
||||
|
||||
@@ -44,8 +44,6 @@ public abstract class CredentialModule {
|
||||
* <p>The credential returned by the Cloud Runtime depends on the runtime environment:
|
||||
*
|
||||
* <ul>
|
||||
* <li>On App Engine, returns a scope-less {@code ComputeEngineCredentials} for
|
||||
* PROJECT_ID@appspot.gserviceaccount.com
|
||||
* <li>On Compute Engine, returns a scope-less {@code ComputeEngineCredentials} for
|
||||
* PROJECT_NUMBER-compute@developer.gserviceaccount.com
|
||||
* <li>On end user host, this returns the credential downloaded by gcloud. Please refer to <a
|
||||
@@ -87,8 +85,8 @@ public abstract class CredentialModule {
|
||||
* the application default credential user.
|
||||
*
|
||||
* <p>The Workspace domain must grant delegated admin access to the default service account user
|
||||
* (project-id@appspot.gserviceaccount.com on AppEngine) with all scopes in {@code defaultScopes}
|
||||
* and {@code delegationScopes}.
|
||||
* (nomulus-service-account@{project-id}.iam.gserviceaccount.com on GCP) with all scopes in {@code
|
||||
* defaultScopes} and {@code delegationScopes}.
|
||||
*/
|
||||
@AdcDelegatedCredential
|
||||
@Provides
|
||||
@@ -113,9 +111,9 @@ public abstract class CredentialModule {
|
||||
* Provides a {@link GoogleCredentialsBundle} for sending emails through Google Workspace.
|
||||
*
|
||||
* <p>The Workspace domain must grant delegated admin access to the default service account user
|
||||
* (project-id@appspot.gserviceaccount.com on AppEngine) with all scopes in {@code defaultScopes}
|
||||
* and {@code delegationScopes}. In addition, the user {@code gSuiteOutgoingEmailAddress} must
|
||||
* have the permission to send emails.
|
||||
* (nomulus-service-account@{project-id}.iam.gserviceaccount.com on GCP) with all scopes in {@code
|
||||
* defaultScopes} and {@code delegationScopes}. In addition, the user {@code
|
||||
* gSuiteOutgoingEmailAddress} must have the permission to send emails.
|
||||
*/
|
||||
@GmailDelegatedCredential
|
||||
@Provides
|
||||
|
||||
@@ -55,8 +55,9 @@ import org.apache.commons.codec.binary.Base64;
|
||||
*
|
||||
* <p>This class accepts the application-default-credential as {@code ServiceAccountSigner},
|
||||
* avoiding the need for exported private keys. In this case, the default credential user itself
|
||||
* (project-id@appspot.gserviceaccount.com on AppEngine) must have domain-wide delegation to the
|
||||
* Workspace APIs. The default credential user also must have the Token Creator role to itself.
|
||||
* (nomulus-service-account@{project-id}.iam.gserviceaccount.com on GCP) must have domain-wide
|
||||
* delegation to the Workspace APIs. The default credential user also must have the Token Creator
|
||||
* role to itself.
|
||||
*
|
||||
* <p>If the user provides a credential {@code S} that carries its own private key, such as {@link
|
||||
* com.google.auth.oauth2.ServiceAccountCredentials}, this class can use {@code S} to impersonate
|
||||
|
||||
@@ -36,8 +36,9 @@ import dagger.Provides;
|
||||
import google.registry.bsa.UploadBsaUnavailableDomainsAction;
|
||||
import google.registry.dns.ReadDnsRefreshRequestsAction;
|
||||
import google.registry.model.common.DnsRefreshRequest;
|
||||
import google.registry.mosapi.MosApiClient;
|
||||
import google.registry.persistence.transaction.JpaTransactionManager;
|
||||
import google.registry.request.Action.GkeService;
|
||||
import google.registry.request.Action.Service;
|
||||
import google.registry.util.RegistryEnvironment;
|
||||
import google.registry.util.YamlUtils;
|
||||
import jakarta.inject.Named;
|
||||
@@ -961,7 +962,7 @@ public final class RegistryConfig {
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of times to retry a GAE operation when {@code TransientFailureException} is thrown.
|
||||
* Number of times to retry an operation when {@code TransientFailureException} is thrown.
|
||||
*
|
||||
* <p>The number of milliseconds it'll sleep before giving up is {@code (2^n - 2) * 100}.
|
||||
*
|
||||
@@ -1415,6 +1416,52 @@ public final class RegistryConfig {
|
||||
return config.bsa.uploadUnavailableDomainsUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URL we send HTTP requests for MoSAPI.
|
||||
*
|
||||
* @see MosApiClient
|
||||
*/
|
||||
@Provides
|
||||
@Config("mosapiServiceUrl")
|
||||
public static String provideMosapiServiceUrl(RegistryConfigSettings config) {
|
||||
return config.mosapi.serviceUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the entityType we send HTTP requests for MoSAPI.
|
||||
*
|
||||
* @see MosApiClient
|
||||
*/
|
||||
@Provides
|
||||
@Config("mosapiEntityType")
|
||||
public static String provideMosapiEntityType(RegistryConfigSettings config) {
|
||||
return config.mosapi.entityType;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("mosapiTlsCertSecretName")
|
||||
public static String provideMosapiTlsCertSecretName(RegistryConfigSettings config) {
|
||||
return config.mosapi.tlsCertSecretName;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("mosapiTlsCertKeyName")
|
||||
public static String provideMosapiTlsKeySecretName(RegistryConfigSettings config) {
|
||||
return config.mosapi.tlsKeySecretName;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("mosapiTlds")
|
||||
public static ImmutableSet<String> provideMosapiTlds(RegistryConfigSettings config) {
|
||||
return ImmutableSet.copyOf(config.mosapi.tlds);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("mosapiServices")
|
||||
public static ImmutableSet<String> provideMosapiServices(RegistryConfigSettings config) {
|
||||
return ImmutableSet.copyOf(config.mosapi.services);
|
||||
}
|
||||
|
||||
private static String formatComments(String text) {
|
||||
return Splitter.on('\n').omitEmptyStrings().trimResults().splitToList(text).stream()
|
||||
.map(s -> "# " + s)
|
||||
@@ -1422,7 +1469,7 @@ public final class RegistryConfig {
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the App Engine project ID, which is based off the environment name. */
|
||||
/** Returns the project ID, which is based off the environment name. */
|
||||
public static String getProjectId() {
|
||||
return CONFIG_SETTINGS.get().gcpProject.projectId;
|
||||
}
|
||||
@@ -1444,55 +1491,10 @@ public final class RegistryConfig {
|
||||
return CONFIG_SETTINGS.get().gcpProject.baseDomain;
|
||||
}
|
||||
|
||||
public static URL getServiceUrl(GkeService service) {
|
||||
public static URL getServiceUrl(Service service) {
|
||||
return makeUrl(String.format("https://%s.%s", service.getServiceId(), getBaseDomain()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the address of the Nomulus app default HTTP server.
|
||||
*
|
||||
* <p>This is used by the {@code nomulus} tool to connect to the App Engine remote API.
|
||||
*/
|
||||
public static URL getDefaultServer() {
|
||||
return makeUrl(CONFIG_SETTINGS.get().gcpProject.defaultServiceUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the address of the Nomulus app backend HTTP server.
|
||||
*
|
||||
* <p>This is used by the {@code nomulus} tool to connect to the App Engine remote API.
|
||||
*/
|
||||
public static URL getBackendServer() {
|
||||
return makeUrl(CONFIG_SETTINGS.get().gcpProject.backendServiceUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the address of the Nomulus app bsa HTTP server.
|
||||
*
|
||||
* <p>This is used by the {@code nomulus} tool to connect to the App Engine remote API.
|
||||
*/
|
||||
public static URL getBsaServer() {
|
||||
return makeUrl(CONFIG_SETTINGS.get().gcpProject.bsaServiceUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the address of the Nomulus app tools HTTP server.
|
||||
*
|
||||
* <p>This is used by the {@code nomulus} tool to connect to the App Engine remote API.
|
||||
*/
|
||||
public static URL getToolsServer() {
|
||||
return makeUrl(CONFIG_SETTINGS.get().gcpProject.toolsServiceUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the address of the Nomulus app pubapi HTTP server.
|
||||
*
|
||||
* <p>This is used by the {@code nomulus} tool to connect to the App Engine remote API.
|
||||
*/
|
||||
public static URL getPubapiServer() {
|
||||
return makeUrl(CONFIG_SETTINGS.get().gcpProject.pubapiServiceUrl);
|
||||
}
|
||||
|
||||
/** Returns the amount of time a singleton should be cached, before expiring. */
|
||||
public static java.time.Duration getSingletonCacheRefreshDuration() {
|
||||
return java.time.Duration.ofSeconds(CONFIG_SETTINGS.get().caching.singletonCacheRefreshSeconds);
|
||||
|
||||
@@ -43,6 +43,7 @@ public class RegistryConfigSettings {
|
||||
public DnsUpdate dnsUpdate;
|
||||
public BulkPricingPackageMonitoring bulkPricingPackageMonitoring;
|
||||
public Bsa bsa;
|
||||
public MosApi mosapi;
|
||||
|
||||
/** Configuration options that apply to the entire GCP project. */
|
||||
public static class GcpProject {
|
||||
@@ -50,11 +51,6 @@ public class RegistryConfigSettings {
|
||||
public long projectIdNumber;
|
||||
public String locationId;
|
||||
public boolean isLocal;
|
||||
public String defaultServiceUrl;
|
||||
public String backendServiceUrl;
|
||||
public String bsaServiceUrl;
|
||||
public String toolsServiceUrl;
|
||||
public String pubapiServiceUrl;
|
||||
public String baseDomain;
|
||||
}
|
||||
|
||||
@@ -267,4 +263,14 @@ public class RegistryConfigSettings {
|
||||
public String unblockableDomainsUrl;
|
||||
public String uploadUnavailableDomainsUrl;
|
||||
}
|
||||
|
||||
/** Configuration for Mosapi. */
|
||||
public static class MosApi {
|
||||
public String serviceUrl;
|
||||
public String tlsCertSecretName;
|
||||
public String tlsKeySecretName;
|
||||
public String entityType;
|
||||
public List<String> tlds;
|
||||
public List<String> services;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,17 +12,11 @@ gcpProject:
|
||||
projectIdNumber: 123456789012
|
||||
# Location of the GCP project, note that us-central1 and europe-west1 are special in that
|
||||
# they are used without the trailing number in GCP commands and Google Cloud Console.
|
||||
# See: https://cloud.google.com/appengine/docs/locations as an example
|
||||
# See: https://docs.cloud.google.com/compute/docs/regions-zones as an example
|
||||
locationId: registry-location-id
|
||||
|
||||
# whether to use local/test credentials when connecting to the servers
|
||||
isLocal: true
|
||||
# URLs of the services for the project.
|
||||
defaultServiceUrl: https://default.example.com
|
||||
backendServiceUrl: https://backend.example.com
|
||||
bsaServiceUrl: https://bsa.example.com
|
||||
toolsServiceUrl: https://tools.example.com
|
||||
pubapiServiceUrl: https://pubapi.example.com
|
||||
|
||||
# The base domain name of the registry service. Services are reachable at [service].baseDomain.
|
||||
baseDomain: registry.test
|
||||
@@ -32,9 +26,9 @@ gSuite:
|
||||
domainName: domain-registry.example
|
||||
|
||||
# Display name and email address used on outgoing emails through G Suite.
|
||||
# The email address must be valid and have permission in the GAE app to send
|
||||
# emails. For more info see:
|
||||
# https://cloud.google.com/appengine/docs/standard/java/mail/#who_can_send_mail
|
||||
# The email address must be valid and the domain must be set up to send emails.
|
||||
# For more info see
|
||||
# https://docs.cloud.google.com/compute/docs/tutorials/sending-mail
|
||||
outgoingEmailDisplayName: Example Registry
|
||||
outgoingEmailAddress: noreply@project-id.appspotmail.com
|
||||
# TODO(b/279671974): reuse `outgoingEmailAddress` after migration
|
||||
@@ -201,18 +195,16 @@ hibernate:
|
||||
# but lock tables explicitly, either using framework-dependent API, or execute
|
||||
# "select table for update" statements directly.
|
||||
connectionIsolation: TRANSACTION_SERIALIZABLE
|
||||
# Whether to log all SQL queries to App Engine logs. Overridable at runtime.
|
||||
# Whether to log all SQL queries. Overridable at runtime.
|
||||
logSqlQueries: false
|
||||
|
||||
# Connection pool configurations.
|
||||
hikariConnectionTimeout: 20000
|
||||
# Cloud SQL connections are a relatively scarce resource (maximum is 1000 as
|
||||
# of March 2021). The minimumIdle should be a small value so that machines may
|
||||
# release connections after a demand spike. The maximumPoolSize is set to 10
|
||||
# because that is the maximum number of concurrent requests a Nomulus server
|
||||
# instance can handle (as limited by AppEngine for basic/manual scaling). Note
|
||||
# that BEAM pipelines are not subject to the maximumPoolSize value defined
|
||||
# here. See PersistenceModule.java for more information.
|
||||
# release connections after a demand spike. Note that BEAM pipelines are not
|
||||
# subject to the maximumPoolSize value defined here. See PersistenceModule.java
|
||||
# for more information.
|
||||
hikariMinimumIdle: 1
|
||||
hikariMaximumPoolSize: 40
|
||||
hikariIdleTimeout: 300000
|
||||
@@ -264,8 +256,8 @@ caching:
|
||||
|
||||
# Maximum total number of static premium list entry entities to cache in
|
||||
# memory, across all premium lists for all TLDs. Tuning this up will use more
|
||||
# memory (and might require using larger App Engine instances). Note that
|
||||
# premium list entries that are absent are cached in addition to ones that are
|
||||
# memory (and might require using larger instances). Note that premium list
|
||||
# entries that are absent are cached in addition to ones that are
|
||||
# present, so the total cache size is not bounded by the total number of
|
||||
# premium price entries that exist.
|
||||
staticPremiumListMaxCachedEntries: 200000
|
||||
@@ -346,12 +338,8 @@ credentialOAuth:
|
||||
localCredentialOauthScopes:
|
||||
# View and manage data in all Google Cloud APIs.
|
||||
- https://www.googleapis.com/auth/cloud-platform
|
||||
# Call App Engine APIs locally.
|
||||
- https://www.googleapis.com/auth/appengine.apis
|
||||
# View your email address.
|
||||
- https://www.googleapis.com/auth/userinfo.email
|
||||
# View and manage your applications deployed on Google App Engine
|
||||
- https://www.googleapis.com/auth/appengine.admin
|
||||
# The lifetime of an access token generated by our custom credentials classes
|
||||
# Must be shorter than one hour.
|
||||
tokenRefreshDelaySeconds: 1800
|
||||
@@ -433,7 +421,7 @@ misc:
|
||||
spec11BccEmailAddresses:
|
||||
- abuse@example.com
|
||||
|
||||
# Number of times to retry a GAE operation when a transient exception is thrown.
|
||||
# Number of times to retry an operation when a transient exception is thrown.
|
||||
# The number of milliseconds it'll sleep before giving up is (2^n - 2) * 100.
|
||||
transientFailureRetries: 12
|
||||
|
||||
@@ -628,3 +616,30 @@ bsa:
|
||||
unblockableDomainsUrl: "https://"
|
||||
# API endpoint for uploading the list of unavailable domain names.
|
||||
uploadUnavailableDomainsUrl: "https://"
|
||||
|
||||
mosapi:
|
||||
# URL for the MosAPI
|
||||
serviceUrl: https://mosapi.icann.org
|
||||
# The type of entity being monitored.
|
||||
# For registries, this is 'ry'
|
||||
# For registrars, this is 'rr'
|
||||
entityType: ry
|
||||
# Add your List of TLDs to be monitored
|
||||
tlds:
|
||||
- your_tld1
|
||||
- your_tld2
|
||||
# Add tls cert secret name
|
||||
# you configured in secret manager
|
||||
tlsCertSecretName: YOUR_TLS_CERT_SECRET_NAME
|
||||
# Add tls key secret name
|
||||
# you configured in secret manager
|
||||
tlsKeySecretName: YOUR_TLS_KEY_SECRET_NAME
|
||||
# List of services to check for each TLD.
|
||||
services:
|
||||
- "dns"
|
||||
- "rdap"
|
||||
- "rdds"
|
||||
- "epp"
|
||||
- "dnssec"
|
||||
|
||||
|
||||
|
||||
@@ -40,14 +40,12 @@ import com.google.common.collect.Streams;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.batch.CloudTasksUtils;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.Action.GkeService;
|
||||
import google.registry.request.Action.Service;
|
||||
import google.registry.request.Parameter;
|
||||
import google.registry.request.ParameterMap;
|
||||
import google.registry.request.RequestParameters;
|
||||
import google.registry.request.Response;
|
||||
import google.registry.request.auth.Auth;
|
||||
import google.registry.util.RegistryEnvironment;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
@@ -59,8 +57,7 @@ import java.util.stream.Stream;
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@code endpoint} (Required) URL path of servlet to launch. This may contain pathargs.
|
||||
* <li>{@code queue} (Required) Name of the App Engine push queue to which this task should be
|
||||
* sent.
|
||||
* <li>{@code queue} (Required) Name of the queue to which this task should be sent.
|
||||
* <li>{@code forEachRealTld} Launch the task in each real TLD namespace.
|
||||
* <li>{@code forEachTestTld} Launch the task in each test TLD namespace.
|
||||
* <li>{@code runInEmpty} Launch the task once, without the TLD argument.
|
||||
@@ -80,7 +77,7 @@ import java.util.stream.Stream;
|
||||
* </ul>
|
||||
*/
|
||||
@Action(
|
||||
service = GaeService.BACKEND,
|
||||
service = Service.BACKEND,
|
||||
path = "/_dr/cron/fanout",
|
||||
automaticallyPrintOk = true,
|
||||
auth = Auth.AUTH_ADMIN)
|
||||
@@ -160,10 +157,6 @@ public final class TldFanoutAction implements Runnable {
|
||||
params.put(RequestParameters.PARAM_TLD, tld);
|
||||
}
|
||||
return cloudTasksUtils.createTaskWithJitter(
|
||||
endpoint,
|
||||
Action.Method.POST,
|
||||
RegistryEnvironment.isOnJetty() ? GkeService.BACKEND : GaeService.BACKEND,
|
||||
params,
|
||||
jitterSeconds);
|
||||
endpoint, Action.Method.POST, Service.BACKEND, params, jitterSeconds);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,6 @@ import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarPoc;
|
||||
import google.registry.model.tld.Tld;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.Header;
|
||||
import google.registry.request.HttpException.ServiceUnavailableException;
|
||||
import google.registry.request.Parameter;
|
||||
@@ -72,7 +71,7 @@ import org.joda.time.Duration;
|
||||
|
||||
/** Task that sends domain and host updates to the DNS server. */
|
||||
@Action(
|
||||
service = GaeService.BACKEND,
|
||||
service = Action.Service.BACKEND,
|
||||
path = PublishDnsUpdatesAction.PATH,
|
||||
method = POST,
|
||||
automaticallyPrintOk = true,
|
||||
|
||||
@@ -45,7 +45,6 @@ import google.registry.dns.DnsUtils.TargetType;
|
||||
import google.registry.model.common.DnsRefreshRequest;
|
||||
import google.registry.model.tld.Tld;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.Parameter;
|
||||
import google.registry.request.auth.Auth;
|
||||
import google.registry.util.Clock;
|
||||
@@ -60,7 +59,7 @@ import org.joda.time.Duration;
|
||||
* table.
|
||||
*/
|
||||
@Action(
|
||||
service = GaeService.BACKEND,
|
||||
service = Action.Service.BACKEND,
|
||||
path = "/_dr/task/readDnsRefreshRequests",
|
||||
automaticallyPrintOk = true,
|
||||
method = POST,
|
||||
|
||||
@@ -26,7 +26,6 @@ import google.registry.model.annotations.ExternalMessagingName;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.HttpException.BadRequestException;
|
||||
import google.registry.request.HttpException.NotFoundException;
|
||||
import google.registry.request.Parameter;
|
||||
@@ -36,7 +35,7 @@ import jakarta.inject.Inject;
|
||||
|
||||
/** Action that manually triggers refresh of DNS information. */
|
||||
@Action(
|
||||
service = GaeService.BACKEND,
|
||||
service = Action.Service.BACKEND,
|
||||
path = "/_dr/task/dnsRefresh",
|
||||
automaticallyPrintOk = true,
|
||||
auth = Auth.AUTH_ADMIN)
|
||||
|
||||
@@ -26,7 +26,6 @@ import google.registry.model.domain.Domain;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.Parameter;
|
||||
import google.registry.request.Response;
|
||||
import google.registry.request.auth.Auth;
|
||||
@@ -34,7 +33,7 @@ import jakarta.inject.Inject;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
@Action(
|
||||
service = GaeService.BACKEND,
|
||||
service = Action.Service.BACKEND,
|
||||
path = PATH,
|
||||
method = Action.Method.POST,
|
||||
auth = Auth.AUTH_ADMIN)
|
||||
|
||||
@@ -36,8 +36,10 @@ import org.xbill.DNS.Opcode;
|
||||
/**
|
||||
* A transport for DNS messages. Sends/receives DNS messages over TCP using old-style {@link Socket}
|
||||
* s and the message framing defined in <a href="https://tools.ietf.org/html/rfc1035">RFC 1035</a>.
|
||||
* We would like use the dnsjava library's {@link org.xbill.DNS.SimpleResolver} class for this, but
|
||||
* it requires {@link java.nio.channels.SocketChannel} which is not supported on AppEngine.
|
||||
*
|
||||
* <p>TODO(b/463732345): now that we're no longer on AppEngine, see if we can use the dnsjava
|
||||
* library's {@link org.xbill.DNS.SimpleResolver} class instead of this (that requires {@link
|
||||
* java.nio.channels.SocketChannel} which is not supported on AppEngine).
|
||||
*/
|
||||
public class DnsMessageTransport {
|
||||
|
||||
|
||||
@@ -36,7 +36,6 @@ import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.tld.Tld;
|
||||
import google.registry.model.tld.Tld.TldType;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.auth.Auth;
|
||||
import google.registry.storage.drive.DriveConnection;
|
||||
import google.registry.util.Clock;
|
||||
@@ -58,7 +57,7 @@ import org.joda.time.DateTimeZone;
|
||||
* name TLD.txt into the domain-lists bucket. Note that this overwrites the files in place.
|
||||
*/
|
||||
@Action(
|
||||
service = GaeService.BACKEND,
|
||||
service = Action.Service.BACKEND,
|
||||
path = "/_dr/task/exportDomainLists",
|
||||
method = POST,
|
||||
auth = Auth.AUTH_ADMIN)
|
||||
|
||||
@@ -33,7 +33,6 @@ import google.registry.model.tld.Tld;
|
||||
import google.registry.model.tld.label.PremiumList.PremiumEntry;
|
||||
import google.registry.model.tld.label.PremiumListDao;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.Parameter;
|
||||
import google.registry.request.RequestParameters;
|
||||
import google.registry.request.Response;
|
||||
@@ -46,7 +45,7 @@ import java.util.SortedSet;
|
||||
|
||||
/** Action that exports the premium terms list for a TLD to Google Drive. */
|
||||
@Action(
|
||||
service = GaeService.BACKEND,
|
||||
service = Action.Service.BACKEND,
|
||||
path = "/_dr/task/exportPremiumTerms",
|
||||
method = POST,
|
||||
auth = Auth.AUTH_ADMIN)
|
||||
|
||||
@@ -25,7 +25,6 @@ import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.net.MediaType;
|
||||
import google.registry.model.tld.Tld;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.Parameter;
|
||||
import google.registry.request.RequestParameters;
|
||||
import google.registry.request.Response;
|
||||
@@ -35,7 +34,7 @@ import jakarta.inject.Inject;
|
||||
|
||||
/** Action that exports the publicly viewable reserved terms list for a TLD to Google Drive. */
|
||||
@Action(
|
||||
service = GaeService.BACKEND,
|
||||
service = Action.Service.BACKEND,
|
||||
path = "/_dr/task/exportReservedTerms",
|
||||
method = POST,
|
||||
auth = Auth.AUTH_ADMIN)
|
||||
|
||||
@@ -34,7 +34,6 @@ import google.registry.groups.GroupsConnection.Role;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarPoc;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.Response;
|
||||
import google.registry.request.auth.Auth;
|
||||
import google.registry.util.Retrier;
|
||||
@@ -53,7 +52,7 @@ import javax.annotation.Nullable;
|
||||
* <p>This uses the <a href="https://developers.google.com/admin-sdk/directory/">Directory API</a>.
|
||||
*/
|
||||
@Action(
|
||||
service = GaeService.BACKEND,
|
||||
service = Action.Service.BACKEND,
|
||||
path = "/_dr/task/syncGroupMembers",
|
||||
method = POST,
|
||||
auth = Auth.AUTH_ADMIN)
|
||||
|
||||
@@ -24,7 +24,6 @@ import static jakarta.servlet.http.HttpServletResponse.SC_OK;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.Parameter;
|
||||
import google.registry.request.Response;
|
||||
import google.registry.request.auth.Auth;
|
||||
@@ -55,7 +54,7 @@ import org.joda.time.Duration;
|
||||
* @see SyncRegistrarsSheet
|
||||
*/
|
||||
@Action(
|
||||
service = GaeService.BACKEND,
|
||||
service = Action.Service.BACKEND,
|
||||
path = SyncRegistrarsSheetAction.PATH,
|
||||
method = POST,
|
||||
auth = Auth.AUTH_ADMIN)
|
||||
|
||||
@@ -54,7 +54,6 @@ import google.registry.model.tld.label.ReservationType;
|
||||
import google.registry.monitoring.whitebox.CheckApiMetric;
|
||||
import google.registry.monitoring.whitebox.CheckApiMetric.Availability;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.Parameter;
|
||||
import google.registry.request.RequestParameters;
|
||||
import google.registry.request.Response;
|
||||
@@ -72,7 +71,7 @@ import org.joda.time.DateTime;
|
||||
* user controlled, lest it open an XSS vector. Do not modify this to return the domain name in the
|
||||
* response.
|
||||
*/
|
||||
@Action(service = GaeService.PUBAPI, path = "/check", auth = Auth.AUTH_PUBLIC)
|
||||
@Action(service = Action.Service.PUBAPI, path = "/check", auth = Auth.AUTH_PUBLIC)
|
||||
public class CheckApiAction implements Runnable {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
package google.registry.flows;
|
||||
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.Action.Method;
|
||||
import google.registry.request.Payload;
|
||||
import google.registry.request.auth.Auth;
|
||||
@@ -27,7 +26,7 @@ import jakarta.servlet.http.HttpServletRequest;
|
||||
* to RFC 5730. Commands must be requested via POST.
|
||||
*/
|
||||
@Action(
|
||||
service = GaeService.DEFAULT,
|
||||
service = Action.Service.FRONTEND,
|
||||
path = "/_dr/epp",
|
||||
method = Method.POST,
|
||||
auth = Auth.AUTH_ADMIN)
|
||||
|
||||
@@ -22,7 +22,6 @@ import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import google.registry.model.eppcommon.ProtocolDefinition;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.Action.Method;
|
||||
import google.registry.request.Parameter;
|
||||
import google.registry.request.auth.Auth;
|
||||
@@ -31,7 +30,7 @@ import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
/** Runs EPP commands directly without logging in, verifying an XSRF token from the tool. */
|
||||
@Action(
|
||||
service = GaeService.TOOLS,
|
||||
service = Action.Service.BACKEND,
|
||||
path = EppToolAction.PATH,
|
||||
method = Method.POST,
|
||||
auth = Auth.AUTH_ADMIN)
|
||||
|
||||
@@ -51,7 +51,7 @@ public class FlowReporter {
|
||||
@Inject Class<? extends Flow> flowClass;
|
||||
@Inject FlowReporter() {}
|
||||
|
||||
/** Records information about the current flow execution in the GAE request logs. */
|
||||
/** Records information about the current flow execution in the request logs. */
|
||||
public void recordToLogs() {
|
||||
// Explicitly log flow metadata separately from the EPP XML itself so that it stays compact
|
||||
// enough to be sure to fit in a single log entry (the XML part in rare cases could be long
|
||||
|
||||
@@ -73,7 +73,7 @@ public class FlowRunner {
|
||||
eppRequestSource,
|
||||
isDryRun ? "DRY_RUN" : "LIVE",
|
||||
isSuperuser ? "SUPERUSER" : "NORMAL");
|
||||
// Record flow info to the GAE request logs for reporting purposes if it's not a dry run.
|
||||
// Record flow info to the request logs for reporting purposes if it's not a dry run.
|
||||
if (!isDryRun) {
|
||||
flowReporter.recordToLogs();
|
||||
}
|
||||
|
||||
@@ -133,10 +133,9 @@ import org.joda.time.DateTime;
|
||||
@ReportingSpec(ActivityReportField.DOMAIN_TRANSFER_REQUEST)
|
||||
public final class DomainTransferRequestFlow implements MutatingFlow {
|
||||
|
||||
private static final ImmutableSet<StatusValue> DISALLOWED_STATUSES = ImmutableSet.of(
|
||||
StatusValue.CLIENT_TRANSFER_PROHIBITED,
|
||||
StatusValue.PENDING_DELETE,
|
||||
StatusValue.SERVER_TRANSFER_PROHIBITED);
|
||||
private static final ImmutableSet<StatusValue> NON_SUPERUSER_DISALLOWED_STATUSES =
|
||||
ImmutableSet.of(
|
||||
StatusValue.CLIENT_TRANSFER_PROHIBITED, StatusValue.SERVER_TRANSFER_PROHIBITED);
|
||||
|
||||
@Inject ResourceCommand resourceCommand;
|
||||
@Inject ExtensionManager extensionManager;
|
||||
@@ -299,8 +298,9 @@ public final class DomainTransferRequestFlow implements MutatingFlow {
|
||||
DateTime now,
|
||||
Optional<DomainTransferRequestSuperuserExtension> superuserExtension)
|
||||
throws EppException {
|
||||
verifyNoDisallowedStatuses(existingDomain, DISALLOWED_STATUSES);
|
||||
verifyNoDisallowedStatuses(existingDomain, ImmutableSet.of(StatusValue.PENDING_DELETE));
|
||||
if (!isSuperuser) {
|
||||
verifyNoDisallowedStatuses(existingDomain, NON_SUPERUSER_DISALLOWED_STATUSES);
|
||||
verifyAuthInfoPresentForResourceTransfer(authInfo);
|
||||
verifyAuthInfo(authInfo.get(), existingDomain);
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ import google.registry.model.eppinput.EppInput.Options;
|
||||
import google.registry.model.eppinput.EppInput.Services;
|
||||
import google.registry.model.eppoutput.EppResponse;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.util.PasswordUtils;
|
||||
import google.registry.util.StopwatchLogger;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Optional;
|
||||
@@ -150,8 +151,19 @@ public class LoginFlow implements MutatingFlow {
|
||||
throw new RegistrarAccountNotActiveException();
|
||||
}
|
||||
|
||||
if (login.getNewPassword().isPresent()) {
|
||||
String newPassword = login.getNewPassword().get();
|
||||
// TODO(b/458423787): Remove this circa March 2026 after enough time has passed for the logins
|
||||
// to have transitioned to Argon2 hashing.
|
||||
if (login.getNewPassword().isPresent()
|
||||
|| registrar.get().getCurrentHashAlgorithm(login.getPassword()).orElse(null)
|
||||
!= PasswordUtils.HashAlgorithm.ARGON_2_ID) {
|
||||
String newPassword =
|
||||
login
|
||||
.getNewPassword()
|
||||
.orElseGet(
|
||||
() -> {
|
||||
logger.atInfo().log("Rehashing existing registrar password with ARGON_2_ID");
|
||||
return login.getPassword();
|
||||
});
|
||||
// Load fresh from database (bypassing the cache) to ensure we don't save stale data.
|
||||
Optional<Registrar> freshRegistrar = Registrar.loadByRegistrarId(login.getClientId());
|
||||
stopwatch.tick("LoginFlow reload freshRegistrar");
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<version comment="Latin LGR">1</version>
|
||||
<date>2025-10-01</date>
|
||||
<language>und-Latn</language>
|
||||
<unicode-version>2</unicode-version>
|
||||
<unicode-version>11.1.0</unicode-version>
|
||||
<description type="text/html"><![CDATA[
|
||||
<div class="instructions">
|
||||
<h2>INSTRUCTIONS</h2>
|
||||
@@ -650,7 +650,6 @@
|
||||
<!--Action elements go here - order defines precedence-->
|
||||
<action disp="invalid" match="leading-combining-mark" comment="labels with leading combining marks are invalid ⍟" />
|
||||
<action disp="invalid" any-variant="out-of-repertoire-var" comment="any variant label with a code point out of repertoire is invalid ⍟" />
|
||||
<action disp="invalid" match="dot-L-dot" comment="labels with one L sharing two middle dots are invalid" />
|
||||
<action disp="blocked" any-variant="blocked" comment="any variant label containing blocked variants is blocked ⍟" />
|
||||
<action disp="allocatable" all-variants="allocatable" comment="variant labels with all variants allocatable are allocatable ⍟" />
|
||||
<action disp="allocatable" all-variants="fallback" comment="any label with all variants of type fallback is allocatable ⍟" />
|
||||
|
||||
@@ -44,7 +44,7 @@ public interface Keyring extends AutoCloseable {
|
||||
* Returns public key for encrypting escrow deposits being staged to cloud storage.
|
||||
*
|
||||
* <p>This adds an additional layer of security so cloud storage administrators won't be tempted
|
||||
* to go poking around the App Engine Cloud Console and see a dump of the entire database.
|
||||
* to go poking around the Pantheon Cloud Console and see a dump of the entire database.
|
||||
*
|
||||
* <p>This keypair should only be known to the domain registry shared registry system.
|
||||
*
|
||||
|
||||
@@ -28,7 +28,6 @@ import com.google.protobuf.Timestamp;
|
||||
import google.registry.batch.CloudTasksUtils;
|
||||
import google.registry.flows.EppToolAction;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.Parameter;
|
||||
import google.registry.request.auth.Auth;
|
||||
import google.registry.util.Clock;
|
||||
@@ -52,7 +51,7 @@ import org.joda.time.DateTime;
|
||||
* least one must be specified in order for load testing to do anything.
|
||||
*/
|
||||
@Action(
|
||||
service = GaeService.TOOLS,
|
||||
service = Action.Service.BACKEND,
|
||||
path = LoadTestAction.PATH,
|
||||
method = Action.Method.POST,
|
||||
automaticallyPrintOk = true,
|
||||
|
||||
@@ -76,7 +76,7 @@ public class Cursor extends UpdateAutoTimestampEntity {
|
||||
*
|
||||
* <p>The way we solve this problem is by having {@code RdeUploadAction} check this cursor
|
||||
* before performing an upload for a given TLD. If the cursor is less than two hours old, the
|
||||
* action will fail with a status code above 300 and App Engine will keep retrying the action
|
||||
* action will fail with a status code above 300 and Cloud Tasks will keep retrying the action
|
||||
* until it's ready.
|
||||
*/
|
||||
RDE_UPLOAD_SFTP(true),
|
||||
|
||||
@@ -37,6 +37,7 @@ import google.registry.tools.IamClient;
|
||||
import google.registry.tools.ServiceConnection;
|
||||
import google.registry.tools.server.UpdateUserGroupAction;
|
||||
import google.registry.util.PasswordUtils;
|
||||
import google.registry.util.PasswordUtils.HashAlgorithm;
|
||||
import google.registry.util.RegistryEnvironment;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Embeddable;
|
||||
@@ -229,6 +230,10 @@ public class User extends UpdateAutoTimestampEntity implements Buildable {
|
||||
|| isNullOrEmpty(registryLockPasswordHash)) {
|
||||
return false;
|
||||
}
|
||||
return getCurrentHashAlgorithm(registryLockPassword).isPresent();
|
||||
}
|
||||
|
||||
public Optional<HashAlgorithm> getCurrentHashAlgorithm(String registryLockPassword) {
|
||||
return PasswordUtils.verifyPassword(
|
||||
registryLockPassword, registryLockPasswordHash, registryLockPasswordSalt);
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ public class ProtocolDefinition {
|
||||
FEE_0_6(FeeCheckCommandExtensionV06.class, FeeCheckResponseExtensionV06.class, true),
|
||||
FEE_0_11(FeeCheckCommandExtensionV11.class, FeeCheckResponseExtensionV11.class, true),
|
||||
FEE_0_12(FeeCheckCommandExtensionV12.class, FeeCheckResponseExtensionV12.class, true),
|
||||
FEE_1_00(FeeCheckCommandExtensionStdV1.class, FeeCheckResponseExtensionStdV1.class, true),
|
||||
FEE_1_00(FeeCheckCommandExtensionStdV1.class, FeeCheckResponseExtensionStdV1.class, false),
|
||||
METADATA_1_0(MetadataExtension.class, null, false);
|
||||
|
||||
private final Class<? extends CommandExtension> commandExtensionClass;
|
||||
|
||||
@@ -67,6 +67,7 @@ import google.registry.persistence.converter.CurrencyToStringMapUserType;
|
||||
import google.registry.persistence.transaction.TransactionManager;
|
||||
import google.registry.util.CidrAddressBlock;
|
||||
import google.registry.util.PasswordUtils;
|
||||
import google.registry.util.PasswordUtils.HashAlgorithm;
|
||||
import jakarta.mail.internet.AddressException;
|
||||
import jakarta.mail.internet.InternetAddress;
|
||||
import jakarta.persistence.AttributeOverride;
|
||||
@@ -672,6 +673,10 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
|
||||
}
|
||||
|
||||
public boolean verifyPassword(String password) {
|
||||
return getCurrentHashAlgorithm(password).isPresent();
|
||||
}
|
||||
|
||||
public Optional<HashAlgorithm> getCurrentHashAlgorithm(String password) {
|
||||
return PasswordUtils.verifyPassword(password, passwordHash, salt);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,8 +19,7 @@ import static jakarta.servlet.http.HttpServletResponse.SC_OK;
|
||||
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.Action.GkeService;
|
||||
import google.registry.request.Action.Service;
|
||||
import google.registry.request.auth.Auth;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
@@ -53,8 +52,7 @@ public class ReadinessProbeAction implements Runnable {
|
||||
}
|
||||
|
||||
@Action(
|
||||
service = GaeService.DEFAULT,
|
||||
gkeService = GkeService.CONSOLE,
|
||||
service = Service.CONSOLE,
|
||||
path = ReadinessProbeConsoleAction.PATH,
|
||||
auth = Auth.AUTH_PUBLIC)
|
||||
public static class ReadinessProbeConsoleAction extends ReadinessProbeAction {
|
||||
@@ -66,11 +64,7 @@ public class ReadinessProbeAction implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
@Action(
|
||||
service = GaeService.PUBAPI,
|
||||
gkeService = GkeService.PUBAPI,
|
||||
path = ReadinessProbeActionPubApi.PATH,
|
||||
auth = Auth.AUTH_PUBLIC)
|
||||
@Action(service = Service.PUBAPI, path = ReadinessProbeActionPubApi.PATH, auth = Auth.AUTH_PUBLIC)
|
||||
public static class ReadinessProbeActionPubApi extends ReadinessProbeAction {
|
||||
public static final String PATH = "/ready/pubapi";
|
||||
|
||||
@@ -81,8 +75,7 @@ public class ReadinessProbeAction implements Runnable {
|
||||
}
|
||||
|
||||
@Action(
|
||||
service = GaeService.DEFAULT,
|
||||
gkeService = GkeService.FRONTEND,
|
||||
service = Service.FRONTEND,
|
||||
path = ReadinessProbeActionFrontend.PATH,
|
||||
auth = Auth.AUTH_PUBLIC)
|
||||
public static final class ReadinessProbeActionFrontend extends ReadinessProbeAction {
|
||||
|
||||
@@ -40,6 +40,7 @@ import google.registry.keyring.api.KeyModule;
|
||||
import google.registry.module.RegistryComponent.RegistryModule;
|
||||
import google.registry.module.RequestComponent.RequestComponentModule;
|
||||
import google.registry.monitoring.whitebox.StackdriverModule;
|
||||
import google.registry.mosapi.module.MosApiModule;
|
||||
import google.registry.persistence.PersistenceModule;
|
||||
import google.registry.privileges.secretmanager.SecretManagerModule;
|
||||
import google.registry.rde.JSchModule;
|
||||
@@ -71,6 +72,7 @@ import jakarta.inject.Singleton;
|
||||
GroupsModule.class,
|
||||
GroupssettingsModule.class,
|
||||
GsonModule.class,
|
||||
MosApiModule.class,
|
||||
JSchModule.class,
|
||||
KeyModule.class,
|
||||
KeyringModule.class,
|
||||
|
||||
@@ -28,7 +28,7 @@ import java.util.concurrent.TimeoutException;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** Base for Servlets that handle all requests to our App Engine modules. */
|
||||
/** Base for Servlets that handle all requests to our modules. */
|
||||
public class ServletBase extends HttpServlet {
|
||||
|
||||
private final RequestHandler<?> requestHandler;
|
||||
|
||||
@@ -1,193 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.module.backend;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Subcomponent;
|
||||
import google.registry.batch.BatchModule;
|
||||
import google.registry.batch.CannedScriptExecutionAction;
|
||||
import google.registry.batch.DeleteExpiredDomainsAction;
|
||||
import google.registry.batch.DeleteLoadTestDataAction;
|
||||
import google.registry.batch.DeleteProberDataAction;
|
||||
import google.registry.batch.ExpandBillingRecurrencesAction;
|
||||
import google.registry.batch.RelockDomainAction;
|
||||
import google.registry.batch.RemoveAllDomainContactsAction;
|
||||
import google.registry.batch.ResaveAllEppResourcesPipelineAction;
|
||||
import google.registry.batch.ResaveEntityAction;
|
||||
import google.registry.batch.SendExpiringCertificateNotificationEmailAction;
|
||||
import google.registry.batch.WipeOutContactHistoryPiiAction;
|
||||
import google.registry.cron.CronModule;
|
||||
import google.registry.cron.TldFanoutAction;
|
||||
import google.registry.dns.DnsModule;
|
||||
import google.registry.dns.PublishDnsUpdatesAction;
|
||||
import google.registry.dns.ReadDnsRefreshRequestsAction;
|
||||
import google.registry.dns.RefreshDnsAction;
|
||||
import google.registry.dns.RefreshDnsOnHostRenameAction;
|
||||
import google.registry.dns.writer.DnsWritersModule;
|
||||
import google.registry.dns.writer.dnsupdate.DnsUpdateConfigModule;
|
||||
import google.registry.export.ExportDomainListsAction;
|
||||
import google.registry.export.ExportPremiumTermsAction;
|
||||
import google.registry.export.ExportReservedTermsAction;
|
||||
import google.registry.export.SyncGroupMembersAction;
|
||||
import google.registry.export.sheet.SheetModule;
|
||||
import google.registry.export.sheet.SyncRegistrarsSheetAction;
|
||||
import google.registry.flows.FlowComponent;
|
||||
import google.registry.flows.custom.CustomLogicModule;
|
||||
import google.registry.monitoring.whitebox.WhiteboxModule;
|
||||
import google.registry.rdap.UpdateRegistrarRdapBaseUrlsAction;
|
||||
import google.registry.rde.BrdaCopyAction;
|
||||
import google.registry.rde.RdeModule;
|
||||
import google.registry.rde.RdeReportAction;
|
||||
import google.registry.rde.RdeReporter;
|
||||
import google.registry.rde.RdeStagingAction;
|
||||
import google.registry.rde.RdeUploadAction;
|
||||
import google.registry.reporting.ReportingModule;
|
||||
import google.registry.reporting.billing.BillingModule;
|
||||
import google.registry.reporting.billing.CopyDetailReportsAction;
|
||||
import google.registry.reporting.billing.GenerateInvoicesAction;
|
||||
import google.registry.reporting.billing.PublishInvoicesAction;
|
||||
import google.registry.reporting.icann.DnsCountQueryCoordinator.DnsCountQueryCoordinatorModule;
|
||||
import google.registry.reporting.icann.IcannReportingModule;
|
||||
import google.registry.reporting.icann.IcannReportingStagingAction;
|
||||
import google.registry.reporting.icann.IcannReportingUploadAction;
|
||||
import google.registry.reporting.spec11.GenerateSpec11ReportAction;
|
||||
import google.registry.reporting.spec11.PublishSpec11ReportAction;
|
||||
import google.registry.reporting.spec11.Spec11Module;
|
||||
import google.registry.request.RequestComponentBuilder;
|
||||
import google.registry.request.RequestModule;
|
||||
import google.registry.request.RequestScope;
|
||||
import google.registry.tmch.NordnUploadAction;
|
||||
import google.registry.tmch.NordnVerifyAction;
|
||||
import google.registry.tmch.TmchCrlAction;
|
||||
import google.registry.tmch.TmchDnlAction;
|
||||
import google.registry.tmch.TmchModule;
|
||||
import google.registry.tmch.TmchSmdrlAction;
|
||||
|
||||
/** Dagger component with per-request lifetime for "backend" App Engine module. */
|
||||
@RequestScope
|
||||
@Subcomponent(
|
||||
modules = {
|
||||
BatchModule.class,
|
||||
BillingModule.class,
|
||||
CronModule.class,
|
||||
CustomLogicModule.class,
|
||||
DnsCountQueryCoordinatorModule.class,
|
||||
DnsModule.class,
|
||||
DnsUpdateConfigModule.class,
|
||||
DnsWritersModule.class,
|
||||
IcannReportingModule.class,
|
||||
RdeModule.class,
|
||||
ReportingModule.class,
|
||||
RequestModule.class,
|
||||
SheetModule.class,
|
||||
Spec11Module.class,
|
||||
TmchModule.class,
|
||||
WhiteboxModule.class,
|
||||
})
|
||||
public interface BackendRequestComponent {
|
||||
|
||||
BrdaCopyAction brdaCopyAction();
|
||||
|
||||
CannedScriptExecutionAction cannedScriptExecutionAction();
|
||||
|
||||
CopyDetailReportsAction copyDetailReportAction();
|
||||
|
||||
DeleteExpiredDomainsAction deleteExpiredDomainsAction();
|
||||
|
||||
DeleteLoadTestDataAction deleteLoadTestDataAction();
|
||||
|
||||
DeleteProberDataAction deleteProberDataAction();
|
||||
|
||||
ExpandBillingRecurrencesAction expandBillingRecurrencesAction();
|
||||
|
||||
ExportDomainListsAction exportDomainListsAction();
|
||||
|
||||
ExportPremiumTermsAction exportPremiumTermsAction();
|
||||
|
||||
ExportReservedTermsAction exportReservedTermsAction();
|
||||
|
||||
FlowComponent.Builder flowComponentBuilder();
|
||||
|
||||
GenerateInvoicesAction generateInvoicesAction();
|
||||
|
||||
GenerateSpec11ReportAction generateSpec11ReportAction();
|
||||
|
||||
IcannReportingStagingAction icannReportingStagingAction();
|
||||
|
||||
IcannReportingUploadAction icannReportingUploadAction();
|
||||
|
||||
NordnUploadAction nordnUploadAction();
|
||||
|
||||
NordnVerifyAction nordnVerifyAction();
|
||||
|
||||
PublishDnsUpdatesAction publishDnsUpdatesAction();
|
||||
|
||||
PublishInvoicesAction uploadInvoicesAction();
|
||||
|
||||
PublishSpec11ReportAction publishSpec11ReportAction();
|
||||
|
||||
ReadDnsRefreshRequestsAction readDnsRefreshRequestsAction();
|
||||
|
||||
RdeReportAction rdeReportAction();
|
||||
|
||||
RdeStagingAction rdeStagingAction();
|
||||
|
||||
RdeUploadAction rdeUploadAction();
|
||||
|
||||
RdeReporter rdeReporter();
|
||||
|
||||
RefreshDnsAction refreshDnsAction();
|
||||
|
||||
RefreshDnsOnHostRenameAction refreshDnsOnHostRenameAction();
|
||||
|
||||
RelockDomainAction relockDomainAction();
|
||||
|
||||
RemoveAllDomainContactsAction removeAllDomainContactsAction();
|
||||
|
||||
ResaveAllEppResourcesPipelineAction resaveAllEppResourcesPipelineAction();
|
||||
|
||||
ResaveEntityAction resaveEntityAction();
|
||||
|
||||
SendExpiringCertificateNotificationEmailAction sendExpiringCertificateNotificationEmailAction();
|
||||
|
||||
SyncGroupMembersAction syncGroupMembersAction();
|
||||
|
||||
SyncRegistrarsSheetAction syncRegistrarsSheetAction();
|
||||
|
||||
TldFanoutAction tldFanoutAction();
|
||||
|
||||
TmchCrlAction tmchCrlAction();
|
||||
|
||||
TmchDnlAction tmchDnlAction();
|
||||
|
||||
TmchSmdrlAction tmchSmdrlAction();
|
||||
|
||||
UpdateRegistrarRdapBaseUrlsAction updateRegistrarRdapBaseUrlsAction();
|
||||
|
||||
WipeOutContactHistoryPiiAction wipeOutContactHistoryPiiAction();
|
||||
|
||||
@Subcomponent.Builder
|
||||
abstract class Builder implements RequestComponentBuilder<BackendRequestComponent> {
|
||||
|
||||
@Override
|
||||
public abstract Builder requestModule(RequestModule requestModule);
|
||||
|
||||
@Override
|
||||
public abstract BackendRequestComponent build();
|
||||
}
|
||||
|
||||
@Module(subcomponents = BackendRequestComponent.class)
|
||||
class BackendRequestComponentModule {}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.module.backend;
|
||||
|
||||
import com.google.monitoring.metrics.MetricReporter;
|
||||
import dagger.Lazy;
|
||||
import google.registry.module.ServletBase;
|
||||
|
||||
/** Servlet that should handle all requests to our "backend" App Engine module. */
|
||||
public final class BackendServlet extends ServletBase {
|
||||
|
||||
private static final BackendComponent component = DaggerBackendComponent.create();
|
||||
private static final BackendRequestHandler requestHandler = component.requestHandler();
|
||||
private static final Lazy<MetricReporter> metricReporter = component.metricReporter();
|
||||
|
||||
public BackendServlet() {
|
||||
super(requestHandler, metricReporter);
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.module.frontend;
|
||||
|
||||
import com.google.monitoring.metrics.MetricReporter;
|
||||
import dagger.Component;
|
||||
import dagger.Lazy;
|
||||
import google.registry.config.CloudTasksUtilsModule;
|
||||
import google.registry.config.CredentialModule;
|
||||
import google.registry.config.RegistryConfig.ConfigModule;
|
||||
import google.registry.flows.ServerTridProviderModule;
|
||||
import google.registry.flows.custom.CustomLogicFactoryModule;
|
||||
import google.registry.flows.domain.DomainDeletionTimeCacheModule;
|
||||
import google.registry.groups.DirectoryModule;
|
||||
import google.registry.groups.GmailModule;
|
||||
import google.registry.groups.GroupsModule;
|
||||
import google.registry.groups.GroupssettingsModule;
|
||||
import google.registry.keyring.KeyringModule;
|
||||
import google.registry.keyring.api.KeyModule;
|
||||
import google.registry.module.frontend.FrontendRequestComponent.FrontendRequestComponentModule;
|
||||
import google.registry.monitoring.whitebox.StackdriverModule;
|
||||
import google.registry.privileges.secretmanager.SecretManagerModule;
|
||||
import google.registry.request.Modules.GsonModule;
|
||||
import google.registry.request.Modules.NetHttpTransportModule;
|
||||
import google.registry.request.auth.AuthModule;
|
||||
import google.registry.util.UtilsModule;
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
/** Dagger component with instance lifetime for "default" App Engine module. */
|
||||
@Singleton
|
||||
@Component(
|
||||
modules = {
|
||||
AuthModule.class,
|
||||
CloudTasksUtilsModule.class,
|
||||
ConfigModule.class,
|
||||
CredentialModule.class,
|
||||
CustomLogicFactoryModule.class,
|
||||
CloudTasksUtilsModule.class,
|
||||
DirectoryModule.class,
|
||||
DomainDeletionTimeCacheModule.class,
|
||||
FrontendRequestComponentModule.class,
|
||||
GmailModule.class,
|
||||
GroupsModule.class,
|
||||
GroupssettingsModule.class,
|
||||
GsonModule.class,
|
||||
KeyModule.class,
|
||||
KeyringModule.class,
|
||||
NetHttpTransportModule.class,
|
||||
SecretManagerModule.class,
|
||||
ServerTridProviderModule.class,
|
||||
StackdriverModule.class,
|
||||
UtilsModule.class
|
||||
})
|
||||
interface FrontendComponent {
|
||||
FrontendRequestHandler requestHandler();
|
||||
|
||||
Lazy<MetricReporter> metricReporter();
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.module.frontend;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Subcomponent;
|
||||
import google.registry.batch.BatchModule;
|
||||
import google.registry.dns.DnsModule;
|
||||
import google.registry.flows.EppTlsAction;
|
||||
import google.registry.flows.FlowComponent;
|
||||
import google.registry.flows.TlsCredentials.EppTlsModule;
|
||||
import google.registry.module.ReadinessProbeAction.ReadinessProbeActionFrontend;
|
||||
import google.registry.module.ReadinessProbeAction.ReadinessProbeConsoleAction;
|
||||
import google.registry.monitoring.whitebox.WhiteboxModule;
|
||||
import google.registry.request.RequestComponentBuilder;
|
||||
import google.registry.request.RequestModule;
|
||||
import google.registry.request.RequestScope;
|
||||
import google.registry.ui.server.console.ConsoleDomainGetAction;
|
||||
import google.registry.ui.server.console.ConsoleDomainListAction;
|
||||
import google.registry.ui.server.console.ConsoleDumDownloadAction;
|
||||
import google.registry.ui.server.console.ConsoleEppPasswordAction;
|
||||
import google.registry.ui.server.console.ConsoleModule;
|
||||
import google.registry.ui.server.console.ConsoleOteAction;
|
||||
import google.registry.ui.server.console.ConsoleRegistryLockAction;
|
||||
import google.registry.ui.server.console.ConsoleRegistryLockVerifyAction;
|
||||
import google.registry.ui.server.console.ConsoleUpdateRegistrarAction;
|
||||
import google.registry.ui.server.console.ConsoleUserDataAction;
|
||||
import google.registry.ui.server.console.ConsoleUsersAction;
|
||||
import google.registry.ui.server.console.PasswordResetRequestAction;
|
||||
import google.registry.ui.server.console.PasswordResetVerifyAction;
|
||||
import google.registry.ui.server.console.RegistrarsAction;
|
||||
import google.registry.ui.server.console.domains.ConsoleBulkDomainAction;
|
||||
import google.registry.ui.server.console.settings.ContactAction;
|
||||
import google.registry.ui.server.console.settings.RdapRegistrarFieldsAction;
|
||||
import google.registry.ui.server.console.settings.SecurityAction;
|
||||
|
||||
/** Dagger component with per-request lifetime for "default" App Engine module. */
|
||||
@RequestScope
|
||||
@Subcomponent(
|
||||
modules = {
|
||||
BatchModule.class,
|
||||
DnsModule.class,
|
||||
EppTlsModule.class,
|
||||
ConsoleModule.class,
|
||||
RequestModule.class,
|
||||
WhiteboxModule.class,
|
||||
})
|
||||
public interface FrontendRequestComponent {
|
||||
ConsoleBulkDomainAction consoleBulkDomainAction();
|
||||
|
||||
ConsoleDomainGetAction consoleDomainGetAction();
|
||||
|
||||
ConsoleDomainListAction consoleDomainListAction();
|
||||
|
||||
ConsoleEppPasswordAction consoleEppPasswordAction();
|
||||
|
||||
ConsoleOteAction consoleOteAction();
|
||||
|
||||
ConsoleRegistryLockAction consoleRegistryLockAction();
|
||||
|
||||
ConsoleRegistryLockVerifyAction consoleRegistryLockVerifyAction();
|
||||
|
||||
ConsoleUpdateRegistrarAction consoleUpdateRegistrarAction();
|
||||
|
||||
ConsoleUserDataAction consoleUserDataAction();
|
||||
|
||||
ConsoleUsersAction consoleUsersAction();
|
||||
|
||||
ConsoleDumDownloadAction consoleDumDownloadAction();
|
||||
|
||||
ContactAction contactAction();
|
||||
|
||||
EppTlsAction eppTlsAction();
|
||||
|
||||
FlowComponent.Builder flowComponentBuilder();
|
||||
|
||||
PasswordResetRequestAction passwordResetRequestAction();
|
||||
|
||||
PasswordResetVerifyAction passwordResetVerifyAction();
|
||||
|
||||
RdapRegistrarFieldsAction rdapRegistrarFieldsAction();
|
||||
|
||||
ReadinessProbeActionFrontend readinessProbeActionFrontend();
|
||||
|
||||
ReadinessProbeConsoleAction readinessProbeConsoleAction();
|
||||
|
||||
RegistrarsAction registrarsAction();
|
||||
|
||||
SecurityAction securityAction();
|
||||
|
||||
@Subcomponent.Builder
|
||||
abstract class Builder implements RequestComponentBuilder<FrontendRequestComponent> {
|
||||
@Override public abstract Builder requestModule(RequestModule requestModule);
|
||||
@Override public abstract FrontendRequestComponent build();
|
||||
}
|
||||
|
||||
@Module(subcomponents = FrontendRequestComponent.class)
|
||||
class FrontendRequestComponentModule {}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.module.frontend;
|
||||
|
||||
import google.registry.request.RequestHandler;
|
||||
import google.registry.request.auth.RequestAuthenticator;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Provider;
|
||||
|
||||
/** Request handler for the frontend module. */
|
||||
public class FrontendRequestHandler extends RequestHandler<FrontendRequestComponent> {
|
||||
|
||||
@Inject FrontendRequestHandler(
|
||||
Provider<FrontendRequestComponent.Builder> componentBuilderProvider,
|
||||
RequestAuthenticator requestAuthenticator) {
|
||||
super(componentBuilderProvider, requestAuthenticator);
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.module.frontend;
|
||||
|
||||
import com.google.monitoring.metrics.MetricReporter;
|
||||
import dagger.Lazy;
|
||||
import google.registry.module.ServletBase;
|
||||
|
||||
/** Servlet that should handle all requests to our "default" App Engine module. */
|
||||
public final class FrontendServlet extends ServletBase {
|
||||
|
||||
private static final FrontendComponent component = DaggerFrontendComponent.create();
|
||||
private static final FrontendRequestHandler requestHandler = component.requestHandler();
|
||||
private static final Lazy<MetricReporter> metricReporter = component.metricReporter();
|
||||
|
||||
public FrontendServlet() {
|
||||
super(requestHandler, metricReporter);
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,6 @@ package google.registry.monitoring.whitebox;
|
||||
|
||||
import com.google.api.services.monitoring.v3.Monitoring;
|
||||
import com.google.api.services.monitoring.v3.model.MonitoredResource;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import com.google.monitoring.metrics.MetricReporter;
|
||||
import com.google.monitoring.metrics.MetricWriter;
|
||||
@@ -29,7 +28,6 @@ import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.util.Clock;
|
||||
import google.registry.util.GoogleCredentialsBundle;
|
||||
import google.registry.util.MetricParameters;
|
||||
import google.registry.util.RegistryEnvironment;
|
||||
import jakarta.inject.Named;
|
||||
import jakarta.inject.Singleton;
|
||||
import org.joda.time.Duration;
|
||||
@@ -40,13 +38,9 @@ public final class StackdriverModule {
|
||||
|
||||
private StackdriverModule() {}
|
||||
|
||||
// We need a fake GCE zone to appease Stackdriver's resource model.
|
||||
// TODO(b/265973059): Switch to resource type "gke_container".
|
||||
private static final String SPOOFED_GCE_ZONE = "us-central1-f";
|
||||
|
||||
// We cannot use a static fake intance ID which is shared by all instances, because metrics might
|
||||
// be flushed to stackdriver with delays, which lead to time inversion erros when another instance
|
||||
// has already written a data point at a later time.
|
||||
// We cannot use a static fake instance ID which is shared by all instances, because metrics might
|
||||
// be flushed to stackdriver with delays, which lead to time inversion errors when another
|
||||
// instance has already written a data point at a later time.
|
||||
@Singleton
|
||||
@Provides
|
||||
@Named("spoofedGceInstanceId")
|
||||
@@ -72,23 +66,11 @@ public final class StackdriverModule {
|
||||
Lazy<MetricParameters> gkeParameters,
|
||||
@Config("projectId") String projectId,
|
||||
@Config("stackdriverMaxQps") int maxQps,
|
||||
@Config("stackdriverMaxPointsPerRequest") int maxPointsPerRequest,
|
||||
@Named("spoofedGceInstanceId") String instanceId) {
|
||||
@Config("stackdriverMaxPointsPerRequest") int maxPointsPerRequest) {
|
||||
MonitoredResource resource =
|
||||
RegistryEnvironment.isOnJetty()
|
||||
? new MonitoredResource()
|
||||
.setType("gke_container")
|
||||
.setLabels(gkeParameters.get().makeLabelsMap())
|
||||
:
|
||||
// The MonitoredResource for GAE apps is not writable (and missing fields anyway) so we
|
||||
// just use the gce_instance resource type instead.
|
||||
new MonitoredResource()
|
||||
.setType("gce_instance")
|
||||
.setLabels(
|
||||
ImmutableMap.of(
|
||||
// The "zone" field MUST be a valid GCE zone, so we fake one.
|
||||
"zone", SPOOFED_GCE_ZONE, "instance_id", instanceId));
|
||||
|
||||
new MonitoredResource()
|
||||
.setType("gke_container")
|
||||
.setLabels(gkeParameters.get().makeLabelsMap());
|
||||
return new StackdriverWriter(
|
||||
monitoringClient, projectId, resource, maxQps, maxPointsPerRequest);
|
||||
}
|
||||
|
||||
150
core/src/main/java/google/registry/mosapi/MosApiClient.java
Normal file
150
core/src/main/java/google/registry/mosapi/MosApiClient.java
Normal file
@@ -0,0 +1,150 @@
|
||||
// Copyright 2025 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.mosapi;
|
||||
|
||||
import static org.apache.beam.sdk.util.Preconditions.checkArgumentNotNull;
|
||||
|
||||
import com.google.common.base.Throwables;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.mosapi.MosApiException.MosApiAuthorizationException;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
import jakarta.inject.Singleton;
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.util.Map;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
|
||||
@Singleton
|
||||
public class MosApiClient {
|
||||
|
||||
private final OkHttpClient httpClient;
|
||||
private final String baseUrl;
|
||||
|
||||
@Inject
|
||||
public MosApiClient(
|
||||
@Named("mosapiHttpClient") OkHttpClient httpClient,
|
||||
@Config("mosapiServiceUrl") String mosapiUrl,
|
||||
@Config("mosapiEntityType") String entityType) {
|
||||
this.httpClient = httpClient;
|
||||
// Pre-calculate base URL and validate it to fail fast on bad config
|
||||
String fullUrl = String.format("%s/%s", mosapiUrl, entityType);
|
||||
checkArgumentNotNull(
|
||||
HttpUrl.parse(fullUrl), "Invalid MoSAPI Service URL configuration: %s", fullUrl);
|
||||
|
||||
this.baseUrl = fullUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a GET request to the specified MoSAPI endpoint.
|
||||
*
|
||||
* @param entityId The TLD or registrar ID the request is for.
|
||||
* @param endpoint The specific API endpoint path (e.g., "v2/monitoring/state").
|
||||
* @param params A map of query parameters to be URL-encoded and appended to the request.
|
||||
* @param headers A map of HTTP headers to be included in the request.
|
||||
* @return The {@link Response} from the server if the request is successful. <b>The caller is
|
||||
* responsible for closing this response.</b>
|
||||
* @throws MosApiException if the request fails due to a network error or an unhandled HTTP
|
||||
* status.
|
||||
* @throws MosApiAuthorizationException if the server returns a 401 Unauthorized status.
|
||||
*/
|
||||
public Response sendGetRequest(
|
||||
String entityId, String endpoint, Map<String, String> params, Map<String, String> headers)
|
||||
throws MosApiException {
|
||||
HttpUrl url = buildUri(entityId, endpoint, params);
|
||||
Request.Builder requestBuilder = new Request.Builder().url(url).get();
|
||||
headers.forEach(requestBuilder::addHeader);
|
||||
try {
|
||||
Response response = httpClient.newCall(requestBuilder.build()).execute();
|
||||
return checkResponseForAuthError(response);
|
||||
} catch (RuntimeException | IOException e) {
|
||||
// Check if it's the specific authorization exception (re-thrown or caught here)
|
||||
Throwables.throwIfInstanceOf(e, MosApiAuthorizationException.class);
|
||||
// Otherwise, treat as a generic connection/API error
|
||||
throw new MosApiException("Error during GET request to " + url, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a POST request to the specified MoSAPI endpoint.
|
||||
*
|
||||
* <p><b>Note:</b> This method is for future use. There are currently no MoSAPI endpoints in the
|
||||
* project scope that require a POST request.
|
||||
*
|
||||
* @param entityId The TLD or registrar ID the request is for.
|
||||
* @param endpoint The specific API endpoint path.
|
||||
* @param params A map of query parameters to be URL-encoded.
|
||||
* @param headers A map of HTTP headers to be included in the request.
|
||||
* @param body The request body to be sent with the POST request.
|
||||
* @return The {@link Response} from the server. <b>The caller is responsible for closing this
|
||||
* response.</b>
|
||||
* @throws MosApiException if the request fails.
|
||||
* @throws MosApiAuthorizationException if the server returns a 401 Unauthorized status.
|
||||
*/
|
||||
public Response sendPostRequest(
|
||||
String entityId,
|
||||
String endpoint,
|
||||
Map<String, String> params,
|
||||
Map<String, String> headers,
|
||||
String body)
|
||||
throws MosApiException {
|
||||
HttpUrl url = buildUri(entityId, endpoint, params);
|
||||
RequestBody requestBody = RequestBody.create(body, MediaType.parse("application/json"));
|
||||
|
||||
Request.Builder requestBuilder = new Request.Builder().url(url).post(requestBody);
|
||||
headers.forEach(requestBuilder::addHeader);
|
||||
try {
|
||||
Response response = httpClient.newCall(requestBuilder.build()).execute();
|
||||
return checkResponseForAuthError(response);
|
||||
} catch (RuntimeException | IOException e) {
|
||||
// Check if it's the specific authorization exception (re-thrown or caught here)
|
||||
Throwables.throwIfInstanceOf(e, MosApiAuthorizationException.class);
|
||||
// Otherwise, treat as a generic connection/API error
|
||||
throw new MosApiException("Error during POST request to " + url, e);
|
||||
}
|
||||
}
|
||||
|
||||
private Response checkResponseForAuthError(Response response)
|
||||
throws MosApiAuthorizationException {
|
||||
if (response.code() == HttpURLConnection.HTTP_UNAUTHORIZED) {
|
||||
response.close();
|
||||
throw new MosApiAuthorizationException(
|
||||
"Authorization failed for the requested resource. The client certificate may not be"
|
||||
+ " authorized for the specified TLD or Registrar.");
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the full URL for a request, including the base URL, entityId, path, and query params.
|
||||
*/
|
||||
private HttpUrl buildUri(String entityId, String path, Map<String, String> queryParams) {
|
||||
String sanitizedPath = path.startsWith("/") ? path.substring(1) : path;
|
||||
|
||||
// We can safely use get() here because we validated baseUrl in the constructor
|
||||
HttpUrl.Builder urlBuilder =
|
||||
HttpUrl.get(baseUrl).newBuilder().addPathSegment(entityId).addPathSegments(sanitizedPath);
|
||||
|
||||
if (queryParams != null) {
|
||||
queryParams.forEach(urlBuilder::addQueryParameter);
|
||||
}
|
||||
return urlBuilder.build();
|
||||
}
|
||||
}
|
||||
110
core/src/main/java/google/registry/mosapi/MosApiException.java
Normal file
110
core/src/main/java/google/registry/mosapi/MosApiException.java
Normal file
@@ -0,0 +1,110 @@
|
||||
// Copyright 2025 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.mosapi;
|
||||
|
||||
import static java.lang.annotation.ElementType.TYPE;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
import google.registry.mosapi.model.MosApiErrorResponse;
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.Optional;
|
||||
|
||||
/** Custom exception for MoSAPI client errors. */
|
||||
public class MosApiException extends IOException {
|
||||
|
||||
private final MosApiErrorResponse errorResponse;
|
||||
|
||||
public MosApiException(MosApiErrorResponse errorResponse) {
|
||||
super(
|
||||
String.format(
|
||||
"MoSAPI returned an error (code: %s): %s",
|
||||
errorResponse.resultCode(), errorResponse.message()));
|
||||
this.errorResponse = errorResponse;
|
||||
}
|
||||
|
||||
public MosApiException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.errorResponse = null;
|
||||
}
|
||||
|
||||
public Optional<MosApiErrorResponse> getErrorResponse() {
|
||||
return Optional.ofNullable(errorResponse);
|
||||
}
|
||||
|
||||
/** Annotation for associating a MoSAPI result code with an exception subclass. */
|
||||
@Documented
|
||||
@Retention(RUNTIME)
|
||||
@Target(TYPE)
|
||||
public @interface MosApiResultCode {
|
||||
String value();
|
||||
}
|
||||
|
||||
/** Thrown when MoSAPI returns a 401 Unauthorized error. */
|
||||
public static class MosApiAuthorizationException extends MosApiException {
|
||||
public MosApiAuthorizationException(String message) {
|
||||
super(message, null);
|
||||
}
|
||||
}
|
||||
|
||||
/** Creates a specific exception based on the MoSAPI error response. */
|
||||
public static MosApiException create(MosApiErrorResponse errorResponse) {
|
||||
Optional<MosApiResponse> responseEnum = MosApiResponse.fromCode(errorResponse.resultCode());
|
||||
if (responseEnum.isPresent()) {
|
||||
return switch (responseEnum.get()) {
|
||||
case DATE_DURATION_INVALID -> new DateDurationInvalidException(errorResponse);
|
||||
case DATE_ORDER_INVALID -> new DateOrderInvalidException(errorResponse);
|
||||
case START_DATE_SYNTAX_INVALID -> new StartDateSyntaxInvalidException(errorResponse);
|
||||
case END_DATE_SYNTAX_INVALID -> new EndDateSyntaxInvalidException(errorResponse);
|
||||
default -> new MosApiException(errorResponse);
|
||||
};
|
||||
}
|
||||
return new MosApiException(errorResponse);
|
||||
}
|
||||
|
||||
/** Thrown when the date duration in a MoSAPI request is invalid. */
|
||||
@MosApiResultCode("2011")
|
||||
public static class DateDurationInvalidException extends MosApiException {
|
||||
public DateDurationInvalidException(MosApiErrorResponse errorResponse) {
|
||||
super(errorResponse);
|
||||
}
|
||||
}
|
||||
|
||||
/** Thrown when the date order in a MoSAPI request is invalid. */
|
||||
@MosApiResultCode("2012")
|
||||
public static class DateOrderInvalidException extends MosApiException {
|
||||
public DateOrderInvalidException(MosApiErrorResponse errorResponse) {
|
||||
super(errorResponse);
|
||||
}
|
||||
}
|
||||
|
||||
/** Thrown when the startDate syntax in a MoSAPI request is invalid. */
|
||||
@MosApiResultCode("2013")
|
||||
public static class StartDateSyntaxInvalidException extends MosApiException {
|
||||
public StartDateSyntaxInvalidException(MosApiErrorResponse errorResponse) {
|
||||
super(errorResponse);
|
||||
}
|
||||
}
|
||||
|
||||
/** Thrown when the endDate syntax in a MoSAPI request is invalid. */
|
||||
@MosApiResultCode("2014")
|
||||
public static class EndDateSyntaxInvalidException extends MosApiException {
|
||||
public EndDateSyntaxInvalidException(MosApiErrorResponse errorResponse) {
|
||||
super(errorResponse);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// Copyright 2024 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.mosapi;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Represents known MoSAPI API result codes and their default messages.
|
||||
*
|
||||
* <p>The definitions for these codes can be found in the official ICANN MoSAPI Specification,
|
||||
* specifically in the 'Result Codes' section.
|
||||
*
|
||||
* @see <a href="https://www.icann.org/mosapi-specification.pdf">ICANN MoSAPI Specification</a>
|
||||
*/
|
||||
public enum MosApiResponse {
|
||||
DATE_DURATION_INVALID(
|
||||
"2011", "The difference between endDate and startDate is " + "more than 31 days"),
|
||||
DATE_ORDER_INVALID("2012", "The EndDate is before startDate"),
|
||||
START_DATE_SYNTAX_INVALID("2013", "StartDate syntax is invalid"),
|
||||
END_DATE_SYNTAX_INVALID("2014", "EndDate syntax is invalid");
|
||||
|
||||
private final String code;
|
||||
private final String defaultMessage;
|
||||
|
||||
private static final Map<String, MosApiResponse> CODE_MAP =
|
||||
Arrays.stream(values()).collect(Collectors.toMap(e -> e.code, Function.identity()));
|
||||
|
||||
MosApiResponse(String code, String defaultMessage) {
|
||||
this.code = code;
|
||||
this.defaultMessage = defaultMessage;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getDefaultMessage() {
|
||||
return defaultMessage;
|
||||
}
|
||||
|
||||
// Returns the enum constant associated with the given result code string
|
||||
public static Optional<MosApiResponse> fromCode(String code) {
|
||||
|
||||
return Optional.ofNullable(CODE_MAP.get(code));
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
// Copyright 2025 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.
|
||||
@@ -12,5 +12,12 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
@javax.annotation.ParametersAreNonnullByDefault
|
||||
package google.registry.module.frontend;
|
||||
package google.registry.mosapi.model;
|
||||
|
||||
/**
|
||||
* Represents the generic JSON error response from the MoSAPI service for a 400 Bad Request.
|
||||
*
|
||||
* @see <a href="https://www.icann.org/mosapi-specification.pdf">ICANN MoSAPI Specification, Section
|
||||
* 8</a>
|
||||
*/
|
||||
public record MosApiErrorResponse(String resultCode, String message, String description) {}
|
||||
@@ -0,0 +1,187 @@
|
||||
// Copyright 2025 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.mosapi.module;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.privileges.secretmanager.SecretManagerClient;
|
||||
import jakarta.inject.Named;
|
||||
import jakarta.inject.Provider;
|
||||
import jakarta.inject.Singleton;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.util.Optional;
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import okhttp3.OkHttpClient;
|
||||
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
|
||||
import org.bouncycastle.openssl.PEMKeyPair;
|
||||
import org.bouncycastle.openssl.PEMParser;
|
||||
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
|
||||
|
||||
@Module
|
||||
public final class MosApiModule {
|
||||
|
||||
// Secret Manager constants
|
||||
private static final String LATEST_SECRET_VERSION = "latest";
|
||||
|
||||
// @Named annotations for Dagger
|
||||
private static final String MOSAPI_TLS_CERT = "mosapiTlsCert";
|
||||
private static final String MOSAPI_TLS_KEY = "mosapiTlsKey";
|
||||
private static final String MOSAPI_SSL_CONTEXT = "mosapiSslContext";
|
||||
private static final String MOSAPI_HTTP_CLIENT = "mosapiHttpClient";
|
||||
|
||||
// Cryptography-related constants
|
||||
private static final String CERTIFICATE_TYPE = "X.509";
|
||||
private static final String KEY_STORE_TYPE = "PKCS12";
|
||||
private static final String KEY_STORE_ALIAS = "client";
|
||||
private static final String SSL_CONTEXT_PROTOCOL = "TLS";
|
||||
|
||||
/**
|
||||
* Provides a Provider for the MoSAPI TLS Cert.
|
||||
*
|
||||
* <p>This method returns a Dagger {@link Provider} that can be used to fetch the TLS Certs for a
|
||||
* MosAPI.
|
||||
*
|
||||
* @param secretManagerClient The injected Secret Manager client.
|
||||
* @param tlsCertSecretName The name of the secret in Secret Manager (from config).
|
||||
* @return A Provider for the MoSAPI TLS Certs.
|
||||
*/
|
||||
@Provides
|
||||
@Named(MOSAPI_TLS_CERT)
|
||||
public static String provideMosapiTlsCert(
|
||||
SecretManagerClient secretManagerClient,
|
||||
@Config("mosapiTlsCertSecretName") String tlsCertSecretName) {
|
||||
return secretManagerClient.getSecretData(tlsCertSecretName, Optional.of(LATEST_SECRET_VERSION));
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a Provider for the MoSAPI TLS Key.
|
||||
*
|
||||
* <p>This method returns a Dagger {@link Provider} that can be used to fetch the TLS Key for a
|
||||
* MosAPI.
|
||||
*
|
||||
* @param secretManagerClient The injected Secret Manager client.
|
||||
* @param tlsKeySecretName The name of the secret in Secret Manager (from config).
|
||||
* @return A Provider for the MoSAPI TLS Key.
|
||||
*/
|
||||
@Provides
|
||||
@Named(MOSAPI_TLS_KEY)
|
||||
public static String provideMosapiTlsKey(
|
||||
SecretManagerClient secretManagerClient,
|
||||
@Config("mosapiTlsKeySecretName") String tlsKeySecretName) {
|
||||
return secretManagerClient.getSecretData(tlsKeySecretName, Optional.of(LATEST_SECRET_VERSION));
|
||||
}
|
||||
|
||||
@Provides
|
||||
static Certificate provideCertificate(@Named(MOSAPI_TLS_CERT) String tlsCert) {
|
||||
try {
|
||||
CertificateFactory cf = CertificateFactory.getInstance(CERTIFICATE_TYPE);
|
||||
return cf.generateCertificate(
|
||||
new ByteArrayInputStream(tlsCert.getBytes(StandardCharsets.UTF_8)));
|
||||
} catch (CertificateException e) {
|
||||
throw new RuntimeException("Could not create X.509 certificate from provided PEM", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Provides
|
||||
static PrivateKey providePrivateKey(@Named(MOSAPI_TLS_KEY) String tlsKey) {
|
||||
try (PEMParser pemParser = new PEMParser(new StringReader(tlsKey))) {
|
||||
Object parsedObj = pemParser.readObject();
|
||||
JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
|
||||
if (parsedObj instanceof PEMKeyPair) {
|
||||
return converter.getPrivateKey(((PEMKeyPair) parsedObj).getPrivateKeyInfo());
|
||||
} else if (parsedObj instanceof PrivateKeyInfo) {
|
||||
return converter.getPrivateKey((PrivateKeyInfo) parsedObj);
|
||||
}
|
||||
throw new IllegalArgumentException(
|
||||
String.format(
|
||||
"Could not parse TLS private key; unexpected format %s",
|
||||
parsedObj != null ? parsedObj.getClass().getName() : "null"));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Could not parse TLS private key from PEM string", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Provides
|
||||
static KeyStore provideKeyStore(PrivateKey privateKey, Certificate certificate) {
|
||||
try {
|
||||
KeyStore keyStore = KeyStore.getInstance(KEY_STORE_TYPE);
|
||||
keyStore.load(null, null);
|
||||
keyStore.setKeyEntry(
|
||||
KEY_STORE_ALIAS, privateKey, new char[0], new Certificate[] {certificate});
|
||||
return keyStore;
|
||||
} catch (GeneralSecurityException | IOException e) {
|
||||
throw new RuntimeException("Could not create KeyStore for mTLS", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Provides
|
||||
static KeyManagerFactory provideKeyManagerFactory(KeyStore keyStore) {
|
||||
try {
|
||||
KeyManagerFactory kmf =
|
||||
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||
kmf.init(keyStore, new char[0]);
|
||||
return kmf;
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new RuntimeException("Could not initialize KeyManagerFactory", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Named(MOSAPI_SSL_CONTEXT)
|
||||
static SSLContext provideSslContext(KeyManagerFactory keyManagerFactory) {
|
||||
try {
|
||||
SSLContext sslContext = SSLContext.getInstance(SSL_CONTEXT_PROTOCOL);
|
||||
sslContext.init(keyManagerFactory.getKeyManagers(), null, null);
|
||||
return sslContext;
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new RuntimeException("Could not initialize SSLContext", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Provides
|
||||
static X509TrustManager provideTrustManager() {
|
||||
try {
|
||||
TrustManagerFactory trustManagerFactory =
|
||||
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||
trustManagerFactory.init((KeyStore) null);
|
||||
return (X509TrustManager) trustManagerFactory.getTrustManagers()[0];
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new RuntimeException("Could not initialize TrustManager", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named(MOSAPI_HTTP_CLIENT)
|
||||
static OkHttpClient provideMosapiHttpClient(
|
||||
@Named(MOSAPI_SSL_CONTEXT) SSLContext sslContext, X509TrustManager trustManager) {
|
||||
return new OkHttpClient.Builder()
|
||||
.sslSocketFactory(sslContext.getSocketFactory(), trustManager)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
// Copyright 2025 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.
|
||||
@@ -13,4 +13,4 @@
|
||||
// limitations under the License.
|
||||
|
||||
@javax.annotation.ParametersAreNonnullByDefault
|
||||
package google.registry.module.backend;
|
||||
package google.registry.mosapi;
|
||||
@@ -20,7 +20,6 @@ import static google.registry.request.Action.Method.HEAD;
|
||||
import google.registry.rdap.RdapMetrics.EndpointType;
|
||||
import google.registry.rdap.RdapObjectClasses.ReplyPayloadBase;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.HttpException.NotImplementedException;
|
||||
import google.registry.request.auth.Auth;
|
||||
import jakarta.inject.Inject;
|
||||
@@ -32,7 +31,7 @@ import jakarta.inject.Inject;
|
||||
* ARIN, not domain registries.
|
||||
*/
|
||||
@Action(
|
||||
service = GaeService.PUBAPI,
|
||||
service = Action.Service.PUBAPI,
|
||||
path = "/rdap/autnum/",
|
||||
method = {GET, HEAD},
|
||||
isPrefix = true,
|
||||
|
||||
@@ -29,7 +29,6 @@ import google.registry.rdap.RdapJsonFormatter.OutputDataType;
|
||||
import google.registry.rdap.RdapMetrics.EndpointType;
|
||||
import google.registry.rdap.RdapObjectClasses.RdapDomain;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.HttpException.BadRequestException;
|
||||
import google.registry.request.HttpException.NotFoundException;
|
||||
import google.registry.request.auth.Auth;
|
||||
@@ -38,7 +37,7 @@ import java.util.Optional;
|
||||
|
||||
/** RDAP action for domain requests. */
|
||||
@Action(
|
||||
service = GaeService.PUBAPI,
|
||||
service = Action.Service.PUBAPI,
|
||||
path = "/rdap/domain/",
|
||||
method = {GET, HEAD},
|
||||
isPrefix = true,
|
||||
|
||||
@@ -42,7 +42,6 @@ import google.registry.rdap.RdapMetrics.WildcardType;
|
||||
import google.registry.rdap.RdapSearchResults.DomainSearchResponse;
|
||||
import google.registry.rdap.RdapSearchResults.IncompletenessWarningType;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.HttpException.BadRequestException;
|
||||
import google.registry.request.HttpException.NotFoundException;
|
||||
import google.registry.request.HttpException.UnprocessableEntityException;
|
||||
@@ -71,7 +70,7 @@ import org.joda.time.DateTime;
|
||||
* Data Access Protocol (RDAP)</a>
|
||||
*/
|
||||
@Action(
|
||||
service = GaeService.PUBAPI,
|
||||
service = Action.Service.PUBAPI,
|
||||
path = "/rdap/domains",
|
||||
method = {GET, HEAD},
|
||||
auth = Auth.AUTH_PUBLIC)
|
||||
|
||||
@@ -30,7 +30,7 @@ import java.io.IOException;
|
||||
* them the help response.
|
||||
*/
|
||||
@Action(
|
||||
service = Action.GaeService.PUBAPI,
|
||||
service = Action.Service.PUBAPI,
|
||||
path = "/rdap/",
|
||||
method = {GET, HEAD},
|
||||
auth = Auth.AUTH_PUBLIC)
|
||||
|
||||
@@ -25,7 +25,6 @@ import google.registry.rdap.RdapJsonFormatter.OutputDataType;
|
||||
import google.registry.rdap.RdapMetrics.EndpointType;
|
||||
import google.registry.rdap.RdapObjectClasses.RdapEntity;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.HttpException.NotFoundException;
|
||||
import google.registry.request.auth.Auth;
|
||||
import jakarta.inject.Inject;
|
||||
@@ -40,7 +39,7 @@ import java.util.Optional;
|
||||
* the handle of the entity with the registrar role is be [sic] equal to the IANA Registrar ID.
|
||||
*/
|
||||
@Action(
|
||||
service = GaeService.PUBAPI,
|
||||
service = Action.Service.PUBAPI,
|
||||
path = "/rdap/entity/",
|
||||
method = {GET, HEAD},
|
||||
isPrefix = true,
|
||||
|
||||
@@ -31,7 +31,6 @@ import google.registry.rdap.RdapMetrics.SearchType;
|
||||
import google.registry.rdap.RdapSearchResults.EntitySearchResponse;
|
||||
import google.registry.rdap.RdapSearchResults.IncompletenessWarningType;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.HttpException.BadRequestException;
|
||||
import google.registry.request.HttpException.NotFoundException;
|
||||
import google.registry.request.HttpException.UnprocessableEntityException;
|
||||
@@ -56,7 +55,7 @@ import java.util.Optional;
|
||||
* Data Access Protocol (RDAP)</a>
|
||||
*/
|
||||
@Action(
|
||||
service = GaeService.PUBAPI,
|
||||
service = Action.Service.PUBAPI,
|
||||
path = "/rdap/entities",
|
||||
method = {GET, HEAD},
|
||||
auth = Auth.AUTH_PUBLIC)
|
||||
|
||||
@@ -22,7 +22,6 @@ import google.registry.rdap.RdapDataStructures.Notice;
|
||||
import google.registry.rdap.RdapMetrics.EndpointType;
|
||||
import google.registry.rdap.RdapObjectClasses.HelpResponse;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.HttpException.NotFoundException;
|
||||
import google.registry.request.auth.Auth;
|
||||
import jakarta.inject.Inject;
|
||||
@@ -30,7 +29,7 @@ import java.util.Optional;
|
||||
|
||||
/** RDAP action for help requests. */
|
||||
@Action(
|
||||
service = GaeService.PUBAPI,
|
||||
service = Action.Service.PUBAPI,
|
||||
path = RdapHelpAction.PATH,
|
||||
method = {GET, HEAD},
|
||||
isPrefix = true,
|
||||
|
||||
@@ -20,7 +20,6 @@ import static google.registry.request.Action.Method.HEAD;
|
||||
import google.registry.rdap.RdapMetrics.EndpointType;
|
||||
import google.registry.rdap.RdapObjectClasses.ReplyPayloadBase;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.HttpException.NotImplementedException;
|
||||
import google.registry.request.auth.Auth;
|
||||
import jakarta.inject.Inject;
|
||||
@@ -32,7 +31,7 @@ import jakarta.inject.Inject;
|
||||
* ARIN, not domain registries.
|
||||
*/
|
||||
@Action(
|
||||
service = GaeService.PUBAPI,
|
||||
service = Action.Service.PUBAPI,
|
||||
path = "/rdap/ip/",
|
||||
method = {GET, HEAD},
|
||||
isPrefix = true,
|
||||
|
||||
@@ -26,7 +26,6 @@ import google.registry.rdap.RdapJsonFormatter.OutputDataType;
|
||||
import google.registry.rdap.RdapMetrics.EndpointType;
|
||||
import google.registry.rdap.RdapObjectClasses.RdapNameserver;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.HttpException.BadRequestException;
|
||||
import google.registry.request.HttpException.NotFoundException;
|
||||
import google.registry.request.auth.Auth;
|
||||
@@ -35,7 +34,7 @@ import java.util.Optional;
|
||||
|
||||
/** RDAP action for nameserver requests. */
|
||||
@Action(
|
||||
service = GaeService.PUBAPI,
|
||||
service = Action.Service.PUBAPI,
|
||||
path = "/rdap/nameserver/",
|
||||
method = {GET, HEAD},
|
||||
isPrefix = true,
|
||||
|
||||
@@ -34,7 +34,6 @@ import google.registry.rdap.RdapMetrics.SearchType;
|
||||
import google.registry.rdap.RdapSearchResults.IncompletenessWarningType;
|
||||
import google.registry.rdap.RdapSearchResults.NameserverSearchResponse;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.HttpException.BadRequestException;
|
||||
import google.registry.request.HttpException.NotFoundException;
|
||||
import google.registry.request.HttpException.UnprocessableEntityException;
|
||||
@@ -57,7 +56,7 @@ import java.util.Optional;
|
||||
* Data Access Protocol (RDAP)</a>
|
||||
*/
|
||||
@Action(
|
||||
service = GaeService.PUBAPI,
|
||||
service = Action.Service.PUBAPI,
|
||||
path = "/rdap/nameservers",
|
||||
method = {GET, HEAD},
|
||||
auth = Auth.AUTH_PUBLIC)
|
||||
|
||||
@@ -27,7 +27,6 @@ import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.persistence.PersistenceModule;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.HttpException.InternalServerErrorException;
|
||||
import google.registry.request.UrlConnectionService;
|
||||
import google.registry.request.UrlConnectionUtils;
|
||||
@@ -54,7 +53,7 @@ import org.apache.commons.csv.CSVRecord;
|
||||
* CSV endpoint requires no authentication.
|
||||
*/
|
||||
@Action(
|
||||
service = GaeService.BACKEND,
|
||||
service = Action.Service.BACKEND,
|
||||
path = "/_dr/task/updateRegistrarRdapBaseUrls",
|
||||
automaticallyPrintOk = true,
|
||||
auth = Auth.AUTH_ADMIN)
|
||||
|
||||
@@ -32,7 +32,6 @@ import google.registry.model.rde.RdeNamingUtils;
|
||||
import google.registry.model.rde.RdeRevision;
|
||||
import google.registry.model.tld.Tld;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.HttpException.NoContentException;
|
||||
import google.registry.request.Parameter;
|
||||
import google.registry.request.RequestParameters;
|
||||
@@ -63,7 +62,7 @@ import org.joda.time.DateTime;
|
||||
* Agreement</a>
|
||||
*/
|
||||
@Action(
|
||||
service = GaeService.BACKEND,
|
||||
service = Action.Service.BACKEND,
|
||||
path = BrdaCopyAction.PATH,
|
||||
method = POST,
|
||||
automaticallyPrintOk = true,
|
||||
|
||||
@@ -168,20 +168,18 @@ final class DomainToXjcConverter {
|
||||
// as the holder of the domain name object.
|
||||
Optional<VKey<Contact>> registrant = model.getRegistrant();
|
||||
if (registrant.isPresent()) {
|
||||
Contact registrantContact = tm().transact(() -> tm().loadByKey(registrant.get()));
|
||||
checkState(
|
||||
registrantContact != null,
|
||||
"Registrant contact %s on domain %s does not exist",
|
||||
registrant,
|
||||
domainName);
|
||||
bean.setRegistrant(registrantContact.getContactId());
|
||||
Optional<Contact> registrantContact =
|
||||
tm().transact(() -> tm().loadByKeyIfPresent(registrant.get()));
|
||||
registrantContact.ifPresent(c -> bean.setRegistrant(c.getContactId()));
|
||||
}
|
||||
|
||||
// o Zero or more OPTIONAL <contact> elements that contain identifiers
|
||||
// for the human or organizational social information objects
|
||||
// associated with the domain name object.
|
||||
for (DesignatedContact contact : model.getContacts()) {
|
||||
bean.getContacts().add(convertDesignatedContact(contact, domainName));
|
||||
Optional<XjcDomainContactType> contactType =
|
||||
convertDesignatedContact(contact, domainName);
|
||||
contactType.ifPresent(c -> bean.getContacts().add(c));
|
||||
}
|
||||
|
||||
// o An OPTIONAL <secDNS> element that contains the public key
|
||||
@@ -292,7 +290,7 @@ final class DomainToXjcConverter {
|
||||
}
|
||||
|
||||
/** Converts {@link DesignatedContact} to {@link XjcDomainContactType}. */
|
||||
private static XjcDomainContactType convertDesignatedContact(
|
||||
private static Optional<XjcDomainContactType> convertDesignatedContact(
|
||||
DesignatedContact model, String domainName) {
|
||||
XjcDomainContactType bean = new XjcDomainContactType();
|
||||
checkState(
|
||||
@@ -300,15 +298,13 @@ final class DomainToXjcConverter {
|
||||
"Contact key for type %s is null on domain %s",
|
||||
model.getType(),
|
||||
domainName);
|
||||
Contact contact = tm().transact(() -> tm().loadByKey(model.getContactKey()));
|
||||
checkState(
|
||||
contact != null,
|
||||
"Contact %s on domain %s does not exist",
|
||||
model.getContactKey(),
|
||||
domainName);
|
||||
Optional<Contact> contact = tm().transact(() -> tm().loadByKeyIfPresent(model.getContactKey()));
|
||||
if (contact.isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
bean.setType(XjcDomainContactAttrType.fromValue(Ascii.toLowerCase(model.getType().toString())));
|
||||
bean.setValue(contact.getContactId());
|
||||
return bean;
|
||||
bean.setValue(contact.get().getContactId());
|
||||
return Optional.of(bean);
|
||||
}
|
||||
|
||||
private DomainToXjcConverter() {}
|
||||
|
||||
@@ -33,10 +33,10 @@ import org.joda.time.Duration;
|
||||
* Runner applying guaranteed reliability to an {@link EscrowTask}.
|
||||
*
|
||||
* <p>This class implements the <i>Locking Rolling Cursor</i> pattern, which solves the problem of
|
||||
* how to reliably execute App Engine tasks which can't be made idempotent.
|
||||
* how to reliably execute Cloud Tasks which can't be made idempotent.
|
||||
*
|
||||
* <p>{@link LockHandler} is used to ensure only one task executes at a time for a given {@code
|
||||
* LockedCursorTask} subclass + TLD combination. This is necessary because App Engine tasks might
|
||||
* LockedCursorTask} subclass + TLD combination. This is necessary because Cloud Task tasks might
|
||||
* double-execute. Normally tasks solve this by being idempotent, but that's not possible for RDE,
|
||||
* which writes to a GCS filename with a deterministic name. So locks are used to guarantee
|
||||
* isolation. If we can't acquire the lock, it means the task is already running, so {@link
|
||||
|
||||
@@ -31,11 +31,7 @@ import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.util.Optional;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* Dagger module for RDE package.
|
||||
*
|
||||
* @see "google.registry.module.backend.BackendRequestComponent"
|
||||
*/
|
||||
/** Dagger module for RDE package. */
|
||||
@Module
|
||||
public abstract class RdeModule {
|
||||
|
||||
|
||||
@@ -36,7 +36,6 @@ import google.registry.model.rde.RdeRevision;
|
||||
import google.registry.model.tld.Tld;
|
||||
import google.registry.rde.EscrowTaskRunner.EscrowTask;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.HttpException.NoContentException;
|
||||
import google.registry.request.Parameter;
|
||||
import google.registry.request.RequestParameters;
|
||||
@@ -54,7 +53,7 @@ import org.joda.time.Duration;
|
||||
* Action that uploads a small XML RDE report to ICANN after {@link RdeUploadAction} has finished.
|
||||
*/
|
||||
@Action(
|
||||
service = GaeService.BACKEND,
|
||||
service = Action.Service.BACKEND,
|
||||
path = RdeReportAction.PATH,
|
||||
method = POST,
|
||||
auth = Auth.AUTH_ADMIN)
|
||||
|
||||
@@ -50,7 +50,6 @@ import google.registry.model.host.Host;
|
||||
import google.registry.model.rde.RdeMode;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.HttpException.BadRequestException;
|
||||
import google.registry.request.Parameter;
|
||||
import google.registry.request.RequestParameters;
|
||||
@@ -101,8 +100,8 @@ import org.joda.time.Duration;
|
||||
*
|
||||
* <h2>Logging</h2>
|
||||
*
|
||||
* <p>To identify the reduce worker request for a deposit in App Engine's log viewer, you can use
|
||||
* search text like {@code tld=soy}, {@code watermark=2015-01-01}, and {@code mode=FULL}.
|
||||
* <p>To identify the reduce worker request for a deposit in the log viewer, you can use search text
|
||||
* like {@code tld=soy}, {@code watermark=2015-01-01}, and {@code mode=FULL}.
|
||||
*
|
||||
* <h3>Error Handling</h3>
|
||||
*
|
||||
@@ -205,7 +204,7 @@ import org.joda.time.Duration;
|
||||
* Name Registration Data Objects Mapping</a>
|
||||
*/
|
||||
@Action(
|
||||
service = GaeService.BACKEND,
|
||||
service = Action.Service.BACKEND,
|
||||
path = RdeStagingAction.PATH,
|
||||
method = {GET, POST},
|
||||
auth = Auth.AUTH_ADMIN)
|
||||
|
||||
@@ -50,7 +50,6 @@ import google.registry.model.tld.Tld;
|
||||
import google.registry.rde.EscrowTaskRunner.EscrowTask;
|
||||
import google.registry.rde.JSchSshSession.JSchSshSessionFactory;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.HttpException.NoContentException;
|
||||
import google.registry.request.Parameter;
|
||||
import google.registry.request.RequestParameters;
|
||||
@@ -84,7 +83,7 @@ import org.joda.time.Duration;
|
||||
* RdeReportAction}.
|
||||
*/
|
||||
@Action(
|
||||
service = GaeService.BACKEND,
|
||||
service = Action.Service.BACKEND,
|
||||
path = RdeUploadAction.PATH,
|
||||
method = POST,
|
||||
auth = Auth.AUTH_ADMIN)
|
||||
|
||||
@@ -33,7 +33,6 @@ import google.registry.gcs.GcsUtils;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.reporting.billing.BillingModule.InvoiceDirectoryPrefix;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.Response;
|
||||
import google.registry.request.auth.Auth;
|
||||
import google.registry.storage.drive.DriveConnection;
|
||||
@@ -45,7 +44,7 @@ import java.util.Optional;
|
||||
|
||||
/** Copy all registrar detail reports in a given bucket's subdirectory from GCS to Drive. */
|
||||
@Action(
|
||||
service = GaeService.BACKEND,
|
||||
service = Action.Service.BACKEND,
|
||||
path = CopyDetailReportsAction.PATH,
|
||||
method = POST,
|
||||
auth = Auth.AUTH_ADMIN)
|
||||
|
||||
@@ -32,7 +32,6 @@ import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.persistence.PersistenceModule;
|
||||
import google.registry.reporting.ReportingModule;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.Parameter;
|
||||
import google.registry.request.Response;
|
||||
import google.registry.request.auth.Auth;
|
||||
@@ -51,7 +50,7 @@ import org.joda.time.YearMonth;
|
||||
* template. The pipeline then generates invoices for the month and stores them on GCS.
|
||||
*/
|
||||
@Action(
|
||||
service = GaeService.BACKEND,
|
||||
service = Action.Service.BACKEND,
|
||||
path = GenerateInvoicesAction.PATH,
|
||||
method = POST,
|
||||
auth = Auth.AUTH_ADMIN)
|
||||
|
||||
@@ -30,7 +30,6 @@ import google.registry.batch.CloudTasksUtils;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.reporting.ReportingModule;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.Parameter;
|
||||
import google.registry.request.Response;
|
||||
import google.registry.request.auth.Auth;
|
||||
@@ -49,7 +48,7 @@ import org.joda.time.YearMonth;
|
||||
* Job States</a>
|
||||
*/
|
||||
@Action(
|
||||
service = GaeService.BACKEND,
|
||||
service = Action.Service.BACKEND,
|
||||
path = PublishInvoicesAction.PATH,
|
||||
method = POST,
|
||||
auth = Auth.AUTH_ADMIN)
|
||||
|
||||
@@ -34,7 +34,6 @@ import google.registry.groups.GmailClient;
|
||||
import google.registry.reporting.ReportingModule;
|
||||
import google.registry.reporting.icann.IcannReportingModule.ReportType;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.Parameter;
|
||||
import google.registry.request.Response;
|
||||
import google.registry.request.auth.Auth;
|
||||
@@ -66,7 +65,7 @@ import org.joda.time.format.DateTimeFormat;
|
||||
* 'transactions'. If none specified - defaults to generating both.
|
||||
*/
|
||||
@Action(
|
||||
service = GaeService.BACKEND,
|
||||
service = Action.Service.BACKEND,
|
||||
path = IcannReportingStagingAction.PATH,
|
||||
method = POST,
|
||||
auth = Auth.AUTH_ADMIN)
|
||||
|
||||
@@ -35,7 +35,6 @@ import google.registry.model.tld.Tld.TldType;
|
||||
import google.registry.model.tld.Tlds;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.HttpException.ServiceUnavailableException;
|
||||
import google.registry.request.Response;
|
||||
import google.registry.request.auth.Auth;
|
||||
@@ -68,7 +67,7 @@ import org.joda.time.Duration;
|
||||
* Defaults to "icann/monthly/[last month in yyyy-MM format]".
|
||||
*/
|
||||
@Action(
|
||||
service = GaeService.BACKEND,
|
||||
service = Action.Service.BACKEND,
|
||||
path = IcannReportingUploadAction.PATH,
|
||||
method = POST,
|
||||
auth = Auth.AUTH_ADMIN)
|
||||
|
||||
@@ -107,7 +107,7 @@ public final class TransactionsReportingQueryBuilder implements QueryBuilder {
|
||||
queriesBuilder.put(
|
||||
getTableName(TRANSACTION_TRANSFER_LOSING, yearMonth), transactionTransferLosingQuery);
|
||||
|
||||
// App Engine log table suffixes use YYYYMMDD format
|
||||
// Log table suffixes use YYYYMMDD format
|
||||
DateTimeFormatter logTableFormatter = DateTimeFormat.forPattern("yyyyMMdd");
|
||||
String attemptedAddsQuery =
|
||||
SqlTemplate.create(getQueryFromFile(ATTEMPTED_ADDS + ".sql"))
|
||||
|
||||
@@ -32,7 +32,6 @@ import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.keyring.api.KeyModule.Key;
|
||||
import google.registry.reporting.ReportingModule;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.Parameter;
|
||||
import google.registry.request.Response;
|
||||
import google.registry.request.auth.Auth;
|
||||
@@ -50,7 +49,7 @@ import org.joda.time.LocalDate;
|
||||
* generates the specified month's Spec11 report and stores it on GCS.
|
||||
*/
|
||||
@Action(
|
||||
service = GaeService.BACKEND,
|
||||
service = Action.Service.BACKEND,
|
||||
path = GenerateSpec11ReportAction.PATH,
|
||||
method = POST,
|
||||
auth = Auth.AUTH_ADMIN)
|
||||
|
||||
@@ -38,7 +38,6 @@ import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.reporting.ReportingModule;
|
||||
import google.registry.reporting.spec11.soy.Spec11EmailSoyInfo;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.Parameter;
|
||||
import google.registry.request.Response;
|
||||
import google.registry.request.auth.Auth;
|
||||
@@ -57,7 +56,7 @@ import org.json.JSONException;
|
||||
* ImmutableSet)} on success or {@link Spec11EmailUtils#sendAlertEmail(String, String)} on failure.
|
||||
*/
|
||||
@Action(
|
||||
service = GaeService.BACKEND,
|
||||
service = Action.Service.BACKEND,
|
||||
path = PublishSpec11ReportAction.PATH,
|
||||
method = POST,
|
||||
auth = Auth.AUTH_ADMIN)
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
package google.registry.request;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
import google.registry.config.RegistryConfig;
|
||||
import google.registry.request.auth.Auth;
|
||||
@@ -38,46 +37,7 @@ public @interface Action {
|
||||
DELETE
|
||||
}
|
||||
|
||||
interface Service {
|
||||
String getServiceId();
|
||||
|
||||
URL getServiceUrl();
|
||||
}
|
||||
|
||||
enum GaeService implements Service {
|
||||
BSA("bsa"),
|
||||
DEFAULT("default"),
|
||||
TOOLS("tools"),
|
||||
BACKEND("backend"),
|
||||
PUBAPI("pubapi");
|
||||
|
||||
private final String serviceId;
|
||||
|
||||
GaeService(String serviceId) {
|
||||
this.serviceId = serviceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServiceId() {
|
||||
return serviceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getServiceUrl() {
|
||||
return switch (this) {
|
||||
case DEFAULT -> RegistryConfig.getDefaultServer();
|
||||
case TOOLS -> RegistryConfig.getToolsServer();
|
||||
case BACKEND -> RegistryConfig.getBackendServer();
|
||||
case BSA -> RegistryConfig.getBsaServer();
|
||||
case PUBAPI -> RegistryConfig.getPubapiServer();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
enum GkeService implements Service {
|
||||
// This designation means that it defers to the GAE service, so we don't have to annotate EVERY
|
||||
// action during the GKE migration.
|
||||
SAME_AS_GAE("same_as_gae"),
|
||||
enum Service {
|
||||
FRONTEND("frontend"),
|
||||
BACKEND("backend"),
|
||||
PUBAPI("pubapi"),
|
||||
@@ -85,27 +45,21 @@ public @interface Action {
|
||||
|
||||
private final String serviceId;
|
||||
|
||||
GkeService(String serviceId) {
|
||||
Service(String serviceId) {
|
||||
this.serviceId = serviceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServiceId() {
|
||||
checkState(this != SAME_AS_GAE, "Cannot get service Id for SAME_AS_GAE");
|
||||
return serviceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getServiceUrl() {
|
||||
return RegistryConfig.getServiceUrl(this);
|
||||
}
|
||||
}
|
||||
|
||||
/** Which App Engine service this action lives on. */
|
||||
GaeService service();
|
||||
|
||||
/** Which GKE service this action lives on. */
|
||||
GkeService gkeService() default GkeService.SAME_AS_GAE;
|
||||
Service service();
|
||||
|
||||
/** HTTP path to serve the action from. The path components must be percent-escaped. */
|
||||
String path();
|
||||
@@ -127,22 +81,4 @@ public @interface Action {
|
||||
|
||||
/** Authentication settings. */
|
||||
Auth auth();
|
||||
|
||||
// TODO(jianglai): Use Action.gkeService() directly once we are off GAE.
|
||||
class ServiceGetter {
|
||||
public static GkeService get(Action action) {
|
||||
GkeService service = action.gkeService();
|
||||
if (service != GkeService.SAME_AS_GAE) {
|
||||
return service;
|
||||
}
|
||||
GaeService gaeService = action.service();
|
||||
return switch (gaeService) {
|
||||
case DEFAULT -> GkeService.FRONTEND;
|
||||
case BACKEND -> GkeService.BACKEND;
|
||||
case TOOLS -> GkeService.BACKEND;
|
||||
case BSA -> GkeService.BACKEND;
|
||||
case PUBAPI -> GkeService.PUBAPI;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user