mirror of
https://github.com/google/nomulus
synced 2026-01-16 02:33:16 +00:00
Compare commits
47 Commits
nomulus-20
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c187c92ae4 | ||
|
|
22ca4e3f2b | ||
|
|
f27136458a | ||
|
|
d8e647316e | ||
|
|
d6e0a7b979 | ||
|
|
5725eb95e0 | ||
|
|
aa12998276 | ||
|
|
d415416bc5 | ||
|
|
3a1068f313 | ||
|
|
69e5d40f04 | ||
|
|
64f6cd9af4 | ||
|
|
40184689ca | ||
|
|
826ad85d20 | ||
|
|
2b47bc9b0a | ||
|
|
9555dca8c6 | ||
|
|
49484c06d3 | ||
|
|
81d222e7d6 | ||
|
|
7e9d4c27d1 | ||
|
|
f9c22ff1c5 | ||
|
|
2562d582f3 | ||
|
|
6f0bc1ded9 | ||
|
|
db9fc3271d | ||
|
|
84491fde70 | ||
|
|
0519e2ffcf | ||
|
|
85f75494ab | ||
|
|
cbba91558a | ||
|
|
c24f09febc | ||
|
|
fd51035f23 | ||
|
|
90eb078e3f | ||
|
|
2a94bdc257 | ||
|
|
50fa49e0c0 | ||
|
|
a581259edb | ||
|
|
fcdac3e86e | ||
|
|
b652f81193 | ||
|
|
d98d65eee5 | ||
|
|
28e72bd0d0 | ||
|
|
0777be3d6c | ||
|
|
f9cd167ae4 | ||
|
|
eed1886121 | ||
|
|
7149fd3307 | ||
|
|
0dc7ab99d7 | ||
|
|
76d4dfbb04 | ||
|
|
8547ad7941 | ||
|
|
b1266c95e8 | ||
|
|
bc9aab6790 | ||
|
|
6cb669a5a7 | ||
|
|
0f92e98028 |
@@ -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);
|
||||
|
||||
@@ -56,7 +56,7 @@ PROPERTIES_HEADER = """\
|
||||
# nom_build), run ./nom_build --help.
|
||||
#
|
||||
# DO NOT EDIT THIS FILE BY HAND
|
||||
org.gradle.jvmargs=-Xmx2048m
|
||||
org.gradle.jvmargs=-Xmx4096m
|
||||
org.gradle.caching=true
|
||||
org.gradle.parallel=true
|
||||
"""
|
||||
@@ -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',
|
||||
@@ -117,28 +117,19 @@ PROPERTIES = [
|
||||
Property('dbUser', 'Database user name for use in connection'),
|
||||
Property('dbPassword', 'Database password for use in connection'),
|
||||
|
||||
Property('publish_repo',
|
||||
'Maven repository that hosts the Cloud SQL schema jar and the '
|
||||
'registry server test jars. Such jars are needed for '
|
||||
'server/schema integration tests. Please refer to <a '
|
||||
'href="./integration/README.md">integration project</a> for more '
|
||||
'information.'),
|
||||
Property('baseSchemaTag',
|
||||
'The nomulus version tag of the schema for use in the schema'
|
||||
'deployment integration test (:db:schemaIncrementalDeployTest)'),
|
||||
Property('schema_version',
|
||||
'The nomulus version tag of the schema for use in a database'
|
||||
'integration test.'),
|
||||
Property('nomulus_version',
|
||||
'The version of nomulus to test against in a database '
|
||||
'integration test.'),
|
||||
Property('dot_path',
|
||||
'The path to "dot", part of the graphviz package that converts '
|
||||
'a BEAM pipeline to image. Setting this property to empty string '
|
||||
'will disable image generation.',
|
||||
'/usr/bin/dot'),
|
||||
Property('pipeline',
|
||||
'The name of the Beam pipeline being staged.')
|
||||
'The name of the Beam pipeline being staged.'),
|
||||
Property('nomulus_env',
|
||||
'For use by scripts. Normally not set manually.'),
|
||||
Property('schema_env',
|
||||
'For use by scripts. Normally not set manually.'),
|
||||
Property('schemaTestArtifactsDir',
|
||||
'For use by scripts. Normally not set manually.')
|
||||
]
|
||||
|
||||
GRADLE_FLAGS = [
|
||||
|
||||
@@ -98,8 +98,8 @@ PRESUBMITS = {
|
||||
"File did not include the license header.",
|
||||
|
||||
# Files must end in a newline
|
||||
PresubmitCheck(r".*\n$", ("java", "js", "soy", "sql", "py", "sh", "gradle", "ts"),
|
||||
{"node_modules/"}, REQUIRED):
|
||||
PresubmitCheck(r".*\n$", ("java", "js", "soy", "sql", "py", "sh", "gradle", "ts", "xml"),
|
||||
{"node_modules/", ".idea"}, REQUIRED):
|
||||
"Source files must end in a newline.",
|
||||
|
||||
# System.(out|err).println should only appear in tools/ or load-testing/
|
||||
|
||||
@@ -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.
|
||||
@@ -646,23 +646,6 @@ artifacts {
|
||||
nomulus_test testUberJar
|
||||
}
|
||||
|
||||
publishing {
|
||||
repositories {
|
||||
maven {
|
||||
url project.publish_repo
|
||||
}
|
||||
}
|
||||
publications {
|
||||
nomulusTestsPublication(MavenPublication) {
|
||||
groupId 'google.registry'
|
||||
artifactId 'nomulus_test'
|
||||
version project.nomulus_version
|
||||
artifact nomulusFossJar
|
||||
artifact testUberJar
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task buildToolImage(dependsOn: nomulus, type: Exec) {
|
||||
commandLine 'docker', 'build', '-t', 'nomulus-tool', '.'
|
||||
}
|
||||
|
||||
@@ -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,242 @@
|
||||
// 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>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. The recommended usage path
|
||||
* is to call this through the {@link google.registry.tools.BulkDomainTransferCommand}, which
|
||||
* handles batching and input handling.
|
||||
*
|
||||
* <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>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);
|
||||
|
||||
@@ -14,10 +14,8 @@
|
||||
|
||||
package google.registry.batch;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static com.google.common.net.MediaType.PLAIN_TEXT_UTF_8;
|
||||
import static google.registry.flows.FlowUtils.marshalWithLenientRetry;
|
||||
import static google.registry.model.common.FeatureFlag.FeatureName.MINIMUM_DATASET_CONTACTS_PROHIBITED;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.util.DateTimeUtils.END_OF_TIME;
|
||||
import static google.registry.util.ResourceUtils.readResourceUtf8;
|
||||
@@ -36,7 +34,6 @@ import google.registry.flows.EppController;
|
||||
import google.registry.flows.EppRequestSource;
|
||||
import google.registry.flows.PasswordOnlyTransportCredentials;
|
||||
import google.registry.flows.StatelessRequestSessionMetadata;
|
||||
import google.registry.model.common.FeatureFlag;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.domain.DesignatedContact;
|
||||
import google.registry.model.domain.Domain;
|
||||
@@ -44,7 +41,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 +63,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 +90,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;
|
||||
@@ -107,11 +103,7 @@ public class RemoveAllDomainContactsAction implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
checkState(
|
||||
tm().transact(() -> FeatureFlag.isActiveNow(MINIMUM_DATASET_CONTACTS_PROHIBITED)),
|
||||
"Minimum dataset migration must be completed prior to running this action");
|
||||
response.setContentType(PLAIN_TEXT_UTF_8);
|
||||
|
||||
Callable<Void> runner =
|
||||
() -> {
|
||||
try {
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -184,10 +184,10 @@ public class RdePipeline implements Serializable {
|
||||
private final CloudTasksUtils cloudTasksUtils;
|
||||
private final RdeMarshaller marshaller;
|
||||
|
||||
// Registrars to be excluded from data escrow. Not including the sandbox-only OTE type so that
|
||||
// if sneaks into production we would get an extra signal.
|
||||
// Registrars to be excluded from data escrow (i.e. all registrar types that have a null IANA
|
||||
// identifier and thus would not be valid according to the RDE schema).
|
||||
private static final ImmutableSet<Type> IGNORED_REGISTRAR_TYPES =
|
||||
Sets.immutableEnumSet(Registrar.Type.MONITORING, Registrar.Type.TEST);
|
||||
Sets.immutableEnumSet(Registrar.Type.MONITORING, Registrar.Type.OTE, Registrar.Type.TEST);
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
|
||||
@@ -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,58 @@ 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("mosapiTlsKeySecretName")
|
||||
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);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("mosapiTldThreadCnt")
|
||||
public static int provideMosapiTldThreads(RegistryConfigSettings config) {
|
||||
return config.mosapi.tldThreadCnt;
|
||||
}
|
||||
|
||||
private static String formatComments(String text) {
|
||||
return Splitter.on('\n').omitEmptyStrings().trimResults().splitToList(text).stream()
|
||||
.map(s -> "# " + s)
|
||||
@@ -1422,7 +1475,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 +1497,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,15 @@ 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;
|
||||
public int tldThreadCnt;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,34 @@ 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"
|
||||
|
||||
# Provides a fixed thread pool for parallel TLD processing.
|
||||
# @see <a href="https://www.icann.org/mosapi-specification.pdf">
|
||||
# ICANN MoSAPI Specification, Section 12.3</a>
|
||||
tldThreadCnt: 4
|
||||
|
||||
|
||||
@@ -322,4 +322,15 @@
|
||||
<service>bsa</service>
|
||||
<schedule>23 8,20 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/task/triggerMosApiServiceState]]></url>
|
||||
<name>triggerMosApiServiceState</name>
|
||||
<description>
|
||||
Fetches the service state from MosAPI and triggers the metrics status for all TLDs.
|
||||
</description>
|
||||
<!-- Runs every 5 minutes. -->
|
||||
<schedule>*/5 * * * *</schedule>
|
||||
</task>
|
||||
|
||||
</entries>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -194,13 +194,27 @@ public final class ResourceFlowUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/** Check that the same values aren't being added and removed in an update command. */
|
||||
public static void checkSameValuesNotAddedAndRemoved(
|
||||
ImmutableSet<?> fieldsToAdd, ImmutableSet<?> fieldsToRemove)
|
||||
throws AddRemoveSameValueException {
|
||||
/**
|
||||
* Verifies the adds and removes on a resource.
|
||||
*
|
||||
* <p>This throws an exception in three different situations: if the same value is being both
|
||||
* added and removed, if a value is being added that is already present, or if a value is being
|
||||
* removed that isn't present.
|
||||
*/
|
||||
public static <T> void verifyAddsAndRemoves(
|
||||
ImmutableSet<T> existingFields, ImmutableSet<T> fieldsToAdd, ImmutableSet<T> fieldsToRemove)
|
||||
throws AddRemoveSameValueException,
|
||||
AddExistingValueException,
|
||||
RemoveNonexistentValueException {
|
||||
if (!intersection(fieldsToAdd, fieldsToRemove).isEmpty()) {
|
||||
throw new AddRemoveSameValueException();
|
||||
}
|
||||
if (!intersection(fieldsToAdd, existingFields).isEmpty()) {
|
||||
throw new AddExistingValueException();
|
||||
}
|
||||
if (intersection(fieldsToRemove, existingFields).size() != fieldsToRemove.size()) {
|
||||
throw new RemoveNonexistentValueException();
|
||||
}
|
||||
}
|
||||
|
||||
/** Check that all {@link StatusValue} objects in a set are client-settable. */
|
||||
@@ -266,6 +280,20 @@ public final class ResourceFlowUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/** Cannot add a value that is already present. */
|
||||
public static class AddExistingValueException extends ParameterValuePolicyErrorException {
|
||||
public AddExistingValueException() {
|
||||
super("Cannot add a value that is already present");
|
||||
}
|
||||
}
|
||||
|
||||
/** Cannot remove a value that does not exist. */
|
||||
public static class RemoveNonexistentValueException extends ParameterValuePolicyErrorException {
|
||||
public RemoveNonexistentValueException() {
|
||||
super("Cannot remove a value that does not exist");
|
||||
}
|
||||
}
|
||||
|
||||
/** The specified status value cannot be set by clients. */
|
||||
public static class StatusNotClientSettableException extends ParameterValueRangeErrorException {
|
||||
public StatusNotClientSettableException(String statusValue) {
|
||||
|
||||
@@ -14,60 +14,19 @@
|
||||
|
||||
package google.registry.flows.contact;
|
||||
|
||||
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyTargetIdCount;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactCommand.Check;
|
||||
import google.registry.model.eppinput.ResourceCommand;
|
||||
import google.registry.model.eppoutput.CheckData.ContactCheck;
|
||||
import google.registry.model.eppoutput.CheckData.ContactCheckData;
|
||||
import google.registry.model.eppoutput.EppResponse;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||
import google.registry.util.Clock;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
/**
|
||||
* An EPP flow that checks whether a contact can be provisioned.
|
||||
* An EPP flow that is meant to check whether a contact can be provisioned.
|
||||
*
|
||||
* <p>This flows can check the existence of multiple contacts simultaneously.
|
||||
*
|
||||
* @error {@link google.registry.flows.exceptions.TooManyResourceChecksException}
|
||||
* @error {@link google.registry.flows.FlowUtils.NotLoggedInException}
|
||||
* @error {@link ContactsProhibitedException}
|
||||
*/
|
||||
@Deprecated
|
||||
@ReportingSpec(ActivityReportField.CONTACT_CHECK)
|
||||
public final class ContactCheckFlow implements TransactionalFlow {
|
||||
|
||||
@Inject ResourceCommand resourceCommand;
|
||||
@Inject @RegistrarId String registrarId;
|
||||
@Inject ExtensionManager extensionManager;
|
||||
@Inject Clock clock;
|
||||
@Inject @Config("maxChecks") int maxChecks;
|
||||
@Inject EppResponse.Builder responseBuilder;
|
||||
public final class ContactCheckFlow extends ContactsProhibitedFlow {
|
||||
@Inject ContactCheckFlow() {}
|
||||
|
||||
@Override
|
||||
public EppResponse run() throws EppException {
|
||||
validateRegistrarIsLoggedIn(registrarId);
|
||||
extensionManager.validate(); // There are no legal extensions for this flow.
|
||||
ImmutableList<String> targetIds = ((Check) resourceCommand).getTargetIds();
|
||||
verifyTargetIdCount(targetIds, maxChecks);
|
||||
ImmutableSet<String> existingIds =
|
||||
ForeignKeyUtils.loadKeys(Contact.class, targetIds, clock.nowUtc()).keySet();
|
||||
ImmutableList.Builder<ContactCheck> checks = new ImmutableList.Builder<>();
|
||||
for (String id : targetIds) {
|
||||
boolean unused = !existingIds.contains(id);
|
||||
checks.add(ContactCheck.create(unused, id, unused ? null : "In use"));
|
||||
}
|
||||
return responseBuilder.setResData(ContactCheckData.create(checks.build())).build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,94 +14,19 @@
|
||||
|
||||
package google.registry.flows.contact;
|
||||
|
||||
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyResourceDoesNotExist;
|
||||
import static google.registry.flows.contact.ContactFlowUtils.validateAsciiPostalInfo;
|
||||
import static google.registry.flows.contact.ContactFlowUtils.validateContactAgainstPolicy;
|
||||
import static google.registry.model.EppResourceUtils.createRepoId;
|
||||
import static google.registry.model.common.FeatureFlag.FeatureName.MINIMUM_DATASET_CONTACTS_PROHIBITED;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.MutatingFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
import google.registry.flows.exceptions.ResourceAlreadyExistsForThisClientException;
|
||||
import google.registry.flows.exceptions.ResourceCreateContentionException;
|
||||
import google.registry.model.common.FeatureFlag;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactCommand.Create;
|
||||
import google.registry.model.contact.ContactHistory;
|
||||
import google.registry.model.domain.metadata.MetadataExtension;
|
||||
import google.registry.model.eppinput.ResourceCommand;
|
||||
import google.registry.model.eppoutput.CreateData.ContactCreateData;
|
||||
import google.registry.model.eppoutput.EppResponse;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||
import jakarta.inject.Inject;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* An EPP flow that creates a new contact.
|
||||
* An EPP flow meant to create a new contact.
|
||||
*
|
||||
* @error {@link google.registry.flows.FlowUtils.NotLoggedInException}
|
||||
* @error {@link ContactsProhibitedException}
|
||||
* @error {@link ResourceAlreadyExistsForThisClientException}
|
||||
* @error {@link ResourceCreateContentionException}
|
||||
* @error {@link ContactFlowUtils.BadInternationalizedPostalInfoException}
|
||||
* @error {@link ContactFlowUtils.DeclineContactDisclosureFieldDisallowedPolicyException}
|
||||
*/
|
||||
@Deprecated
|
||||
@ReportingSpec(ActivityReportField.CONTACT_CREATE)
|
||||
public final class ContactCreateFlow implements MutatingFlow {
|
||||
|
||||
@Inject ResourceCommand resourceCommand;
|
||||
@Inject ExtensionManager extensionManager;
|
||||
@Inject @RegistrarId String registrarId;
|
||||
@Inject @TargetId String targetId;
|
||||
@Inject ContactHistory.Builder historyBuilder;
|
||||
@Inject EppResponse.Builder responseBuilder;
|
||||
@Inject @Config("contactAndHostRoidSuffix") String roidSuffix;
|
||||
public final class ContactCreateFlow extends ContactsProhibitedFlow {
|
||||
@Inject ContactCreateFlow() {}
|
||||
|
||||
@Override
|
||||
public EppResponse run() throws EppException {
|
||||
extensionManager.register(MetadataExtension.class);
|
||||
validateRegistrarIsLoggedIn(registrarId);
|
||||
extensionManager.validate();
|
||||
if (FeatureFlag.isActiveNow(MINIMUM_DATASET_CONTACTS_PROHIBITED)) {
|
||||
throw new ContactsProhibitedException();
|
||||
}
|
||||
Create command = (Create) resourceCommand;
|
||||
DateTime now = tm().getTransactionTime();
|
||||
verifyResourceDoesNotExist(Contact.class, targetId, now, registrarId);
|
||||
Contact newContact =
|
||||
new Contact.Builder()
|
||||
.setContactId(targetId)
|
||||
.setAuthInfo(command.getAuthInfo())
|
||||
.setCreationRegistrarId(registrarId)
|
||||
.setPersistedCurrentSponsorRegistrarId(registrarId)
|
||||
.setRepoId(createRepoId(tm().allocateId(), roidSuffix))
|
||||
.setFaxNumber(command.getFax())
|
||||
.setVoiceNumber(command.getVoice())
|
||||
.setDisclose(command.getDisclose())
|
||||
.setEmailAddress(command.getEmail())
|
||||
.setInternationalizedPostalInfo(command.getInternationalizedPostalInfo())
|
||||
.setLocalizedPostalInfo(command.getLocalizedPostalInfo())
|
||||
.build();
|
||||
validateAsciiPostalInfo(newContact.getInternationalizedPostalInfo());
|
||||
validateContactAgainstPolicy(newContact);
|
||||
historyBuilder
|
||||
.setType(HistoryEntry.Type.CONTACT_CREATE)
|
||||
.setXmlBytes(null) // We don't want to store contact details in the history entry.
|
||||
.setContact(newContact);
|
||||
tm().insertAll(ImmutableSet.of(newContact, historyBuilder.build()));
|
||||
return responseBuilder
|
||||
.setResData(ContactCreateData.create(newContact.getContactId(), now))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,97 +14,20 @@
|
||||
|
||||
package google.registry.flows.contact;
|
||||
|
||||
import static google.registry.flows.FlowUtils.DELETE_PROHIBITED_STATUSES;
|
||||
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
|
||||
import static google.registry.flows.ResourceFlowUtils.checkLinkedDomains;
|
||||
import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyNoDisallowedStatuses;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyOptionalAuthInfo;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyResourceOwnership;
|
||||
import static google.registry.model.ResourceTransferUtils.denyPendingTransfer;
|
||||
import static google.registry.model.ResourceTransferUtils.handlePendingTransferOnDelete;
|
||||
import static google.registry.model.eppoutput.Result.Code.SUCCESS;
|
||||
import static google.registry.model.transfer.TransferStatus.SERVER_CANCELLED;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.Superuser;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.MutatingFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactHistory;
|
||||
import google.registry.model.domain.metadata.MetadataExtension;
|
||||
import google.registry.model.eppcommon.AuthInfo;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.eppcommon.Trid;
|
||||
import google.registry.model.eppoutput.EppResponse;
|
||||
import google.registry.model.reporting.HistoryEntry.Type;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Optional;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* An EPP flow that deletes a contact.
|
||||
* An EPP flow that is meant to delete a contact.
|
||||
*
|
||||
* <p>Contacts that are in use by any domain cannot be deleted. The flow may return immediately if a
|
||||
* quick smoke check determines that deletion is impossible due to an existing reference. However, a
|
||||
* successful delete will always be asynchronous, as all existing domains must be checked for
|
||||
* references to the host before the deletion is allowed to proceed. A poll message will be written
|
||||
* with the success or failure message when the process is complete.
|
||||
*
|
||||
* @error {@link google.registry.flows.FlowUtils.NotLoggedInException}
|
||||
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException}
|
||||
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException}
|
||||
* @error {@link google.registry.flows.exceptions.ResourceStatusProhibitsOperationException}
|
||||
* @error {@link google.registry.flows.exceptions.ResourceToDeleteIsReferencedException}
|
||||
* @error {@link ContactsProhibitedException}
|
||||
*/
|
||||
@Deprecated
|
||||
@ReportingSpec(ActivityReportField.CONTACT_DELETE)
|
||||
public final class ContactDeleteFlow implements MutatingFlow {
|
||||
|
||||
@Inject ExtensionManager extensionManager;
|
||||
@Inject @RegistrarId String registrarId;
|
||||
@Inject @TargetId String targetId;
|
||||
@Inject Trid trid;
|
||||
@Inject @Superuser boolean isSuperuser;
|
||||
@Inject Optional<AuthInfo> authInfo;
|
||||
@Inject ContactHistory.Builder historyBuilder;
|
||||
@Inject EppResponse.Builder responseBuilder;
|
||||
|
||||
public final class ContactDeleteFlow extends ContactsProhibitedFlow {
|
||||
@Inject
|
||||
ContactDeleteFlow() {}
|
||||
|
||||
@Override
|
||||
public EppResponse run() throws EppException {
|
||||
extensionManager.register(MetadataExtension.class);
|
||||
validateRegistrarIsLoggedIn(registrarId);
|
||||
extensionManager.validate();
|
||||
DateTime now = tm().getTransactionTime();
|
||||
checkLinkedDomains(targetId, now, Contact.class);
|
||||
Contact existingContact = loadAndVerifyExistence(Contact.class, targetId, now);
|
||||
verifyOptionalAuthInfo(authInfo, existingContact);
|
||||
verifyNoDisallowedStatuses(existingContact, ImmutableSet.of(StatusValue.PENDING_DELETE));
|
||||
if (!isSuperuser) {
|
||||
verifyNoDisallowedStatuses(existingContact, DELETE_PROHIBITED_STATUSES);
|
||||
verifyResourceOwnership(registrarId, existingContact);
|
||||
}
|
||||
// Handle pending transfers on contact deletion.
|
||||
Contact newContact =
|
||||
existingContact.getStatusValues().contains(StatusValue.PENDING_TRANSFER)
|
||||
? denyPendingTransfer(existingContact, SERVER_CANCELLED, now, registrarId)
|
||||
: existingContact;
|
||||
// Wipe out PII on contact deletion.
|
||||
newContact =
|
||||
newContact.asBuilder().wipeOut().setStatusValues(null).setDeletionTime(now).build();
|
||||
ContactHistory contactHistory =
|
||||
historyBuilder.setType(Type.CONTACT_DELETE).setContact(newContact).build();
|
||||
handlePendingTransferOnDelete(existingContact, newContact, now, contactHistory);
|
||||
tm().insert(contactHistory);
|
||||
tm().update(newContact);
|
||||
return responseBuilder.setResultFromCode(SUCCESS).build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,126 +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.flows.contact;
|
||||
|
||||
import static google.registry.model.contact.PostalInfo.Type.INTERNATIONALIZED;
|
||||
|
||||
import com.google.common.base.CharMatcher;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Sets;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.EppException.ParameterValuePolicyErrorException;
|
||||
import google.registry.flows.EppException.ParameterValueSyntaxErrorException;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactAddress;
|
||||
import google.registry.model.contact.PostalInfo;
|
||||
import google.registry.model.poll.PendingActionNotificationResponse.ContactPendingActionNotificationResponse;
|
||||
import google.registry.model.poll.PollMessage;
|
||||
import google.registry.model.reporting.HistoryEntry.HistoryEntryId;
|
||||
import google.registry.model.transfer.TransferData;
|
||||
import google.registry.model.transfer.TransferResponse.ContactTransferResponse;
|
||||
import java.util.Set;
|
||||
import javax.annotation.Nullable;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** Static utility functions for contact flows. */
|
||||
public class ContactFlowUtils {
|
||||
/** Check that an internationalized postal info has only ascii characters. */
|
||||
static void validateAsciiPostalInfo(@Nullable PostalInfo internationalized) throws EppException {
|
||||
if (internationalized != null) {
|
||||
Preconditions.checkState(INTERNATIONALIZED.equals(internationalized.getType()));
|
||||
ContactAddress address = internationalized.getAddress();
|
||||
Set<String> fields = Sets.newHashSet(
|
||||
internationalized.getName(),
|
||||
internationalized.getOrg(),
|
||||
address.getCity(),
|
||||
address.getCountryCode(),
|
||||
address.getState(),
|
||||
address.getZip());
|
||||
fields.addAll(address.getStreet());
|
||||
for (String field : fields) {
|
||||
if (field != null && !CharMatcher.ascii().matchesAllOf(field)) {
|
||||
throw new BadInternationalizedPostalInfoException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Check contact's state against server policy. */
|
||||
static void validateContactAgainstPolicy(Contact contact) throws EppException {
|
||||
if (contact.getDisclose() != null && !contact.getDisclose().getFlag()) {
|
||||
throw new DeclineContactDisclosureFieldDisallowedPolicyException();
|
||||
}
|
||||
}
|
||||
|
||||
/** Create a poll message for the gaining client in a transfer. */
|
||||
static PollMessage createGainingTransferPollMessage(
|
||||
String targetId, TransferData transferData, DateTime now, HistoryEntryId contactHistoryId) {
|
||||
return new PollMessage.OneTime.Builder()
|
||||
.setRegistrarId(transferData.getGainingRegistrarId())
|
||||
.setEventTime(transferData.getPendingTransferExpirationTime())
|
||||
.setMsg(transferData.getTransferStatus().getMessage())
|
||||
.setResponseData(
|
||||
ImmutableList.of(
|
||||
createTransferResponse(targetId, transferData),
|
||||
ContactPendingActionNotificationResponse.create(
|
||||
targetId,
|
||||
transferData.getTransferStatus().isApproved(),
|
||||
transferData.getTransferRequestTrid(),
|
||||
now)))
|
||||
.setContactHistoryId(contactHistoryId)
|
||||
.build();
|
||||
}
|
||||
|
||||
/** Create a poll message for the losing client in a transfer. */
|
||||
static PollMessage createLosingTransferPollMessage(
|
||||
String targetId, TransferData transferData, HistoryEntryId contactHistoryId) {
|
||||
return new PollMessage.OneTime.Builder()
|
||||
.setRegistrarId(transferData.getLosingRegistrarId())
|
||||
.setEventTime(transferData.getPendingTransferExpirationTime())
|
||||
.setMsg(transferData.getTransferStatus().getMessage())
|
||||
.setResponseData(ImmutableList.of(createTransferResponse(targetId, transferData)))
|
||||
.setContactHistoryId(contactHistoryId)
|
||||
.build();
|
||||
}
|
||||
|
||||
/** Create a {@link ContactTransferResponse} off of the info in a {@link TransferData}. */
|
||||
static ContactTransferResponse createTransferResponse(
|
||||
String targetId, TransferData transferData) {
|
||||
return new ContactTransferResponse.Builder()
|
||||
.setContactId(targetId)
|
||||
.setGainingRegistrarId(transferData.getGainingRegistrarId())
|
||||
.setLosingRegistrarId(transferData.getLosingRegistrarId())
|
||||
.setPendingTransferExpirationTime(transferData.getPendingTransferExpirationTime())
|
||||
.setTransferRequestTime(transferData.getTransferRequestTime())
|
||||
.setTransferStatus(transferData.getTransferStatus())
|
||||
.build();
|
||||
}
|
||||
|
||||
/** Declining contact disclosure is disallowed by server policy. */
|
||||
static class DeclineContactDisclosureFieldDisallowedPolicyException
|
||||
extends ParameterValuePolicyErrorException {
|
||||
public DeclineContactDisclosureFieldDisallowedPolicyException() {
|
||||
super("Declining contact disclosure is disallowed by server policy.");
|
||||
}
|
||||
}
|
||||
|
||||
/** Internationalized postal infos can only contain ASCII characters. */
|
||||
static class BadInternationalizedPostalInfoException extends ParameterValueSyntaxErrorException {
|
||||
public BadInternationalizedPostalInfoException() {
|
||||
super("Internationalized postal infos can only contain ASCII characters");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,91 +14,20 @@
|
||||
|
||||
package google.registry.flows.contact;
|
||||
|
||||
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
|
||||
import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyResourceOwnership;
|
||||
import static google.registry.model.EppResourceUtils.isLinked;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.Superuser;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactInfoData;
|
||||
import google.registry.model.eppcommon.AuthInfo;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.eppoutput.EppResponse;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||
import google.registry.util.Clock;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Optional;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* An EPP flow that returns information about a contact.
|
||||
* An EPP flow that is meant to return information about a contact.
|
||||
*
|
||||
* <p>The response includes the contact's postal info, phone numbers, emails, the authInfo which can
|
||||
* be used to request a transfer and the details of the contact's most recent transfer if it has
|
||||
* ever been transferred. Any registrar can see any contact's information, but the authInfo is only
|
||||
* visible to the registrar that owns the contact or to a registrar that already supplied it.
|
||||
*
|
||||
* @error {@link google.registry.flows.FlowUtils.NotLoggedInException}
|
||||
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException}
|
||||
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException}
|
||||
* @error {@link ContactsProhibitedException}
|
||||
*/
|
||||
@Deprecated
|
||||
@ReportingSpec(ActivityReportField.CONTACT_INFO)
|
||||
public final class ContactInfoFlow implements TransactionalFlow {
|
||||
|
||||
@Inject ExtensionManager extensionManager;
|
||||
@Inject Clock clock;
|
||||
@Inject @RegistrarId String registrarId;
|
||||
@Inject @TargetId String targetId;
|
||||
@Inject Optional<AuthInfo> authInfo;
|
||||
@Inject @Superuser boolean isSuperuser;
|
||||
@Inject EppResponse.Builder responseBuilder;
|
||||
|
||||
public final class ContactInfoFlow extends ContactsProhibitedFlow {
|
||||
@Inject
|
||||
ContactInfoFlow() {}
|
||||
|
||||
@Override
|
||||
public EppResponse run() throws EppException {
|
||||
DateTime now = clock.nowUtc();
|
||||
validateRegistrarIsLoggedIn(registrarId);
|
||||
extensionManager.validate(); // There are no legal extensions for this flow.
|
||||
Contact contact = loadAndVerifyExistence(Contact.class, targetId, now);
|
||||
if (!isSuperuser) {
|
||||
verifyResourceOwnership(registrarId, contact);
|
||||
}
|
||||
boolean includeAuthInfo =
|
||||
registrarId.equals(contact.getCurrentSponsorRegistrarId()) || authInfo.isPresent();
|
||||
ImmutableSet.Builder<StatusValue> statusValues = new ImmutableSet.Builder<>();
|
||||
statusValues.addAll(contact.getStatusValues());
|
||||
if (isLinked(contact.createVKey(), now)) {
|
||||
statusValues.add(StatusValue.LINKED);
|
||||
}
|
||||
return responseBuilder
|
||||
.setResData(
|
||||
ContactInfoData.newBuilder()
|
||||
.setContactId(contact.getContactId())
|
||||
.setRepoId(contact.getRepoId())
|
||||
.setStatusValues(statusValues.build())
|
||||
.setPostalInfos(contact.getPostalInfosAsList())
|
||||
.setVoiceNumber(contact.getVoiceNumber())
|
||||
.setFaxNumber(contact.getFaxNumber())
|
||||
.setEmailAddress(contact.getEmailAddress())
|
||||
.setCurrentSponsorRegistrarId(contact.getCurrentSponsorRegistrarId())
|
||||
.setCreationRegistrarId(contact.getCreationRegistrarId())
|
||||
.setCreationTime(contact.getCreationTime())
|
||||
.setLastEppUpdateRegistrarId(contact.getLastEppUpdateRegistrarId())
|
||||
.setLastEppUpdateTime(contact.getLastEppUpdateTime())
|
||||
.setLastTransferTime(contact.getLastTransferTime())
|
||||
.setAuthInfo(includeAuthInfo ? contact.getAuthInfo() : null)
|
||||
.setDisclose(contact.getDisclose())
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,92 +14,19 @@
|
||||
|
||||
package google.registry.flows.contact;
|
||||
|
||||
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
|
||||
import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyHasPendingTransfer;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyOptionalAuthInfo;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyResourceOwnership;
|
||||
import static google.registry.flows.contact.ContactFlowUtils.createGainingTransferPollMessage;
|
||||
import static google.registry.flows.contact.ContactFlowUtils.createTransferResponse;
|
||||
import static google.registry.model.ResourceTransferUtils.approvePendingTransfer;
|
||||
import static google.registry.model.reporting.HistoryEntry.Type.CONTACT_TRANSFER_APPROVE;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.MutatingFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactHistory;
|
||||
import google.registry.model.domain.metadata.MetadataExtension;
|
||||
import google.registry.model.eppcommon.AuthInfo;
|
||||
import google.registry.model.eppinput.ResourceCommand;
|
||||
import google.registry.model.eppoutput.EppResponse;
|
||||
import google.registry.model.poll.PollMessage;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||
import google.registry.model.transfer.TransferStatus;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Optional;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* An EPP flow that approves a pending transfer on a contact.
|
||||
* An EPP flow that is meant to approve a pending transfer on a contact.
|
||||
*
|
||||
* <p>The "gaining" registrar requests a transfer from the "losing" (aka current) registrar. The
|
||||
* losing registrar has a "transfer" time period to respond (by default five days) after which the
|
||||
* transfer is automatically approved. Within that window, this flow allows the losing client to
|
||||
* explicitly approve the transfer request, which then becomes effective immediately.
|
||||
*
|
||||
* @error {@link google.registry.flows.FlowUtils.NotLoggedInException}
|
||||
* @error {@link google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException}
|
||||
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException}
|
||||
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException}
|
||||
* @error {@link google.registry.flows.exceptions.NotPendingTransferException}
|
||||
* @error {@link ContactsProhibitedException}
|
||||
*/
|
||||
@Deprecated
|
||||
@ReportingSpec(ActivityReportField.CONTACT_TRANSFER_APPROVE)
|
||||
public final class ContactTransferApproveFlow implements MutatingFlow {
|
||||
|
||||
@Inject ResourceCommand resourceCommand;
|
||||
@Inject ExtensionManager extensionManager;
|
||||
@Inject @RegistrarId String registrarId;
|
||||
@Inject @TargetId String targetId;
|
||||
@Inject Optional<AuthInfo> authInfo;
|
||||
@Inject ContactHistory.Builder historyBuilder;
|
||||
@Inject EppResponse.Builder responseBuilder;
|
||||
public final class ContactTransferApproveFlow extends ContactsProhibitedFlow {
|
||||
@Inject ContactTransferApproveFlow() {}
|
||||
|
||||
/**
|
||||
* The logic in this flow, which handles client approvals, very closely parallels the logic in
|
||||
* {@link Contact#cloneProjectedAtTime} which handles implicit server approvals.
|
||||
*/
|
||||
@Override
|
||||
public EppResponse run() throws EppException {
|
||||
extensionManager.register(MetadataExtension.class);
|
||||
validateRegistrarIsLoggedIn(registrarId);
|
||||
extensionManager.validate();
|
||||
DateTime now = tm().getTransactionTime();
|
||||
Contact existingContact = loadAndVerifyExistence(Contact.class, targetId, now);
|
||||
verifyOptionalAuthInfo(authInfo, existingContact);
|
||||
verifyHasPendingTransfer(existingContact);
|
||||
verifyResourceOwnership(registrarId, existingContact);
|
||||
Contact newContact =
|
||||
approvePendingTransfer(existingContact, TransferStatus.CLIENT_APPROVED, now);
|
||||
ContactHistory contactHistory =
|
||||
historyBuilder.setType(CONTACT_TRANSFER_APPROVE).setContact(newContact).build();
|
||||
// Create a poll message for the gaining client.
|
||||
PollMessage gainingPollMessage =
|
||||
createGainingTransferPollMessage(
|
||||
targetId, newContact.getTransferData(), now, contactHistory.getHistoryEntryId());
|
||||
tm().insertAll(ImmutableSet.of(contactHistory, gainingPollMessage));
|
||||
tm().update(newContact);
|
||||
// Delete the billing event and poll messages that were written in case the transfer would have
|
||||
// been implicitly server approved.
|
||||
tm().delete(existingContact.getTransferData().getServerApproveEntities());
|
||||
return responseBuilder
|
||||
.setResData(createTransferResponse(targetId, newContact.getTransferData()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,88 +14,19 @@
|
||||
|
||||
package google.registry.flows.contact;
|
||||
|
||||
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
|
||||
import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyHasPendingTransfer;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyOptionalAuthInfo;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyTransferInitiator;
|
||||
import static google.registry.flows.contact.ContactFlowUtils.createLosingTransferPollMessage;
|
||||
import static google.registry.flows.contact.ContactFlowUtils.createTransferResponse;
|
||||
import static google.registry.model.ResourceTransferUtils.denyPendingTransfer;
|
||||
import static google.registry.model.reporting.HistoryEntry.Type.CONTACT_TRANSFER_CANCEL;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.MutatingFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactHistory;
|
||||
import google.registry.model.domain.metadata.MetadataExtension;
|
||||
import google.registry.model.eppcommon.AuthInfo;
|
||||
import google.registry.model.eppinput.ResourceCommand;
|
||||
import google.registry.model.eppoutput.EppResponse;
|
||||
import google.registry.model.poll.PollMessage;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||
import google.registry.model.transfer.TransferStatus;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Optional;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* An EPP flow that cancels a pending transfer on a contact.
|
||||
* An EPP flow that is meant to cancel a pending transfer on a contact.
|
||||
*
|
||||
* <p>The "gaining" registrar requests a transfer from the "losing" (aka current) registrar. The
|
||||
* losing registrar has a "transfer" time period to respond (by default five days) after which the
|
||||
* transfer is automatically approved. Within that window, this flow allows the gaining client to
|
||||
* withdraw the transfer request.
|
||||
*
|
||||
* @error {@link google.registry.flows.FlowUtils.NotLoggedInException}
|
||||
* @error {@link google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException}
|
||||
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException}
|
||||
* @error {@link google.registry.flows.exceptions.NotPendingTransferException}
|
||||
* @error {@link google.registry.flows.exceptions.NotTransferInitiatorException}
|
||||
* @error {@link ContactsProhibitedException}
|
||||
*/
|
||||
@Deprecated
|
||||
@ReportingSpec(ActivityReportField.CONTACT_TRANSFER_CANCEL)
|
||||
public final class ContactTransferCancelFlow implements MutatingFlow {
|
||||
|
||||
@Inject ResourceCommand resourceCommand;
|
||||
@Inject ExtensionManager extensionManager;
|
||||
@Inject Optional<AuthInfo> authInfo;
|
||||
@Inject @RegistrarId String registrarId;
|
||||
@Inject @TargetId String targetId;
|
||||
@Inject ContactHistory.Builder historyBuilder;
|
||||
@Inject EppResponse.Builder responseBuilder;
|
||||
public final class ContactTransferCancelFlow extends ContactsProhibitedFlow {
|
||||
@Inject ContactTransferCancelFlow() {}
|
||||
|
||||
@Override
|
||||
public EppResponse run() throws EppException {
|
||||
extensionManager.register(MetadataExtension.class);
|
||||
validateRegistrarIsLoggedIn(registrarId);
|
||||
extensionManager.validate();
|
||||
DateTime now = tm().getTransactionTime();
|
||||
Contact existingContact = loadAndVerifyExistence(Contact.class, targetId, now);
|
||||
verifyOptionalAuthInfo(authInfo, existingContact);
|
||||
verifyHasPendingTransfer(existingContact);
|
||||
verifyTransferInitiator(registrarId, existingContact);
|
||||
Contact newContact =
|
||||
denyPendingTransfer(existingContact, TransferStatus.CLIENT_CANCELLED, now, registrarId);
|
||||
ContactHistory contactHistory =
|
||||
historyBuilder.setType(CONTACT_TRANSFER_CANCEL).setContact(newContact).build();
|
||||
// Create a poll message for the losing client.
|
||||
PollMessage losingPollMessage =
|
||||
createLosingTransferPollMessage(
|
||||
targetId, newContact.getTransferData(), contactHistory.getHistoryEntryId());
|
||||
tm().insertAll(ImmutableSet.of(contactHistory, losingPollMessage));
|
||||
tm().update(newContact);
|
||||
// Delete the billing event and poll messages that were written in case the transfer would have
|
||||
// been implicitly server approved.
|
||||
tm().delete(existingContact.getTransferData().getServerApproveEntities());
|
||||
return responseBuilder
|
||||
.setResData(createTransferResponse(targetId, newContact.getTransferData()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,74 +14,19 @@
|
||||
|
||||
package google.registry.flows.contact;
|
||||
|
||||
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
|
||||
import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyOptionalAuthInfo;
|
||||
import static google.registry.flows.contact.ContactFlowUtils.createTransferResponse;
|
||||
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.flows.exceptions.NoTransferHistoryToQueryException;
|
||||
import google.registry.flows.exceptions.NotAuthorizedToViewTransferException;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.eppcommon.AuthInfo;
|
||||
import google.registry.model.eppoutput.EppResponse;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||
import google.registry.util.Clock;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* An EPP flow that queries a pending transfer on a contact.
|
||||
* An EPP flow that is meant to query a pending transfer on a contact.
|
||||
*
|
||||
* <p>The "gaining" registrar requests a transfer from the "losing" (aka current) registrar. The
|
||||
* losing registrar has a "transfer" time period to respond (by default five days) after which the
|
||||
* transfer is automatically approved. This flow can be used by the gaining or losing registrars (or
|
||||
* anyone with the correct authId) to see the status of a transfer, which may still be pending or
|
||||
* may have been approved, rejected, cancelled or implicitly approved by virtue of the transfer
|
||||
* period expiring.
|
||||
*
|
||||
* @error {@link google.registry.flows.FlowUtils.NotLoggedInException}
|
||||
* @error {@link google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException}
|
||||
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException}
|
||||
* @error {@link google.registry.flows.exceptions.NoTransferHistoryToQueryException}
|
||||
* @error {@link google.registry.flows.exceptions.NotAuthorizedToViewTransferException}
|
||||
* @error {@link ContactsProhibitedException}
|
||||
*/
|
||||
@Deprecated
|
||||
@ReportingSpec(ActivityReportField.CONTACT_TRANSFER_QUERY)
|
||||
public final class ContactTransferQueryFlow implements TransactionalFlow {
|
||||
|
||||
@Inject ExtensionManager extensionManager;
|
||||
@Inject Optional<AuthInfo> authInfo;
|
||||
@Inject @RegistrarId String registrarId;
|
||||
@Inject @TargetId String targetId;
|
||||
@Inject Clock clock;
|
||||
@Inject EppResponse.Builder responseBuilder;
|
||||
public final class ContactTransferQueryFlow extends ContactsProhibitedFlow {
|
||||
@Inject ContactTransferQueryFlow() {}
|
||||
|
||||
@Override
|
||||
public EppResponse run() throws EppException {
|
||||
validateRegistrarIsLoggedIn(registrarId);
|
||||
extensionManager.validate(); // There are no legal extensions for this flow.
|
||||
Contact contact = loadAndVerifyExistence(Contact.class, targetId, clock.nowUtc());
|
||||
verifyOptionalAuthInfo(authInfo, contact);
|
||||
// Most of the fields on the transfer response are required, so there's no way to return valid
|
||||
// XML if the object has never been transferred (and hence the fields aren't populated).
|
||||
if (contact.getTransferData().getTransferStatus() == null) {
|
||||
throw new NoTransferHistoryToQueryException();
|
||||
}
|
||||
// Note that the authorization info on the command (if present) has already been verified. If
|
||||
// it's present, then the other checks are unnecessary.
|
||||
if (authInfo.isEmpty()
|
||||
&& !registrarId.equals(contact.getTransferData().getGainingRegistrarId())
|
||||
&& !registrarId.equals(contact.getTransferData().getLosingRegistrarId())) {
|
||||
throw new NotAuthorizedToViewTransferException();
|
||||
}
|
||||
return responseBuilder
|
||||
.setResData(createTransferResponse(targetId, contact.getTransferData()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,85 +14,19 @@
|
||||
|
||||
package google.registry.flows.contact;
|
||||
|
||||
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
|
||||
import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyHasPendingTransfer;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyOptionalAuthInfo;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyResourceOwnership;
|
||||
import static google.registry.flows.contact.ContactFlowUtils.createGainingTransferPollMessage;
|
||||
import static google.registry.flows.contact.ContactFlowUtils.createTransferResponse;
|
||||
import static google.registry.model.ResourceTransferUtils.denyPendingTransfer;
|
||||
import static google.registry.model.reporting.HistoryEntry.Type.CONTACT_TRANSFER_REJECT;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.MutatingFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactHistory;
|
||||
import google.registry.model.domain.metadata.MetadataExtension;
|
||||
import google.registry.model.eppcommon.AuthInfo;
|
||||
import google.registry.model.eppoutput.EppResponse;
|
||||
import google.registry.model.poll.PollMessage;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||
import google.registry.model.transfer.TransferStatus;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Optional;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* An EPP flow that rejects a pending transfer on a contact.
|
||||
* An EPP flow that is meant to reject a pending transfer on a contact.
|
||||
*
|
||||
* <p>The "gaining" registrar requests a transfer from the "losing" (aka current) registrar. The
|
||||
* losing registrar has a "transfer" time period to respond (by default five days) after which the
|
||||
* transfer is automatically approved. Within that window, this flow allows the losing client to
|
||||
* reject the transfer request.
|
||||
*
|
||||
* @error {@link google.registry.flows.FlowUtils.NotLoggedInException}
|
||||
* @error {@link google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException}
|
||||
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException}
|
||||
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException}
|
||||
* @error {@link google.registry.flows.exceptions.NotPendingTransferException}
|
||||
* @error {@link ContactsProhibitedException}
|
||||
*/
|
||||
@Deprecated
|
||||
@ReportingSpec(ActivityReportField.CONTACT_TRANSFER_REJECT)
|
||||
public final class ContactTransferRejectFlow implements MutatingFlow {
|
||||
|
||||
@Inject ExtensionManager extensionManager;
|
||||
@Inject Optional<AuthInfo> authInfo;
|
||||
@Inject @RegistrarId String registrarId;
|
||||
@Inject @TargetId String targetId;
|
||||
@Inject ContactHistory.Builder historyBuilder;
|
||||
@Inject EppResponse.Builder responseBuilder;
|
||||
public final class ContactTransferRejectFlow extends ContactsProhibitedFlow {
|
||||
@Inject ContactTransferRejectFlow() {}
|
||||
|
||||
@Override
|
||||
public EppResponse run() throws EppException {
|
||||
extensionManager.register(MetadataExtension.class);
|
||||
validateRegistrarIsLoggedIn(registrarId);
|
||||
extensionManager.validate();
|
||||
DateTime now = tm().getTransactionTime();
|
||||
Contact existingContact = loadAndVerifyExistence(Contact.class, targetId, now);
|
||||
verifyOptionalAuthInfo(authInfo, existingContact);
|
||||
verifyHasPendingTransfer(existingContact);
|
||||
verifyResourceOwnership(registrarId, existingContact);
|
||||
Contact newContact =
|
||||
denyPendingTransfer(existingContact, TransferStatus.CLIENT_REJECTED, now, registrarId);
|
||||
ContactHistory contactHistory =
|
||||
historyBuilder.setType(CONTACT_TRANSFER_REJECT).setContact(newContact).build();
|
||||
PollMessage gainingPollMessage =
|
||||
createGainingTransferPollMessage(
|
||||
targetId, newContact.getTransferData(), now, contactHistory.getHistoryEntryId());
|
||||
tm().insertAll(ImmutableSet.of(contactHistory, gainingPollMessage));
|
||||
tm().update(newContact);
|
||||
// Delete the billing event and poll messages that were written in case the transfer would have
|
||||
// been implicitly server approved.
|
||||
tm().delete(existingContact.getTransferData().getServerApproveEntities());
|
||||
return responseBuilder
|
||||
.setResData(createTransferResponse(targetId, newContact.getTransferData()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,162 +14,20 @@
|
||||
|
||||
package google.registry.flows.contact;
|
||||
|
||||
import static google.registry.flows.FlowUtils.createHistoryEntryId;
|
||||
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
|
||||
import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyAuthInfo;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyAuthInfoPresentForResourceTransfer;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyNoDisallowedStatuses;
|
||||
import static google.registry.flows.contact.ContactFlowUtils.createGainingTransferPollMessage;
|
||||
import static google.registry.flows.contact.ContactFlowUtils.createLosingTransferPollMessage;
|
||||
import static google.registry.flows.contact.ContactFlowUtils.createTransferResponse;
|
||||
import static google.registry.model.eppoutput.Result.Code.SUCCESS_WITH_ACTION_PENDING;
|
||||
import static google.registry.model.reporting.HistoryEntry.Type.CONTACT_TRANSFER_REQUEST;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.MutatingFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.flows.exceptions.AlreadyPendingTransferException;
|
||||
import google.registry.flows.exceptions.ObjectAlreadySponsoredException;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactHistory;
|
||||
import google.registry.model.domain.metadata.MetadataExtension;
|
||||
import google.registry.model.eppcommon.AuthInfo;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.eppcommon.Trid;
|
||||
import google.registry.model.eppoutput.EppResponse;
|
||||
import google.registry.model.poll.PollMessage;
|
||||
import google.registry.model.reporting.HistoryEntry.HistoryEntryId;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||
import google.registry.model.transfer.ContactTransferData;
|
||||
import google.registry.model.transfer.TransferStatus;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Optional;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Duration;
|
||||
|
||||
/**
|
||||
* An EPP flow that requests a transfer on a contact.
|
||||
* An EPP flow that is meant to request a transfer on a contact.
|
||||
*
|
||||
* <p>The "gaining" registrar requests a transfer from the "losing" (aka current) registrar. The
|
||||
* losing registrar has a "transfer" time period to respond (by default five days) after which the
|
||||
* transfer is automatically approved. Within that window, the transfer might be approved explicitly
|
||||
* by the losing registrar or rejected, and the gaining registrar can also cancel the transfer
|
||||
* request.
|
||||
*
|
||||
* @error {@link google.registry.flows.FlowUtils.NotLoggedInException}
|
||||
* @error {@link google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException}
|
||||
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException}
|
||||
* @error {@link google.registry.flows.exceptions.AlreadyPendingTransferException}
|
||||
* @error {@link google.registry.flows.exceptions.MissingTransferRequestAuthInfoException}
|
||||
* @error {@link google.registry.flows.exceptions.ObjectAlreadySponsoredException}
|
||||
* @error {@link google.registry.flows.exceptions.ResourceStatusProhibitsOperationException}
|
||||
* @error {@link ContactsProhibitedException}
|
||||
*/
|
||||
@Deprecated
|
||||
@ReportingSpec(ActivityReportField.CONTACT_TRANSFER_REQUEST)
|
||||
public final class ContactTransferRequestFlow implements MutatingFlow {
|
||||
|
||||
private static final ImmutableSet<StatusValue> DISALLOWED_STATUSES =
|
||||
ImmutableSet.of(
|
||||
StatusValue.CLIENT_TRANSFER_PROHIBITED,
|
||||
StatusValue.PENDING_DELETE,
|
||||
StatusValue.SERVER_TRANSFER_PROHIBITED);
|
||||
|
||||
@Inject ExtensionManager extensionManager;
|
||||
@Inject Optional<AuthInfo> authInfo;
|
||||
@Inject @RegistrarId String gainingClientId;
|
||||
@Inject @TargetId String targetId;
|
||||
|
||||
@Inject
|
||||
@Config("contactAutomaticTransferLength")
|
||||
Duration automaticTransferLength;
|
||||
|
||||
@Inject ContactHistory.Builder historyBuilder;
|
||||
@Inject Trid trid;
|
||||
@Inject EppResponse.Builder responseBuilder;
|
||||
|
||||
public final class ContactTransferRequestFlow extends ContactsProhibitedFlow {
|
||||
@Inject
|
||||
ContactTransferRequestFlow() {}
|
||||
|
||||
@Override
|
||||
public EppResponse run() throws EppException {
|
||||
extensionManager.register(MetadataExtension.class);
|
||||
validateRegistrarIsLoggedIn(gainingClientId);
|
||||
extensionManager.validate();
|
||||
DateTime now = tm().getTransactionTime();
|
||||
Contact existingContact = loadAndVerifyExistence(Contact.class, targetId, now);
|
||||
verifyAuthInfoPresentForResourceTransfer(authInfo);
|
||||
verifyAuthInfo(authInfo.get(), existingContact);
|
||||
// Verify that the resource does not already have a pending transfer.
|
||||
if (TransferStatus.PENDING.equals(existingContact.getTransferData().getTransferStatus())) {
|
||||
throw new AlreadyPendingTransferException(targetId);
|
||||
}
|
||||
String losingClientId = existingContact.getCurrentSponsorRegistrarId();
|
||||
// Verify that this client doesn't already sponsor this resource.
|
||||
if (gainingClientId.equals(losingClientId)) {
|
||||
throw new ObjectAlreadySponsoredException();
|
||||
}
|
||||
verifyNoDisallowedStatuses(existingContact, DISALLOWED_STATUSES);
|
||||
|
||||
DateTime transferExpirationTime = now.plus(automaticTransferLength);
|
||||
ContactTransferData serverApproveTransferData =
|
||||
new ContactTransferData.Builder()
|
||||
.setTransferRequestTime(now)
|
||||
.setTransferRequestTrid(trid)
|
||||
.setGainingRegistrarId(gainingClientId)
|
||||
.setLosingRegistrarId(losingClientId)
|
||||
.setPendingTransferExpirationTime(transferExpirationTime)
|
||||
.setTransferStatus(TransferStatus.SERVER_APPROVED)
|
||||
.build();
|
||||
HistoryEntryId contactHistoryId = createHistoryEntryId(existingContact);
|
||||
historyBuilder
|
||||
.setRevisionId(contactHistoryId.getRevisionId())
|
||||
.setType(CONTACT_TRANSFER_REQUEST);
|
||||
// If the transfer is server approved, this message will be sent to the losing registrar. */
|
||||
PollMessage serverApproveLosingPollMessage =
|
||||
createLosingTransferPollMessage(targetId, serverApproveTransferData, contactHistoryId);
|
||||
// If the transfer is server approved, this message will be sent to the gaining registrar. */
|
||||
PollMessage serverApproveGainingPollMessage =
|
||||
createGainingTransferPollMessage(
|
||||
targetId, serverApproveTransferData, now, contactHistoryId);
|
||||
ContactTransferData pendingTransferData =
|
||||
serverApproveTransferData
|
||||
.asBuilder()
|
||||
.setTransferStatus(TransferStatus.PENDING)
|
||||
.setServerApproveEntities(
|
||||
serverApproveGainingPollMessage.getContactRepoId(),
|
||||
contactHistoryId.getRevisionId(),
|
||||
ImmutableSet.of(
|
||||
serverApproveGainingPollMessage.createVKey(),
|
||||
serverApproveLosingPollMessage.createVKey()))
|
||||
.build();
|
||||
// When a transfer is requested, a poll message is created to notify the losing registrar.
|
||||
PollMessage requestPollMessage =
|
||||
createLosingTransferPollMessage(targetId, pendingTransferData, contactHistoryId)
|
||||
.asBuilder()
|
||||
.setEventTime(now) // Unlike the serverApprove messages, this applies immediately.
|
||||
.build();
|
||||
Contact newContact =
|
||||
existingContact
|
||||
.asBuilder()
|
||||
.setTransferData(pendingTransferData)
|
||||
.addStatusValue(StatusValue.PENDING_TRANSFER)
|
||||
.build();
|
||||
tm().update(newContact);
|
||||
tm().insertAll(
|
||||
ImmutableSet.of(
|
||||
historyBuilder.setContact(newContact).build(),
|
||||
requestPollMessage,
|
||||
serverApproveGainingPollMessage,
|
||||
serverApproveLosingPollMessage));
|
||||
return responseBuilder
|
||||
.setResultFromCode(SUCCESS_WITH_ACTION_PENDING)
|
||||
.setResData(createTransferResponse(targetId, newContact.getTransferData()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,158 +14,19 @@
|
||||
|
||||
package google.registry.flows.contact;
|
||||
|
||||
import static com.google.common.collect.Sets.union;
|
||||
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
|
||||
import static google.registry.flows.ResourceFlowUtils.checkSameValuesNotAddedAndRemoved;
|
||||
import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyAllStatusesAreClientSettable;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyNoDisallowedStatuses;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyOptionalAuthInfo;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyResourceOwnership;
|
||||
import static google.registry.flows.contact.ContactFlowUtils.validateAsciiPostalInfo;
|
||||
import static google.registry.flows.contact.ContactFlowUtils.validateContactAgainstPolicy;
|
||||
import static google.registry.model.common.FeatureFlag.FeatureName.MINIMUM_DATASET_CONTACTS_PROHIBITED;
|
||||
import static google.registry.model.reporting.HistoryEntry.Type.CONTACT_UPDATE;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.Superuser;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.MutatingFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
import google.registry.flows.exceptions.ResourceHasClientUpdateProhibitedException;
|
||||
import google.registry.model.common.FeatureFlag;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactCommand.Update;
|
||||
import google.registry.model.contact.ContactCommand.Update.Change;
|
||||
import google.registry.model.contact.ContactHistory;
|
||||
import google.registry.model.contact.PostalInfo;
|
||||
import google.registry.model.domain.metadata.MetadataExtension;
|
||||
import google.registry.model.eppcommon.AuthInfo;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.eppinput.ResourceCommand;
|
||||
import google.registry.model.eppoutput.EppResponse;
|
||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Optional;
|
||||
import javax.annotation.Nullable;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* An EPP flow that updates a contact.
|
||||
* An EPP flow meant to update a contact.
|
||||
*
|
||||
* @error {@link ContactsProhibitedException}
|
||||
* @error {@link google.registry.flows.FlowUtils.NotLoggedInException}
|
||||
* @error {@link google.registry.flows.ResourceFlowUtils.AddRemoveSameValueException}
|
||||
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException}
|
||||
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException}
|
||||
* @error {@link google.registry.flows.ResourceFlowUtils.StatusNotClientSettableException}
|
||||
* @error {@link google.registry.flows.exceptions.ResourceHasClientUpdateProhibitedException}
|
||||
* @error {@link google.registry.flows.exceptions.ResourceStatusProhibitsOperationException}
|
||||
* @error {@link ContactFlowUtils.BadInternationalizedPostalInfoException}
|
||||
* @error {@link ContactFlowUtils.DeclineContactDisclosureFieldDisallowedPolicyException}
|
||||
*/
|
||||
@Deprecated
|
||||
@ReportingSpec(ActivityReportField.CONTACT_UPDATE)
|
||||
public final class ContactUpdateFlow implements MutatingFlow {
|
||||
|
||||
/**
|
||||
* Note that CLIENT_UPDATE_PROHIBITED is intentionally not in this list. This is because it
|
||||
* requires special checking, since you must be able to clear the status off the object with an
|
||||
* update.
|
||||
*/
|
||||
private static final ImmutableSet<StatusValue> DISALLOWED_STATUSES = ImmutableSet.of(
|
||||
StatusValue.PENDING_DELETE,
|
||||
StatusValue.SERVER_UPDATE_PROHIBITED);
|
||||
|
||||
@Inject ResourceCommand resourceCommand;
|
||||
@Inject ExtensionManager extensionManager;
|
||||
@Inject Optional<AuthInfo> authInfo;
|
||||
@Inject @RegistrarId String registrarId;
|
||||
@Inject @TargetId String targetId;
|
||||
@Inject @Superuser boolean isSuperuser;
|
||||
@Inject ContactHistory.Builder historyBuilder;
|
||||
@Inject EppResponse.Builder responseBuilder;
|
||||
public final class ContactUpdateFlow extends ContactsProhibitedFlow {
|
||||
@Inject ContactUpdateFlow() {}
|
||||
|
||||
@Override
|
||||
public EppResponse run() throws EppException {
|
||||
extensionManager.register(MetadataExtension.class);
|
||||
validateRegistrarIsLoggedIn(registrarId);
|
||||
extensionManager.validate();
|
||||
if (FeatureFlag.isActiveNow(MINIMUM_DATASET_CONTACTS_PROHIBITED)) {
|
||||
throw new ContactsProhibitedException();
|
||||
}
|
||||
Update command = (Update) resourceCommand;
|
||||
DateTime now = tm().getTransactionTime();
|
||||
Contact existingContact = loadAndVerifyExistence(Contact.class, targetId, now);
|
||||
verifyOptionalAuthInfo(authInfo, existingContact);
|
||||
ImmutableSet<StatusValue> statusToRemove = command.getInnerRemove().getStatusValues();
|
||||
ImmutableSet<StatusValue> statusesToAdd = command.getInnerAdd().getStatusValues();
|
||||
if (!isSuperuser) { // The superuser can update any contact and set any status.
|
||||
verifyResourceOwnership(registrarId, existingContact);
|
||||
verifyAllStatusesAreClientSettable(union(statusesToAdd, statusToRemove));
|
||||
}
|
||||
verifyNoDisallowedStatuses(existingContact, DISALLOWED_STATUSES);
|
||||
checkSameValuesNotAddedAndRemoved(statusesToAdd, statusToRemove);
|
||||
Contact.Builder builder = existingContact.asBuilder();
|
||||
Change change = command.getInnerChange();
|
||||
// The spec requires the following behaviors:
|
||||
// * If you update part of a postal info, the fields that you didn't update are unchanged.
|
||||
// * If you update one postal info but not the other, the other is deleted.
|
||||
// Therefore, if you want to preserve one postal info and update another you need to send the
|
||||
// update and also something that technically updates the preserved one, even if it only
|
||||
// "updates" it by setting just one field to the same value.
|
||||
PostalInfo internationalized = change.getInternationalizedPostalInfo();
|
||||
PostalInfo localized = change.getLocalizedPostalInfo();
|
||||
if (internationalized != null) {
|
||||
builder.overlayInternationalizedPostalInfo(internationalized);
|
||||
if (localized == null) {
|
||||
builder.setLocalizedPostalInfo(null);
|
||||
}
|
||||
}
|
||||
if (localized != null) {
|
||||
builder.overlayLocalizedPostalInfo(localized);
|
||||
if (internationalized == null) {
|
||||
builder.setInternationalizedPostalInfo(null);
|
||||
}
|
||||
}
|
||||
Contact newContact =
|
||||
builder
|
||||
.setLastEppUpdateTime(now)
|
||||
.setLastEppUpdateRegistrarId(registrarId)
|
||||
.setAuthInfo(preferFirst(change.getAuthInfo(), existingContact.getAuthInfo()))
|
||||
.setDisclose(preferFirst(change.getDisclose(), existingContact.getDisclose()))
|
||||
.setEmailAddress(preferFirst(change.getEmail(), existingContact.getEmailAddress()))
|
||||
.setFaxNumber(preferFirst(change.getFax(), existingContact.getFaxNumber()))
|
||||
.setVoiceNumber(preferFirst(change.getVoice(), existingContact.getVoiceNumber()))
|
||||
.addStatusValues(statusesToAdd)
|
||||
.removeStatusValues(statusToRemove)
|
||||
.build();
|
||||
// If the resource is marked with clientUpdateProhibited, and this update did not clear that
|
||||
// status, then the update must be disallowed (unless a superuser is requesting the change).
|
||||
if (!isSuperuser
|
||||
&& existingContact.getStatusValues().contains(StatusValue.CLIENT_UPDATE_PROHIBITED)
|
||||
&& newContact.getStatusValues().contains(StatusValue.CLIENT_UPDATE_PROHIBITED)) {
|
||||
throw new ResourceHasClientUpdateProhibitedException();
|
||||
}
|
||||
validateAsciiPostalInfo(newContact.getInternationalizedPostalInfo());
|
||||
validateContactAgainstPolicy(newContact);
|
||||
historyBuilder
|
||||
.setType(CONTACT_UPDATE)
|
||||
.setXmlBytes(null) // We don't want to store contact details in the history entry.
|
||||
.setContact(newContact);
|
||||
tm().insert(historyBuilder.build());
|
||||
tm().update(newContact);
|
||||
return responseBuilder.build();
|
||||
}
|
||||
|
||||
/** Return the first non-null param, or null if both are null. */
|
||||
@Nullable
|
||||
private static <T> T preferFirst(@Nullable T a, @Nullable T b) {
|
||||
return a != null ? a : b;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
// 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.flows.contact;
|
||||
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.Flow;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
import google.registry.model.eppoutput.EppResponse;
|
||||
|
||||
/** Nomulus follows the Minimum Dataset Requirements, meaning it stores no contact information. */
|
||||
public abstract class ContactsProhibitedFlow implements Flow {
|
||||
@Override
|
||||
public EppResponse run() throws EppException {
|
||||
throw new ContactsProhibitedException();
|
||||
}
|
||||
}
|
||||
@@ -88,6 +88,7 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import org.joda.money.CurrencyUnit;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
@@ -298,11 +299,13 @@ public final class DomainCheckFlow implements TransactionalFlow {
|
||||
|
||||
boolean shouldUseTieredPricingPromotion =
|
||||
RegistryConfig.getTieredPricingPromotionRegistrarIds().contains(registrarId);
|
||||
ImmutableSet.Builder<CurrencyUnit> currenciesBuilder = new ImmutableSet.Builder<>();
|
||||
for (FeeCheckCommandExtensionItem feeCheckItem : feeCheck.getItems()) {
|
||||
for (String domainName : getDomainNamesToCheckForFee(feeCheckItem, domainNames.keySet())) {
|
||||
FeeCheckResponseExtensionItem.Builder<?> builder = feeCheckItem.createResponseBuilder();
|
||||
Optional<Domain> domain = Optional.ofNullable(domainObjs.get(domainName));
|
||||
Tld tld = Tld.get(domainNames.get(domainName).parent().toString());
|
||||
currenciesBuilder.add(tld.getCurrency());
|
||||
Optional<AllocationToken> token;
|
||||
try {
|
||||
// The precise token to use for this fee request may vary based on the domain or even the
|
||||
@@ -385,7 +388,8 @@ public final class DomainCheckFlow implements TransactionalFlow {
|
||||
responseItems.add(builder.setDomainNameIfSupported(domainName).build());
|
||||
}
|
||||
}
|
||||
return ImmutableList.of(feeCheck.createResponse(responseItems.build()));
|
||||
return ImmutableList.of(
|
||||
feeCheck.createResponse(responseItems.build(), currenciesBuilder.build()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -108,7 +108,8 @@ public final class DomainClaimsCheckFlow implements TransactionalFlow {
|
||||
verifyClaimsPeriodNotEnded(tld, now);
|
||||
}
|
||||
}
|
||||
Optional<String> claimKey = ClaimsListDao.get().getClaimKey(parsedDomain.parts().get(0));
|
||||
Optional<String> claimKey =
|
||||
ClaimsListDao.get(tldStr).getClaimKey(parsedDomain.parts().get(0));
|
||||
launchChecksBuilder.add(
|
||||
LaunchCheck.create(
|
||||
LaunchCheckName.create(claimKey.isPresent(), domainName), claimKey.orElse(null)));
|
||||
|
||||
@@ -186,12 +186,9 @@ import org.joda.time.Duration;
|
||||
* @error {@link DomainFlowUtils.LinkedResourceInPendingDeleteProhibitsOperationException}
|
||||
* @error {@link DomainFlowUtils.MalformedTcnIdException}
|
||||
* @error {@link DomainFlowUtils.MaxSigLifeNotSupportedException}
|
||||
* @error {@link DomainFlowUtils.MissingAdminContactException}
|
||||
* @error {@link DomainFlowUtils.MissingBillingAccountMapException}
|
||||
* @error {@link DomainFlowUtils.MissingClaimsNoticeException}
|
||||
* @error {@link DomainFlowUtils.MissingContactTypeException}
|
||||
* @error {@link DomainFlowUtils.MissingRegistrantException}
|
||||
* @error {@link DomainFlowUtils.MissingTechnicalContactException}
|
||||
* @error {@link DomainFlowUtils.NameserversNotAllowedForTldException}
|
||||
* @error {@link DomainFlowUtils.NameserversNotSpecifiedForTldWithNameserverAllowListException}
|
||||
* @error {@link DomainFlowUtils.PremiumNameBlockedException}
|
||||
@@ -283,7 +280,7 @@ public final class DomainCreateFlow implements MutatingFlow {
|
||||
checkAllowedAccessToTld(registrarId, tld.getTldStr());
|
||||
checkHasBillingAccount(registrarId, tld.getTldStr());
|
||||
boolean isValidReservedCreate = isValidReservedCreate(domainName, allocationToken);
|
||||
ClaimsList claimsList = ClaimsListDao.get();
|
||||
ClaimsList claimsList = ClaimsListDao.get(tld.getTldStr());
|
||||
verifyIsGaOrSpecialCase(
|
||||
tld,
|
||||
claimsList,
|
||||
@@ -315,7 +312,8 @@ public final class DomainCreateFlow implements MutatingFlow {
|
||||
// at this point so that we can verify it before the "after validation" extension point.
|
||||
signedMarkId =
|
||||
tmchUtils
|
||||
.verifySignedMarks(launchCreate.get().getSignedMarks(), domainLabel, now)
|
||||
.verifySignedMarks(
|
||||
tld.getTldStr(), launchCreate.get().getSignedMarks(), domainLabel, now)
|
||||
.getId();
|
||||
}
|
||||
verifyNotBlockedByBsa(domainName, tld, now, allocationToken);
|
||||
|
||||
@@ -55,7 +55,7 @@ public final class DomainFlowTmchUtils {
|
||||
}
|
||||
|
||||
public SignedMark verifySignedMarks(
|
||||
ImmutableList<AbstractSignedMark> signedMarks, String domainLabel, DateTime now)
|
||||
String tld, ImmutableList<AbstractSignedMark> signedMarks, String domainLabel, DateTime now)
|
||||
throws EppException {
|
||||
if (signedMarks.size() > 1) {
|
||||
throw new TooManySignedMarksException();
|
||||
@@ -64,7 +64,7 @@ public final class DomainFlowTmchUtils {
|
||||
throw new SignedMarksMustBeEncodedException();
|
||||
}
|
||||
SignedMark signedMark =
|
||||
verifyEncodedSignedMark((EncodedSignedMark) signedMarks.get(0), now);
|
||||
verifyEncodedSignedMark(tld, (EncodedSignedMark) signedMarks.get(0), now);
|
||||
return verifySignedMarkValidForDomainLabel(signedMark, domainLabel);
|
||||
}
|
||||
|
||||
@@ -76,8 +76,9 @@ public final class DomainFlowTmchUtils {
|
||||
return signedMark;
|
||||
}
|
||||
|
||||
public SignedMark verifyEncodedSignedMark(EncodedSignedMark encodedSignedMark, DateTime now)
|
||||
throws EppException {
|
||||
// TODO(b/412715713): remove the tld parameter when RST completes.
|
||||
public SignedMark verifyEncodedSignedMark(
|
||||
String tld, EncodedSignedMark encodedSignedMark, DateTime now) throws EppException {
|
||||
if (!encodedSignedMark.getEncoding().equals("base64")) {
|
||||
throw new Base64RequiredForEncodedSignedMarksException();
|
||||
}
|
||||
@@ -95,7 +96,7 @@ public final class DomainFlowTmchUtils {
|
||||
throw new SignedMarkParsingErrorException();
|
||||
}
|
||||
|
||||
if (SignedMarkRevocationList.get().isSmdRevoked(signedMark.getId(), now)) {
|
||||
if (SignedMarkRevocationList.get(tld).isSmdRevoked(signedMark.getId(), now)) {
|
||||
throw new SignedMarkRevokedErrorException();
|
||||
}
|
||||
|
||||
|
||||
@@ -24,8 +24,6 @@ import static com.google.common.collect.Sets.difference;
|
||||
import static com.google.common.collect.Sets.intersection;
|
||||
import static com.google.common.collect.Sets.union;
|
||||
import static google.registry.bsa.persistence.BsaLabelUtils.isLabelBlocked;
|
||||
import static google.registry.model.common.FeatureFlag.FeatureName.MINIMUM_DATASET_CONTACTS_OPTIONAL;
|
||||
import static google.registry.model.common.FeatureFlag.FeatureName.MINIMUM_DATASET_CONTACTS_PROHIBITED;
|
||||
import static google.registry.model.domain.Domain.MAX_REGISTRATION_YEARS;
|
||||
import static google.registry.model.domain.token.AllocationToken.TokenType.REGISTER_BSA;
|
||||
import static google.registry.model.tld.Tld.TldState.GENERAL_AVAILABILITY;
|
||||
@@ -81,7 +79,6 @@ import google.registry.model.EppResource;
|
||||
import google.registry.model.billing.BillingBase.Flag;
|
||||
import google.registry.model.billing.BillingBase.Reason;
|
||||
import google.registry.model.billing.BillingRecurrence;
|
||||
import google.registry.model.common.FeatureFlag;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.domain.DesignatedContact;
|
||||
import google.registry.model.domain.DesignatedContact.Type;
|
||||
@@ -138,7 +135,6 @@ import google.registry.util.Idn;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
@@ -222,7 +218,7 @@ public class DomainFlowUtils {
|
||||
return domainName;
|
||||
}
|
||||
|
||||
private static void validateFirstLabel(String firstLabel) throws EppException {
|
||||
public static void validateFirstLabel(String firstLabel) throws EppException {
|
||||
if (firstLabel.length() > MAX_LABEL_SIZE) {
|
||||
throw new DomainLabelTooLongException();
|
||||
}
|
||||
@@ -486,31 +482,12 @@ public class DomainFlowUtils {
|
||||
*/
|
||||
static void validateCreateContactData(
|
||||
Optional<VKey<Contact>> registrant, Set<DesignatedContact> contacts)
|
||||
throws RequiredParameterMissingException, ParameterValuePolicyErrorException {
|
||||
// TODO(b/353347632): Change these flag checks to a registry config check once minimum data set
|
||||
// migration is completed.
|
||||
if (FeatureFlag.isActiveNow(MINIMUM_DATASET_CONTACTS_PROHIBITED)) {
|
||||
if (registrant.isPresent()) {
|
||||
throw new RegistrantProhibitedException();
|
||||
}
|
||||
if (!contacts.isEmpty()) {
|
||||
throw new ContactsProhibitedException();
|
||||
}
|
||||
} else if (!FeatureFlag.isActiveNow(MINIMUM_DATASET_CONTACTS_OPTIONAL)) {
|
||||
if (registrant.isEmpty()) {
|
||||
throw new MissingRegistrantException();
|
||||
}
|
||||
|
||||
Set<Type> roles = new HashSet<>();
|
||||
for (DesignatedContact contact : contacts) {
|
||||
roles.add(contact.getType());
|
||||
}
|
||||
if (!roles.contains(Type.ADMIN)) {
|
||||
throw new MissingAdminContactException();
|
||||
}
|
||||
if (!roles.contains(Type.TECH)) {
|
||||
throw new MissingTechnicalContactException();
|
||||
}
|
||||
throws ParameterValuePolicyErrorException {
|
||||
if (registrant.isPresent()) {
|
||||
throw new RegistrantProhibitedException();
|
||||
}
|
||||
if (!contacts.isEmpty()) {
|
||||
throw new ContactsProhibitedException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -523,33 +500,14 @@ public class DomainFlowUtils {
|
||||
Optional<VKey<Contact>> newRegistrant,
|
||||
Set<DesignatedContact> existingContacts,
|
||||
Set<DesignatedContact> newContacts)
|
||||
throws RequiredParameterMissingException, ParameterValuePolicyErrorException {
|
||||
// TODO(b/353347632): Change these flag checks to a registry config check once minimum data set
|
||||
// migration is completed.
|
||||
if (FeatureFlag.isActiveNow(MINIMUM_DATASET_CONTACTS_PROHIBITED)) {
|
||||
// Throw if the update specifies a new registrant that is different from the existing one.
|
||||
if (newRegistrant.isPresent() && !newRegistrant.equals(existingRegistrant)) {
|
||||
throw new RegistrantProhibitedException();
|
||||
}
|
||||
// Throw if the update specifies any new contacts that weren't already present on the domain.
|
||||
if (!Sets.difference(newContacts, existingContacts).isEmpty()) {
|
||||
throw new ContactsProhibitedException();
|
||||
}
|
||||
} else if (!FeatureFlag.isActiveNow(MINIMUM_DATASET_CONTACTS_OPTIONAL)) {
|
||||
// Throw if the update empties out a registrant that had been present.
|
||||
if (newRegistrant.isEmpty() && existingRegistrant.isPresent()) {
|
||||
throw new MissingRegistrantException();
|
||||
}
|
||||
// Throw if the update contains no admin contact when one had been present.
|
||||
if (existingContacts.stream().anyMatch(c -> c.getType().equals(Type.ADMIN))
|
||||
&& newContacts.stream().noneMatch(c -> c.getType().equals(Type.ADMIN))) {
|
||||
throw new MissingAdminContactException();
|
||||
}
|
||||
// Throw if the update contains no tech contact when one had been present.
|
||||
if (existingContacts.stream().anyMatch(c -> c.getType().equals(Type.TECH))
|
||||
&& newContacts.stream().noneMatch(c -> c.getType().equals(Type.TECH))) {
|
||||
throw new MissingTechnicalContactException();
|
||||
}
|
||||
throws ParameterValuePolicyErrorException {
|
||||
// Throw if the update specifies a new registrant that is different from the existing one.
|
||||
if (newRegistrant.isPresent() && !newRegistrant.equals(existingRegistrant)) {
|
||||
throw new RegistrantProhibitedException();
|
||||
}
|
||||
// Throw if the update specifies any new contacts that weren't already present on the domain.
|
||||
if (!Sets.difference(newContacts, existingContacts).isEmpty()) {
|
||||
throw new ContactsProhibitedException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1018,23 +976,21 @@ public class DomainFlowUtils {
|
||||
throw new UrgentAttributeNotSupportedException();
|
||||
}
|
||||
// There must be at least one of add/rem/chg, and chg isn't actually supported.
|
||||
if (secDnsUpdate.getChange() != null) {
|
||||
if (secDnsUpdate.getChange().isPresent()) {
|
||||
// The only thing you can change is maxSigLife, and we don't support that at all.
|
||||
throw new MaxSigLifeChangeNotSupportedException();
|
||||
}
|
||||
Add add = secDnsUpdate.getAdd();
|
||||
Remove remove = secDnsUpdate.getRemove();
|
||||
if (add == null && remove == null) {
|
||||
Optional<Add> add = secDnsUpdate.getAdd();
|
||||
Optional<Remove> remove = secDnsUpdate.getRemove();
|
||||
if (add.isEmpty() && remove.isEmpty()) {
|
||||
throw new EmptySecDnsUpdateException();
|
||||
}
|
||||
if (remove != null && Boolean.FALSE.equals(remove.getAll())) {
|
||||
if (remove.isPresent() && Boolean.FALSE.equals(remove.get().getAll())) {
|
||||
throw new SecDnsAllUsageException(); // Explicit all=false is meaningless.
|
||||
}
|
||||
Set<DomainDsData> toAdd = (add == null) ? ImmutableSet.of() : add.getDsData();
|
||||
Set<DomainDsData> toAdd = add.map(Add::getDsData).orElse(ImmutableSet.of());
|
||||
Set<DomainDsData> toRemove =
|
||||
(remove == null)
|
||||
? ImmutableSet.of()
|
||||
: (remove.getAll() == null) ? remove.getDsData() : oldDsData;
|
||||
remove.map(r -> (r.getAll() == null) ? r.getDsData() : oldDsData).orElse(ImmutableSet.of());
|
||||
// RFC 5910 specifies that removes are processed before adds.
|
||||
return ImmutableSet.copyOf(union(difference(oldDsData, toRemove), toAdd));
|
||||
}
|
||||
@@ -1282,49 +1238,49 @@ public class DomainFlowUtils {
|
||||
}
|
||||
|
||||
/** Domain names can only contain a-z, 0-9, '.' and '-'. */
|
||||
static class BadDomainNameCharacterException extends ParameterValuePolicyErrorException {
|
||||
static class BadDomainNameCharacterException extends ParameterValueSyntaxErrorException {
|
||||
public BadDomainNameCharacterException() {
|
||||
super("Domain names can only contain a-z, 0-9, '.' and '-'");
|
||||
}
|
||||
}
|
||||
|
||||
/** Non-IDN domain names cannot contain hyphens in the third or fourth position. */
|
||||
static class DashesInThirdAndFourthException extends ParameterValuePolicyErrorException {
|
||||
static class DashesInThirdAndFourthException extends ParameterValueSyntaxErrorException {
|
||||
public DashesInThirdAndFourthException() {
|
||||
super("Non-IDN domain names cannot contain dashes in the third or fourth position");
|
||||
}
|
||||
}
|
||||
|
||||
/** Domain labels cannot begin with a dash. */
|
||||
static class LeadingDashException extends ParameterValuePolicyErrorException {
|
||||
static class LeadingDashException extends ParameterValueSyntaxErrorException {
|
||||
public LeadingDashException() {
|
||||
super("Domain labels cannot begin with a dash");
|
||||
}
|
||||
}
|
||||
|
||||
/** Domain labels cannot end with a dash. */
|
||||
static class TrailingDashException extends ParameterValuePolicyErrorException {
|
||||
static class TrailingDashException extends ParameterValueSyntaxErrorException {
|
||||
public TrailingDashException() {
|
||||
super("Domain labels cannot end with a dash");
|
||||
}
|
||||
}
|
||||
|
||||
/** Domain labels cannot be longer than 63 characters. */
|
||||
static class DomainLabelTooLongException extends ParameterValuePolicyErrorException {
|
||||
static class DomainLabelTooLongException extends ParameterValueSyntaxErrorException {
|
||||
public DomainLabelTooLongException() {
|
||||
super("Domain labels cannot be longer than 63 characters");
|
||||
}
|
||||
}
|
||||
|
||||
/** No part of a domain name can be empty. */
|
||||
static class EmptyDomainNamePartException extends ParameterValuePolicyErrorException {
|
||||
static class EmptyDomainNamePartException extends ParameterValueSyntaxErrorException {
|
||||
public EmptyDomainNamePartException() {
|
||||
super("No part of a domain name can be empty");
|
||||
}
|
||||
}
|
||||
|
||||
/** Domain name starts with xn-- but is not a valid IDN. */
|
||||
static class InvalidPunycodeException extends ParameterValuePolicyErrorException {
|
||||
static class InvalidPunycodeException extends ParameterValueSyntaxErrorException {
|
||||
public InvalidPunycodeException() {
|
||||
super("Domain name starts with xn-- but is not a valid IDN");
|
||||
}
|
||||
@@ -1398,13 +1354,6 @@ public class DomainFlowUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/** Registrant is required. */
|
||||
static class MissingRegistrantException extends RequiredParameterMissingException {
|
||||
public MissingRegistrantException() {
|
||||
super("Registrant is required");
|
||||
}
|
||||
}
|
||||
|
||||
/** Having a registrant is prohibited by registry policy. */
|
||||
static class RegistrantProhibitedException extends ParameterValuePolicyErrorException {
|
||||
public RegistrantProhibitedException() {
|
||||
@@ -1412,20 +1361,6 @@ public class DomainFlowUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/** Admin contact is required. */
|
||||
static class MissingAdminContactException extends RequiredParameterMissingException {
|
||||
public MissingAdminContactException() {
|
||||
super("Admin contact is required");
|
||||
}
|
||||
}
|
||||
|
||||
/** Technical contact is required. */
|
||||
static class MissingTechnicalContactException extends RequiredParameterMissingException {
|
||||
public MissingTechnicalContactException() {
|
||||
super("Technical contact is required");
|
||||
}
|
||||
}
|
||||
|
||||
/** Too many nameservers set on this domain. */
|
||||
static class TooManyNameserversException extends ParameterValuePolicyErrorException {
|
||||
public TooManyNameserversException(String message) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -21,8 +21,8 @@ import static com.google.common.collect.Sets.union;
|
||||
import static google.registry.dns.DnsUtils.requestDomainDnsRefresh;
|
||||
import static google.registry.flows.FlowUtils.persistEntityChanges;
|
||||
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
|
||||
import static google.registry.flows.ResourceFlowUtils.checkSameValuesNotAddedAndRemoved;
|
||||
import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyAddsAndRemoves;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyAllStatusesAreClientSettable;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyNoDisallowedStatuses;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyOptionalAuthInfo;
|
||||
@@ -39,8 +39,6 @@ import static google.registry.flows.domain.DomainFlowUtils.validateNoDuplicateCo
|
||||
import static google.registry.flows.domain.DomainFlowUtils.validateUpdateContactData;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.verifyClientUpdateNotProhibited;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.verifyNotInPendingDelete;
|
||||
import static google.registry.model.common.FeatureFlag.FeatureName.MINIMUM_DATASET_CONTACTS_OPTIONAL;
|
||||
import static google.registry.model.common.FeatureFlag.FeatureName.MINIMUM_DATASET_CONTACTS_PROHIBITED;
|
||||
import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_UPDATE;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
@@ -61,13 +59,11 @@ import google.registry.flows.custom.DomainUpdateFlowCustomLogic;
|
||||
import google.registry.flows.custom.DomainUpdateFlowCustomLogic.AfterValidationParameters;
|
||||
import google.registry.flows.custom.DomainUpdateFlowCustomLogic.BeforeSaveParameters;
|
||||
import google.registry.flows.custom.EntityChanges;
|
||||
import google.registry.flows.domain.DomainFlowUtils.MissingRegistrantException;
|
||||
import google.registry.flows.domain.DomainFlowUtils.NameserversNotSpecifiedForTldWithNameserverAllowListException;
|
||||
import google.registry.flows.domain.DomainFlowUtils.RegistrantProhibitedException;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.billing.BillingBase.Reason;
|
||||
import google.registry.model.billing.BillingEvent;
|
||||
import google.registry.model.common.FeatureFlag;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.domain.DesignatedContact;
|
||||
import google.registry.model.domain.Domain;
|
||||
@@ -79,6 +75,8 @@ import google.registry.model.domain.fee.FeeUpdateCommandExtension;
|
||||
import google.registry.model.domain.metadata.MetadataExtension;
|
||||
import google.registry.model.domain.secdns.DomainDsData;
|
||||
import google.registry.model.domain.secdns.SecDnsUpdateExtension;
|
||||
import google.registry.model.domain.secdns.SecDnsUpdateExtension.Add;
|
||||
import google.registry.model.domain.secdns.SecDnsUpdateExtension.Remove;
|
||||
import google.registry.model.domain.superuser.DomainUpdateSuperuserExtension;
|
||||
import google.registry.model.eppcommon.AuthInfo;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
@@ -123,10 +121,7 @@ import org.joda.time.DateTime;
|
||||
* @error {@link DomainFlowUtils.LinkedResourcesDoNotExistException}
|
||||
* @error {@link DomainFlowUtils.LinkedResourceInPendingDeleteProhibitsOperationException}
|
||||
* @error {@link DomainFlowUtils.MaxSigLifeChangeNotSupportedException}
|
||||
* @error {@link DomainFlowUtils.MissingAdminContactException}
|
||||
* @error {@link DomainFlowUtils.MissingContactTypeException}
|
||||
* @error {@link DomainFlowUtils.MissingTechnicalContactException}
|
||||
* @error {@link DomainFlowUtils.MissingRegistrantException}
|
||||
* @error {@link DomainFlowUtils.NameserversNotAllowedForTldException}
|
||||
* @error {@link NameserversNotSpecifiedForTldWithNameserverAllowListException}
|
||||
* @error {@link DomainFlowUtils.NotAuthorizedForTldException}
|
||||
@@ -252,12 +247,19 @@ public final class DomainUpdateFlow implements MutatingFlow {
|
||||
private Domain performUpdate(Update command, Domain domain, DateTime now) throws EppException {
|
||||
AddRemove add = command.getInnerAdd();
|
||||
AddRemove remove = command.getInnerRemove();
|
||||
checkSameValuesNotAddedAndRemoved(add.getNameservers(), remove.getNameservers());
|
||||
checkSameValuesNotAddedAndRemoved(add.getContacts(), remove.getContacts());
|
||||
checkSameValuesNotAddedAndRemoved(add.getStatusValues(), remove.getStatusValues());
|
||||
Change change = command.getInnerChange();
|
||||
Optional<SecDnsUpdateExtension> secDnsUpdate =
|
||||
eppInput.getSingleExtension(SecDnsUpdateExtension.class);
|
||||
verifyAddsAndRemoves(domain.getNameservers(), add.getNameservers(), remove.getNameservers());
|
||||
verifyAddsAndRemoves(domain.getContacts(), add.getContacts(), remove.getContacts());
|
||||
verifyAddsAndRemoves(domain.getStatusValues(), add.getStatusValues(), remove.getStatusValues());
|
||||
if (secDnsUpdate.isPresent()) {
|
||||
SecDnsUpdateExtension ext = secDnsUpdate.get();
|
||||
verifyAddsAndRemoves(
|
||||
domain.getDsData(),
|
||||
ext.getAdd().map(Add::getDsData).orElse(ImmutableSet.of()),
|
||||
ext.getRemove().map(Remove::getDsData).orElse(ImmutableSet.of()));
|
||||
}
|
||||
Change change = command.getInnerChange();
|
||||
|
||||
// We have to verify no duplicate contacts _before_ constructing the domain because it is
|
||||
// illegal to construct a domain with duplicate contacts.
|
||||
@@ -307,18 +309,11 @@ public final class DomainUpdateFlow implements MutatingFlow {
|
||||
return domainBuilder.build();
|
||||
}
|
||||
|
||||
private Optional<VKey<Contact>> determineUpdatedRegistrant(Change change, Domain domain)
|
||||
throws EppException {
|
||||
private Optional<VKey<Contact>> determineUpdatedRegistrant(Change change, Domain domain) {
|
||||
// During or after the minimum dataset transition, allow registrant to be removed.
|
||||
if (change.getRegistrantContactId().isPresent()
|
||||
&& change.getRegistrantContactId().get().isEmpty()) {
|
||||
// TODO(b/353347632): Change this flag check to a registry config check.
|
||||
if (FeatureFlag.isActiveNow(MINIMUM_DATASET_CONTACTS_OPTIONAL)
|
||||
|| FeatureFlag.isActiveNow(MINIMUM_DATASET_CONTACTS_PROHIBITED)) {
|
||||
return Optional.empty();
|
||||
} else {
|
||||
throw new MissingRegistrantException();
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
return change.getRegistrant().or(domain::getRegistrant);
|
||||
}
|
||||
|
||||
@@ -65,6 +65,7 @@ public final class HostCheckFlow implements TransactionalFlow {
|
||||
ForeignKeyUtils.loadKeys(Host.class, hostnames, clock.nowUtc()).keySet();
|
||||
ImmutableList.Builder<HostCheck> checks = new ImmutableList.Builder<>();
|
||||
for (String hostname : hostnames) {
|
||||
HostFlowUtils.validateHostName(hostname);
|
||||
boolean unused = !existingIds.contains(hostname);
|
||||
checks.add(HostCheck.create(unused, hostname, unused ? null : "In use"));
|
||||
}
|
||||
|
||||
@@ -116,6 +116,7 @@ public final class HostCreateFlow implements MutatingFlow {
|
||||
? new SubordinateHostMustHaveIpException()
|
||||
: new UnexpectedExternalHostIpException();
|
||||
}
|
||||
HostFlowUtils.validateInetAddresses(command.getInetAddresses());
|
||||
Host newHost =
|
||||
new Host.Builder()
|
||||
.setCreationRegistrarId(registrarId)
|
||||
|
||||
@@ -14,12 +14,16 @@
|
||||
|
||||
package google.registry.flows.host;
|
||||
|
||||
import static google.registry.flows.domain.DomainFlowUtils.validateFirstLabel;
|
||||
import static google.registry.model.EppResourceUtils.isActive;
|
||||
import static google.registry.model.tld.Tlds.findTldForName;
|
||||
import static google.registry.util.DomainNameUtils.canonicalizeHostname;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
|
||||
import com.google.common.base.Ascii;
|
||||
import com.google.common.base.CharMatcher;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.net.InternetDomainName;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.EppException.AuthorizationErrorException;
|
||||
@@ -31,13 +35,17 @@ import google.registry.flows.EppException.StatusProhibitsOperationException;
|
||||
import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.util.Idn;
|
||||
import java.net.InetAddress;
|
||||
import java.util.Optional;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** Static utility functions for host flows. */
|
||||
public class HostFlowUtils {
|
||||
|
||||
/** Validator for ASCII lowercase letters, digits, and "-_", allowing "." as a separator */
|
||||
private static final CharMatcher HOST_NAME_ALLOWED_CHARS =
|
||||
CharMatcher.inRange('a', 'z').or(CharMatcher.inRange('0', '9').or(CharMatcher.anyOf("-._")));
|
||||
|
||||
/** Checks that a host name is valid. */
|
||||
public static InternetDomainName validateHostName(String name) throws EppException {
|
||||
checkArgumentNotNull(name, "Must specify host name to validate");
|
||||
@@ -49,10 +57,13 @@ public class HostFlowUtils {
|
||||
throw new HostNameNotLowerCaseException(hostNameLowerCase);
|
||||
}
|
||||
try {
|
||||
String hostNamePunyCoded = Idn.toASCII(name);
|
||||
String hostNamePunyCoded = canonicalizeHostname(name);
|
||||
if (!name.equals(hostNamePunyCoded)) {
|
||||
throw new HostNameNotPunyCodedException(hostNamePunyCoded);
|
||||
}
|
||||
if (!HOST_NAME_ALLOWED_CHARS.matchesAllOf(name)) {
|
||||
throw new BadHostNameCharacterException();
|
||||
}
|
||||
InternetDomainName hostName = InternetDomainName.from(name);
|
||||
if (!name.equals(hostName.toString())) {
|
||||
throw new HostNameNotNormalizedException(hostName.toString());
|
||||
@@ -71,6 +82,7 @@ public class HostFlowUtils {
|
||||
if (hostName.parts().size() < effectiveTld.parts().size() + 2) {
|
||||
throw new HostNameTooShallowException();
|
||||
}
|
||||
validateFirstLabel(hostName.parts().getFirst());
|
||||
return hostName;
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new InvalidHostNameException();
|
||||
@@ -98,6 +110,24 @@ public class HostFlowUtils {
|
||||
return superordinateDomain;
|
||||
}
|
||||
|
||||
/** Makes sure that no provided IP addresses are local / loopback addresses. */
|
||||
public static void validateInetAddresses(ImmutableSet<InetAddress> inetAddresses)
|
||||
throws EppException {
|
||||
if (inetAddresses == null) {
|
||||
return;
|
||||
}
|
||||
if (inetAddresses.stream().anyMatch(InetAddress::isLoopbackAddress)) {
|
||||
throw new LoopbackIpNotValidForHostException();
|
||||
}
|
||||
}
|
||||
|
||||
/** Loopback IPs are not valid for hosts. */
|
||||
static class LoopbackIpNotValidForHostException extends ParameterValuePolicyErrorException {
|
||||
public LoopbackIpNotValidForHostException() {
|
||||
super("Loopback IPs are not valid for hosts");
|
||||
}
|
||||
}
|
||||
|
||||
/** Superordinate domain for this hostname does not exist. */
|
||||
static class SuperordinateDomainDoesNotExistException extends ObjectDoesNotExistException {
|
||||
public SuperordinateDomainDoesNotExistException(String domainName) {
|
||||
@@ -180,4 +210,11 @@ public class HostFlowUtils {
|
||||
String.format("Host names must be in normalized format; expected %s", expectedHostName));
|
||||
}
|
||||
}
|
||||
|
||||
/** Host names can only contain a-z, 0-9, '.', '_', and '-'. */
|
||||
static class BadHostNameCharacterException extends ParameterValueSyntaxErrorException {
|
||||
public BadHostNameCharacterException() {
|
||||
super("Host names can only contain a-z, 0-9, '.', '_', and '-'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,8 +20,8 @@ import static google.registry.dns.DnsUtils.requestHostDnsRefresh;
|
||||
import static google.registry.dns.RefreshDnsOnHostRenameAction.PARAM_HOST_KEY;
|
||||
import static google.registry.dns.RefreshDnsOnHostRenameAction.QUEUE_HOST_RENAME;
|
||||
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
|
||||
import static google.registry.flows.ResourceFlowUtils.checkSameValuesNotAddedAndRemoved;
|
||||
import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyAddsAndRemoves;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyAllStatusesAreClientSettable;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyNoDisallowedStatuses;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyResourceOwnership;
|
||||
@@ -159,8 +159,11 @@ public final class HostUpdateFlow implements MutatingFlow {
|
||||
}
|
||||
AddRemove add = command.getInnerAdd();
|
||||
AddRemove remove = command.getInnerRemove();
|
||||
checkSameValuesNotAddedAndRemoved(add.getStatusValues(), remove.getStatusValues());
|
||||
checkSameValuesNotAddedAndRemoved(add.getInetAddresses(), remove.getInetAddresses());
|
||||
verifyAddsAndRemoves(
|
||||
existingHost.getStatusValues(), add.getStatusValues(), remove.getStatusValues());
|
||||
verifyAddsAndRemoves(
|
||||
existingHost.getInetAddresses(), add.getInetAddresses(), remove.getInetAddresses());
|
||||
HostFlowUtils.validateInetAddresses(add.getInetAddresses());
|
||||
VKey<Domain> newSuperordinateDomainKey =
|
||||
newSuperordinateDomain.map(Domain::createVKey).orElse(null);
|
||||
// If the superordinateDomain field is changing, set the lastSuperordinateChange to now.
|
||||
|
||||
@@ -135,7 +135,6 @@ public class FlowPicker {
|
||||
return switch (((Poll) innerCommand).getPollOp()) {
|
||||
case ACK -> PollAckFlow.class;
|
||||
case REQUEST -> PollRequestFlow.class;
|
||||
default -> UnimplementedFlow.class;
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
package google.registry.model.domain.fee;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.model.eppinput.EppInput.CommandExtension;
|
||||
import org.joda.money.CurrencyUnit;
|
||||
|
||||
@@ -42,4 +44,11 @@ public interface FeeCheckCommandExtension<
|
||||
ImmutableList<C> getItems();
|
||||
|
||||
R createResponse(ImmutableList<? extends FeeCheckResponseExtensionItem> items);
|
||||
|
||||
default R createResponse(
|
||||
ImmutableList<? extends FeeCheckResponseExtensionItem> items,
|
||||
ImmutableSet<CurrencyUnit> currenciesSeen)
|
||||
throws EppException {
|
||||
return createResponse(items);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ import com.google.common.base.Ascii;
|
||||
import google.registry.model.domain.Period;
|
||||
import google.registry.model.domain.fee.FeeCheckCommandExtensionItem;
|
||||
import jakarta.xml.bind.annotation.XmlAttribute;
|
||||
import jakarta.xml.bind.annotation.XmlElement;
|
||||
import jakarta.xml.bind.annotation.XmlType;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
@@ -32,12 +31,13 @@ import org.joda.time.DateTime;
|
||||
* <pre>{@code
|
||||
* <fee:command name="renew" phase="sunrise" subphase="hello">
|
||||
* <fee:period unit="y">1</fee:period>
|
||||
* <fee:class>premium</fee:class>
|
||||
* <fee:date>2017-05-17T13:22:21.0Z</fee:date>
|
||||
* </fee:command>
|
||||
* }</pre>
|
||||
*
|
||||
* <p>The `feeClass` and `feeDate` attributes that are present in version 0.12 are removed from this
|
||||
* version.
|
||||
*/
|
||||
@XmlType(propOrder = {"period", "feeClass", "feeDate"})
|
||||
@XmlType(propOrder = {"period"})
|
||||
public class FeeCheckCommandExtensionItemStdV1 extends FeeCheckCommandExtensionItem {
|
||||
|
||||
/** The default validity period (if not specified) is 1 year for all operations. */
|
||||
@@ -50,12 +50,6 @@ public class FeeCheckCommandExtensionItemStdV1 extends FeeCheckCommandExtensionI
|
||||
|
||||
@XmlAttribute String subphase;
|
||||
|
||||
@XmlElement(name = "class")
|
||||
String feeClass;
|
||||
|
||||
@XmlElement(name = "date")
|
||||
DateTime feeDate;
|
||||
|
||||
/** Version 1.0 does not support domain name or currency in fee extension items. */
|
||||
@Override
|
||||
public boolean isDomainNameSupported() {
|
||||
@@ -107,6 +101,6 @@ public class FeeCheckCommandExtensionItemStdV1 extends FeeCheckCommandExtensionI
|
||||
|
||||
@Override
|
||||
public Optional<DateTime> getEffectiveDate() {
|
||||
return Optional.ofNullable(feeDate);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,9 @@ package google.registry.model.domain.feestdv1;
|
||||
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.EppException.ParameterValuePolicyErrorException;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.domain.fee.FeeCheckCommandExtension;
|
||||
import google.registry.model.domain.fee.FeeCheckResponseExtensionItem;
|
||||
@@ -51,13 +54,33 @@ public class FeeCheckCommandExtensionStdV1 extends ImmutableObject
|
||||
@Override
|
||||
public FeeCheckResponseExtensionStdV1 createResponse(
|
||||
ImmutableList<? extends FeeCheckResponseExtensionItem> items) {
|
||||
throw new UnsupportedOperationException("FeeCheckCommandExtensionStdV1 requires a currency");
|
||||
}
|
||||
|
||||
@Override
|
||||
public FeeCheckResponseExtensionStdV1 createResponse(
|
||||
ImmutableList<? extends FeeCheckResponseExtensionItem> items,
|
||||
ImmutableSet<CurrencyUnit> currenciesSeen)
|
||||
throws EppException {
|
||||
ImmutableList.Builder<FeeCheckResponseExtensionItemStdV1> builder =
|
||||
new ImmutableList.Builder<>();
|
||||
for (FeeCheckResponseExtensionItem item : items) {
|
||||
if (item instanceof FeeCheckResponseExtensionItemStdV1) {
|
||||
builder.add((FeeCheckResponseExtensionItemStdV1) item);
|
||||
if (item instanceof FeeCheckResponseExtensionItemStdV1 stdv1Item) {
|
||||
builder.add(stdv1Item);
|
||||
}
|
||||
}
|
||||
return FeeCheckResponseExtensionStdV1.create(currency, builder.build());
|
||||
if (currenciesSeen.size() > 1) {
|
||||
throw new MultipleCurrenciesCannotBeCheckedException();
|
||||
}
|
||||
return FeeCheckResponseExtensionStdV1.create(currenciesSeen.iterator().next(), builder.build());
|
||||
}
|
||||
|
||||
/** Domains across multiple currencies cannot be checked simultaneously. */
|
||||
public static class MultipleCurrenciesCannotBeCheckedException
|
||||
extends ParameterValuePolicyErrorException {
|
||||
public MultipleCurrenciesCannotBeCheckedException() {
|
||||
// The fee extension 1.0 only supports one currency shared across all results
|
||||
super("Domains across multiple currencies cannot be checked simultaneously");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,13 +24,11 @@ import google.registry.model.domain.Period;
|
||||
import google.registry.model.domain.fee.Fee;
|
||||
import google.registry.model.domain.fee.FeeQueryCommandExtensionItem.CommandName;
|
||||
import jakarta.xml.bind.annotation.XmlAttribute;
|
||||
import jakarta.xml.bind.annotation.XmlElement;
|
||||
import jakarta.xml.bind.annotation.XmlType;
|
||||
import java.util.List;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** The version 1.0 response command entity for a domain check on a single resource. */
|
||||
@XmlType(propOrder = {"period", "fee", "feeClass", "effectiveDate", "notAfterDate"})
|
||||
@XmlType(propOrder = {"period", "fee"})
|
||||
public class FeeCheckResponseExtensionItemCommandStdV1 extends ImmutableObject {
|
||||
|
||||
/** The command that was checked. */
|
||||
@@ -53,26 +51,6 @@ public class FeeCheckResponseExtensionItemCommandStdV1 extends ImmutableObject {
|
||||
*/
|
||||
List<Fee> fee;
|
||||
|
||||
/**
|
||||
* The type of the fee.
|
||||
*
|
||||
* <p>We will use "premium" for fees on premium names, and omit the field otherwise.
|
||||
*/
|
||||
@XmlElement(name = "class")
|
||||
String feeClass;
|
||||
|
||||
/** The effective date that the check is to be performed on (if specified in the query). */
|
||||
@XmlElement(name = "date")
|
||||
DateTime effectiveDate;
|
||||
|
||||
/** The date after which the quoted fee is no longer valid (if applicable). */
|
||||
@XmlElement(name = "notAfter")
|
||||
DateTime notAfterDate;
|
||||
|
||||
public String getFeeClass() {
|
||||
return feeClass;
|
||||
}
|
||||
|
||||
/** Builder for {@link FeeCheckResponseExtensionItemCommandStdV1}. */
|
||||
public static class Builder extends Buildable.Builder<FeeCheckResponseExtensionItemCommandStdV1> {
|
||||
|
||||
@@ -96,24 +74,9 @@ public class FeeCheckResponseExtensionItemCommandStdV1 extends ImmutableObject {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setEffectiveDate(DateTime effectiveDate) {
|
||||
getInstance().effectiveDate = effectiveDate;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setNotAfterDate(DateTime notAfterDate) {
|
||||
getInstance().notAfterDate = notAfterDate;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setFee(List<Fee> fees) {
|
||||
getInstance().fee = forceEmptyToNull(ImmutableList.copyOf(fees));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setClass(String feeClass) {
|
||||
getInstance().feeClass = feeClass;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,20 +17,18 @@ package google.registry.model.domain.feestdv1;
|
||||
import static google.registry.util.CollectionUtils.forceEmptyToNull;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import google.registry.model.domain.DomainObjectSpec;
|
||||
import google.registry.model.domain.Period;
|
||||
import google.registry.model.domain.fee.Fee;
|
||||
import google.registry.model.domain.fee.FeeCheckResponseExtensionItem;
|
||||
import google.registry.model.domain.fee.FeeQueryCommandExtensionItem.CommandName;
|
||||
import jakarta.xml.bind.annotation.XmlType;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** The version 1.0 response for a domain check on a single resource. */
|
||||
@XmlType(propOrder = {"object", "command"})
|
||||
@XmlType(propOrder = {"objID", "feeClass", "command"})
|
||||
public class FeeCheckResponseExtensionItemStdV1 extends FeeCheckResponseExtensionItem {
|
||||
|
||||
/** The domain that was checked. */
|
||||
DomainObjectSpec object;
|
||||
String objID;
|
||||
|
||||
/** The command that was checked. */
|
||||
FeeCheckResponseExtensionItemCommandStdV1 command;
|
||||
@@ -53,15 +51,6 @@ public class FeeCheckResponseExtensionItemStdV1 extends FeeCheckResponseExtensio
|
||||
return super.getFees();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is not annotated for JAXB because this version of the extension doesn't support
|
||||
* "feeClass" and because the data comes off of the command object rather than a field.
|
||||
*/
|
||||
@Override
|
||||
public String getFeeClass() {
|
||||
return command.getFeeClass();
|
||||
}
|
||||
|
||||
/** Builder for {@link FeeCheckResponseExtensionItemStdV1}. */
|
||||
public static class Builder
|
||||
extends FeeCheckResponseExtensionItem.Builder<FeeCheckResponseExtensionItemStdV1> {
|
||||
@@ -91,13 +80,13 @@ public class FeeCheckResponseExtensionItemStdV1 extends FeeCheckResponseExtensio
|
||||
|
||||
@Override
|
||||
public Builder setClass(String feeClass) {
|
||||
commandBuilder.setClass(feeClass);
|
||||
super.setClass(feeClass);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder setDomainNameIfSupported(String name) {
|
||||
getInstance().object = new DomainObjectSpec(name);
|
||||
getInstance().objID = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -106,17 +95,5 @@ public class FeeCheckResponseExtensionItemStdV1 extends FeeCheckResponseExtensio
|
||||
getInstance().command = commandBuilder.build();
|
||||
return super.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder setEffectiveDateIfSupported(DateTime effectiveDate) {
|
||||
commandBuilder.setEffectiveDate(effectiveDate);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder setNotAfterDateIfSupported(DateTime notAfterDate) {
|
||||
commandBuilder.setNotAfterDate(notAfterDate);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||
@Access(AccessType.FIELD)
|
||||
public abstract class DomainDsDataBase extends ImmutableObject implements UnsafeSerializable {
|
||||
|
||||
@XmlTransient @Transient String domainRepoId;
|
||||
@XmlTransient @Transient @Insignificant String domainRepoId;
|
||||
|
||||
/** The identifier for this particular key in the domain. */
|
||||
@Transient int keyTag;
|
||||
|
||||
@@ -24,6 +24,7 @@ import jakarta.xml.bind.annotation.XmlElement;
|
||||
import jakarta.xml.bind.annotation.XmlRootElement;
|
||||
import jakarta.xml.bind.annotation.XmlTransient;
|
||||
import jakarta.xml.bind.annotation.XmlType;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
/** The EPP secDNS extension that may be present on domain update commands. */
|
||||
@@ -55,16 +56,16 @@ public class SecDnsUpdateExtension extends ImmutableObject implements CommandExt
|
||||
return urgent;
|
||||
}
|
||||
|
||||
public Remove getRemove() {
|
||||
return remove;
|
||||
public Optional<Remove> getRemove() {
|
||||
return Optional.ofNullable(remove);
|
||||
}
|
||||
|
||||
public Add getAdd() {
|
||||
return add;
|
||||
public Optional<Add> getAdd() {
|
||||
return Optional.ofNullable(add);
|
||||
}
|
||||
|
||||
public Change getChange() {
|
||||
return change;
|
||||
public Optional<Change> getChange() {
|
||||
return Optional.ofNullable(change);
|
||||
}
|
||||
|
||||
@XmlTransient
|
||||
|
||||
@@ -14,13 +14,17 @@
|
||||
|
||||
package google.registry.model.eppcommon;
|
||||
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.eppinput.EppInput;
|
||||
import google.registry.model.eppoutput.EppOutput;
|
||||
import google.registry.util.NonFinalForTesting;
|
||||
import google.registry.util.RegistryEnvironment;
|
||||
import google.registry.xml.ValidationMode;
|
||||
import google.registry.xml.XmlException;
|
||||
import google.registry.xml.XmlTransformer;
|
||||
@@ -31,7 +35,7 @@ import java.io.ByteArrayOutputStream;
|
||||
public class EppXmlTransformer {
|
||||
|
||||
// Hardcoded XML schemas, ordered with respect to dependency.
|
||||
private static final ImmutableList<String> SCHEMAS =
|
||||
private static final ImmutableList<String> ALL_SCHEMAS =
|
||||
ImmutableList.of(
|
||||
"eppcom.xsd",
|
||||
"epp.xsd",
|
||||
@@ -54,11 +58,39 @@ public class EppXmlTransformer {
|
||||
"allocationToken-1.0.xsd",
|
||||
"bulkToken.xsd");
|
||||
|
||||
private static final XmlTransformer INPUT_TRANSFORMER =
|
||||
new XmlTransformer(SCHEMAS, EppInput.class);
|
||||
// XML schemas that should not be used in production (yet)
|
||||
private static final ImmutableSet<String> NON_PROD_SCHEMAS = ImmutableSet.of("fee-std-v1.xsd");
|
||||
|
||||
private static final XmlTransformer OUTPUT_TRANSFORMER =
|
||||
new XmlTransformer(SCHEMAS, EppOutput.class);
|
||||
// XML schemas that should only be used in production (for backcompat)
|
||||
private static final ImmutableSet<String> ONLY_PROD_SCHEMAS =
|
||||
ImmutableSet.of("fee06.xsd", "fee11.xsd", "fee12.xsd");
|
||||
|
||||
// TODO(gbrodman): make this final when we can actually remove the old fee extensions and aren't
|
||||
// relying on switching by environment
|
||||
@NonFinalForTesting
|
||||
private static XmlTransformer INPUT_TRANSFORMER =
|
||||
new XmlTransformer(getSchemas(), EppInput.class);
|
||||
|
||||
// TODO(gbrodman): make this final when we can actually remove the old fee extensions and aren't
|
||||
// relying on switching by environment
|
||||
@NonFinalForTesting
|
||||
private static XmlTransformer OUTPUT_TRANSFORMER =
|
||||
new XmlTransformer(getSchemas(), EppOutput.class);
|
||||
|
||||
@VisibleForTesting
|
||||
public static ImmutableList<String> getSchemas() {
|
||||
ImmutableSet<String> schemasToSkip =
|
||||
RegistryEnvironment.get().equals(RegistryEnvironment.PRODUCTION)
|
||||
? NON_PROD_SCHEMAS
|
||||
: ONLY_PROD_SCHEMAS;
|
||||
return ALL_SCHEMAS.stream().filter(s -> !schemasToSkip.contains(s)).collect(toImmutableList());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static void reloadTransformers() {
|
||||
INPUT_TRANSFORMER = new XmlTransformer(getSchemas(), EppInput.class);
|
||||
OUTPUT_TRANSFORMER = new XmlTransformer(getSchemas(), EppOutput.class);
|
||||
}
|
||||
|
||||
public static void validateOutput(String xml) throws XmlException {
|
||||
OUTPUT_TRANSFORMER.validate(xml);
|
||||
|
||||
@@ -17,6 +17,7 @@ package google.registry.model.eppcommon;
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static com.google.common.collect.Maps.uniqueIndex;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.model.domain.fee06.FeeCheckCommandExtensionV06;
|
||||
@@ -33,6 +34,8 @@ import google.registry.model.domain.rgp.RgpUpdateExtension;
|
||||
import google.registry.model.domain.secdns.SecDnsCreateExtension;
|
||||
import google.registry.model.eppinput.EppInput.CommandExtension;
|
||||
import google.registry.model.eppoutput.EppResponse.ResponseExtension;
|
||||
import google.registry.util.NonFinalForTesting;
|
||||
import google.registry.util.RegistryEnvironment;
|
||||
import jakarta.xml.bind.annotation.XmlSchema;
|
||||
import java.util.EnumSet;
|
||||
|
||||
@@ -43,35 +46,52 @@ public class ProtocolDefinition {
|
||||
public static final String LANGUAGE = "en";
|
||||
|
||||
public static final ImmutableSet<String> SUPPORTED_OBJECT_SERVICES =
|
||||
ImmutableSet.of(
|
||||
"urn:ietf:params:xml:ns:host-1.0",
|
||||
"urn:ietf:params:xml:ns:domain-1.0",
|
||||
"urn:ietf:params:xml:ns:contact-1.0");
|
||||
ImmutableSet.of("urn:ietf:params:xml:ns:host-1.0", "urn:ietf:params:xml:ns:domain-1.0");
|
||||
|
||||
/** Enums repesenting valid service extensions that are recognized by the server. */
|
||||
/** Enum representing which environments should have which service extensions enabled. */
|
||||
private enum ServiceExtensionVisibility {
|
||||
ALL,
|
||||
ONLY_IN_PRODUCTION,
|
||||
ONLY_IN_NON_PRODUCTION,
|
||||
NONE
|
||||
}
|
||||
|
||||
/** Enum representing valid service extensions that are recognized by the server. */
|
||||
public enum ServiceExtension {
|
||||
LAUNCH_EXTENSION_1_0(LaunchCreateExtension.class, null, true),
|
||||
REDEMPTION_GRACE_PERIOD_1_0(RgpUpdateExtension.class, null, true),
|
||||
SECURE_DNS_1_1(SecDnsCreateExtension.class, null, true),
|
||||
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),
|
||||
METADATA_1_0(MetadataExtension.class, null, false);
|
||||
LAUNCH_EXTENSION_1_0(LaunchCreateExtension.class, null, ServiceExtensionVisibility.ALL),
|
||||
REDEMPTION_GRACE_PERIOD_1_0(RgpUpdateExtension.class, null, ServiceExtensionVisibility.ALL),
|
||||
SECURE_DNS_1_1(SecDnsCreateExtension.class, null, ServiceExtensionVisibility.ALL),
|
||||
FEE_0_6(
|
||||
FeeCheckCommandExtensionV06.class,
|
||||
FeeCheckResponseExtensionV06.class,
|
||||
ServiceExtensionVisibility.ONLY_IN_PRODUCTION),
|
||||
FEE_0_11(
|
||||
FeeCheckCommandExtensionV11.class,
|
||||
FeeCheckResponseExtensionV11.class,
|
||||
ServiceExtensionVisibility.ONLY_IN_PRODUCTION),
|
||||
FEE_0_12(
|
||||
FeeCheckCommandExtensionV12.class,
|
||||
FeeCheckResponseExtensionV12.class,
|
||||
ServiceExtensionVisibility.ONLY_IN_PRODUCTION),
|
||||
FEE_1_00(
|
||||
FeeCheckCommandExtensionStdV1.class,
|
||||
FeeCheckResponseExtensionStdV1.class,
|
||||
ServiceExtensionVisibility.ONLY_IN_NON_PRODUCTION),
|
||||
METADATA_1_0(MetadataExtension.class, null, ServiceExtensionVisibility.NONE);
|
||||
|
||||
private final Class<? extends CommandExtension> commandExtensionClass;
|
||||
private final Class<? extends ResponseExtension> responseExtensionClass;
|
||||
private final String uri;
|
||||
private final boolean visible;
|
||||
private final ServiceExtensionVisibility visibility;
|
||||
|
||||
ServiceExtension(
|
||||
Class<? extends CommandExtension> commandExtensionClass,
|
||||
Class<? extends ResponseExtension> responseExtensionClass,
|
||||
boolean visible) {
|
||||
ServiceExtensionVisibility visibility) {
|
||||
this.commandExtensionClass = commandExtensionClass;
|
||||
this.responseExtensionClass = responseExtensionClass;
|
||||
this.uri = getCommandExtensionUri(commandExtensionClass);
|
||||
this.visible = visible;
|
||||
this.visibility = visibility;
|
||||
}
|
||||
|
||||
public Class<? extends CommandExtension> getCommandExtensionClass() {
|
||||
@@ -86,14 +106,20 @@ public class ProtocolDefinition {
|
||||
return uri;
|
||||
}
|
||||
|
||||
public boolean getVisible() {
|
||||
return visible;
|
||||
}
|
||||
|
||||
/** Returns the namespace URI of the command extension class. */
|
||||
public static String getCommandExtensionUri(Class<? extends CommandExtension> clazz) {
|
||||
return clazz.getPackage().getAnnotation(XmlSchema.class).namespace();
|
||||
}
|
||||
|
||||
public boolean isVisible() {
|
||||
return switch (visibility) {
|
||||
case ALL -> true;
|
||||
case ONLY_IN_PRODUCTION -> RegistryEnvironment.get().equals(RegistryEnvironment.PRODUCTION);
|
||||
case ONLY_IN_NON_PRODUCTION ->
|
||||
!RegistryEnvironment.get().equals(RegistryEnvironment.PRODUCTION);
|
||||
case NONE -> false;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,15 +136,25 @@ public class ProtocolDefinition {
|
||||
}
|
||||
|
||||
/** A set of all the visible extension URIs. */
|
||||
private static final ImmutableSet<String> visibleServiceExtensionUris =
|
||||
EnumSet.allOf(ServiceExtension.class)
|
||||
.stream()
|
||||
.filter(ServiceExtension::getVisible)
|
||||
.map(ServiceExtension::getUri)
|
||||
.collect(toImmutableSet());
|
||||
// TODO(gbrodman): make this final when we can actually remove the old fee extensions and aren't
|
||||
// relying on switching by environment
|
||||
@NonFinalForTesting private static ImmutableSet<String> visibleServiceExtensionUris;
|
||||
|
||||
static {
|
||||
reloadServiceExtensionUris();
|
||||
}
|
||||
|
||||
/** Return the set of all visible service extension URIs. */
|
||||
public static ImmutableSet<String> getVisibleServiceExtensionUris() {
|
||||
return visibleServiceExtensionUris;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static void reloadServiceExtensionUris() {
|
||||
visibleServiceExtensionUris =
|
||||
EnumSet.allOf(ServiceExtension.class).stream()
|
||||
.filter(ServiceExtension::isVisible)
|
||||
.map(ServiceExtension::getUri)
|
||||
.collect(toImmutableSet());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import static google.registry.util.DateTimeUtils.isBeforeOrAt;
|
||||
import com.google.common.base.Supplier;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.tmch.RstTmchUtils;
|
||||
import jakarta.persistence.CollectionTable;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.ElementCollection;
|
||||
@@ -71,6 +72,11 @@ public class SignedMarkRevocationList extends ImmutableObject {
|
||||
return CACHE.get();
|
||||
}
|
||||
|
||||
// TODO(b/412715713): remove the tld parameter when RST completes.
|
||||
public static SignedMarkRevocationList get(String tld) {
|
||||
return RstTmchUtils.getSmdrList(tld).orElseGet(SignedMarkRevocationList::get);
|
||||
}
|
||||
|
||||
/** Create a new {@link SignedMarkRevocationList} without saving it. */
|
||||
public static SignedMarkRevocationList create(
|
||||
DateTime creationTime, ImmutableMap<String, DateTime> revokes) {
|
||||
|
||||
@@ -1034,12 +1034,13 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl
|
||||
return this;
|
||||
}
|
||||
|
||||
public static final Pattern ROID_SUFFIX_PATTERN = Pattern.compile("^[A-Z\\d_]{1,8}$");
|
||||
public static final Pattern ROID_SUFFIX_PATTERN = Pattern.compile("^[A-Z\\d]{1,8}$");
|
||||
|
||||
public Builder setRoidSuffix(String roidSuffix) {
|
||||
checkArgument(
|
||||
ROID_SUFFIX_PATTERN.matcher(roidSuffix).matches(),
|
||||
"ROID suffix must be in format %s",
|
||||
"ROID suffix %s must be in format %s",
|
||||
roidSuffix,
|
||||
ROID_SUFFIX_PATTERN.pattern());
|
||||
getInstance().roidSuffix = roidSuffix;
|
||||
return this;
|
||||
|
||||
@@ -22,6 +22,7 @@ import com.github.benmanes.caffeine.cache.LoadingCache;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import google.registry.model.CacheUtils;
|
||||
import google.registry.tmch.RstTmchUtils;
|
||||
import java.time.Duration;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -72,6 +73,11 @@ public class ClaimsListDao {
|
||||
return CACHE.get(ClaimsListDao.class);
|
||||
}
|
||||
|
||||
// TODO(b/412715713): remove the tld parameter when RST completes.
|
||||
public static ClaimsList get(String tld) {
|
||||
return RstTmchUtils.getClaimsList(tld).orElseGet(ClaimsListDao::get);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the most recent revision of the {@link ClaimsList} in SQL or an empty list if it
|
||||
* doesn't exist.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -17,6 +17,7 @@ package google.registry.module;
|
||||
import dagger.Module;
|
||||
import dagger.Subcomponent;
|
||||
import google.registry.batch.BatchModule;
|
||||
import google.registry.batch.BulkDomainTransferAction;
|
||||
import google.registry.batch.CannedScriptExecutionAction;
|
||||
import google.registry.batch.DeleteExpiredDomainsAction;
|
||||
import google.registry.batch.DeleteLoadTestDataAction;
|
||||
@@ -61,6 +62,9 @@ import google.registry.module.ReadinessProbeAction.ReadinessProbeActionFrontend;
|
||||
import google.registry.module.ReadinessProbeAction.ReadinessProbeActionPubApi;
|
||||
import google.registry.module.ReadinessProbeAction.ReadinessProbeConsoleAction;
|
||||
import google.registry.monitoring.whitebox.WhiteboxModule;
|
||||
import google.registry.mosapi.GetServiceStateAction;
|
||||
import google.registry.mosapi.TriggerServiceStateAction;
|
||||
import google.registry.mosapi.module.MosApiRequestModule;
|
||||
import google.registry.rdap.RdapAutnumAction;
|
||||
import google.registry.rdap.RdapDomainAction;
|
||||
import google.registry.rdap.RdapDomainSearchAction;
|
||||
@@ -150,6 +154,7 @@ import google.registry.ui.server.console.settings.SecurityAction;
|
||||
EppToolModule.class,
|
||||
IcannReportingModule.class,
|
||||
LoadTestModule.class,
|
||||
MosApiRequestModule.class,
|
||||
RdapModule.class,
|
||||
RdeModule.class,
|
||||
ReportingModule.class,
|
||||
@@ -171,6 +176,8 @@ interface RequestComponent {
|
||||
|
||||
BsaValidateAction bsaValidateAction();
|
||||
|
||||
BulkDomainTransferAction bulkDomainTransferAction();
|
||||
|
||||
CannedScriptExecutionAction cannedScriptExecutionAction();
|
||||
|
||||
CheckApiAction checkApiAction();
|
||||
@@ -229,6 +236,8 @@ interface RequestComponent {
|
||||
|
||||
GenerateZoneFilesAction generateZoneFilesAction();
|
||||
|
||||
GetServiceStateAction getServiceStateAction();
|
||||
|
||||
IcannReportingStagingAction icannReportingStagingAction();
|
||||
|
||||
IcannReportingUploadAction icannReportingUploadAction();
|
||||
@@ -331,6 +340,8 @@ interface RequestComponent {
|
||||
|
||||
TmchSmdrlAction tmchSmdrlAction();
|
||||
|
||||
TriggerServiceStateAction triggerServiceStateAction();
|
||||
|
||||
UpdateRegistrarRdapBaseUrlsAction updateRegistrarRdapBaseUrlsAction();
|
||||
|
||||
UpdateUserGroupAction updateUserGroupAction();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
// 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 com.google.common.net.MediaType;
|
||||
import com.google.gson.Gson;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.HttpException.ServiceUnavailableException;
|
||||
import google.registry.request.Parameter;
|
||||
import google.registry.request.Response;
|
||||
import google.registry.request.auth.Auth;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Optional;
|
||||
|
||||
/** An action that returns the current MoSAPI service state for a given TLD or all TLDs. */
|
||||
@Action(
|
||||
service = Action.Service.BACKEND,
|
||||
path = GetServiceStateAction.PATH,
|
||||
method = Action.Method.GET,
|
||||
auth = Auth.AUTH_ADMIN)
|
||||
public class GetServiceStateAction implements Runnable {
|
||||
|
||||
public static final String PATH = "/_dr/mosapi/getServiceState";
|
||||
public static final String TLD_PARAM = "tld";
|
||||
|
||||
private final MosApiStateService stateService;
|
||||
private final Response response;
|
||||
private final Gson gson;
|
||||
private final Optional<String> tld;
|
||||
|
||||
@Inject
|
||||
public GetServiceStateAction(
|
||||
MosApiStateService stateService,
|
||||
Response response,
|
||||
Gson gson,
|
||||
@Parameter(TLD_PARAM) Optional<String> tld) {
|
||||
this.stateService = stateService;
|
||||
this.response = response;
|
||||
this.gson = gson;
|
||||
this.tld = tld;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
response.setContentType(MediaType.JSON_UTF_8);
|
||||
try {
|
||||
if (tld.isPresent()) {
|
||||
response.setPayload(gson.toJson(stateService.getServiceStateSummary(tld.get())));
|
||||
} else {
|
||||
response.setPayload(gson.toJson(stateService.getAllServiceStateSummaries()));
|
||||
}
|
||||
} catch (MosApiException e) {
|
||||
throw new ServiceUnavailableException("Error fetching MoSAPI service state.");
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user