1
0
mirror of https://github.com/google/nomulus synced 2026-05-21 15:21:48 +00:00

Compare commits

...

10 Commits

Author SHA1 Message Date
Lai Jiang
01464e8e05 Remove async-delete-pull queue (#1841)
It is not being used anymore.
2022-11-07 22:16:49 -05:00
Weimin Yu
52b0574c73 Use GoogleCredentials for tools Cloud SQL access (#1844) 2022-11-04 17:20:21 -04:00
Lai Jiang
a0f177b71f The only remaining Ofy entity is GaeUserConverter after this PR. (#1838)
Therefore this PR also removed several classes and related tests that
support the setup and verification of Ofy entities.

In addition, support for creating a VKey from a string is limited to
VKey<? extends EppResource> only because it is the only use case (to
pass a key to an EPP resource in a web safe way to facilitate resave),
and we do not want to keep an extra simple name to class mapping, in
addition to what persistence.xml contains. I looked into using
PersistenceXmlUtility to obtain the mapping, but the xml file contains
classes with the same simple name (namely OneTime from both PollMessage
and BillingEvent). It doesn't seem like a worthwhile investment to write
more code to deal with that, when the fact is that we only need to
consider EppResource.
2022-11-04 12:47:11 -04:00
Weimin Yu
e01448b52e Fix list_cursors command for globals (#1840) 2022-11-04 11:18:20 -04:00
Weimin Yu
4da474e094 Remove Cloud KMS from Nomulus Server (#1839)
* Remove Cloud KMS from Nomulus Server

Removed Cloud KMS from the Nomulus (:core) since it is no longer used.

Renamed remaining classes to reflect their use of the SecretManager.

Updated the config instructions to use a new codename for the keyring:
KMS to CSM. This PR works with both codenames. Will drop 'KMS' after
the internal repo is updated.
2022-11-04 11:17:15 -04:00
Weimin Yu
e273a18b4a Implement Keyless Delegated credential (#1836)
Add a implementation of Delegated credential without using downloaded private key.

This is a stop-gap implementation while waiting for a solution from the Java auth library.

Also added a verifier action to test the new credential in production. Testing is helpful because:

Configuration is per-environment, therefore, success in alpha does not fully validate prod.
The relevant use case is triggered by low-frequency activities. Problem may not pop out for hours or longer.
2022-11-03 11:45:23 -04:00
Weimin Yu
8275bc45b9 Switch MetricReporter to App default credential (#1837) 2022-11-02 20:02:52 -04:00
Pavlo Tkach
0b6805531a Add flyway files for allocateId sequence alternative (#1830) 2022-11-02 12:06:40 -04:00
Lai Jiang
592454d97d Remove ofy support from HistoryEntry (#1823)
This PR removes all Ofy related cruft around `HistoryEntry` and its three subclasses in order to support dual-write to datastore and SQL. The class structure was refactored to take advantage of inheritance to reduce code duplication and improve clarity.

Note that for the embedded EPP resources, either their columns are all empty (for pre-3.0 entities imported into SQL), including their unique foreign key (domain name, host name, contact id) and the update timestamp; or they are filled as expected (for entities that were written since dual writing was implemented).

Therefore the check for foreign key column nullness in the various `@PostLoad` methods in the original code is an no-op as the EPP resource would have been loaded as null. In another word, there is no case where the update timestamp is null but other columns are not.

See the following query for the most recent entries in each table where the foreign key column or the update timestamp are null -- they are the same.

```
[I]postgres=> select MAX(history_modification_time) from "DomainHistory" where update_timestamp is null;
            max
----------------------------
 2021-09-27 15:56:52.502+00
(1 row)

[I]postgres=> select MAX(history_modification_time) from "DomainHistory" where domain_name is null;
            max
----------------------------
 2021-09-27 15:56:52.502+00
(1 row)

[I]postgres=> select MAX(history_modification_time) from "ContactHistory" where update_timestamp is null;
            max
----------------------------
 2021-09-27 15:56:04.311+00
(1 row)

[I]postgres=> select MAX(history_modification_time) from "ContactHistory" where contact_id is null;
            max
----------------------------
 2021-09-27 15:56:04.311+00
(1 row)

[I]postgres=> select MAX(history_modification_time) from "HostHistory" where update_timestamp is null;
            max
----------------------------
 2021-09-27 15:52:16.517+00
(1 row)

[I]postgres=> select MAX(history_modification_time) from "HostHistory" where host_name is null;
            max
----------------------------
 2021-09-27 15:52:16.517+00
(1 row)
```
2022-11-01 21:17:20 -04:00
Weimin Yu
671e42474c Document alternative method to deploy schema 2022-11-01 12:58:11 -04:00
223 changed files with 2140 additions and 5094 deletions

View File

@@ -186,7 +186,6 @@ dependencies {
implementation deps['com.google.apis:google-api-services-admin-directory']
implementation deps['com.google.apis:google-api-services-appengine']
implementation deps['com.google.apis:google-api-services-bigquery']
implementation deps['com.google.apis:google-api-services-cloudkms']
implementation deps['com.google.apis:google-api-services-dataflow']
implementation deps['com.google.apis:google-api-services-dns']
implementation deps['com.google.apis:google-api-services-drive']

View File

@@ -77,7 +77,6 @@ com.google.apis:google-api-services-admin-directory:directory_v1-rev118-1.25.0=c
com.google.apis:google-api-services-appengine:v1-rev20220612-1.32.1=compileClasspath,default,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-bigquery:v2-rev20211129-1.32.1=compileClasspath,default,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-clouddebugger:v2-rev20210813-1.32.1=compileClasspath,default,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-cloudkms:v1-rev20220701-1.32.1=compileClasspath,default,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-cloudresourcemanager:v1-rev20211017-1.32.1=compileClasspath,default,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-dataflow:v1b3-rev20210818-1.32.1=compileClasspath,default,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-dns:v2beta1-rev99-1.25.0=compileClasspath,default,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath

View File

@@ -17,25 +17,16 @@ package google.registry.batch;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.util.DateTimeUtils.isBeforeOrAt;
import com.google.appengine.api.taskqueue.Queue;
import com.google.appengine.api.taskqueue.TaskOptions;
import com.google.appengine.api.taskqueue.TaskOptions.Method;
import com.google.appengine.api.taskqueue.TransientFailureException;
import com.google.common.base.Joiner;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Multimap;
import com.google.common.flogger.FluentLogger;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.EppResource;
import google.registry.model.eppcommon.Trid;
import google.registry.model.host.Host;
import google.registry.persistence.VKey;
import google.registry.request.Action.Service;
import google.registry.util.CloudTasksUtils;
import google.registry.util.Retrier;
import javax.inject.Inject;
import javax.inject.Named;
import org.joda.time.DateTime;
import org.joda.time.Duration;
@@ -44,45 +35,28 @@ public final class AsyncTaskEnqueuer {
/** The HTTP parameter names used by async flows. */
public static final String PARAM_RESOURCE_KEY = "resourceKey";
public static final String PARAM_REQUESTING_CLIENT_ID = "requestingClientId";
public static final String PARAM_CLIENT_TRANSACTION_ID = "clientTransactionId";
public static final String PARAM_SERVER_TRANSACTION_ID = "serverTransactionId";
public static final String PARAM_IS_SUPERUSER = "isSuperuser";
public static final String PARAM_HOST_KEY = "hostKey";
public static final String PARAM_REQUESTED_TIME = "requestedTime";
public static final String PARAM_RESAVE_TIMES = "resaveTimes";
/** The task queue names used by async flows. */
public static final String QUEUE_ASYNC_ACTIONS = "async-actions";
public static final String QUEUE_ASYNC_DELETE = "async-delete-pull";
public static final String QUEUE_ASYNC_HOST_RENAME = "async-host-rename-pull";
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private static final Duration MAX_ASYNC_ETA = Duration.standardDays(30);
private final Duration asyncDeleteDelay;
private final Queue asyncDeletePullQueue;
private final Queue asyncDnsRefreshPullQueue;
private final Retrier retrier;
private CloudTasksUtils cloudTasksUtils;
private final CloudTasksUtils cloudTasksUtils;
@Inject
public AsyncTaskEnqueuer(
@Named(QUEUE_ASYNC_DELETE) Queue asyncDeletePullQueue,
@Named(QUEUE_ASYNC_HOST_RENAME) Queue asyncDnsRefreshPullQueue,
@Config("asyncDeleteDelay") Duration asyncDeleteDelay,
CloudTasksUtils cloudTasksUtils,
Retrier retrier) {
this.asyncDeletePullQueue = asyncDeletePullQueue;
this.asyncDnsRefreshPullQueue = asyncDnsRefreshPullQueue;
this.asyncDeleteDelay = asyncDeleteDelay;
public AsyncTaskEnqueuer(CloudTasksUtils cloudTasksUtils) {
this.cloudTasksUtils = cloudTasksUtils;
this.retrier = retrier;
}
/** Enqueues a task to asynchronously re-save an entity at some point in the future. */
public void enqueueAsyncResave(VKey<?> entityToResave, DateTime now, DateTime whenToResave) {
public void enqueueAsyncResave(
VKey<? extends EppResource> entityToResave, DateTime now, DateTime whenToResave) {
enqueueAsyncResave(entityToResave, now, ImmutableSortedSet.of(whenToResave));
}
@@ -93,7 +67,9 @@ public final class AsyncTaskEnqueuer {
* itself to run at the next time if there are remaining re-saves scheduled.
*/
public void enqueueAsyncResave(
VKey<?> entityKey, DateTime now, ImmutableSortedSet<DateTime> whenToResave) {
VKey<? extends EppResource> entityKey,
DateTime now,
ImmutableSortedSet<DateTime> whenToResave) {
DateTime firstResave = whenToResave.first();
checkArgument(isBeforeOrAt(now, firstResave), "Can't enqueue a resave to run in the past");
Duration etaDuration = new Duration(now, firstResave);
@@ -115,46 +91,4 @@ public final class AsyncTaskEnqueuer {
cloudTasksUtils.createPostTaskWithDelay(
ResaveEntityAction.PATH, Service.BACKEND.toString(), params, etaDuration));
}
/** Enqueues a task to asynchronously delete a contact or host, by key. */
public void enqueueAsyncDelete(
EppResource resourceToDelete,
DateTime now,
String requestingRegistrarId,
Trid trid,
boolean isSuperuser) {
logger.atInfo().log(
"Enqueuing async deletion of %s on behalf of registrar %s.",
resourceToDelete.getRepoId(), requestingRegistrarId);
TaskOptions task =
TaskOptions.Builder.withMethod(Method.PULL)
.countdownMillis(asyncDeleteDelay.getMillis())
.param(PARAM_RESOURCE_KEY, resourceToDelete.createVKey().stringify())
.param(PARAM_REQUESTING_CLIENT_ID, requestingRegistrarId)
.param(PARAM_SERVER_TRANSACTION_ID, trid.getServerTransactionId())
.param(PARAM_IS_SUPERUSER, Boolean.toString(isSuperuser))
.param(PARAM_REQUESTED_TIME, now.toString());
trid.getClientTransactionId()
.ifPresent(clTrid -> task.param(PARAM_CLIENT_TRANSACTION_ID, clTrid));
addTaskToQueueWithRetry(asyncDeletePullQueue, task);
}
/** Enqueues a task to asynchronously refresh DNS for a renamed host. */
public void enqueueAsyncDnsRefresh(Host host, DateTime now) {
VKey<Host> hostKey = host.createVKey();
logger.atInfo().log("Enqueuing async DNS refresh for renamed host %s.", hostKey);
addTaskToQueueWithRetry(
asyncDnsRefreshPullQueue,
TaskOptions.Builder.withMethod(Method.PULL)
.param(PARAM_HOST_KEY, hostKey.stringify())
.param(PARAM_REQUESTED_TIME, now.toString()));
}
/**
* Adds a task to a queue with retrying, to avoid aborting the entire flow over a transient issue
* enqueuing a task.
*/
private void addTaskToQueueWithRetry(final Queue queue, final TaskOptions task) {
retrier.callWithRetry(() -> queue.add(task), TransientFailureException.class);
}
}

View File

@@ -14,13 +14,10 @@
package google.registry.batch;
import static com.google.appengine.api.taskqueue.QueueFactory.getQueue;
import static google.registry.batch.AsyncTaskEnqueuer.PARAM_REQUESTED_TIME;
import static google.registry.batch.AsyncTaskEnqueuer.PARAM_RESAVE_TIMES;
import static google.registry.batch.AsyncTaskEnqueuer.PARAM_RESOURCE_KEY;
import static google.registry.batch.AsyncTaskEnqueuer.QUEUE_ASYNC_ACTIONS;
import static google.registry.batch.AsyncTaskEnqueuer.QUEUE_ASYNC_DELETE;
import static google.registry.batch.AsyncTaskEnqueuer.QUEUE_ASYNC_HOST_RENAME;
import static google.registry.batch.CannedScriptExecutionAction.SCRIPT_PARAM;
import static google.registry.request.RequestParameters.extractBooleanParameter;
import static google.registry.request.RequestParameters.extractIntParameter;
import static google.registry.request.RequestParameters.extractLongParameter;
@@ -32,13 +29,11 @@ import static google.registry.request.RequestParameters.extractRequiredDatetimeP
import static google.registry.request.RequestParameters.extractRequiredParameter;
import static google.registry.request.RequestParameters.extractSetOfDatetimeParameters;
import com.google.appengine.api.taskqueue.Queue;
import com.google.common.collect.ImmutableSet;
import dagger.Module;
import dagger.Provides;
import google.registry.request.Parameter;
import java.util.Optional;
import javax.inject.Named;
import javax.servlet.http.HttpServletRequest;
import org.joda.time.DateTime;
@@ -128,21 +123,10 @@ public class BatchModule {
return extractBooleanParameter(req, PARAM_DRY_RUN);
}
// TODO(b/234424397): remove method after credential changes are rolled out.
@Provides
@Named(QUEUE_ASYNC_ACTIONS)
static Queue provideAsyncActionsPushQueue() {
return getQueue(QUEUE_ASYNC_ACTIONS);
}
@Provides
@Named(QUEUE_ASYNC_DELETE)
static Queue provideAsyncDeletePullQueue() {
return getQueue(QUEUE_ASYNC_DELETE);
}
@Provides
@Named(QUEUE_ASYNC_HOST_RENAME)
static Queue provideAsyncHostRenamePullQueue() {
return getQueue(QUEUE_ASYNC_HOST_RENAME);
@Parameter(SCRIPT_PARAM)
static String provideScriptName(HttpServletRequest req) {
return extractRequiredParameter(req, SCRIPT_PARAM);
}
}

View File

@@ -0,0 +1,74 @@
// Copyright 2022 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 google.registry.request.Action.Method.POST;
import com.google.common.collect.ImmutableMap;
import com.google.common.flogger.FluentLogger;
import google.registry.batch.cannedscript.GroupsApiChecker;
import google.registry.request.Action;
import google.registry.request.Parameter;
import google.registry.request.auth.Auth;
import javax.inject.Inject;
/**
* Action that executes a canned script specified by the caller.
*
* <p>This class is introduced to help the safe rollout of credential changes. The delegated
* credentials in particular, benefit from this: they require manual configuration of the peer
* system in each environment, and may wait hours or even days after deployment until triggered by
* user activities.
*
* <p>This action can be invoked using the Nomulus CLI command: {@code nomulus -e ${env} curl
* --service BACKEND -X POST -u '/_dr/task/executeCannedScript?script=${script_name}'}
*/
// TODO(b/234424397): remove class after credential changes are rolled out.
@Action(
service = Action.Service.BACKEND,
path = "/_dr/task/executeCannedScript",
method = POST,
automaticallyPrintOk = true,
auth = Auth.AUTH_INTERNAL_OR_ADMIN)
public class CannedScriptExecutionAction implements Runnable {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
static final String SCRIPT_PARAM = "script";
static final ImmutableMap<String, Runnable> SCRIPTS =
ImmutableMap.of("runGroupsApiChecks", GroupsApiChecker::runGroupsApiChecks);
private final String scriptName;
@Inject
CannedScriptExecutionAction(@Parameter(SCRIPT_PARAM) String scriptName) {
logger.atInfo().log("Received request to run script %s", scriptName);
this.scriptName = scriptName;
}
@Override
public void run() {
if (!SCRIPTS.containsKey(scriptName)) {
throw new IllegalArgumentException("Script not found:" + scriptName);
}
try {
SCRIPTS.get(scriptName).run();
logger.atInfo().log("Finished running %s.", scriptName);
} catch (Throwable t) {
logger.atWarning().withCause(t).log("Error executing %s", scriptName);
throw new RuntimeException("Execution failed.");
}
}
}

View File

@@ -52,7 +52,7 @@ public class CheckPackagesComplianceAction implements Runnable {
"FROM DomainHistory WHERE current_package_token = :token AND"
+ " modificationTime >= :lastBilling AND type = 'DOMAIN_CREATE'",
DomainHistory.class)
.setParameter("token", packagePromo.getToken().getSqlKey().toString())
.setParameter("token", packagePromo.getToken().getKey().toString())
.setParameter(
"lastBilling", packagePromo.getNextBillingDate().minusYears(1))
.getResultList();
@@ -62,7 +62,7 @@ public class CheckPackagesComplianceAction implements Runnable {
logger.atInfo().log(
"Package with package token %s has exceeded their max domain creation limit"
+ " by %d name(s).",
packagePromo.getToken().getSqlKey(), overage);
packagePromo.getToken().getKey(), overage);
packagesOverCreateLimit.add(packagePromo);
}
}

View File

@@ -101,15 +101,20 @@ public class DeleteProberDataAction implements Runnable {
@Inject DnsQueue dnsQueue;
@Inject @Parameter(PARAM_DRY_RUN) boolean isDryRun;
@Inject
@Parameter(PARAM_DRY_RUN)
boolean isDryRun;
/** List of TLDs to work on. If empty - will work on all TLDs that end with .test. */
@Inject @Parameter(PARAM_TLDS) ImmutableSet<String> tlds;
@Inject
@Parameter(PARAM_TLDS)
ImmutableSet<String> tlds;
@Inject
@Config("registryAdminClientId")
String registryAdminRegistrarId;
@Inject DeleteProberDataAction() {}
@Inject
DeleteProberDataAction() {}
@Override
public void run() {
@@ -151,7 +156,7 @@ public class DeleteProberDataAction implements Runnable {
DateTime now = tm().getTransactionTime();
// Scroll through domains, soft-deleting as necessary (very few will be soft-deleted) and
// keeping track of which domains to hard-delete (there can be many, so we batch them up)
ScrollableResults scrollableResult =
try (ScrollableResults scrollableResult =
jpaTm()
.query(DOMAIN_QUERY_STRING, Domain.class)
.setParameter("tlds", deletableTlds)
@@ -161,28 +166,30 @@ public class DeleteProberDataAction implements Runnable {
.setParameter("nowAutoTimestamp", CreateAutoTimestamp.create(now))
.unwrap(Query.class)
.setCacheMode(CacheMode.IGNORE)
.scroll(ScrollMode.FORWARD_ONLY);
ImmutableList.Builder<String> domainRepoIdsToHardDelete = new ImmutableList.Builder<>();
ImmutableList.Builder<String> hostNamesToHardDelete = new ImmutableList.Builder<>();
for (int i = 1; scrollableResult.next(); i = (i + 1) % BATCH_SIZE) {
Domain domain = (Domain) scrollableResult.get(0);
processDomain(
domain,
domainRepoIdsToHardDelete,
hostNamesToHardDelete,
softDeletedDomains,
hardDeletedDomains);
// Batch the deletion and DB flush + session clearing so we don't OOM
if (i == 0) {
hardDeleteDomainsAndHosts(domainRepoIdsToHardDelete.build(), hostNamesToHardDelete.build());
domainRepoIdsToHardDelete = new ImmutableList.Builder<>();
hostNamesToHardDelete = new ImmutableList.Builder<>();
jpaTm().getEntityManager().flush();
jpaTm().getEntityManager().clear();
.scroll(ScrollMode.FORWARD_ONLY)) {
ImmutableList.Builder<String> domainRepoIdsToHardDelete = new ImmutableList.Builder<>();
ImmutableList.Builder<String> hostNamesToHardDelete = new ImmutableList.Builder<>();
for (int i = 1; scrollableResult.next(); i = (i + 1) % BATCH_SIZE) {
Domain domain = (Domain) scrollableResult.get(0);
processDomain(
domain,
domainRepoIdsToHardDelete,
hostNamesToHardDelete,
softDeletedDomains,
hardDeletedDomains);
// Batch the deletion and DB flush + session clearing, so we don't OOM
if (i == 0) {
hardDeleteDomainsAndHosts(
domainRepoIdsToHardDelete.build(), hostNamesToHardDelete.build());
domainRepoIdsToHardDelete = new ImmutableList.Builder<>();
hostNamesToHardDelete = new ImmutableList.Builder<>();
jpaTm().getEntityManager().flush();
jpaTm().getEntityManager().clear();
}
}
// process the remainder
hardDeleteDomainsAndHosts(domainRepoIdsToHardDelete.build(), hostNamesToHardDelete.build());
}
// process the remainder
hardDeleteDomainsAndHosts(domainRepoIdsToHardDelete.build(), hostNamesToHardDelete.build());
}
private void processDomain(
@@ -236,7 +243,7 @@ public class DeleteProberDataAction implements Runnable {
.setParameter("repoIds", domainRepoIds)
.executeUpdate();
jpaTm()
.query("DELETE FROM DomainHistory WHERE domainRepoId IN :repoIds")
.query("DELETE FROM DomainHistory WHERE repoId IN :repoIds")
.setParameter("repoIds", domainRepoIds)
.executeUpdate();
jpaTm()

View File

@@ -253,7 +253,7 @@ public class ExpandRecurringBillingEventsAction implements Runnable {
final ImmutableSet<DateTime> billingTimes =
getBillingTimesInScope(eventTimes, cursorTime, executeTime, tld);
VKey<Domain> domainKey = VKey.createSql(Domain.class, recurring.getDomainRepoId());
VKey<Domain> domainKey = VKey.create(Domain.class, recurring.getDomainRepoId());
Iterable<OneTime> oneTimesForDomain;
oneTimesForDomain =
tm().createQueryComposer(OneTime.class)

View File

@@ -23,7 +23,6 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.flogger.FluentLogger;
import google.registry.model.EppResource;
import google.registry.model.ImmutableObject;
import google.registry.persistence.VKey;
import google.registry.request.Action;
import google.registry.request.Action.Method;
@@ -75,14 +74,11 @@ public class ResaveEntityAction implements Runnable {
"Re-saving entity %s which was enqueued at %s.", resourceKey, requestedTime);
tm().transact(
() -> {
ImmutableObject entity = tm().loadByKey(VKey.create(resourceKey));
tm().put(
(entity instanceof EppResource)
? ((EppResource) entity).cloneProjectedAtTime(tm().getTransactionTime())
: entity);
EppResource entity = tm().loadByKey(VKey.createEppVKeyFromString(resourceKey));
tm().put(entity.cloneProjectedAtTime(tm().getTransactionTime()));
if (!resaveTimes.isEmpty()) {
asyncTaskEnqueuer.enqueueAsyncResave(
VKey.create(resourceKey), requestedTime, resaveTimes);
VKey.createEppVKeyFromString(resourceKey), requestedTime, resaveTimes);
}
});
response.setPayload("Entity re-saved.");

View File

@@ -0,0 +1,122 @@
// Copyright 2022 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.cannedscript;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.util.RegistrarUtils.normalizeRegistrarId;
import com.google.api.services.admin.directory.Directory;
import com.google.api.services.groupssettings.Groupssettings;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.base.Throwables;
import com.google.common.collect.Streams;
import com.google.common.flogger.FluentLogger;
import dagger.Component;
import dagger.Module;
import dagger.Provides;
import google.registry.config.CredentialModule;
import google.registry.config.CredentialModule.AdcDelegatedCredential;
import google.registry.config.RegistryConfig.Config;
import google.registry.config.RegistryConfig.ConfigModule;
import google.registry.groups.DirectoryGroupsConnection;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarPoc;
import google.registry.util.GoogleCredentialsBundle;
import google.registry.util.UtilsModule;
import java.util.List;
import java.util.Set;
import javax.inject.Singleton;
/**
* Verifies that the credential with the {@link AdcDelegatedCredential} annotation can be used to
* access the Google Workspace Groups API.
*/
// TODO(b/234424397): remove class after credential changes are rolled out.
public class GroupsApiChecker {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private static final Supplier<GroupsConnectionComponent> COMPONENT_SUPPLIER =
Suppliers.memoize(DaggerGroupsApiChecker_GroupsConnectionComponent::create);
public static void runGroupsApiChecks() {
GroupsConnectionComponent component = COMPONENT_SUPPLIER.get();
DirectoryGroupsConnection groupsConnection = component.groupsConnection();
List<Registrar> registrars =
Streams.stream(Registrar.loadAllCached())
.filter(registrar -> registrar.isLive() && registrar.getType() == Registrar.Type.REAL)
.collect(toImmutableList());
for (Registrar registrar : registrars) {
for (final RegistrarPoc.Type type : RegistrarPoc.Type.values()) {
String groupKey =
String.format(
"%s-%s-contacts@%s",
normalizeRegistrarId(registrar.getRegistrarId()),
type.getDisplayName(),
component.gSuiteDomainName());
try {
Set<String> currentMembers = groupsConnection.getMembersOfGroup(groupKey);
logger.atInfo().log("Found %s members for %s.", currentMembers.size(), groupKey);
} catch (Exception e) {
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
}
}
}
}
@Singleton
@Component(
modules = {
ConfigModule.class,
CredentialModule.class,
GroupsApiModule.class,
UtilsModule.class
})
interface GroupsConnectionComponent {
DirectoryGroupsConnection groupsConnection();
@Config("gSuiteDomainName")
String gSuiteDomainName();
}
@Module
static class GroupsApiModule {
@Provides
static Directory provideDirectory(
@AdcDelegatedCredential GoogleCredentialsBundle credentialsBundle,
@Config("projectId") String projectId) {
return new Directory.Builder(
credentialsBundle.getHttpTransport(),
credentialsBundle.getJsonFactory(),
credentialsBundle.getHttpRequestInitializer())
.setApplicationName(projectId)
.build();
}
@Provides
static Groupssettings provideGroupsSettings(
@AdcDelegatedCredential GoogleCredentialsBundle credentialsBundle,
@Config("projectId") String projectId) {
return new Groupssettings.Builder(
credentialsBundle.getHttpTransport(),
credentialsBundle.getJsonFactory(),
credentialsBundle.getHttpRequestInitializer())
.setApplicationName(projectId)
.build();
}
}
}

View File

@@ -55,7 +55,7 @@ import google.registry.model.rde.RdeMode;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.Registrar.Type;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.reporting.HistoryEntryDao;
import google.registry.model.reporting.HistoryEntry.HistoryEntryId;
import google.registry.persistence.PersistenceModule.TransactionIsolationLevel;
import google.registry.persistence.VKey;
import google.registry.rde.DepositFragment;
@@ -71,11 +71,9 @@ import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.util.HashSet;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.persistence.IdClass;
import org.apache.beam.sdk.Pipeline;
import org.apache.beam.sdk.PipelineResult;
import org.apache.beam.sdk.coders.KvCoder;
@@ -172,6 +170,7 @@ import org.joda.time.DateTime;
* @see <a href="https://cloud.google.com/dataflow/docs/guides/templates/using-flex-templates">Using
* Flex Templates</a>
*/
@SuppressWarnings("ALL")
@Singleton
public class RdePipeline implements Serializable {
@@ -191,16 +190,6 @@ public class RdePipeline implements Serializable {
private static final ImmutableSet<Type> IGNORED_REGISTRAR_TYPES =
Sets.immutableEnumSet(Registrar.Type.MONITORING, Registrar.Type.TEST);
// The field name of the EPP resource embedded in its corresponding history entry.
private static final ImmutableMap<Class<? extends HistoryEntry>, String> EPP_RESOURCE_FIELD_NAME =
ImmutableMap.of(
DomainHistory.class,
"domainBase",
ContactHistory.class,
"contactBase",
HostHistory.class,
"hostBase");
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@Inject
@@ -314,7 +303,7 @@ public class RdePipeline implements Serializable {
TypeDescriptor.of(DepositFragment.class)))
.via(
(String registrarRepoId) -> {
VKey<Registrar> key = VKey.createSql(Registrar.class, registrarRepoId);
VKey<Registrar> key = VKey.create(Registrar.class, registrarRepoId);
includedRegistrarCounter.inc();
Registrar registrar = jpaTm().transact(() -> jpaTm().loadByKey(key));
DepositFragment fragment = marshaller.marshalRegistrar(registrar);
@@ -337,26 +326,20 @@ public class RdePipeline implements Serializable {
*/
private <T extends HistoryEntry> PCollection<KV<String, Long>> getMostRecentHistoryEntries(
Pipeline pipeline, Class<T> historyClass) {
String repoIdFieldName = HistoryEntryDao.REPO_ID_FIELD_NAMES.get(historyClass);
String resourceFieldName = EPP_RESOURCE_FIELD_NAME.get(historyClass);
return pipeline.apply(
String.format("Load most recent %s", historyClass.getSimpleName()),
RegistryJpaIO.read(
("SELECT %repoIdField%, id FROM %entity% WHERE (%repoIdField%, modificationTime)"
+ " IN (SELECT %repoIdField%, MAX(modificationTime) FROM %entity% WHERE"
+ " modificationTime <= :watermark GROUP BY %repoIdField%) AND"
+ " %resourceField%.deletionTime > :watermark AND"
+ " COALESCE(%resourceField%.creationClientId, '') NOT LIKE 'prober-%' AND"
+ " COALESCE(%resourceField%.currentSponsorClientId, '') NOT LIKE 'prober-%'"
+ " AND COALESCE(%resourceField%.lastEppUpdateClientId, '') NOT LIKE"
("SELECT repoId, revisionId FROM %entity% WHERE (repoId, modificationTime) IN"
+ " (SELECT repoId, MAX(modificationTime) FROM %entity% WHERE"
+ " modificationTime <= :watermark GROUP BY repoId) AND resource.deletionTime"
+ " > :watermark AND COALESCE(resource.creationRegistrarId, '') NOT LIKE"
+ " 'prober-%' AND COALESCE(resource.currentSponsorRegistrarId, '') NOT LIKE"
+ " 'prober-%' AND COALESCE(resource.lastEppUpdateRegistrarId, '') NOT LIKE"
+ " 'prober-%' "
+ (historyClass == DomainHistory.class
? "AND %resourceField%.tld IN "
+ "(SELECT id FROM Tld WHERE tldType = 'REAL')"
? "AND resource.tld IN " + "(SELECT id FROM Tld WHERE tldType = 'REAL')"
: ""))
.replace("%entity%", historyClass.getSimpleName())
.replace("%repoIdField%", repoIdFieldName)
.replace("%resourceField%", resourceFieldName),
.replace("%entity%", historyClass.getSimpleName()),
ImmutableMap.of("watermark", watermark),
Object[].class,
row -> KV.of((String) row[0], (long) row[1]))
@@ -380,38 +363,28 @@ public class RdePipeline implements Serializable {
checkState(
dedupedIds.size() == 1,
"Multiple unique revision IDs detected for %s repo ID %s: %s",
EPP_RESOURCE_FIELD_NAME.get(historyEntryClazz),
historyEntryClazz.getSimpleName(),
repoId,
ids);
logger.atSevere().log(
"Duplicate revision IDs detected for %s repo ID %s: %s",
EPP_RESOURCE_FIELD_NAME.get(historyEntryClazz), repoId, ids);
historyEntryClazz.getSimpleName(), repoId, ids);
}
return loadResourceByHistoryEntryId(historyEntryClazz, repoId, ids.get(0));
}
private <T extends HistoryEntry> EppResource loadResourceByHistoryEntryId(
Class<T> historyEntryClazz, String repoId, long revisionId) {
try {
Class<?> idClazz = historyEntryClazz.getAnnotation(IdClass.class).value();
Serializable idObject =
(Serializable)
idClazz.getConstructor(String.class, long.class).newInstance(repoId, revisionId);
return jpaTm()
.transact(() -> jpaTm().loadByKey(VKey.createSql(historyEntryClazz, idObject)))
.getResourceAtPointInTime()
.map(resource -> resource.cloneProjectedAtTime(watermark))
.get();
} catch (NoSuchMethodException
| InvocationTargetException
| InstantiationException
| IllegalAccessException e) {
throw new RuntimeException(
String.format(
"Cannot load resource from %s with repoId %s and revisionId %s",
historyEntryClazz.getSimpleName(), repoId, revisionId),
e);
}
return jpaTm()
.transact(
() ->
jpaTm()
.loadByKey(
VKey.create(historyEntryClazz, new HistoryEntryId(repoId, revisionId))))
.getResourceAtPointInTime()
.map(resource -> resource.cloneProjectedAtTime(watermark))
.get();
}
/**
@@ -496,12 +469,12 @@ public class RdePipeline implements Serializable {
// Contacts and hosts are only deposited in RDE, not BRDA.
if (pendingDeposit.mode() == RdeMode.FULL) {
HashSet<Serializable> contacts = new HashSet<>();
contacts.add(domain.getAdminContact().getSqlKey());
contacts.add(domain.getTechContact().getSqlKey());
contacts.add(domain.getRegistrant().getSqlKey());
contacts.add(domain.getAdminContact().getKey());
contacts.add(domain.getTechContact().getKey());
contacts.add(domain.getRegistrant().getKey());
// Billing contact is not mandatory.
if (domain.getBillingContact() != null) {
contacts.add(domain.getBillingContact().getSqlKey());
contacts.add(domain.getBillingContact().getKey());
}
referencedContactCounter.inc(contacts.size());
contacts.forEach(
@@ -519,7 +492,7 @@ public class RdePipeline implements Serializable {
.get(REFERENCED_HOSTS)
.output(
KV.of(
(String) hostKey.getSqlKey(),
(String) hostKey.getKey(),
pendingDeposit)));
}
}
@@ -592,7 +565,7 @@ public class RdePipeline implements Serializable {
// The output are pairs of
// (superordinateDomainRepoId,
// (subordinateHostRepoId, (pendingDeposit, revisionId))).
KV.of((String) host.getSuperordinateDomain().getSqlKey(), kv));
KV.of((String) host.getSuperordinateDomain().getKey(), kv));
} else {
externalHostCounter.inc();
DepositFragment fragment = marshaller.marshalExternalHost(host);

View File

@@ -115,7 +115,7 @@ public class Spec11Pipeline implements Serializable {
Read<Object[], KV<String, String>> read =
RegistryJpaIO.read(
"select d.repoId, r.emailAddress from Domain d join Registrar r on"
+ " d.currentSponsorClientId = r.registrarId where r.type = 'REAL' and"
+ " d.currentSponsorRegistrarId = r.registrarId where r.type = 'REAL' and"
+ " d.deletionTime > now()",
false,
Spec11Pipeline::parseRow)
@@ -133,9 +133,7 @@ public class Spec11Pipeline implements Serializable {
Domain domain =
jpaTm()
.transact(
() ->
jpaTm()
.loadByKey(VKey.createSql(Domain.class, input.getKey())));
() -> jpaTm().loadByKey(VKey.create(Domain.class, input.getKey())));
String emailAddress = input.getValue();
if (emailAddress == null) {
emailAddress = "";

View File

@@ -14,14 +14,17 @@
package google.registry.config;
import static com.google.common.base.Preconditions.checkArgument;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.auth.ServiceAccountSigner;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.common.collect.ImmutableList;
import dagger.Module;
import dagger.Provides;
import google.registry.config.RegistryConfig.Config;
import google.registry.keyring.api.KeyModule.Key;
import google.registry.util.Clock;
import google.registry.util.GoogleCredentialsBundle;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@@ -29,6 +32,7 @@ import java.io.UncheckedIOException;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.time.Duration;
import javax.inject.Qualifier;
import javax.inject.Singleton;
@@ -158,6 +162,48 @@ public abstract class CredentialModule {
.createScoped(requiredScopes));
}
/**
* Provides a {@link GoogleCredentialsBundle} with delegated access to Google Workspace APIs for
* 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}.
*/
@AdcDelegatedCredential
@Provides
@Singleton
public static GoogleCredentialsBundle provideSelfSignedDelegatedCredential(
@Config("defaultCredentialOauthScopes") ImmutableList<String> defaultScopes,
@Config("delegatedCredentialOauthScopes") ImmutableList<String> delegationScopes,
@ApplicationDefaultCredential GoogleCredentialsBundle credentialsBundle,
@Config("gSuiteAdminAccountEmailAddress") String gSuiteAdminAccountEmailAddress,
@Config("tokenRefreshDelay") Duration tokenRefreshDelay,
Clock clock) {
GoogleCredentials signer = credentialsBundle.getGoogleCredentials();
checkArgument(
signer instanceof ServiceAccountSigner,
"Expecting a ServiceAccountSigner, found %s.",
signer.getClass().getSimpleName());
try {
// Refreshing as sanity check on the ADC.
signer.refresh();
} catch (IOException e) {
throw new RuntimeException("Cannot refresh the ApplicationDefaultCredential", e);
}
DelegatedCredentials credential =
DelegatedCredentials.createSelfSignedDelegatedCredential(
(ServiceAccountSigner) signer,
ImmutableList.<String>builder().addAll(defaultScopes).addAll(delegationScopes).build(),
gSuiteAdminAccountEmailAddress,
clock,
tokenRefreshDelay);
return GoogleCredentialsBundle.create(credential);
}
/** Dagger qualifier for the scope-less Application Default Credential. */
@Qualifier
@Documented
@@ -195,6 +241,15 @@ public abstract class CredentialModule {
@Retention(RetentionPolicy.RUNTIME)
public @interface DelegatedCredential {}
/**
* Dagger qualifier for a credential with delegated admin access for a dasher domain (for Google
* Workspace) backed by the application default credential (ADC).
*/
@Qualifier
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface AdcDelegatedCredential {}
/** Dagger qualifier for the local credential used in the nomulus tool. */
@Qualifier
@Documented

View File

@@ -0,0 +1,268 @@
// Copyright 2022 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.config;
import static com.google.common.base.Preconditions.checkArgument;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpBackOffIOExceptionHandler;
import com.google.api.client.http.HttpBackOffUnsuccessfulResponseHandler;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpRequestFactory;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.UrlEncodedContent;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.JsonObjectParser;
import com.google.api.client.json.gson.GsonFactory;
import com.google.api.client.json.webtoken.JsonWebSignature;
import com.google.api.client.json.webtoken.JsonWebToken;
import com.google.api.client.util.ExponentialBackOff;
import com.google.api.client.util.GenericData;
import com.google.api.client.util.StringUtils;
import com.google.auth.ServiceAccountSigner;
import com.google.auth.http.HttpTransportFactory;
import com.google.auth.oauth2.AccessToken;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import google.registry.util.Clock;
import java.io.IOException;
import java.math.BigDecimal;
import java.time.Duration;
import java.util.Collection;
import java.util.Date;
import java.util.Map;
import java.util.ServiceLoader;
import org.apache.commons.codec.binary.Base64;
/**
* OAuth2 credentials for accessing Google Workspace APIs with domain-wide delegation. It fetches
* access tokens using JSON Web Tokens (JWT) signed by a user-provided {@link ServiceAccountSigner}.
*
* <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.
*
* <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
* another service account {@code D} and gain delegated access as {@code D}, as long as S has the
* Token Creator role to {@code D}. This usage is documented here for future reference.
*
* <p>As of October 2022, the functionalities described above are not implemented in the GCP Java
* Auth library, although they are available in the Python library. We have filed a <a
* href="https://github.com/googleapis/google-auth-library-java/issues/1064">feature request</a>.
* This class is a stop-gap implementation.
*
* <p>The main body of this class is adapted from {@link
* com.google.auth.oauth2.ServiceAccountCredentials} with cosmetic changes. The important changes
* include the removal of all uses of the private key and the signing of the JWT (in {@link
* #signAssertion}). We choose not to extend {@code ServiceAccountCredentials} because it would add
* dependency to the non-public details of that class.
*/
public class DelegatedCredentials extends GoogleCredentials {
private static final long serialVersionUID = 617127523756785546L;
private static final String DEFAULT_TOKEN_URI = "https://accounts.google.com/o/oauth2/token";
private static final String GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer";
private static final JsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance();
private static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport();
private static final String VALUE_NOT_FOUND_MESSAGE = "%sExpected value %s not found.";
private static final String VALUE_WRONG_TYPE_MESSAGE = "%sExpected %s value %s of wrong type.";
private static final String PARSE_ERROR_PREFIX = "Error parsing token refresh response. ";
private static final Duration MAX_TOKEN_REFRESH_DELAY = Duration.ofHours(1);
private final ServiceAccountSigner signer;
private final String delegatedServiceAccountUser;
private final ImmutableList<String> scopes;
private final String delegatingUserEmail;
private final Clock clock;
private final Duration tokenRefreshDelay;
private final HttpTransportFactory transportFactory;
/**
* Creates a {@link DelegatedCredentials} instance that is self-signed by the signer, which must
* have delegated access to the Workspace APIs.
*
* @param signer Signs for the generated JWT tokens. This may be the application default
* credential
* @param scopes The scopes to use when generating JWT tokens
* @param delegatingUserEmail The Workspace user whose permissions are delegated to the signer
* @param clock Used for setting token expiration times.
* @param tokenRefreshDelay The lifetime of each token. Should not exceed one hour according to
* GCP recommendations.
* @return
*/
static DelegatedCredentials createSelfSignedDelegatedCredential(
ServiceAccountSigner signer,
Collection<String> scopes,
String delegatingUserEmail,
Clock clock,
Duration tokenRefreshDelay) {
return new DelegatedCredentials(
signer, signer.getAccount(), scopes, delegatingUserEmail, clock, tokenRefreshDelay);
}
private DelegatedCredentials(
ServiceAccountSigner signer,
String delegatedServiceAccountUser,
Collection<String> scopes,
String delegatingUserEmail,
Clock clock,
Duration tokenRefreshDelay) {
checkArgument(
tokenRefreshDelay.getSeconds() <= MAX_TOKEN_REFRESH_DELAY.getSeconds(),
"Max refresh delay must not exceed %s.",
MAX_TOKEN_REFRESH_DELAY);
this.signer = signer;
this.delegatedServiceAccountUser = delegatedServiceAccountUser;
this.scopes = ImmutableList.copyOf(scopes);
this.delegatingUserEmail = delegatingUserEmail;
this.clock = clock;
this.tokenRefreshDelay = tokenRefreshDelay;
this.transportFactory =
getFromServiceLoader(
HttpTransportFactory.class, DelegatedCredentials::provideHttpTransport);
}
/**
* Refreshes the OAuth2 access token by getting a new access token using a JSON Web Token (JWT).
*/
@Override
public AccessToken refreshAccessToken() throws IOException {
JsonFactory jsonFactory = JSON_FACTORY;
long currentTime = clock.nowUtc().getMillis();
String assertion = createAssertion(jsonFactory, currentTime);
GenericData tokenRequest = new GenericData();
tokenRequest.set("grant_type", GRANT_TYPE);
tokenRequest.set("assertion", assertion);
UrlEncodedContent content = new UrlEncodedContent(tokenRequest);
HttpRequestFactory requestFactory = transportFactory.create().createRequestFactory();
HttpRequest request =
requestFactory.buildPostRequest(new GenericUrl(DEFAULT_TOKEN_URI), content);
request.setParser(new JsonObjectParser(jsonFactory));
request.setIOExceptionHandler(new HttpBackOffIOExceptionHandler(new ExponentialBackOff()));
request.setUnsuccessfulResponseHandler(
new HttpBackOffUnsuccessfulResponseHandler(new ExponentialBackOff())
.setBackOffRequired(
response -> {
int code = response.getStatusCode();
return (
// Server error --- includes timeout errors, which use 500 instead of 408
code / 100 == 5
// Forbidden error --- for historical reasons, used for rate_limit_exceeded
// errors instead of 429, but there currently seems no robust automatic way
// to
// distinguish these cases: see
// https://github.com/google/google-api-java-client/issues/662
|| code == 403);
}));
HttpResponse response;
try {
response = request.execute();
} catch (IOException e) {
throw new IOException(
String.format("Error getting access token for service account: %s", e.getMessage()), e);
}
GenericData responseData = response.parseAs(GenericData.class);
String accessToken = validateString(responseData, "access_token", PARSE_ERROR_PREFIX);
int expiresInSeconds = validateInt32(responseData, "expires_in", PARSE_ERROR_PREFIX);
long expiresAtMilliseconds = clock.nowUtc().getMillis() + expiresInSeconds * 1000L;
return new AccessToken(accessToken, new Date(expiresAtMilliseconds));
}
String createAssertion(JsonFactory jsonFactory, long currentTime) throws IOException {
JsonWebSignature.Header header = new JsonWebSignature.Header();
header.setAlgorithm("RS256");
header.setType("JWT");
JsonWebToken.Payload payload = new JsonWebToken.Payload();
payload.setIssuer(this.delegatedServiceAccountUser);
payload.setIssuedAtTimeSeconds(currentTime / 1000);
payload.setExpirationTimeSeconds(currentTime / 1000 + tokenRefreshDelay.getSeconds());
payload.setSubject(delegatingUserEmail);
payload.put("scope", Joiner.on(' ').join(scopes));
payload.setAudience(DEFAULT_TOKEN_URI);
return signAssertion(jsonFactory, header, payload);
}
String signAssertion(
JsonFactory jsonFactory, JsonWebSignature.Header header, JsonWebToken.Payload payload)
throws IOException {
String content =
Base64.encodeBase64URLSafeString(jsonFactory.toByteArray(header))
+ "."
+ Base64.encodeBase64URLSafeString(jsonFactory.toByteArray(payload));
byte[] contentBytes = StringUtils.getBytesUtf8(content);
byte[] signature = signer.sign(contentBytes); // Changed from ServiceAccountCredentials.
return content + "." + Base64.encodeBase64URLSafeString(signature);
}
static HttpTransport provideHttpTransport() {
return HTTP_TRANSPORT;
}
protected static <T> T getFromServiceLoader(Class<? extends T> clazz, T defaultInstance) {
return Iterables.getFirst(ServiceLoader.load(clazz), defaultInstance);
}
/** Return the specified string from JSON or throw a helpful error message. */
static String validateString(Map<String, Object> map, String key, String errorPrefix)
throws IOException {
Object value = map.get(key);
if (value == null) {
throw new IOException(String.format(VALUE_NOT_FOUND_MESSAGE, errorPrefix, key));
}
if (!(value instanceof String)) {
throw new IOException(String.format(VALUE_WRONG_TYPE_MESSAGE, errorPrefix, "string", key));
}
return (String) value;
}
/** Return the specified integer from JSON or throw a helpful error message. */
static int validateInt32(Map<String, Object> map, String key, String errorPrefix)
throws IOException {
Object value = map.get(key);
if (value == null) {
throw new IOException(String.format(VALUE_NOT_FOUND_MESSAGE, errorPrefix, key));
}
if (value instanceof BigDecimal) {
BigDecimal bigDecimalValue = (BigDecimal) value;
return bigDecimalValue.intValueExact();
}
if (!(value instanceof Integer)) {
throw new IOException(String.format(VALUE_WRONG_TYPE_MESSAGE, errorPrefix, "integer", key));
}
return (Integer) value;
}
}

View File

@@ -1027,38 +1027,6 @@ public final class RegistryConfig {
return 50;
}
/**
* Returns the delay before executing async delete flow mapreduces.
*
* <p>This delay should be sufficiently longer than a transaction, to solve the following
* problem:
*
* <ul>
* <li>a domain mutation flow starts a transaction
* <li>the domain flow non-transactionally reads a resource and sees that it's not in
* PENDING_DELETE
* <li>the domain flow creates a new reference to this resource
* <li>a contact/host delete flow runs and marks the resource PENDING_DELETE and commits
* <li>the domain flow commits
* </ul>
*
* <p>Although we try not to add references to a PENDING_DELETE resource, strictly speaking that
* is ok as long as the mapreduce eventually sees the new reference (and therefore
* asynchronously fails the delete). Without this delay, the mapreduce might have started before
* the domain flow committed, and could potentially miss the reference.
*
* <p>If you are using EPP resource caching (eppResourceCachingEnabled in YAML), then this
* duration should also be longer than that cache duration (eppResourceCachingSeconds).
*
* @see google.registry.config.RegistryConfigSettings.Caching
* @see google.registry.batch.AsyncTaskEnqueuer
*/
@Provides
@Config("asyncDeleteDelay")
public static Duration provideAsyncDeleteDelay(RegistryConfigSettings config) {
return Duration.standardSeconds(config.misc.asyncDeleteDelaySeconds);
}
/**
* The server ID used in the 'svID' element of an EPP 'greeting'.
*
@@ -1076,24 +1044,6 @@ public final class RegistryConfig {
return config.keyring.activeKeyring;
}
/**
* The name to use for the Cloud KMS KeyRing containing encryption keys for Nomulus secrets.
*
* @see <a
* href="https://cloud.google.com/kms/docs/reference/rest/v1/projects.locations.keyRings#KeyRing">projects.locations.keyRings</a>
*/
@Provides
@Config("cloudKmsKeyRing")
public static String provideCloudKmsKeyRing(RegistryConfigSettings config) {
return config.keyring.kms.keyringName;
}
@Provides
@Config("cloudKmsProjectId")
public static String provideCloudKmsProjectId(RegistryConfigSettings config) {
return config.keyring.kms.projectId;
}
@Provides
@Config("customLogicFactoryClass")
public static String provideCustomLogicFactoryClass(RegistryConfigSettings config) {
@@ -1223,6 +1173,12 @@ public final class RegistryConfig {
return ImmutableList.copyOf(config.credentialOAuth.localCredentialOauthScopes);
}
@Provides
@Config("tokenRefreshDelay")
public static java.time.Duration provideTokenRefreshDelay(RegistryConfigSettings config) {
return java.time.Duration.ofSeconds(config.credentialOAuth.tokenRefreshDelaySeconds);
}
/** OAuth client ID used by the nomulus tool. */
@Provides
@Config("toolsClientId")

View File

@@ -67,6 +67,7 @@ public class RegistryConfigSettings {
public List<String> defaultCredentialOauthScopes;
public List<String> delegatedCredentialOauthScopes;
public List<String> localCredentialOauthScopes;
public int tokenRefreshDelaySeconds;
}
/** Configuration options for the G Suite account used by Nomulus. */
@@ -206,13 +207,13 @@ public class RegistryConfigSettings {
public String alertRecipientEmailAddress;
public String spec11OutgoingEmailAddress;
public List<String> spec11BccEmailAddresses;
public int asyncDeleteDelaySeconds;
public int transientFailureRetries;
}
/** Configuration for keyrings (used to store secrets outside of source). */
public static class Keyring {
public String activeKeyring;
// TODO(b/257276342): Remove after config files in nomulus-internal are updated.
public Kms kms;
}

View File

@@ -340,6 +340,9 @@ credentialOAuth:
- 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
icannReporting:
# URL we PUT monthly ICANN transactions reports to.
@@ -418,11 +421,6 @@ misc:
spec11BccEmailAddresses:
- abuse@example.com
# How long to delay processing of asynchronous deletions. This should always
# be longer than eppResourceCachingSeconds, to prevent deleted contacts or
# hosts from being used on domains.
asyncDeleteDelaySeconds: 90
# Number of times to retry a GAE operation when a transient exception is thrown.
# The number of milliseconds it'll sleep before giving up is (2^n - 2) * 100.
transientFailureRetries: 12
@@ -446,7 +444,8 @@ beam:
stagingBucketUrl: gcs-bucket-with-staged-templates
keyring:
# The name of the active keyring, either "KMS" or "Dummy".
# The name of the active keyring, either "Dummy" or "CSM". The latter stands
# for Cloud SecretManager.
activeKeyring: Dummy
# Configuration options specific to Google Cloud KMS.

View File

@@ -293,6 +293,12 @@ have been in the database for a certain period of time. -->
<url-pattern>/_dr/task/wipeOutCloudSql</url-pattern>
</servlet-mapping>
<!-- Action to execute canned scripts -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/executeCannedScript</url-pattern>
</servlet-mapping>
<!-- Security config -->
<security-constraint>
<web-resource-collection>

View File

@@ -18,86 +18,6 @@
</retry-parameters>
</queue>
<queue>
<name>async-delete-pull</name>
<mode>pull</mode>
</queue>
<queue>
<name>async-host-rename-pull</name>
<mode>pull</mode>
</queue>
<queue>
<name>export-commits</name>
<rate>10/s</rate>
<bucket-size>100</bucket-size>
<retry-parameters>
<!-- Retry aggressively since a single delayed export increases our time window of
unrecoverable data loss in the event of a Datastore failure. -->
<min-backoff-seconds>1</min-backoff-seconds>
<max-backoff-seconds>60</max-backoff-seconds>
<!-- No age limit; a failed export should be retried as long as possible to avoid
having data missing from our exported commit log record. -->
</retry-parameters>
</queue>
<!-- Queue for polling export BigQuery jobs for completion. -->
<queue>
<name>export-bigquery-poll</name>
<!-- Limit queue to 5 concurrent tasks and 5 per second to avoid hitting BigQuery quotas. -->
<rate>5/s</rate>
<bucket-size>5</bucket-size>
<max-concurrent-requests>5</max-concurrent-requests>
<!-- Check every 20s and increase interval to every 5 minutes. -->
<retry-parameters>
<min-backoff-seconds>20</min-backoff-seconds>
<max-backoff-seconds>300</max-backoff-seconds>
<max-doublings>2</max-doublings>
</retry-parameters>
</queue>
<!-- Queue for launching new snapshots and for triggering the initial BigQuery load jobs. -->
<queue>
<name>export-snapshot</name>
<rate>1/s</rate>
<retry-parameters>
<!-- Should be less than the exportSnapshot cron interval; see cron.xml. -->
<task-age-limit>22h</task-age-limit>
<!-- Retry starting at a 5m interval and increasing up to a 30m interval. -->
<min-backoff-seconds>300</min-backoff-seconds>
<max-backoff-seconds>1800</max-backoff-seconds>
<task-retry-limit>10</task-retry-limit>
</retry-parameters>
</queue>
<!-- Queue for polling managed backup snapshots for completion. -->
<queue>
<name>export-snapshot-poll</name>
<rate>5/m</rate>
<retry-parameters>
<!-- Should be less than the exportSnapshot cron interval; see cron.xml. -->
<task-age-limit>22h</task-age-limit>
<!-- Retry starting at a 1m interval and increasing up to a 5m interval. -->
<min-backoff-seconds>60</min-backoff-seconds>
<max-backoff-seconds>300</max-backoff-seconds>
</retry-parameters>
</queue>
<!-- Queue for updating BigQuery views after a snapshot kind's load job completes. -->
<queue>
<name>export-snapshot-update-view</name>
<rate>1/s</rate>
<retry-parameters>
<!-- Should be less than the exportSnapshot cron interval; see cron.xml. -->
<task-age-limit>22h</task-age-limit>
<!-- Retry starting at a 10s interval and increasing up to a 1m interval. -->
<min-backoff-seconds>10</min-backoff-seconds>
<max-backoff-seconds>60</max-backoff-seconds>
<task-retry-limit>10</task-retry-limit>
</retry-parameters>
</queue>
<queue>
<name>rde-upload</name>
<rate>10/m</rate>
@@ -149,7 +69,7 @@
</retry-parameters>
</queue>
<!-- Queue for tasks to produce LORDN CSV reports, either by by the query or queue method. -->
<!-- Queue for tasks to produce LORDN CSV reports, either by the query or queue method. -->
<queue>
<name>nordn</name>
<rate>1/s</rate>
@@ -171,17 +91,6 @@
<mode>pull</mode>
</queue>
<!-- Queue used by the MapReduce library for running tasks.
Do not re-use this queue for tasks that our code creates (e.g. tasks to launch MapReduces
that aren't themselves part of a running MapReduce).-->
<queue>
<name>mapreduce</name>
<!-- Warning: DO NOT SET A <target> parameter for this queue. See b/24782801 for why. -->
<rate>500/s</rate>
<bucket-size>100</bucket-size>
</queue>
<!-- Queue for tasks that sync data to Google Spreadsheets. -->
<queue>
<name>sheet</name>
@@ -208,71 +117,4 @@
<max-concurrent-requests>5</max-concurrent-requests>
</queue>
<!-- Queue for replaying commit logs to SQL during the transition from Datastore -> SQL. -->
<queue>
<name>replay-commit-logs-to-sql</name>
<rate>1/s</rate>
</queue>
<!-- The load[0-9] queues are used for load-testing, and can be safely deleted
in any environment that doesn't require load-testing. -->
<queue>
<name>load0</name>
<rate>500/s</rate>
<bucket-size>500</bucket-size>
</queue>
<queue>
<name>load1</name>
<rate>500/s</rate>
<bucket-size>500</bucket-size>
</queue>
<queue>
<name>load2</name>
<rate>500/s</rate>
<bucket-size>500</bucket-size>
</queue>
<queue>
<name>load3</name>
<rate>500/s</rate>
<bucket-size>500</bucket-size>
</queue>
<queue>
<name>load4</name>
<rate>500/s</rate>
<bucket-size>500</bucket-size>
</queue>
<queue>
<name>load5</name>
<rate>500/s</rate>
<bucket-size>500</bucket-size>
</queue>
<queue>
<name>load6</name>
<rate>500/s</rate>
<bucket-size>500</bucket-size>
</queue>
<queue>
<name>load7</name>
<rate>500/s</rate>
<bucket-size>500</bucket-size>
</queue>
<queue>
<name>load8</name>
<rate>500/s</rate>
<bucket-size>500</bucket-size>
</queue>
<queue>
<name>load9</name>
<rate>500/s</rate>
<bucket-size>500</bucket-size>
</queue>
</queue-entries>

View File

@@ -23,7 +23,6 @@ import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.Throwables;
import com.google.common.flogger.FluentLogger;
import com.googlecode.objectify.Key;
import google.registry.flows.EppException.CommandUseErrorException;
import google.registry.flows.EppException.ParameterValueRangeErrorException;
import google.registry.flows.EppException.SyntaxErrorException;
@@ -34,7 +33,7 @@ import google.registry.model.eppcommon.EppXmlTransformer;
import google.registry.model.eppinput.EppInput.WrongProtocolVersionException;
import google.registry.model.eppoutput.EppOutput;
import google.registry.model.host.InetAddressAdapter.IpVersionMismatchException;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.reporting.HistoryEntry.HistoryEntryId;
import google.registry.model.translators.CurrencyUnitAdapter.UnknownCurrencyException;
import google.registry.xml.XmlException;
import java.util.List;
@@ -103,9 +102,8 @@ public final class FlowUtils {
}
}
public static <H extends HistoryEntry> Key<H> createHistoryKey(
EppResource parent, Class<H> clazz) {
return Key.create(Key.create(parent), clazz, allocateId());
public static HistoryEntryId createHistoryEntryId(EppResource parent) {
return new HistoryEntryId(parent.getRepoId(), allocateId());
}
/** Registrar is not logged in. */
@@ -118,7 +116,7 @@ public final class FlowUtils {
/** IP address version mismatch. */
public static class IpAddressVersionMismatchException extends ParameterValueRangeErrorException {
public IpAddressVersionMismatchException() {
super("IP adddress version mismatch");
super("IP address version mismatch");
}
}

View File

@@ -163,7 +163,7 @@ public final class ResourceFlowUtils {
// The roid should match one of the contacts.
Optional<VKey<Contact>> foundContact =
domain.getReferencedContacts().stream()
.filter(key -> key.getSqlKey().equals(authRepoId))
.filter(key -> key.getKey().equals(authRepoId))
.findFirst();
if (!foundContact.isPresent()) {
throw new BadAuthInfoForResourceException();

View File

@@ -20,17 +20,15 @@ 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 com.googlecode.objectify.Key;
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.ContactHistory;
import google.registry.model.contact.ContactHistory.ContactHistoryId;
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;
@@ -69,10 +67,7 @@ public class ContactFlowUtils {
/** Create a poll message for the gaining client in a transfer. */
static PollMessage createGainingTransferPollMessage(
String targetId,
TransferData transferData,
DateTime now,
Key<ContactHistory> contactHistoryKey) {
String targetId, TransferData transferData, DateTime now, HistoryEntryId contactHistoryId) {
return new PollMessage.OneTime.Builder()
.setRegistrarId(transferData.getGainingRegistrarId())
.setEventTime(transferData.getPendingTransferExpirationTime())
@@ -85,23 +80,19 @@ public class ContactFlowUtils {
transferData.getTransferStatus().isApproved(),
transferData.getTransferRequestTrid(),
now)))
.setContactHistoryId(
new ContactHistoryId(
contactHistoryKey.getParent().getName(), contactHistoryKey.getId()))
.setContactHistoryId(contactHistoryId)
.build();
}
/** Create a poll message for the losing client in a transfer. */
static PollMessage createLosingTransferPollMessage(
String targetId, TransferData transferData, Key<ContactHistory> contactHistoryKey) {
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(
new ContactHistoryId(
contactHistoryKey.getParent().getName(), contactHistoryKey.getId()))
.setContactHistoryId(contactHistoryId)
.build();
}

View File

@@ -90,10 +90,10 @@ public final class ContactInfoFlow implements Flow {
.setVoiceNumber(contact.getVoiceNumber())
.setFaxNumber(contact.getFaxNumber())
.setEmailAddress(contact.getEmailAddress())
.setCurrentSponsorClientId(contact.getCurrentSponsorRegistrarId())
.setCreationClientId(contact.getCreationRegistrarId())
.setCurrentSponsorRegistrarId(contact.getCurrentSponsorRegistrarId())
.setCreationRegistrarId(contact.getCreationRegistrarId())
.setCreationTime(contact.getCreationTime())
.setLastEppUpdateClientId(contact.getLastEppUpdateRegistrarId())
.setLastEppUpdateRegistrarId(contact.getLastEppUpdateRegistrarId())
.setLastEppUpdateTime(contact.getLastEppUpdateTime())
.setLastTransferTime(contact.getLastTransferTime())
.setAuthInfo(includeAuthInfo ? contact.getAuthInfo() : null)

View File

@@ -26,7 +26,6 @@ import static google.registry.model.reporting.HistoryEntry.Type.CONTACT_TRANSFER
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key;
import google.registry.flows.EppException;
import google.registry.flows.ExtensionManager;
import google.registry.flows.FlowModule.RegistrarId;
@@ -93,7 +92,7 @@ public final class ContactTransferApproveFlow implements TransactionalFlow {
// Create a poll message for the gaining client.
PollMessage gainingPollMessage =
createGainingTransferPollMessage(
targetId, newContact.getTransferData(), now, Key.create(contactHistory));
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

View File

@@ -26,7 +26,6 @@ import static google.registry.model.reporting.HistoryEntry.Type.CONTACT_TRANSFER
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key;
import google.registry.flows.EppException;
import google.registry.flows.ExtensionManager;
import google.registry.flows.FlowModule.RegistrarId;
@@ -73,7 +72,7 @@ public final class ContactTransferCancelFlow implements TransactionalFlow {
@Inject ContactTransferCancelFlow() {}
@Override
public final EppResponse run() throws EppException {
public EppResponse run() throws EppException {
extensionManager.register(MetadataExtension.class);
validateRegistrarIsLoggedIn(registrarId);
extensionManager.validate();
@@ -89,7 +88,7 @@ public final class ContactTransferCancelFlow implements TransactionalFlow {
// Create a poll message for the losing client.
PollMessage losingPollMessage =
createLosingTransferPollMessage(
targetId, newContact.getTransferData(), Key.create(contactHistory));
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

View File

@@ -26,7 +26,6 @@ import static google.registry.model.reporting.HistoryEntry.Type.CONTACT_TRANSFER
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key;
import google.registry.flows.EppException;
import google.registry.flows.ExtensionManager;
import google.registry.flows.FlowModule.RegistrarId;
@@ -86,7 +85,7 @@ public final class ContactTransferRejectFlow implements TransactionalFlow {
historyBuilder.setType(CONTACT_TRANSFER_REJECT).setContact(newContact).build();
PollMessage gainingPollMessage =
createGainingTransferPollMessage(
targetId, newContact.getTransferData(), now, Key.create(contactHistory));
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

View File

@@ -14,7 +14,7 @@
package google.registry.flows.contact;
import static google.registry.flows.FlowUtils.createHistoryKey;
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;
@@ -28,7 +28,6 @@ import static google.registry.model.reporting.HistoryEntry.Type.CONTACT_TRANSFER
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key;
import google.registry.config.RegistryConfig.Config;
import google.registry.flows.EppException;
import google.registry.flows.ExtensionManager;
@@ -46,6 +45,7 @@ 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.model.reporting.IcannReportingTypes.ActivityReportField;
import google.registry.model.transfer.ContactTransferData;
import google.registry.model.transfer.TransferStatus;
@@ -74,21 +74,27 @@ import org.joda.time.Duration;
@ReportingSpec(ActivityReportField.CONTACT_TRANSFER_REQUEST)
public final class ContactTransferRequestFlow implements TransactionalFlow {
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> 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
@Config("contactAutomaticTransferLength")
Duration automaticTransferLength;
@Inject ContactHistory.Builder historyBuilder;
@Inject Trid trid;
@Inject EppResponse.Builder responseBuilder;
@Inject ContactTransferRequestFlow() {}
@Inject
ContactTransferRequestFlow() {}
@Override
public EppResponse run() throws EppException {
@@ -120,29 +126,31 @@ public final class ContactTransferRequestFlow implements TransactionalFlow {
.setPendingTransferExpirationTime(transferExpirationTime)
.setTransferStatus(TransferStatus.SERVER_APPROVED)
.build();
Key<ContactHistory> contactHistoryKey = createHistoryKey(existingContact, ContactHistory.class);
historyBuilder.setId(contactHistoryKey.getId()).setType(CONTACT_TRANSFER_REQUEST);
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, contactHistoryKey);
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, contactHistoryKey);
targetId, serverApproveTransferData, now, contactHistoryId);
ContactTransferData pendingTransferData =
serverApproveTransferData
.asBuilder()
.setTransferStatus(TransferStatus.PENDING)
.setServerApproveEntities(
serverApproveGainingPollMessage.getContactRepoId(),
contactHistoryKey.getId(),
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, contactHistoryKey)
createLosingTransferPollMessage(targetId, pendingTransferData, contactHistoryId)
.asBuilder()
.setEventTime(now) // Unlike the serverApprove messages, this applies immediately.
.build();
@@ -165,4 +173,3 @@ public final class ContactTransferRequestFlow implements TransactionalFlow {
.build();
}
}

View File

@@ -86,7 +86,6 @@ import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainCommand;
import google.registry.model.domain.DomainCommand.Create;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.DomainHistory.DomainHistoryId;
import google.registry.model.domain.GracePeriod;
import google.registry.model.domain.Period;
import google.registry.model.domain.fee.FeeCreateCommandExtension;
@@ -110,6 +109,7 @@ import google.registry.model.poll.PollMessage.Autorenew;
import google.registry.model.reporting.DomainTransactionRecord;
import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.reporting.HistoryEntry.HistoryEntryId;
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
import google.registry.model.tld.Registry;
import google.registry.model.tld.Registry.TldState;
@@ -327,14 +327,14 @@ public final class DomainCreateFlow implements TransactionalFlow {
FeesAndCredits feesAndCredits =
pricingLogic.getCreatePrice(
registry, targetId, now, years, isAnchorTenant, allocationToken);
validateFeeChallenge(targetId, now, feeCreate, feesAndCredits);
validateFeeChallenge(feeCreate, feesAndCredits);
Optional<SecDnsCreateExtension> secDnsCreate =
validateSecDnsExtension(eppInput.getSingleExtension(SecDnsCreateExtension.class));
DateTime registrationExpirationTime = leapSafeAddYears(now, years);
String repoId = createDomainRepoId(allocateId(), registry.getTldStr());
long historyRevisionId = allocateId();
DomainHistoryId domainHistoryId = new DomainHistoryId(repoId, historyRevisionId);
historyBuilder.setId(historyRevisionId);
HistoryEntryId domainHistoryId = new HistoryEntryId(repoId, historyRevisionId);
historyBuilder.setRevisionId(historyRevisionId);
// Bill for the create.
BillingEvent.OneTime createBillingEvent =
createOneTimeBillingEvent(
@@ -406,7 +406,8 @@ public final class DomainCreateFlow implements TransactionalFlow {
if (allocationToken.isPresent()
&& TokenType.SINGLE_USE.equals(allocationToken.get().getTokenType())) {
entitiesToSave.add(
allocationTokenFlowUtils.redeemToken(allocationToken.get(), domainHistory.createVKey()));
allocationTokenFlowUtils.redeemToken(
allocationToken.get(), domainHistory.getHistoryEntryId()));
}
enqueueTasks(domain, hasSignedMarks, hasClaimsNotice);
@@ -562,7 +563,7 @@ public final class DomainCreateFlow implements TransactionalFlow {
boolean isReserved,
int years,
FeesAndCredits feesAndCredits,
DomainHistoryId domainHistoryId,
HistoryEntryId domainHistoryId,
Optional<AllocationToken> allocationToken,
DateTime now) {
ImmutableSet.Builder<Flag> flagsBuilder = new ImmutableSet.Builder<>();
@@ -596,7 +597,7 @@ public final class DomainCreateFlow implements TransactionalFlow {
}
private Recurring createAutorenewBillingEvent(
DomainHistoryId domainHistoryId,
HistoryEntryId domainHistoryId,
DateTime registrationExpirationTime,
RenewalPriceInfo renewalpriceInfo) {
return new BillingEvent.Recurring.Builder()
@@ -613,7 +614,7 @@ public final class DomainCreateFlow implements TransactionalFlow {
}
private Autorenew createAutorenewPollMessage(
DomainHistoryId domainHistoryId, DateTime registrationExpirationTime) {
HistoryEntryId domainHistoryId, DateTime registrationExpirationTime) {
return new PollMessage.Autorenew.Builder()
.setTargetId(targetId)
.setRegistrarId(registrarId)
@@ -634,7 +635,7 @@ public final class DomainCreateFlow implements TransactionalFlow {
.setEventTime(createBillingEvent.getEventTime())
.setBillingTime(createBillingEvent.getBillingTime())
.setFlags(createBillingEvent.getFlags())
.setDomainHistoryId(createBillingEvent.getDomainHistoryId())
.setDomainHistoryId(createBillingEvent.getHistoryEntryId())
.build();
}
@@ -674,11 +675,11 @@ public final class DomainCreateFlow implements TransactionalFlow {
Optional<AllocationToken> allocationToken,
FeesAndCredits feesAndCredits) {
if (isAnchorTenant) {
if (allocationToken.isPresent()) {
checkArgument(
allocationToken.get().getRenewalPriceBehavior() != RenewalPriceBehavior.SPECIFIED,
"Renewal price behavior cannot be SPECIFIED for anchor tenant");
}
allocationToken.ifPresent(
token ->
checkArgument(
token.getRenewalPriceBehavior() != RenewalPriceBehavior.SPECIFIED,
"Renewal price behavior cannot be SPECIFIED for anchor tenant"));
return RenewalPriceInfo.create(RenewalPriceBehavior.NONPREMIUM, null);
} else if (allocationToken.isPresent()
&& allocationToken.get().getRenewalPriceBehavior() == RenewalPriceBehavior.SPECIFIED) {
@@ -705,9 +706,12 @@ public final class DomainCreateFlow implements TransactionalFlow {
private static ImmutableList<FeeTransformResponseExtension> createResponseExtensions(
Optional<FeeCreateCommandExtension> feeCreate, FeesAndCredits feesAndCredits) {
return feeCreate.isPresent()
? ImmutableList.of(createFeeCreateResponse(feeCreate.get(), feesAndCredits))
: ImmutableList.of();
return feeCreate
.map(
feeCreateCommandExtension ->
ImmutableList.of(
createFeeCreateResponse(feeCreateCommandExtension, feesAndCredits)))
.orElseGet(ImmutableList::of);
}
/** Signed marks are only allowed during sunrise. */

View File

@@ -16,7 +16,7 @@ package google.registry.flows.domain;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Strings.isNullOrEmpty;
import static google.registry.flows.FlowUtils.createHistoryKey;
import static google.registry.flows.FlowUtils.createHistoryEntryId;
import static google.registry.flows.FlowUtils.persistEntityChanges;
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
@@ -43,7 +43,6 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Sets;
import com.googlecode.objectify.Key;
import google.registry.batch.AsyncTaskEnqueuer;
import google.registry.dns.DnsQueue;
import google.registry.flows.EppException;
@@ -66,7 +65,6 @@ import google.registry.model.billing.BillingEvent;
import google.registry.model.billing.BillingEvent.Recurring;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.DomainHistory.DomainHistoryId;
import google.registry.model.domain.GracePeriod;
import google.registry.model.domain.fee.BaseFee.FeeType;
import google.registry.model.domain.fee.Credit;
@@ -88,6 +86,7 @@ import google.registry.model.poll.PendingActionNotificationResponse.DomainPendin
import google.registry.model.poll.PollMessage;
import google.registry.model.reporting.DomainTransactionRecord;
import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField;
import google.registry.model.reporting.HistoryEntry.HistoryEntryId;
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
import google.registry.model.tld.Registry;
import google.registry.model.tld.Registry.TldType;
@@ -181,8 +180,8 @@ public final class DomainDeleteFlow implements TransactionalFlow {
? Duration.ZERO
// By default, this should be 30 days of grace, and 5 days of pending delete.
: redemptionGracePeriodLength.plus(pendingDeleteLength);
Key<DomainHistory> domainHistoryKey = createHistoryKey(existingDomain, DomainHistory.class);
historyBuilder.setId(domainHistoryKey.getId());
HistoryEntryId domainHistoryId = createHistoryEntryId(existingDomain);
historyBuilder.setRevisionId(domainHistoryId.getRevisionId());
DateTime deletionTime = now.plus(durationUntilDelete);
if (durationUntilDelete.equals(Duration.ZERO)) {
builder.setDeletionTime(now).setStatusValues(null);
@@ -214,7 +213,7 @@ public final class DomainDeleteFlow implements TransactionalFlow {
// it is synchronous).
if (durationUntilDelete.isLongerThan(Duration.ZERO) || isSuperuser) {
PollMessage.OneTime deletePollMessage =
createDeletePollMessage(existingDomain, domainHistoryKey, deletionTime);
createDeletePollMessage(existingDomain, domainHistoryId, deletionTime);
entitiesToSave.add(deletePollMessage);
builder.setDeletePollMessage(deletePollMessage.createVKey());
}
@@ -224,7 +223,7 @@ public final class DomainDeleteFlow implements TransactionalFlow {
if (durationUntilDelete.isLongerThan(Duration.ZERO)
&& !registrarId.equals(existingDomain.getPersistedCurrentSponsorRegistrarId())) {
entitiesToSave.add(
createImmediateDeletePollMessage(existingDomain, domainHistoryKey, now, deletionTime));
createImmediateDeletePollMessage(existingDomain, domainHistoryId, now, deletionTime));
}
// Cancel any grace periods that were still active, and set the expiration time accordingly.
@@ -233,14 +232,9 @@ public final class DomainDeleteFlow implements TransactionalFlow {
// No cancellation is written if the grace period was not for a billable event.
if (gracePeriod.hasBillingEvent()) {
entitiesToSave.add(
BillingEvent.Cancellation.forGracePeriod(
gracePeriod,
now,
new DomainHistoryId(
domainHistoryKey.getParent().getName(), domainHistoryKey.getId()),
targetId));
BillingEvent.Cancellation.forGracePeriod(gracePeriod, now, domainHistoryId, targetId));
if (gracePeriod.getOneTimeBillingEvent() != null) {
// Take the amount of amount of registration time being refunded off the expiration time.
// Take the amount of registration time being refunded off the expiration time.
// This can be either add grace periods or renew grace periods.
BillingEvent.OneTime oneTime = tm().loadByKey(gracePeriod.getOneTimeBillingEvent());
newExpirationTime = newExpirationTime.minusYears(oneTime.getPeriodYears());
@@ -262,7 +256,7 @@ public final class DomainDeleteFlow implements TransactionalFlow {
Recurring existingRecurring = tm().loadByKey(existingDomain.getAutorenewBillingEvent());
BillingEvent.Recurring recurringBillingEvent =
updateAutorenewRecurrenceEndTime(
existingDomain, existingRecurring, now, domainHistory.getDomainHistoryId());
existingDomain, existingRecurring, now, domainHistory.getHistoryEntryId());
// If there's a pending transfer, the gaining client's autorenew billing
// event and poll message will already have been deleted in
// ResourceDeleteFlow since it's listed in serverApproveEntities.
@@ -343,7 +337,7 @@ public final class DomainDeleteFlow implements TransactionalFlow {
}
private PollMessage.OneTime createDeletePollMessage(
Domain existingDomain, Key<DomainHistory> domainHistoryKey, DateTime deletionTime) {
Domain existingDomain, HistoryEntryId domainHistoryId, DateTime deletionTime) {
Optional<MetadataExtension> metadataExtension =
eppInput.getSingleExtension(MetadataExtension.class);
boolean hasMetadataMessage =
@@ -362,21 +356,16 @@ public final class DomainDeleteFlow implements TransactionalFlow {
ImmutableList.of(
DomainPendingActionNotificationResponse.create(
existingDomain.getDomainName(), true, trid, deletionTime)))
.setDomainHistoryId(
new DomainHistoryId(domainHistoryKey.getParent().getName(), domainHistoryKey.getId()))
.setDomainHistoryId(domainHistoryId)
.build();
}
private PollMessage.OneTime createImmediateDeletePollMessage(
Domain existingDomain,
Key<DomainHistory> domainHistoryKey,
DateTime now,
DateTime deletionTime) {
Domain existingDomain, HistoryEntryId domainHistoryId, DateTime now, DateTime deletionTime) {
return new PollMessage.OneTime.Builder()
.setRegistrarId(existingDomain.getPersistedCurrentSponsorRegistrarId())
.setEventTime(now)
.setDomainHistoryId(
new DomainHistoryId(domainHistoryKey.getParent().getName(), domainHistoryKey.getId()))
.setDomainHistoryId(domainHistoryId)
.setMsg(
String.format(
"Domain %s was deleted by registry administrator with final deletion effective: %s",

View File

@@ -88,7 +88,6 @@ import google.registry.model.domain.DomainCommand.CreateOrUpdate;
import google.registry.model.domain.DomainCommand.InvalidReferencesException;
import google.registry.model.domain.DomainCommand.Update;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.DomainHistory.DomainHistoryId;
import google.registry.model.domain.ForeignKeyedDesignatedContact;
import google.registry.model.domain.Period;
import google.registry.model.domain.fee.BaseFee;
@@ -121,7 +120,7 @@ import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.Registrar.State;
import google.registry.model.reporting.DomainTransactionRecord;
import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.reporting.HistoryEntry.HistoryEntryId;
import google.registry.model.tld.Registry;
import google.registry.model.tld.Registry.TldState;
import google.registry.model.tld.Registry.TldType;
@@ -429,7 +428,7 @@ public class DomainFlowUtils {
contacts.stream()
.collect(
toImmutableSetMultimap(
DesignatedContact::getType, contact -> contact.getContactKey()));
DesignatedContact::getType, DesignatedContact::getContactKey));
// If any contact type has multiple contacts:
if (contactsByType.asMap().values().stream().anyMatch(v -> v.size() > 1)) {
@@ -590,7 +589,7 @@ public class DomainFlowUtils {
Domain domain,
Recurring existingRecurring,
DateTime newEndTime,
@Nullable DomainHistoryId historyId) {
@Nullable HistoryEntryId historyId) {
Optional<PollMessage.Autorenew> autorenewPollMessage =
tm().loadByKeyIfPresent(domain.getAutorenewPollMessage());
@@ -609,7 +608,7 @@ public class DomainFlowUtils {
historyId, "Cannot create a new autorenew poll message without a domain history id");
updatedAutorenewPollMessage =
newAutorenewPollMessage(domain)
.setId((Long) domain.getAutorenewPollMessage().getSqlKey())
.setId((Long) domain.getAutorenewPollMessage().getKey())
.setAutorenewEndTime(newEndTime)
.setDomainHistoryId(historyId)
.build();
@@ -773,8 +772,6 @@ public class DomainFlowUtils {
* domain names.
*/
public static void validateFeeChallenge(
String domainName,
DateTime priceTime,
final Optional<? extends FeeTransformCommandExtension> feeCommand,
FeesAndCredits feesAndCredits)
throws EppException {
@@ -1135,9 +1132,9 @@ public class DomainFlowUtils {
Duration maxSearchPeriod,
final ImmutableSet<TransactionReportField> cancelableFields) {
List<? extends HistoryEntry> recentHistoryEntries =
List<DomainHistory> recentHistoryEntries =
findRecentHistoryEntries(domain, now, maxSearchPeriod);
Optional<? extends HistoryEntry> entryToCancel =
Optional<DomainHistory> entryToCancel =
Streams.findLast(
recentHistoryEntries.stream()
.filter(
@@ -1174,11 +1171,11 @@ public class DomainFlowUtils {
return recordsBuilder.build();
}
private static List<? extends HistoryEntry> findRecentHistoryEntries(
private static List<DomainHistory> findRecentHistoryEntries(
Domain domain, DateTime now, Duration maxSearchPeriod) {
return jpaTm()
.query(
"FROM DomainHistory WHERE modificationTime >= :beginning AND domainRepoId = "
"FROM DomainHistory WHERE modificationTime >= :beginning AND repoId = "
+ ":repoId ORDER BY modificationTime ASC",
DomainHistory.class)
.setParameter("beginning", now.minus(maxSearchPeriod))

View File

@@ -111,7 +111,7 @@ public final class DomainInfoFlow implements Flow {
DomainInfoData.newBuilder()
.setDomainName(domain.getDomainName())
.setRepoId(domain.getRepoId())
.setCurrentSponsorClientId(domain.getCurrentSponsorRegistrarId())
.setCurrentSponsorRegistrarId(domain.getCurrentSponsorRegistrarId())
.setRegistrant(
tm().transact(() -> tm().loadByKey(domain.getRegistrant())).getContactId());
// If authInfo is non-null, then the caller is authorized to see the full information since we
@@ -125,9 +125,9 @@ public final class DomainInfoFlow implements Flow {
.setNameservers(hostsRequest.requestDelegated() ? domain.loadNameserverHostNames() : null)
.setSubordinateHosts(
hostsRequest.requestSubordinate() ? domain.getSubordinateHosts() : null)
.setCreationClientId(domain.getCreationRegistrarId())
.setCreationRegistrarId(domain.getCreationRegistrarId())
.setCreationTime(domain.getCreationTime())
.setLastEppUpdateClientId(domain.getLastEppUpdateRegistrarId())
.setLastEppUpdateRegistrarId(domain.getLastEppUpdateRegistrarId())
.setLastEppUpdateTime(domain.getLastEppUpdateTime())
.setRegistrationExpirationTime(domain.getRegistrationExpirationTime())
.setLastTransferTime(domain.getLastTransferTime())

View File

@@ -14,7 +14,7 @@
package google.registry.flows.domain;
import static google.registry.flows.FlowUtils.createHistoryKey;
import static google.registry.flows.FlowUtils.createHistoryEntryId;
import static google.registry.flows.FlowUtils.persistEntityChanges;
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
@@ -38,7 +38,6 @@ import static google.registry.util.DateTimeUtils.leapSafeAddYears;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key;
import google.registry.flows.EppException;
import google.registry.flows.EppException.ParameterValueRangeErrorException;
import google.registry.flows.ExtensionManager;
@@ -62,7 +61,6 @@ import google.registry.model.billing.BillingEvent.Recurring;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainCommand.Renew;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.DomainHistory.DomainHistoryId;
import google.registry.model.domain.DomainRenewData;
import google.registry.model.domain.GracePeriod;
import google.registry.model.domain.Period;
@@ -83,6 +81,7 @@ import google.registry.model.eppoutput.EppResponse;
import google.registry.model.poll.PollMessage;
import google.registry.model.reporting.DomainTransactionRecord;
import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField;
import google.registry.model.reporting.HistoryEntry.HistoryEntryId;
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
import google.registry.model.tld.Registry;
import java.util.Optional;
@@ -100,7 +99,7 @@ import org.joda.time.Duration;
*
* <p>ICANN prohibits any registration from being longer than ten years so if the request would
* result in a registration greater than ten years long it will fail. In practice this means it's
* impossible to request a ten year renewal, since that will always cause the new registration to be
* impossible to request a ten-year renewal, since that will always cause the new registration to be
* longer than 10 years unless it comes in at the exact millisecond that the domain would have
* expired.
*
@@ -200,44 +199,36 @@ public final class DomainRenewFlow implements TransactionalFlow {
now,
years,
existingRecurringBillingEvent);
validateFeeChallenge(targetId, now, feeRenew, feesAndCredits);
validateFeeChallenge(feeRenew, feesAndCredits);
flowCustomLogic.afterValidation(
AfterValidationParameters.newBuilder()
.setExistingDomain(existingDomain)
.setNow(now)
.setYears(years)
.build());
Key<DomainHistory> domainHistoryKey = createHistoryKey(existingDomain, DomainHistory.class);
historyBuilder.setId(domainHistoryKey.getId());
HistoryEntryId domainHistoryId = createHistoryEntryId(existingDomain);
historyBuilder.setRevisionId(domainHistoryId.getRevisionId());
String tld = existingDomain.getTld();
// Bill for this explicit renew itself.
BillingEvent.OneTime explicitRenewEvent =
createRenewBillingEvent(
tld, feesAndCredits.getTotalCost(), years, domainHistoryKey, allocationToken, now);
tld, feesAndCredits.getTotalCost(), years, domainHistoryId, allocationToken, now);
// Create a new autorenew billing event and poll message starting at the new expiration time.
BillingEvent.Recurring newAutorenewEvent =
newAutorenewBillingEvent(existingDomain)
.setEventTime(newExpirationTime)
.setRenewalPrice(existingRecurringBillingEvent.getRenewalPrice().orElse(null))
.setRenewalPriceBehavior(existingRecurringBillingEvent.getRenewalPriceBehavior())
.setDomainHistoryId(
new DomainHistoryId(
domainHistoryKey.getParent().getName(), domainHistoryKey.getId()))
.setDomainHistoryId(domainHistoryId)
.build();
PollMessage.Autorenew newAutorenewPollMessage =
newAutorenewPollMessage(existingDomain)
.setEventTime(newExpirationTime)
.setDomainHistoryId(
new DomainHistoryId(
domainHistoryKey.getParent().getName(), domainHistoryKey.getId()))
.setDomainHistoryId(domainHistoryId)
.build();
// End the old autorenew billing event and poll message now. This may delete the poll message.
Recurring existingRecurring = tm().loadByKey(existingDomain.getAutorenewBillingEvent());
updateAutorenewRecurrenceEndTime(
existingDomain,
existingRecurring,
now,
new DomainHistoryId(domainHistoryKey.getParent().getName(), domainHistoryKey.getId()));
updateAutorenewRecurrenceEndTime(existingDomain, existingRecurring, now, domainHistoryId);
Domain newDomain =
existingDomain
.asBuilder()
@@ -260,7 +251,8 @@ public final class DomainRenewFlow implements TransactionalFlow {
if (allocationToken.isPresent()
&& TokenType.SINGLE_USE.equals(allocationToken.get().getTokenType())) {
entitiesToSave.add(
allocationTokenFlowUtils.redeemToken(allocationToken.get(), domainHistory.createVKey()));
allocationTokenFlowUtils.redeemToken(
allocationToken.get(), domainHistory.getHistoryEntryId()));
}
EntityChanges entityChanges =
flowCustomLogic.beforeSave(
@@ -339,7 +331,7 @@ public final class DomainRenewFlow implements TransactionalFlow {
String tld,
Money renewCost,
int years,
Key<DomainHistory> domainHistoryKey,
HistoryEntryId domainHistoryId,
Optional<AllocationToken> allocationToken,
DateTime now) {
return new BillingEvent.OneTime.Builder()
@@ -355,27 +347,27 @@ public final class DomainRenewFlow implements TransactionalFlow {
.map(AllocationToken::createVKey)
.orElse(null))
.setBillingTime(now.plus(Registry.get(tld).getRenewGracePeriodLength()))
.setDomainHistoryId(
new DomainHistoryId(domainHistoryKey.getParent().getName(), domainHistoryKey.getId()))
.setDomainHistoryId(domainHistoryId)
.build();
}
private ImmutableList<FeeTransformResponseExtension> createResponseExtensions(
FeesAndCredits feesAndCredits, Optional<FeeRenewCommandExtension> feeRenew) {
return feeRenew.isPresent()
? ImmutableList.of(
feeRenew
.get()
.createResponseBuilder()
.setCurrency(feesAndCredits.getCurrency())
.setFees(
ImmutableList.of(
Fee.create(
feesAndCredits.getRenewCost().getAmount(),
FeeType.RENEW,
feesAndCredits.hasPremiumFeesOfType(FeeType.RENEW))))
.build())
: ImmutableList.of();
return feeRenew
.map(
feeRenewCommandExtension ->
ImmutableList.of(
feeRenewCommandExtension
.createResponseBuilder()
.setCurrency(feesAndCredits.getCurrency())
.setFees(
ImmutableList.of(
Fee.create(
feesAndCredits.getRenewCost().getAmount(),
FeeType.RENEW,
feesAndCredits.hasPremiumFeesOfType(FeeType.RENEW))))
.build()))
.orElseGet(ImmutableList::of);
}
/** The current expiration date is incorrect. */

View File

@@ -14,7 +14,7 @@
package google.registry.flows.domain;
import static google.registry.flows.FlowUtils.createHistoryKey;
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.verifyOptionalAuthInfo;
@@ -34,7 +34,6 @@ import static google.registry.util.DateTimeUtils.END_OF_TIME;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.net.InternetDomainName;
import com.googlecode.objectify.Key;
import google.registry.dns.DnsQueue;
import google.registry.flows.EppException;
import google.registry.flows.EppException.CommandUseErrorException;
@@ -52,7 +51,6 @@ import google.registry.model.billing.BillingEvent.Reason;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainCommand.Update;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.DomainHistory.DomainHistoryId;
import google.registry.model.domain.fee.BaseFee.FeeType;
import google.registry.model.domain.fee.Fee;
import google.registry.model.domain.fee.FeeTransformResponseExtension;
@@ -67,6 +65,7 @@ import google.registry.model.eppoutput.EppResponse;
import google.registry.model.poll.PollMessage;
import google.registry.model.reporting.DomainTransactionRecord;
import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField;
import google.registry.model.reporting.HistoryEntry.HistoryEntryId;
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
import google.registry.model.tld.Registry;
import java.util.Optional;
@@ -85,12 +84,12 @@ import org.joda.time.DateTime;
*
* <p>This flow is called a restore "request" because technically it is only supposed to signal that
* the registrar requests the restore, which the registry can choose to process or not based on a
* restore report that is submitted through an out of band process and details the request. However,
* in practice this flow does the restore immediately. This is allowable because all of the fields
* on a restore report are optional or have default values, and so by policy when the request comes
* in we consider it to have been accompanied by a default-initialized report which we auto-approve.
* restore report that is submitted through an out-of-band process and details the request. However,
* in practice this flow does the restore immediately. This is allowable because all the fields on a
* restore report are optional or have default values, and so by policy when the request comes in we
* consider it to have been accompanied by a default-initialized report which we auto-approve.
*
* <p>Restores cost a fixed restore fee plus a one year renewal fee for the domain. The domain is
* <p>Restores cost a fixed restore fee plus a one-year renewal fee for the domain. The domain is
* restored to a single year expiration starting at the restore time, regardless of what the
* original expiration time was.
*
@@ -147,10 +146,8 @@ public final class DomainRestoreRequestFlow implements TransactionalFlow {
Optional<FeeUpdateCommandExtension> feeUpdate =
eppInput.getSingleExtension(FeeUpdateCommandExtension.class);
verifyRestoreAllowed(command, existingDomain, feeUpdate, feesAndCredits, now);
Key<DomainHistory> domainHistoryKey = createHistoryKey(existingDomain, DomainHistory.class);
historyBuilder.setId(domainHistoryKey.getId());
DomainHistoryId domainHistoryId =
new DomainHistoryId(domainHistoryKey.getParent().getName(), domainHistoryKey.getId());
HistoryEntryId domainHistoryId = createHistoryEntryId(existingDomain);
historyBuilder.setRevisionId(domainHistoryId.getRevisionId());
ImmutableSet.Builder<ImmutableObject> entitiesToSave = new ImmutableSet.Builder<>();
DateTime newExpirationTime =
@@ -175,9 +172,7 @@ public final class DomainRestoreRequestFlow implements TransactionalFlow {
newAutorenewPollMessage(existingDomain)
.setEventTime(newExpirationTime)
.setAutorenewEndTime(END_OF_TIME)
.setDomainHistoryId(
new DomainHistoryId(
domainHistoryKey.getParent().getName(), domainHistoryKey.getId()))
.setDomainHistoryId(domainHistoryId)
.build();
Domain newDomain =
performRestore(
@@ -231,7 +226,7 @@ public final class DomainRestoreRequestFlow implements TransactionalFlow {
if (!existingDomain.getGracePeriodStatuses().contains(GracePeriodStatus.REDEMPTION)) {
throw new DomainNotEligibleForRestoreException();
}
validateFeeChallenge(targetId, now, feeUpdate, feesAndCredits);
validateFeeChallenge(feeUpdate, feesAndCredits);
}
private static Domain performRestore(
@@ -259,17 +254,17 @@ public final class DomainRestoreRequestFlow implements TransactionalFlow {
}
private OneTime createRenewBillingEvent(
DomainHistoryId domainHistoryId, Money renewCost, DateTime now) {
HistoryEntryId domainHistoryId, Money renewCost, DateTime now) {
return prepareBillingEvent(domainHistoryId, renewCost, now).setReason(Reason.RENEW).build();
}
private BillingEvent.OneTime createRestoreBillingEvent(
DomainHistoryId domainHistoryId, Money restoreCost, DateTime now) {
HistoryEntryId domainHistoryId, Money restoreCost, DateTime now) {
return prepareBillingEvent(domainHistoryId, restoreCost, now).setReason(Reason.RESTORE).build();
}
private OneTime.Builder prepareBillingEvent(
DomainHistoryId domainHistoryId, Money cost, DateTime now) {
HistoryEntryId domainHistoryId, Money cost, DateTime now) {
return new BillingEvent.OneTime.Builder()
.setTargetId(targetId)
.setRegistrarId(registrarId)
@@ -297,15 +292,16 @@ public final class DomainRestoreRequestFlow implements TransactionalFlow {
FeeType.RENEW,
feesAndCredits.hasPremiumFeesOfType(FeeType.RENEW)));
}
return feeUpdate.isPresent()
? ImmutableList.of(
feeUpdate
.get()
.createResponseBuilder()
.setCurrency(feesAndCredits.getCurrency())
.setFees(fees.build())
.build())
: ImmutableList.of();
return feeUpdate
.map(
feeUpdateCommandExtension ->
ImmutableList.of(
feeUpdateCommandExtension
.createResponseBuilder()
.setCurrency(feesAndCredits.getCurrency())
.setFees(fees.build())
.build()))
.orElseGet(ImmutableList::of);
}
/** Restore command cannot have other changes specified. */

View File

@@ -15,7 +15,7 @@
package google.registry.flows.domain;
import static com.google.common.collect.Iterables.getOnlyElement;
import static google.registry.flows.FlowUtils.createHistoryKey;
import static google.registry.flows.FlowUtils.createHistoryEntryId;
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
import static google.registry.flows.ResourceFlowUtils.computeExDateForApprovalTime;
import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
@@ -36,7 +36,6 @@ import static google.registry.util.DateTimeUtils.END_OF_TIME;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key;
import google.registry.flows.EppException;
import google.registry.flows.ExtensionManager;
import google.registry.flows.FlowModule.RegistrarId;
@@ -52,7 +51,6 @@ import google.registry.model.billing.BillingEvent.Reason;
import google.registry.model.billing.BillingEvent.Recurring;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.DomainHistory.DomainHistoryId;
import google.registry.model.domain.GracePeriod;
import google.registry.model.domain.metadata.MetadataExtension;
import google.registry.model.domain.rgp.GracePeriodStatus;
@@ -62,6 +60,7 @@ import google.registry.model.eppinput.EppInput;
import google.registry.model.eppoutput.EppResponse;
import google.registry.model.poll.PollMessage;
import google.registry.model.reporting.DomainTransactionRecord;
import google.registry.model.reporting.HistoryEntry.HistoryEntryId;
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
import google.registry.model.tld.Registry;
import google.registry.model.transfer.DomainTransferData;
@@ -146,8 +145,8 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
// Create a transfer billing event for 1 year, unless the superuser extension was used to set
// the transfer period to zero. There is not a transfer cost if the transfer period is zero.
Recurring existingRecurring = tm().loadByKey(existingDomain.getAutorenewBillingEvent());
Key<DomainHistory> domainHistoryKey = createHistoryKey(existingDomain, DomainHistory.class);
historyBuilder.setId(domainHistoryKey.getId());
HistoryEntryId domainHistoryId = createHistoryEntryId(existingDomain);
historyBuilder.setRevisionId(domainHistoryId.getRevisionId());
Optional<BillingEvent.OneTime> billingEvent =
transferData.getTransferPeriod().getValue() == 0
? Optional.empty()
@@ -167,9 +166,7 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
.getRenewCost())
.setEventTime(now)
.setBillingTime(now.plus(Registry.get(tld).getTransferGracePeriodLength()))
.setDomainHistoryId(
new DomainHistoryId(
domainHistoryKey.getParent().getName(), domainHistoryKey.getId()))
.setDomainHistoryId(domainHistoryId)
.build());
ImmutableList.Builder<ImmutableObject> entitiesToSave = new ImmutableList.Builder<>();
// If we are within an autorenew grace period, cancel the autorenew billing event and don't
@@ -185,20 +182,12 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
if (billingEvent.isPresent()) {
entitiesToSave.add(
BillingEvent.Cancellation.forGracePeriod(
autorenewGrace,
now,
new DomainHistoryId(
domainHistoryKey.getParent().getName(), domainHistoryKey.getId()),
targetId));
autorenewGrace, now, domainHistoryId, targetId));
}
}
// Close the old autorenew event and poll message at the transfer time (aka now). This may end
// up deleting the poll message.
updateAutorenewRecurrenceEndTime(
existingDomain,
existingRecurring,
now,
new DomainHistoryId(domainHistoryKey.getParent().getName(), domainHistoryKey.getId()));
updateAutorenewRecurrenceEndTime(existingDomain, existingRecurring, now, domainHistoryId);
DateTime newExpirationTime =
computeExDateForApprovalTime(existingDomain, now, transferData.getTransferPeriod());
// Create a new autorenew event starting at the expiration time.
@@ -212,9 +201,7 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
.setRenewalPriceBehavior(existingRecurring.getRenewalPriceBehavior())
.setRenewalPrice(existingRecurring.getRenewalPrice().orElse(null))
.setRecurrenceEndTime(END_OF_TIME)
.setDomainHistoryId(
new DomainHistoryId(
domainHistoryKey.getParent().getName(), domainHistoryKey.getId()))
.setDomainHistoryId(domainHistoryId)
.build();
// Create a new autorenew poll message.
PollMessage.Autorenew gainingClientAutorenewPollMessage =
@@ -224,9 +211,7 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
.setEventTime(newExpirationTime)
.setAutorenewEndTime(END_OF_TIME)
.setMsg("Domain was auto-renewed.")
.setDomainHistoryId(
new DomainHistoryId(
domainHistoryKey.getParent().getName(), domainHistoryKey.getId()))
.setDomainHistoryId(domainHistoryId)
.build();
// Construct the post-transfer domain.
Domain partiallyApprovedDomain =
@@ -264,7 +249,7 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
// Create a poll message for the gaining client.
PollMessage gainingClientPollMessage =
createGainingTransferPollMessage(
targetId, newDomain.getTransferData(), newExpirationTime, now, domainHistoryKey);
targetId, newDomain.getTransferData(), newExpirationTime, now, domainHistoryId);
billingEvent.ifPresent(entitiesToSave::add);
entitiesToSave.add(
autorenewEvent,

View File

@@ -14,7 +14,7 @@
package google.registry.flows.domain;
import static google.registry.flows.FlowUtils.createHistoryKey;
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.verifyHasPendingTransfer;
@@ -32,7 +32,6 @@ import static google.registry.persistence.transaction.TransactionManagerFactory.
import static google.registry.util.DateTimeUtils.END_OF_TIME;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key;
import google.registry.flows.EppException;
import google.registry.flows.ExtensionManager;
import google.registry.flows.FlowModule.RegistrarId;
@@ -47,6 +46,7 @@ import google.registry.model.domain.metadata.MetadataExtension;
import google.registry.model.eppcommon.AuthInfo;
import google.registry.model.eppoutput.EppResponse;
import google.registry.model.reporting.DomainTransactionRecord;
import google.registry.model.reporting.HistoryEntry.HistoryEntryId;
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
import google.registry.model.tld.Registry;
import google.registry.model.transfer.TransferStatus;
@@ -83,7 +83,9 @@ public final class DomainTransferCancelFlow implements TransactionalFlow {
@Inject @Superuser boolean isSuperuser;
@Inject DomainHistory.Builder historyBuilder;
@Inject EppResponse.Builder responseBuilder;
@Inject DomainTransferCancelFlow() {}
@Inject
DomainTransferCancelFlow() {}
@Override
public EppResponse run() throws EppException {
@@ -100,9 +102,9 @@ public final class DomainTransferCancelFlow implements TransactionalFlow {
}
Registry registry = Registry.get(existingDomain.getTld());
Key<DomainHistory> domainHistoryKey = createHistoryKey(existingDomain, DomainHistory.class);
HistoryEntryId domainHistoryId = createHistoryEntryId(existingDomain);
historyBuilder
.setId(domainHistoryKey.getId())
.setRevisionId(domainHistoryId.getRevisionId())
.setOtherRegistrarId(existingDomain.getTransferData().getLosingRegistrarId());
Domain newDomain =
@@ -112,12 +114,12 @@ public final class DomainTransferCancelFlow implements TransactionalFlow {
newDomain,
domainHistory,
createLosingTransferPollMessage(
targetId, newDomain.getTransferData(), null, domainHistoryKey));
targetId, newDomain.getTransferData(), null, domainHistoryId));
// Reopen the autorenew event and poll message that we closed for the implicit transfer. This
// may recreate the autorenew poll message if it was deleted when the transfer request was made.
Recurring existingRecurring = tm().loadByKey(existingDomain.getAutorenewBillingEvent());
updateAutorenewRecurrenceEndTime(
existingDomain, existingRecurring, END_OF_TIME, domainHistory.getDomainHistoryId());
existingDomain, existingRecurring, END_OF_TIME, domainHistory.getHistoryEntryId());
// Delete the billing event and poll messages that were written in case the transfer would have
// been implicitly server approved.
tm().delete(existingDomain.getTransferData().getServerApproveEntities());

View File

@@ -14,7 +14,7 @@
package google.registry.flows.domain;
import static google.registry.flows.FlowUtils.createHistoryKey;
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.verifyHasPendingTransfer;
@@ -34,7 +34,6 @@ import static google.registry.util.CollectionUtils.union;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key;
import google.registry.flows.EppException;
import google.registry.flows.ExtensionManager;
import google.registry.flows.FlowModule.RegistrarId;
@@ -49,6 +48,7 @@ import google.registry.model.domain.metadata.MetadataExtension;
import google.registry.model.eppcommon.AuthInfo;
import google.registry.model.eppoutput.EppResponse;
import google.registry.model.reporting.DomainTransactionRecord;
import google.registry.model.reporting.HistoryEntry.HistoryEntryId;
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
import google.registry.model.tld.Registry;
import google.registry.model.transfer.TransferStatus;
@@ -95,9 +95,9 @@ public final class DomainTransferRejectFlow implements TransactionalFlow {
DateTime now = tm().getTransactionTime();
Domain existingDomain = loadAndVerifyExistence(Domain.class, targetId, now);
Registry registry = Registry.get(existingDomain.getTld());
Key<DomainHistory> domainHistoryKey = createHistoryKey(existingDomain, DomainHistory.class);
HistoryEntryId domainHistoryId = createHistoryEntryId(existingDomain);
historyBuilder
.setId(domainHistoryKey.getId())
.setRevisionId(domainHistoryId.getRevisionId())
.setOtherRegistrarId(existingDomain.getTransferData().getGainingRegistrarId());
verifyOptionalAuthInfo(authInfo, existingDomain);
@@ -113,12 +113,12 @@ public final class DomainTransferRejectFlow implements TransactionalFlow {
newDomain,
domainHistory,
createGainingTransferPollMessage(
targetId, newDomain.getTransferData(), null, now, domainHistoryKey));
targetId, newDomain.getTransferData(), null, now, domainHistoryId));
// Reopen the autorenew event and poll message that we closed for the implicit transfer. This
// may end up recreating the poll message if it was deleted upon the transfer request.
Recurring existingRecurring = tm().loadByKey(existingDomain.getAutorenewBillingEvent());
updateAutorenewRecurrenceEndTime(
existingDomain, existingRecurring, END_OF_TIME, domainHistory.getDomainHistoryId());
existingDomain, existingRecurring, END_OF_TIME, domainHistory.getHistoryEntryId());
// Delete the billing event and poll messages that were written in case the transfer would have
// been implicitly server approved.
tm().delete(existingDomain.getTransferData().getServerApproveEntities());

View File

@@ -14,7 +14,7 @@
package google.registry.flows.domain;
import static google.registry.flows.FlowUtils.createHistoryKey;
import static google.registry.flows.FlowUtils.createHistoryEntryId;
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
import static google.registry.flows.ResourceFlowUtils.computeExDateForApprovalTime;
import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
@@ -38,7 +38,6 @@ import static google.registry.persistence.transaction.TransactionManagerFactory.
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key;
import google.registry.batch.AsyncTaskEnqueuer;
import google.registry.flows.EppException;
import google.registry.flows.ExtensionManager;
@@ -57,7 +56,6 @@ import google.registry.model.billing.BillingEvent.Recurring;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainCommand.Transfer;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.DomainHistory.DomainHistoryId;
import google.registry.model.domain.Period;
import google.registry.model.domain.fee.FeeTransferCommandExtension;
import google.registry.model.domain.fee.FeeTransformResponseExtension;
@@ -73,6 +71,7 @@ import google.registry.model.eppoutput.EppResponse;
import google.registry.model.poll.PollMessage;
import google.registry.model.reporting.DomainTransactionRecord;
import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField;
import google.registry.model.reporting.HistoryEntry.HistoryEntryId;
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
import google.registry.model.tld.Registry;
import google.registry.model.transfer.DomainTransferData;
@@ -196,16 +195,16 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
// If the period is zero, then there is no fee for the transfer.
Recurring existingRecurring = tm().loadByKey(existingDomain.getAutorenewBillingEvent());
Optional<FeesAndCredits> feesAndCredits =
(period.getValue() == 0)
period.getValue() == 0
? Optional.empty()
: Optional.of(
pricingLogic.getTransferPrice(registry, targetId, now, existingRecurring));
if (feesAndCredits.isPresent()) {
validateFeeChallenge(targetId, now, feeTransfer, feesAndCredits.get());
validateFeeChallenge(feeTransfer, feesAndCredits.get());
}
Key<DomainHistory> domainHistoryKey = createHistoryKey(existingDomain, DomainHistory.class);
HistoryEntryId domainHistoryId = createHistoryEntryId(existingDomain);
historyBuilder
.setId(domainHistoryKey.getId())
.setRevisionId(domainHistoryId.getRevisionId())
.setOtherRegistrarId(existingDomain.getCurrentSponsorRegistrarId());
DateTime automaticTransferTime =
superuserExtension
@@ -230,7 +229,7 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
createTransferServerApproveEntities(
automaticTransferTime,
serverApproveNewExpirationTime,
domainHistoryKey,
domainHistoryId,
existingDomain,
existingRecurring,
trid,
@@ -241,7 +240,7 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
DomainTransferData pendingTransferData =
createPendingTransferData(
domainAtTransferTime.getRepoId(),
domainHistoryKey.getId(),
domainHistoryId.getRevisionId(),
new DomainTransferData.Builder()
.setTransferRequestTrid(trid)
.setTransferRequestTime(now)
@@ -254,7 +253,7 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
// Create a poll message to notify the losing registrar that a transfer was requested.
PollMessage requestPollMessage =
createLosingTransferPollMessage(
targetId, pendingTransferData, serverApproveNewExpirationTime, domainHistoryKey)
targetId, pendingTransferData, serverApproveNewExpirationTime, domainHistoryId)
.asBuilder()
.setEventTime(now)
.build();
@@ -263,10 +262,7 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
// cloneProjectedAtTime() will replace these old autorenew entities with the server approve ones
// that we've created in this flow and stored in pendingTransferData.
updateAutorenewRecurrenceEndTime(
existingDomain,
existingRecurring,
automaticTransferTime,
new DomainHistoryId(domainHistoryKey.getParent().getName(), domainHistoryKey.getId()));
existingDomain, existingRecurring, automaticTransferTime, domainHistoryId);
Domain newDomain =
existingDomain
.asBuilder()
@@ -385,7 +381,7 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
private static ImmutableList<FeeTransformResponseExtension> createResponseExtensions(
Optional<FeesAndCredits> feesAndCredits, Optional<FeeTransferCommandExtension> feeTransfer) {
return (feeTransfer.isPresent() && feesAndCredits.isPresent())
return feeTransfer.isPresent() && feesAndCredits.isPresent()
? ImmutableList.of(
feeTransfer
.get()

View File

@@ -20,20 +20,18 @@ import static google.registry.util.DateTimeUtils.END_OF_TIME;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key;
import google.registry.model.billing.BillingEvent;
import google.registry.model.billing.BillingEvent.Flag;
import google.registry.model.billing.BillingEvent.Reason;
import google.registry.model.billing.BillingEvent.Recurring;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.DomainHistory.DomainHistoryId;
import google.registry.model.domain.GracePeriod;
import google.registry.model.domain.Period;
import google.registry.model.domain.rgp.GracePeriodStatus;
import google.registry.model.eppcommon.Trid;
import google.registry.model.poll.PendingActionNotificationResponse.DomainPendingActionNotificationResponse;
import google.registry.model.poll.PollMessage;
import google.registry.model.reporting.HistoryEntry.HistoryEntryId;
import google.registry.model.tld.Registry;
import google.registry.model.transfer.DomainTransferData;
import google.registry.model.transfer.TransferData;
@@ -109,7 +107,7 @@ public final class DomainTransferUtils {
public static ImmutableSet<TransferServerApproveEntity> createTransferServerApproveEntities(
DateTime automaticTransferTime,
DateTime serverApproveNewExpirationTime,
Key<DomainHistory> domainHistoryKey,
HistoryEntryId domainHistoryId,
Domain existingDomain,
Recurring existingRecurring,
Trid trid,
@@ -135,38 +133,38 @@ public final class DomainTransferUtils {
builder.add(
createTransferBillingEvent(
automaticTransferTime,
domainHistoryKey,
domainHistoryId,
targetId,
gainingRegistrarId,
registry,
cost)));
createOptionalAutorenewCancellation(
automaticTransferTime, now, domainHistoryKey, targetId, existingDomain, transferCost)
automaticTransferTime, now, domainHistoryId, targetId, existingDomain, transferCost)
.ifPresent(builder::add);
return builder
.add(
createGainingClientAutorenewEvent(
existingRecurring,
serverApproveNewExpirationTime,
domainHistoryKey,
domainHistoryId,
targetId,
gainingRegistrarId))
.add(
createGainingClientAutorenewPollMessage(
serverApproveNewExpirationTime, domainHistoryKey, targetId, gainingRegistrarId))
serverApproveNewExpirationTime, domainHistoryId, targetId, gainingRegistrarId))
.add(
createGainingTransferPollMessage(
targetId,
serverApproveTransferData,
serverApproveNewExpirationTime,
now,
domainHistoryKey))
domainHistoryId))
.add(
createLosingTransferPollMessage(
targetId,
serverApproveTransferData,
serverApproveNewExpirationTime,
domainHistoryKey))
domainHistoryId))
.build();
}
@@ -176,7 +174,7 @@ public final class DomainTransferUtils {
TransferData transferData,
@Nullable DateTime extendedRegistrationExpirationTime,
DateTime now,
Key<DomainHistory> domainHistoryKey) {
HistoryEntryId domainHistoryId) {
return new PollMessage.OneTime.Builder()
.setRegistrarId(transferData.getGainingRegistrarId())
.setEventTime(transferData.getPendingTransferExpirationTime())
@@ -189,8 +187,7 @@ public final class DomainTransferUtils {
transferData.getTransferStatus().isApproved(),
transferData.getTransferRequestTrid(),
now)))
.setDomainHistoryId(
new DomainHistoryId(domainHistoryKey.getParent().getName(), domainHistoryKey.getId()))
.setDomainHistoryId(domainHistoryId)
.build();
}
@@ -199,7 +196,7 @@ public final class DomainTransferUtils {
String targetId,
TransferData transferData,
@Nullable DateTime extendedRegistrationExpirationTime,
Key<DomainHistory> domainHistoryKey) {
HistoryEntryId domainHistoryId) {
return new PollMessage.OneTime.Builder()
.setRegistrarId(transferData.getLosingRegistrarId())
.setEventTime(transferData.getPendingTransferExpirationTime())
@@ -207,8 +204,7 @@ public final class DomainTransferUtils {
.setResponseData(
ImmutableList.of(
createTransferResponse(targetId, transferData, extendedRegistrationExpirationTime)))
.setDomainHistoryId(
new DomainHistoryId(domainHistoryKey.getParent().getName(), domainHistoryKey.getId()))
.setDomainHistoryId(domainHistoryId)
.build();
}
@@ -230,7 +226,7 @@ public final class DomainTransferUtils {
private static PollMessage.Autorenew createGainingClientAutorenewPollMessage(
DateTime serverApproveNewExpirationTime,
Key<DomainHistory> domainHistoryKey,
HistoryEntryId domainHistoryId,
String targetId,
String gainingRegistrarId) {
return new PollMessage.Autorenew.Builder()
@@ -239,15 +235,14 @@ public final class DomainTransferUtils {
.setEventTime(serverApproveNewExpirationTime)
.setAutorenewEndTime(END_OF_TIME)
.setMsg("Domain was auto-renewed.")
.setDomainHistoryId(
new DomainHistoryId(domainHistoryKey.getParent().getName(), domainHistoryKey.getId()))
.setDomainHistoryId(domainHistoryId)
.build();
}
private static BillingEvent.Recurring createGainingClientAutorenewEvent(
Recurring existingRecurring,
DateTime serverApproveNewExpirationTime,
Key<DomainHistory> domainHistoryKey,
HistoryEntryId domainHistoryId,
String targetId,
String gainingRegistrarId) {
return new BillingEvent.Recurring.Builder()
@@ -259,8 +254,7 @@ public final class DomainTransferUtils {
.setRecurrenceEndTime(END_OF_TIME)
.setRenewalPriceBehavior(existingRecurring.getRenewalPriceBehavior())
.setRenewalPrice(existingRecurring.getRenewalPrice().orElse(null))
.setDomainHistoryId(
new DomainHistoryId(domainHistoryKey.getParent().getName(), domainHistoryKey.getId()))
.setDomainHistoryId(domainHistoryId)
.build();
}
@@ -283,7 +277,7 @@ public final class DomainTransferUtils {
private static Optional<BillingEvent.Cancellation> createOptionalAutorenewCancellation(
DateTime automaticTransferTime,
DateTime now,
Key<DomainHistory> domainHistoryKey,
HistoryEntryId domainHistoryId,
String targetId,
Domain existingDomain,
Optional<Money> transferCost) {
@@ -294,11 +288,7 @@ public final class DomainTransferUtils {
if (autorenewGracePeriod != null && transferCost.isPresent()) {
return Optional.of(
BillingEvent.Cancellation.forGracePeriod(
autorenewGracePeriod,
now,
new DomainHistoryId(
domainHistoryKey.getParent().getName(), domainHistoryKey.getId()),
targetId)
autorenewGracePeriod, now, domainHistoryId, targetId)
.asBuilder()
.setEventTime(automaticTransferTime)
.build());
@@ -308,7 +298,7 @@ public final class DomainTransferUtils {
private static BillingEvent.OneTime createTransferBillingEvent(
DateTime automaticTransferTime,
Key<DomainHistory> domainHistoryKey,
HistoryEntryId domainHistoryId,
String targetId,
String gainingRegistrarId,
Registry registry,
@@ -321,8 +311,7 @@ public final class DomainTransferUtils {
.setPeriodYears(1)
.setEventTime(automaticTransferTime)
.setBillingTime(automaticTransferTime.plus(registry.getTransferGracePeriodLength()))
.setDomainHistoryId(
new DomainHistoryId(domainHistoryKey.getParent().getName(), domainHistoryKey.getId()))
.setDomainHistoryId(domainHistoryId)
.build();
}

View File

@@ -208,12 +208,9 @@ public final class DomainUpdateFlow implements TransactionalFlow {
/** Determines if any of the changes to new domain should trigger DNS update. */
private boolean requiresDnsUpdate(Domain existingDomain, Domain newDomain) {
if (existingDomain.shouldPublishToDns() != newDomain.shouldPublishToDns()
return existingDomain.shouldPublishToDns() != newDomain.shouldPublishToDns()
|| !Objects.equals(newDomain.getDsData(), existingDomain.getDsData())
|| !Objects.equals(newDomain.getNsHosts(), existingDomain.getNsHosts())) {
return true;
}
return false;
|| !Objects.equals(newDomain.getNsHosts(), existingDomain.getNsHosts());
}
/** Fail if the object doesn't exist or was deleted. */
@@ -363,7 +360,7 @@ public final class DomainUpdateFlow implements TransactionalFlow {
.map(StatusValue::getXmlName)
.collect(toImmutableSortedSet(Ordering.natural()));
String msg = "";
String msg;
if (addedServerStatuses.size() > 0 && removedServerStatuses.size() > 0) {
msg =
String.format(

View File

@@ -36,7 +36,7 @@ import google.registry.model.domain.token.AllocationToken.TokenBehavior;
import google.registry.model.domain.token.AllocationToken.TokenStatus;
import google.registry.model.domain.token.AllocationToken.TokenType;
import google.registry.model.domain.token.AllocationTokenExtension;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.reporting.HistoryEntry.HistoryEntryId;
import google.registry.model.tld.Registry;
import google.registry.persistence.VKey;
import java.util.List;
@@ -95,12 +95,11 @@ public class AllocationTokenFlowUtils {
}
/** Redeems a SINGLE_USE {@link AllocationToken}, returning the redeemed copy. */
public AllocationToken redeemToken(
AllocationToken token, VKey<? extends HistoryEntry> redemptionHistoryEntry) {
public AllocationToken redeemToken(AllocationToken token, HistoryEntryId redemptionHistoryId) {
checkArgument(
TokenType.SINGLE_USE.equals(token.getTokenType()),
"Only SINGLE_USE tokens can be marked as redeemed");
return token.asBuilder().setRedemptionHistoryEntry(redemptionHistoryEntry).build();
return token.asBuilder().setRedemptionHistoryId(redemptionHistoryId).build();
}
/**
@@ -110,7 +109,7 @@ public class AllocationTokenFlowUtils {
*
* @throws EppException if the token is invalid in any way
*/
private void validateToken(
private static void validateToken(
InternetDomainName domainName, AllocationToken token, String registrarId, DateTime now)
throws EppException {
@@ -138,7 +137,7 @@ public class AllocationTokenFlowUtils {
}
/** Loads a given token and validates that it is not redeemed */
private AllocationToken loadToken(String token) throws EppException {
private static AllocationToken loadToken(String token) throws EppException {
if (Strings.isNullOrEmpty(token)) {
// We load the token directly from the input XML. If it's null or empty we should throw
// an InvalidAllocationTokenException before the database load attempt fails.
@@ -152,7 +151,7 @@ public class AllocationTokenFlowUtils {
}
maybeTokenEntity =
tm().transact(() -> tm().loadByKeyIfPresent(VKey.createSql(AllocationToken.class, token)));
tm().transact(() -> tm().loadByKeyIfPresent(VKey.create(AllocationToken.class, token)));
if (!maybeTokenEntity.isPresent()) {
throw new InvalidAllocationTokenException();

View File

@@ -80,14 +80,14 @@ public final class HostInfoFlow implements Flow {
tm().transact(
() -> tm().loadByKey(host.getSuperordinateDomain()).cloneProjectedAtTime(now));
hostInfoDataBuilder
.setCurrentSponsorClientId(superordinateDomain.getCurrentSponsorRegistrarId())
.setCurrentSponsorRegistrarId(superordinateDomain.getCurrentSponsorRegistrarId())
.setLastTransferTime(host.computeLastTransferTime(superordinateDomain));
if (superordinateDomain.getStatusValues().contains(StatusValue.PENDING_TRANSFER)) {
statusValues.add(StatusValue.PENDING_TRANSFER);
}
} else {
hostInfoDataBuilder
.setCurrentSponsorClientId(host.getPersistedCurrentSponsorRegistrarId())
.setCurrentSponsorRegistrarId(host.getPersistedCurrentSponsorRegistrarId())
.setLastTransferTime(host.getLastTransferTime());
}
return responseBuilder
@@ -97,9 +97,9 @@ public final class HostInfoFlow implements Flow {
.setRepoId(host.getRepoId())
.setStatusValues(statusValues.build())
.setInetAddresses(host.getInetAddresses())
.setCreationClientId(host.getCreationRegistrarId())
.setCreationRegistrarId(host.getCreationRegistrarId())
.setCreationTime(host.getCreationTime())
.setLastEppUpdateClientId(host.getLastEppUpdateRegistrarId())
.setLastEppUpdateRegistrarId(host.getLastEppUpdateRegistrarId())
.setLastEppUpdateTime(host.getLastEppUpdateTime())
.build())
.build();

View File

@@ -268,7 +268,7 @@ public final class HostUpdateFlow implements TransactionalFlow {
}
// We must also enqueue updates for all domains that use this host as their nameserver so
// that their NS records can be updated to point at the new name.
asyncTaskEnqueuer.enqueueAsyncDnsRefresh(existingHost, tm().getTransactionTime());
// TODO(jianglai): implement a SQL based solution.
}
}

View File

@@ -1,41 +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.keyring.kms;
import com.google.auto.value.AutoValue;
import com.google.common.annotations.VisibleForTesting;
/**
* A value type class containing a Cloud KMS encrypted and encoded ciphertext, and the name of the
* CryptoKeyVersion used to encrypt it.
*/
@AutoValue
abstract class EncryptResponse {
static EncryptResponse create(
com.google.api.services.cloudkms.v1.model.EncryptResponse cloudKmsEncryptResponse) {
return new AutoValue_EncryptResponse(
cloudKmsEncryptResponse.getCiphertext(), cloudKmsEncryptResponse.getName());
}
@VisibleForTesting
static EncryptResponse create(String ciphertext, String cryptoKeyVersionName) {
return new AutoValue_EncryptResponse(ciphertext, cryptoKeyVersionName);
}
abstract String ciphertext();
abstract String cryptoKeyVersionName();
}

View File

@@ -1,48 +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.keyring.kms;
import google.registry.keyring.api.KeyringException;
/** An abstraction to simplify Cloud KMS operations. */
public interface KmsConnection {
/**
* The maximum allowable secret size, as set by Cloud KMS.
*
* @see <a
* href="https://cloud.google.com/kms/docs/reference/rest/v1/projects.locations.keyRings.cryptoKeys/encrypt#request-body">projects.locations.keyRings.cryptoKeys.encrypt</a>
*/
int MAX_SECRET_SIZE_BYTES = 64 * 1024;
/**
* Encrypts a plaintext with CryptoKey {@code cryptoKeyName} on KeyRing {@code keyRingName}.
*
* <p>The latest CryptoKeyVersion is used to encrypt the value. The value must not be larger than
* {@code MAX_SECRET_SIZE_BYTES}.
*
* <p>If no applicable CryptoKey or CryptoKeyVersion exist, they will be created.
*
* @throws KeyringException on encryption failure.
*/
EncryptResponse encrypt(String cryptoKeyName, byte[] plaintext);
/**
* Decrypts a Cloud KMS encrypted and encoded value with CryptoKey {@code cryptoKeyName}.
*
* @throws KeyringException on decryption failure.
*/
byte[] decrypt(String cryptoKeyName, String encodedCiphertext);
}

View File

@@ -1,177 +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.keyring.kms;
import static com.google.common.base.Preconditions.checkArgument;
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
import com.google.api.client.http.HttpStatusCodes;
import com.google.api.services.cloudkms.v1.CloudKMS;
import com.google.api.services.cloudkms.v1.model.CryptoKey;
import com.google.api.services.cloudkms.v1.model.CryptoKeyVersion;
import com.google.api.services.cloudkms.v1.model.DecryptRequest;
import com.google.api.services.cloudkms.v1.model.EncryptRequest;
import com.google.api.services.cloudkms.v1.model.KeyRing;
import com.google.api.services.cloudkms.v1.model.UpdateCryptoKeyPrimaryVersionRequest;
import google.registry.keyring.api.KeyringException;
import google.registry.util.Retrier;
import java.io.IOException;
/** The {@link KmsConnection} which talks to Cloud KMS. */
class KmsConnectionImpl implements KmsConnection {
private static final String KMS_LOCATION_FORMAT = "projects/%s/locations/global";
private static final String KMS_KEYRING_NAME_FORMAT = "projects/%s/locations/global/keyRings/%s";
private static final String KMS_CRYPTO_KEY_NAME_FORMAT =
"projects/%s/locations/global/keyRings/%s/cryptoKeys/%s";
private final CloudKMS kms;
private final String kmsKeyRingName;
private final String projectId;
private final Retrier retrier;
KmsConnectionImpl(String projectId, String kmsKeyRingName, Retrier retrier, CloudKMS kms) {
this.projectId = projectId;
this.kmsKeyRingName = kmsKeyRingName;
this.retrier = retrier;
this.kms = kms;
}
@Override
public EncryptResponse encrypt(String cryptoKeyName, byte[] value) {
checkArgument(
value.length <= MAX_SECRET_SIZE_BYTES,
"Value to encrypt was larger than %s bytes",
MAX_SECRET_SIZE_BYTES);
try {
return attemptEncrypt(cryptoKeyName, value);
} catch (IOException e) {
throw new KeyringException(
String.format("CloudKMS encrypt operation failed for secret %s", cryptoKeyName), e);
}
}
private EncryptResponse attemptEncrypt(String cryptoKeyName, byte[] value) throws IOException {
String fullKeyRingName = getKeyRingName(projectId, kmsKeyRingName);
try {
kms.projects().locations().keyRings().get(fullKeyRingName).execute();
} catch (GoogleJsonResponseException jsonException) {
if (jsonException.getStatusCode() == HttpStatusCodes.STATUS_CODE_NOT_FOUND) {
// Create the KeyRing in the "global" namespace. Encryption keys will be accessible from all
// GCP regions.
kms.projects()
.locations()
.keyRings()
.create(getLocationName(projectId), new KeyRing())
.setKeyRingId(kmsKeyRingName)
.execute();
} else {
throw jsonException;
}
}
String fullKeyName = getCryptoKeyName(projectId, kmsKeyRingName, cryptoKeyName);
boolean newCryptoKey = false;
try {
kms.projects().locations().keyRings().cryptoKeys().get(fullKeyName).execute();
} catch (GoogleJsonResponseException jsonException) {
if (jsonException.getStatusCode() == HttpStatusCodes.STATUS_CODE_NOT_FOUND) {
newCryptoKey = true;
kms.projects()
.locations()
.keyRings()
.cryptoKeys()
.create(fullKeyRingName, new CryptoKey().setPurpose("ENCRYPT_DECRYPT"))
.setCryptoKeyId(cryptoKeyName)
.execute();
} else {
throw jsonException;
}
}
// New CryptoKeys start with a CryptoKeyVersion, so we only create a new CryptoKeyVersion and
// rotate to it if we're dealing with an existing CryptoKey.
if (!newCryptoKey) {
CryptoKeyVersion cryptoKeyVersion =
kms.projects()
.locations()
.keyRings()
.cryptoKeys()
.cryptoKeyVersions()
.create(fullKeyName, new CryptoKeyVersion())
.execute();
kms.projects()
.locations()
.keyRings()
.cryptoKeys()
.updatePrimaryVersion(
fullKeyName,
new UpdateCryptoKeyPrimaryVersionRequest()
.setCryptoKeyVersionId(getCryptoKeyVersionId(cryptoKeyVersion)))
.execute();
}
return EncryptResponse.create(
kms.projects()
.locations()
.keyRings()
.cryptoKeys()
.encrypt(fullKeyName, new EncryptRequest().encodePlaintext(value))
.execute());
}
@Override
public byte[] decrypt(final String cryptoKeyName, final String encodedCiphertext) {
try {
return retrier.callWithRetry(
() -> attemptDecrypt(cryptoKeyName, encodedCiphertext), IOException.class);
} catch (RuntimeException e) {
throw new KeyringException(
String.format("CloudKMS decrypt operation failed for secret %s", cryptoKeyName), e);
}
}
private byte[] attemptDecrypt(String cryptoKeyName, String encodedCiphertext) throws IOException {
return kms.projects()
.locations()
.keyRings()
.cryptoKeys()
.decrypt(
getCryptoKeyName(projectId, kmsKeyRingName, cryptoKeyName),
new DecryptRequest().setCiphertext(encodedCiphertext))
.execute()
.decodePlaintext();
}
private static String getLocationName(String projectId) {
return String.format(KMS_LOCATION_FORMAT, projectId);
}
private static String getKeyRingName(String projectId, String kmsKeyRingName) {
return String.format(KMS_KEYRING_NAME_FORMAT, projectId, kmsKeyRingName);
}
private static String getCryptoKeyName(
String projectId, String kmsKeyRingName, String cryptoKeyName) {
return String.format(KMS_CRYPTO_KEY_NAME_FORMAT, projectId, kmsKeyRingName, cryptoKeyName);
}
private static String getCryptoKeyVersionId(CryptoKeyVersion cryptoKeyVersion) {
String cryptoKeyVersionName = cryptoKeyVersion.getName();
return cryptoKeyVersionName.substring(cryptoKeyVersionName.lastIndexOf('/') + 1);
}
}

View File

@@ -1,84 +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.keyring.kms;
import com.google.api.services.cloudkms.v1.CloudKMS;
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoMap;
import dagger.multibindings.StringKey;
import google.registry.config.CredentialModule.DefaultCredential;
import google.registry.config.RegistryConfig.Config;
import google.registry.keyring.api.Keyring;
import google.registry.util.GoogleCredentialsBundle;
import google.registry.util.Retrier;
/** Dagger module for Cloud KMS. */
@Module
public abstract class KmsModule {
public static final String NAME = "KMS";
@Provides
@Config("defaultKms")
static CloudKMS provideKms(
@DefaultCredential GoogleCredentialsBundle credentialsBundle,
@Config("cloudKmsProjectId") String projectId) {
return createKms(credentialsBundle, projectId);
}
@Provides
@Config("beamKms")
static CloudKMS provideBeamKms(
@DefaultCredential GoogleCredentialsBundle credentialsBundle,
@Config("beamCloudKmsProjectId") String projectId) {
return createKms(credentialsBundle, projectId);
}
private static CloudKMS createKms(GoogleCredentialsBundle credentialsBundle, String projectId) {
return new CloudKMS.Builder(
credentialsBundle.getHttpTransport(),
credentialsBundle.getJsonFactory(),
credentialsBundle.getHttpRequestInitializer())
.setApplicationName(projectId)
.build();
}
@Provides
@Config("defaultKmsConnection")
static KmsConnection provideKmsConnection(
@Config("cloudKmsProjectId") String projectId,
@Config("cloudKmsKeyRing") String keyringName,
Retrier retrier,
@Config("defaultKms") CloudKMS defaultKms) {
return new KmsConnectionImpl(projectId, keyringName, retrier, defaultKms);
}
@Provides
@Config("beamKmsConnection")
static KmsConnection provideBeamKmsConnection(
@Config("beamCloudKmsProjectId") String projectId,
@Config("beamCloudKmsKeyRing") String keyringName,
Retrier retrier,
@Config("beamKms") CloudKMS defaultKms) {
return new KmsConnectionImpl(projectId, keyringName, retrier, defaultKms);
}
@Binds
@IntoMap
@StringKey(NAME)
abstract Keyring provideKeyring(KmsKeyring keyring);
}

View File

@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.keyring.kms;
package google.registry.keyring.secretmanager;
import static com.google.common.base.CaseFormat.LOWER_HYPHEN;
import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE;
@@ -29,8 +29,7 @@ import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
/** A {@link Keyring} implementation which stores sensitive data in the Secret Manager. */
// TODO(2021-08-01): rename this class to SecretManagerKeyring and update config files.
public class KmsKeyring implements Keyring {
public class SecretManagerKeyring implements Keyring {
/** Key labels for private key secrets. */
enum PrivateKeyLabel {
@@ -75,7 +74,7 @@ public class KmsKeyring implements Keyring {
private final KeyringSecretStore secretStore;
@Inject
KmsKeyring(KeyringSecretStore secretStore) {
SecretManagerKeyring(KeyringSecretStore secretStore) {
this.secretStore = secretStore;
}

View File

@@ -0,0 +1,40 @@
// 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.keyring.secretmanager;
import dagger.Binds;
import dagger.Module;
import dagger.multibindings.IntoMap;
import dagger.multibindings.StringKey;
import google.registry.keyring.api.Keyring;
/** Dagger module for {@link Keyring} backed by the Cloud SecretManager. */
@Module
public abstract class SecretManagerKeyringModule {
public static final String NAME = "CSM";
// TODO(b/257276342): Remove after configs in nomulus-internal are updated.
public static final String DEPRECATED_NAME = "KMS";
@Binds
@IntoMap
@StringKey(DEPRECATED_NAME)
abstract Keyring provideDeprecatedKeyring(SecretManagerKeyring keyring);
@Binds
@IntoMap
@StringKey(NAME)
abstract Keyring provideKeyring(SecretManagerKeyring keyring);
}

View File

@@ -12,33 +12,33 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.keyring.kms;
package google.registry.keyring.secretmanager;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static google.registry.keyring.kms.KmsKeyring.PrivateKeyLabel.BRDA_SIGNING_PRIVATE;
import static google.registry.keyring.kms.KmsKeyring.PrivateKeyLabel.RDE_SIGNING_PRIVATE;
import static google.registry.keyring.kms.KmsKeyring.PrivateKeyLabel.RDE_STAGING_PRIVATE;
import static google.registry.keyring.kms.KmsKeyring.PublicKeyLabel.BRDA_RECEIVER_PUBLIC;
import static google.registry.keyring.kms.KmsKeyring.PublicKeyLabel.BRDA_SIGNING_PUBLIC;
import static google.registry.keyring.kms.KmsKeyring.PublicKeyLabel.RDE_RECEIVER_PUBLIC;
import static google.registry.keyring.kms.KmsKeyring.PublicKeyLabel.RDE_SIGNING_PUBLIC;
import static google.registry.keyring.kms.KmsKeyring.PublicKeyLabel.RDE_STAGING_PUBLIC;
import static google.registry.keyring.kms.KmsKeyring.StringKeyLabel.ICANN_REPORTING_PASSWORD_STRING;
import static google.registry.keyring.kms.KmsKeyring.StringKeyLabel.JSON_CREDENTIAL_STRING;
import static google.registry.keyring.kms.KmsKeyring.StringKeyLabel.MARKSDB_DNL_LOGIN_STRING;
import static google.registry.keyring.kms.KmsKeyring.StringKeyLabel.MARKSDB_LORDN_PASSWORD_STRING;
import static google.registry.keyring.kms.KmsKeyring.StringKeyLabel.MARKSDB_SMDRL_LOGIN_STRING;
import static google.registry.keyring.kms.KmsKeyring.StringKeyLabel.RDE_SSH_CLIENT_PRIVATE_STRING;
import static google.registry.keyring.kms.KmsKeyring.StringKeyLabel.RDE_SSH_CLIENT_PUBLIC_STRING;
import static google.registry.keyring.kms.KmsKeyring.StringKeyLabel.SAFE_BROWSING_API_KEY;
import static google.registry.keyring.secretmanager.SecretManagerKeyring.PrivateKeyLabel.BRDA_SIGNING_PRIVATE;
import static google.registry.keyring.secretmanager.SecretManagerKeyring.PrivateKeyLabel.RDE_SIGNING_PRIVATE;
import static google.registry.keyring.secretmanager.SecretManagerKeyring.PrivateKeyLabel.RDE_STAGING_PRIVATE;
import static google.registry.keyring.secretmanager.SecretManagerKeyring.PublicKeyLabel.BRDA_RECEIVER_PUBLIC;
import static google.registry.keyring.secretmanager.SecretManagerKeyring.PublicKeyLabel.BRDA_SIGNING_PUBLIC;
import static google.registry.keyring.secretmanager.SecretManagerKeyring.PublicKeyLabel.RDE_RECEIVER_PUBLIC;
import static google.registry.keyring.secretmanager.SecretManagerKeyring.PublicKeyLabel.RDE_SIGNING_PUBLIC;
import static google.registry.keyring.secretmanager.SecretManagerKeyring.PublicKeyLabel.RDE_STAGING_PUBLIC;
import static google.registry.keyring.secretmanager.SecretManagerKeyring.StringKeyLabel.ICANN_REPORTING_PASSWORD_STRING;
import static google.registry.keyring.secretmanager.SecretManagerKeyring.StringKeyLabel.JSON_CREDENTIAL_STRING;
import static google.registry.keyring.secretmanager.SecretManagerKeyring.StringKeyLabel.MARKSDB_DNL_LOGIN_STRING;
import static google.registry.keyring.secretmanager.SecretManagerKeyring.StringKeyLabel.MARKSDB_LORDN_PASSWORD_STRING;
import static google.registry.keyring.secretmanager.SecretManagerKeyring.StringKeyLabel.MARKSDB_SMDRL_LOGIN_STRING;
import static google.registry.keyring.secretmanager.SecretManagerKeyring.StringKeyLabel.RDE_SSH_CLIENT_PRIVATE_STRING;
import static google.registry.keyring.secretmanager.SecretManagerKeyring.StringKeyLabel.RDE_SSH_CLIENT_PUBLIC_STRING;
import static google.registry.keyring.secretmanager.SecretManagerKeyring.StringKeyLabel.SAFE_BROWSING_API_KEY;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import com.google.common.flogger.FluentLogger;
import google.registry.keyring.api.KeySerializer;
import google.registry.keyring.kms.KmsKeyring.PrivateKeyLabel;
import google.registry.keyring.kms.KmsKeyring.PublicKeyLabel;
import google.registry.keyring.kms.KmsKeyring.StringKeyLabel;
import google.registry.keyring.secretmanager.SecretManagerKeyring.PrivateKeyLabel;
import google.registry.keyring.secretmanager.SecretManagerKeyring.PublicKeyLabel;
import google.registry.keyring.secretmanager.SecretManagerKeyring.StringKeyLabel;
import google.registry.privileges.secretmanager.KeyringSecretStore;
import java.io.IOException;
import java.util.HashMap;
@@ -50,73 +50,77 @@ import org.bouncycastle.openpgp.PGPKeyPair;
import org.bouncycastle.openpgp.PGPPublicKey;
/**
* The {@link KmsUpdater} accumulates updates to a {@link KmsKeyring} and persists them to KMS and
* Datastore when closed.
* The {@link SecretManagerKeyringUpdater} accumulates updates to a {@link SecretManagerKeyring} and
* persists them to KMS and Datastore when closed.
*/
// TODO(2021-06-01): rename this class to SecretManagerKeyringUpdater
public final class KmsUpdater {
public final class SecretManagerKeyringUpdater {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final KeyringSecretStore secretStore;
private final HashMap<String, byte[]> secretValues;
@Inject
public KmsUpdater(KeyringSecretStore secretStore) {
public SecretManagerKeyringUpdater(KeyringSecretStore secretStore) {
this.secretStore = secretStore;
// Use LinkedHashMap to preserve insertion order on update() to simplify testing and debugging
this.secretValues = new LinkedHashMap<>();
}
public KmsUpdater setRdeSigningKey(PGPKeyPair keyPair) throws IOException, PGPException {
public SecretManagerKeyringUpdater setRdeSigningKey(PGPKeyPair keyPair)
throws IOException, PGPException {
return setKeyPair(keyPair, RDE_SIGNING_PRIVATE, RDE_SIGNING_PUBLIC);
}
public KmsUpdater setRdeStagingKey(PGPKeyPair keyPair) throws IOException, PGPException {
public SecretManagerKeyringUpdater setRdeStagingKey(PGPKeyPair keyPair)
throws IOException, PGPException {
return setKeyPair(keyPair, RDE_STAGING_PRIVATE, RDE_STAGING_PUBLIC);
}
public KmsUpdater setRdeReceiverPublicKey(PGPPublicKey publicKey) throws IOException {
public SecretManagerKeyringUpdater setRdeReceiverPublicKey(PGPPublicKey publicKey)
throws IOException {
return setPublicKey(publicKey, RDE_RECEIVER_PUBLIC);
}
public KmsUpdater setBrdaSigningKey(PGPKeyPair keyPair) throws IOException, PGPException {
public SecretManagerKeyringUpdater setBrdaSigningKey(PGPKeyPair keyPair)
throws IOException, PGPException {
return setKeyPair(keyPair, BRDA_SIGNING_PRIVATE, BRDA_SIGNING_PUBLIC);
}
public KmsUpdater setBrdaReceiverPublicKey(PGPPublicKey publicKey) throws IOException {
public SecretManagerKeyringUpdater setBrdaReceiverPublicKey(PGPPublicKey publicKey)
throws IOException {
return setPublicKey(publicKey, BRDA_RECEIVER_PUBLIC);
}
public KmsUpdater setRdeSshClientPublicKey(String asciiPublicKey) {
public SecretManagerKeyringUpdater setRdeSshClientPublicKey(String asciiPublicKey) {
return setString(asciiPublicKey, RDE_SSH_CLIENT_PUBLIC_STRING);
}
public KmsUpdater setRdeSshClientPrivateKey(String asciiPrivateKey) {
public SecretManagerKeyringUpdater setRdeSshClientPrivateKey(String asciiPrivateKey) {
return setString(asciiPrivateKey, RDE_SSH_CLIENT_PRIVATE_STRING);
}
public KmsUpdater setSafeBrowsingAPIKey(String apiKey) {
public SecretManagerKeyringUpdater setSafeBrowsingAPIKey(String apiKey) {
return setString(apiKey, SAFE_BROWSING_API_KEY);
}
public KmsUpdater setIcannReportingPassword(String password) {
public SecretManagerKeyringUpdater setIcannReportingPassword(String password) {
return setString(password, ICANN_REPORTING_PASSWORD_STRING);
}
public KmsUpdater setMarksdbDnlLoginAndPassword(String login) {
public SecretManagerKeyringUpdater setMarksdbDnlLoginAndPassword(String login) {
return setString(login, MARKSDB_DNL_LOGIN_STRING);
}
public KmsUpdater setMarksdbLordnPassword(String password) {
public SecretManagerKeyringUpdater setMarksdbLordnPassword(String password) {
return setString(password, MARKSDB_LORDN_PASSWORD_STRING);
}
public KmsUpdater setMarksdbSmdrlLoginAndPassword(String login) {
public SecretManagerKeyringUpdater setMarksdbSmdrlLoginAndPassword(String login) {
return setString(login, MARKSDB_SMDRL_LOGIN_STRING);
}
public KmsUpdater setJsonCredential(String credential) {
public SecretManagerKeyringUpdater setJsonCredential(String credential) {
return setString(credential, JSON_CREDENTIAL_STRING);
}
@@ -144,22 +148,22 @@ public final class KmsUpdater {
}
}
private KmsUpdater setString(String key, StringKeyLabel stringKeyLabel) {
private SecretManagerKeyringUpdater setString(String key, StringKeyLabel stringKeyLabel) {
checkArgumentNotNull(key);
setSecret(stringKeyLabel.getLabel(), KeySerializer.serializeString(key));
return this;
}
private KmsUpdater setPublicKey(PGPPublicKey publicKey, PublicKeyLabel publicKeyLabel)
throws IOException {
private SecretManagerKeyringUpdater setPublicKey(
PGPPublicKey publicKey, PublicKeyLabel publicKeyLabel) throws IOException {
checkArgumentNotNull(publicKey);
setSecret(publicKeyLabel.getLabel(), KeySerializer.serializePublicKey(publicKey));
return this;
}
private KmsUpdater setKeyPair(
private SecretManagerKeyringUpdater setKeyPair(
PGPKeyPair keyPair, PrivateKeyLabel privateKeyLabel, PublicKeyLabel publicKeyLabel)
throws IOException, PGPException {
checkArgumentNotNull(keyPair);

View File

@@ -1,45 +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.model;
import com.google.common.collect.ImmutableSet;
import google.registry.model.annotations.DeleteAfterMigration;
import google.registry.model.common.GaeUserIdConverter;
import google.registry.model.contact.Contact;
import google.registry.model.contact.ContactHistory;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainHistory;
import google.registry.model.host.Host;
import google.registry.model.host.HostHistory;
import google.registry.model.reporting.HistoryEntry;
/** Sets of classes of the Objectify-registered entities in use throughout the model. */
@DeleteAfterMigration
public final class EntityClasses {
/** Set of entity classes. */
public static final ImmutableSet<Class<? extends ImmutableObject>> ALL_CLASSES =
ImmutableSet.of(
Contact.class,
ContactHistory.class,
Domain.class,
DomainHistory.class,
GaeUserIdConverter.class,
HistoryEntry.class,
Host.class,
HostHistory.class);
private EntityClasses() {}
}

View File

@@ -31,11 +31,9 @@ import com.github.benmanes.caffeine.cache.LoadingCache;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.annotation.Id;
import com.googlecode.objectify.annotation.Ignore;
import com.googlecode.objectify.annotation.Index;
import google.registry.config.RegistryConfig;
import google.registry.model.CacheUtils.AppEngineEnvironmentCacheLoader;
import google.registry.model.annotations.OfyIdAllocation;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.transfer.TransferData;
import google.registry.persistence.VKey;
@@ -47,7 +45,6 @@ import java.util.Set;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.Column;
import javax.persistence.MappedSuperclass;
import javax.persistence.Transient;
@@ -55,7 +52,7 @@ import org.joda.time.DateTime;
/** An EPP entity object (i.e. a domain, contact, or host). */
@MappedSuperclass
@Access(AccessType.FIELD) // otherwise it'll use the default if the repoId (property)
@Access(AccessType.FIELD) // otherwise it'll use the default of the repoId (property)
public abstract class EppResource extends UpdateAutoTimestampEntity implements Buildable {
private static final long serialVersionUID = -252782773382339534L;
@@ -70,7 +67,7 @@ public abstract class EppResource extends UpdateAutoTimestampEntity implements B
*
* @see <a href="https://tools.ietf.org/html/rfc5730">RFC 5730</a>
*/
@Id @Transient String repoId;
@OfyIdAllocation @Transient String repoId;
/**
* The ID of the registrar that is currently sponsoring this resource.
@@ -78,9 +75,7 @@ public abstract class EppResource extends UpdateAutoTimestampEntity implements B
* <p>This can be null in the case of pre-Registry-3.0-migration history objects with null
* resource fields.
*/
@Index
@Column(name = "currentSponsorRegistrarId")
String currentSponsorClientId;
String currentSponsorRegistrarId;
/**
* The ID of the registrar that created this resource.
@@ -88,8 +83,7 @@ public abstract class EppResource extends UpdateAutoTimestampEntity implements B
* <p>This can be null in the case of pre-Registry-3.0-migration history objects with null
* resource fields.
*/
@Column(name = "creationRegistrarId")
String creationClientId;
String creationRegistrarId;
/**
* The ID of the registrar that last updated this resource.
@@ -98,8 +92,7 @@ public abstract class EppResource extends UpdateAutoTimestampEntity implements B
* edits; it only includes EPP-visible modifications such as {@literal <update>}. Can be null if
* the resource has never been modified.
*/
@Column(name = "lastEppUpdateRegistrarId")
String lastEppUpdateClientId;
String lastEppUpdateRegistrarId;
/**
* The time when this resource was created.
@@ -111,8 +104,8 @@ public abstract class EppResource extends UpdateAutoTimestampEntity implements B
* <p>This can be null in the case of pre-Registry-3.0-migration history objects with null
* resource fields.
*/
@AttributeOverrides(@AttributeOverride(name = "creationTime", column = @Column))
@Ignore
// Need to override the default non-null column attribute.
@AttributeOverride(name = "creationTime", column = @Column)
CreateAutoTimestamp creationTime = CreateAutoTimestamp.create(null);
/**
@@ -128,7 +121,7 @@ public abstract class EppResource extends UpdateAutoTimestampEntity implements B
* out of the index at that time, as long as we query for resources whose deletion time is before
* now.
*/
@Index DateTime deletionTime;
DateTime deletionTime;
/**
* The time that this resource was last updated.
@@ -140,7 +133,7 @@ public abstract class EppResource extends UpdateAutoTimestampEntity implements B
DateTime lastEppUpdateTime;
/** Status values associated with this resource. */
@Ignore Set<StatusValue> statuses;
Set<StatusValue> statuses;
public String getRepoId() {
return repoId;
@@ -164,7 +157,7 @@ public abstract class EppResource extends UpdateAutoTimestampEntity implements B
}
public String getCreationRegistrarId() {
return creationClientId;
return creationRegistrarId;
}
public DateTime getLastEppUpdateTime() {
@@ -172,17 +165,17 @@ public abstract class EppResource extends UpdateAutoTimestampEntity implements B
}
public String getLastEppUpdateRegistrarId() {
return lastEppUpdateClientId;
return lastEppUpdateRegistrarId;
}
/**
* Get the stored value of {@link #currentSponsorClientId}.
* Get the stored value of {@link #currentSponsorRegistrarId}.
*
* <p>For subordinate hosts, this value may not represent the actual current client id, which is
* the client id of the superordinate host. For all other resources this is the true client id.
*/
public final String getPersistedCurrentSponsorRegistrarId() {
return currentSponsorClientId;
return currentSponsorRegistrarId;
}
public final ImmutableSet<StatusValue> getStatusValues() {
@@ -272,13 +265,13 @@ public abstract class EppResource extends UpdateAutoTimestampEntity implements B
/** Set the current sponsoring registrar. */
public B setPersistedCurrentSponsorRegistrarId(String currentSponsorRegistrarId) {
getInstance().currentSponsorClientId = currentSponsorRegistrarId;
getInstance().currentSponsorRegistrarId = currentSponsorRegistrarId;
return thisCastToDerived();
}
/** Set the registrar that created this resource. */
public B setCreationRegistrarId(String creationRegistrarId) {
getInstance().creationClientId = creationRegistrarId;
getInstance().creationRegistrarId = creationRegistrarId;
return thisCastToDerived();
}
@@ -290,7 +283,7 @@ public abstract class EppResource extends UpdateAutoTimestampEntity implements B
/** Set the registrar who last performed a {@literal <update>} on this resource. */
public B setLastEppUpdateRegistrarId(String lastEppUpdateRegistrarId) {
getInstance().lastEppUpdateClientId = lastEppUpdateRegistrarId;
getInstance().lastEppUpdateRegistrarId = lastEppUpdateRegistrarId;
return thisCastToDerived();
}
@@ -320,14 +313,14 @@ public abstract class EppResource extends UpdateAutoTimestampEntity implements B
/** Add to this resource's status values. */
public B addStatusValues(ImmutableSet<StatusValue> statusValues) {
return setStatusValues(ImmutableSet.copyOf(
union(getInstance().getStatusValues(), statusValues)));
return setStatusValues(
ImmutableSet.copyOf(union(getInstance().getStatusValues(), statusValues)));
}
/** Remove from this resource's status values. */
public B removeStatusValues(ImmutableSet<StatusValue> statusValues) {
return setStatusValues(ImmutableSet.copyOf(
difference(getInstance().getStatusValues(), statusValues)));
return setStatusValues(
ImmutableSet.copyOf(difference(getInstance().getStatusValues(), statusValues)));
}
/** Set this resource's repoId. */

View File

@@ -26,7 +26,6 @@ import static google.registry.util.DateTimeUtils.latestOf;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.flogger.FluentLogger;
import com.googlecode.objectify.Key;
import google.registry.config.RegistryConfig;
import google.registry.model.EppResource.BuilderWithTransferData;
import google.registry.model.EppResource.ForeignKeyedEppResource;
@@ -359,7 +358,7 @@ public final class EppResourceUtils {
jpaTm()
.getEntityManager()
.createNativeQuery(HOST_LINKED_DOMAIN_QUERY)
.setParameter("fkRepoId", key.getSqlKey())
.setParameter("fkRepoId", key.getKey())
.setParameter("now", now.toDate());
}
if (limit != null) {
@@ -370,9 +369,7 @@ public final class EppResourceUtils {
(ImmutableSet<VKey<Domain>>)
query
.getResultStream()
.map(
repoId ->
Domain.createVKey(Key.create(Domain.class, (String) repoId)))
.map(repoId -> Domain.createVKey((String) repoId))
.collect(toImmutableSet());
return domainKeySet;
});

View File

@@ -89,7 +89,7 @@ public final class ForeignKeyUtils {
Class<E> clazz, Collection<String> foreignKeys, final DateTime now) {
return load(clazz, foreignKeys, false).entrySet().stream()
.filter(e -> now.isBefore(e.getValue().deletionTime()))
.collect(toImmutableMap(Entry::getKey, e -> VKey.createSql(clazz, e.getValue().repoId())));
.collect(toImmutableMap(Entry::getKey, e -> VKey.create(clazz, e.getValue().repoId())));
}
/**
@@ -146,16 +146,14 @@ public final class ForeignKeyUtils {
// called, it is always passed with a list of VKeys with the same type.
Class<? extends EppResource> clazz = keys.iterator().next().getKind();
ImmutableList<String> foreignKeys =
Streams.stream(keys)
.map(key -> (String) key.getSqlKey())
.collect(toImmutableList());
Streams.stream(keys).map(key -> (String) key.getKey()).collect(toImmutableList());
ImmutableMap<String, MostRecentResource> existingKeys =
ForeignKeyUtils.load(clazz, foreignKeys, true);
// The above map only contains keys that exist in the database, so we re-add the
// missing ones with Optional.empty() values for caching.
return Maps.asMap(
ImmutableSet.copyOf(keys),
key -> Optional.ofNullable(existingKeys.get((String) key.getSqlKey())));
key -> Optional.ofNullable(existingKeys.get((String) key.getKey())));
}
};
@@ -211,15 +209,14 @@ public final class ForeignKeyUtils {
return load(clazz, foreignKeys, now);
}
return foreignKeyCache
.getAll(
foreignKeys.stream().map(fk -> VKey.createSql(clazz, fk)).collect(toImmutableList()))
.getAll(foreignKeys.stream().map(fk -> VKey.create(clazz, fk)).collect(toImmutableList()))
.entrySet()
.stream()
.filter(e -> e.getValue().isPresent() && now.isBefore(e.getValue().get().deletionTime()))
.collect(
toImmutableMap(
e -> (String) e.getKey().getSqlKey(),
e -> VKey.createSql(clazz, e.getValue().get().repoId())));
e -> (String) e.getKey().getKey(),
e -> VKey.create(clazz, e.getValue().get().repoId())));
}
@AutoValue

View File

@@ -183,11 +183,11 @@ public final class ResourceTransferUtils {
* sets the last EPP update client id to the given client id.
*/
public static <R extends EppResource & ResourceWithTransferData> R denyPendingTransfer(
R resource, TransferStatus transferStatus, DateTime now, String lastEppUpdateClientId) {
R resource, TransferStatus transferStatus, DateTime now, String lastEppUpdateRegistrarId) {
checkArgument(transferStatus.isDenied(), "Not a denial transfer status");
return resolvePendingTransfer(resource, transferStatus, now)
.setLastEppUpdateTime(now)
.setLastEppUpdateRegistrarId(lastEppUpdateClientId)
.setLastEppUpdateRegistrarId(lastEppUpdateRegistrarId)
.build();
}
}

View File

@@ -1,69 +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.model;
import static com.google.common.base.Predicates.or;
import static com.google.common.base.Predicates.subtypeOf;
import static java.util.stream.Collectors.joining;
import com.google.common.collect.Ordering;
import google.registry.model.annotations.DeleteAfterMigration;
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.SortedSet;
import java.util.TreeSet;
/** Utility methods for getting the version of the model schema from the model code. */
@DeleteAfterMigration
public final class SchemaVersion {
/**
* Returns a set of classes corresponding to all types persisted within the model classes, sorted
* by the string representation.
*/
private static SortedSet<Class<?>> getAllPersistedTypes() {
SortedSet<Class<?>> persistedTypes = new TreeSet<>(Ordering.usingToString());
// Do a breadth-first search for persisted types, starting with @Entity types and expanding each
// ImmutableObject by querying it for all its persisted field types.
persistedTypes.addAll(EntityClasses.ALL_CLASSES);
Queue<Class<?>> queue = new ArrayDeque<>(persistedTypes);
while (!queue.isEmpty()) {
Class<?> clazz = queue.remove();
if (ImmutableObject.class.isAssignableFrom(clazz)) {
for (Class<?> persistedFieldType : ModelUtils.getPersistedFieldTypes(clazz)) {
if (persistedTypes.add(persistedFieldType)) {
// If we haven't seen this type before, add it to the queue to query its field types.
queue.add(persistedFieldType);
}
}
}
}
return persistedTypes;
}
/**
* Return a string representing the schema which includes the definition of all persisted entity
* types (and their field types, recursively). Each definition contains the field names and their
* types (for classes), or else a list of all possible values (for enums).
*/
public static String getSchema() {
return getAllPersistedTypes().stream()
.filter(or(subtypeOf(Enum.class), subtypeOf(ImmutableObject.class)))
.map(ModelUtils::getSchema)
.collect(joining("\n"));
}
private SchemaVersion() {}
}

View File

@@ -30,7 +30,7 @@ public abstract class UpdateAutoTimestampEntity extends ImmutableObject
implements UnsafeSerializable {
/**
* An automatically managed timestamp of when this object was last written to Datastore.
* An automatically managed timestamp of when this object was last written to the database.
*
* <p>Note that this is distinct from the EPP-specified {@link EppResource#lastEppUpdateTime}, in
* that this is updated on every save, rather than only in response to an {@code <update>} command

View File

@@ -32,10 +32,10 @@ import google.registry.model.annotations.OfyIdAllocation;
import google.registry.model.annotations.ReportedOn;
import google.registry.model.common.TimeOfYear;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.DomainHistory.DomainHistoryId;
import google.registry.model.domain.GracePeriod;
import google.registry.model.domain.rgp.GracePeriodStatus;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.reporting.HistoryEntry.HistoryEntryId;
import google.registry.model.transfer.TransferData.TransferServerApproveEntity;
import google.registry.persistence.VKey;
import google.registry.persistence.WithVKey;
@@ -204,8 +204,8 @@ public abstract class BillingEvent extends ImmutableObject
return targetId;
}
public DomainHistoryId getDomainHistoryId() {
return new DomainHistoryId(domainRepoId, domainHistoryRevisionId);
public HistoryEntryId getHistoryEntryId() {
return new HistoryEntryId(domainRepoId, domainHistoryRevisionId);
}
public ImmutableSet<Flag> getFlags() {
@@ -259,14 +259,14 @@ public abstract class BillingEvent extends ImmutableObject
return thisCastToDerived();
}
public B setDomainHistoryId(DomainHistoryId domainHistoryId) {
getInstance().domainHistoryRevisionId = domainHistoryId.getId();
getInstance().domainRepoId = domainHistoryId.getDomainRepoId();
public B setDomainHistoryId(HistoryEntryId domainHistoryId) {
getInstance().domainHistoryRevisionId = domainHistoryId.getRevisionId();
getInstance().domainRepoId = domainHistoryId.getRepoId();
return thisCastToDerived();
}
public B setDomainHistory(DomainHistory domainHistory) {
return setDomainHistoryId(domainHistory.getDomainHistoryId());
return setDomainHistoryId(domainHistory.getHistoryEntryId());
}
@Override
@@ -375,7 +375,7 @@ public abstract class BillingEvent extends ImmutableObject
}
public static VKey<OneTime> createVKey(long id) {
return VKey.createSql(OneTime.class, id);
return VKey.create(OneTime.class, id);
}
@Override
@@ -538,7 +538,7 @@ public abstract class BillingEvent extends ImmutableObject
}
public static VKey<Recurring> createVKey(Long id) {
return VKey.createSql(Recurring.class, id);
return VKey.create(Recurring.class, id);
}
@Override
@@ -653,7 +653,7 @@ public abstract class BillingEvent extends ImmutableObject
public static Cancellation forGracePeriod(
GracePeriod gracePeriod,
DateTime eventTime,
DomainHistoryId domainHistoryId,
HistoryEntryId domainHistoryId,
String targetId) {
checkArgument(
gracePeriod.hasBillingEvent(),
@@ -682,7 +682,7 @@ public abstract class BillingEvent extends ImmutableObject
}
public static VKey<Cancellation> createVKey(long id) {
return VKey.createSql(Cancellation.class, id);
return VKey.create(Cancellation.class, id);
}
@Override

View File

@@ -1,68 +0,0 @@
// Copyright 2021 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.model.common;
import static com.google.common.base.Functions.identity;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.model.EntityClasses.ALL_CLASSES;
import com.google.common.annotations.VisibleForTesting;
import com.googlecode.objectify.annotation.EntitySubclass;
import java.util.Map;
import java.util.stream.Collectors;
/** A helper to manage class name and class path mapping. */
public class ClassPathManager {
/**
* Class registry allowing us to restore the original class object from the unqualified class
* name, which is all the datastore key gives us. Note that entities annotated
* with @EntitySubclass are removed because they share the same kind of the key with their parent
* class.
*/
public static final Map<String, Class<?>> CLASS_REGISTRY =
ALL_CLASSES.stream()
.filter(clazz -> !clazz.isAnnotationPresent(EntitySubclass.class))
.collect(Collectors.toMap(com.googlecode.objectify.Key::getKind, identity()));
/**
* Class name registry allowing us to obtain the class name the unqualified class, which is all
* the datastore key gives us. Note that entities annotated with @EntitySubclass are removed
* because they share the same kind of the key with their parent class.
*/
public static final Map<Class<?>, String> CLASS_NAME_REGISTRY =
ALL_CLASSES.stream()
.filter(clazz -> !clazz.isAnnotationPresent(EntitySubclass.class))
.collect(Collectors.toMap(identity(), com.googlecode.objectify.Key::getKind));
@VisibleForTesting
public static void addTestEntityClass(Class<?> clazz) {
CLASS_REGISTRY.put(clazz.getSimpleName(), clazz);
CLASS_NAME_REGISTRY.put(clazz, clazz.getSimpleName());
}
public static <T> Class<T> getClass(String className) {
checkArgument(
CLASS_REGISTRY.containsKey(className), "Class %s not found in class registry", className);
return (Class<T>) CLASS_REGISTRY.get(className);
}
public static <T> String getClassName(Class<T> clazz) {
checkArgument(
CLASS_NAME_REGISTRY.containsKey(clazz),
"Class %s not found in class name registry",
clazz.getSimpleName());
return CLASS_NAME_REGISTRY.get(clazz);
}
}

View File

@@ -14,22 +14,15 @@
package google.registry.model.common;
import com.googlecode.objectify.annotation.Id;
import google.registry.model.ImmutableObject;
import google.registry.model.annotations.DeleteAfterMigration;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
/**
* A singleton entity in the database.
*
* <p>This class should not be deleted after the migration, because there is still a concept of
* singleton in SQL. We should remove the ofy @Id annotation after all of its subclass are Ofy-free.
*/
@DeleteAfterMigration
/** A singleton entity in the database. */
@MappedSuperclass
public abstract class CrossTldSingleton extends ImmutableObject {
public static final long SINGLETON_ID = 1; // There is always exactly one of these.
@Id @javax.persistence.Id long id = SINGLETON_ID;
@Id long id = SINGLETON_ID;
}

View File

@@ -141,7 +141,7 @@ public class Cursor extends UpdateAutoTimestampEntity {
private static VKey<Cursor> createVKey(CursorType type, String scope) {
checkValidCursorTypeForScope(type, scope);
return VKey.createSql(Cursor.class, new CursorId(type, scope));
return VKey.create(Cursor.class, new CursorId(type, scope));
}
public DateTime getLastUpdateTime() {

View File

@@ -16,7 +16,6 @@ package google.registry.model.contact;
import google.registry.model.EppResource.ForeignKeyedEppResource;
import google.registry.model.annotations.ExternalMessagingName;
import google.registry.model.annotations.ReportedOn;
import google.registry.persistence.VKey;
import google.registry.persistence.WithVKey;
import javax.persistence.Access;
@@ -32,9 +31,7 @@ import org.joda.time.DateTime;
*
* @see <a href="https://tools.ietf.org/html/rfc5733">RFC 5733</a>
*/
@ReportedOn
@Entity
@com.googlecode.objectify.annotation.Entity
@Table(
name = "Contact",
indexes = {
@@ -51,7 +48,7 @@ public class Contact extends ContactBase implements ForeignKeyedEppResource {
@Override
public VKey<Contact> createVKey() {
return VKey.createSql(Contact.class, getRepoId());
return VKey.create(Contact.class, getRepoId());
}
@Override

View File

@@ -19,11 +19,6 @@ import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.model.EppResourceUtils.projectResourceOntoBuilderAtTime;
import com.google.common.collect.ImmutableList;
import com.googlecode.objectify.annotation.Ignore;
import com.googlecode.objectify.annotation.IgnoreSave;
import com.googlecode.objectify.annotation.Index;
import com.googlecode.objectify.annotation.OnLoad;
import com.googlecode.objectify.condition.IfNull;
import google.registry.model.EppResource;
import google.registry.model.EppResource.ResourceWithTransferData;
import google.registry.model.transfer.ContactTransferData;
@@ -54,7 +49,8 @@ import org.joda.time.DateTime;
@MappedSuperclass
@Embeddable
@Access(AccessType.FIELD)
public class ContactBase extends EppResource implements ResourceWithTransferData {
public class ContactBase extends EppResource
implements ResourceWithTransferData<ContactTransferData> {
/**
* Unique identifier for this contact.
@@ -69,7 +65,6 @@ public class ContactBase extends EppResource implements ResourceWithTransferData
* Localized postal info for the contact. All contained values must be representable in the 7-bit
* US-ASCII character set. Personal info; cleared by {@link Contact.Builder#wipeOut}.
*/
@Ignore
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "name", column = @Column(name = "addr_local_name")),
@@ -97,7 +92,6 @@ public class ContactBase extends EppResource implements ResourceWithTransferData
* Internationalized postal info for the contact. Personal info; cleared by {@link
* Contact.Builder#wipeOut}.
*/
@Ignore
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "name", column = @Column(name = "addr_i18n_name")),
@@ -126,10 +120,9 @@ public class ContactBase extends EppResource implements ResourceWithTransferData
* postal name, or if null, the localized postal name, or if that is null as well, null. Personal
* info; cleared by {@link Contact.Builder#wipeOut}.
*/
@Index String searchName;
String searchName;
/** Contacts voice number. Personal info; cleared by {@link Contact.Builder#wipeOut}. */
@Ignore
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "phoneNumber", column = @Column(name = "voice_phone_number")),
@@ -138,7 +131,6 @@ public class ContactBase extends EppResource implements ResourceWithTransferData
ContactPhoneNumber voice;
/** Contacts fax number. Personal info; cleared by {@link Contact.Builder#wipeOut}. */
@Ignore
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "phoneNumber", column = @Column(name = "fax_phone_number")),
@@ -147,11 +139,9 @@ public class ContactBase extends EppResource implements ResourceWithTransferData
ContactPhoneNumber fax;
/** Contacts email address. Personal info; cleared by {@link Contact.Builder#wipeOut}. */
@IgnoreSave(IfNull.class)
String email;
/** Authorization info (aka transfer secret) of the contact. */
@Ignore
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "pw.value", column = @Column(name = "auth_info_value")),
@@ -160,7 +150,7 @@ public class ContactBase extends EppResource implements ResourceWithTransferData
ContactAuthInfo authInfo;
/** Data about any pending or past transfers on this contact. */
@Ignore ContactTransferData transferData;
ContactTransferData transferData;
/**
* The time that this resource was last transferred.
@@ -173,7 +163,6 @@ public class ContactBase extends EppResource implements ResourceWithTransferData
// the wipeOut() function, so that data is not kept around for deleted contacts.
/** Disclosure policy. */
@Ignore
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "name", column = @Column(name = "disclose_types_name")),
@@ -193,17 +182,6 @@ public class ContactBase extends EppResource implements ResourceWithTransferData
+ " use Contact instead");
}
@OnLoad
void onLoad() {
if (voice != null && voice.hasNullFields()) {
voice = null;
}
if (fax != null && fax.hasNullFields()) {
fax = null;
}
}
public String getContactId() {
return contactId;
}

View File

@@ -14,24 +14,18 @@
package google.registry.model.contact;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.EntitySubclass;
import google.registry.model.EppResource;
import google.registry.model.ImmutableObject;
import google.registry.model.UnsafeSerializable;
import google.registry.model.contact.ContactHistory.ContactHistoryId;
import google.registry.model.reporting.HistoryEntry;
import google.registry.persistence.VKey;
import java.io.Serializable;
import java.util.Optional;
import javax.annotation.Nullable;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.AttributeOverride;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.PostLoad;
import javax.persistence.Index;
import javax.persistence.Table;
/**
* A persisted history entry representing an EPP modification to a contact.
@@ -39,53 +33,26 @@ import javax.persistence.PostLoad;
* <p>In addition to the general history fields (e.g. action time, registrar ID) we also persist a
* copy of the contact entity at this point in time. We persist a raw {@link ContactBase} so that
* the foreign-keyed fields in that class can refer to this object.
*
* <p>This class is only marked as a Datastore entity subclass and registered with Objectify so that
* when building it its ID can be auto-populated by Objectify. It is converted to its superclass
* {@link HistoryEntry} when persisted to Datastore using {@link
* google.registry.persistence.transaction.TransactionManager}.
*/
@Entity
@javax.persistence.Table(
@Table(
indexes = {
@javax.persistence.Index(columnList = "creationTime"),
@javax.persistence.Index(columnList = "historyRegistrarId"),
@javax.persistence.Index(columnList = "historyType"),
@javax.persistence.Index(columnList = "historyModificationTime")
@Index(columnList = "creationTime"),
@Index(columnList = "historyRegistrarId"),
@Index(columnList = "historyType"),
@Index(columnList = "historyModificationTime")
})
@EntitySubclass
@AttributeOverride(name = "repoId", column = @Column(name = "contactRepoId"))
@Access(AccessType.FIELD)
@IdClass(ContactHistoryId.class)
public class ContactHistory extends HistoryEntry implements UnsafeSerializable {
public class ContactHistory extends HistoryEntry {
// Store ContactBase instead of Contact so we don't pick up its @Id
// Nullable for the sake of pre-Registry-3.0 history objects
@Nullable ContactBase contactBase;
// Store ContactBase instead of Contact, so we don't pick up its @Id
// @Nullable for the sake of pre-Registry-3.0 history objects
@Nullable ContactBase resource;
@Id
@Access(AccessType.PROPERTY)
public String getContactRepoId() {
// We need to handle null case here because Hibernate sometimes accesses this method before
// parent gets initialized
return parent == null ? null : parent.getName();
}
/** This method is private because it is only used by Hibernate. */
@SuppressWarnings("unused")
private void setContactRepoId(String contactRepoId) {
parent = Key.create(Contact.class, contactRepoId);
}
@Id
@Column(name = "historyRevisionId")
@Access(AccessType.PROPERTY)
@Override
public long getId() {
return super.getId();
}
public ContactHistoryId getContactHistoryId() {
return new ContactHistoryId(getContactRepoId(), getId());
protected ContactBase getResource() {
return resource;
}
/**
@@ -95,19 +62,13 @@ public class ContactHistory extends HistoryEntry implements UnsafeSerializable {
* <p>Will be absent for objects created prior to the Registry 3.0 SQL migration.
*/
public Optional<ContactBase> getContactBase() {
return Optional.ofNullable(contactBase);
}
/** The key to the {@link Contact} this is based off of. */
public VKey<Contact> getParentVKey() {
return VKey.create(Contact.class, getContactRepoId());
return Optional.ofNullable(resource);
}
/** Creates a {@link VKey} instance for this entity. */
@SuppressWarnings("unchecked")
@Override
public VKey<ContactHistory> createVKey() {
return (VKey<ContactHistory>) createVKey(Key.create(this));
return VKey.create(ContactHistory.class, getHistoryEntryId());
}
@Override
@@ -115,77 +76,6 @@ public class ContactHistory extends HistoryEntry implements UnsafeSerializable {
return getContactBase().map(contactBase -> new Contact.Builder().copyFrom(contactBase).build());
}
@PostLoad
void postLoad() {
// Normally Hibernate would see that the contact fields are all null and would fill contactBase
// with a null object. Unfortunately, the updateTimestamp is never null in SQL.
if (contactBase != null && contactBase.getContactId() == null) {
contactBase = null;
}
if (contactBase != null && contactBase.getRepoId() == null) {
// contactBase hasn't been fully constructed yet, so it's ok to go in and mutate it. Though
// the use of the Builder is not necessarily problematic in this case, this is still safer as
// the Builder can do things like comparisons that compute the hash code.
contactBase.setRepoId(parent.getName());
}
}
/** Class to represent the composite primary key of {@link ContactHistory} entity. */
public static class ContactHistoryId extends ImmutableObject implements Serializable {
private String contactRepoId;
private Long id;
/** Hibernate requires this default constructor. */
private ContactHistoryId() {}
public ContactHistoryId(String contactRepoId, long id) {
this.contactRepoId = contactRepoId;
this.id = id;
}
/**
* Returns the contact repository id.
*
* <p>This method is private because it is only used by Hibernate.
*/
public String getContactRepoId() {
return contactRepoId;
}
/**
* Returns the history revision id.
*
* <p>This method is private because it is only used by Hibernate.
*/
public long getId() {
return id;
}
/**
* Sets the contact repository id.
*
* <p>This method is private because it is only used by Hibernate and should not be used
* externally to keep immutability.
*/
@SuppressWarnings("unused")
private void setContactRepoId(String contactRepoId) {
this.contactRepoId = contactRepoId;
}
/**
* Sets the history revision id.
*
* <p>This method is private because it is only used by Hibernate and should not be used
* externally to keep immutability.
*/
@SuppressWarnings("unused")
private void setId(long id) {
this.id = id;
}
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));
@@ -199,23 +89,13 @@ public class ContactHistory extends HistoryEntry implements UnsafeSerializable {
super(instance);
}
public Builder setContact(@Nullable ContactBase contactBase) {
// Nullable for the sake of pre-Registry-3.0 history objects
if (contactBase == null) {
return this;
}
getInstance().contactBase = contactBase;
return super.setParent(contactBase);
}
public Builder setContactRepoId(String contactRepoId) {
getInstance().parent = Key.create(Contact.class, contactRepoId);
return this;
public Builder setContact(ContactBase contactBase) {
getInstance().resource = contactBase;
return setRepoId(contactBase);
}
public Builder wipeOutPii() {
getInstance().contactBase =
getInstance().getContactBase().get().asBuilder().wipeOut().build();
getInstance().resource = getInstance().resource.asBuilder().wipeOut().build();
return this;
}
}

View File

@@ -30,22 +30,24 @@ import org.joda.time.DateTime;
/** The {@link ResponseData} returned for an EPP info flow on a contact. */
@XmlRootElement(name = "infData")
@XmlType(propOrder = {
"contactId",
"repoId",
"statusValues",
"postalInfos",
"voiceNumber",
"faxNumber",
"emailAddress",
"currentSponsorClientId",
"creationClientId",
"creationTime",
"lastEppUpdateClientId",
"lastEppUpdateTime",
"lastTransferTime",
"authInfo",
"disclose" })
@XmlType(
propOrder = {
"contactId",
"repoId",
"statusValues",
"postalInfos",
"voiceNumber",
"faxNumber",
"emailAddress",
"currentSponsorRegistrarId",
"creationRegistrarId",
"creationTime",
"lastEppUpdateRegistrarId",
"lastEppUpdateTime",
"lastTransferTime",
"authInfo",
"disclose"
})
@AutoValue
@CopyAnnotations
public abstract class ContactInfoData implements ResponseData {
@@ -76,17 +78,17 @@ public abstract class ContactInfoData implements ResponseData {
abstract String getEmailAddress();
@XmlElement(name = "clID")
abstract String getCurrentSponsorClientId();
abstract String getCurrentSponsorRegistrarId();
@XmlElement(name = "crID")
abstract String getCreationClientId();
abstract String getCreationRegistrarId();
@XmlElement(name = "crDate")
abstract DateTime getCreationTime();
@XmlElement(name = "upID")
@Nullable
abstract String getLastEppUpdateClientId();
abstract String getLastEppUpdateRegistrarId();
@XmlElement(name = "upDate")
@Nullable
@@ -114,10 +116,15 @@ public abstract class ContactInfoData implements ResponseData {
public abstract Builder setVoiceNumber(@Nullable ContactPhoneNumber voiceNumber);
public abstract Builder setFaxNumber(@Nullable ContactPhoneNumber faxNumber);
public abstract Builder setEmailAddress(@Nullable String emailAddress);
public abstract Builder setCurrentSponsorClientId(String currentSponsorClientId);
public abstract Builder setCreationClientId(String creationClientId);
public abstract Builder setCurrentSponsorRegistrarId(String currentSponsorRegistrarId);
public abstract Builder setCreationRegistrarId(String creationRegistrarId);
public abstract Builder setCreationTime(DateTime creationTime);
public abstract Builder setLastEppUpdateClientId(@Nullable String lastEppUpdateClientId);
public abstract Builder setLastEppUpdateRegistrarId(@Nullable String lastEppUpdateRegistrarId);
public abstract Builder setLastEppUpdateTime(@Nullable DateTime lastEppUpdateTime);
public abstract Builder setLastTransferTime(@Nullable DateTime lastTransferTime);
public abstract Builder setAuthInfo(@Nullable ContactAuthInfo authInfo);

View File

@@ -16,9 +16,7 @@ package google.registry.model.domain;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Ignore;
import com.googlecode.objectify.annotation.Index;
import google.registry.model.ImmutableObject;
import google.registry.model.UnsafeSerializable;
import google.registry.model.contact.Contact;
@@ -66,13 +64,11 @@ public class DesignatedContact extends ImmutableObject implements UnsafeSerializ
DesignatedContact instance = new DesignatedContact();
instance.type = type;
instance.contactVKey = checkArgumentNotNull(contact, "Must specify contact key");
instance.contact = contact.maybeGetOfyKey().orElse(null);
return instance;
}
Type type;
@Index Key<Contact> contact;
@Ignore VKey<Contact> contactVKey;
public Type getType() {
@@ -82,8 +78,4 @@ public class DesignatedContact extends ImmutableObject implements UnsafeSerializ
public VKey<Contact> getContactKey() {
return contactVKey;
}
public DesignatedContact reconstitute() {
return create(type, VKey.from(contact));
}
}

View File

@@ -14,11 +14,9 @@
package google.registry.model.domain;
import com.googlecode.objectify.Key;
import google.registry.model.EppResource;
import google.registry.model.EppResource.ForeignKeyedEppResource;
import google.registry.model.annotations.ExternalMessagingName;
import google.registry.model.annotations.ReportedOn;
import google.registry.model.domain.secdns.DomainDsData;
import google.registry.model.host.Host;
import google.registry.persistence.VKey;
@@ -31,6 +29,7 @@ import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
@@ -45,8 +44,6 @@ import org.joda.time.DateTime;
*
* @see <a href="https://tools.ietf.org/html/rfc5731">RFC 5731</a>
*/
@ReportedOn
@com.googlecode.objectify.annotation.Entity
@Entity
@Table(
name = "Domain",
@@ -72,7 +69,7 @@ import org.joda.time.DateTime;
public class Domain extends DomainBase implements ForeignKeyedEppResource {
@Override
@javax.persistence.Id
@Id
@Access(AccessType.PROPERTY)
public String getRepoId() {
return super.getRepoId();
@@ -110,7 +107,7 @@ public class Domain extends DomainBase implements ForeignKeyedEppResource {
referencedColumnName = "repoId",
insertable = false,
updatable = false)
@SuppressWarnings("UnusedMethod")
@SuppressWarnings("unused")
private Set<GracePeriod> getInternalGracePeriods() {
return gracePeriods;
}
@@ -132,7 +129,7 @@ public class Domain extends DomainBase implements ForeignKeyedEppResource {
referencedColumnName = "repoId",
insertable = false,
updatable = false)
@SuppressWarnings("UnusedMethod")
@SuppressWarnings("unused")
private Set<DomainDsData> getInternalDelegationSignerData() {
return dsData;
}
@@ -147,7 +144,7 @@ public class Domain extends DomainBase implements ForeignKeyedEppResource {
@Override
public VKey<Domain> createVKey() {
return VKey.createSql(Domain.class, getRepoId());
return VKey.create(Domain.class, getRepoId());
}
@Override
@@ -155,8 +152,8 @@ public class Domain extends DomainBase implements ForeignKeyedEppResource {
return cloneDomainProjectedAtTime(this, now);
}
public static VKey<Domain> createVKey(Key<Domain> key) {
return VKey.create(Domain.class, key.getName(), key);
public static VKey<Domain> createVKey(String repoId) {
return VKey.create(Domain.class, repoId);
}
/** An override of {@link EppResource#asBuilder} with tighter typing. */

View File

@@ -36,20 +36,15 @@ import static google.registry.util.DomainNameUtils.canonicalizeHostname;
import static google.registry.util.DomainNameUtils.getTldFromDomainName;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Ordering;
import com.google.common.collect.Sets;
import com.googlecode.objectify.annotation.Ignore;
import com.googlecode.objectify.annotation.IgnoreSave;
import com.googlecode.objectify.annotation.Index;
import com.googlecode.objectify.condition.IfNull;
import google.registry.dns.RefreshDnsAction;
import google.registry.flows.ResourceFlowUtils;
import google.registry.model.EppResource;
import google.registry.model.EppResource.ResourceWithTransferData;
import google.registry.model.billing.BillingEvent;
import google.registry.model.billing.BillingEvent.Recurring;
import google.registry.model.contact.Contact;
import google.registry.model.domain.launch.LaunchNotice;
import google.registry.model.domain.rgp.GracePeriodStatus;
@@ -59,16 +54,20 @@ import google.registry.model.domain.token.AllocationToken.TokenType;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.host.Host;
import google.registry.model.poll.PollMessage;
import google.registry.model.poll.PollMessage.Autorenew;
import google.registry.model.poll.PollMessage.OneTime;
import google.registry.model.tld.Registry;
import google.registry.model.transfer.DomainTransferData;
import google.registry.model.transfer.TransferStatus;
import google.registry.persistence.VKey;
import google.registry.tldconfig.idn.IdnLabelValidator;
import google.registry.util.CollectionUtils;
import google.registry.util.DateTimeUtils;
import java.util.HashSet;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import javax.annotation.Nullable;
import javax.persistence.Access;
import javax.persistence.AccessType;
@@ -77,6 +76,7 @@ import javax.persistence.AttributeOverrides;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.Embedded;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import javax.persistence.Transient;
import org.hibernate.collection.internal.PersistentSet;
@@ -86,9 +86,9 @@ import org.joda.time.Interval;
/**
* A persistable domain resource including mutable and non-mutable fields.
*
* <p>This class deliberately does not include an {@link javax.persistence.Id} so that any
* foreign-keyed fields can refer to the proper parent entity's ID, whether we're storing this in
* the DB itself or as part of another entity.
* <p>This class deliberately does not include an {@link Id} so that any foreign-keyed fields can
* refer to the proper parent entity's ID, whether we're storing this in the DB itself or as part of
* another entity.
*
* @see <a href="https://tools.ietf.org/html/rfc5731">RFC 5731</a>
*/
@@ -118,13 +118,13 @@ public class DomainBase extends EppResource
*
* @invariant domainName == domainName.toLowerCase(Locale.ENGLISH)
*/
@Index String domainName;
String domainName;
/** The top level domain this is under, dernormalized from {@link #domainName}. */
@Index String tld;
/** The top level domain this is under, de-normalized from {@link #domainName}. */
String tld;
/** References to hosts that are the nameservers for the domain. */
@EmptySetToNull @Index @Transient Set<VKey<Host>> nsHosts;
@EmptySetToNull @Transient Set<VKey<Host>> nsHosts;
/** Contacts. */
VKey<Contact> adminContact;
@@ -134,7 +134,6 @@ public class DomainBase extends EppResource
VKey<Contact> registrantContact;
/** Authorization info (aka transfer secret) of the domain. */
@Ignore
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "pw.value", column = @Column(name = "auth_info_value")),
@@ -143,14 +142,13 @@ public class DomainBase extends EppResource
DomainAuthInfo authInfo;
/** Data used to construct DS records for this domain. */
@Ignore @Transient Set<DomainDsData> dsData;
@Transient Set<DomainDsData> dsData;
/**
* The claims notice supplied when this domain was created, if there was one.
*
* <p>It's {@literal @}XmlTransient because it's not returned in an info response.
*/
@Ignore
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "noticeId.tcnId", column = @Column(name = "launch_notice_tcn_id")),
@@ -169,9 +167,8 @@ public class DomainBase extends EppResource
/**
* Name of first IDN table associated with TLD that matched the characters in this domain label.
*
* @see google.registry.tldconfig.idn.IdnLabelValidator#findValidIdnTableForTld
* @see IdnLabelValidator#findValidIdnTableForTld
*/
@IgnoreSave(IfNull.class)
String idnTableName;
/** Fully qualified host names of this domain's active subordinate hosts. */
@@ -188,44 +185,42 @@ public class DomainBase extends EppResource
* restored, the message should be deleted.
*/
@Column(name = "deletion_poll_message_id")
VKey<PollMessage.OneTime> deletePollMessage;
VKey<OneTime> deletePollMessage;
/**
* The recurring billing event associated with this domain's autorenewals.
*
* <p>The recurrence should be open ended unless the domain is in pending delete or fully deleted,
* <p>The recurrence should be open-ended unless the domain is in pending delete or fully deleted,
* in which case it should be closed at the time the delete was requested. Whenever the domain's
* {@link #registrationExpirationTime} is changed the recurrence should be closed, a new one
* should be created, and this field should be updated to point to the new one.
*/
@Column(name = "billing_recurrence_id")
@Ignore
VKey<BillingEvent.Recurring> autorenewBillingEvent;
VKey<Recurring> autorenewBillingEvent;
/**
* The recurring poll message associated with this domain's autorenewals.
*
* <p>The recurrence should be open ended unless the domain is in pending delete or fully deleted,
* <p>The recurrence should be open-ended unless the domain is in pending delete or fully deleted,
* in which case it should be closed at the time the delete was requested. Whenever the domain's
* {@link #registrationExpirationTime} is changed the recurrence should be closed, a new one
* should be created, and this field should be updated to point to the new one.
*/
@Column(name = "autorenew_poll_message_id")
VKey<PollMessage.Autorenew> autorenewPollMessage;
VKey<Autorenew> autorenewPollMessage;
/** The unexpired grace periods for this domain (some of which may not be active yet). */
@Ignore @Transient Set<GracePeriod> gracePeriods;
@Transient Set<GracePeriod> gracePeriods;
/**
* The id of the signed mark that was used to create this domain in sunrise.
*
* <p>Will only be populated for domains created in sunrise.
*/
@IgnoreSave(IfNull.class)
String smdId;
/** Data about any pending or past transfers on this domain. */
@Ignore DomainTransferData transferData;
DomainTransferData transferData;
/**
* The time that this resource was last transferred.
@@ -249,12 +244,12 @@ public class DomainBase extends EppResource
* difference domains that have reached their life and must be deleted now, and domains that
* happen to be in the autorenew grace period now but should be deleted in roughly a year.
*/
@Index DateTime autorenewEndTime;
DateTime autorenewEndTime;
/**
* When this domain's DNS was requested to be refreshed, or null if its DNS is up-to-date.
*
* <p>This will almost always be null except in the couple minutes' interval between when a
* <p>This will almost always be null except in the couple of minutes' interval between when a
* DNS-affecting create or update operation takes place and when the {@link RefreshDnsAction}
* runs, which resets this back to null upon completion of the DNS refresh task. This is a {@link
* DateTime} rather than a simple dirty boolean so that the DNS refresh action can order by the
@@ -271,10 +266,10 @@ public class DomainBase extends EppResource
* it remains as a permanent record of which actions were DNS-affecting and which were not.
*/
// TODO(mcilwain): Start using this field once we are further along in the DB migration.
@Ignore DateTime dnsRefreshRequestTime;
DateTime dnsRefreshRequestTime;
/** The {@link AllocationToken} for the package this domain is currently a part of. */
@Ignore @Nullable VKey<AllocationToken> currentPackageToken;
@Nullable VKey<AllocationToken> currentPackageToken;
/**
* Returns the DNS refresh request time iff this domain's DNS needs refreshing, otherwise absent.
@@ -291,15 +286,15 @@ public class DomainBase extends EppResource
return registrationExpirationTime;
}
public VKey<PollMessage.OneTime> getDeletePollMessage() {
public VKey<OneTime> getDeletePollMessage() {
return deletePollMessage;
}
public VKey<BillingEvent.Recurring> getAutorenewBillingEvent() {
public VKey<Recurring> getAutorenewBillingEvent() {
return autorenewBillingEvent;
}
public VKey<PollMessage.Autorenew> getAutorenewPollMessage() {
public VKey<Autorenew> getAutorenewPollMessage() {
return autorenewPollMessage;
}
@@ -362,7 +357,7 @@ public class DomainBase extends EppResource
}
// Hibernate needs this in order to populate nsHosts but no one else should ever use it
@SuppressWarnings("UnusedMethod")
@SuppressWarnings("unused")
private void setNsHosts(Set<VKey<Host>> nsHosts) {
this.nsHosts = forceEmptyToNull(nsHosts);
}
@@ -370,14 +365,14 @@ public class DomainBase extends EppResource
// Note: for the two methods below, how we wish to treat the Hibernate setters depends on the
// current state of the object and what's passed in. The key principle is that we wish to maintain
// the link between parent and child objects, meaning that we should keep around whichever of the
// two sets (the parameter vs the class variable and clear/populate that as appropriate.
// two sets (the parameter vs the class variable and clear/populate that as appropriate).
//
// If the class variable is a PersistentSet and we overwrite it here, Hibernate will throw
// If the class variable is a PersistentSet, and we overwrite it here, Hibernate will throw
// an exception "A collection with cascade=”all-delete-orphan” was no longer referenced by the
// owning entity instance". See https://stackoverflow.com/questions/5587482 for more details.
// Hibernate needs this in order to populate gracePeriods but no one else should ever use it
@SuppressWarnings("UnusedMethod")
@SuppressWarnings("unused")
private void setInternalGracePeriods(Set<GracePeriod> gracePeriods) {
if (this.gracePeriods instanceof PersistentSet) {
Set<GracePeriod> nonNullGracePeriods = nullToEmpty(gracePeriods);
@@ -389,7 +384,7 @@ public class DomainBase extends EppResource
}
// Hibernate needs this in order to populate dsData but no one else should ever use it
@SuppressWarnings("UnusedMethod")
@SuppressWarnings("unused")
private void setInternalDelegationSignerData(Set<DomainDsData> dsData) {
if (this.dsData instanceof PersistentSet) {
Set<DomainDsData> nonNullDsData = nullToEmpty(dsData);
@@ -488,7 +483,7 @@ public class DomainBase extends EppResource
.setAutorenewBillingEvent(transferData.getServerApproveAutorenewEvent())
.setAutorenewPollMessage(transferData.getServerApproveAutorenewPollMessage());
if (transferData.getTransferPeriod().getValue() == 1) {
// Set the grace period using a key to the prescheduled transfer billing event. Not using
// Set the grace period using a key to the pre-scheduled transfer billing event. Not using
// GracePeriod.forBillingEvent() here in order to avoid the actual Datastore fetch.
builder.setGracePeriods(
ImmutableSet.of(
@@ -551,7 +546,7 @@ public class DomainBase extends EppResource
}
}
// It is possible that the lastEppUpdateClientId is different from current sponsor client
// It is possible that the lastEppUpdateRegistrarId is different from current sponsor client
// id, so we have to do the comparison instead of having one variable just storing the most
// recent time.
if (newLastEppUpdateTime.isPresent()) {
@@ -656,7 +651,7 @@ public class DomainBase extends EppResource
if (includeRegistrant) {
registrantContact = null;
}
HashSet<DesignatedContact.Type> contactsDiscovered = new HashSet<DesignatedContact.Type>();
HashSet<DesignatedContact.Type> contactsDiscovered = new HashSet<>();
for (DesignatedContact contact : contacts) {
checkArgument(
!contactsDiscovered.contains(contact.getType()),
@@ -851,17 +846,17 @@ public class DomainBase extends EppResource
return thisCastToDerived();
}
public B setDeletePollMessage(VKey<PollMessage.OneTime> deletePollMessage) {
public B setDeletePollMessage(VKey<OneTime> deletePollMessage) {
getInstance().deletePollMessage = deletePollMessage;
return thisCastToDerived();
}
public B setAutorenewBillingEvent(VKey<BillingEvent.Recurring> autorenewBillingEvent) {
public B setAutorenewBillingEvent(VKey<Recurring> autorenewBillingEvent) {
getInstance().autorenewBillingEvent = autorenewBillingEvent;
return thisCastToDerived();
}
public B setAutorenewPollMessage(@Nullable VKey<PollMessage.Autorenew> autorenewPollMessage) {
public B setAutorenewPollMessage(@Nullable VKey<Autorenew> autorenewPollMessage) {
getInstance().autorenewPollMessage = autorenewPollMessage;
return thisCastToDerived();
}
@@ -931,7 +926,7 @@ public class DomainBase extends EppResource
new IllegalArgumentException(
String.format(
"The package token %s does not exist",
currentPackageToken.getSqlKey())));
currentPackageToken.getKey())));
checkArgument(
token.getTokenType().equals(TokenType.PACKAGE),
"The currentPackageToken must have a PACKAGE TokenType");

View File

@@ -18,12 +18,8 @@ import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.EntitySubclass;
import com.googlecode.objectify.annotation.Ignore;
import google.registry.model.EppResource;
import google.registry.model.ImmutableObject;
import google.registry.model.domain.DomainHistory.DomainHistoryId;
import google.registry.model.domain.GracePeriod.GracePeriodHistory;
import google.registry.model.domain.secdns.DomainDsData;
import google.registry.model.domain.secdns.DomainDsDataHistory;
@@ -31,7 +27,6 @@ import google.registry.model.host.Host;
import google.registry.model.reporting.DomainTransactionRecord;
import google.registry.model.reporting.HistoryEntry;
import google.registry.persistence.VKey;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
@@ -45,8 +40,6 @@ import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.JoinColumns;
@@ -62,11 +55,6 @@ import org.hibernate.Hibernate;
* <p>In addition to the general history fields (e.g. action time, registrar ID) we also persist a
* copy of the domain entity at this point in time. We persist a raw {@link DomainBase} so that the
* foreign-keyed fields in that class can refer to this object.
*
* <p>This class is only marked as a Datastore entity subclass and registered with Objectify so that
* when building it its ID can be auto-populated by Objectify. It is converted to its superclass
* {@link HistoryEntry} when persisted to Datastore using {@link
* google.registry.persistence.transaction.TransactionManager}.
*/
@Entity
@Table(
@@ -76,49 +64,44 @@ import org.hibernate.Hibernate;
@Index(columnList = "historyType"),
@Index(columnList = "historyModificationTime")
})
@EntitySubclass
@Access(AccessType.FIELD)
@IdClass(DomainHistoryId.class)
@AttributeOverride(name = "repoId", column = @Column(name = "domainRepoId"))
public class DomainHistory extends HistoryEntry {
// Store DomainBase instead of Domain so we don't pick up its @Id
// Nullable for the sake of pre-Registry-3.0 history objects
@Nullable DomainBase domainBase;
// Store DomainBase instead of Domain, so we don't pick up its @Id
// @Nullable for the sake of pre-Registry-3.0 history objects
@Nullable DomainBase resource;
@Id
@Access(AccessType.PROPERTY)
public String getDomainRepoId() {
// We need to handle null case here because Hibernate sometimes accesses this method before
// parent gets initialized
return parent == null ? null : parent.getName();
@Override
protected DomainBase getResource() {
return resource;
}
/** This method is private because it is only used by Hibernate. */
@SuppressWarnings("unused")
private void setDomainRepoId(String domainRepoId) {
parent = Key.create(Domain.class, domainRepoId);
/**
* The values of all the fields on the {@link DomainBase} object after the action represented by
* this history object was executed.
*
* <p>Will be absent for objects created prior to the Registry 3.0 SQL migration.
*/
public Optional<DomainBase> getDomainBase() {
return Optional.ofNullable(resource);
}
// We could have reused domainBase.nsHosts here, but Hibernate throws a weird exception after
// we change to use a composite primary key.
// TODO(b/166776754): Investigate if we can reuse domainBase.nsHosts for storing host keys.
@ElementCollection
@JoinTable(
name = "DomainHistoryHost",
indexes = {
@Index(
columnList =
"domain_history_history_revision_id,domain_history_domain_repo_id,host_repo_id",
unique = true),
})
@ImmutableObject.EmptySetToNull
indexes =
@Index(
columnList =
"domain_history_history_revision_id,domain_history_domain_repo_id,host_repo_id",
unique = true))
@EmptySetToNull
@Column(name = "host_repo_id")
Set<VKey<Host>> nsHosts;
@OneToMany(
cascade = {CascadeType.ALL},
fetch = FetchType.EAGER,
orphanRemoval = true)
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
@JoinColumns({
@JoinColumn(
name = "domainHistoryRevisionId",
@@ -135,10 +118,7 @@ public class DomainHistory extends HistoryEntry {
@Ignore
Set<DomainDsDataHistory> dsDataHistories = new HashSet<>();
@OneToMany(
cascade = {CascadeType.ALL},
fetch = FetchType.EAGER,
orphanRemoval = true)
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
@JoinColumns({
@JoinColumn(
name = "domainHistoryRevisionId",
@@ -152,19 +132,14 @@ public class DomainHistory extends HistoryEntry {
updatable = false)
})
// HashSet rather than ImmutableSet so that Hibernate can fill them out lazily on request
@Ignore
Set<GracePeriodHistory> gracePeriodHistories = new HashSet<>();
@Override
@Nullable
@Access(AccessType.PROPERTY)
/** The length of time that a create, allocate, renewal, or transfer request was issued for. */
@AttributeOverrides({
@AttributeOverride(name = "unit", column = @Column(name = "historyPeriodUnit")),
@AttributeOverride(name = "value", column = @Column(name = "historyPeriodValue"))
})
public Period getPeriod() {
return super.getPeriod();
}
Period period;
/**
* For transfers, the id of the other registrar.
@@ -174,56 +149,29 @@ public class DomainHistory extends HistoryEntry {
* registrar is the gaining party.
*/
@Nullable
@Access(AccessType.PROPERTY)
@Column(name = "historyOtherRegistrarId")
@Override
public String getOtherRegistrarId() {
return super.getOtherRegistrarId();
}
String otherRegistrarId;
/**
* Logging field for transaction reporting.
*
* <p>This will be empty for any DomainHistory/HistoryEntry generated before this field was added,
* mid-2017, as well as any action that does not generate billable events (e.g. updates).
*
* <p>This method is dedicated for Hibernate, external caller should use {@link
* #getDomainTransactionRecords()}.
* <p>This will be empty for any DomainHistory/HistoryEntry generated before this field was added
* (mid-2017), as well as any action that does not generate billable events (e.g. contact/host
* updates). *
*/
@Access(AccessType.PROPERTY)
@OneToMany(
cascade = {CascadeType.ALL},
fetch = FetchType.EAGER,
orphanRemoval = true)
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
@JoinColumn(name = "historyRevisionId", referencedColumnName = "historyRevisionId")
@JoinColumn(name = "domainRepoId", referencedColumnName = "domainRepoId")
@SuppressWarnings("unused")
private Set<DomainTransactionRecord> getInternalDomainTransactionRecords() {
return domainTransactionRecords;
}
@EmptySetToNull
Set<DomainTransactionRecord> domainTransactionRecords;
/** Sets the domain transaction records. This method is dedicated for Hibernate. */
@SuppressWarnings("unused")
private void setInternalDomainTransactionRecords(
Set<DomainTransactionRecord> domainTransactionRecords) {
super.setDomainTransactionRecords(domainTransactionRecords);
}
@Id
@Column(name = "historyRevisionId")
@Access(AccessType.PROPERTY)
@Override
public long getId() {
return super.getId();
}
public DomainHistoryId getDomainHistoryId() {
return new DomainHistoryId(getDomainRepoId(), getId());
public Set<DomainTransactionRecord> getDomainTransactionRecords() {
return nullToEmptyImmutableCopy(domainTransactionRecords);
}
/** Returns keys to the {@link Host} that are the nameservers for the domain. */
public Set<VKey<Host>> getNsHosts() {
return nsHosts;
return ImmutableSet.copyOf(nsHosts);
}
/** Returns the collection of {@link DomainDsDataHistory} instances. */
@@ -231,30 +179,14 @@ public class DomainHistory extends HistoryEntry {
return nullToEmptyImmutableCopy(dsDataHistories);
}
/**
* The values of all the fields on the {@link DomainBase} object after the action represented by
* this history object was executed.
*
* <p>Will be absent for objects created prior to the Registry 3.0 SQL migration.
*/
public Optional<DomainBase> getDomainBase() {
return Optional.ofNullable(domainBase);
}
/** The key to the {@link Domain} this is based off of. */
public VKey<Domain> getParentVKey() {
return VKey.create(Domain.class, getDomainRepoId());
}
public Set<GracePeriodHistory> getGracePeriodHistories() {
return nullToEmptyImmutableCopy(gracePeriodHistories);
}
/** Creates a {@link VKey} instance for this entity. */
@SuppressWarnings("unchecked")
@Override
public VKey<DomainHistory> createVKey() {
return (VKey<DomainHistory>) createVKey(Key.create(this));
return VKey.create(DomainHistory.class, getHistoryEntryId());
}
@Override
@@ -262,102 +194,55 @@ public class DomainHistory extends HistoryEntry {
return getDomainBase().map(domainBase -> new Domain.Builder().copyFrom(domainBase).build());
}
public String getOtherRegistrarId() {
return otherRegistrarId;
}
public Period getPeriod() {
return period;
}
@Override
@PostLoad
void postLoad() {
protected void postLoad() {
// TODO(b/188044616): Determine why Eager loading doesn't work here.
Hibernate.initialize(domainTransactionRecords);
Hibernate.initialize(nsHosts);
Hibernate.initialize(dsDataHistories);
Hibernate.initialize(gracePeriodHistories);
if (domainBase != null) {
domainBase.nsHosts = nullToEmptyImmutableCopy(nsHosts);
domainBase.gracePeriods =
if (resource != null) {
resource.nsHosts = nullToEmptyImmutableCopy(nsHosts);
resource.gracePeriods =
gracePeriodHistories.stream()
.map(GracePeriod::createFromHistory)
.collect(toImmutableSet());
domainBase.dsData =
resource.dsData =
dsDataHistories.stream().map(DomainDsData::create).collect(toImmutableSet());
// Normally Hibernate would see that the domain fields are all null and would fill
// domainBase with a null object. Unfortunately, the updateTimestamp is never null in SQL.
if (domainBase.getDomainName() == null) {
domainBase = null;
} else {
if (domainBase.getRepoId() == null) {
// domainBase still hasn't been fully constructed yet, so it's ok to go in and mutate
// it. In fact, we have to because going through the builder causes the hash codes of
// contained objects to be calculated prematurely.
domainBase.setRepoId(parent.getName());
}
}
}
processResourcePostLoad();
}
private static void fillAuxiliaryFieldsFromDomain(DomainHistory domainHistory) {
if (domainHistory.domainBase != null) {
domainHistory.nsHosts = nullToEmptyImmutableCopy(domainHistory.domainBase.nsHosts);
DomainBase domainBase = domainHistory.resource;
if (domainBase != null) {
domainHistory.nsHosts = nullToEmptyImmutableCopy(domainBase.nsHosts);
domainHistory.dsDataHistories =
nullToEmptyImmutableCopy(domainHistory.domainBase.getDsData()).stream()
nullToEmptyImmutableCopy(domainBase.getDsData()).stream()
.filter(dsData -> dsData.getDigest() != null && dsData.getDigest().length > 0)
.map(dsData -> DomainDsDataHistory.createFrom(domainHistory.id, dsData))
.map(dsData -> DomainDsDataHistory.createFrom(domainHistory.getRevisionId(), dsData))
.collect(toImmutableSet());
domainHistory.gracePeriodHistories =
nullToEmptyImmutableCopy(domainHistory.domainBase.getGracePeriods()).stream()
.map(gracePeriod -> GracePeriodHistory.createFrom(domainHistory.id, gracePeriod))
nullToEmptyImmutableCopy(domainBase.getGracePeriods()).stream()
.map(
gracePeriod ->
GracePeriodHistory.createFrom(domainHistory.getRevisionId(), gracePeriod))
.collect(toImmutableSet());
} else {
domainHistory.nsHosts = ImmutableSet.of();
}
}
/** Class to represent the composite primary key of {@link DomainHistory} entity. */
public static class DomainHistoryId extends ImmutableObject implements Serializable {
private String domainRepoId;
private Long id;
/** Hibernate requires this default constructor. */
private DomainHistoryId() {}
public DomainHistoryId(String domainRepoId, long id) {
this.domainRepoId = domainRepoId;
this.id = id;
}
/** Returns the domain repository id. */
public String getDomainRepoId() {
return domainRepoId;
}
/** Returns the history revision id. */
public long getId() {
return id;
}
/**
* Sets the domain repository id.
*
* <p>This method is private because it is only used by Hibernate and should not be used
* externally to keep immutability.
*/
@SuppressWarnings("unused")
private void setDomainRepoId(String domainRepoId) {
this.domainRepoId = domainRepoId;
}
/**
* Sets the history revision id.
*
* <p>This method is private because it is only used by Hibernate and should not be used
* externally to keep immutability.
*/
@SuppressWarnings("unused")
private void setId(long id) {
this.id = id;
}
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));
@@ -371,35 +256,30 @@ public class DomainHistory extends HistoryEntry {
super(instance);
}
public Builder setDomain(@Nullable DomainBase domainBase) {
// Nullable for the sake of pre-Registry-3.0 history objects
if (domainBase == null) {
return this;
}
// TODO(b/203609982): if actual type of domainBase is Domain, convert to DomainBase
// Note: a DomainHistory fetched by JPA has DomainBase in this field. Allowing Domain
// in the setter makes equality checks messy.
getInstance().domainBase = domainBase;
if (domainBase instanceof Domain) {
super.setParent(domainBase);
} else {
super.setParent(Key.create(Domain.class, domainBase.getRepoId()));
}
public Builder setDomain(DomainBase domainBase) {
getInstance().resource = domainBase;
return setRepoId(domainBase);
}
public Builder setPeriod(Period period) {
getInstance().period = period;
return this;
}
public Builder setDomainRepoId(String domainRepoId) {
getInstance().parent = Key.create(Domain.class, domainRepoId);
public Builder setOtherRegistrarId(String otherRegistrarId) {
getInstance().otherRegistrarId = otherRegistrarId;
return this;
}
public Builder setDomainTransactionRecords(
ImmutableSet<DomainTransactionRecord> domainTransactionRecords) {
getInstance().domainTransactionRecords = domainTransactionRecords;
return this;
}
@Override
public DomainHistory build() {
DomainHistory instance = super.build();
// TODO(b/171990736): Assert instance.domainBase is not null after database migration.
// Note that we cannot assert that instance.domainBase is not null here because this
// builder is also used to convert legacy HistoryEntry objects to DomainHistory, when
// domainBase is not available.
fillAuxiliaryFieldsFromDomain(instance);
return instance;
}

View File

@@ -39,10 +39,10 @@ import org.joda.time.DateTime;
"contacts",
"nameservers",
"subordinateHosts",
"currentSponsorClientId",
"creationClientId",
"currentSponsorRegistrarId",
"creationRegistrarId",
"creationTime",
"lastEppUpdateClientId",
"lastEppUpdateRegistrarId",
"lastEppUpdateTime",
"registrationExpirationTime",
"lastTransferTime",
@@ -79,11 +79,11 @@ public abstract class DomainInfoData implements ResponseData {
abstract ImmutableSet<String> getSubordinateHosts();
@XmlElement(name = "clID")
abstract String getCurrentSponsorClientId();
abstract String getCurrentSponsorRegistrarId();
@XmlElement(name = "crID")
@Nullable
abstract String getCreationClientId();
abstract String getCreationRegistrarId();
@XmlElement(name = "crDate")
@Nullable
@@ -91,7 +91,7 @@ public abstract class DomainInfoData implements ResponseData {
@XmlElement(name = "upID")
@Nullable
abstract String getLastEppUpdateClientId();
abstract String getLastEppUpdateRegistrarId();
@XmlElement(name = "upDate")
@Nullable
@@ -121,10 +121,15 @@ public abstract class DomainInfoData implements ResponseData {
@Nullable ImmutableSet<ForeignKeyedDesignatedContact> contacts);
public abstract Builder setNameservers(@Nullable ImmutableSet<String> nameservers);
public abstract Builder setSubordinateHosts(@Nullable ImmutableSet<String> subordinateHosts);
public abstract Builder setCurrentSponsorClientId(String currentSponsorClientId);
public abstract Builder setCreationClientId(@Nullable String creationClientId);
public abstract Builder setCurrentSponsorRegistrarId(String currentSponsorRegistrarId);
public abstract Builder setCreationRegistrarId(@Nullable String creationRegistrarId);
public abstract Builder setCreationTime(@Nullable DateTime creationTime);
public abstract Builder setLastEppUpdateClientId(@Nullable String lastEppUpdateClientId);
public abstract Builder setLastEppUpdateRegistrarId(@Nullable String lastEppUpdateRegistrarId);
public abstract Builder setLastEppUpdateTime(@Nullable DateTime lastEppUpdateTime);
public abstract Builder setRegistrationExpirationTime(
@Nullable DateTime registrationExpirationTime);

View File

@@ -21,8 +21,8 @@ import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import com.google.common.annotations.VisibleForTesting;
import google.registry.model.billing.BillingEvent;
import google.registry.model.billing.BillingEvent.Recurring;
import google.registry.model.domain.DomainHistory.DomainHistoryId;
import google.registry.model.domain.rgp.GracePeriodStatus;
import google.registry.model.reporting.HistoryEntry.HistoryEntryId;
import google.registry.persistence.VKey;
import javax.annotation.Nullable;
import javax.persistence.Access;
@@ -63,7 +63,8 @@ public class GracePeriod extends GracePeriodBase {
@Nullable VKey<BillingEvent.OneTime> billingEventOneTime,
@Nullable VKey<BillingEvent.Recurring> billingEventRecurring,
@Nullable Long gracePeriodId) {
checkArgument((billingEventOneTime == null) || (billingEventRecurring == null),
checkArgument(
billingEventOneTime == null || billingEventRecurring == null,
"A grace period can have at most one billing event");
checkArgument(
(billingEventRecurring != null) == GracePeriodStatus.AUTO_RENEW.equals(type),
@@ -176,18 +177,6 @@ public class GracePeriod extends GracePeriodBase {
billingEvent.createVKey());
}
/**
* Returns a clone of this {@link GracePeriod} with {@link #domainRepoId} set to the given value
* and reconstructed history ids.
*
* <p>TODO(b/162739503): Remove this function after fully migrating to Cloud SQL.
*/
GracePeriod cloneAfterOfyLoad(String domainRepoId) {
GracePeriod clone = clone(this);
clone.domainRepoId = checkArgumentNotNull(domainRepoId);
return clone;
}
/** Entity class to represent a historic {@link GracePeriod}. */
@Entity(name = "GracePeriodHistory")
@Table(indexes = @Index(columnList = "domainRepoId"))
@@ -203,8 +192,8 @@ public class GracePeriod extends GracePeriodBase {
return super.getGracePeriodId();
}
public DomainHistoryId getDomainHistoryId() {
return new DomainHistoryId(getDomainRepoId(), domainHistoryRevisionId);
public HistoryEntryId getHistoryEntryId() {
return new HistoryEntryId(getDomainRepoId(), domainHistoryRevisionId);
}
static GracePeriodHistory createFrom(long historyRevisionId, GracePeriod gracePeriod) {

View File

@@ -38,7 +38,7 @@ public class PackageTokenResponseExtension extends ImmutableObject implements Re
PackageTokenResponseExtension instance = new PackageTokenResponseExtension();
instance.token = "";
if (tokenKey.isPresent()) {
instance.token = tokenKey.get().getSqlKey().toString();
instance.token = tokenKey.get().getKey().toString();
}
return instance;
}

View File

@@ -16,9 +16,8 @@ package google.registry.model.domain.secdns;
import static google.registry.model.IdService.allocateId;
import google.registry.model.UnsafeSerializable;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.DomainHistory.DomainHistoryId;
import google.registry.model.reporting.HistoryEntry.HistoryEntryId;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.Column;
@@ -27,7 +26,7 @@ import javax.persistence.Id;
/** Entity class to represent a historic {@link DomainDsData}. */
@Entity
public class DomainDsDataHistory extends DomainDsDataBase implements UnsafeSerializable {
public class DomainDsDataHistory extends DomainDsDataBase {
@Id Long dsDataHistoryRevisionId;
@@ -53,8 +52,8 @@ public class DomainDsDataHistory extends DomainDsDataBase implements UnsafeSeria
return instance;
}
public DomainHistory.DomainHistoryId getDomainHistoryId() {
return new DomainHistoryId(getDomainRepoId(), domainHistoryRevisionId);
public HistoryEntryId getHistoryEntryId() {
return new HistoryEntryId(getDomainRepoId(), domainHistoryRevisionId);
}
@Override

View File

@@ -38,8 +38,7 @@ import google.registry.model.CreateAutoTimestamp;
import google.registry.model.UpdateAutoTimestampEntity;
import google.registry.model.billing.BillingEvent.RenewalPriceBehavior;
import google.registry.model.common.TimedTransitionProperty;
import google.registry.model.reporting.HistoryEntry;
import google.registry.persistence.DomainHistoryVKey;
import google.registry.model.reporting.HistoryEntry.HistoryEntryId;
import google.registry.persistence.VKey;
import google.registry.persistence.WithVKey;
import java.util.Optional;
@@ -154,11 +153,9 @@ public class AllocationToken extends UpdateAutoTimestampEntity implements Builda
@Nullable
@AttributeOverrides({
@AttributeOverride(name = "repoId", column = @Column(name = "redemption_domain_repo_id")),
@AttributeOverride(
name = "historyRevisionId",
column = @Column(name = "redemption_domain_history_id"))
@AttributeOverride(name = "revisionId", column = @Column(name = "redemption_domain_history_id"))
})
DomainHistoryVKey redemptionHistoryEntry;
HistoryEntryId redemptionHistoryId;
/** The fully-qualified domain name that this token is limited to, if any. */
@Nullable String domainName;
@@ -212,13 +209,12 @@ public class AllocationToken extends UpdateAutoTimestampEntity implements Builda
return token;
}
public Optional<VKey<? extends HistoryEntry>> getRedemptionHistoryEntry() {
return Optional.ofNullable(
redemptionHistoryEntry == null ? null : redemptionHistoryEntry.createDomainHistoryVKey());
public Optional<HistoryEntryId> getRedemptionHistoryId() {
return Optional.ofNullable(redemptionHistoryId);
}
public boolean isRedeemed() {
return redemptionHistoryEntry != null;
return redemptionHistoryId != null;
}
public Optional<String> getDomainName() {
@@ -277,7 +273,7 @@ public class AllocationToken extends UpdateAutoTimestampEntity implements Builda
throw new IllegalArgumentException(
String.format("%s tokens are not stored in the database", getTokenBehavior()));
}
return VKey.createSql(AllocationToken.class, getToken());
return VKey.create(AllocationToken.class, getToken());
}
@Override
@@ -309,7 +305,7 @@ public class AllocationToken extends UpdateAutoTimestampEntity implements Builda
getInstance().domainName == null || TokenType.SINGLE_USE.equals(getInstance().tokenType),
"Domain name can only be specified for SINGLE_USE tokens");
checkArgument(
getInstance().redemptionHistoryEntry == null
getInstance().redemptionHistoryId == null
|| TokenType.SINGLE_USE.equals(getInstance().tokenType),
"Redemption history entry can only be specified for SINGLE_USE tokens");
checkArgument(
@@ -345,10 +341,9 @@ public class AllocationToken extends UpdateAutoTimestampEntity implements Builda
return this;
}
public Builder setRedemptionHistoryEntry(VKey<? extends HistoryEntry> redemptionHistoryEntry) {
checkArgumentNotNull(redemptionHistoryEntry, "Redemption history entry must not be null");
getInstance().redemptionHistoryEntry =
DomainHistoryVKey.create(redemptionHistoryEntry.getOfyKey());
public Builder setRedemptionHistoryId(HistoryEntryId redemptionHistoryId) {
checkArgumentNotNull(redemptionHistoryId, "Redemption history entry ID must not be null");
getInstance().redemptionHistoryId = redemptionHistoryId;
return this;
}

View File

@@ -104,14 +104,14 @@ public class PackagePromotion extends ImmutableObject implements Buildable {
jpaTm().assertInTransaction();
return jpaTm()
.query("FROM PackagePromotion WHERE token = :token", PackagePromotion.class)
.setParameter("token", VKey.createSql(AllocationToken.class, tokenString))
.setParameter("token", VKey.create(AllocationToken.class, tokenString))
.getResultStream()
.findFirst();
}
@Override
public VKey<PackagePromotion> createVKey() {
return VKey.createSql(PackagePromotion.class, packagePromotionId);
return VKey.create(PackagePromotion.class, packagePromotionId);
}
@Override

View File

@@ -64,7 +64,7 @@ public class Host extends HostBase implements ForeignKeyedEppResource {
@Override
public VKey<Host> createVKey() {
return VKey.createSql(Host.class, getRepoId());
return VKey.create(Host.class, getRepoId());
}
@Override

View File

@@ -22,9 +22,6 @@ import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static google.registry.util.DomainNameUtils.canonicalizeHostname;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.annotation.IgnoreSave;
import com.googlecode.objectify.annotation.Index;
import com.googlecode.objectify.condition.IfNull;
import google.registry.model.EppResource;
import google.registry.model.domain.Domain;
import google.registry.model.transfer.TransferData;
@@ -63,14 +60,12 @@ public class HostBase extends EppResource {
* from (creationTime, deletionTime) there can only be one host in Datastore with this name.
* However, there can be many hosts with the same name and non-overlapping lifetimes.
*/
@Index String hostName;
String hostName;
/** IP Addresses for this host. Can be null if this is an external host. */
@Index Set<InetAddress> inetAddresses;
Set<InetAddress> inetAddresses;
/** The superordinate domain of this host, or null if this is an external host. */
@Index
@IgnoreSave(IfNull.class)
@DoNotHydrate
VKey<Domain> superordinateDomain;
@@ -144,7 +139,8 @@ public class HostBase extends EppResource {
* superordinate was transferred. If the last superordinate change was before this time, then the
* host was attached to this superordinate domain during that transfer.
*
* <p>If the host is not subordinate the domain can be null and we just return last transfer time.
* <p>If the host is not subordinate the domain can be null, and we just return last transfer
* time.
*
* @param superordinateDomain the loaded superordinate domain, which must match the key in the
* {@link #superordinateDomain} field. Passing it as a parameter allows the caller to control

View File

@@ -1,11 +1,8 @@
// Copyright 2020 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
//
// 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.
@@ -14,24 +11,18 @@
package google.registry.model.host;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.EntitySubclass;
import google.registry.model.EppResource;
import google.registry.model.ImmutableObject;
import google.registry.model.UnsafeSerializable;
import google.registry.model.host.HostHistory.HostHistoryId;
import google.registry.model.reporting.HistoryEntry;
import google.registry.persistence.VKey;
import java.io.Serializable;
import java.util.Optional;
import javax.annotation.Nullable;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.AttributeOverride;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.PostLoad;
import javax.persistence.Index;
import javax.persistence.Table;
/**
* A persisted history entry representing an EPP modification to a host.
@@ -39,54 +30,27 @@ import javax.persistence.PostLoad;
* <p>In addition to the general history fields (e.g. action time, registrar ID) we also persist a
* copy of the host entity at this point in time. We persist a raw {@link HostBase} so that the
* foreign-keyed fields in that class can refer to this object.
*
* <p>This class is only marked as a Datastore entity subclass and registered with Objectify so that
* when building it its ID can be auto-populated by Objectify. It is converted to its superclass
* {@link HistoryEntry} when persisted to Datastore using {@link
* google.registry.persistence.transaction.TransactionManager}.
*/
@Entity
@javax.persistence.Table(
@Table(
indexes = {
@javax.persistence.Index(columnList = "creationTime"),
@javax.persistence.Index(columnList = "historyRegistrarId"),
@javax.persistence.Index(columnList = "hostName"),
@javax.persistence.Index(columnList = "historyType"),
@javax.persistence.Index(columnList = "historyModificationTime")
@Index(columnList = "creationTime"),
@Index(columnList = "historyRegistrarId"),
@Index(columnList = "hostName"),
@Index(columnList = "historyType"),
@Index(columnList = "historyModificationTime")
})
@EntitySubclass
@Access(AccessType.FIELD)
@IdClass(HostHistoryId.class)
public class HostHistory extends HistoryEntry implements UnsafeSerializable {
@AttributeOverride(name = "repoId", column = @Column(name = "hostRepoId"))
public class HostHistory extends HistoryEntry {
// Store HostBase instead of Host so we don't pick up its @Id
// Nullable for the sake of pre-Registry-3.0 history objects
@Nullable HostBase hostBase;
// Store HostBase instead of Host, so we don't pick up its @Id
// @Nullable for the sake of pre-Registry-3.0 history objects
@Nullable HostBase resource;
@Id
@Access(AccessType.PROPERTY)
public String getHostRepoId() {
// We need to handle null case here because Hibernate sometimes accesses this method before
// parent gets initialized
return parent == null ? null : parent.getName();
}
/** This method is private because it is only used by Hibernate. */
@SuppressWarnings("unused")
private void setHostRepoId(String hostRepoId) {
parent = Key.create(Host.class, hostRepoId);
}
@Id
@Column(name = "historyRevisionId")
@Access(AccessType.PROPERTY)
@Override
public long getId() {
return super.getId();
}
public HostHistoryId getHostHistoryId() {
return new HostHistoryId(getHostRepoId(), getId());
protected HostBase getResource() {
return resource;
}
/**
@@ -96,19 +60,13 @@ public class HostHistory extends HistoryEntry implements UnsafeSerializable {
* <p>Will be absent for objects created prior to the Registry 3.0 SQL migration.
*/
public Optional<HostBase> getHostBase() {
return Optional.ofNullable(hostBase);
}
/** The key to the {@link Host} this is based off of. */
public VKey<Host> getParentVKey() {
return VKey.create(Host.class, getHostRepoId());
return Optional.ofNullable(resource);
}
/** Creates a {@link VKey} instance for this entity. */
@SuppressWarnings("unchecked")
@Override
public VKey<HostHistory> createVKey() {
return (VKey<HostHistory>) createVKey(Key.create(this));
return VKey.create(HostHistory.class, getHistoryEntryId());
}
@Override
@@ -116,82 +74,13 @@ public class HostHistory extends HistoryEntry implements UnsafeSerializable {
return getHostBase().map(hostBase -> new Host.Builder().copyFrom(hostBase).build());
}
@PostLoad
void postLoad() {
// Normally Hibernate would see that the host fields are all null and would fill hostBase
// with a null object. Unfortunately, the updateTimestamp is never null in SQL.
if (hostBase != null && hostBase.getHostName() == null) {
hostBase = null;
}
if (hostBase != null && hostBase.getRepoId() == null) {
// hostBase hasn't been fully constructed yet, so it's ok to go in and mutate it. Though the
// use of the Builder is not necessarily problematic in this case, this is still safer as the
// Builder can do things like comparisons that compute the hash code.
hostBase.setRepoId(parent.getName());
}
}
/** Class to represent the composite primary key of {@link HostHistory} entity. */
public static class HostHistoryId extends ImmutableObject implements Serializable {
private String hostRepoId;
private Long id;
/** Hibernate requires this default constructor. */
private HostHistoryId() {}
public HostHistoryId(String hostRepoId, long id) {
this.hostRepoId = hostRepoId;
this.id = id;
}
/**
* Returns the host repository id.
*
* <p>This method is private because it is only used by Hibernate.
*/
public String getHostRepoId() {
return hostRepoId;
}
/**
* Returns the history revision id.
*
* <p>This method is private because it is only used by Hibernate.
*/
public long getId() {
return id;
}
/**
* Sets the host repository id.
*
* <p>This method is private because it is only used by Hibernate and should not be used
* externally to keep immutability.
*/
@SuppressWarnings("unused")
private void setHostRepoId(String hostRepoId) {
this.hostRepoId = hostRepoId;
}
/**
* Sets the history revision id.
*
* <p>This method is private because it is only used by Hibernate and should not be used
* externally to keep immutability.
*/
@SuppressWarnings("unused")
private void setId(long id) {
this.id = id;
}
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));
}
public static class Builder extends HistoryEntry.Builder<HostHistory, Builder> {
public Builder() {}
@@ -200,18 +89,9 @@ public class HostHistory extends HistoryEntry implements UnsafeSerializable {
super(instance);
}
public Builder setHost(@Nullable HostBase hostBase) {
// Nullable for the sake of pre-Registry-3.0 history objects
if (hostBase == null) {
return this;
}
getInstance().hostBase = hostBase;
return super.setParent(hostBase);
}
public Builder setHostRepoId(String hostRepoId) {
getInstance().parent = Key.create(Host.class, hostRepoId);
return this;
public Builder setHost(HostBase hostBase) {
getInstance().resource = hostBase;
return setRepoId(hostBase);
}
}
}

View File

@@ -34,10 +34,10 @@ import org.joda.time.DateTime;
"repoId",
"statusValues",
"inetAddresses",
"currentSponsorClientId",
"creationClientId",
"currentSponsorRegistrarId",
"creationRegistrarId",
"creationTime",
"lastEppUpdateClientId",
"lastEppUpdateRegistrarId",
"lastEppUpdateTime",
"lastTransferTime"
})
@@ -58,17 +58,17 @@ public abstract class HostInfoData implements ResponseData {
abstract ImmutableSet<InetAddress> getInetAddresses();
@XmlElement(name = "clID")
abstract String getCurrentSponsorClientId();
abstract String getCurrentSponsorRegistrarId();
@XmlElement(name = "crID")
abstract String getCreationClientId();
abstract String getCreationRegistrarId();
@XmlElement(name = "crDate")
abstract DateTime getCreationTime();
@XmlElement(name = "upID")
@Nullable
abstract String getLastEppUpdateClientId();
abstract String getLastEppUpdateRegistrarId();
@XmlElement(name = "upDate")
@Nullable
@@ -86,10 +86,15 @@ public abstract class HostInfoData implements ResponseData {
public abstract Builder setRepoId(String repoId);
public abstract Builder setStatusValues(ImmutableSet<StatusValue> statusValues);
public abstract Builder setInetAddresses(ImmutableSet<InetAddress> inetAddresses);
public abstract Builder setCurrentSponsorClientId(String currentSponsorClientId);
public abstract Builder setCreationClientId(String creationClientId);
public abstract Builder setCurrentSponsorRegistrarId(String currentSponsorRegistrarId);
public abstract Builder setCreationRegistrarId(String creationRegistrarId);
public abstract Builder setCreationTime(DateTime creationTime);
public abstract Builder setLastEppUpdateClientId(@Nullable String lastEppUpdateClientId);
public abstract Builder setLastEppUpdateRegistrarId(@Nullable String lastEppUpdateRegistrarId);
public abstract Builder setLastEppUpdateTime(@Nullable DateTime lastEppUpdateTime);
public abstract Builder setLastTransferTime(@Nullable DateTime lastTransferTime);
public abstract HostInfoData build();

View File

@@ -33,17 +33,15 @@ import com.googlecode.objectify.impl.translate.TranslatorFactory;
import com.googlecode.objectify.impl.translate.opt.joda.MoneyStringTranslatorFactory;
import google.registry.config.RegistryEnvironment;
import google.registry.model.Buildable;
import google.registry.model.EntityClasses;
import google.registry.model.ImmutableObject;
import google.registry.model.annotations.DeleteAfterMigration;
import google.registry.model.common.GaeUserIdConverter;
import google.registry.model.translators.BloomFilterOfStringTranslatorFactory;
import google.registry.model.translators.CidrAddressBlockTranslatorFactory;
import google.registry.model.translators.CurrencyUnitTranslatorFactory;
import google.registry.model.translators.DurationTranslatorFactory;
import google.registry.model.translators.EppHistoryVKeyTranslatorFactory;
import google.registry.model.translators.InetAddressTranslatorFactory;
import google.registry.model.translators.ReadableInstantUtcTranslatorFactory;
import google.registry.model.translators.VKeyTranslatorFactory;
/**
* An instance of Ofy, obtained via {@code #auditedOfy()}, should be used to access all persistable
@@ -107,7 +105,7 @@ public class ObjectifyService {
// Translators must be registered before any entities can be registered.
registerTranslators();
registerEntityClasses(EntityClasses.ALL_CLASSES);
registerEntityClasses(ImmutableSet.of(GaeUserIdConverter.class));
}
/** Register translators that allow less common types to be stored directly in Datastore. */
@@ -118,11 +116,9 @@ public class ObjectifyService {
new CidrAddressBlockTranslatorFactory(),
new CurrencyUnitTranslatorFactory(),
new DurationTranslatorFactory(),
new EppHistoryVKeyTranslatorFactory(),
new InetAddressTranslatorFactory(),
new MoneyStringTranslatorFactory(),
new ReadableInstantUtcTranslatorFactory(),
new VKeyTranslatorFactory())) {
new ReadableInstantUtcTranslatorFactory())) {
factory().getTranslators().add(translatorFactory);
}
}

View File

@@ -28,19 +28,17 @@ import google.registry.model.annotations.ExternalMessagingName;
import google.registry.model.annotations.OfyIdAllocation;
import google.registry.model.contact.Contact;
import google.registry.model.contact.ContactHistory;
import google.registry.model.contact.ContactHistory.ContactHistoryId;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.DomainHistory.DomainHistoryId;
import google.registry.model.domain.DomainRenewData;
import google.registry.model.eppoutput.EppResponse.ResponseData;
import google.registry.model.host.Host;
import google.registry.model.host.HostHistory;
import google.registry.model.host.HostHistory.HostHistoryId;
import google.registry.model.poll.PendingActionNotificationResponse.ContactPendingActionNotificationResponse;
import google.registry.model.poll.PendingActionNotificationResponse.DomainPendingActionNotificationResponse;
import google.registry.model.poll.PendingActionNotificationResponse.HostPendingActionNotificationResponse;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.reporting.HistoryEntry.HistoryEntryId;
import google.registry.model.transfer.TransferData.TransferServerApproveEntity;
import google.registry.model.transfer.TransferResponse;
import google.registry.model.transfer.TransferResponse.ContactTransferResponse;
@@ -197,10 +195,10 @@ public abstract class PollMessage extends ImmutableObject
}
/**
* Gets the name of the underlying resource that the PollMessage is for, regardless of the type of
* the resource.
* Gets the repo ID of the underlying resource that the PollMessage is for, regardless of the type
* of the resource.
*/
public String getResourceName() {
public String getResourceId() {
return domainRepoId != null ? domainRepoId : contactRepoId != null ? contactRepoId : hostRepoId;
}
@@ -262,34 +260,35 @@ public abstract class PollMessage extends ImmutableObject
return thisCastToDerived();
}
public B setDomainHistoryId(DomainHistoryId historyId) {
getInstance().domainRepoId = historyId.getDomainRepoId();
getInstance().domainHistoryRevisionId = historyId.getId();
public B setDomainHistoryId(HistoryEntryId historyId) {
getInstance().domainRepoId = historyId.getRepoId();
getInstance().domainHistoryRevisionId = historyId.getRevisionId();
return thisCastToDerived();
}
public B setContactHistoryId(ContactHistoryId historyId) {
getInstance().contactRepoId = historyId.getContactRepoId();
getInstance().contactHistoryRevisionId = historyId.getId();
public B setContactHistoryId(HistoryEntryId historyId) {
getInstance().contactRepoId = historyId.getRepoId();
getInstance().contactHistoryRevisionId = historyId.getRevisionId();
return thisCastToDerived();
}
public B setHostHistoryId(HostHistoryId historyId) {
getInstance().hostRepoId = historyId.getHostRepoId();
getInstance().hostHistoryRevisionId = historyId.getId();
public B setHostHistoryId(HistoryEntryId historyId) {
getInstance().hostRepoId = historyId.getRepoId();
getInstance().hostHistoryRevisionId = historyId.getRevisionId();
return thisCastToDerived();
}
public B setHistoryEntry(HistoryEntry history) {
HistoryEntryId historyId = history.getHistoryEntryId();
// Set the appropriate field based on the history entry type.
if (history instanceof DomainHistory) {
return setDomainHistoryId(((DomainHistory) history).getDomainHistoryId());
return setDomainHistoryId(historyId);
}
if (history instanceof ContactHistory) {
return setContactHistoryId(((ContactHistory) history).getContactHistoryId());
return setContactHistoryId(historyId);
}
if (history instanceof HostHistory) {
return setHostHistoryId(((HostHistory) history).getHostHistoryId());
return setHostHistoryId(historyId);
}
return thisCastToDerived();
}
@@ -399,7 +398,7 @@ public abstract class PollMessage extends ImmutableObject
@Override
public VKey<OneTime> createVKey() {
return VKey.createSql(OneTime.class, getId());
return VKey.create(OneTime.class, getId());
}
@Override
@@ -564,7 +563,7 @@ public abstract class PollMessage extends ImmutableObject
@Override
public VKey<Autorenew> createVKey() {
return VKey.createSql(Autorenew.class, getId());
return VKey.create(Autorenew.class, getId());
}
@Override

View File

@@ -61,7 +61,7 @@ public final class PollMessageExternalKeyConverter {
}
try {
Long id = Long.parseLong(idComponents.get(0));
return VKey.createSql(PollMessage.class, id);
return VKey.create(PollMessage.class, id);
// Note that idComponents.get(1) is entirely ignored; we never use the year field internally.
} catch (NumberFormatException e) {
throw new PollMessageExternalKeyParseException();

View File

@@ -85,7 +85,7 @@ public final class RdeRevision extends UpdateAutoTimestampEntity {
public static int getNextRevision(String tld, DateTime date, RdeMode mode) {
RdeRevisionId sqlKey = RdeRevisionId.create(tld, date.toLocalDate(), mode);
Optional<RdeRevision> revisionOptional =
tm().transact(() -> tm().loadByKeyIfPresent(VKey.createSql(RdeRevision.class, sqlKey)));
tm().transact(() -> tm().loadByKeyIfPresent(VKey.create(RdeRevision.class, sqlKey)));
return revisionOptional.map(rdeRevision -> rdeRevision.revision + 1).orElse(0);
}
@@ -112,7 +112,7 @@ public final class RdeRevision extends UpdateAutoTimestampEntity {
tm().assertInTransaction();
RdeRevisionId sqlKey = RdeRevisionId.create(tld, date.toLocalDate(), mode);
Optional<RdeRevision> revisionOptional =
tm().loadByKeyIfPresent(VKey.createSql(RdeRevision.class, sqlKey));
tm().loadByKeyIfPresent(VKey.create(RdeRevision.class, sqlKey));
if (revision == 0) {
revisionOptional.ifPresent(
rdeRevision -> {

View File

@@ -656,7 +656,7 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
/** Creates a {@link VKey} for the given {@code registrarId}. */
public static VKey<Registrar> createVKey(String registrarId) {
checkArgumentNotNull(registrarId, "registrarId must be specified");
return VKey.createSql(Registrar.class, registrarId);
return VKey.create(Registrar.class, registrarId);
}
/** A builder for constructing {@link Registrar}, since it is immutable. */

View File

@@ -319,7 +319,7 @@ public class RegistrarPoc extends ImmutableObject implements Jsonifiable, Unsafe
@Override
public VKey<RegistrarPoc> createVKey() {
return VKey.createSql(RegistrarPoc.class, new RegistrarPocId(emailAddress, registrarId));
return VKey.create(RegistrarPoc.class, new RegistrarPocId(emailAddress, registrarId));
}
/** Class to represent the composite primary key for {@link RegistrarPoc} entity. */

View File

@@ -21,7 +21,7 @@ import com.google.common.collect.ImmutableSet;
import google.registry.model.Buildable;
import google.registry.model.ImmutableObject;
import google.registry.model.UnsafeSerializable;
import google.registry.model.domain.DomainHistory.DomainHistoryId;
import google.registry.model.reporting.HistoryEntry.HistoryEntryId;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
@@ -48,6 +48,7 @@ public class DomainTransactionRecord extends ImmutableObject
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Insignificant
@SuppressWarnings("unused")
Long id;
/** The TLD this record operates on. */
@@ -66,8 +67,8 @@ public class DomainTransactionRecord extends ImmutableObject
* The time this Transaction takes effect (counting grace periods and other nuances).
*
* <p>Net adds, renews and transfers are modificationTime + 5 days for the grace period, while
* Autorenews have a 45 day grace period. For deletions, this is the purge date of the domain. And
* for restored names, this is the modificationTime, if done in the 30 day redemption period.
* Autorenews have a 45-day grace period. For deletions, this is the purge date of the domain. And
* for restored names, this is the modificationTime, if done in the 30-day redemption period.
*
* @see <a
* href="https://www.icann.org/resources/unthemed-pages/registry-agmt-appc-10-2001-05-11-en">
@@ -178,8 +179,8 @@ public class DomainTransactionRecord extends ImmutableObject
}
}
public DomainHistoryId getDomainHistoryId() {
return new DomainHistoryId(domainRepoId, historyRevisionId);
public HistoryEntryId getHistoryEntryId() {
return new HistoryEntryId(domainRepoId, historyRevisionId);
}
public DateTime getReportingTime() {

View File

@@ -15,75 +15,49 @@
package google.registry.model.reporting;
import static com.google.common.base.Preconditions.checkArgument;
import static com.googlecode.objectify.Key.getKind;
import static google.registry.util.CollectionUtils.nullToEmpty;
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
import com.googlecode.objectify.annotation.Ignore;
import com.googlecode.objectify.annotation.Index;
import com.googlecode.objectify.annotation.Parent;
import google.registry.model.Buildable;
import google.registry.model.EppResource;
import google.registry.model.ImmutableObject;
import google.registry.model.UnsafeSerializable;
import google.registry.model.annotations.ReportedOn;
import google.registry.model.contact.Contact;
import google.registry.model.annotations.OfyIdAllocation;
import google.registry.model.contact.ContactBase;
import google.registry.model.contact.ContactHistory;
import google.registry.model.contact.ContactHistory.ContactHistoryId;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainBase;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.DomainHistory.DomainHistoryId;
import google.registry.model.domain.Period;
import google.registry.model.eppcommon.Trid;
import google.registry.model.host.Host;
import google.registry.model.host.HostBase;
import google.registry.model.host.HostHistory;
import google.registry.model.host.HostHistory.HostHistoryId;
import google.registry.persistence.VKey;
import google.registry.model.reporting.HistoryEntry.HistoryEntryId;
import java.util.Optional;
import java.util.Set;
import javax.annotation.Nullable;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.MappedSuperclass;
import javax.persistence.Transient;
import javax.persistence.PostLoad;
import org.apache.commons.lang3.BooleanUtils;
import org.hibernate.collection.internal.PersistentSet;
import org.joda.time.DateTime;
/**
* A record of an EPP command that mutated a resource.
*
* <p>Due to historical reasons this class is persisted only to Datastore. It has three subclasses
* that include the parent resource itself which are persisted to Cloud SQL. During migration this
* class cannot be made abstract in order for the class to be persisted and loaded to and from
* Datastore. However it should never be used directly in the Java code itself. When it is loaded
* from Datastore it should be converted to a subclass for handling and when a new history entry is
* built it should always be a subclass, which is automatically converted to HistoryEntry when
* persisting to Datastore.
*
* <p>Some care has been taken to make it close to impossible to use this class directly, but the
* user should still exercise caution. After the migration is complete this class will be made
* abstract.
* <p>This abstract class has three subclasses that include the parent resource itself and are
* persisted to Cloud SQL.
*/
@ReportedOn
@Entity
@MappedSuperclass
@Access(AccessType.FIELD)
public class HistoryEntry extends ImmutableObject implements Buildable, UnsafeSerializable {
@IdClass(HistoryEntryId.class)
public abstract class HistoryEntry extends ImmutableObject
implements Buildable, UnsafeSerializable {
/** Represents the type of history entry. */
public enum Type {
@@ -127,36 +101,34 @@ public class HistoryEntry extends ImmutableObject implements Buildable, UnsafeSe
/** Resource was created by an escrow file import. */
RDE_IMPORT,
/**
* A synthetic history entry created by a tool or back-end migration script outside of the scope
* of usual EPP flows. These are sometimes needed to serve as parents for billing events or poll
* A synthetic history entry created by a tool or back-end migration script outside the scope of
* usual EPP flows. These are sometimes needed to serve as parents for billing events or poll
* messages that otherwise wouldn't have a suitable parent.
*/
SYNTHETIC
}
/**
* The autogenerated id of this event. Note that, this field is marked as {@link Transient} in the
* SQL schema, this is because the child class of {@link HistoryEntry}, e.g. {@link
* DomainHistory}, uses a composite primary key which the id is part of, and Hibernate requires
* that all the {@link javax.persistence.Id} fields must be put in the exact same class.
*/
@Id @Transient @VisibleForTesting public Long id;
/** The autogenerated id of this event. */
@Id
@OfyIdAllocation
@Column(nullable = false, name = "historyRevisionId")
protected Long revisionId;
/** The resource this event mutated. */
@Parent @Transient protected Key<? extends EppResource> parent;
/**
* The repo ID of the embedded {@link EppResource} that this event mutated.
*
* <p>Note that the embedded EPP resource is of a base type for which the repo ID field is
* {@code @Transient}, which is NOT persisted as part of the embedded entity. After a {@link
* HistoryEntry} is loaded from SQL, the {@link #postLoad()} methods re-populates the field inside
* the EPP resource.
*/
@Id protected String repoId;
/** The type of history entry. */
@Column(nullable = false, name = "historyType")
@Enumerated(EnumType.STRING)
Type type;
/**
* The length of time that a create, allocate, renewal, or transfer request was issued for. Will
* be null for all other types.
*/
@Ignore @Transient // domain-specific
Period period;
/**
* The actual EPP xml of the command, stored as bytes to be agnostic of encoding.
*
@@ -166,28 +138,15 @@ public class HistoryEntry extends ImmutableObject implements Buildable, UnsafeSe
byte[] xmlBytes;
/** The time the command occurred, represented by the ofy transaction time. */
@Index
@Column(nullable = false, name = "historyModificationTime")
DateTime modificationTime;
/** The id of the registrar that sent the command. */
@Index
@Column(name = "historyRegistrarId")
String clientId;
/**
* For transfers, the id of the other registrar.
*
* <p>For requests and cancels, the other registrar is the losing party (because the registrar
* sending the EPP transfer command is the gaining party). For approves and rejects, the other
* registrar is the gaining party.
*/
@Transient // domain-specific
String otherClientId;
/** Transaction id that made this change, or null if the entry was not created by a flow. */
@Nullable
@Ignore
@AttributeOverrides({
@AttributeOverride(
name = "clientTransactionId",
@@ -210,51 +169,33 @@ public class HistoryEntry extends ImmutableObject implements Buildable, UnsafeSe
@Column(name = "historyRequestedByRegistrar")
Boolean requestedByRegistrar;
/**
* Logging field for transaction reporting.
*
* <p>This will be empty for any HistoryEntry generated before this field was added. This will
* also be empty if the HistoryEntry refers to an EPP mutation that does not affect domain
* transaction counts (such as contact or host mutations).
*/
@Ignore
@Transient // domain-specific
@ImmutableObject.EmptySetToNull
protected Set<DomainTransactionRecord> domainTransactionRecords;
// Make it impossible to instantiate a HistoryEntry explicitly. One should only instantiate a
// subtype of HistoryEntry.
protected HistoryEntry() {
super();
}
public long getId() {
// For some reason, Hibernate throws NPE during some initialization phase if we don't deal with
public long getRevisionId() {
// For some reason, Hibernate throws NPE during some initialization phases if we don't deal with
// the null case. Setting the id to 0L when it is null should be fine because 0L for primitive
// type is considered as null for wrapper class in the Hibernate context.
return id == null ? 0L : id;
return revisionId == null ? 0L : revisionId;
}
/** This method exists solely to satisfy Hibernate. Use the {@link Builder} instead. */
@SuppressWarnings("UnusedMethod")
private void setId(long id) {
this.id = id;
protected abstract EppResource getResource();
public Class<? extends EppResource> getResourceType() {
return getResource().getClass();
}
public Key<? extends EppResource> getParent() {
return parent;
public String getRepoId() {
return repoId;
}
public HistoryEntryId getHistoryEntryId() {
return new HistoryEntryId(repoId, revisionId);
}
public Type getType() {
return type;
}
public Period getPeriod() {
return period;
}
public byte[] getXmlBytes() {
return xmlBytes;
return xmlBytes == null ? null : xmlBytes.clone();
}
public DateTime getModificationTime() {
@@ -265,10 +206,6 @@ public class HistoryEntry extends ImmutableObject implements Buildable, UnsafeSe
return clientId;
}
public String getOtherRegistrarId() {
return otherClientId;
}
/** Returns the TRID, which may be null if the entry was not created by a normal flow. */
@Nullable
public Trid getTrid() {
@@ -287,146 +224,36 @@ public class HistoryEntry extends ImmutableObject implements Buildable, UnsafeSe
return requestedByRegistrar;
}
public Set<DomainTransactionRecord> getDomainTransactionRecords() {
return nullToEmptyImmutableCopy(domainTransactionRecords);
}
public abstract Optional<? extends EppResource> getResourceAtPointInTime();
/**
* Throws an error when attempting to retrieve the EppResource at this point in time.
*
* <p>Subclasses must override this to return the resource; it is non-abstract for legacy reasons
* and objects created prior to the Registry 3.0 migration.
*/
public Optional<? extends EppResource> getResourceAtPointInTime() {
throw new UnsupportedOperationException(
"Raw HistoryEntry objects do not store the resource at that point in time.");
}
/** This method exists solely to satisfy Hibernate. Use the {@link Builder} instead. */
@SuppressWarnings("UnusedMethod")
private void setPeriod(Period period) {
this.period = period;
}
/** This method exists solely to satisfy Hibernate. Use the {@link Builder} instead. */
@SuppressWarnings("UnusedMethod")
private void setOtherRegistrarId(String otherRegistrarId) {
this.otherClientId = otherRegistrarId;
}
/** This method exists solely to satisfy Hibernate. Use the {@link Builder} instead. */
@SuppressWarnings("UnusedMethod")
protected void setDomainTransactionRecords(
Set<DomainTransactionRecord> domainTransactionRecords) {
// Note: how we wish to treat this Hibernate setter depends on the current state of the object
// and what's passed in. The key principle is that we wish to maintain the link between parent
// and child objects, meaning that we should keep around whichever of the two sets (the
// parameter vs the class variable) and clear/populate that as appropriate.
//
// If the class variable is a PersistentSet and we overwrite it here, Hibernate will throw
// an exception "A collection with cascade=”all-delete-orphan” was no longer referenced by the
// owning entity instance". See https://stackoverflow.com/questions/5587482 for more details.
if (this.domainTransactionRecords instanceof PersistentSet) {
Set<DomainTransactionRecord> nonNullRecords = nullToEmpty(domainTransactionRecords);
this.domainTransactionRecords.retainAll(nonNullRecords);
this.domainTransactionRecords.addAll(nonNullRecords);
} else {
this.domainTransactionRecords = domainTransactionRecords;
protected void processResourcePostLoad() {
// Post-Registry 3.0 entity should always have the resource field, whereas pre-Registry 3.0
// will return a null resource.
if (getResource() != null && getResource().getRepoId() == null) {
// The repoId field in EppResource is transient, so we go ahead and set it to the value read
// from SQL.
getResource().setRepoId(repoId);
}
}
/**
* Throws an error when trying to get a builder from a bare {@link HistoryEntry}.
*
* <p>This method only exists to satisfy the requirement that the {@link HistoryEntry} is NOT
* abstract, it should never be called directly and all three of the subclass of {@link
* HistoryEntry} implements it.
*/
@Override
public Builder<? extends HistoryEntry, ?> asBuilder() {
throw new UnsupportedOperationException(
"You should never attempt to build a HistoryEntry from a raw HistoryEntry. A raw "
+ "HistoryEntry should only exist internally when persisting to datastore. If you need "
+ "to build from a raw HistoryEntry, use "
+ "{Contact,Host,Domain}History.Builder.copyFrom(HistoryEntry) instead.");
@PostLoad
protected void postLoad() {
processResourcePostLoad();
}
/**
* Clones and returns a {@code HistoryEntry} objec
*
* <p>This is useful when converting a subclass to the base class to persist to Datastore.
*/
public HistoryEntry asHistoryEntry() {
HistoryEntry historyEntry = new HistoryEntry();
copy(this, historyEntry);
return historyEntry;
}
@Override
public abstract Builder<? extends HistoryEntry, ?> asBuilder();
protected static void copy(HistoryEntry src, HistoryEntry dst) {
dst.id = src.id;
dst.parent = src.parent;
dst.revisionId = src.revisionId;
dst.type = src.type;
dst.period = src.period;
dst.xmlBytes = src.xmlBytes;
dst.modificationTime = src.modificationTime;
dst.clientId = src.clientId;
dst.otherClientId = src.otherClientId;
dst.trid = src.trid;
dst.bySuperuser = src.bySuperuser;
dst.reason = src.reason;
dst.requestedByRegistrar = src.requestedByRegistrar;
dst.domainTransactionRecords =
src.domainTransactionRecords == null
? null
: ImmutableSet.copyOf(src.domainTransactionRecords);
}
@SuppressWarnings("unchecked")
public HistoryEntry toChildHistoryEntity() {
String parentKind = getParent().getKind();
final HistoryEntry resultEntity;
// can't use a switch statement since we're calling getKind()
if (parentKind.equals(getKind(Domain.class))) {
resultEntity =
new DomainHistory.Builder().copyFrom(this).setDomainRepoId(parent.getName()).build();
} else if (parentKind.equals(getKind(Host.class))) {
resultEntity =
new HostHistory.Builder().copyFrom(this).setHostRepoId(parent.getName()).build();
} else if (parentKind.equals(getKind(Contact.class))) {
resultEntity =
new ContactHistory.Builder().copyFrom(this).setContactRepoId(parent.getName()).build();
} else {
throw new IllegalStateException(
String.format("Unknown kind of HistoryEntry parent %s", parentKind));
}
return resultEntity;
}
/** Creates a {@link VKey} instance from a {@link Key} instance. */
public static VKey<? extends HistoryEntry> createVKey(Key<HistoryEntry> key) {
String repoId = key.getParent().getName();
long id = key.getId();
Key<EppResource> parent = key.getParent();
String parentKind = parent.getKind();
if (parentKind.equals(getKind(Domain.class))) {
return VKey.create(
DomainHistory.class,
new DomainHistoryId(repoId, id),
Key.create(parent, DomainHistory.class, id));
} else if (parentKind.equals(getKind(Host.class))) {
return VKey.create(
HostHistory.class,
new HostHistoryId(repoId, id),
Key.create(parent, HostHistory.class, id));
} else if (parentKind.equals(getKind(Contact.class))) {
return VKey.create(
ContactHistory.class,
new ContactHistoryId(repoId, id),
Key.create(parent, ContactHistory.class, id));
} else {
throw new IllegalStateException(
String.format("Unknown kind of HistoryEntry parent %s", parentKind));
}
}
/** A builder for {@link HistoryEntry} since it is immutable */
@@ -451,7 +278,8 @@ public class HistoryEntry extends ImmutableObject implements Buildable, UnsafeSe
@Override
public T build() {
// TODO(mcilwain): Add null checking for id/parent once DB migration is complete.
checkArgumentNotNull(getInstance().getResource(), "EPP resource must be specified");
checkArgumentNotNull(getInstance().repoId, "repoId must be specified");
checkArgumentNotNull(getInstance().type, "History entry type must be specified");
checkArgumentNotNull(getInstance().modificationTime, "Modification time must be specified");
checkArgumentNotNull(getInstance().clientId, "Registrar ID must be specified");
@@ -462,19 +290,15 @@ public class HistoryEntry extends ImmutableObject implements Buildable, UnsafeSe
return super.build();
}
public B setId(Long id) {
getInstance().id = id;
public B setRevisionId(Long revisionId) {
getInstance().revisionId = revisionId;
return thisCastToDerived();
}
protected B setParent(EppResource parent) {
getInstance().parent = Key.create(parent);
return thisCastToDerived();
}
// Until we move completely to SQL, override this in subclasses (e.g. HostHistory) to set VKeys
protected B setParent(Key<? extends EppResource> parent) {
getInstance().parent = parent;
protected B setRepoId(EppResource eppResource) {
if (eppResource != null) {
getInstance().repoId = eppResource.getRepoId();
}
return thisCastToDerived();
}
@@ -483,13 +307,8 @@ public class HistoryEntry extends ImmutableObject implements Buildable, UnsafeSe
return thisCastToDerived();
}
public B setPeriod(Period period) {
getInstance().period = period;
return thisCastToDerived();
}
public B setXmlBytes(byte[] xmlBytes) {
getInstance().xmlBytes = xmlBytes;
getInstance().xmlBytes = xmlBytes == null ? null : xmlBytes.clone();
return thisCastToDerived();
}
@@ -503,11 +322,6 @@ public class HistoryEntry extends ImmutableObject implements Buildable, UnsafeSe
return thisCastToDerived();
}
public B setOtherRegistrarId(String otherRegistrarId) {
getInstance().otherClientId = otherRegistrarId;
return thisCastToDerived();
}
public B setTrid(Trid trid) {
getInstance().trid = trid;
return thisCastToDerived();
@@ -527,12 +341,6 @@ public class HistoryEntry extends ImmutableObject implements Buildable, UnsafeSe
getInstance().requestedByRegistrar = requestedByRegistrar;
return thisCastToDerived();
}
public B setDomainTransactionRecords(
ImmutableSet<DomainTransactionRecord> domainTransactionRecords) {
getInstance().setDomainTransactionRecords(domainTransactionRecords);
return thisCastToDerived();
}
}
public static <E extends EppResource>
@@ -549,4 +357,41 @@ public class HistoryEntry extends ImmutableObject implements Buildable, UnsafeSe
"Class %s does not have an associated HistoryEntry", parent.getClass().getName()));
}
}
/** Class to represent the composite primary key of a {@link HistoryEntry}. */
@Embeddable
@Access(AccessType.PROPERTY)
public static class HistoryEntryId extends ImmutableObject implements UnsafeSerializable {
private String repoId;
private long revisionId;
protected HistoryEntryId() {}
public HistoryEntryId(String repoId, long revisionId) {
this.repoId = repoId;
this.revisionId = revisionId;
}
/** Returns the {@code history_revision_id} of the {@link HistoryEntry}. */
public long getRevisionId() {
return revisionId;
}
@SuppressWarnings("unused")
private void setRevisionId(long revisionId) {
this.revisionId = revisionId;
}
/** Returns the {@code [domain|contact|host]_repo_id} of the {@link HistoryEntry}. */
public String getRepoId() {
return repoId;
}
@SuppressWarnings("unused")
private void setRepoId(String repoId) {
this.repoId = repoId;
}
}
}

View File

@@ -40,12 +40,7 @@ import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import org.joda.time.DateTime;
/**
* Retrieves {@link HistoryEntry} descendants (e.g. {@link DomainHistory}).
*
* <p>This class is configured to retrieve either from Datastore or SQL, depending on which database
* is currently considered the primary database.
*/
/** Retrieves {@link HistoryEntry} descendants (e.g. {@link DomainHistory}). */
public class HistoryEntryDao {
public static ImmutableMap<Class<? extends EppResource>, Class<? extends HistoryEntry>>
@@ -58,15 +53,6 @@ public class HistoryEntryDao {
Host.class,
HostHistory.class);
public static ImmutableMap<Class<? extends HistoryEntry>, String> REPO_ID_FIELD_NAMES =
ImmutableMap.of(
ContactHistory.class,
"contactRepoId",
DomainHistory.class,
"domainRepoId",
HostHistory.class,
"hostRepoId");
/** Loads all history objects in the times specified, including all types. */
public static ImmutableList<HistoryEntry> loadAllHistoryObjects(
DateTime afterTime, DateTime beforeTime) {
@@ -74,59 +60,58 @@ public class HistoryEntryDao {
.transact(
() ->
new ImmutableList.Builder<HistoryEntry>()
.addAll(
loadAllHistoryObjectsFromSql(ContactHistory.class, afterTime, beforeTime))
.addAll(
loadAllHistoryObjectsFromSql(DomainHistory.class, afterTime, beforeTime))
.addAll(loadAllHistoryObjectsFromSql(HostHistory.class, afterTime, beforeTime))
.addAll(loadAllHistoryObjects(ContactHistory.class, afterTime, beforeTime))
.addAll(loadAllHistoryObjects(DomainHistory.class, afterTime, beforeTime))
.addAll(loadAllHistoryObjects(HostHistory.class, afterTime, beforeTime))
.build());
}
/** Loads all history objects corresponding to the given {@link EppResource}. */
public static ImmutableList<HistoryEntry> loadHistoryObjectsForResource(
VKey<? extends EppResource> parentKey) {
return loadHistoryObjectsForResource(parentKey, START_OF_TIME, END_OF_TIME);
VKey<? extends EppResource> resourceKey) {
return loadHistoryObjectsForResource(resourceKey, START_OF_TIME, END_OF_TIME);
}
/**
* Loads all history objects corresponding to the given {@link EppResource} and casted to the
* Loads all history objects corresponding to the given {@link EppResource} and cast to the
* appropriate subclass.
*/
public static <T extends HistoryEntry> ImmutableList<T> loadHistoryObjectsForResource(
VKey<? extends EppResource> parentKey, Class<T> subclazz) {
return loadHistoryObjectsForResource(parentKey, START_OF_TIME, END_OF_TIME, subclazz);
VKey<? extends EppResource> resourceKey, Class<T> subclazz) {
return loadHistoryObjectsForResource(resourceKey, START_OF_TIME, END_OF_TIME, subclazz);
}
/** Loads all history objects in the time period specified for the given {@link EppResource}. */
public static ImmutableList<HistoryEntry> loadHistoryObjectsForResource(
VKey<? extends EppResource> parentKey, DateTime afterTime, DateTime beforeTime) {
VKey<? extends EppResource> resourceKey, DateTime afterTime, DateTime beforeTime) {
return jpaTm()
.transact(() -> loadHistoryObjectsForResourceFromSql(parentKey, afterTime, beforeTime));
.transact(() -> loadHistoryObjectsForResourceInternal(resourceKey, afterTime, beforeTime));
}
/**
* Loads all history objects in the time period specified for the given {@link EppResource} and
* casted to the appropriate subclass.
* cast to the appropriate subclass.
*
* <p>Note that the subclass must be explicitly provided because we need the compile time
* information of T to return an {@code ImmutableList<T>}, even though at runtime we can call
* {@link #getHistoryClassFromParent(Class)} to obtain it, which we also did to confirm that the
* provided subclass is indeed correct.
* <p>Note that the subclass must be explicitly provided because we need compile time information
* of T to return an {@code ImmutableList<T>}, even though at runtime we can call {@link
* #getHistoryClassFromParent(Class)} to obtain it, which we also did to confirm that the provided
* subclass is indeed correct.
*/
public static <T extends HistoryEntry> ImmutableList<T> loadHistoryObjectsForResource(
VKey<? extends EppResource> parentKey,
private static <T extends HistoryEntry> ImmutableList<T> loadHistoryObjectsForResource(
VKey<? extends EppResource> resourceKey,
DateTime afterTime,
DateTime beforeTime,
Class<T> subclazz) {
Class<? extends HistoryEntry> expectedSubclazz = getHistoryClassFromParent(parentKey.getKind());
Class<? extends HistoryEntry> expectedSubclazz =
getHistoryClassFromParent(resourceKey.getKind());
checkArgument(
subclazz.equals(expectedSubclazz),
"The supplied HistoryEntry subclass %s is incompatible with the EppResource %s, "
+ "use %s instead",
subclazz.getSimpleName(),
parentKey.getKind().getSimpleName(),
resourceKey.getKind().getSimpleName(),
expectedSubclazz.getSimpleName());
return loadHistoryObjectsForResource(parentKey, afterTime, beforeTime).stream()
return loadHistoryObjectsForResource(resourceKey, afterTime, beforeTime).stream()
.map(subclazz::cast)
.collect(toImmutableList());
}
@@ -138,14 +123,14 @@ public class HistoryEntryDao {
.transact(
() ->
Streams.concat(
loadHistoryObjectFromSqlByRegistrars(ContactHistory.class, registrarIds),
loadHistoryObjectFromSqlByRegistrars(DomainHistory.class, registrarIds),
loadHistoryObjectFromSqlByRegistrars(HostHistory.class, registrarIds))
loadHistoryObjectByRegistrarsInternal(ContactHistory.class, registrarIds),
loadHistoryObjectByRegistrarsInternal(DomainHistory.class, registrarIds),
loadHistoryObjectByRegistrarsInternal(HostHistory.class, registrarIds))
.sorted(Comparator.comparing(HistoryEntry::getModificationTime))
.collect(toImmutableList()));
}
private static <T extends HistoryEntry> Stream<T> loadHistoryObjectFromSqlByRegistrars(
private static <T extends HistoryEntry> Stream<T> loadHistoryObjectByRegistrarsInternal(
Class<T> historyClass, ImmutableCollection<String> registrarIds) {
return jpaTm()
.criteriaQuery(
@@ -155,45 +140,33 @@ public class HistoryEntryDao {
.getResultStream();
}
private static ImmutableList<HistoryEntry> loadHistoryObjectsForResourceFromSql(
VKey<? extends EppResource> parentKey, DateTime afterTime, DateTime beforeTime) {
// The class we're searching from is based on which parent type (e.g. Domain) we have
Class<? extends HistoryEntry> historyClass = getHistoryClassFromParent(parentKey.getKind());
// The field representing repo ID unfortunately varies by history class
String repoIdFieldName = getRepoIdFieldNameFromHistoryClass(historyClass);
private static ImmutableList<HistoryEntry> loadHistoryObjectsForResourceInternal(
VKey<? extends EppResource> resourceKey, DateTime afterTime, DateTime beforeTime) {
// The class we're searching from is based on which resource type (e.g. Domain) we have
Class<? extends HistoryEntry> historyClass = getHistoryClassFromParent(resourceKey.getKind());
CriteriaBuilder criteriaBuilder = jpaTm().getEntityManager().getCriteriaBuilder();
CriteriaQuery<? extends HistoryEntry> criteriaQuery =
CriteriaQueryBuilder.create(historyClass)
.where("modificationTime", criteriaBuilder::greaterThanOrEqualTo, afterTime)
.where("modificationTime", criteriaBuilder::lessThanOrEqualTo, beforeTime)
.where(repoIdFieldName, criteriaBuilder::equal, parentKey.getSqlKey().toString())
.orderByAsc("id")
.where("repoId", criteriaBuilder::equal, resourceKey.getKey().toString())
.orderByAsc("revisionId")
.orderByAsc("modificationTime")
.build();
return ImmutableList.sortedCopyOf(
Comparator.comparing(HistoryEntry::getModificationTime),
jpaTm().criteriaQuery(criteriaQuery).getResultList());
return ImmutableList.copyOf(jpaTm().criteriaQuery(criteriaQuery).getResultList());
}
public static Class<? extends HistoryEntry> getHistoryClassFromParent(
Class<? extends EppResource> parent) {
if (!RESOURCE_TYPES_TO_HISTORY_TYPES.containsKey(parent)) {
Class<? extends EppResource> resourceType) {
if (!RESOURCE_TYPES_TO_HISTORY_TYPES.containsKey(resourceType)) {
throw new IllegalArgumentException(
String.format("Unknown history type for parent %s", parent.getName()));
String.format("Unknown history type for resourceType %s", resourceType.getName()));
}
return RESOURCE_TYPES_TO_HISTORY_TYPES.get(parent);
return RESOURCE_TYPES_TO_HISTORY_TYPES.get(resourceType);
}
public static String getRepoIdFieldNameFromHistoryClass(
Class<? extends HistoryEntry> historyClass) {
if (!REPO_ID_FIELD_NAMES.containsKey(historyClass)) {
throw new IllegalArgumentException(
String.format("Unknown history type %s", historyClass.getName()));
}
return REPO_ID_FIELD_NAMES.get(historyClass);
}
private static <T extends HistoryEntry> List<T> loadAllHistoryObjectsFromSql(
private static <T extends HistoryEntry> List<T> loadAllHistoryObjects(
Class<T> historyClass, DateTime afterTime, DateTime beforeTime) {
CriteriaBuilder criteriaBuilder = jpaTm().getEntityManager().getCriteriaBuilder();
return jpaTm()

View File

@@ -217,7 +217,7 @@ public class Lock extends ImmutableObject implements Serializable {
// Checking if an unexpired lock still exists - if so, the lock can't be acquired.
Lock lock =
jpaTm()
.loadByKeyIfPresent(VKey.createSql(Lock.class, new LockId(resourceName, scope)))
.loadByKeyIfPresent(VKey.create(Lock.class, new LockId(resourceName, scope)))
.orElse(null);
if (lock != null) {
logger.atInfo().log(
@@ -257,7 +257,7 @@ public class Lock extends ImmutableObject implements Serializable {
// delete it. If the lock in the database was different, then this lock is gone already;
// this can happen if release() is called around the expiration time and the lock
// expires underneath us.
VKey<Lock> key = VKey.createSql(Lock.class, new LockId(resourceName, scope));
VKey<Lock> key = VKey.create(Lock.class, new LockId(resourceName, scope));
Lock loadedLock = jpaTm().loadByKeyIfPresent(key).orElse(null);
if (equals(loadedLock)) {
// Use deleteIgnoringReadOnly() so that we don't create a commit log entry for deleting

View File

@@ -210,12 +210,12 @@ public class Registry extends ImmutableObject implements Buildable, UnsafeSerial
});
public static VKey<Registry> createVKey(String tld) {
return VKey.createSql(Registry.class, tld);
return VKey.create(Registry.class, tld);
}
@Override
public VKey<Registry> createVKey() {
return VKey.createSql(Registry.class, tldStr);
return VKey.create(Registry.class, tldStr);
}
/**

View File

@@ -88,9 +88,9 @@ public abstract class TransferData extends BaseTransferObject implements Buildab
public ImmutableSet<VKey<? extends TransferServerApproveEntity>> getServerApproveEntities() {
return NullIgnoringCollectionBuilder.create(
new ImmutableSet.Builder<VKey<? extends TransferServerApproveEntity>>())
.add(pollMessageId1 != null ? VKey.createSql(PollMessage.class, pollMessageId1) : null)
.add(pollMessageId2 != null ? VKey.createSql(PollMessage.class, pollMessageId2) : null)
.add(pollMessageId3 != null ? VKey.createSql(PollMessage.class, pollMessageId3) : null)
.add(pollMessageId1 != null ? VKey.create(PollMessage.class, pollMessageId1) : null)
.add(pollMessageId2 != null ? VKey.create(PollMessage.class, pollMessageId2) : null)
.add(pollMessageId3 != null ? VKey.create(PollMessage.class, pollMessageId3) : null)
.getBuilder()
.build();
}
@@ -153,7 +153,7 @@ public abstract class TransferData extends BaseTransferObject implements Buildab
Set<VKey<? extends TransferServerApproveEntity>> serverApproveEntities) {
return nullToEmpty(serverApproveEntities).stream()
.filter(vKey -> PollMessage.class.isAssignableFrom(vKey.getKind()))
.map(vKey -> (long) vKey.getSqlKey())
.map(vKey -> (long) vKey.getKey())
.sorted()
.collect(toImmutableList());
}

View File

@@ -1,107 +0,0 @@
// Copyright 2020 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.model.translators;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static java.util.function.Function.identity;
import com.google.appengine.api.datastore.Key;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import google.registry.persistence.DomainHistoryVKey;
import google.registry.persistence.EppHistoryVKey;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import javax.annotation.Nullable;
/** Translator factory for {@link EppHistoryVKey}. */
public class EppHistoryVKeyTranslatorFactory
extends AbstractSimpleTranslatorFactory<EppHistoryVKey, Key> {
public EppHistoryVKeyTranslatorFactory() {
super(EppHistoryVKey.class);
}
// This map is used when we need to convert the raw Datastore key to its VKey instance. We have
// one dedicated VKey class, e.g. DomainHistoryVKey, for each such kind of entity, and we need
// a way to map the raw Datastore key to its VKey class. So, we use the kind path as the key of
// the map, and the kind path is created by concatenating all the kind strings in a raw Datastore
// key, e.g. the map key for ContactPollMessageVKey is "Contact/HistoryEntry/PollMessage".
@VisibleForTesting
static final ImmutableMap<String, Class<? extends EppHistoryVKey>> kindPathToVKeyClass =
ImmutableSet.of(DomainHistoryVKey.class).stream()
.collect(toImmutableMap(EppHistoryVKeyTranslatorFactory::getKindPath, identity()));
/**
* Gets the kind path string for the given {@link Class}.
*
* <p>This method calls the getKindPath method on an instance of the given {@link Class} to get
* the kind path string.
*/
private static String getKindPath(Class<? extends EppHistoryVKey> clazz) {
try {
Constructor<?> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
Object instance = constructor.newInstance();
Method getKindPathMethod = EppHistoryVKey.class.getDeclaredMethod("getKindPath");
getKindPathMethod.setAccessible(true);
return (String) getKindPathMethod.invoke(instance);
} catch (Throwable t) {
throw new IllegalStateException(t);
}
}
@Override
SimpleTranslator<EppHistoryVKey, Key> createTranslator() {
return new SimpleTranslator<EppHistoryVKey, Key>() {
@Nullable
@Override
public EppHistoryVKey loadValue(@Nullable Key datastoreValue) {
if (datastoreValue == null) {
return null;
} else {
com.googlecode.objectify.Key<?> ofyKey =
com.googlecode.objectify.Key.create(datastoreValue);
String kindPath = EppHistoryVKey.createKindPath(ofyKey);
if (kindPathToVKeyClass.containsKey(kindPath)) {
Class<? extends EppHistoryVKey> vKeyClass = kindPathToVKeyClass.get(kindPath);
try {
Method createVKeyMethod =
vKeyClass.getDeclaredMethod("create", com.googlecode.objectify.Key.class);
return (EppHistoryVKey) createVKeyMethod.invoke(null, ofyKey);
} catch (NoSuchMethodException e) {
throw new IllegalStateException(
"Missing static method create(com.googlecode.objectify.Key) on " + vKeyClass);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new IllegalStateException("Error invoking createVKey on " + vKeyClass, e);
}
} else {
throw new IllegalStateException(
"Missing EppHistoryVKey implementation for kind path: " + kindPath);
}
}
}
@Nullable
@Override
public Key saveValue(@Nullable EppHistoryVKey pojoValue) {
throw new UnsupportedOperationException("saveValue for EppHistory keys removed.");
}
};
}
}

View File

@@ -1,98 +0,0 @@
// Copyright 2020 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.model.translators;
import static com.google.common.base.Preconditions.checkArgument;
import com.google.appengine.api.datastore.Key;
import google.registry.model.common.ClassPathManager;
import google.registry.persistence.VKey;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import javax.annotation.Nullable;
/**
* Translator factory for VKey.
*
* <p>These get translated to a string containing the URL safe encoding of the objectify key
* followed by a (url-unsafe) ampersand delimiter and the SQL key.
*/
public class VKeyTranslatorFactory extends AbstractSimpleTranslatorFactory<VKey, Key> {
public VKeyTranslatorFactory() {
super(VKey.class);
}
/** Create a VKey from a raw datastore key. */
@Nullable
public static VKey<?> createVKey(@Nullable Key datastoreKey) {
if (datastoreKey == null) {
return null;
}
return createVKey(com.googlecode.objectify.Key.create(datastoreKey));
}
/** Create a VKey from an objectify Key. */
@Nullable
@SuppressWarnings("unchecked")
public static <T> VKey<T> createVKey(@Nullable com.googlecode.objectify.Key<T> key) {
if (key == null) {
return null;
}
// Try to create the VKey from its reference type.
Class<T> clazz = ClassPathManager.getClass(key.getKind());
checkArgument(clazz != null, "Unknown Key type: %s", key.getKind());
try {
Method createVKeyMethod =
clazz.getDeclaredMethod("createVKey", com.googlecode.objectify.Key.class);
return (VKey<T>) createVKeyMethod.invoke(null, new Object[] {key});
} catch (NoSuchMethodException e) {
checkArgument(
key.getParent() == null,
"Cannot auto-convert key %s of kind %s because it has a parent. Add a createVKey(Key) "
+ "method for it.",
key,
key.getKind());
if (key.getName() != null) {
return VKey.create(clazz, key.getName(), key);
} else {
return VKey.create(clazz, key.getId(), key);
}
} catch (IllegalAccessException | InvocationTargetException e) {
// If we have a createVKey(Key) method with incorrect permissions or that is non-static, this
// is probably an error so let's reported.
throw new RuntimeException(e);
}
}
@Override
public SimpleTranslator<VKey, Key> createTranslator() {
return new SimpleTranslator<VKey, Key>() {
@Nullable
@Override
public VKey loadValue(@Nullable Key datastoreValue) {
return createVKey(datastoreValue);
}
@Nullable
@Override
public Key saveValue(@Nullable VKey key) {
return key == null ? null : key.getOfyKey().getRaw();
}
};
}
}

View File

@@ -33,7 +33,7 @@ import google.registry.groups.GroupssettingsModule;
import google.registry.keyring.KeyringModule;
import google.registry.keyring.api.DummyKeyringModule;
import google.registry.keyring.api.KeyModule;
import google.registry.keyring.kms.KmsModule;
import google.registry.keyring.secretmanager.SecretManagerKeyringModule;
import google.registry.module.backend.BackendRequestComponent.BackendRequestComponentModule;
import google.registry.monitoring.whitebox.StackdriverModule;
import google.registry.persistence.PersistenceModule;
@@ -70,9 +70,9 @@ import javax.inject.Singleton;
GsonModule.class,
KeyModule.class,
KeyringModule.class,
KmsModule.class,
NetHttpTransportModule.class,
PersistenceModule.class,
SecretManagerKeyringModule.class,
SecretManagerModule.class,
ServerTridProviderModule.class,
SheetsServiceModule.class,

View File

@@ -17,6 +17,7 @@ package google.registry.module.backend;
import dagger.Module;
import dagger.Subcomponent;
import google.registry.batch.BatchModule;
import google.registry.batch.CannedScriptExecutionAction;
import google.registry.batch.DeleteExpiredDomainsAction;
import google.registry.batch.DeleteLoadTestDataAction;
import google.registry.batch.DeleteProberDataAction;
@@ -102,6 +103,8 @@ interface BackendRequestComponent {
BrdaCopyAction brdaCopyAction();
CannedScriptExecutionAction cannedScriptExecutionAction();
CopyDetailReportsAction copyDetailReportAction();
DeleteExpiredDomainsAction deleteExpiredDomainsAction();

View File

@@ -28,7 +28,7 @@ import google.registry.groups.GroupssettingsModule;
import google.registry.keyring.KeyringModule;
import google.registry.keyring.api.DummyKeyringModule;
import google.registry.keyring.api.KeyModule;
import google.registry.keyring.kms.KmsModule;
import google.registry.keyring.secretmanager.SecretManagerKeyringModule;
import google.registry.module.frontend.FrontendRequestComponent.FrontendRequestComponentModule;
import google.registry.monitoring.whitebox.StackdriverModule;
import google.registry.privileges.secretmanager.SecretManagerModule;
@@ -59,8 +59,8 @@ import javax.inject.Singleton;
GsonModule.class,
KeyModule.class,
KeyringModule.class,
KmsModule.class,
NetHttpTransportModule.class,
SecretManagerKeyringModule.class,
SecretManagerModule.class,
ServerTridProviderModule.class,
StackdriverModule.class,

View File

@@ -27,7 +27,7 @@ import google.registry.groups.GroupssettingsModule;
import google.registry.keyring.KeyringModule;
import google.registry.keyring.api.DummyKeyringModule;
import google.registry.keyring.api.KeyModule;
import google.registry.keyring.kms.KmsModule;
import google.registry.keyring.secretmanager.SecretManagerKeyringModule;
import google.registry.module.pubapi.PubApiRequestComponent.PubApiRequestComponentModule;
import google.registry.monitoring.whitebox.StackdriverModule;
import google.registry.persistence.PersistenceModule;
@@ -54,10 +54,10 @@ import javax.inject.Singleton;
GsonModule.class,
KeyModule.class,
KeyringModule.class,
KmsModule.class,
NetHttpTransportModule.class,
PersistenceModule.class,
PubApiRequestComponentModule.class,
SecretManagerKeyringModule.class,
SecretManagerModule.class,
ServerTridProviderModule.class,
StackdriverModule.class,

View File

@@ -29,7 +29,7 @@ import google.registry.groups.GroupssettingsModule;
import google.registry.keyring.KeyringModule;
import google.registry.keyring.api.DummyKeyringModule;
import google.registry.keyring.api.KeyModule;
import google.registry.keyring.kms.KmsModule;
import google.registry.keyring.secretmanager.SecretManagerKeyringModule;
import google.registry.module.tools.ToolsRequestComponent.ToolsRequestComponentModule;
import google.registry.monitoring.whitebox.StackdriverModule;
import google.registry.privileges.secretmanager.SecretManagerModule;
@@ -57,8 +57,8 @@ import javax.inject.Singleton;
GsonModule.class,
KeyModule.class,
KeyringModule.class,
KmsModule.class,
NetHttpTransportModule.class,
SecretManagerKeyringModule.class,
SecretManagerModule.class,
ServerTridProviderModule.class,
StackdriverModule.class,

View File

@@ -24,7 +24,7 @@ import com.google.monitoring.metrics.MetricWriter;
import com.google.monitoring.metrics.stackdriver.StackdriverWriter;
import dagger.Module;
import dagger.Provides;
import google.registry.config.CredentialModule.JsonCredential;
import google.registry.config.CredentialModule.ApplicationDefaultCredential;
import google.registry.config.RegistryConfig.Config;
import google.registry.util.GoogleCredentialsBundle;
import org.joda.time.Duration;
@@ -39,7 +39,7 @@ public final class StackdriverModule {
@Provides
static Monitoring provideMonitoring(
@JsonCredential GoogleCredentialsBundle credentialsBundle,
@ApplicationDefaultCredential GoogleCredentialsBundle credentialsBundle,
@Config("projectId") String projectId) {
return new Monitoring.Builder(
credentialsBundle.getHttpTransport(),

View File

@@ -1,57 +0,0 @@
// Copyright 2020 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.persistence;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import com.googlecode.objectify.Key;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.DomainHistory.DomainHistoryId;
import google.registry.model.reporting.HistoryEntry;
import java.io.Serializable;
import javax.persistence.Embeddable;
/** {@link VKey} for {@link HistoryEntry} which parent is {@link Domain}. */
@Embeddable
public class DomainHistoryVKey extends EppHistoryVKey<HistoryEntry, Domain> {
// Hibernate requires a default constructor
private DomainHistoryVKey() {}
private DomainHistoryVKey(String repoId, long historyRevisionId) {
super(repoId, historyRevisionId);
}
@Override
public Serializable createSqlKey() {
return new DomainHistoryId(repoId, historyRevisionId);
}
/** Creates {@link DomainHistoryVKey} from the given {@link Key} instance. */
public static DomainHistoryVKey create(Key<? extends HistoryEntry> ofyKey) {
checkArgumentNotNull(ofyKey, "ofyKey must be specified");
String repoId = ofyKey.getParent().getName();
long historyRevisionId = ofyKey.getId();
return new DomainHistoryVKey(repoId, historyRevisionId);
}
public VKey<? extends HistoryEntry> createDomainHistoryVKey() {
return VKey.create(
DomainHistory.class,
createSqlKey(),
Key.create(Key.create(Domain.class, repoId), DomainHistory.class, historyRevisionId));
}
}

View File

@@ -1,106 +0,0 @@
// Copyright 2020 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.persistence;
import com.google.common.base.Joiner;
import com.googlecode.objectify.Key;
import google.registry.model.EppResource;
import google.registry.model.ImmutableObject;
import google.registry.model.reporting.HistoryEntry;
import google.registry.util.TypeUtils.TypeInstantiator;
import java.io.Serializable;
import javax.annotation.Nullable;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.MappedSuperclass;
/**
* Base class for {@link VKey} which ofyKey has a {@link HistoryEntry} key as its parent and a key
* for EPP resource as its grandparent.
*
* <p>For such a {@link VKey}, we need to provide two type parameters to indicate the type of {@link
* VKey} itself and the type of EPP resource respectively.
*
* @param <K> type of the {@link VKey}
* @param <E> type of the EPP resource that the key belongs to
*/
@MappedSuperclass
@Access(AccessType.FIELD)
public abstract class EppHistoryVKey<K extends ImmutableObject, E extends EppResource>
extends ImmutableObject implements Serializable {
private static final long serialVersionUID = -3906580677709539818L;
String repoId;
Long historyRevisionId;
// Hibernate requires a default constructor.
EppHistoryVKey() {}
EppHistoryVKey(String repoId, long historyRevisionId) {
this.repoId = repoId;
this.historyRevisionId = historyRevisionId;
}
/**
* Returns the kind path for the ofyKey in this instance.
*
* <p>This method is only used reflectively by {@link EppHistoryVKeyTranslatorFactory} to get the
* kind path for a given {@link EppHistoryVKey} instance so it is marked as a private method.
*
* @see #createKindPath(Key)
*/
@SuppressWarnings("unused")
private String getKindPath() {
String eppKind = Key.getKind(new TypeInstantiator<E>(getClass()) {}.getExactType());
String keyKind = Key.getKind(new TypeInstantiator<K>(getClass()) {}.getExactType());
if (keyKind.equals(Key.getKind(HistoryEntry.class))) {
return createKindPath(eppKind, keyKind);
} else {
return createKindPath(eppKind, Key.getKind(HistoryEntry.class), keyKind);
}
}
/**
* Creates the kind path for the given ofyKey}.
*
* <p>The kind path is a string including all kind names(delimited by slash) of a hierarchical
* {@link Key}, e.g., the kind path for BillingEvent.OneTime is "Domain/HistoryEntry/OneTime".
*/
@Nullable
public static String createKindPath(@Nullable Key<?> ofyKey) {
if (ofyKey == null) {
return null;
} else if (ofyKey.getParent() == null) {
return ofyKey.getKind();
} else {
return createKindPath(createKindPath(ofyKey.getParent()), ofyKey.getKind());
}
}
private static String createKindPath(String... kinds) {
return Joiner.on("/").join(kinds);
}
/** Creates a {@link VKey} from this instance. */
@Override
public VKey<K> createVKey() {
Class<K> vKeyType = new TypeInstantiator<K>(getClass()) {}.getExactType();
return VKey.createSql(vKeyType, createSqlKey());
}
public abstract Serializable createSqlKey();
}

Some files were not shown because too many files have changed in this diff Show More