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

Compare commits

...

48 Commits

Author SHA1 Message Date
sarahcaseybot
0dee97934a Prevent creation of package domains for more than 1 year (#1786)
* Prevent creation of package domains for more than 1 year

* Fix docs test
2022-09-14 14:49:56 -04:00
gbrodman
1070173264 Load, project, and save in one txn in ResaveAERP (#1780) 2022-09-13 15:59:49 -04:00
Pavlo Tkach
b9a3c0cd96 Add dry run test for remove package token (#1782) 2022-09-13 11:20:53 -04:00
sarahcaseybot
120456d138 Increase dns update failure max retry count (#1781) 2022-09-12 16:17:31 -04:00
gbrodman
66736d52f0 Add a cookie-based OAuth2 authenticator (#1761)
This uses the GoogleIdTokenVerifier to verify ID tokens passed in
(presumably from a front end) via cookies. This isn't used anywhere yet
but it will be used for front-end API calls for the new console.
2022-09-12 15:03:05 -04:00
Lai Jiang
b159541278 Remove ofy support from ServerSecret (#1773) 2022-09-09 10:38:12 -04:00
Lai Jiang
335b229ce8 Remove ofy support from TransferData (#1775)
Also makes some changes to eliminate the use of raw types.
2022-09-08 19:25:41 -04:00
Lai Jiang
8ee0a85531 Remove ofy embedded classes (#1778) 2022-09-08 16:12:57 -04:00
gbrodman
5cbc307cd1 Add a DAO for User objects and fix up the User DB object (#1765)
First, we create a sequence of User IDs in Postgres and assign it to the
User ID field, meaning that Hibernate can autogenerate IDs.

Next, add an update timestamp.

Next, add a constraint that we can't have multiple Users with the same
email address.

Finally, create a DAO since we'll usually want to query by that email
address (at least for now).
2022-09-08 15:21:56 -04:00
Lai Jiang
bd37541b49 Remove ofy support from ForeignKeyIndex (#1777)
FKI used to be persisted in datastore to help speed up loading by foreign key.
Now it is just a helper class to do the same thing in SQL because
indexing is natively supported in SQL.
2022-09-08 13:12:02 -04:00
Lai Jiang
312bc143d5 Delete EntityGroupRoot (#1776) 2022-09-08 12:54:10 -04:00
Lai Jiang
49ade014ab Remove ofy from Lock (#1771)
<!-- Reviewable:start -->
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/google/nomulus/1771)
<!-- Reviewable:end -->
2022-09-07 17:32:03 -04:00
Lai Jiang
b8d901effe Remove ofy support from registrar (#1762)
Also fixes some warnings about the use of raw types.
2022-09-07 14:24:42 -04:00
Lai Jiang
23520048dc Remove ofy support from AllocationToken (#1770) 2022-09-07 14:22:42 -04:00
Lai Jiang
37ed6c925c Remove ofy support from RdeRevision (#1772) 2022-09-07 13:30:38 -04:00
Pavlo Tkach
17a21f3326 Update renew flow to accept and process REMOVEPACKAGE token (#1768) 2022-09-02 17:32:59 -04:00
Pavlo Tkach
f623da9948 Prohibit renewals of package domains unless REMOVEPACKAGE token is included (#1758) 2022-08-31 18:58:31 -04:00
gbrodman
ddc4a615db Fix a few DB issues with the User class (#1766)
- Create a sequence to generate IDs for the user (this allows us to have
  Long ID types so that Hibernate can autogenerate IDs)
- Add an update timestamp column so we can extend BackupGroupRoot
- Add a restriction that there can't be multiple users with the same
  email address
2022-08-31 16:09:07 -04:00
sarahcaseybot
06a1fc0022 Add a packageToken EPP extension for use in the DomainInfo flow (#1760)
* Add a packageToken EPP extension for use in the DomainInfo flow

* small fixes

* Change namespace
2022-08-30 17:50:42 -04:00
sarahcaseybot
eec272b6ba Increase max backoff seconds for dns-publish queue (#1764) 2022-08-29 16:30:56 -04:00
Ben McIlwain
3d5b52b853 Rename ContactResource -> Contact (#1763)
* Rename ContactResource -> Contact

This is a follow-up to PR #1725 and #1733. Now all EPP resource entity class
names have been rationalized to match with their SQL table names.
2022-08-29 14:48:32 -04:00
Lai Jiang
bd4af052a6 Remove ofy support from Address (#1759) 2022-08-26 12:35:48 -04:00
Pavlo Tkach
78249e1329 Replace PubApi master calls with replica (#1742) 2022-08-26 10:15:30 -04:00
gbrodman
7aec579d96 Add DB annotations to console User and related classes (#1757)
We added the DB code last week, this is the corresponding bit now that
that has been released.
2022-08-25 16:54:39 -04:00
Lai Jiang
b9f8faa165 Drop autorenew poll message history id column from the domain table (#1743)
We stopped using the column since #1732.
2022-08-25 15:52:32 -04:00
Pavlo Tkach
b0e4e86586 Add registry email to bcc for outgoing DNS failure emails (#1755) 2022-08-25 14:15:20 -04:00
gbrodman
3412f4417f Allow UserAuthInfo to contain either old GAE Users or new console Users (#1744)
This means that LegacyAuthenticationMechanism or a to-be-created
OAuth2AuthenticationMechanism) can return a UserAuthInfo object that
contains either the GAE User or the console User as appropriate. The
goal is that the non-auth flows shouldn't have to know about which user
type it is. Note: the registry lock flow (for now) needs to know about
the separate types of auth because it is a separate level of auth from
the standard AuthenticatedRegistrarAccessor.

The AuthenticatedRegistrarAccessor code is a bit odd because the new
role system doesn't quite fit neatly into the old registrar ->
OWNER,ADMIN system but this is a fine approximation. Basically, any
new registrar role will map to the old OWNER role.
2022-08-24 14:18:32 -04:00
sarahcaseybot
db6329a070 Add the PackagePromotion table (#1745)
* Add the PackagePromotion table

* Add long id

* Add NOT NULL

* fix formatting

* make package price non null

* Add not nulls to java file

* Fix broken tests from merge conflicts
2022-08-24 14:16:34 -04:00
gbrodman
02af277148 Allow usage of allocation tokens in nomulus create_domain (#1756)
Useful when doing internal registrations like get.boo
2022-08-24 13:18:53 -04:00
sarahcaseybot
8b02f76ae9 Add currentPackageToken on create flow (#1751)
* Add currentPackageToken on create flow

* Change to Truth8 assertion

* Add check for specified renewal behavior
2022-08-23 14:47:41 -04:00
gbrodman
6dd96c247a Reset the claims list cache in any test that saves to it (#1754) 2022-08-22 15:58:45 -04:00
gbrodman
919c744d8c Update currently-active ICANN-provided SMD test file (#1753)
The test files they provided before have expired, and they only provide
one valid currently-active test file now, so only test that one.

The test files are located at https://newgtlds.icann.org/en/about/trademark-clearinghouse/registries-registrars
2022-08-22 13:59:38 -04:00
gbrodman
5bccd65bd7 Add main method to ResaveAllEppResourcesPipeline (#1748)
Not sure how this got missed before, I am pretty sure we tested this on
alpha.
2022-08-22 12:39:34 -04:00
Lai Jiang
5268e35155 Remove redundant test extension (#1752)
This extension field is already defined in the super class.

<!-- Reviewable:start -->
- - -
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/google/nomulus/1752)
<!-- Reviewable:end -->
2022-08-21 12:15:15 -04:00
Lai Jiang
6e201450f0 Remove ofy support from PollMessage (#1732)
Also deletes the autorenew poll message history revision id field in
Domain, which is only needed to recreate the ofy key for the poll
message. The column already contains null values in it, making it
impossible to depend on it. The column itself will be deleted from the
schema after this PR is deployed.

The logic to update autorenew recurrence end time is changed
accordingly: When a poll message already exists, we simply update the
endtime, but when it no longer exists, i. e. when it's deleted
speculatively after a transfer request, we recreate one using the
history entry id that resulted in its creation (e. g. cancelled or rejected
transfer).

This should fix b/240984498. Though the exact reason for that bug is
still unclear to me. Namely, it throws an NPE at this line during an
explicit domain transfer approval:

https://cs.opensource.google/nomulus/nomulus/+/master:core/src/main/java/google/registry/flows/domain/DomainFlowUtils.java;l=603;bpv=1;bpt=0;drc=ede919d7dcdb7f209b074563b3d449ebee19118a

The domain in question has a null autorenewPollMessageHistoryId, but
that in itself should not have caused an NPE because we are not
operating on the null pointer. On that line the only possible way to
throw an NPE is for the domain itself to be null, but if that were the
case, the NPE would have been thrown at line 599 where we called a
method on the domain object.

Regardless of the cause, with this PR we are using an explicitly
provided history id and checking for its nullness before using it. If a
similar issue arises again, we should have a better idea why.

Lastly, the way poll message id is constructed is largely simplified in
PollMessageExternalKeyConverter as a result of the removal ofy parent
keys in PollMessage. This does present a possibility of failure when
immediately before deployment, a registrar requests a poll message and
received the old id, but by the time the registrar acks the id, the new
version is deployed and therefore does not recognize the old key. The
likelihood of this happening should be slim, and we could have prevented
it by letting the converter recognize both the old and the new key.
However, we would like to eventually phase out the old key, and in
theory a registrar could ack a poll message at any time after it was
requested. So, there is not a safe time by which all the old ids are
acked, lest we develop some elaborate scheme to keep track of which
messages were sent with an old id when requested and which of these old
ids are acked. Only then can we be truly safe to phase out the old id.
The benefit does not seem to warrant the effort. If a registrar does
encounter a situation like this, they could open a support bug to have
us manually ack the poll message for them.
2022-08-19 14:24:03 -04:00
sarahcaseybot
dda9a3ef7e Flyway files for PackagePromotion table (#1746)
* Include missing file

* Fix merge conflicts

* make package price non null
2022-08-19 12:53:58 -04:00
gbrodman
8c1fb6bf00 Add another TestCacheExtension usage (#1750) 2022-08-19 10:17:15 -04:00
gbrodman
4e21152f04 Add TestCacheExtension in ResourceFlowTC to fix flakes (#1749)
Basically, what's happening here is that some flow tests are adding
things to the claims list cache which is stored statically, meaning that
some other tests can pick those up when they shouldn't. By adding the
extension in RFTC, it'll clear out the caches after each test.
2022-08-18 15:04:29 -04:00
gbrodman
22193474d5 Add Flyway and golden files for console User object (#1747) 2022-08-17 16:48:14 -04:00
Pavlo Tkach
efd5244ebd Add email notification when DNS update fails (#1734) 2022-08-16 12:59:08 -04:00
gbrodman
87e5d19fe5 Allow anchor tenant creation via allocation token behavior (#1735)
* Allow anchor tenant creation via allocation token behavior

This also enforces that non-superusers cannot create registrations on
trademarked names prior to the sunrise period, even if they have an
allocation token with ANCHOR_TENANT behavior.
2022-08-15 12:42:16 -04:00
Lai Jiang
bbb6174c9f Remove InjectExtension (#1739)
It is only used to set the clock used by Ofy, and it admits itself being
an ugly hack...

Also applied IntelliJ suggestions on touched files.
2022-08-12 14:56:53 -04:00
gbrodman
2b826651e6 Create a registry lock permission and corresponding account manager role (#1740)
* Create a registry lock permission and corresponding account manager role

This allows us to distinguish between standard account managers and
users that might have the registry lock permission. This will make the
registry lock password-setting flow easier (user can reset their
password iff they have the REGISTRY_LOCK permission, instead of having a
separate boolean) and allows us to easily determine whether or not a
user should have access to registry lock views in the UI.
2022-08-12 12:18:09 -04:00
Lai Jiang
e4132db8ed Delete SetClockExtension (#1738)
We no longer write to commit logs, and the ReplayExtension that this
extension is supposed to be used with is already deleted.
2022-08-11 13:16:30 -04:00
Pavlo Tkach
45d90e7c68 Extend IP validation test with message verification (#1736) 2022-08-10 13:27:55 -04:00
sarahcaseybot
028005906a Add allocation token to transfer command (#1737) 2022-08-09 15:18:23 -04:00
sarahcaseybot
78d78e21cb Add CurrentPackageToken to Domain table (#1720)
* Add allocation token to Domain table

* Add tests

* Change column name

* change test names
2022-08-09 11:23:21 -04:00
Lai Jiang
2f3ac2e43b Remove unused columns in BillingCancellation (#1721)
We stopped using these columns after BillingVKey is removed in
https://github.com/google/nomulus/pull/1710.
2022-08-08 10:28:30 -04:00
337 changed files with 9203 additions and 7531 deletions

View File

@@ -61,12 +61,6 @@ by Joshua Bloch in his book Effective Java -->
<property name="message" value="Use assertThrows and expectThrows from JUnitBackports instead of the deprecated methods on ExpectedException."/>
</module>
<!-- Checks that the deprecated MockitoJUnitRunner is not used. -->
<module name="RegexpSingleline">
<property name="format" value="MockitoJUnitRunner"/>
<property name="message" value="MockitoJUnitRunner is deprecated. Use @RunWith(JUnit4.class) and MockitoRule instead."/>
</module>
<module name="LineLength">
<!-- Checks if a line is too long. -->
<property name="max" value="${com.puppycrawl.tools.checkstyle.checks.sizes.LineLength.max}" default="100"/>

View File

@@ -29,7 +29,7 @@ import google.registry.config.RegistryEnvironment;
import google.registry.flows.poll.PollFlowUtils;
import google.registry.model.EppResource;
import google.registry.model.EppResourceUtils;
import google.registry.model.contact.ContactResource;
import google.registry.model.contact.Contact;
import google.registry.model.domain.Domain;
import google.registry.model.host.Host;
import google.registry.model.poll.PollMessage;
@@ -43,8 +43,8 @@ import google.registry.util.Clock;
import javax.inject.Inject;
/**
* Hard deletes load-test ContactResources, Hosts, their subordinate history entries, and the
* associated ForeignKey and EppResourceIndex entities.
* Hard deletes load-test Contacts, Hosts, their subordinate history entries, and the associated
* ForeignKey and EppResourceIndex entities.
*
* <p>This only deletes contacts and hosts, NOT domains. To delete domains, use {@link
* DeleteProberDataAction} and pass it the TLD(s) that the load test domains were created on. Note
@@ -92,7 +92,7 @@ public class DeleteLoadTestDataAction implements Runnable {
tm().transact(
() -> {
LOAD_TEST_REGISTRARS.forEach(this::deletePollMessages);
tm().loadAllOfStream(ContactResource.class).forEach(this::deleteContact);
tm().loadAllOfStream(Contact.class).forEach(this::deleteContact);
tm().loadAllOfStream(Host.class).forEach(this::deleteHost);
});
}
@@ -108,7 +108,7 @@ public class DeleteLoadTestDataAction implements Runnable {
}
}
private void deleteContact(ContactResource contact) {
private void deleteContact(Contact contact) {
if (!LOAD_TEST_REGISTRARS.contains(contact.getPersistedCurrentSponsorRegistrarId())) {
return;
}

View File

@@ -19,7 +19,6 @@ import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static google.registry.batch.BatchModule.PARAM_DRY_RUN;
import static google.registry.config.RegistryEnvironment.PRODUCTION;
import static google.registry.model.ResourceTransferUtils.updateForeignKeyIndexDeletionTime;
import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_DELETE;
import static google.registry.model.tld.Registries.getTldsOfType;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
@@ -267,8 +266,6 @@ public class DeleteProberDataAction implements Runnable {
// messages, or auto-renews because those will all be hard-deleted the next time the job runs
// anyway.
tm().putAll(ImmutableList.of(deletedDomain, historyEntry));
// updating foreign keys is a no-op in SQL
updateForeignKeyIndexDeletionTime(deletedDomain);
dnsQueue.addDomainRefreshTask(deletedDomain.getDomainName());
}
}

View File

@@ -17,7 +17,7 @@ package google.registry.beam.common;
import static com.google.common.base.Verify.verify;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import google.registry.model.contact.ContactResource;
import google.registry.model.contact.Contact;
import google.registry.persistence.transaction.CriteriaQueryBuilder;
import google.registry.persistence.transaction.JpaTransactionManager;
import java.io.Serializable;
@@ -46,8 +46,7 @@ public class JpaDemoPipeline implements Serializable {
.apply(
"Read contacts",
RegistryJpaIO.read(
() -> CriteriaQueryBuilder.create(ContactResource.class).build(),
ContactResource::getRepoId))
() -> CriteriaQueryBuilder.create(Contact.class).build(), Contact::getRepoId))
.apply(
"Count Contacts",
ParDo.of(

View File

@@ -45,8 +45,8 @@ import google.registry.config.CredentialModule;
import google.registry.config.RegistryConfig.ConfigModule;
import google.registry.gcs.GcsUtils;
import google.registry.model.EppResource;
import google.registry.model.contact.Contact;
import google.registry.model.contact.ContactHistory;
import google.registry.model.contact.ContactResource;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainHistory;
import google.registry.model.host.Host;
@@ -140,7 +140,7 @@ import org.joda.time.DateTime;
* pairs of (contact/host repo ID: pending deposit) for all RDE pending deposits for further
* processing.
*
* <h3>{@link ContactResource}</h3>
* <h3>{@link Contact}</h3>
*
* We first join most recent contact histories, represented by (contact repo ID: contact history
* revision ID) pairs, with referenced contacts, represented by (contact repo ID: pending deposit)
@@ -150,13 +150,13 @@ import org.joda.time.DateTime;
*
* <h3>{@link Host}</h3>
*
* Similar to {@link ContactResource}, we join the most recent host history with referenced hosts to
* find most recent referenced hosts. For external hosts we do the same treatment as we did on
* contacts and obtain the (pending deposit: deposit fragment) pairs. For subordinate hosts, we need
* to find the superordinate domain in order to properly handle pending transfer in the deposit as
* well. So we first find the superordinate domain repo ID from the host and join the (superordinate
* domain repo ID: (subordinate host repo ID: (pending deposit: revision ID))) pair with the (domain
* repo ID: revision ID) pair obtained from the domain history query in order to map the host at
* Similar to {@link Contact}, we join the most recent host history with referenced hosts to find
* most recent referenced hosts. For external hosts we do the same treatment as we did on contacts
* and obtain the (pending deposit: deposit fragment) pairs. For subordinate hosts, we need to find
* the superordinate domain in order to properly handle pending transfer in the deposit as well. So
* we first find the superordinate domain repo ID from the host and join the (superordinate domain
* repo ID: (subordinate host repo ID: (pending deposit: revision ID))) pair with the (domain repo
* ID: revision ID) pair obtained from the domain history query in order to map the host at
* watermark to the domain at watermark. We then proceed to create the (pending deposit: deposit
* fragment) pair for subordinate hosts using the added domain information.
*
@@ -466,10 +466,10 @@ public class RdePipeline implements Serializable {
private PCollectionTuple processDomainHistories(PCollection<KV<String, Long>> domainHistories) {
Counter activeDomainCounter = Metrics.counter("RDE", "ActiveDomainBase");
Counter domainFragmentCounter = Metrics.counter("RDE", "DomainFragment");
Counter referencedContactCounter = Metrics.counter("RDE", "ReferencedContactResource");
Counter referencedContactCounter = Metrics.counter("RDE", "ReferencedContact");
Counter referencedHostCounter = Metrics.counter("RDE", "ReferencedHost");
return domainHistories.apply(
"Map DomainHistory to DepositFragment " + "and emit referenced ContactResource and Host",
"Map DomainHistory to DepositFragment " + "and emit referenced Contact and Host",
ParDo.of(
new DoFn<KV<String, Long>, KV<PendingDeposit, DepositFragment>>() {
@ProcessElement
@@ -533,17 +533,17 @@ public class RdePipeline implements Serializable {
PCollection<KV<String, PendingDeposit>> referencedContacts,
PCollection<KV<String, Long>> contactHistories) {
Counter contactFragmentCounter = Metrics.counter("RDE", "ContactFragment");
return removeUnreferencedResource(referencedContacts, contactHistories, ContactResource.class)
return removeUnreferencedResource(referencedContacts, contactHistories, Contact.class)
.apply(
"Map ContactResource to DepositFragment",
"Map Contact to DepositFragment",
FlatMapElements.into(
kvs(
TypeDescriptor.of(PendingDeposit.class),
TypeDescriptor.of(DepositFragment.class)))
.via(
(KV<String, CoGbkResult> kv) -> {
ContactResource contact =
(ContactResource)
Contact contact =
(Contact)
loadResourceByHistoryEntryId(
ContactHistory.class,
kv.getKey(),

View File

@@ -14,25 +14,29 @@
package google.registry.beam.resave;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static org.apache.beam.sdk.values.TypeDescriptors.integers;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Streams;
import google.registry.beam.common.RegistryJpaIO;
import google.registry.beam.common.RegistryJpaIO.Read;
import google.registry.model.EppResource;
import google.registry.model.contact.ContactResource;
import google.registry.model.contact.Contact;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainBase;
import google.registry.model.host.Host;
import google.registry.persistence.PersistenceModule.TransactionIsolationLevel;
import google.registry.persistence.transaction.CriteriaQueryBuilder;
import google.registry.persistence.VKey;
import google.registry.util.DateTimeUtils;
import java.io.Serializable;
import java.util.concurrent.ThreadLocalRandom;
import org.apache.beam.sdk.Pipeline;
import org.apache.beam.sdk.PipelineResult;
import org.apache.beam.sdk.options.PipelineOptionsFactory;
import org.apache.beam.sdk.transforms.DoFn;
import org.apache.beam.sdk.transforms.GroupIntoBatches;
import org.apache.beam.sdk.transforms.ParDo;
@@ -52,7 +56,7 @@ import org.joda.time.DateTime;
public class ResaveAllEppResourcesPipeline implements Serializable {
private static final ImmutableSet<Class<? extends EppResource>> EPP_RESOURCE_CLASSES =
ImmutableSet.of(ContactResource.class, Domain.class, Host.class);
ImmutableSet.of(Contact.class, Domain.class, Host.class);
/**
* There exist three possible situations where we know we'll want to project domains to the
@@ -68,7 +72,7 @@ public class ResaveAllEppResourcesPipeline implements Serializable {
* multiple times, and to avoid projecting and resaving the same domain multiple times.
*/
private static final String DOMAINS_TO_PROJECT_QUERY =
"FROM Domain d WHERE (d.transferData.transferStatus = 'PENDING' AND"
"SELECT repoId FROM Domain d WHERE (d.transferData.transferStatus = 'PENDING' AND"
+ " d.transferData.pendingTransferExpirationTime < current_timestamp()) OR"
+ " (d.registrationExpirationTime < current_timestamp() AND d.deletionTime ="
+ " (:END_OF_TIME)) OR (EXISTS (SELECT 1 FROM GracePeriod gp WHERE gp.domainRepoId ="
@@ -98,13 +102,13 @@ public class ResaveAllEppResourcesPipeline implements Serializable {
/** Projects to the current time and saves any contacts with expired transfers. */
private void fastResaveContacts(Pipeline pipeline) {
Read<ContactResource, ContactResource> read =
Read<String, String> repoIdRead =
RegistryJpaIO.read(
"FROM Contact WHERE transferData.transferStatus = 'PENDING' AND"
"SELECT repoId FROM Contact WHERE transferData.transferStatus = 'PENDING' AND"
+ " transferData.pendingTransferExpirationTime < current_timestamp()",
ContactResource.class,
c -> c);
projectAndResaveResources(pipeline, ContactResource.class, read);
String.class,
r -> r);
projectAndResaveResources(pipeline, Contact.class, repoIdRead);
}
/**
@@ -115,61 +119,81 @@ public class ResaveAllEppResourcesPipeline implements Serializable {
* DomainBase#cloneProjectedAtTime(DateTime)}.
*/
private void fastResaveDomains(Pipeline pipeline) {
Read<Domain, Domain> read =
Read<String, String> repoIdRead =
RegistryJpaIO.read(
DOMAINS_TO_PROJECT_QUERY,
ImmutableMap.of("END_OF_TIME", DateTimeUtils.END_OF_TIME),
Domain.class,
d -> d);
projectAndResaveResources(pipeline, Domain.class, read);
String.class,
r -> r);
projectAndResaveResources(pipeline, Domain.class, repoIdRead);
}
/** Projects all resources to the current time and saves them. */
private <T extends EppResource> void forceResaveAllResources(Pipeline pipeline, Class<T> clazz) {
Read<T, T> read = RegistryJpaIO.read(() -> CriteriaQueryBuilder.create(clazz).build());
projectAndResaveResources(pipeline, clazz, read);
Read<String, String> repoIdRead =
RegistryJpaIO.read(
// Note: cannot use SQL parameters for the table name
String.format("SELECT repoId FROM %s", clazz.getSimpleName()), String.class, r -> r);
projectAndResaveResources(pipeline, clazz, repoIdRead);
}
/** Projects and re-saves the result of the provided {@link Read}. */
/** Projects and re-saves all resources with repo IDs provided by the {@link Read}. */
private <T extends EppResource> void projectAndResaveResources(
Pipeline pipeline, Class<T> clazz, Read<?, T> read) {
Pipeline pipeline, Class<T> clazz, Read<?, String> repoIdRead) {
int numShards = options.getSqlWriteShards();
int batchSize = options.getSqlWriteBatchSize();
String className = clazz.getSimpleName();
pipeline
.apply("Read " + className, read)
.apply("Read " + className, repoIdRead)
.apply(
"Shard data for class" + className,
WithKeys.<Integer, T>of(e -> ThreadLocalRandom.current().nextInt(numShards))
WithKeys.<Integer, String>of(e -> ThreadLocalRandom.current().nextInt(numShards))
.withKeyType(integers()))
.apply(
"Group into batches for class" + className,
GroupIntoBatches.<Integer, T>ofSize(batchSize).withShardedKey())
.apply("Map " + className + " to now", ParDo.of(new BatchedProjectionFunction<>()))
GroupIntoBatches.<Integer, String>ofSize(batchSize).withShardedKey())
.apply(
"Write transformed " + className,
RegistryJpaIO.<EppResource>write()
.withName("Write transformed " + className)
.withBatchSize(batchSize)
.withShards(numShards));
"Load, map, and save " + className,
ParDo.of(new BatchedLoadProjectAndSaveFunction(clazz)));
}
private static class BatchedProjectionFunction<T extends EppResource>
extends DoFn<KV<ShardedKey<Integer>, Iterable<T>>, EppResource> {
/** Function that loads, projects, and saves resources all in the same transaction. */
private static class BatchedLoadProjectAndSaveFunction
extends DoFn<KV<ShardedKey<Integer>, Iterable<String>>, Void> {
private final Class<? extends EppResource> clazz;
private BatchedLoadProjectAndSaveFunction(Class<? extends EppResource> clazz) {
this.clazz = clazz;
}
@ProcessElement
public void processElement(
@Element KV<ShardedKey<Integer>, Iterable<T>> element,
OutputReceiver<EppResource> outputReceiver) {
@Element KV<ShardedKey<Integer>, Iterable<String>> element,
OutputReceiver<Void> outputReceiver) {
jpaTm()
.transact(
() ->
element
.getValue()
.forEach(
resource ->
outputReceiver.output(
resource.cloneProjectedAtTime(jpaTm().getTransactionTime()))));
() -> {
DateTime now = jpaTm().getTransactionTime();
ImmutableList<VKey<? extends EppResource>> keys =
Streams.stream(element.getValue())
.map(repoId -> VKey.create(clazz, repoId))
.collect(toImmutableList());
ImmutableList<EppResource> mappedResources =
jpaTm().loadByKeys(keys).values().stream()
.map(r -> r.cloneProjectedAtTime(now))
.collect(toImmutableList());
jpaTm().putAll(mappedResources);
});
}
}
public static void main(String[] args) {
PipelineOptionsFactory.register(ResaveAllEppResourcesPipelineOptions.class);
ResaveAllEppResourcesPipelineOptions options =
PipelineOptionsFactory.fromArgs(args)
.withValidation()
.as(ResaveAllEppResourcesPipelineOptions.class);
new ResaveAllEppResourcesPipeline(options).run();
}
}

View File

@@ -1300,6 +1300,36 @@ public final class RegistryConfig {
return config.sslCertificateValidation.expirationWarningEmailSubjectText;
}
@Provides
@Config("dnsUpdateFailEmailSubjectText")
public static String provideDnsUpdateFailEmailSubjectText(RegistryConfigSettings config) {
return config.dnsUpdate.dnsUpdateFailEmailSubjectText;
}
@Provides
@Config("dnsUpdateFailEmailBodyText")
public static String provideDnsUpdateFailEmailBodyText(RegistryConfigSettings config) {
return config.dnsUpdate.dnsUpdateFailEmailBodyText;
}
@Provides
@Config("dnsUpdateFailRegistryName")
public static String provideDnsUpdateFailRegistryName(RegistryConfigSettings config) {
return config.dnsUpdate.dnsUpdateFailRegistryName;
}
@Provides
@Config("registrySupportEmail")
public static InternetAddress provideRegistrySupportEmail(RegistryConfigSettings config) {
return parseEmailAddress(config.dnsUpdate.registrySupportEmail);
}
@Provides
@Config("registryCcEmail")
public static InternetAddress provideRegistryCcEmail(RegistryConfigSettings config) {
return parseEmailAddress(config.dnsUpdate.registryCcEmail);
}
@Provides
@Config("allowedEcdsaCurves")
public static ImmutableSet<String> provideAllowedEcdsaCurves(RegistryConfigSettings config) {

View File

@@ -42,6 +42,7 @@ public class RegistryConfigSettings {
public RegistryTool registryTool;
public SslCertificateValidation sslCertificateValidation;
public ContactHistory contactHistory;
public DnsUpdate dnsUpdate;
/** Configuration options that apply to the entire App Engine project. */
public static class AppEngine {
@@ -246,4 +247,13 @@ public class RegistryConfigSettings {
public int minMonthsBeforeWipeOut;
public int wipeOutQueryBatchSize;
}
/** Configuration for dns update. */
public static class DnsUpdate {
public String dnsUpdateFailEmailSubjectText;
public String dnsUpdateFailEmailBodyText;
public String dnsUpdateFailRegistryName;
public String registrySupportEmail;
public String registryCcEmail;
}
}

View File

@@ -477,6 +477,29 @@ contactHistory:
# The batch size for querying ContactHistory table in the database.
wipeOutQueryBatchSize: 500
# Configuration options relevant to the DNS update functionality.
dnsUpdate:
dnsUpdateFailRegistryName: Example name
registrySupportEmail: email@example.com
registryCcEmail: email@example.com
# Email subject text template to notify partners after repeatedly failing DNS update
dnsUpdateFailEmailSubjectText: "[ACTION REQUIRED]: Incomplete DNS Update"
# Email body text template for failing DNS update that accepts 5 parameters:
# registrar name, domain or host address, 'domain' or 'host' as a string that failed,
# registry support email (see dnsUpdateFailRegistrySupportEmail) and registry display name
dnsUpdateFailEmailBodyText: >
Dear %1$s,
We are contacting you regarding the changes you recently made to one of your %2$ss.
The DNS update for the %3$s %2$s failed to process. Please review your %2$s's DNS records
and ensure that it is valid before trying another update.
If you have any questions or require additional support, please contact us
at %4$s.
Regards,
%5$s
# Configuration options for checking SSL certificates.
sslCertificateValidation:
# A map specifying the maximum amount of days the certificate can be valid.

View File

@@ -14,6 +14,7 @@
package google.registry.dns;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.dns.DnsConstants.DNS_PUBLISH_PUSH_QUEUE_NAME;
import static google.registry.dns.DnsModule.PARAM_DNS_WRITER;
import static google.registry.dns.DnsModule.PARAM_DOMAINS;
@@ -22,6 +23,7 @@ import static google.registry.dns.DnsModule.PARAM_LOCK_INDEX;
import static google.registry.dns.DnsModule.PARAM_NUM_PUBLISH_LOCKS;
import static google.registry.dns.DnsModule.PARAM_PUBLISH_TASK_ENQUEUED;
import static google.registry.dns.DnsModule.PARAM_REFRESH_REQUEST_CREATED;
import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.request.Action.Method.POST;
import static google.registry.request.RequestParameters.PARAM_TLD;
import static google.registry.util.CollectionUtils.nullToEmpty;
@@ -32,11 +34,16 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.flogger.FluentLogger;
import com.google.common.net.InternetDomainName;
import dagger.Lazy;
import google.registry.config.RegistryConfig.Config;
import google.registry.dns.DnsMetrics.ActionStatus;
import google.registry.dns.DnsMetrics.CommitStatus;
import google.registry.dns.DnsMetrics.PublishStatus;
import google.registry.dns.writer.DnsWriter;
import google.registry.model.domain.Domain;
import google.registry.model.host.Host;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarPoc;
import google.registry.model.tld.Registry;
import google.registry.request.Action;
import google.registry.request.Action.Service;
@@ -49,10 +56,13 @@ import google.registry.request.lock.LockHandler;
import google.registry.util.Clock;
import google.registry.util.CloudTasksUtils;
import google.registry.util.DomainNameUtils;
import google.registry.util.EmailMessage;
import google.registry.util.SendEmailService;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Callable;
import javax.inject.Inject;
import javax.mail.internet.InternetAddress;
import org.joda.time.DateTime;
import org.joda.time.Duration;
@@ -71,7 +81,7 @@ public final class PublishDnsUpdatesAction implements Runnable, Callable<Void> {
// tasks.
public static final String APP_ENGINE_RETRY_HEADER = "X-AppEngine-TaskRetryCount";
public static final String CLOUD_TASKS_RETRY_HEADER = "X-CloudTasks-TaskRetryCount";
public static final int RETRIES_BEFORE_PERMANENT_FAILURE = 10;
public static final int RETRIES_BEFORE_PERMANENT_FAILURE = 20;
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -102,6 +112,13 @@ public final class PublishDnsUpdatesAction implements Runnable, Callable<Void> {
private final Clock clock;
private final CloudTasksUtils cloudTasksUtils;
private final Response response;
private final SendEmailService sendEmailService;
private final String dnsUpdateFailEmailSubjectText;
private final String dnsUpdateFailEmailBodyText;
private final String dnsUpdateFailRegistryName;
private final Lazy<InternetAddress> registrySupportEmail;
private final Lazy<InternetAddress> registryCcEmail;
private final InternetAddress gSuiteOutgoingEmailAddress;
@Inject
public PublishDnsUpdatesAction(
@@ -114,6 +131,12 @@ public final class PublishDnsUpdatesAction implements Runnable, Callable<Void> {
@Parameter(PARAM_HOSTS) Set<String> hosts,
@Parameter(PARAM_TLD) String tld,
@Config("publishDnsUpdatesLockDuration") Duration timeout,
@Config("dnsUpdateFailEmailSubjectText") String dnsUpdateFailEmailSubjectText,
@Config("dnsUpdateFailEmailBodyText") String dnsUpdateFailEmailBodyText,
@Config("dnsUpdateFailRegistryName") String dnsUpdateFailRegistryName,
@Config("registrySupportEmail") Lazy<InternetAddress> registrySupportEmail,
@Config("registryCcEmail") Lazy<InternetAddress> registryCcEmail,
@Config("gSuiteOutgoingEmailAddress") InternetAddress gSuiteOutgoingEmailAddress,
@Header(APP_ENGINE_RETRY_HEADER) Optional<Integer> appEngineRetryCount,
@Header(CLOUD_TASKS_RETRY_HEADER) Optional<Integer> cloudTasksRetryCount,
DnsQueue dnsQueue,
@@ -122,11 +145,13 @@ public final class PublishDnsUpdatesAction implements Runnable, Callable<Void> {
LockHandler lockHandler,
Clock clock,
CloudTasksUtils cloudTasksUtils,
SendEmailService sendEmailService,
Response response) {
this.dnsQueue = dnsQueue;
this.dnsWriterProxy = dnsWriterProxy;
this.dnsMetrics = dnsMetrics;
this.timeout = timeout;
this.sendEmailService = sendEmailService;
this.retryCount =
cloudTasksRetryCount.orElse(
appEngineRetryCount.orElseThrow(
@@ -143,6 +168,12 @@ public final class PublishDnsUpdatesAction implements Runnable, Callable<Void> {
this.clock = clock;
this.cloudTasksUtils = cloudTasksUtils;
this.response = response;
this.dnsUpdateFailEmailBodyText = dnsUpdateFailEmailBodyText;
this.dnsUpdateFailEmailSubjectText = dnsUpdateFailEmailSubjectText;
this.dnsUpdateFailRegistryName = dnsUpdateFailRegistryName;
this.registrySupportEmail = registrySupportEmail;
this.registryCcEmail = registryCcEmail;
this.gSuiteOutgoingEmailAddress = gSuiteOutgoingEmailAddress;
}
private void recordActionResult(ActionStatus status) {
@@ -209,9 +240,35 @@ public final class PublishDnsUpdatesAction implements Runnable, Callable<Void> {
} else if (retryCount < RETRIES_BEFORE_PERMANENT_FAILURE) {
// If the batch only contains 1 name, allow it more retries
throw e;
} else {
// By the time we get here there's either single domain or a single host
domains.stream()
.findFirst()
.ifPresent(
dn -> {
Optional<Domain> domain = loadByForeignKey(Domain.class, dn, clock.nowUtc());
if (domain.isPresent()) {
notifyWithEmailAboutDnsUpdateFailure(
domain.get().getCurrentSponsorRegistrarId(), dn, false);
} else {
logger.atSevere().log(String.format("Domain entity for %s not found", dn));
}
});
hosts.stream()
.findFirst()
.ifPresent(
hn -> {
Optional<Host> host = loadByForeignKey(Host.class, hn, clock.nowUtc());
if (host.isPresent()) {
notifyWithEmailAboutDnsUpdateFailure(
host.get().getPersistedCurrentSponsorRegistrarId(), hn, true);
} else {
logger.atSevere().log(String.format("Host entity for %s not found", hn));
}
});
}
// If we get here, we should terminate this task as it is likely a perpetually failing task.
// TODO(b/237302821): Send an email notifying partner the dns update failed
recordActionResult(ActionStatus.MAX_RETRIES_EXCEEDED);
response.setStatus(SC_ACCEPTED);
logger.atSevere().withCause(e).log("Terminated task after too many retries");
@@ -219,6 +276,53 @@ public final class PublishDnsUpdatesAction implements Runnable, Callable<Void> {
return null;
}
private InternetAddress emailToInternetAddress(String email) {
try {
return new InternetAddress(email, true);
} catch (Exception e) {
logger.atWarning().withCause(e).log(
String.format(
"Could not parse email contact %s to send DNS failure notification", email));
return null;
}
}
/** Sends an email to partners regarding a failure during DNS update */
private void notifyWithEmailAboutDnsUpdateFailure(
String registrarId, String hostOrDomainName, Boolean isHost) {
Optional<Registrar> registrar = Registrar.loadByRegistrarIdCached(registrarId);
if (registrar.isPresent()) {
String body =
String.format(
dnsUpdateFailEmailBodyText,
registrar.get().getRegistrarName(),
hostOrDomainName,
isHost ? "host" : "domain",
registrySupportEmail.get().getAddress(),
dnsUpdateFailRegistryName);
ImmutableList<InternetAddress> recipients =
registrar.get().getContacts().stream()
.filter(c -> c.getTypes().contains(RegistrarPoc.Type.ADMIN))
.map(RegistrarPoc::getEmailAddress)
.map(this::emailToInternetAddress)
.collect(toImmutableList());
sendEmailService.sendEmail(
EmailMessage.newBuilder()
.setBody(body)
.setSubject(dnsUpdateFailEmailSubjectText)
.setRecipients(recipients)
.addBcc(registryCcEmail.get())
.setFrom(gSuiteOutgoingEmailAddress)
.build());
} else {
logger.atSevere().log(String.format("Could not find registrar %s", registrarId));
}
}
/** Splits the domains and hosts in a batch into smaller batches and adds them to the queue. */
private void splitBatch() {
ImmutableList<String> domainList = ImmutableList.copyOf(domains);
@@ -294,8 +398,7 @@ public final class PublishDnsUpdatesAction implements Runnable, Callable<Void> {
int domainsPublished = 0;
int domainsRejected = 0;
for (String domain : nullToEmpty(domains)) {
if (!DomainNameUtils.isUnder(
InternetDomainName.from(domain), InternetDomainName.from(tld))) {
if (!DomainNameUtils.isUnder(InternetDomainName.from(domain), InternetDomainName.from(tld))) {
logger.atSevere().log("%s: skipping domain %s not under TLD.", tld, domain);
domainsRejected += 1;
} else {
@@ -310,8 +413,7 @@ public final class PublishDnsUpdatesAction implements Runnable, Callable<Void> {
int hostsPublished = 0;
int hostsRejected = 0;
for (String host : nullToEmpty(hosts)) {
if (!DomainNameUtils.isUnder(
InternetDomainName.from(host), InternetDomainName.from(tld))) {
if (!DomainNameUtils.isUnder(InternetDomainName.from(host), InternetDomainName.from(tld))) {
logger.atSevere().log("%s: skipping host %s not under TLD.", tld, host);
hostsRejected += 1;
} else {

View File

@@ -1,6 +1,6 @@
<datastore-indexes autoGenerate="false">
<!-- For finding contact resources by registrar. -->
<datastore-index kind="ContactResource" ancestor="false" source="manual">
<datastore-index kind="Contact" ancestor="false" source="manual">
<property name="currentSponsorClientId" direction="asc"/>
<property name="deletionTime" direction="asc"/>
<property name="searchName" direction="asc"/>
@@ -91,7 +91,7 @@
<property name="deletionTime" direction="asc"/>
<property name="fullyQualifiedHostName" direction="asc"/>
</datastore-index>
<datastore-index kind="ContactResource" ancestor="false" source="manual">
<datastore-index kind="Contact" ancestor="false" source="manual">
<property name="deletionTime" direction="asc"/>
<property name="searchName" direction="asc"/>
</datastore-index>

View File

@@ -10,10 +10,10 @@
<name>dns-publish</name>
<rate>100/s</rate>
<bucket-size>100</bucket-size>
<!-- 30 sec backoff increasing linearly up to 10 minutes. -->
<!-- 30 sec backoff increasing linearly up to 30 minutes. -->
<retry-parameters>
<min-backoff-seconds>30</min-backoff-seconds>
<max-backoff-seconds>600</max-backoff-seconds>
<max-backoff-seconds>1800</max-backoff-seconds>
<max-doublings>0</max-doublings>
</retry-parameters>
</queue>

View File

@@ -38,7 +38,7 @@ import google.registry.flows.exceptions.TooManyResourceChecksException;
import google.registry.model.EppResource;
import google.registry.model.EppResource.ForeignKeyedEppResource;
import google.registry.model.EppResource.ResourceWithTransferData;
import google.registry.model.contact.ContactResource;
import google.registry.model.contact.Contact;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainBase;
import google.registry.model.domain.Period;
@@ -139,7 +139,7 @@ public final class ResourceFlowUtils {
}
/** Check that the given AuthInfo is either missing or else is valid for the given resource. */
public static void verifyOptionalAuthInfo(Optional<AuthInfo> authInfo, ContactResource contact)
public static void verifyOptionalAuthInfo(Optional<AuthInfo> authInfo, Contact contact)
throws EppException {
if (authInfo.isPresent()) {
verifyAuthInfo(authInfo.get(), contact);
@@ -167,7 +167,7 @@ public final class ResourceFlowUtils {
return;
}
// The roid should match one of the contacts.
Optional<VKey<ContactResource>> foundContact =
Optional<VKey<Contact>> foundContact =
domain.getReferencedContacts().stream()
.filter(key -> key.getSqlKey().equals(authRepoId))
.findFirst();
@@ -179,8 +179,7 @@ public final class ResourceFlowUtils {
}
/** Check that the given {@link AuthInfo} is valid for the given contact. */
public static void verifyAuthInfo(AuthInfo authInfo, ContactResource contact)
throws EppException {
public static void verifyAuthInfo(AuthInfo authInfo, Contact contact) throws EppException {
String authRepoId = authInfo.getPw().getRepoId();
String authPassword = authInfo.getPw().getValue();
String contactPassword = contact.getAuthInfo().getPw().getValue();

View File

@@ -220,7 +220,8 @@ public class TlsCredentials implements TransportCredentials {
super(
clientInetAddr.isPresent()
? String.format(
"Registrar IP address %s is not in stored allow list", clientInetAddr.get())
"Registrar IP address %s is not in stored allow list",
clientInetAddr.get().getHostAddress())
: "Registrar IP address is not in stored allow list");
}
}

View File

@@ -26,8 +26,8 @@ import google.registry.flows.ExtensionManager;
import google.registry.flows.Flow;
import google.registry.flows.FlowModule.RegistrarId;
import google.registry.flows.annotations.ReportingSpec;
import google.registry.model.contact.Contact;
import google.registry.model.contact.ContactCommand.Check;
import google.registry.model.contact.ContactResource;
import google.registry.model.eppinput.ResourceCommand;
import google.registry.model.eppoutput.CheckData.ContactCheck;
import google.registry.model.eppoutput.CheckData.ContactCheckData;
@@ -62,7 +62,7 @@ public final class ContactCheckFlow implements Flow {
ImmutableList<String> targetIds = ((Check) resourceCommand).getTargetIds();
verifyTargetIdCount(targetIds, maxChecks);
ImmutableSet<String> existingIds =
checkResourcesExist(ContactResource.class, targetIds, clock.nowUtc());
checkResourcesExist(Contact.class, targetIds, clock.nowUtc());
ImmutableList.Builder<ContactCheck> checks = new ImmutableList.Builder<>();
for (String id : targetIds) {
boolean unused = !existingIds.contains(id);

View File

@@ -33,15 +33,14 @@ import google.registry.flows.TransactionalFlow;
import google.registry.flows.annotations.ReportingSpec;
import google.registry.flows.exceptions.ResourceAlreadyExistsForThisClientException;
import google.registry.flows.exceptions.ResourceCreateContentionException;
import google.registry.model.contact.Contact;
import google.registry.model.contact.ContactCommand.Create;
import google.registry.model.contact.ContactHistory;
import google.registry.model.contact.ContactResource;
import google.registry.model.domain.metadata.MetadataExtension;
import google.registry.model.eppinput.ResourceCommand;
import google.registry.model.eppoutput.CreateData.ContactCreateData;
import google.registry.model.eppoutput.EppResponse;
import google.registry.model.index.EppResourceIndex;
import google.registry.model.index.ForeignKeyIndex;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
import javax.inject.Inject;
@@ -75,9 +74,9 @@ public final class ContactCreateFlow implements TransactionalFlow {
extensionManager.validate();
Create command = (Create) resourceCommand;
DateTime now = tm().getTransactionTime();
verifyResourceDoesNotExist(ContactResource.class, targetId, now, registrarId);
ContactResource newContact =
new ContactResource.Builder()
verifyResourceDoesNotExist(Contact.class, targetId, now, registrarId);
Contact newContact =
new Contact.Builder()
.setContactId(targetId)
.setAuthInfo(command.getAuthInfo())
.setCreationRegistrarId(registrarId)
@@ -100,7 +99,6 @@ public final class ContactCreateFlow implements TransactionalFlow {
ImmutableSet.of(
newContact,
historyBuilder.build(),
ForeignKeyIndex.create(newContact, newContact.getDeletionTime()),
EppResourceIndex.create(Key.create(newContact))));
return responseBuilder
.setResData(ContactCreateData.create(newContact.getContactId(), now))

View File

@@ -35,8 +35,8 @@ import google.registry.flows.FlowModule.Superuser;
import google.registry.flows.FlowModule.TargetId;
import google.registry.flows.TransactionalFlow;
import google.registry.flows.annotations.ReportingSpec;
import google.registry.model.contact.Contact;
import google.registry.model.contact.ContactHistory;
import google.registry.model.contact.ContactResource;
import google.registry.model.domain.metadata.MetadataExtension;
import google.registry.model.eppcommon.AuthInfo;
import google.registry.model.eppcommon.StatusValue;
@@ -91,15 +91,15 @@ public final class ContactDeleteFlow implements TransactionalFlow {
validateRegistrarIsLoggedIn(registrarId);
extensionManager.validate();
DateTime now = tm().getTransactionTime();
checkLinkedDomains(targetId, now, ContactResource.class);
ContactResource existingContact = loadAndVerifyExistence(ContactResource.class, targetId, now);
checkLinkedDomains(targetId, now, Contact.class);
Contact existingContact = loadAndVerifyExistence(Contact.class, targetId, now);
verifyNoDisallowedStatuses(existingContact, DISALLOWED_STATUSES);
verifyOptionalAuthInfo(authInfo, existingContact);
if (!isSuperuser) {
verifyResourceOwnership(registrarId, existingContact);
}
// Handle pending transfers on contact deletion.
ContactResource newContact =
Contact newContact =
existingContact.getStatusValues().contains(StatusValue.PENDING_TRANSFER)
? denyPendingTransfer(existingContact, SERVER_CANCELLED, now, registrarId)
: existingContact;

View File

@@ -24,10 +24,10 @@ 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.ContactResource;
import google.registry.model.contact.PostalInfo;
import google.registry.model.poll.PendingActionNotificationResponse.ContactPendingActionNotificationResponse;
import google.registry.model.poll.PollMessage;
@@ -61,7 +61,7 @@ public class ContactFlowUtils {
}
/** Check contact's state against server policy. */
static void validateContactAgainstPolicy(ContactResource contact) throws EppException {
static void validateContactAgainstPolicy(Contact contact) throws EppException {
if (contact.getDisclose() != null && !contact.getDisclose().getFlag()) {
throw new DeclineContactDisclosureFieldDisallowedPolicyException();
}

View File

@@ -27,8 +27,8 @@ import google.registry.flows.FlowModule.RegistrarId;
import google.registry.flows.FlowModule.Superuser;
import google.registry.flows.FlowModule.TargetId;
import google.registry.flows.annotations.ReportingSpec;
import google.registry.model.contact.Contact;
import google.registry.model.contact.ContactInfoData;
import google.registry.model.contact.ContactResource;
import google.registry.model.eppcommon.AuthInfo;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.eppoutput.EppResponse;
@@ -69,7 +69,7 @@ public final class ContactInfoFlow implements Flow {
DateTime now = clock.nowUtc();
validateRegistrarIsLoggedIn(registrarId);
extensionManager.validate(); // There are no legal extensions for this flow.
ContactResource contact = loadAndVerifyExistence(ContactResource.class, targetId, now);
Contact contact = loadAndVerifyExistence(Contact.class, targetId, now);
if (!isSuperuser) {
verifyResourceOwnership(registrarId, contact);
}

View File

@@ -33,8 +33,8 @@ import google.registry.flows.FlowModule.RegistrarId;
import google.registry.flows.FlowModule.TargetId;
import google.registry.flows.TransactionalFlow;
import google.registry.flows.annotations.ReportingSpec;
import google.registry.model.contact.Contact;
import google.registry.model.contact.ContactHistory;
import google.registry.model.contact.ContactResource;
import google.registry.model.domain.metadata.MetadataExtension;
import google.registry.model.eppcommon.AuthInfo;
import google.registry.model.eppinput.ResourceCommand;
@@ -74,7 +74,7 @@ public final class ContactTransferApproveFlow implements TransactionalFlow {
/**
* The logic in this flow, which handles client approvals, very closely parallels the logic in
* {@link ContactResource#cloneProjectedAtTime} which handles implicit server approvals.
* {@link Contact#cloneProjectedAtTime} which handles implicit server approvals.
*/
@Override
public EppResponse run() throws EppException {
@@ -82,11 +82,11 @@ public final class ContactTransferApproveFlow implements TransactionalFlow {
validateRegistrarIsLoggedIn(registrarId);
extensionManager.validate();
DateTime now = tm().getTransactionTime();
ContactResource existingContact = loadAndVerifyExistence(ContactResource.class, targetId, now);
Contact existingContact = loadAndVerifyExistence(Contact.class, targetId, now);
verifyOptionalAuthInfo(authInfo, existingContact);
verifyHasPendingTransfer(existingContact);
verifyResourceOwnership(registrarId, existingContact);
ContactResource newContact =
Contact newContact =
approvePendingTransfer(existingContact, TransferStatus.CLIENT_APPROVED, now);
ContactHistory contactHistory =
historyBuilder.setType(CONTACT_TRANSFER_APPROVE).setContact(newContact).build();

View File

@@ -33,8 +33,8 @@ import google.registry.flows.FlowModule.RegistrarId;
import google.registry.flows.FlowModule.TargetId;
import google.registry.flows.TransactionalFlow;
import google.registry.flows.annotations.ReportingSpec;
import google.registry.model.contact.Contact;
import google.registry.model.contact.ContactHistory;
import google.registry.model.contact.ContactResource;
import google.registry.model.domain.metadata.MetadataExtension;
import google.registry.model.eppcommon.AuthInfo;
import google.registry.model.eppinput.ResourceCommand;
@@ -78,11 +78,11 @@ public final class ContactTransferCancelFlow implements TransactionalFlow {
validateRegistrarIsLoggedIn(registrarId);
extensionManager.validate();
DateTime now = tm().getTransactionTime();
ContactResource existingContact = loadAndVerifyExistence(ContactResource.class, targetId, now);
Contact existingContact = loadAndVerifyExistence(Contact.class, targetId, now);
verifyOptionalAuthInfo(authInfo, existingContact);
verifyHasPendingTransfer(existingContact);
verifyTransferInitiator(registrarId, existingContact);
ContactResource newContact =
Contact newContact =
denyPendingTransfer(existingContact, TransferStatus.CLIENT_CANCELLED, now, registrarId);
ContactHistory contactHistory =
historyBuilder.setType(CONTACT_TRANSFER_CANCEL).setContact(newContact).build();

View File

@@ -27,7 +27,7 @@ import google.registry.flows.FlowModule.TargetId;
import google.registry.flows.annotations.ReportingSpec;
import google.registry.flows.exceptions.NoTransferHistoryToQueryException;
import google.registry.flows.exceptions.NotAuthorizedToViewTransferException;
import google.registry.model.contact.ContactResource;
import google.registry.model.contact.Contact;
import google.registry.model.eppcommon.AuthInfo;
import google.registry.model.eppoutput.EppResponse;
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
@@ -66,8 +66,7 @@ public final class ContactTransferQueryFlow implements Flow {
public EppResponse run() throws EppException {
validateRegistrarIsLoggedIn(registrarId);
extensionManager.validate(); // There are no legal extensions for this flow.
ContactResource contact =
loadAndVerifyExistence(ContactResource.class, targetId, clock.nowUtc());
Contact contact = loadAndVerifyExistence(Contact.class, targetId, clock.nowUtc());
verifyOptionalAuthInfo(authInfo, contact);
// Most of the fields on the transfer response are required, so there's no way to return valid
// XML if the object has never been transferred (and hence the fields aren't populated).

View File

@@ -33,8 +33,8 @@ import google.registry.flows.FlowModule.RegistrarId;
import google.registry.flows.FlowModule.TargetId;
import google.registry.flows.TransactionalFlow;
import google.registry.flows.annotations.ReportingSpec;
import google.registry.model.contact.Contact;
import google.registry.model.contact.ContactHistory;
import google.registry.model.contact.ContactResource;
import google.registry.model.domain.metadata.MetadataExtension;
import google.registry.model.eppcommon.AuthInfo;
import google.registry.model.eppoutput.EppResponse;
@@ -76,11 +76,11 @@ public final class ContactTransferRejectFlow implements TransactionalFlow {
validateRegistrarIsLoggedIn(registrarId);
extensionManager.validate();
DateTime now = tm().getTransactionTime();
ContactResource existingContact = loadAndVerifyExistence(ContactResource.class, targetId, now);
Contact existingContact = loadAndVerifyExistence(Contact.class, targetId, now);
verifyOptionalAuthInfo(authInfo, existingContact);
verifyHasPendingTransfer(existingContact);
verifyResourceOwnership(registrarId, existingContact);
ContactResource newContact =
Contact newContact =
denyPendingTransfer(existingContact, TransferStatus.CLIENT_REJECTED, now, registrarId);
ContactHistory contactHistory =
historyBuilder.setType(CONTACT_TRANSFER_REJECT).setContact(newContact).build();

View File

@@ -38,8 +38,8 @@ import google.registry.flows.TransactionalFlow;
import google.registry.flows.annotations.ReportingSpec;
import google.registry.flows.exceptions.AlreadyPendingTransferException;
import google.registry.flows.exceptions.ObjectAlreadySponsoredException;
import google.registry.model.contact.Contact;
import google.registry.model.contact.ContactHistory;
import google.registry.model.contact.ContactResource;
import google.registry.model.domain.metadata.MetadataExtension;
import google.registry.model.eppcommon.AuthInfo;
import google.registry.model.eppcommon.StatusValue;
@@ -96,7 +96,7 @@ public final class ContactTransferRequestFlow implements TransactionalFlow {
validateRegistrarIsLoggedIn(gainingClientId);
extensionManager.validate();
DateTime now = tm().getTransactionTime();
ContactResource existingContact = loadAndVerifyExistence(ContactResource.class, targetId, now);
Contact existingContact = loadAndVerifyExistence(Contact.class, targetId, now);
verifyAuthInfoPresentForResourceTransfer(authInfo);
verifyAuthInfo(authInfo.get(), existingContact);
// Verify that the resource does not already have a pending transfer.
@@ -146,10 +146,12 @@ public final class ContactTransferRequestFlow implements TransactionalFlow {
.asBuilder()
.setEventTime(now) // Unlike the serverApprove messages, this applies immediately.
.build();
ContactResource newContact = existingContact.asBuilder()
.setTransferData(pendingTransferData)
.addStatusValue(StatusValue.PENDING_TRANSFER)
.build();
Contact newContact =
existingContact
.asBuilder()
.setTransferData(pendingTransferData)
.addStatusValue(StatusValue.PENDING_TRANSFER)
.build();
tm().update(newContact);
tm().insertAll(
ImmutableSet.of(

View File

@@ -36,10 +36,10 @@ import google.registry.flows.FlowModule.TargetId;
import google.registry.flows.TransactionalFlow;
import google.registry.flows.annotations.ReportingSpec;
import google.registry.flows.exceptions.ResourceHasClientUpdateProhibitedException;
import google.registry.model.contact.Contact;
import google.registry.model.contact.ContactCommand.Update;
import google.registry.model.contact.ContactCommand.Update.Change;
import google.registry.model.contact.ContactHistory;
import google.registry.model.contact.ContactResource;
import google.registry.model.contact.PostalInfo;
import google.registry.model.domain.metadata.MetadataExtension;
import google.registry.model.eppcommon.AuthInfo;
@@ -94,7 +94,7 @@ public final class ContactUpdateFlow implements TransactionalFlow {
extensionManager.validate();
Update command = (Update) resourceCommand;
DateTime now = tm().getTransactionTime();
ContactResource existingContact = loadAndVerifyExistence(ContactResource.class, targetId, now);
Contact existingContact = loadAndVerifyExistence(Contact.class, targetId, now);
verifyOptionalAuthInfo(authInfo, existingContact);
ImmutableSet<StatusValue> statusToRemove = command.getInnerRemove().getStatusValues();
ImmutableSet<StatusValue> statusesToAdd = command.getInnerAdd().getStatusValues();
@@ -104,7 +104,7 @@ public final class ContactUpdateFlow implements TransactionalFlow {
}
verifyNoDisallowedStatuses(existingContact, DISALLOWED_STATUSES);
checkSameValuesNotAddedAndRemoved(statusesToAdd, statusToRemove);
ContactResource.Builder builder = existingContact.asBuilder();
Contact.Builder builder = existingContact.asBuilder();
Change change = command.getInnerChange();
// The spec requires the following behaviors:
// * If you update part of a postal info, the fields that you didn't update are unchanged.
@@ -126,7 +126,7 @@ public final class ContactUpdateFlow implements TransactionalFlow {
builder.setInternationalizedPostalInfo(null);
}
}
ContactResource newContact =
Contact newContact =
builder
.setLastEppUpdateTime(now)
.setLastEppUpdateRegistrarId(registrarId)

View File

@@ -106,7 +106,6 @@ import google.registry.model.eppinput.ResourceCommand;
import google.registry.model.eppoutput.CreateData.DomainCreateData;
import google.registry.model.eppoutput.EppResponse;
import google.registry.model.index.EppResourceIndex;
import google.registry.model.index.ForeignKeyIndex;
import google.registry.model.poll.PendingActionNotificationResponse.DomainPendingActionNotificationResponse;
import google.registry.model.poll.PollMessage;
import google.registry.model.poll.PollMessage.Autorenew;
@@ -153,6 +152,8 @@ import org.joda.time.Duration;
* @error {@link DomainCreateFlow.AnchorTenantCreatePeriodException}
* @error {@link DomainCreateFlow.MustHaveSignedMarksInCurrentPhaseException}
* @error {@link DomainCreateFlow.NoGeneralRegistrationsInCurrentPhaseException}
* @error {@link DomainCreateFlow.NoTrademarkedRegistrationsBeforeSunriseException}
* @error {@link DomainCreateFlow.PackageDomainRegisteredForTooManyYearsException}
* @error {@link DomainCreateFlow.SignedMarksOnlyDuringSunriseException}
* @error {@link DomainFlowTmchUtils.NoMarksFoundMatchingDomainException}
* @error {@link DomainFlowTmchUtils.FoundMarkNotYetValidException}
@@ -376,8 +377,7 @@ public final class DomainCreateFlow implements TransactionalFlow {
.setIdnTableName(validateDomainNameWithIdnTables(domainName))
.setRegistrationExpirationTime(registrationExpirationTime)
.setAutorenewBillingEvent(autorenewBillingEvent.createVKey())
.setAutorenewPollMessage(
autorenewPollMessage.createVKey(), autorenewPollMessage.getHistoryRevisionId())
.setAutorenewPollMessage(autorenewPollMessage.createVKey())
.setLaunchNotice(hasClaimsNotice ? launchCreate.get().getNotice() : null)
.setSmdId(signedMarkId)
.setDsData(secDnsCreate.map(SecDnsCreateExtension::getDsData).orElse(null))
@@ -390,6 +390,14 @@ public final class DomainCreateFlow implements TransactionalFlow {
.addGracePeriod(
GracePeriod.forBillingEvent(GracePeriodStatus.ADD, repoId, createBillingEvent))
.build();
if (allocationToken.isPresent()
&& allocationToken.get().getTokenType().equals(TokenType.PACKAGE)) {
if (years > 1) {
throw new PackageDomainRegisteredForTooManyYearsException(allocationToken.get().getToken());
}
domain =
domain.asBuilder().setCurrentPackageToken(allocationToken.get().createVKey()).build();
}
DomainHistory domainHistory =
buildDomainHistory(domain, registry, now, period, registry.getAddGracePeriodLength());
if (reservationTypes.contains(NAME_COLLISION)) {
@@ -399,7 +407,6 @@ public final class DomainCreateFlow implements TransactionalFlow {
entitiesToSave.add(
domain,
domainHistory,
ForeignKeyIndex.create(domain, domain.getDeletionTime()),
EppResourceIndex.create(Key.create(domain)));
if (allocationToken.isPresent()
&& TokenType.SINGLE_USE.equals(allocationToken.get().getTokenType())) {
@@ -481,7 +488,8 @@ public final class DomainCreateFlow implements TransactionalFlow {
boolean isValidReservedCreate,
boolean hasSignedMarks)
throws NoGeneralRegistrationsInCurrentPhaseException,
MustHaveSignedMarksInCurrentPhaseException {
MustHaveSignedMarksInCurrentPhaseException,
NoTrademarkedRegistrationsBeforeSunriseException {
// We allow general registration during GA.
TldState currentState = registry.getTldState(now);
if (currentState.equals(GENERAL_AVAILABILITY)) {
@@ -496,16 +504,23 @@ public final class DomainCreateFlow implements TransactionalFlow {
// Bypass most TLD state checks if that behavior is specified by the token
if (behavior.equals(RegistrationBehavior.BYPASS_TLD_STATE)
|| behavior.equals(RegistrationBehavior.ANCHOR_TENANT)) {
// If bypassing TLD state checks, a post-sunrise state is always fine
if (!currentState.equals(START_DATE_SUNRISE)
&& registry.getTldStateTransitions().headMap(now).containsValue(START_DATE_SUNRISE)) {
return;
}
// Non-trademarked names with the state check bypassed are always available
if (!claimsList.getClaimKey(domainLabel).isPresent()) {
return;
}
if (!currentState.equals(START_DATE_SUNRISE)) {
// Trademarked domains cannot be registered until after the sunrise period has ended, unless
// a valid signed mark is provided. Signed marks can only be provided during sunrise.
// Thus, when bypassing TLD state checks, a post-sunrise state is always fine.
if (registry.getTldStateTransitions().headMap(now).containsValue(START_DATE_SUNRISE)) {
return;
} else {
// If sunrise hasn't happened yet, trademarked domains are unavailable
throw new NoTrademarkedRegistrationsBeforeSunriseException(domainLabel);
}
}
}
// Otherwise, signed marks are necessary and sufficient in the sunrise period
if (currentState.equals(START_DATE_SUNRISE)) {
if (!hasSignedMarks) {
@@ -724,6 +739,17 @@ public final class DomainCreateFlow implements TransactionalFlow {
}
}
/** Trademarked domains cannot be registered before the sunrise period. */
static class NoTrademarkedRegistrationsBeforeSunriseException
extends ParameterValuePolicyErrorException {
public NoTrademarkedRegistrationsBeforeSunriseException(String domainLabel) {
super(
String.format(
"The trademarked label %s cannot be registered before the sunrise period.",
domainLabel));
}
}
/** Anchor tenant domain create is for the wrong number of years. */
static class AnchorTenantCreatePeriodException extends ParameterValuePolicyErrorException {
public AnchorTenantCreatePeriodException(int invalidYears) {
@@ -733,4 +759,14 @@ public final class DomainCreateFlow implements TransactionalFlow {
ANCHOR_TENANT_CREATE_VALID_YEARS, invalidYears));
}
}
/** Package domain registered for too many years. */
static class PackageDomainRegisteredForTooManyYearsException extends CommandUseErrorException {
public PackageDomainRegisteredForTooManyYearsException(String token) {
super(
String.format(
"The package token %s cannot be used to register names for longer than 1 year.",
token));
}
}
}

View File

@@ -29,7 +29,6 @@ import static google.registry.flows.domain.DomainFlowUtils.updateAutorenewRecurr
import static google.registry.flows.domain.DomainFlowUtils.verifyNotInPredelegation;
import static google.registry.model.ResourceTransferUtils.denyPendingTransfer;
import static google.registry.model.ResourceTransferUtils.handlePendingTransferOnDelete;
import static google.registry.model.ResourceTransferUtils.updateForeignKeyIndexDeletionTime;
import static google.registry.model.eppoutput.Result.Code.SUCCESS;
import static google.registry.model.eppoutput.Result.Code.SUCCESS_WITH_ACTION_PENDING;
import static google.registry.model.reporting.DomainTransactionRecord.TransactionReportField.ADD_FIELDS;
@@ -257,13 +256,13 @@ public final class DomainDeleteFlow implements TransactionalFlow {
Domain newDomain = builder.build();
DomainHistory domainHistory =
buildDomainHistory(newDomain, registry, now, durationUntilDelete, inAddGracePeriod);
updateForeignKeyIndexDeletionTime(newDomain);
handlePendingTransferOnDelete(existingDomain, newDomain, now, domainHistory);
// Close the autorenew billing event and poll message. This may delete the poll message. Store
// the updated recurring billing event, we'll need it later and can't reload it.
Recurring existingRecurring = tm().loadByKey(existingDomain.getAutorenewBillingEvent());
BillingEvent.Recurring recurringBillingEvent =
updateAutorenewRecurrenceEndTime(existingDomain, existingRecurring, now);
updateAutorenewRecurrenceEndTime(
existingDomain, existingRecurring, now, domainHistory.getDomainHistoryId());
// 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.

View File

@@ -79,7 +79,7 @@ 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.contact.ContactResource;
import google.registry.model.contact.Contact;
import google.registry.model.domain.DesignatedContact;
import google.registry.model.domain.DesignatedContact.Type;
import google.registry.model.domain.Domain;
@@ -112,6 +112,7 @@ import google.registry.model.domain.secdns.SecDnsUpdateExtension;
import google.registry.model.domain.secdns.SecDnsUpdateExtension.Add;
import google.registry.model.domain.secdns.SecDnsUpdateExtension.Remove;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.domain.token.AllocationToken.RegistrationBehavior;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.eppoutput.EppResponse.ResponseExtension;
import google.registry.model.host.Host;
@@ -270,7 +271,13 @@ public class DomainFlowUtils {
&& token.get().getDomainName().get().equals(domainName.toString())) {
return true;
}
// Otherwise check whether the metadata extension is being used by a superuser to specify that
// Otherwise, check to see if we're using the specialized anchor tenant registration behavior on
// the allocation token
if (token.isPresent()
&& token.get().getRegistrationBehavior().equals(RegistrationBehavior.ANCHOR_TENANT)) {
return true;
}
// Otherwise, check whether the metadata extension is being used by a superuser to specify that
// it's an anchor tenant creation.
return metadataExtension.isPresent() && metadataExtension.get().getIsAnchorTenant();
}
@@ -373,9 +380,7 @@ public class DomainFlowUtils {
/** Verify that no linked resources have disallowed statuses. */
static void verifyNotInPendingDelete(
Set<DesignatedContact> contacts,
VKey<ContactResource> registrant,
Set<VKey<Host>> nameservers)
Set<DesignatedContact> contacts, VKey<Contact> registrant, Set<VKey<Host>> nameservers)
throws EppException {
ImmutableList.Builder<VKey<? extends EppResource>> keysToLoad = new ImmutableList.Builder<>();
contacts.stream().map(DesignatedContact::getContactKey).forEach(keysToLoad::add);
@@ -420,7 +425,7 @@ public class DomainFlowUtils {
static void validateNoDuplicateContacts(Set<DesignatedContact> contacts)
throws ParameterValuePolicyErrorException {
ImmutableMultimap<Type, VKey<ContactResource>> contactsByType =
ImmutableMultimap<Type, VKey<Contact>> contactsByType =
contacts.stream()
.collect(
toImmutableSetMultimap(
@@ -429,15 +434,13 @@ public class DomainFlowUtils {
// If any contact type has multiple contacts:
if (contactsByType.asMap().values().stream().anyMatch(v -> v.size() > 1)) {
// Find the duplicates.
Map<Type, Collection<VKey<ContactResource>>> dupeKeysMap =
Map<Type, Collection<VKey<Contact>>> dupeKeysMap =
Maps.filterEntries(contactsByType.asMap(), e -> e.getValue().size() > 1);
ImmutableList<VKey<ContactResource>> dupeKeys =
ImmutableList<VKey<Contact>> dupeKeys =
dupeKeysMap.values().stream().flatMap(Collection::stream).collect(toImmutableList());
// Load the duplicates in one batch.
Map<VKey<? extends ContactResource>, ContactResource> dupeContacts =
tm().loadByKeys(dupeKeys);
ImmutableMultimap.Builder<Type, VKey<ContactResource>> typesMap =
new ImmutableMultimap.Builder<>();
Map<VKey<? extends Contact>, Contact> dupeContacts = tm().loadByKeys(dupeKeys);
ImmutableMultimap.Builder<Type, VKey<Contact>> typesMap = new ImmutableMultimap.Builder<>();
dupeKeysMap.forEach(typesMap::putAll);
// Create an error message showing the type and contact IDs of the duplicates.
throw new DuplicateContactForRoleException(
@@ -446,7 +449,7 @@ public class DomainFlowUtils {
}
static void validateRequiredContactsPresent(
@Nullable VKey<ContactResource> registrant, Set<DesignatedContact> contacts)
@Nullable VKey<Contact> registrant, Set<DesignatedContact> contacts)
throws RequiredParameterMissingException {
if (registrant == null) {
throw new MissingRegistrantException();
@@ -584,7 +587,10 @@ public class DomainFlowUtils {
* <p>Returns the new autorenew recurring billing event.
*/
public static Recurring updateAutorenewRecurrenceEndTime(
Domain domain, Recurring existingRecurring, DateTime newEndTime) {
Domain domain,
Recurring existingRecurring,
DateTime newEndTime,
@Nullable DomainHistoryId historyId) {
Optional<PollMessage.Autorenew> autorenewPollMessage =
tm().loadByKeyIfPresent(domain.getAutorenewPollMessage());
@@ -592,20 +598,25 @@ public class DomainFlowUtils {
// create a new one at the same id. This can happen if a transfer was requested on a domain
// where all autorenew poll messages had already been delivered (this would cause the poll
// message to be deleted), and then subsequently the transfer was canceled, rejected, or deleted
// (which would cause the poll message to be recreated here).
PollMessage.Autorenew updatedAutorenewPollMessage =
autorenewPollMessage.isPresent()
? autorenewPollMessage.get().asBuilder().setAutorenewEndTime(newEndTime).build()
: newAutorenewPollMessage(domain)
.setId((Long) domain.getAutorenewPollMessage().getSqlKey())
.setAutorenewEndTime(newEndTime)
.setDomainHistoryId(
new DomainHistoryId(
domain.getRepoId(), domain.getAutorenewPollMessageHistoryId()))
.build();
// (which would cause the poll message to be recreated here). In the latter case, the history id
// of the event that created the new poll message will also be used.
PollMessage.Autorenew updatedAutorenewPollMessage;
if (autorenewPollMessage.isPresent()) {
updatedAutorenewPollMessage =
autorenewPollMessage.get().asBuilder().setAutorenewEndTime(newEndTime).build();
} else {
checkNotNull(
historyId, "Cannot create a new autorenew poll message without a domain history id");
updatedAutorenewPollMessage =
newAutorenewPollMessage(domain)
.setId((Long) domain.getAutorenewPollMessage().getSqlKey())
.setAutorenewEndTime(newEndTime)
.setDomainHistoryId(historyId)
.build();
}
// If the resultant autorenew poll message would have no poll messages to deliver, then just
// delete it. Otherwise save it with the new end time.
// delete it. Otherwise, save it with the new end time.
if (isAtOrAfter(updatedAutorenewPollMessage.getEventTime(), newEndTime)) {
autorenewPollMessage.ifPresent(autorenew -> tm().delete(autorenew));
} else {

View File

@@ -30,6 +30,7 @@ import google.registry.flows.EppException;
import google.registry.flows.ExtensionManager;
import google.registry.flows.Flow;
import google.registry.flows.FlowModule.RegistrarId;
import google.registry.flows.FlowModule.Superuser;
import google.registry.flows.FlowModule.TargetId;
import google.registry.flows.annotations.ReportingSpec;
import google.registry.flows.custom.DomainInfoFlowCustomLogic;
@@ -42,6 +43,8 @@ import google.registry.model.domain.DomainCommand.Info.HostsRequest;
import google.registry.model.domain.DomainInfoData;
import google.registry.model.domain.fee06.FeeInfoCommandExtensionV06;
import google.registry.model.domain.fee06.FeeInfoResponseExtensionV06;
import google.registry.model.domain.packagetoken.PackageTokenExtension;
import google.registry.model.domain.packagetoken.PackageTokenResponseExtension;
import google.registry.model.domain.rgp.GracePeriodStatus;
import google.registry.model.domain.rgp.RgpInfoExtension;
import google.registry.model.eppcommon.AuthInfo;
@@ -85,13 +88,14 @@ public final class DomainInfoFlow implements Flow {
@Inject EppResponse.Builder responseBuilder;
@Inject DomainInfoFlowCustomLogic flowCustomLogic;
@Inject DomainPricingLogic pricingLogic;
@Inject @Superuser boolean isSuperuser;
@Inject
DomainInfoFlow() {}
@Override
public EppResponse run() throws EppException {
extensionManager.register(FeeInfoCommandExtensionV06.class);
extensionManager.register(FeeInfoCommandExtensionV06.class, PackageTokenExtension.class);
flowCustomLogic.beforeValidation();
validateRegistrarIsLoggedIn(registrarId);
extensionManager.validate();
@@ -150,6 +154,15 @@ public final class DomainInfoFlow implements Flow {
if (!gracePeriodStatuses.isEmpty()) {
extensions.add(RgpInfoExtension.create(gracePeriodStatuses));
}
Optional<PackageTokenExtension> packageInfo =
eppInput.getSingleExtension(PackageTokenExtension.class);
if (packageInfo.isPresent()) {
// Package info was requested.
if (isSuperuser || registrarId.equals(domain.getCurrentSponsorRegistrarId())) {
// Only show package info to owning registrar or superusers
extensions.add(PackageTokenResponseExtension.create(domain.getCurrentPackageToken()));
}
}
Optional<FeeInfoCommandExtensionV06> feeInfo =
eppInput.getSingleExtension(FeeInfoCommandExtensionV06.class);
if (feeInfo.isPresent()) { // Fee check was requested.

View File

@@ -30,6 +30,8 @@ import static google.registry.flows.domain.DomainFlowUtils.validateFeeChallenge;
import static google.registry.flows.domain.DomainFlowUtils.validateRegistrationPeriod;
import static google.registry.flows.domain.DomainFlowUtils.verifyRegistrarIsActive;
import static google.registry.flows.domain.DomainFlowUtils.verifyUnitIsYears;
import static google.registry.flows.domain.token.AllocationTokenFlowUtils.maybeApplyPackageRemovalToken;
import static google.registry.flows.domain.token.AllocationTokenFlowUtils.verifyTokenAllowedOnDomain;
import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_RENEW;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.DateTimeUtils.leapSafeAddYears;
@@ -119,6 +121,10 @@ import org.joda.time.Duration;
* @error {@link DomainFlowUtils.UnsupportedFeeAttributeException}
* @error {@link DomainRenewFlow.IncorrectCurrentExpirationDateException}
* @error {@link
* google.registry.flows.domain.token.AllocationTokenFlowUtils.MissingRemovePackageTokenOnPackageDomainException}
* @error {@link
* google.registry.flows.domain.token.AllocationTokenFlowUtils.RemovePackageTokenOnNonPackageDomainException}
* @error {@link
* google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotValidForDomainException}
* @error {@link
* google.registry.flows.domain.token.AllocationTokenFlowUtils.InvalidAllocationTokenException}
@@ -174,7 +180,11 @@ public final class DomainRenewFlow implements TransactionalFlow {
registrarId,
now,
eppInput.getSingleExtension(AllocationTokenExtension.class));
verifyRenewAllowed(authInfo, existingDomain, command);
verifyRenewAllowed(authInfo, existingDomain, command, allocationToken);
// If client passed an applicable static token this updates the domain
existingDomain = maybeApplyPackageRemovalToken(existingDomain, allocationToken);
int years = command.getPeriod().getValue();
DateTime newExpirationTime =
leapSafeAddYears(existingDomain.getRegistrationExpirationTime(), years); // Uncapped
@@ -223,7 +233,11 @@ public final class DomainRenewFlow implements TransactionalFlow {
.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);
updateAutorenewRecurrenceEndTime(
existingDomain,
existingRecurring,
now,
new DomainHistoryId(domainHistoryKey.getParent().getName(), domainHistoryKey.getId()));
Domain newDomain =
existingDomain
.asBuilder()
@@ -231,9 +245,7 @@ public final class DomainRenewFlow implements TransactionalFlow {
.setLastEppUpdateRegistrarId(registrarId)
.setRegistrationExpirationTime(newExpirationTime)
.setAutorenewBillingEvent(newAutorenewEvent.createVKey())
.setAutorenewPollMessage(
newAutorenewPollMessage.createVKey(),
newAutorenewPollMessage.getHistoryRevisionId())
.setAutorenewPollMessage(newAutorenewPollMessage.createVKey())
.addGracePeriod(
GracePeriod.forBillingEvent(
GracePeriodStatus.RENEW, existingDomain.getRepoId(), explicitRenewEvent))
@@ -300,7 +312,11 @@ public final class DomainRenewFlow implements TransactionalFlow {
.build();
}
private void verifyRenewAllowed(Optional<AuthInfo> authInfo, Domain existingDomain, Renew command)
private void verifyRenewAllowed(
Optional<AuthInfo> authInfo,
Domain existingDomain,
Renew command,
Optional<AllocationToken> allocationToken)
throws EppException {
verifyOptionalAuthInfo(authInfo, existingDomain);
verifyNoDisallowedStatuses(existingDomain, RENEW_DISALLOWED_STATUSES);
@@ -310,6 +326,8 @@ public final class DomainRenewFlow implements TransactionalFlow {
checkHasBillingAccount(registrarId, existingDomain.getTld());
}
verifyUnitIsYears(command.getPeriod());
// We only allow __REMOVE_PACKAGE__ token on promo package domains for now
verifyTokenAllowedOnDomain(existingDomain, allocationToken);
// If the date they specify doesn't match the expiration, fail. (This is an idempotence check).
if (!command.getCurrentExpirationDate().equals(
existingDomain.getRegistrationExpirationTime().toLocalDate())) {
@@ -331,7 +349,11 @@ public final class DomainRenewFlow implements TransactionalFlow {
.setPeriodYears(years)
.setCost(renewCost)
.setEventTime(now)
.setAllocationToken(allocationToken.map(AllocationToken::createVKey).orElse(null))
.setAllocationToken(
allocationToken
.filter(t -> AllocationToken.TokenBehavior.DEFAULT.equals(t.getTokenBehavior()))
.map(AllocationToken::createVKey)
.orElse(null))
.setBillingTime(now.plus(Registry.get(tld).getRenewGracePeriodLength()))
.setDomainHistoryId(
new DomainHistoryId(domainHistoryKey.getParent().getName(), domainHistoryKey.getId()))

View File

@@ -27,7 +27,6 @@ import static google.registry.flows.domain.DomainFlowUtils.validateFeeChallenge;
import static google.registry.flows.domain.DomainFlowUtils.verifyNotReserved;
import static google.registry.flows.domain.DomainFlowUtils.verifyPremiumNameIsNotBlocked;
import static google.registry.flows.domain.DomainFlowUtils.verifyRegistrarIsActive;
import static google.registry.model.ResourceTransferUtils.updateForeignKeyIndexDeletionTime;
import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_RESTORE;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
@@ -188,7 +187,6 @@ public final class DomainRestoreRequestFlow implements TransactionalFlow {
autorenewPollMessage,
now,
registrarId);
updateForeignKeyIndexDeletionTime(newDomain);
DomainHistory domainHistory = buildDomainHistory(newDomain, now);
entitiesToSave.add(newDomain, domainHistory, autorenewEvent, autorenewPollMessage);
tm().putAll(entitiesToSave.build());
@@ -251,8 +249,7 @@ public final class DomainRestoreRequestFlow implements TransactionalFlow {
.setGracePeriods(null)
.setDeletePollMessage(null)
.setAutorenewBillingEvent(autorenewEvent.createVKey())
.setAutorenewPollMessage(
autorenewPollMessage.createVKey(), autorenewPollMessage.getHistoryRevisionId())
.setAutorenewPollMessage(autorenewPollMessage.createVKey())
// Clear the autorenew end time so if it had expired but is now explicitly being restored,
// it won't immediately be deleted again.
.setAutorenewEndTime(Optional.empty())

View File

@@ -44,6 +44,7 @@ import google.registry.flows.FlowModule.Superuser;
import google.registry.flows.FlowModule.TargetId;
import google.registry.flows.TransactionalFlow;
import google.registry.flows.annotations.ReportingSpec;
import google.registry.flows.domain.token.AllocationTokenFlowUtils;
import google.registry.model.ImmutableObject;
import google.registry.model.billing.BillingEvent;
import google.registry.model.billing.BillingEvent.Flag;
@@ -55,7 +56,9 @@ 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;
import google.registry.model.domain.token.AllocationTokenExtension;
import google.registry.model.eppcommon.AuthInfo;
import google.registry.model.eppinput.EppInput;
import google.registry.model.eppoutput.EppResponse;
import google.registry.model.poll.PollMessage;
import google.registry.model.reporting.DomainTransactionRecord;
@@ -85,6 +88,18 @@ import org.joda.time.DateTime;
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException}
* @error {@link google.registry.flows.exceptions.NotPendingTransferException}
* @error {@link DomainFlowUtils.NotAuthorizedForTldException}
* @error {@link
* google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotValidForDomainException}
* @error {@link
* google.registry.flows.domain.token.AllocationTokenFlowUtils.InvalidAllocationTokenException}
* @error {@link
* google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotInPromotionException}
* @error {@link
* google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotValidForRegistrarException}
* @error {@link
* google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotValidForTldException}
* @error {@link
* google.registry.flows.domain.token.AllocationTokenFlowUtils.AlreadyRedeemedAllocationTokenException}
*/
@ReportingSpec(ActivityReportField.DOMAIN_TRANSFER_APPROVE)
public final class DomainTransferApproveFlow implements TransactionalFlow {
@@ -97,6 +112,8 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
@Inject DomainHistory.Builder historyBuilder;
@Inject EppResponse.Builder responseBuilder;
@Inject DomainPricingLogic pricingLogic;
@Inject AllocationTokenFlowUtils allocationTokenFlowUtils;
@Inject EppInput eppInput;
@Inject DomainTransferApproveFlow() {}
@@ -106,11 +123,17 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
*/
@Override
public EppResponse run() throws EppException {
extensionManager.register(MetadataExtension.class);
extensionManager.register(MetadataExtension.class, AllocationTokenExtension.class);
validateRegistrarIsLoggedIn(registrarId);
extensionManager.validate();
DateTime now = tm().getTransactionTime();
Domain existingDomain = loadAndVerifyExistence(Domain.class, targetId, now);
allocationTokenFlowUtils.verifyAllocationTokenIfPresent(
existingDomain,
Registry.get(existingDomain.getTld()),
registrarId,
now,
eppInput.getSingleExtension(AllocationTokenExtension.class));
verifyOptionalAuthInfo(authInfo, existingDomain);
verifyHasPendingTransfer(existingDomain);
verifyResourceOwnership(registrarId, existingDomain);
@@ -155,9 +178,9 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
getOnlyElement(existingDomain.getGracePeriodsOfType(GracePeriodStatus.AUTO_RENEW), null);
if (autorenewGrace != null) {
// During a normal transfer, if the domain is in the auto-renew grace period, the auto-renew
// billing event is cancelled and the gaining registrar is charged for the one year renewal.
// billing event is cancelled and the gaining registrar is charged for the one-year renewal.
// But, if the superuser extension is used to request a transfer without an additional year
// then the gaining registrar is not charged for the one year renewal and the losing registrar
// then the gaining registrar is not charged for the one-year renewal and the losing registrar
// still needs to be charged for the auto-renew.
if (billingEvent.isPresent()) {
entitiesToSave.add(
@@ -171,7 +194,11 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
}
// 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);
updateAutorenewRecurrenceEndTime(
existingDomain,
existingRecurring,
now,
new DomainHistoryId(domainHistoryKey.getParent().getName(), domainHistoryKey.getId()));
DateTime newExpirationTime =
computeExDateForApprovalTime(existingDomain, now, transferData.getTransferPeriod());
// Create a new autorenew event starting at the expiration time.
@@ -217,9 +244,7 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
.build())
.setRegistrationExpirationTime(newExpirationTime)
.setAutorenewBillingEvent(autorenewEvent.createVKey())
.setAutorenewPollMessage(
gainingClientAutorenewPollMessage.createVKey(),
gainingClientAutorenewPollMessage.getHistoryRevisionId())
.setAutorenewPollMessage(gainingClientAutorenewPollMessage.createVKey())
// Remove all the old grace periods and add a new one for the transfer.
.setGracePeriods(
billingEvent

View File

@@ -116,7 +116,8 @@ public final class DomainTransferCancelFlow implements TransactionalFlow {
// 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);
updateAutorenewRecurrenceEndTime(
existingDomain, existingRecurring, END_OF_TIME, domainHistory.getDomainHistoryId());
// 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

@@ -117,7 +117,8 @@ public final class DomainTransferRejectFlow implements TransactionalFlow {
// 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);
updateAutorenewRecurrenceEndTime(
existingDomain, existingRecurring, END_OF_TIME, domainHistory.getDomainHistoryId());
// 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

@@ -47,6 +47,7 @@ import google.registry.flows.FlowModule.Superuser;
import google.registry.flows.FlowModule.TargetId;
import google.registry.flows.TransactionalFlow;
import google.registry.flows.annotations.ReportingSpec;
import google.registry.flows.domain.token.AllocationTokenFlowUtils;
import google.registry.flows.exceptions.AlreadyPendingTransferException;
import google.registry.flows.exceptions.InvalidTransferPeriodValueException;
import google.registry.flows.exceptions.ObjectAlreadySponsoredException;
@@ -56,11 +57,13 @@ 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;
import google.registry.model.domain.metadata.MetadataExtension;
import google.registry.model.domain.superuser.DomainTransferRequestSuperuserExtension;
import google.registry.model.domain.token.AllocationTokenExtension;
import google.registry.model.eppcommon.AuthInfo;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.eppcommon.Trid;
@@ -117,6 +120,18 @@ import org.joda.time.DateTime;
* @error {@link DomainFlowUtils.PremiumNameBlockedException}
* @error {@link DomainFlowUtils.RegistrarMustBeActiveForThisOperationException}
* @error {@link DomainFlowUtils.UnsupportedFeeAttributeException}
* @error {@link
* google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotValidForDomainException}
* @error {@link
* google.registry.flows.domain.token.AllocationTokenFlowUtils.InvalidAllocationTokenException}
* @error {@link
* google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotInPromotionException}
* @error {@link
* google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotValidForRegistrarException}
* @error {@link
* google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotValidForTldException}
* @error {@link
* google.registry.flows.domain.token.AllocationTokenFlowUtils.AlreadyRedeemedAllocationTokenException}
*/
@ReportingSpec(ActivityReportField.DOMAIN_TRANSFER_REQUEST)
public final class DomainTransferRequestFlow implements TransactionalFlow {
@@ -138,6 +153,8 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
@Inject AsyncTaskEnqueuer asyncTaskEnqueuer;
@Inject EppResponse.Builder responseBuilder;
@Inject DomainPricingLogic pricingLogic;
@Inject AllocationTokenFlowUtils allocationTokenFlowUtils;
@Inject DomainTransferRequestFlow() {}
@Override
@@ -145,12 +162,19 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
extensionManager.register(
DomainTransferRequestSuperuserExtension.class,
FeeTransferCommandExtension.class,
MetadataExtension.class);
MetadataExtension.class,
AllocationTokenExtension.class);
validateRegistrarIsLoggedIn(gainingClientId);
verifyRegistrarIsActive(gainingClientId);
extensionManager.validate();
DateTime now = tm().getTransactionTime();
Domain existingDomain = loadAndVerifyExistence(Domain.class, targetId, now);
allocationTokenFlowUtils.verifyAllocationTokenIfPresent(
existingDomain,
Registry.get(existingDomain.getTld()),
gainingClientId,
now,
eppInput.getSingleExtension(AllocationTokenExtension.class));
Optional<DomainTransferRequestSuperuserExtension> superuserExtension =
eppInput.getSingleExtension(DomainTransferRequestSuperuserExtension.class);
Period period =
@@ -183,9 +207,12 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
.setId(domainHistoryKey.getId())
.setOtherRegistrarId(existingDomain.getCurrentSponsorRegistrarId());
DateTime automaticTransferTime =
superuserExtension.isPresent()
? now.plusDays(superuserExtension.get().getAutomaticTransferLength())
: now.plus(registry.getAutomaticTransferLength());
superuserExtension
.map(
domainTransferRequestSuperuserExtension ->
now.plusDays(
domainTransferRequestSuperuserExtension.getAutomaticTransferLength()))
.orElseGet(() -> now.plus(registry.getAutomaticTransferLength()));
// If the domain will be in the auto-renew grace period at the moment of transfer, the transfer
// will subsume the autorenew, so we don't add the normal extra year from the transfer.
// The gaining registrar is still billed for the extra year; the losing registrar will get a
@@ -234,7 +261,11 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
// the poll message if it has no events left. Note that if the automatic transfer succeeds, then
// 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);
updateAutorenewRecurrenceEndTime(
existingDomain,
existingRecurring,
automaticTransferTime,
new DomainHistoryId(domainHistoryKey.getParent().getName(), domainHistoryKey.getId()));
Domain newDomain =
existingDomain
.asBuilder()
@@ -298,7 +329,7 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
*
* <p>Even if not required, this policy is desirable because it dramatically simplifies the logic
* in transfer flows. Registrars appear to never request 2+ year transfers in practice, and they
* can always decompose an multi-year transfer into a 1-year transfer followed by a manual renewal
* can always decompose a multi-year transfer into a 1-year transfer followed by a manual renewal
* afterwards. The <a href="https://tools.ietf.org/html/rfc5731#section-3.2.4">EPP Domain RFC,
* section 3.2.4</a> says about EPP transfer periods that "the number of units available MAY be
* subject to limits imposed by the server" so we're just limiting the units to one.
@@ -343,7 +374,7 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
private DomainTransferResponse createResponse(
Period period, Domain existingDomain, Domain newDomain, DateTime now) {
// If the registration were approved this instant, this is what the new expiration would be,
// because we cap at 10 years from the moment of approval. This is different than the server
// because we cap at 10 years from the moment of approval. This is different from the server
// approval new expiration time, which is capped at 10 years from the server approve time.
DateTime approveNowExtendedRegistrationTime =
computeExDateForApprovalTime(existingDomain, now, period);

View File

@@ -15,6 +15,7 @@
package google.registry.flows.domain.token;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.google.common.base.Strings;
@@ -26,9 +27,12 @@ import google.registry.flows.EppException;
import google.registry.flows.EppException.AssociationProhibitsOperationException;
import google.registry.flows.EppException.AuthorizationErrorException;
import google.registry.flows.EppException.StatusProhibitsOperationException;
import google.registry.model.billing.BillingEvent.Recurring;
import google.registry.model.billing.BillingEvent.RenewalPriceBehavior;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainCommand;
import google.registry.model.domain.token.AllocationToken;
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;
@@ -109,23 +113,27 @@ public class AllocationTokenFlowUtils {
private void validateToken(
InternetDomainName domainName, AllocationToken token, String registrarId, DateTime now)
throws EppException {
if (!token.getAllowedRegistrarIds().isEmpty()
&& !token.getAllowedRegistrarIds().contains(registrarId)) {
throw new AllocationTokenNotValidForRegistrarException();
}
if (!token.getAllowedTlds().isEmpty()
&& !token.getAllowedTlds().contains(domainName.parent().toString())) {
throw new AllocationTokenNotValidForTldException();
}
if (token.getDomainName().isPresent()
&& !token.getDomainName().get().equals(domainName.toString())) {
throw new AllocationTokenNotValidForDomainException();
}
// Tokens without status transitions will just have a single-entry NOT_STARTED map, so only
// check the status transitions map if it's non-trivial.
if (token.getTokenStatusTransitions().size() > 1
&& !TokenStatus.VALID.equals(token.getTokenStatusTransitions().getValueAtTime(now))) {
throw new AllocationTokenNotInPromotionException();
// Only tokens with default behavior require validation
if (TokenBehavior.DEFAULT.equals(token.getTokenBehavior())) {
if (!token.getAllowedRegistrarIds().isEmpty()
&& !token.getAllowedRegistrarIds().contains(registrarId)) {
throw new AllocationTokenNotValidForRegistrarException();
}
if (!token.getAllowedTlds().isEmpty()
&& !token.getAllowedTlds().contains(domainName.parent().toString())) {
throw new AllocationTokenNotValidForTldException();
}
if (token.getDomainName().isPresent()
&& !token.getDomainName().get().equals(domainName.toString())) {
throw new AllocationTokenNotValidForDomainException();
}
// Tokens without status transitions will just have a single-entry NOT_STARTED map, so only
// check the status transitions map if it's non-trivial.
if (token.getTokenStatusTransitions().size() > 1
&& !TokenStatus.VALID.equals(token.getTokenStatusTransitions().getValueAtTime(now))) {
throw new AllocationTokenNotInPromotionException();
}
}
}
@@ -137,8 +145,15 @@ public class AllocationTokenFlowUtils {
// See https://tools.ietf.org/html/draft-ietf-regext-allocation-token-04#section-2.1
throw new InvalidAllocationTokenException();
}
Optional<AllocationToken> maybeTokenEntity =
tm().transact(() -> tm().loadByKeyIfPresent(VKey.create(AllocationToken.class, token)));
Optional<AllocationToken> maybeTokenEntity = AllocationToken.maybeGetStaticTokenInstance(token);
if (maybeTokenEntity.isPresent()) {
return maybeTokenEntity.get();
}
maybeTokenEntity =
tm().transact(() -> tm().loadByKeyIfPresent(VKey.createSql(AllocationToken.class, token)));
if (!maybeTokenEntity.isPresent()) {
throw new InvalidAllocationTokenException();
}
@@ -187,6 +202,49 @@ public class AllocationTokenFlowUtils {
tokenCustomLogic.validateToken(existingDomain, tokenEntity, registry, registrarId, now));
}
public static void verifyTokenAllowedOnDomain(
Domain domain, Optional<AllocationToken> allocationToken) throws EppException {
boolean domainHasPackageToken = domain.getCurrentPackageToken().isPresent();
boolean hasRemovePackageToken =
allocationToken.isPresent()
&& TokenBehavior.REMOVE_PACKAGE.equals(allocationToken.get().getTokenBehavior());
if (hasRemovePackageToken && !domainHasPackageToken) {
throw new RemovePackageTokenOnNonPackageDomainException();
} else if (!hasRemovePackageToken && domainHasPackageToken) {
throw new MissingRemovePackageTokenOnPackageDomainException();
}
}
public static Domain maybeApplyPackageRemovalToken(
Domain domain, Optional<AllocationToken> allocationToken) {
if (!allocationToken.isPresent()
|| !TokenBehavior.REMOVE_PACKAGE.equals(allocationToken.get().getTokenBehavior())) {
return domain;
}
Recurring newRecurringBillingEvent =
tm().loadByKey(domain.getAutorenewBillingEvent())
.asBuilder()
.setRenewalPriceBehavior(RenewalPriceBehavior.DEFAULT)
.setRenewalPrice(null)
.build();
// the Recurring billing event is reloaded later in the renew flow, so we synchronize changed
// RecurringBillingEvent with storage manually
tm().put(newRecurringBillingEvent);
jpaTm().getEntityManager().flush();
jpaTm().getEntityManager().clear();
// Remove current package token
return domain
.asBuilder()
.setCurrentPackageToken(null)
.setAutorenewBillingEvent(newRecurringBillingEvent.createVKey())
.build();
}
// Note: exception messages should be <= 32 characters long for domain check results
/** The allocation token is not currently valid. */
@@ -234,4 +292,20 @@ public class AllocationTokenFlowUtils {
super("The allocation token is invalid");
}
}
/** The __REMOVEPACKAGE__ token is missing on a renew package domain command */
public static class MissingRemovePackageTokenOnPackageDomainException
extends AssociationProhibitsOperationException {
MissingRemovePackageTokenOnPackageDomainException() {
super("Domains that are inside packages cannot be explicitly renewed");
}
}
/** The __REMOVEPACKAGE__ token is not allowed on non package domains */
public static class RemovePackageTokenOnNonPackageDomainException
extends AssociationProhibitsOperationException {
RemovePackageTokenOnNonPackageDomainException() {
super("__REMOVEPACKAGE__ token is not allowed on non package domains");
}
}
}

View File

@@ -50,7 +50,6 @@ import google.registry.model.host.Host;
import google.registry.model.host.HostCommand.Create;
import google.registry.model.host.HostHistory;
import google.registry.model.index.EppResourceIndex;
import google.registry.model.index.ForeignKeyIndex;
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
import java.util.Optional;
import javax.inject.Inject;
@@ -135,7 +134,6 @@ public final class HostCreateFlow implements TransactionalFlow {
ImmutableSet.of(
newHost,
historyBuilder.build(),
ForeignKeyIndex.create(newHost, newHost.getDeletionTime()),
EppResourceIndex.create(Key.create(newHost)));
if (superordinateDomain.isPresent()) {
tm().update(

View File

@@ -57,7 +57,6 @@ import google.registry.model.host.HostCommand.Update;
import google.registry.model.host.HostCommand.Update.AddRemove;
import google.registry.model.host.HostCommand.Update.Change;
import google.registry.model.host.HostHistory;
import google.registry.model.index.ForeignKeyIndex;
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
import google.registry.persistence.VKey;
import java.util.Objects;
@@ -194,11 +193,7 @@ public final class HostUpdateFlow implements TransactionalFlow {
ImmutableSet.Builder<ImmutableObject> entitiesToInsert = new ImmutableSet.Builder<>();
ImmutableSet.Builder<ImmutableObject> entitiesToUpdate = new ImmutableSet.Builder<>();
entitiesToUpdate.add(newHost);
// Keep the {@link ForeignKeyIndex} for this host up to date.
if (isHostRename) {
// Update the foreign key for the old host name and save one for the new host name.
entitiesToUpdate.add(ForeignKeyIndex.create(existingHost, now));
entitiesToUpdate.add(ForeignKeyIndex.create(newHost, newHost.getDeletionTime()));
updateSuperordinateDomains(existingHost, newHost);
}
enqueueTasks(existingHost, newHost);

View File

@@ -16,24 +16,16 @@ package google.registry.model;
import com.google.common.collect.ImmutableSet;
import google.registry.model.annotations.DeleteAfterMigration;
import google.registry.model.common.EntityGroupRoot;
import google.registry.model.common.GaeUserIdConverter;
import google.registry.model.contact.Contact;
import google.registry.model.contact.ContactHistory;
import google.registry.model.contact.ContactResource;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.host.Host;
import google.registry.model.host.HostHistory;
import google.registry.model.index.EppResourceIndex;
import google.registry.model.index.EppResourceIndexBucket;
import google.registry.model.index.ForeignKeyIndex;
import google.registry.model.poll.PollMessage;
import google.registry.model.rde.RdeRevision;
import google.registry.model.registrar.Registrar;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.server.Lock;
import google.registry.model.server.ServerSecret;
/** Sets of classes of the Objectify-registered entities in use throughout the model. */
@DeleteAfterMigration
@@ -42,28 +34,16 @@ public final class EntityClasses {
/** Set of entity classes. */
public static final ImmutableSet<Class<? extends ImmutableObject>> ALL_CLASSES =
ImmutableSet.of(
AllocationToken.class,
Contact.class,
ContactHistory.class,
ContactResource.class,
Domain.class,
DomainHistory.class,
EntityGroupRoot.class,
EppResourceIndex.class,
EppResourceIndexBucket.class,
ForeignKeyIndex.ForeignKeyContactIndex.class,
ForeignKeyIndex.ForeignKeyDomainIndex.class,
ForeignKeyIndex.ForeignKeyHostIndex.class,
GaeUserIdConverter.class,
HistoryEntry.class,
Host.class,
HostHistory.class,
Lock.class,
PollMessage.class,
PollMessage.Autorenew.class,
PollMessage.OneTime.class,
RdeRevision.class,
Registrar.class,
ServerSecret.class);
HostHistory.class);
private EntityClasses() {}
}

View File

@@ -140,6 +140,7 @@ public abstract class EppResource extends BackupGroupRoot implements Buildable {
DateTime lastEppUpdateTime;
/** Status values associated with this resource. */
@Ignore
@Column(name = "statuses")
// TODO(b/177567432): rename to "statuses" once we're off datastore.
Set<StatusValue> status;

View File

@@ -32,7 +32,7 @@ import google.registry.config.RegistryConfig;
import google.registry.model.EppResource.BuilderWithTransferData;
import google.registry.model.EppResource.ForeignKeyedEppResource;
import google.registry.model.EppResource.ResourceWithTransferData;
import google.registry.model.contact.ContactResource;
import google.registry.model.contact.Contact;
import google.registry.model.domain.Domain;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.host.Host;
@@ -345,10 +345,10 @@ public final class EppResourceUtils {
public static ImmutableSet<VKey<Domain>> getLinkedDomainKeys(
VKey<? extends EppResource> key, DateTime now, @Nullable Integer limit) {
checkArgument(
key.getKind().equals(ContactResource.class) || key.getKind().equals(Host.class),
"key must be either VKey<ContactResource> or VKey<Host>, but it is %s",
key.getKind().equals(Contact.class) || key.getKind().equals(Host.class),
"key must be either VKey<Contact> or VKey<Host>, but it is %s",
key);
boolean isContactKey = key.getKind().equals(ContactResource.class);
boolean isContactKey = key.getKind().equals(Contact.class);
if (tm().isOfy()) {
com.googlecode.objectify.cmd.Query<Domain> query =
auditedOfy()

View File

@@ -257,7 +257,7 @@ public abstract class ImmutableObject implements Cloneable {
return (Map<String, Object>) toMapRecursive(this);
}
public VKey createVKey() {
public VKey<? extends ImmutableObject> createVKey() {
throw new UnsupportedOperationException("VKey creation is not supported for this entity");
}
}

View File

@@ -23,13 +23,11 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import google.registry.model.EppResource.BuilderWithTransferData;
import google.registry.model.EppResource.ForeignKeyedEppResource;
import google.registry.model.EppResource.ResourceWithTransferData;
import google.registry.model.contact.ContactResource;
import google.registry.model.contact.Contact;
import google.registry.model.domain.Domain;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.eppcommon.Trid;
import google.registry.model.index.ForeignKeyIndex;
import google.registry.model.poll.PendingActionNotificationResponse;
import google.registry.model.poll.PendingActionNotificationResponse.ContactPendingActionNotificationResponse;
import google.registry.model.poll.PendingActionNotificationResponse.DomainPendingActionNotificationResponse;
@@ -60,7 +58,7 @@ public final class ResourceTransferUtils {
EppResource eppResource, TransferData transferData) {
assertIsContactOrDomain(eppResource);
TransferResponse.Builder<? extends TransferResponse, ?> builder;
if (eppResource instanceof ContactResource) {
if (eppResource instanceof Contact) {
builder = new ContactTransferResponse.Builder().setContactId(eppResource.getForeignKey());
} else {
DomainTransferData domainTransferData = (DomainTransferData) transferData;
@@ -93,7 +91,7 @@ public final class ResourceTransferUtils {
boolean actionResult,
DateTime processedDate) {
assertIsContactOrDomain(eppResource);
return eppResource instanceof ContactResource
return eppResource instanceof Contact
? ContactPendingActionNotificationResponse.create(
eppResource.getForeignKey(), actionResult, transferRequestTrid, processedDate)
: DomainPendingActionNotificationResponse.create(
@@ -101,14 +99,7 @@ public final class ResourceTransferUtils {
}
private static void assertIsContactOrDomain(EppResource eppResource) {
checkState(eppResource instanceof ContactResource || eppResource instanceof Domain);
}
/** Update the relevant {@link ForeignKeyIndex} to cache the new deletion time. */
public static <R extends EppResource> void updateForeignKeyIndexDeletionTime(R resource) {
if (resource instanceof ForeignKeyedEppResource) {
tm().insert(ForeignKeyIndex.create(resource, resource.getDeletionTime()));
}
checkState(eppResource instanceof Contact || eppResource instanceof Domain);
}
/** If there is a transfer out, delete the server-approve entities and enqueue a poll message. */

View File

@@ -1,35 +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.annotations;
import com.googlecode.objectify.annotation.Entity;
import google.registry.model.common.EntityGroupRoot;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation for an Objectify {@link Entity} to indicate that it is in the cross-TLD entity group.
*
* <p>This means that the entity's <code>@Parent</code> field has to have the value of {@link
* EntityGroupRoot#getCrossTldKey}.
*/
@DeleteAfterMigration
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Inherited
public @interface InCrossTld {}

View File

@@ -212,6 +212,9 @@ public abstract class BillingEvent extends ImmutableObject
return nullToEmptyImmutableCopy(flags);
}
@Override
public abstract VKey<? extends BillingEvent> createVKey();
/** Override Buildable.asBuilder() to give this method stronger typing. */
@Override
public abstract Builder<?, ?> asBuilder();

View File

@@ -14,26 +14,22 @@
package google.registry.model.common;
import static google.registry.model.common.EntityGroupRoot.getCrossTldKey;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Id;
import com.googlecode.objectify.annotation.Parent;
import google.registry.model.ImmutableObject;
import google.registry.model.annotations.DeleteAfterMigration;
import google.registry.model.annotations.InCrossTld;
import javax.persistence.MappedSuperclass;
import javax.persistence.Transient;
/** A singleton entity in Datastore. */
/**
* 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
@MappedSuperclass
@InCrossTld
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;
@Transient @Parent Key<EntityGroupRoot> parent = getCrossTldKey();
}

View File

@@ -1,57 +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.common;
import com.google.apphosting.api.ApiProxy;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
import google.registry.model.BackupGroupRoot;
import google.registry.model.annotations.DeleteAfterMigration;
import javax.annotation.Nullable;
/**
* The root key for the entity group which is known as the cross-tld entity group for historical
* reasons.
*
* <p>This exists as a storage place for common configuration options and global settings that
* aren't updated too frequently. Entities in this entity group are usually cached upon load. The
* reason this common entity group exists is because it enables strongly consistent queries and
* updates across this seldomly updated data. This shared entity group also helps cut down on a
* potential ballooning in the number of entity groups enlisted in transactions.
*
* <p>Historically, each TLD used to have a separate namespace, and all entities for a TLD were in a
* single EntityGroupRoot for that TLD. Hence why there was a "cross-tld" entity group -- it was the
* entity group for the single namespace where global data applicable for all TLDs lived.
*/
@Entity
@DeleteAfterMigration
public class EntityGroupRoot extends BackupGroupRoot {
@SuppressWarnings("unused")
@Id
private String id;
/** The root key for cross-tld resources such as registrars. */
public static @Nullable Key<EntityGroupRoot> getCrossTldKey() {
// If we cannot get a current environment, calling Key.create() will fail. Instead we return a
// null in cases where this key is not actually needed (for example when loading an entity from
// SQL) to initialize an object, to avoid having to register a DatastoreEntityExtension in
// tests.
return ApiProxy.getCurrentEnvironment() == null
? null
: Key.create(EntityGroupRoot.class, "cross-tld");
}
}

View File

@@ -25,7 +25,6 @@ import static org.joda.time.DateTimeZone.UTC;
import com.google.common.base.Splitter;
import com.google.common.collect.ContiguousSet;
import com.google.common.collect.Range;
import com.googlecode.objectify.annotation.Embed;
import com.googlecode.objectify.annotation.Index;
import google.registry.model.ImmutableObject;
import google.registry.model.UnsafeSerializable;
@@ -44,7 +43,6 @@ import org.joda.time.DateTime;
* allows it to be embeddable with no translation needed and also delays parsing of the string on
* load until it's actually needed.
*/
@Embed
@Embeddable
public class TimeOfYear extends ImmutableObject implements UnsafeSerializable {

View File

@@ -63,5 +63,7 @@ public enum ConsolePermission {
/** View announcements in the UI. */
VIEW_ANNOUNCEMENTS,
/** Viewing a record of actions performed in the UI for a particular registrar. */
VIEW_ACTIVITY_LOG
VIEW_ACTIVITY_LOG,
/** View and perform registry locks. */
REGISTRY_LOCK
}

View File

@@ -44,7 +44,8 @@ public class ConsoleRoleDefinitions {
ConsolePermission.VIEW_OPERATIONAL_DATA,
ConsolePermission.SEND_ANNOUNCEMENTS,
ConsolePermission.VIEW_ANNOUNCEMENTS,
ConsolePermission.VIEW_ACTIVITY_LOG);
ConsolePermission.VIEW_ACTIVITY_LOG,
ConsolePermission.REGISTRY_LOCK);
/** Permissions for a registry support lead. */
static final ImmutableSet<ConsolePermission> SUPPORT_LEAD_PERMISSIONS =
@@ -76,10 +77,17 @@ public class ConsoleRoleDefinitions {
ConsolePermission.VIEW_OPERATIONAL_DATA,
ConsolePermission.VIEW_ANNOUNCEMENTS);
/** Permissions for a registrar partner account manager that can perform registry locks. */
static final ImmutableSet<ConsolePermission> ACCOUNT_MANAGER_WITH_REGISTRY_LOCK_PERMISSIONS =
new ImmutableSet.Builder<ConsolePermission>()
.addAll(ACCOUNT_MANAGER_PERMISSIONS)
.add(ConsolePermission.REGISTRY_LOCK)
.build();
/** Permissions for the tech contact of a registrar. */
static final ImmutableSet<ConsolePermission> TECH_CONTACT_PERMISSIONS =
new ImmutableSet.Builder<ConsolePermission>()
.addAll(ACCOUNT_MANAGER_PERMISSIONS)
.addAll(ACCOUNT_MANAGER_WITH_REGISTRY_LOCK_PERMISSIONS)
.add(
ConsolePermission.MANAGE_ACCREDITATION,
ConsolePermission.CONFIGURE_EPP_CONNECTION,

View File

@@ -15,6 +15,7 @@
package google.registry.model.console;
import static google.registry.model.console.ConsoleRoleDefinitions.ACCOUNT_MANAGER_PERMISSIONS;
import static google.registry.model.console.ConsoleRoleDefinitions.ACCOUNT_MANAGER_WITH_REGISTRY_LOCK_PERMISSIONS;
import static google.registry.model.console.ConsoleRoleDefinitions.PRIMARY_CONTACT_PERMISSIONS;
import static google.registry.model.console.ConsoleRoleDefinitions.TECH_CONTACT_PERMISSIONS;
@@ -25,6 +26,8 @@ public enum RegistrarRole {
/** The user is a standard account manager at a registrar. */
ACCOUNT_MANAGER(ACCOUNT_MANAGER_PERMISSIONS),
/** The user is an account manager that can perform registry locks. */
ACCOUNT_MANAGER_WITH_REGISTRY_LOCK(ACCOUNT_MANAGER_WITH_REGISTRY_LOCK_PERMISSIONS),
/** The user is a technical contact of a registrar. */
TECH_CONTACT(TECH_CONTACT_PERMISSIONS),
/** The user is the primary contact at a registrar. */

View File

@@ -22,30 +22,42 @@ import static google.registry.util.PasswordUtils.SALT_SUPPLIER;
import static google.registry.util.PasswordUtils.hashPassword;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import google.registry.model.BackupGroupRoot;
import google.registry.model.Buildable;
import google.registry.model.ImmutableObject;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.Table;
/** A console user, either a registry employee or a registrar partner. */
public class User extends ImmutableObject implements Buildable {
@Entity
@Table(
indexes = {
@Index(columnList = "gaiaId", name = "user_gaia_id_idx"),
@Index(columnList = "emailAddress", name = "user_email_address_idx")
})
public class User extends BackupGroupRoot implements Buildable {
/** Autogenerated unique ID of this user. */
private long id;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/** GAIA ID associated with the user in question. */
@Column(nullable = false)
private String gaiaId;
/** Email address of the user in question. */
@Column(nullable = false)
private String emailAddress;
/** Roles (which grant permissions) associated with this user. */
@Column(nullable = false)
private UserRoles userRoles;
/**
* Whether the contact is allowed to set their registry lock password through the registrar
* console. This will be set to false on contact creation and when the user sets a password.
*/
boolean allowedToSetRegistryLockPassword = false;
/**
* A hashed password that exists iff this contact is registry-lock-enabled. The hash is a base64
* encoded SHA256 string.
@@ -55,6 +67,10 @@ public class User extends ImmutableObject implements Buildable {
/** Randomly generated hash salt. */
String registryLockPasswordSalt;
public Long getId() {
return id;
}
public String getGaiaId() {
return gaiaId;
}
@@ -67,11 +83,7 @@ public class User extends ImmutableObject implements Buildable {
return userRoles;
}
public boolean isAllowedToSetRegistryLockPassword() {
return allowedToSetRegistryLockPassword;
}
public boolean isRegistryLockAllowed() {
public boolean hasRegistryLockPassword() {
return !isNullOrEmpty(registryLockPasswordHash) && !isNullOrEmpty(registryLockPasswordSalt);
}
@@ -85,6 +97,22 @@ public class User extends ImmutableObject implements Buildable {
.equals(registryLockPasswordHash);
}
/**
* Whether the user has the registry lock permission on any registrar or globally.
*
* <p>If so, they should be allowed to (re)set their registry lock password.
*/
public boolean hasAnyRegistryLockPermission() {
if (userRoles == null) {
return false;
}
if (userRoles.isAdmin() || userRoles.hasGlobalPermission(ConsolePermission.REGISTRY_LOCK)) {
return true;
}
return userRoles.getRegistrarRoles().values().stream()
.anyMatch(role -> role.hasPermission(ConsolePermission.REGISTRY_LOCK));
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));
@@ -99,6 +127,7 @@ public class User extends ImmutableObject implements Buildable {
super(user);
}
@Override
public User build() {
checkArgumentNotNull(getInstance().gaiaId, "Gaia ID cannot be null");
checkArgumentNotNull(getInstance().emailAddress, "Email address cannot be null");
@@ -123,25 +152,22 @@ public class User extends ImmutableObject implements Buildable {
return this;
}
public Builder setAllowedToSetRegistryLockPassword(boolean allowedToSetRegistryLockPassword) {
if (allowedToSetRegistryLockPassword) {
getInstance().registryLockPasswordSalt = null;
getInstance().registryLockPasswordHash = null;
}
getInstance().allowedToSetRegistryLockPassword = allowedToSetRegistryLockPassword;
public Builder removeRegistryLockPassword() {
getInstance().registryLockPasswordHash = null;
getInstance().registryLockPasswordSalt = null;
return this;
}
public Builder setRegistryLockPassword(String registryLockPassword) {
checkArgument(
getInstance().allowedToSetRegistryLockPassword,
"Not allowed to set registry lock password for this user");
getInstance().hasAnyRegistryLockPermission(), "User has no registry lock permission");
checkArgument(
!getInstance().hasRegistryLockPassword(), "User already has a password, remove it first");
checkArgument(
!isNullOrEmpty(registryLockPassword), "Registry lock password was null or empty");
getInstance().registryLockPasswordSalt = base64().encode(SALT_SUPPLIER.get());
getInstance().registryLockPasswordHash =
hashPassword(registryLockPassword, getInstance().registryLockPasswordSalt);
getInstance().allowedToSetRegistryLockPassword = false;
return this;
}
}

View File

@@ -0,0 +1,57 @@
// 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.model.console;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import java.util.Optional;
/** Data access object for {@link User} objects to simplify saving and retrieval. */
public class UserDao {
/** Retrieves the one user with this email address if it exists. */
public static Optional<User> loadUser(String emailAddress) {
return jpaTm()
.transact(
() ->
jpaTm()
.query("FROM User WHERE emailAddress = :emailAddress", User.class)
.setParameter("emailAddress", emailAddress)
.getResultStream()
.findFirst());
}
/** Saves the given user, checking that no existing user already exists with this email. */
public static void saveUser(User user) {
jpaTm()
.transact(
() -> {
// Check for an existing user (the unique constraint protects us, but this gives a
// nicer exception)
Optional<User> maybeSavedUser = loadUser(user.getEmailAddress());
if (maybeSavedUser.isPresent()) {
User savedUser = maybeSavedUser.get();
checkArgument(
savedUser.getId().equals(user.getId()),
String.format(
"Attempted save of User with email address %s and ID %s, user with that"
+ " email already exists with ID %s",
user.getEmailAddress(), user.getId(), savedUser.getId()));
}
jpaTm().put(user);
});
}
}

View File

@@ -21,6 +21,10 @@ import com.google.common.collect.ImmutableMap;
import google.registry.model.Buildable;
import google.registry.model.ImmutableObject;
import java.util.Map;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
/**
* Contains the global and per-registrar roles for a given user.
@@ -28,15 +32,19 @@ import java.util.Map;
* <p>See <a href="https://go/nomulus-console-authz">go/nomulus-console-authz</a> for more
* information.
*/
@Embeddable
public class UserRoles extends ImmutableObject implements Buildable {
/** Whether the user is a global admin, who has access to everything. */
@Column(nullable = false)
private boolean isAdmin = false;
/**
* The global role (e.g. {@link GlobalRole#SUPPORT_AGENT}) that the user has across all
* registrars.
*/
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private GlobalRole globalRole = GlobalRole.NONE;
/** Any per-registrar roles that this user may have. */

View File

@@ -15,7 +15,6 @@
package google.registry.model.contact;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Entity;
import google.registry.model.EppResource.ForeignKeyedEppResource;
import google.registry.model.annotations.ExternalMessagingName;
import google.registry.model.annotations.ReportedOn;
@@ -23,6 +22,10 @@ import google.registry.persistence.VKey;
import google.registry.persistence.WithStringVKey;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.Table;
import org.joda.time.DateTime;
/**
@@ -32,35 +35,35 @@ import org.joda.time.DateTime;
*/
@ReportedOn
@Entity
@javax.persistence.Entity(name = "Contact")
@javax.persistence.Table(
@com.googlecode.objectify.annotation.Entity
@Table(
name = "Contact",
indexes = {
@javax.persistence.Index(columnList = "creationTime"),
@javax.persistence.Index(columnList = "currentSponsorRegistrarId"),
@javax.persistence.Index(columnList = "deletionTime"),
@javax.persistence.Index(columnList = "contactId"),
@javax.persistence.Index(columnList = "searchName")
@Index(columnList = "creationTime"),
@Index(columnList = "currentSponsorRegistrarId"),
@Index(columnList = "deletionTime"),
@Index(columnList = "contactId"),
@Index(columnList = "searchName")
})
@ExternalMessagingName("contact")
@WithStringVKey
@Access(AccessType.FIELD)
public class ContactResource extends ContactBase implements ForeignKeyedEppResource {
public class Contact extends ContactBase implements ForeignKeyedEppResource {
@Override
public VKey<ContactResource> createVKey() {
return VKey.create(ContactResource.class, getRepoId(), Key.create(this));
public VKey<Contact> createVKey() {
return VKey.create(Contact.class, getRepoId(), Key.create(this));
}
@Override
@javax.persistence.Id
@Id
@Access(AccessType.PROPERTY)
public String getRepoId() {
return super.getRepoId();
}
@Override
public ContactResource cloneProjectedAtTime(DateTime now) {
public Contact cloneProjectedAtTime(DateTime now) {
return ContactBase.cloneContactProjectedAtTime(this, now);
}
@@ -69,12 +72,12 @@ public class ContactResource extends ContactBase implements ForeignKeyedEppResou
return new Builder(clone(this));
}
/** A builder for constructing {@link ContactResource}, since it is immutable. */
public static class Builder extends ContactBase.Builder<ContactResource, Builder> {
/** A builder for constructing {@link Contact}, since it is immutable. */
public static class Builder extends ContactBase.Builder<Contact, Builder> {
public Builder() {}
private Builder(ContactResource instance) {
private Builder(Contact instance) {
super(instance);
}

View File

@@ -14,7 +14,6 @@
package google.registry.model.contact;
import com.googlecode.objectify.annotation.Embed;
import google.registry.model.eppcommon.Address;
import javax.persistence.Embeddable;
@@ -30,7 +29,6 @@ import javax.persistence.Embeddable;
*
* @see PostalInfo
*/
@Embed
@Embeddable
public class ContactAddress extends Address {

View File

@@ -14,13 +14,12 @@
package google.registry.model.contact;
import com.googlecode.objectify.annotation.Embed;
import google.registry.model.eppcommon.AuthInfo;
import javax.persistence.Embeddable;
import javax.xml.bind.annotation.XmlType;
/** A version of authInfo specifically for contacts. */
@Embed
@javax.persistence.Embeddable
@Embeddable
@XmlType(namespace = "urn:ietf:params:xml:ns:contact-1.0")
public class ContactAuthInfo extends AuthInfo {
public static ContactAuthInfo create(PasswordAuth pw) {

View File

@@ -19,6 +19,7 @@ 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;
@@ -66,9 +67,9 @@ 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 ContactResource.Builder#wipeOut}.
* US-ASCII character set. Personal info; cleared by {@link Contact.Builder#wipeOut}.
*/
@IgnoreSave(IfNull.class)
@Ignore
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "name", column = @Column(name = "addr_local_name")),
@@ -94,9 +95,9 @@ public class ContactBase extends EppResource implements ResourceWithTransferData
/**
* Internationalized postal info for the contact. Personal info; cleared by {@link
* ContactResource.Builder#wipeOut}.
* Contact.Builder#wipeOut}.
*/
@IgnoreSave(IfNull.class)
@Ignore
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "name", column = @Column(name = "addr_i18n_name")),
@@ -123,12 +124,12 @@ public class ContactBase extends EppResource implements ResourceWithTransferData
/**
* Contact name used for name searches. This is set automatically to be the internationalized
* postal name, or if null, the localized postal name, or if that is null as well, null. Personal
* info; cleared by {@link ContactResource.Builder#wipeOut}.
* info; cleared by {@link Contact.Builder#wipeOut}.
*/
@Index String searchName;
/** Contacts voice number. Personal info; cleared by {@link ContactResource.Builder#wipeOut}. */
@IgnoreSave(IfNull.class)
/** Contacts voice number. Personal info; cleared by {@link Contact.Builder#wipeOut}. */
@Ignore
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "phoneNumber", column = @Column(name = "voice_phone_number")),
@@ -136,8 +137,8 @@ public class ContactBase extends EppResource implements ResourceWithTransferData
})
ContactPhoneNumber voice;
/** Contacts fax number. Personal info; cleared by {@link ContactResource.Builder#wipeOut}. */
@IgnoreSave(IfNull.class)
/** Contacts fax number. Personal info; cleared by {@link Contact.Builder#wipeOut}. */
@Ignore
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "phoneNumber", column = @Column(name = "fax_phone_number")),
@@ -145,11 +146,12 @@ public class ContactBase extends EppResource implements ResourceWithTransferData
})
ContactPhoneNumber fax;
/** Contacts email address. Personal info; cleared by {@link ContactResource.Builder#wipeOut}. */
/** 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")),
@@ -158,7 +160,7 @@ public class ContactBase extends EppResource implements ResourceWithTransferData
ContactAuthInfo authInfo;
/** Data about any pending or past transfers on this contact. */
ContactTransferData transferData;
@Ignore ContactTransferData transferData;
/**
* The time that this resource was last transferred.
@@ -171,6 +173,7 @@ 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")),
@@ -187,7 +190,7 @@ public class ContactBase extends EppResource implements ResourceWithTransferData
public VKey<? extends ContactBase> createVKey() {
throw new UnsupportedOperationException(
"ContactBase is not an actual persisted entity you can create a key to;"
+ " use ContactResource instead");
+ " use Contact instead");
}
@OnLoad
@@ -291,7 +294,7 @@ public class ContactBase extends EppResource implements ResourceWithTransferData
return new Builder<>(clone(this));
}
/** A builder for constructing {@link ContactResource}, since it is immutable. */
/** A builder for constructing {@link Contact}, since it is immutable. */
public static class Builder<T extends ContactBase, B extends Builder<T, B>>
extends EppResource.Builder<T, B> implements BuilderWithTransferData<ContactTransferData, B> {

View File

@@ -34,13 +34,13 @@ import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.CollapsedStringAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
/** A collection of {@link ContactResource} commands. */
/** A collection of {@link Contact} commands. */
public class ContactCommand {
/** The fields on "chgType" from <a href="http://tools.ietf.org/html/rfc5733">RFC5733</a>. */
@XmlTransient
public static class ContactCreateOrChange extends ImmutableObject
implements ResourceCreateOrChange<ContactResource.Builder> {
implements ResourceCreateOrChange<Contact.Builder> {
/** Postal info for the contact. */
List<PostalInfo> postalInfo;
@@ -111,13 +111,13 @@ public class ContactCommand {
}
/**
* A create command for a {@link ContactResource}, mapping "createType" from <a
* A create command for a {@link Contact}, mapping "createType" from <a
* href="http://tools.ietf.org/html/rfc5733">RFC5733</a>}.
*/
@XmlType(propOrder = {"contactId", "postalInfo", "voice", "fax", "email", "authInfo", "disclose"})
@XmlRootElement
public static class Create extends ContactCreateOrChange
implements SingleResourceCommand, ResourceCreateOrChange<ContactResource.Builder> {
implements SingleResourceCommand, ResourceCreateOrChange<Contact.Builder> {
/**
* Unique identifier for this contact.
*
@@ -139,29 +139,29 @@ public class ContactCommand {
}
}
/** A delete command for a {@link ContactResource}. */
/** A delete command for a {@link Contact}. */
@XmlRootElement
public static class Delete extends AbstractSingleResourceCommand {}
/** An info request for a {@link ContactResource}. */
/** An info request for a {@link Contact}. */
@XmlRootElement
@XmlType(propOrder = {"targetId", "authInfo"})
public static class Info extends AbstractContactAuthCommand {}
/** A check request for {@link ContactResource}. */
/** A check request for {@link Contact}. */
@XmlRootElement
public static class Check extends ResourceCheck {}
/** A transfer operation for a {@link ContactResource}. */
/** A transfer operation for a {@link Contact}. */
@XmlRootElement
@XmlType(propOrder = {"targetId", "authInfo"})
public static class Transfer extends AbstractContactAuthCommand {}
/** An update to a {@link ContactResource}. */
/** An update to a {@link Contact}. */
@XmlRootElement
@XmlType(propOrder = {"targetId", "innerAdd", "innerRemove", "innerChange"})
public static class Update
extends ResourceUpdate<Update.AddRemove, ContactResource.Builder, Update.Change> {
extends ResourceUpdate<Update.AddRemove, Contact.Builder, Update.Change> {
@XmlElement(name = "chg")
protected Change innerChange;

View File

@@ -58,7 +58,7 @@ import javax.persistence.PostLoad;
@IdClass(ContactHistoryId.class)
public class ContactHistory extends HistoryEntry implements UnsafeSerializable {
// Store ContactBase instead of ContactResource so we don't pick up its @Id
// Store ContactBase instead of Contact so we don't pick up its @Id
// Nullable for the sake of pre-Registry-3.0 history objects
@DoNotCompare @Nullable ContactBase contactBase;
@@ -73,7 +73,7 @@ public class ContactHistory extends HistoryEntry implements UnsafeSerializable {
/** This method is private because it is only used by Hibernate. */
@SuppressWarnings("unused")
private void setContactRepoId(String contactRepoId) {
parent = Key.create(ContactResource.class, contactRepoId);
parent = Key.create(Contact.class, contactRepoId);
}
@Id
@@ -98,9 +98,9 @@ public class ContactHistory extends HistoryEntry implements UnsafeSerializable {
return Optional.ofNullable(contactBase);
}
/** The key to the {@link ContactResource} this is based off of. */
public VKey<ContactResource> getParentVKey() {
return VKey.create(ContactResource.class, getContactRepoId());
/** The key to the {@link Contact} this is based off of. */
public VKey<Contact> getParentVKey() {
return VKey.create(Contact.class, getContactRepoId());
}
/** Creates a {@link VKey} instance for this entity. */
@@ -112,8 +112,7 @@ public class ContactHistory extends HistoryEntry implements UnsafeSerializable {
@Override
public Optional<? extends EppResource> getResourceAtPointInTime() {
return getContactBase()
.map(contactBase -> new ContactResource.Builder().copyFrom(contactBase).build());
return getContactBase().map(contactBase -> new Contact.Builder().copyFrom(contactBase).build());
}
@PostLoad
@@ -210,7 +209,7 @@ public class ContactHistory extends HistoryEntry implements UnsafeSerializable {
}
public Builder setContactRepoId(String contactRepoId) {
getInstance().parent = Key.create(ContactResource.class, contactRepoId);
getInstance().parent = Key.create(Contact.class, contactRepoId);
return this;
}

View File

@@ -14,20 +14,18 @@
package google.registry.model.contact;
import com.googlecode.objectify.annotation.Embed;
import google.registry.model.eppcommon.PhoneNumber;
import javax.persistence.Embeddable;
/**
* EPP Contact Phone Number
*
* <p>This class is embedded inside a {@link ContactResource} hold the phone number of an EPP
* contact. The fields are all defined in the parent class {@link PhoneNumber}, but the subclass is
* still necessary to pick up the contact namespace.
* <p>This class is embedded inside a {@link Contact} hold the phone number of an EPP contact. The
* fields are all defined in the parent class {@link PhoneNumber}, but the subclass is still
* necessary to pick up the contact namespace.
*
* @see ContactResource
* @see Contact
*/
@Embed
@Embeddable
public class ContactPhoneNumber extends PhoneNumber {

View File

@@ -17,7 +17,6 @@ package google.registry.model.contact;
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import com.google.common.collect.ImmutableList;
import com.googlecode.objectify.annotation.Embed;
import google.registry.model.Buildable;
import google.registry.model.ImmutableObject;
import google.registry.model.UnsafeSerializable;
@@ -30,7 +29,6 @@ import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlType;
/** The "discloseType" from <a href="http://tools.ietf.org/html/rfc5733">RFC5733</a>. */
@Embed
@Embeddable
@XmlType(propOrder = {"name", "org", "addr", "voice", "fax", "email"})
public class Disclose extends ImmutableObject implements UnsafeSerializable {
@@ -79,7 +77,6 @@ public class Disclose extends ImmutableObject implements UnsafeSerializable {
}
/** The "intLocType" from <a href="http://tools.ietf.org/html/rfc5733">RFC5733</a>. */
@Embed
public static class PostalInfoChoice extends ImmutableObject implements Serializable {
@XmlAttribute
PostalInfo.Type type;

View File

@@ -16,7 +16,6 @@ package google.registry.model.contact;
import static com.google.common.base.Preconditions.checkState;
import com.googlecode.objectify.annotation.Embed;
import google.registry.model.Buildable;
import google.registry.model.Buildable.Overlayable;
import google.registry.model.ImmutableObject;
@@ -36,7 +35,6 @@ import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
* Implementation of both "postalInfoType" and "chgPostalInfoType" from <a href=
* "http://tools.ietf.org/html/rfc5733">RFC5733</a>.
*/
@Embed
@Embeddable
@XmlType(propOrder = {"name", "org", "address", "type"})
public class PostalInfo extends ImmutableObject

View File

@@ -17,12 +17,11 @@ package google.registry.model.domain;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Embed;
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.ContactResource;
import google.registry.model.contact.Contact;
import google.registry.persistence.VKey;
import javax.persistence.Embeddable;
import javax.xml.bind.annotation.XmlEnumValue;
@@ -45,7 +44,6 @@ import javax.xml.bind.annotation.XmlEnumValue;
* @see <a href="http://tools.ietf.org/html/rfc5731#section-2.2">RFC 5731 - EPP Domain Name Mapping
* - Contact and Client Identifiers</a>
*/
@Embed
@Embeddable
public class DesignatedContact extends ImmutableObject implements UnsafeSerializable {
@@ -64,7 +62,7 @@ public class DesignatedContact extends ImmutableObject implements UnsafeSerializ
REGISTRANT
}
public static DesignatedContact create(Type type, VKey<ContactResource> contact) {
public static DesignatedContact create(Type type, VKey<Contact> contact) {
DesignatedContact instance = new DesignatedContact();
instance.type = type;
instance.contactVKey = checkArgumentNotNull(contact, "Must specify contact key");
@@ -74,14 +72,14 @@ public class DesignatedContact extends ImmutableObject implements UnsafeSerializ
Type type;
@Index Key<ContactResource> contact;
@Ignore VKey<ContactResource> contactVKey;
@Index Key<Contact> contact;
@Ignore VKey<Contact> contactVKey;
public Type getType() {
return type;
}
public VKey<ContactResource> getContactKey() {
public VKey<Contact> getContactKey() {
return contactVKey;
}

View File

@@ -90,13 +90,13 @@ public class Domain extends DomainBase implements ForeignKeyedEppResource {
@Access(AccessType.PROPERTY)
@Column(name = "host_repo_id")
public Set<VKey<Host>> getNsHosts() {
return super.nsHosts;
return nsHosts;
}
/**
* Returns the set of {@link GracePeriod} associated with the domain.
*
* <p>This is the getter method specific for Hibernate to access the field so it is set to
* <p>This is the getter method specific for Hibernate to access the field, so it is set to
* private. The caller can use the public {@link #getGracePeriods()} to get the grace periods.
*
* <p>Note that we need to set `insertable = false, updatable = false` for @JoinColumn, otherwise
@@ -104,10 +104,7 @@ public class Domain extends DomainBase implements ForeignKeyedEppResource {
* deleting the whole entry from the table when the {@link GracePeriod} is removed from the set.
*/
@Access(AccessType.PROPERTY)
@OneToMany(
cascade = {CascadeType.ALL},
fetch = FetchType.EAGER,
orphanRemoval = true)
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
@JoinColumn(
name = "domainRepoId",
referencedColumnName = "repoId",
@@ -121,7 +118,7 @@ public class Domain extends DomainBase implements ForeignKeyedEppResource {
/**
* Returns the set of {@link DelegationSignerData} associated with the domain.
*
* <p>This is the getter method specific for Hibernate to access the field so it is set to
* <p>This is the getter method specific for Hibernate to access the field, so it is set to
* private. The caller can use the public {@link #getDsData()} to get the DS data.
*
* <p>Note that we need to set `insertable = false, updatable = false` for @JoinColumn, otherwise
@@ -130,10 +127,7 @@ public class Domain extends DomainBase implements ForeignKeyedEppResource {
* the set.
*/
@Access(AccessType.PROPERTY)
@OneToMany(
cascade = {CascadeType.ALL},
fetch = FetchType.EAGER,
orphanRemoval = true)
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
@JoinColumn(
name = "domainRepoId",
referencedColumnName = "repoId",
@@ -182,10 +176,9 @@ public class Domain extends DomainBase implements ForeignKeyedEppResource {
}
public Builder copyFrom(DomainBase domainBase) {
this.getInstance().copyUpdateTimestamp(domainBase);
return this.setAuthInfo(domainBase.getAuthInfo())
.setAutorenewPollMessage(
domainBase.getAutorenewPollMessage(), domainBase.getAutorenewPollMessageHistoryId())
getInstance().copyUpdateTimestamp(domainBase);
return setAuthInfo(domainBase.getAuthInfo())
.setAutorenewPollMessage(domainBase.getAutorenewPollMessage())
.setAutorenewBillingEvent(domainBase.getAutorenewBillingEvent())
.setAutorenewEndTime(domainBase.getAutorenewEndTime())
.setContacts(domainBase.getContacts())
@@ -210,7 +203,8 @@ public class Domain extends DomainBase implements ForeignKeyedEppResource {
.setSubordinateHosts(domainBase.getSubordinateHosts())
.setStatusValues(domainBase.getStatusValues())
.setTransferData(domainBase.getTransferData())
.setDnsRefreshRequestTime(domainBase.getDnsRefreshRequestTime());
.setDnsRefreshRequestTime(domainBase.getDnsRefreshRequestTime())
.setCurrentPackageToken(domainBase.getCurrentPackageToken().orElse(null));
}
}
}

View File

@@ -14,12 +14,11 @@
package google.registry.model.domain;
import com.googlecode.objectify.annotation.Embed;
import google.registry.model.eppcommon.AuthInfo;
import javax.persistence.Embeddable;
/** A version of authInfo specifically for domains. */
@Embed
@javax.persistence.Embeddable
@Embeddable
public class DomainAuthInfo extends AuthInfo {
public static DomainAuthInfo create(PasswordAuth pw) {
DomainAuthInfo instance = new DomainAuthInfo();

View File

@@ -41,7 +41,6 @@ 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.Key;
import com.googlecode.objectify.annotation.Ignore;
import com.googlecode.objectify.annotation.IgnoreSave;
import com.googlecode.objectify.annotation.Index;
@@ -51,15 +50,14 @@ 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.common.EntityGroupRoot;
import google.registry.model.contact.ContactResource;
import google.registry.model.contact.Contact;
import google.registry.model.domain.launch.LaunchNotice;
import google.registry.model.domain.rgp.GracePeriodStatus;
import google.registry.model.domain.secdns.DelegationSignerData;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.host.Host;
import google.registry.model.poll.PollMessage;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.tld.Registry;
import google.registry.model.transfer.DomainTransferData;
import google.registry.model.transfer.TransferStatus;
@@ -131,13 +129,14 @@ public class DomainBase extends EppResource
@EmptySetToNull @Index @Transient Set<VKey<Host>> nsHosts;
/** Contacts. */
VKey<ContactResource> adminContact;
VKey<Contact> adminContact;
VKey<ContactResource> billingContact;
VKey<ContactResource> techContact;
VKey<ContactResource> registrantContact;
VKey<Contact> billingContact;
VKey<Contact> techContact;
VKey<Contact> registrantContact;
/** Authorization info (aka transfer secret) of the domain. */
@Ignore
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "pw.value", column = @Column(name = "auth_info_value")),
@@ -146,13 +145,14 @@ public class DomainBase extends EppResource
DomainAuthInfo authInfo;
/** Data used to construct DS records for this domain. */
@Transient Set<DelegationSignerData> dsData;
@Ignore @Transient Set<DelegationSignerData> 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")),
@@ -215,15 +215,8 @@ public class DomainBase extends EppResource
@Column(name = "autorenew_poll_message_id")
VKey<PollMessage.Autorenew> autorenewPollMessage;
/**
* History record for the autorenew poll message.
*
* <p>Here so we can restore the original ofy key from sql.
*/
@Ignore Long autorenewPollMessageHistoryId;
/** The unexpired grace periods for this domain (some of which may not be active yet). */
@Transient Set<GracePeriod> gracePeriods;
@Ignore @Transient Set<GracePeriod> gracePeriods;
/**
* The id of the signed mark that was used to create this domain in sunrise.
@@ -234,7 +227,7 @@ public class DomainBase extends EppResource
String smdId;
/** Data about any pending or past transfers on this domain. */
DomainTransferData transferData;
@Ignore DomainTransferData transferData;
/**
* The time that this resource was last transferred.
@@ -282,6 +275,9 @@ public class DomainBase extends EppResource
// TODO(mcilwain): Start using this field once we are further along in the DB migration.
@Ignore DateTime dnsRefreshRequestTime;
/** The {@link AllocationToken} for the package this domain is currently a part of. */
@Ignore @Nullable VKey<AllocationToken> currentPackageToken;
/**
* Returns the DNS refresh request time iff this domain's DNS needs refreshing, otherwise absent.
*/
@@ -289,15 +285,6 @@ public class DomainBase extends EppResource
return Optional.ofNullable(dnsRefreshRequestTime);
}
public static <T> VKey<T> restoreOfyFrom(Key<Domain> domainKey, VKey<T> key, Long historyId) {
if (historyId == null) {
// This is a legacy key (or a null key, in which case this works too)
return VKey.restoreOfyFrom(key, EntityGroupRoot.class, "per-tld");
} else {
return VKey.restoreOfyFrom(key, domainKey, HistoryEntry.class, historyId);
}
}
public ImmutableSet<String> getSubordinateHosts() {
return nullToEmptyImmutableCopy(subordinateHosts);
}
@@ -318,10 +305,6 @@ public class DomainBase extends EppResource
return autorenewPollMessage;
}
public Long getAutorenewPollMessageHistoryId() {
return autorenewPollMessageHistoryId;
}
public ImmutableSet<GracePeriod> getGracePeriods() {
return nullToEmptyImmutableCopy(gracePeriods);
}
@@ -330,6 +313,10 @@ public class DomainBase extends EppResource
return smdId;
}
public Optional<VKey<AllocationToken>> getCurrentPackageToken() {
return Optional.ofNullable(currentPackageToken);
}
/**
* Returns the autorenew end time if there is one, otherwise empty.
*
@@ -501,9 +488,7 @@ public class DomainBase extends EppResource
// Set the speculatively-written new autorenew events as the domain's autorenew
// events.
.setAutorenewBillingEvent(transferData.getServerApproveAutorenewEvent())
.setAutorenewPollMessage(
transferData.getServerApproveAutorenewPollMessage(),
transferData.getServerApproveAutorenewPollMessageHistoryId());
.setAutorenewPollMessage(transferData.getServerApproveAutorenewPollMessage());
if (transferData.getTransferPeriod().getValue() == 1) {
// Set the grace period using a key to the prescheduled transfer billing event. Not using
// GracePeriod.forBillingEvent() here in order to avoid the actual Datastore fetch.
@@ -607,19 +592,19 @@ public class DomainBase extends EppResource
}
/** A key to the registrant who registered this domain. */
public VKey<ContactResource> getRegistrant() {
public VKey<Contact> getRegistrant() {
return registrantContact;
}
public VKey<ContactResource> getAdminContact() {
public VKey<Contact> getAdminContact() {
return adminContact;
}
public VKey<ContactResource> getBillingContact() {
public VKey<Contact> getBillingContact() {
return billingContact;
}
public VKey<ContactResource> getTechContact() {
public VKey<Contact> getTechContact() {
return techContact;
}
@@ -633,7 +618,7 @@ public class DomainBase extends EppResource
}
/** Returns all referenced contacts from this domain. */
public ImmutableSet<VKey<ContactResource>> getReferencedContacts() {
public ImmutableSet<VKey<Contact>> getReferencedContacts() {
return nullToEmptyImmutableCopy(getAllContacts(true)).stream()
.map(DesignatedContact::getContactKey)
.filter(Objects::nonNull)
@@ -776,7 +761,7 @@ public class DomainBase extends EppResource
return thisCastToDerived();
}
public B setRegistrant(VKey<ContactResource> registrant) {
public B setRegistrant(VKey<Contact> registrant) {
// Set the registrant field specifically.
getInstance().registrantContact = registrant;
return thisCastToDerived();
@@ -878,11 +863,8 @@ public class DomainBase extends EppResource
return thisCastToDerived();
}
public B setAutorenewPollMessage(
@Nullable VKey<PollMessage.Autorenew> autorenewPollMessage,
@Nullable Long autorenewPollMessageHistoryId) {
public B setAutorenewPollMessage(@Nullable VKey<PollMessage.Autorenew> autorenewPollMessage) {
getInstance().autorenewPollMessage = autorenewPollMessage;
getInstance().autorenewPollMessageHistoryId = autorenewPollMessageHistoryId;
return thisCastToDerived();
}
@@ -938,5 +920,10 @@ public class DomainBase extends EppResource
getInstance().lastTransferTime = lastTransferTime;
return thisCastToDerived();
}
public B setCurrentPackageToken(@Nullable VKey<AllocationToken> currentPackageToken) {
getInstance().currentPackageToken = currentPackageToken;
return thisCastToDerived();
}
}
}

View File

@@ -32,7 +32,7 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import google.registry.model.EppResource;
import google.registry.model.ImmutableObject;
import google.registry.model.contact.ContactResource;
import google.registry.model.contact.Contact;
import google.registry.model.eppinput.ResourceCommand.AbstractSingleResourceCommand;
import google.registry.model.eppinput.ResourceCommand.ResourceCheck;
import google.registry.model.eppinput.ResourceCommand.ResourceCreateOrChange;
@@ -80,7 +80,7 @@ public class DomainCommand {
String registrantContactId;
/** A resolved key to the registrant who registered this domain. */
@XmlTransient VKey<ContactResource> registrant;
@XmlTransient VKey<Contact> registrant;
/** Authorization info (aka transfer secret) of the domain. */
DomainAuthInfo authInfo;
@@ -90,7 +90,7 @@ public class DomainCommand {
}
@Nullable
public VKey<ContactResource> getRegistrant() {
public VKey<Contact> getRegistrant() {
return registrant;
}
@@ -391,7 +391,7 @@ public class DomainCommand {
? null
: getOnlyElement(
loadByForeignKeysCached(
ImmutableSet.of(clone.registrantContactId), ContactResource.class, now)
ImmutableSet.of(clone.registrantContactId), Contact.class, now)
.values());
return clone;
}
@@ -431,8 +431,8 @@ public class DomainCommand {
for (ForeignKeyedDesignatedContact contact : contacts) {
foreignKeys.add(contact.contactId);
}
ImmutableMap<String, VKey<ContactResource>> loadedContacts =
loadByForeignKeysCached(foreignKeys.build(), ContactResource.class, now);
ImmutableMap<String, VKey<Contact>> loadedContacts =
loadByForeignKeysCached(foreignKeys.build(), Contact.class, now);
ImmutableSet.Builder<DesignatedContact> linkedContacts = new ImmutableSet.Builder<>();
for (ForeignKeyedDesignatedContact contact : contacts) {
linkedContacts.add(

View File

@@ -20,6 +20,7 @@ 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;
@@ -133,6 +134,7 @@ public class DomainHistory extends HistoryEntry {
updatable = false)
})
// HashSet rather than ImmutableSet so that Hibernate can fill them out lazily on request
@Ignore
Set<DomainDsDataHistory> dsDataHistories = new HashSet<>();
@DoNotCompare
@@ -153,6 +155,7 @@ 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

View File

@@ -19,7 +19,6 @@ import static google.registry.model.IdService.allocateId;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import com.google.common.annotations.VisibleForTesting;
import com.googlecode.objectify.annotation.Embed;
import google.registry.model.billing.BillingEvent;
import google.registry.model.billing.BillingEvent.Recurring;
import google.registry.model.domain.DomainHistory.DomainHistoryId;
@@ -40,7 +39,6 @@ import org.joda.time.DateTime;
* <p>When a grace period expires, it is lazily removed from the {@link Domain} the next time the
* resource is loaded from Datastore.
*/
@Embed
@Entity
@Table(
indexes = {

View File

@@ -14,8 +14,6 @@
package google.registry.model.domain;
import com.googlecode.objectify.annotation.Embed;
import com.googlecode.objectify.annotation.Ignore;
import google.registry.model.ImmutableObject;
import google.registry.model.UnsafeSerializable;
import google.registry.model.billing.BillingEvent;
@@ -31,7 +29,6 @@ import javax.persistence.Transient;
import org.joda.time.DateTime;
/** Base class containing common fields and methods for {@link GracePeriod}. */
@Embed
@MappedSuperclass
@Access(AccessType.FIELD)
public class GracePeriodBase extends ImmutableObject implements UnsafeSerializable {
@@ -40,7 +37,6 @@ public class GracePeriodBase extends ImmutableObject implements UnsafeSerializab
@Transient long gracePeriodId;
/** Repository id for the domain which this grace period belongs to. */
@Ignore
@Column(nullable = false)
String domainRepoId;
@@ -65,7 +61,6 @@ public class GracePeriodBase extends ImmutableObject implements UnsafeSerializab
// NB: Would @IgnoreSave(IfNull.class), but not allowed for @Embed collections.
@Access(AccessType.FIELD)
@Column(name = "billing_event_id")
@Ignore
VKey<BillingEvent.OneTime> billingEventOneTime = null;
/**
@@ -75,7 +70,6 @@ public class GracePeriodBase extends ImmutableObject implements UnsafeSerializab
// NB: Would @IgnoreSave(IfNull.class), but not allowed for @Embed collections.
@Access(AccessType.FIELD)
@Column(name = "billing_recurrence_id")
@Ignore
VKey<BillingEvent.Recurring> billingEventRecurring = null;
public long getGracePeriodId() {

View File

@@ -14,9 +14,9 @@
package google.registry.model.domain;
import com.googlecode.objectify.annotation.Embed;
import google.registry.model.ImmutableObject;
import google.registry.model.UnsafeSerializable;
import javax.persistence.Embeddable;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.xml.bind.annotation.XmlAttribute;
@@ -24,8 +24,7 @@ import javax.xml.bind.annotation.XmlEnumValue;
import javax.xml.bind.annotation.XmlValue;
/** The "periodType" from <a href="http://tools.ietf.org/html/rfc5731">RFC5731</a>. */
@Embed
@javax.persistence.Embeddable
@Embeddable
public class Period extends ImmutableObject implements UnsafeSerializable {
@Enumerated(EnumType.STRING)

View File

@@ -23,12 +23,10 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS;
import com.google.common.base.Ascii;
import com.google.common.base.CharMatcher;
import com.google.common.primitives.Ints;
import com.googlecode.objectify.annotation.Embed;
import com.googlecode.objectify.annotation.IgnoreSave;
import com.googlecode.objectify.condition.IfNull;
import google.registry.model.ImmutableObject;
import google.registry.model.UnsafeSerializable;
import java.util.Optional;
import javax.persistence.Embeddable;
import javax.persistence.Embedded;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
@@ -37,17 +35,15 @@ import javax.xml.bind.annotation.XmlValue;
import org.joda.time.DateTime;
/** The claims notice id from the claims phase. */
@Embed
@XmlType(propOrder = {"noticeId", "expirationTime", "acceptedTime"})
@javax.persistence.Embeddable
@Embeddable
public class LaunchNotice extends ImmutableObject implements UnsafeSerializable {
/** An empty instance to use in place of null. */
private static final NoticeIdType EMPTY_NOTICE_ID = new NoticeIdType();
/** An id with a validator-id attribute. */
@Embed
@javax.persistence.Embeddable
@Embeddable
public static class NoticeIdType extends ImmutableObject implements UnsafeSerializable {
/**
@@ -57,7 +53,6 @@ public class LaunchNotice extends ImmutableObject implements UnsafeSerializable
@XmlValue String tcnId;
/** The identifier of the TMDB provider to use, defaulting to the TMCH. */
@IgnoreSave(IfNull.class)
@XmlAttribute(name = "validatorID")
String validatorId;

View File

@@ -16,7 +16,6 @@ package google.registry.model.domain.launch;
import static java.util.Objects.hash;
import com.googlecode.objectify.annotation.Embed;
import google.registry.model.ImmutableObject;
import java.util.Objects;
import javax.xml.bind.annotation.XmlAttribute;
@@ -47,7 +46,6 @@ import javax.xml.bind.annotation.XmlValue;
* sets it is the one that needs to make sure the domain isn't a trademark and that the fields are
* correct.
*/
@Embed
public class LaunchPhase extends ImmutableObject {
/**

View File

@@ -0,0 +1,23 @@
// 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.model.domain.packagetoken;
import google.registry.model.ImmutableObject;
import google.registry.model.eppinput.EppInput.CommandExtension;
import javax.xml.bind.annotation.XmlRootElement;
/** A package token extension that may be present on EPP domain commands. */
@XmlRootElement(name = "info")
public class PackageTokenExtension extends ImmutableObject implements CommandExtension {}

View File

@@ -0,0 +1,45 @@
// 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.model.domain.packagetoken;
import google.registry.model.ImmutableObject;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.eppoutput.EppResponse.ResponseExtension;
import google.registry.persistence.VKey;
import google.registry.xml.TrimWhitespaceAdapter;
import java.util.Optional;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
/**
* An XML data object that represents a package token extension that may be present on the response
* to EPP domain info commands.
*/
@XmlRootElement(name = "packageData")
public class PackageTokenResponseExtension extends ImmutableObject implements ResponseExtension {
/** Token string of the PACKAGE token the name belongs to. */
@XmlJavaTypeAdapter(TrimWhitespaceAdapter.class)
String token;
public static PackageTokenResponseExtension create(Optional<VKey<AllocationToken>> tokenKey) {
PackageTokenResponseExtension instance = new PackageTokenResponseExtension();
instance.token = "";
if (tokenKey.isPresent()) {
instance.token = tokenKey.get().getSqlKey().toString();
}
return instance;
}
}

View File

@@ -0,0 +1,27 @@
// 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.
@XmlSchema(
namespace = "urn:google:params:xml:ns:packageToken-1.0",
xmlns =
@XmlNs(prefix = "packageToken", namespaceURI = "urn:google:params:xml:ns:packageToken-1.0"),
elementFormDefault = XmlNsForm.QUALIFIED)
@XmlAccessorType(XmlAccessType.FIELD)
package google.registry.model.domain.packagetoken;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlNs;
import javax.xml.bind.annotation.XmlNsForm;
import javax.xml.bind.annotation.XmlSchema;

View File

@@ -16,7 +16,6 @@ package google.registry.model.domain.secdns;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import com.googlecode.objectify.annotation.Embed;
import google.registry.model.ImmutableObject;
import google.registry.model.domain.secdns.DelegationSignerData.DomainDsDataId;
import java.io.Serializable;
@@ -37,7 +36,6 @@ import javax.xml.bind.annotation.XmlType;
* @see <a href="http://tools.ietf.org/html/rfc4034">RFC 4034</a>
* <p>TODO(b/177567432): Rename this class to DomainDsData.
*/
@Embed
@XmlType(name = "dsData")
@Entity
@IdClass(DomainDsDataId.class)

View File

@@ -14,8 +14,6 @@
package google.registry.model.domain.secdns;
import com.googlecode.objectify.annotation.Embed;
import com.googlecode.objectify.annotation.Ignore;
import google.registry.model.ImmutableObject;
import google.registry.model.UnsafeSerializable;
import javax.persistence.Access;
@@ -29,12 +27,11 @@ import javax.xml.bind.annotation.adapters.HexBinaryAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
/** Base class for {@link DelegationSignerData} and {@link DomainDsDataHistory}. */
@Embed
@MappedSuperclass
@Access(AccessType.FIELD)
public abstract class DomainDsDataBase extends ImmutableObject implements UnsafeSerializable {
@Ignore @XmlTransient @Transient String domainRepoId;
@XmlTransient @Transient String domainRepoId;
/** The identifier for this particular key in the domain. */
@Transient int keyTag;

View File

@@ -15,14 +15,12 @@
package google.registry.model.domain.secdns;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.annotation.Embed;
import google.registry.model.ImmutableObject;
import google.registry.model.eppoutput.EppResponse.ResponseExtension;
import javax.xml.bind.annotation.XmlRootElement;
/** The EPP secDNS extension to be returned with domain info commands. */
@XmlRootElement(name = "infData")
@Embed
public class SecDnsInfoExtension extends ImmutableObject implements ResponseExtension {
/** Signatures for this domain. */

View File

@@ -26,22 +26,16 @@ import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Range;
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.OnLoad;
import google.registry.flows.EppException;
import google.registry.flows.domain.DomainFlowUtils;
import google.registry.model.BackupGroupRoot;
import google.registry.model.Buildable;
import google.registry.model.CreateAutoTimestamp;
import google.registry.model.annotations.ReportedOn;
import google.registry.model.billing.BillingEvent.RenewalPriceBehavior;
import google.registry.model.common.TimedTransitionProperty;
import google.registry.model.reporting.HistoryEntry;
@@ -54,31 +48,31 @@ import javax.annotation.Nullable;
import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.Table;
import org.joda.time.DateTime;
/** An entity representing an allocation token. */
@ReportedOn
@Entity
@WithStringVKey
@javax.persistence.Entity
@WithStringVKey(compositeKey = true)
@Table(
indexes = {
@javax.persistence.Index(
columnList = "token",
name = "allocation_token_token_idx",
unique = true),
@javax.persistence.Index(
columnList = "domainName",
name = "allocation_token_domain_name_idx"),
@javax.persistence.Index(columnList = "tokenType"),
@javax.persistence.Index(columnList = "redemption_domain_repo_id")
@Index(columnList = "token", name = "allocation_token_token_idx", unique = true),
@Index(columnList = "domainName", name = "allocation_token_domain_name_idx"),
@Index(columnList = "tokenType"),
@Index(columnList = "redemption_domain_repo_id")
})
public class AllocationToken extends BackupGroupRoot implements Buildable {
private static final long serialVersionUID = -3954475393220876903L;
private static final String REMOVE_PACKAGE = "__REMOVEPACKAGE__";
private static final ImmutableMap<String, TokenBehavior> STATIC_TOKEN_BEHAVIORS =
ImmutableMap.of(REMOVE_PACKAGE, TokenBehavior.REMOVE_PACKAGE);
// Promotions should only move forward, and ENDED / CANCELLED are terminal states.
private static final ImmutableMultimap<TokenStatus, TokenStatus> VALID_TOKEN_STATUS_TRANSITIONS =
@@ -87,6 +81,18 @@ public class AllocationToken extends BackupGroupRoot implements Buildable {
.putAll(VALID, ENDED, CANCELLED)
.build();
private static final ImmutableMap<String, AllocationToken> BEHAVIORAL_TOKENS =
ImmutableMap.of(
REMOVE_PACKAGE,
new AllocationToken.Builder()
.setTokenType(TokenType.UNLIMITED_USE)
.setToken(REMOVE_PACKAGE)
.build());
public static Optional<AllocationToken> maybeGetStaticTokenInstance(String name) {
return Optional.ofNullable(BEHAVIORAL_TOKENS.get(name));
}
/** Any special behavior that should be used when registering domains using this token. */
public enum RegistrationBehavior {
/** No special behavior */
@@ -103,10 +109,28 @@ public class AllocationToken extends BackupGroupRoot implements Buildable {
ANCHOR_TENANT
}
/** Single-use tokens are invalid after use. Infinite-use tokens, predictably, are not. */
/**
* Single-use tokens are invalid after use. Infinite-use tokens, predictably, are not. Package
* tokens are used in package promotions.
*/
public enum TokenType {
PACKAGE,
SINGLE_USE,
UNLIMITED_USE
UNLIMITED_USE,
}
/**
* System behaves differently based on a token it gets inside a command. This enumerates different
* types of behaviors we support.
*/
public enum TokenBehavior {
/** No special behavior */
DEFAULT,
/**
* REMOVE_PACKAGE triggers domain removal from promotional package, bypasses DEFAULT token
* validations.
*/
REMOVE_PACKAGE
}
/** The status of this token with regard to any potential promotion. */
@@ -122,11 +146,10 @@ public class AllocationToken extends BackupGroupRoot implements Buildable {
}
/** The allocation token string. */
@javax.persistence.Id @Id String token;
@Id String token;
/** The key of the history entry for which the token was used. Null if not yet used. */
@Nullable
@Index
@AttributeOverrides({
@AttributeOverride(name = "repoId", column = @Column(name = "redemption_domain_repo_id")),
@AttributeOverride(
@@ -136,10 +159,10 @@ public class AllocationToken extends BackupGroupRoot implements Buildable {
DomainHistoryVKey redemptionHistoryEntry;
/** The fully-qualified domain name that this token is limited to, if any. */
@Nullable @Index String domainName;
@Nullable String domainName;
/** When this token was created. */
@Ignore CreateAutoTimestamp creationTime = CreateAutoTimestamp.create(null);
CreateAutoTimestamp creationTime = CreateAutoTimestamp.create(null);
/** Allowed registrar client IDs for this token, or null if all registrars are allowed. */
@Column(name = "allowedRegistrarIds")
@@ -168,21 +191,12 @@ public class AllocationToken extends BackupGroupRoot implements Buildable {
@Enumerated(EnumType.STRING)
@Column(name = "renewalPriceBehavior", nullable = false)
@Ignore
RenewalPriceBehavior renewalPriceBehavior = RenewalPriceBehavior.DEFAULT;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
RegistrationBehavior registrationBehavior = RegistrationBehavior.DEFAULT;
// TODO: Remove onLoad once all allocation tokens are migrated to have a discountYears of 1.
@OnLoad
void onLoad() {
if (discountYears == 0) {
discountYears = 1;
}
}
/**
* Promotional token validity periods.
*
@@ -251,9 +265,17 @@ public class AllocationToken extends BackupGroupRoot implements Buildable {
return registrationBehavior;
}
public TokenBehavior getTokenBehavior() {
return STATIC_TOKEN_BEHAVIORS.getOrDefault(token, TokenBehavior.DEFAULT);
}
@Override
public VKey<AllocationToken> createVKey() {
return VKey.create(AllocationToken.class, getToken(), Key.create(this));
if (!AllocationToken.TokenBehavior.DEFAULT.equals(getTokenBehavior())) {
throw new IllegalArgumentException(
String.format("%s tokens are not stored in the database", getTokenBehavior()));
}
return VKey.createSql(AllocationToken.class, getToken());
}
@Override
@@ -274,6 +296,10 @@ public class AllocationToken extends BackupGroupRoot implements Buildable {
public AllocationToken build() {
checkArgumentNotNull(getInstance().tokenType, "Token type must be specified");
checkArgument(!Strings.isNullOrEmpty(getInstance().token), "Token must not be null or empty");
checkArgument(
!getInstance().tokenType.equals(TokenType.PACKAGE)
|| getInstance().renewalPriceBehavior.equals(RenewalPriceBehavior.SPECIFIED),
"Package tokens must have renewalPriceBehavior set to SPECIFIED");
checkArgument(
getInstance().domainName == null || TokenType.SINGLE_USE.equals(getInstance().tokenType),
"Domain name can only be specified for SINGLE_USE tokens");
@@ -281,6 +307,11 @@ public class AllocationToken extends BackupGroupRoot implements Buildable {
getInstance().redemptionHistoryEntry == null
|| TokenType.SINGLE_USE.equals(getInstance().tokenType),
"Redemption history entry can only be specified for SINGLE_USE tokens");
checkArgument(
getInstance().tokenType != TokenType.PACKAGE
|| (getInstance().allowedClientIds != null
&& getInstance().allowedClientIds.size() == 1),
"PACKAGE tokens must have exactly one allowed client registrar");
checkArgument(
getInstance().discountFraction > 0 || !getInstance().discountPremiums,
"Discount premiums can only be specified along with a discount fraction");

View File

@@ -0,0 +1,157 @@
// 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.model.domain.token;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import google.registry.model.Buildable;
import google.registry.model.ImmutableObject;
import google.registry.model.domain.token.AllocationToken.TokenType;
import google.registry.persistence.VKey;
import google.registry.persistence.converter.JodaMoneyType;
import java.util.Optional;
import javax.annotation.Nullable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.hibernate.annotations.Columns;
import org.hibernate.annotations.Type;
import org.joda.money.Money;
import org.joda.time.DateTime;
/** An entity representing a package promotion. */
@Entity
@javax.persistence.Table(indexes = {@javax.persistence.Index(columnList = "token")})
public class PackagePromotion extends ImmutableObject implements Buildable {
/** An autogenerated identifier for the package promotion. */
@Id long packagePromotionId;
/** The allocation token string for the package. */
@Column(nullable = false)
VKey<AllocationToken> token;
/** The maximum number of active domains the package allows at any given time. */
@Column(nullable = false)
int maxDomains;
/** The maximum number of domains that can be created in the package each year. */
@Column(nullable = false)
int maxCreates;
/** The annual price of the package. */
@Type(type = JodaMoneyType.TYPE_NAME)
@Columns(
columns = {
@Column(name = "package_price_amount", nullable = false),
@Column(name = "package_price_currency", nullable = false)
})
Money packagePrice;
/** The next billing date of the package. */
@Column(nullable = false)
DateTime nextBillingDate = END_OF_TIME;
/** Date the last warning email was sent that the package has exceeded the maxDomains limit. */
@Nullable DateTime lastNotificationSent;
public VKey<AllocationToken> getToken() {
return token;
}
public int getMaxDomains() {
return maxDomains;
}
public int getMaxCreates() {
return maxCreates;
}
public Money getPackagePrice() {
return packagePrice;
}
public DateTime getNextBillingDate() {
return nextBillingDate;
}
public Optional<DateTime> getLastNotificationSent() {
return Optional.ofNullable(lastNotificationSent);
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));
}
/** A builder for constructing {@link PackagePromotion} objects, since they are immutable. */
public static class Builder extends Buildable.Builder<PackagePromotion> {
public Builder() {}
private Builder(PackagePromotion instance) {
super(instance);
}
@Override
public PackagePromotion build() {
checkArgumentNotNull(getInstance().token, "Allocation token must be specified");
AllocationToken allocationToken = tm().transact(() -> tm().loadByKey(getInstance().token));
checkArgument(
allocationToken.tokenType == TokenType.PACKAGE,
"Allocation token must be a PACKAGE type");
return super.build();
}
public Builder setToken(AllocationToken token) {
checkArgumentNotNull(token, "Allocation token must not be null");
checkArgument(
token.tokenType == TokenType.PACKAGE, "Allocation token must be a PACKAGE type");
getInstance().token = token.createVKey();
return this;
}
public Builder setMaxDomains(int maxDomains) {
checkArgumentNotNull(maxDomains, "maxDomains must not be null");
getInstance().maxDomains = maxDomains;
return this;
}
public Builder setMaxCreates(int maxCreates) {
checkArgumentNotNull(maxCreates, "maxCreates must not be null");
getInstance().maxCreates = maxCreates;
return this;
}
public Builder setPackagePrice(Money packagePrice) {
checkArgumentNotNull(packagePrice, "Package price must not be null");
getInstance().packagePrice = packagePrice;
return this;
}
public Builder setNextBillingDate(@Nullable DateTime nextBillingDate) {
checkArgumentNotNull(nextBillingDate, "Next billing date must not be null");
getInstance().nextBillingDate = nextBillingDate;
return this;
}
public Builder setLastNotificationSent(@Nullable DateTime lastNotificationSent) {
getInstance().lastNotificationSent = lastNotificationSent;
return this;
}
}
}

View File

@@ -15,14 +15,11 @@
package google.registry.model.eppcommon;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Strings.nullToEmpty;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.googlecode.objectify.annotation.AlsoLoad;
import com.googlecode.objectify.annotation.Ignore;
import google.registry.model.Buildable;
import google.registry.model.ImmutableObject;
import google.registry.model.JsonMapBuilder;
@@ -59,40 +56,62 @@ import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
@MappedSuperclass
public class Address extends ImmutableObject implements Jsonifiable, UnsafeSerializable {
/** The schema validation will enforce that this has 3 lines at most. */
// TODO(b/177569726): Remove this field after migration. We need to figure out how to generate
// same XML from streetLine[1,2,3].
/**
* At most three lines of addresses parsed from XML elements.
*
* <p>This field is used to marshal to/unmarshal from XML elements. When persisting to/from SQL,
* the next three separate fields are used. Those lines are <em>only</em> used for persistence
* purpose and should not be directly used in Java.
*
* <p>We need to keep the list and the three fields in sync in all the following scenarios when an
* entity containing a {@link Address} is created:
*
* <ul>
* <li>When creating an {@link Address} directly in java, using the {@link Builder}: The {@link
* Builder#setStreet(ImmutableList)} sets both the list and the fields.
* <li>When unmarshalling from XML: The list will be set based on the content of the XML.
* Afterwards, {@link #afterUnmarshal(Unmarshaller, Object)}} will be called to set the
* fields.
* <li>When loading from the database: The fields will be set by the values in SQL. Afterwards,
* {@link #postLoad()} will be called to set the list.
* </ul>
*
* The syncing is especially important because when merging a detached entity into a session, JPA
* provides no guarantee that transient fields will be preserved. In fact, Hibernate chooses to
* discard them when returning a newly merged entity. This means that it is not enough to provide
* callbacks to populate the fields based on the list before persistence, as merging does not
* invoke the callbacks, and we would lose the address fields if only the list is set. Instead,
* the fields must be populated when the list is.
*
* <p>Schema validation will enforce the 3-line limit.
*/
@XmlJavaTypeAdapter(NormalizedStringAdapter.class)
@Transient
List<String> street;
protected List<String> street;
@Ignore @XmlTransient @IgnoredInDiffableMap String streetLine1;
@XmlTransient @IgnoredInDiffableMap protected String streetLine1;
@Ignore @XmlTransient @IgnoredInDiffableMap String streetLine2;
@XmlTransient @IgnoredInDiffableMap protected String streetLine2;
@Ignore @XmlTransient @IgnoredInDiffableMap String streetLine3;
@XmlTransient @IgnoredInDiffableMap protected String streetLine3;
@XmlJavaTypeAdapter(NormalizedStringAdapter.class)
String city;
protected String city;
@XmlElement(name = "sp")
@XmlJavaTypeAdapter(NormalizedStringAdapter.class)
String state;
protected String state;
@XmlElement(name = "pc")
@XmlJavaTypeAdapter(CollapsedStringAdapter.class)
String zip;
protected String zip;
@XmlElement(name = "cc")
@XmlJavaTypeAdapter(CollapsedStringAdapter.class)
String countryCode;
protected String countryCode;
public ImmutableList<String> getStreet() {
if (street == null && streetLine1 != null) {
return ImmutableList.of(streetLine1, nullToEmpty(streetLine2), nullToEmpty(streetLine3));
} else {
return nullToEmptyImmutableCopy(street);
}
return nullToEmptyImmutableCopy(street);
}
public String getCity() {
@@ -139,7 +158,13 @@ public class Address extends ImmutableObject implements Jsonifiable, UnsafeSeria
public Builder<T> setStreet(ImmutableList<String> street) {
checkArgument(
street == null || (!street.isEmpty() && street.size() <= 3),
"Street address must have [1-3] lines: %s", street);
"Street address must have [1-3] lines: %s",
street);
//noinspection ConstantConditions
checkArgument(
street.stream().noneMatch(String::isEmpty),
"Street address cannot contain empty string: %s",
street);
getInstance().street = street;
getInstance().streetLine1 = street.get(0);
getInstance().streetLine2 = street.size() >= 2 ? street.get(1) : null;
@@ -171,24 +196,13 @@ public class Address extends ImmutableObject implements Jsonifiable, UnsafeSeria
}
}
/**
* Sets {@link #streetLine1}, {@link #streetLine2} and {@link #streetLine3} after loading the
* entity from Datastore.
*
* <p>This callback method is used by Objectify to set streetLine[1,2,3] fields as they are not
* persisted in the Datastore.
*/
void onLoad(@AlsoLoad("street") List<String> street) {
mapStreetListToIndividualFields(street);
}
/**
* Sets {@link #street} after loading the entity from Cloud SQL.
*
* <p>This callback method is used by Hibernate to set {@link #street} field as it is not
* persisted in Cloud SQL. We are doing this because the street list field is exposed by Address
* class and is used everywhere in our code base. Also, setting/reading a list of strings is more
* convenient.
* <p>This callback method is used by Hibernate to set the {@link #street} field as it is not
* persisted in Cloud SQL. We are doing this because this field is exposed and used everywhere in
* our code base, whereas the individual {@code streetLine} fields are only used by Hibernate for
* persistence. Also, setting/reading a list of strings is more convenient.
*/
@PostLoad
void postLoad() {
@@ -206,12 +220,9 @@ public class Address extends ImmutableObject implements Jsonifiable, UnsafeSeria
*
* <p>This is a callback function that JAXB invokes after unmarshalling the XML message.
*/
@SuppressWarnings("unused")
void afterUnmarshal(Unmarshaller unmarshaller, Object parent) {
mapStreetListToIndividualFields(street);
}
private void mapStreetListToIndividualFields(List<String> street) {
if (street == null || street.size() == 0) {
if (street == null || street.isEmpty()) {
return;
}
streetLine1 = street.get(0);

View File

@@ -14,7 +14,6 @@
package google.registry.model.eppcommon;
import com.googlecode.objectify.annotation.Embed;
import google.registry.model.ImmutableObject;
import google.registry.model.UnsafeSerializable;
import javax.persistence.Embeddable;
@@ -45,7 +44,6 @@ public abstract class AuthInfo extends ImmutableObject implements UnsafeSerializ
}
/** The "pwAuthInfoType" complex type. */
@Embed
@XmlType(namespace = "urn:ietf:params:xml:ns:eppcom-1.0")
@Embeddable
public static class PasswordAuth extends ImmutableObject implements UnsafeSerializable {

View File

@@ -31,25 +31,27 @@ import java.io.ByteArrayOutputStream;
public class EppXmlTransformer {
// Hardcoded XML schemas, ordered with respect to dependency.
private static final ImmutableList<String> SCHEMAS = ImmutableList.of(
"eppcom.xsd",
"epp.xsd",
"contact.xsd",
"host.xsd",
"domain.xsd",
"rgp.xsd",
"secdns.xsd",
"fee06.xsd",
"fee11.xsd",
"fee12.xsd",
"metadata.xsd",
"mark.xsd",
"dsig.xsd",
"smd.xsd",
"launch.xsd",
"allocate.xsd",
"superuser.xsd",
"allocationToken-1.0.xsd");
private static final ImmutableList<String> SCHEMAS =
ImmutableList.of(
"eppcom.xsd",
"epp.xsd",
"contact.xsd",
"host.xsd",
"domain.xsd",
"rgp.xsd",
"secdns.xsd",
"fee06.xsd",
"fee11.xsd",
"fee12.xsd",
"metadata.xsd",
"mark.xsd",
"dsig.xsd",
"smd.xsd",
"launch.xsd",
"allocate.xsd",
"superuser.xsd",
"allocationToken-1.0.xsd",
"packageToken.xsd");
private static final XmlTransformer INPUT_TRANSFORMER =
new XmlTransformer(SCHEMAS, EppInput.class);

View File

@@ -14,7 +14,6 @@
package google.registry.model.eppcommon;
import com.googlecode.objectify.annotation.Embed;
import google.registry.model.ImmutableObject;
import java.io.Serializable;
import javax.persistence.Embeddable;
@@ -26,7 +25,6 @@ import javax.xml.bind.annotation.XmlTransient;
* <p>When placed in a field "foo", this will correctly unmarshal from both {@code <foo/>} and
* {@code <foo></foo>}, and will unmarshal always to {@code <foo/>}.
*/
@Embed
@Embeddable
public class PresenceMarker extends ImmutableObject implements Serializable {
@XmlTransient

View File

@@ -20,8 +20,8 @@ import static com.google.common.base.Strings.nullToEmpty;
import com.google.common.collect.ImmutableSet;
import google.registry.model.EppResource;
import google.registry.model.contact.Contact;
import google.registry.model.contact.ContactBase;
import google.registry.model.contact.ContactResource;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainBase;
import google.registry.model.host.Host;
@@ -130,12 +130,12 @@ public enum StatusValue implements EppEnum {
/** Enum to help clearly list which resource types a status value is allowed to be present on. */
private enum AllowedOn {
ALL(
Contact.class,
ContactBase.class,
ContactResource.class,
DomainBase.class,
Domain.class,
HostBase.class,
Host.class),
DomainBase.class,
Host.class,
HostBase.class),
NONE,
DOMAINS(DomainBase.class, Domain.class);

View File

@@ -16,11 +16,11 @@ package google.registry.model.eppcommon;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import com.googlecode.objectify.annotation.Embed;
import google.registry.model.ImmutableObject;
import google.registry.model.UnsafeSerializable;
import java.util.Optional;
import javax.annotation.Nullable;
import javax.persistence.Embeddable;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
@@ -30,9 +30,8 @@ import javax.xml.bind.annotation.XmlType;
* is formed using the {@code clTRID} associated with the command if supplied by the client and a
* {@code svTRID} (server transaction identifier) that is assigned by and unique to the server."
*/
@Embed
@XmlType(propOrder = {"clientTransactionId", "serverTransactionId"})
@javax.persistence.Embeddable
@Embeddable
public class Trid extends ImmutableObject implements UnsafeSerializable {
/** The server transaction id. */

View File

@@ -45,6 +45,7 @@ import google.registry.model.domain.launch.LaunchDeleteExtension;
import google.registry.model.domain.launch.LaunchInfoExtension;
import google.registry.model.domain.launch.LaunchUpdateExtension;
import google.registry.model.domain.metadata.MetadataExtension;
import google.registry.model.domain.packagetoken.PackageTokenExtension;
import google.registry.model.domain.rgp.RgpUpdateExtension;
import google.registry.model.domain.secdns.SecDnsCreateExtension;
import google.registry.model.domain.secdns.SecDnsUpdateExtension;
@@ -362,6 +363,7 @@ public class EppInput extends ImmutableObject {
// Other extensions
@XmlElementRef(type = AllocationTokenExtension.class),
@XmlElementRef(type = MetadataExtension.class),
@XmlElementRef(type = PackageTokenExtension.class),
@XmlElementRef(type = RgpUpdateExtension.class),
@XmlElementRef(type = SecDnsCreateExtension.class),
@XmlElementRef(type = SecDnsUpdateExtension.class)

View File

@@ -43,6 +43,7 @@ import google.registry.model.domain.fee12.FeeRenewResponseExtensionV12;
import google.registry.model.domain.fee12.FeeTransferResponseExtensionV12;
import google.registry.model.domain.fee12.FeeUpdateResponseExtensionV12;
import google.registry.model.domain.launch.LaunchCheckResponseExtension;
import google.registry.model.domain.packagetoken.PackageTokenResponseExtension;
import google.registry.model.domain.rgp.RgpInfoExtension;
import google.registry.model.domain.secdns.SecDnsInfoExtension;
import google.registry.model.eppcommon.Trid;
@@ -121,28 +122,30 @@ public class EppResponse extends ImmutableObject implements ResponseOrGreeting {
/** Zero or more response extensions. */
@XmlElementRefs({
@XmlElementRef(type = FeeCheckResponseExtensionV06.class),
@XmlElementRef(type = FeeInfoResponseExtensionV06.class),
@XmlElementRef(type = FeeCreateResponseExtensionV06.class),
@XmlElementRef(type = FeeDeleteResponseExtensionV06.class),
@XmlElementRef(type = FeeRenewResponseExtensionV06.class),
@XmlElementRef(type = FeeTransferResponseExtensionV06.class),
@XmlElementRef(type = FeeUpdateResponseExtensionV06.class),
@XmlElementRef(type = FeeCheckResponseExtensionV11.class),
@XmlElementRef(type = FeeCreateResponseExtensionV11.class),
@XmlElementRef(type = FeeDeleteResponseExtensionV11.class),
@XmlElementRef(type = FeeRenewResponseExtensionV11.class),
@XmlElementRef(type = FeeTransferResponseExtensionV11.class),
@XmlElementRef(type = FeeUpdateResponseExtensionV11.class),
@XmlElementRef(type = FeeCheckResponseExtensionV12.class),
@XmlElementRef(type = FeeCreateResponseExtensionV12.class),
@XmlElementRef(type = FeeDeleteResponseExtensionV12.class),
@XmlElementRef(type = FeeRenewResponseExtensionV12.class),
@XmlElementRef(type = FeeTransferResponseExtensionV12.class),
@XmlElementRef(type = FeeUpdateResponseExtensionV12.class),
@XmlElementRef(type = LaunchCheckResponseExtension.class),
@XmlElementRef(type = RgpInfoExtension.class),
@XmlElementRef(type = SecDnsInfoExtension.class) })
@XmlElementRef(type = FeeCheckResponseExtensionV06.class),
@XmlElementRef(type = FeeInfoResponseExtensionV06.class),
@XmlElementRef(type = FeeCreateResponseExtensionV06.class),
@XmlElementRef(type = FeeDeleteResponseExtensionV06.class),
@XmlElementRef(type = FeeRenewResponseExtensionV06.class),
@XmlElementRef(type = FeeTransferResponseExtensionV06.class),
@XmlElementRef(type = FeeUpdateResponseExtensionV06.class),
@XmlElementRef(type = FeeCheckResponseExtensionV11.class),
@XmlElementRef(type = FeeCreateResponseExtensionV11.class),
@XmlElementRef(type = FeeDeleteResponseExtensionV11.class),
@XmlElementRef(type = FeeRenewResponseExtensionV11.class),
@XmlElementRef(type = FeeTransferResponseExtensionV11.class),
@XmlElementRef(type = FeeUpdateResponseExtensionV11.class),
@XmlElementRef(type = FeeCheckResponseExtensionV12.class),
@XmlElementRef(type = FeeCreateResponseExtensionV12.class),
@XmlElementRef(type = FeeDeleteResponseExtensionV12.class),
@XmlElementRef(type = FeeRenewResponseExtensionV12.class),
@XmlElementRef(type = FeeTransferResponseExtensionV12.class),
@XmlElementRef(type = FeeUpdateResponseExtensionV12.class),
@XmlElementRef(type = LaunchCheckResponseExtension.class),
@XmlElementRef(type = PackageTokenResponseExtension.class),
@XmlElementRef(type = RgpInfoExtension.class),
@XmlElementRef(type = SecDnsInfoExtension.class)
})
@XmlElementWrapper(name = "extension")
ImmutableList<? extends ResponseExtension> extensions;

View File

@@ -19,10 +19,8 @@ import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static google.registry.config.RegistryConfig.getEppResourceCachingDuration;
import static google.registry.config.RegistryConfig.getEppResourceMaxCachedEntries;
import static google.registry.model.ofy.ObjectifyService.auditedOfy;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaJpaTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.CollectionUtils.entriesToImmutableMap;
import static google.registry.util.TypeUtils.instantiate;
@@ -36,18 +34,11 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimaps;
import com.google.common.collect.Streams;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
import com.googlecode.objectify.annotation.Index;
import google.registry.config.RegistryConfig;
import google.registry.model.BackupGroupRoot;
import google.registry.model.CacheUtils;
import google.registry.model.CacheUtils.AppEngineEnvironmentCacheLoader;
import google.registry.model.EppResource;
import google.registry.model.annotations.DeleteAfterMigration;
import google.registry.model.annotations.ReportedOn;
import google.registry.model.contact.ContactResource;
import google.registry.model.contact.Contact;
import google.registry.model.domain.Domain;
import google.registry.model.host.Host;
import google.registry.persistence.VKey;
@@ -67,56 +58,44 @@ import org.joda.time.DateTime;
* the foreign key string. The instance is never deleted, but it is updated if a newer entity
* becomes the active entity.
*/
@DeleteAfterMigration
public abstract class ForeignKeyIndex<E extends EppResource> extends BackupGroupRoot {
/** The {@link ForeignKeyIndex} type for {@link ContactResource} entities. */
@ReportedOn
@Entity
public static class ForeignKeyContactIndex extends ForeignKeyIndex<ContactResource> {}
/** The {@link ForeignKeyIndex} type for {@link Contact} entities. */
public static class ForeignKeyContactIndex extends ForeignKeyIndex<Contact> {}
/** The {@link ForeignKeyIndex} type for {@link Domain} entities. */
@ReportedOn
@Entity
public static class ForeignKeyDomainIndex extends ForeignKeyIndex<Domain> {}
/** The {@link ForeignKeyIndex} type for {@link Host} entities. */
@ReportedOn
@Entity
public static class ForeignKeyHostIndex extends ForeignKeyIndex<Host> {}
private static final ImmutableBiMap<
Class<? extends EppResource>, Class<? extends ForeignKeyIndex<?>>>
RESOURCE_CLASS_TO_FKI_CLASS =
ImmutableBiMap.of(
ContactResource.class, ForeignKeyContactIndex.class,
Contact.class, ForeignKeyContactIndex.class,
Domain.class, ForeignKeyDomainIndex.class,
Host.class, ForeignKeyHostIndex.class);
private static final ImmutableMap<Class<? extends EppResource>, String>
RESOURCE_CLASS_TO_FKI_PROPERTY =
ImmutableMap.of(
ContactResource.class, "contactId",
Contact.class, "contactId",
Domain.class, "fullyQualifiedDomainName",
Host.class, "fullyQualifiedHostName");
@Id String foreignKey;
String foreignKey;
/**
* The deletion time of this {@link ForeignKeyIndex}.
*
* <p>This will generally be equal to the deletion time of {@link #topReference}. However, in the
* <p>This will generally be equal to the deletion time of {@link #reference}. However, in the
* case of a {@link Host} that was renamed, this field will hold the time of the rename.
*/
@Index DateTime deletionTime;
DateTime deletionTime;
/**
* The referenced resource.
*
* <p>This field holds a key to the only referenced resource. It is named "topReference" for
* historical reasons.
*/
VKey<E> topReference;
/** The referenced resource. */
VKey<E> reference;
public String getForeignKey() {
return foreignKey;
@@ -127,7 +106,7 @@ public abstract class ForeignKeyIndex<E extends EppResource> extends BackupGroup
}
public VKey<E> getResourceKey() {
return topReference;
return reference;
}
@SuppressWarnings("unchecked")
@@ -137,26 +116,19 @@ public abstract class ForeignKeyIndex<E extends EppResource> extends BackupGroup
}
/** Create a {@link ForeignKeyIndex} instance for a resource, expiring at a specified time. */
public static <E extends EppResource> ForeignKeyIndex<E> create(
@SuppressWarnings("unchecked")
private static <E extends EppResource> ForeignKeyIndex<E> create(
E resource, DateTime deletionTime) {
@SuppressWarnings("unchecked")
Class<E> resourceClass = (Class<E>) resource.getClass();
ForeignKeyIndex<E> instance = instantiate(mapToFkiClass(resourceClass));
instance.topReference = (VKey<E>) resource.createVKey();
instance.reference = (VKey<E>) resource.createVKey();
instance.foreignKey = resource.getForeignKey();
instance.deletionTime = deletionTime;
return instance;
}
/** Create a {@link ForeignKeyIndex} key for a resource. */
public static <E extends EppResource> Key<ForeignKeyIndex<E>> createKey(E resource) {
@SuppressWarnings("unchecked")
Class<E> resourceClass = (Class<E>) resource.getClass();
return Key.create(mapToFkiClass(resourceClass), resource.getForeignKey());
}
/**
* Loads a {@link Key} to an {@link EppResource} from Datastore by foreign key.
* Loads a {@link VKey} to an {@link EppResource} from the database by foreign key.
*
* <p>Returns null if no foreign key index with this foreign key was ever created, or if the most
* recently created foreign key index was deleted before time "now". This method does not actually
@@ -172,7 +144,7 @@ public abstract class ForeignKeyIndex<E extends EppResource> extends BackupGroup
public static <E extends EppResource> VKey<E> loadAndGetKey(
Class<E> clazz, String foreignKey, DateTime now) {
ForeignKeyIndex<E> index = load(clazz, foreignKey, now);
return (index == null) ? null : index.getResourceKey();
return index == null ? null : index.getResourceKey();
}
/**
@@ -196,7 +168,7 @@ public abstract class ForeignKeyIndex<E extends EppResource> extends BackupGroup
*/
public static <E extends EppResource> ImmutableMap<String, ForeignKeyIndex<E>> load(
Class<E> clazz, Collection<String> foreignKeys, final DateTime now) {
return loadIndexesFromStore(clazz, foreignKeys, true, false).entrySet().stream()
return loadIndexesFromStore(clazz, foreignKeys, false).entrySet().stream()
.filter(e -> now.isBefore(e.getValue().getDeletionTime()))
.collect(entriesToImmutableMap());
}
@@ -206,55 +178,37 @@ public abstract class ForeignKeyIndex<E extends EppResource> extends BackupGroup
* keys, regardless of whether or not they have been soft-deleted.
*
* <p>Used by both the cached (w/o deletion check) and the non-cached (with deletion check) calls.
*
* <p>Note that in the cached case, we wish to run this outside of any transaction because we may
* be loading many entities, going over the Datastore limit on the number of enrolled entity
* groups per transaction (25). If we require consistency, however, we must use a transaction.
*
* @param inTransaction whether or not to use an Objectify transaction
*/
private static <E extends EppResource>
ImmutableMap<String, ForeignKeyIndex<E>> loadIndexesFromStore(
Class<E> clazz,
Collection<String> foreignKeys,
boolean inTransaction,
boolean useReplicaJpaTm) {
if (tm().isOfy()) {
Class<ForeignKeyIndex<E>> fkiClass = mapToFkiClass(clazz);
return ImmutableMap.copyOf(
inTransaction
? auditedOfy().load().type(fkiClass).ids(foreignKeys)
: tm().doTransactionless(() -> auditedOfy().load().type(fkiClass).ids(foreignKeys)));
} else {
String property = RESOURCE_CLASS_TO_FKI_PROPERTY.get(clazz);
JpaTransactionManager jpaTmToUse = useReplicaJpaTm ? replicaJpaTm() : jpaTm();
ImmutableList<ForeignKeyIndex<E>> indexes =
jpaTmToUse.transact(
() ->
jpaTmToUse
.criteriaQuery(
CriteriaQueryBuilder.create(clazz)
.whereFieldIsIn(property, foreignKeys)
.build())
.getResultStream()
.map(e -> ForeignKeyIndex.create(e, e.getDeletionTime()))
.collect(toImmutableList()));
// We need to find and return the entities with the maximum deletionTime for each foreign key.
return Multimaps.index(indexes, ForeignKeyIndex::getForeignKey).asMap().entrySet().stream()
.map(
entry ->
Maps.immutableEntry(
entry.getKey(),
entry.getValue().stream()
.max(Comparator.comparing(ForeignKeyIndex::getDeletionTime))
.get()))
.collect(entriesToImmutableMap());
}
Class<E> clazz, Collection<String> foreignKeys, boolean useReplicaJpaTm) {
String property = RESOURCE_CLASS_TO_FKI_PROPERTY.get(clazz);
JpaTransactionManager jpaTmToUse = useReplicaJpaTm ? replicaJpaTm() : jpaTm();
ImmutableList<ForeignKeyIndex<E>> indexes =
jpaTmToUse.transact(
() ->
jpaTmToUse
.criteriaQuery(
CriteriaQueryBuilder.create(clazz)
.whereFieldIsIn(property, foreignKeys)
.build())
.getResultStream()
.map(e -> create(e, e.getDeletionTime()))
.collect(toImmutableList()));
// We need to find and return the entities with the maximum deletionTime for each foreign key.
return Multimaps.index(indexes, ForeignKeyIndex::getForeignKey).asMap().entrySet().stream()
.map(
entry ->
Maps.immutableEntry(
entry.getKey(),
entry.getValue().stream()
.max(Comparator.comparing(ForeignKeyIndex::getDeletionTime))
.get()))
.collect(entriesToImmutableMap());
}
static final CacheLoader<VKey<ForeignKeyIndex<?>>, Optional<ForeignKeyIndex<?>>> CACHE_LOADER =
new AppEngineEnvironmentCacheLoader<
VKey<ForeignKeyIndex<?>>, Optional<ForeignKeyIndex<?>>>() {
new CacheLoader<VKey<ForeignKeyIndex<?>>, Optional<ForeignKeyIndex<?>>>() {
@Override
public Optional<ForeignKeyIndex<?>> load(VKey<ForeignKeyIndex<?>> key) {
@@ -263,7 +217,6 @@ public abstract class ForeignKeyIndex<E extends EppResource> extends BackupGroup
loadIndexesFromStore(
RESOURCE_CLASS_TO_FKI_CLASS.inverse().get(key.getKind()),
ImmutableSet.of(foreignKey),
false,
true)
.get(foreignKey));
}
@@ -280,7 +233,7 @@ public abstract class ForeignKeyIndex<E extends EppResource> extends BackupGroup
Streams.stream(keys).map(v -> v.getSqlKey().toString()).collect(toImmutableSet());
ImmutableSet<VKey<ForeignKeyIndex<?>>> typedKeys = ImmutableSet.copyOf(keys);
ImmutableMap<String, ? extends ForeignKeyIndex<? extends EppResource>> existingFkis =
loadIndexesFromStore(resourceClass, foreignKeys, false, true);
loadIndexesFromStore(resourceClass, foreignKeys, true);
// ofy omits keys that don't have values in Datastore, so re-add them in
// here with Optional.empty() values.
return Maps.asMap(
@@ -333,14 +286,14 @@ public abstract class ForeignKeyIndex<E extends EppResource> extends BackupGroup
public static <E extends EppResource> ImmutableMap<String, ForeignKeyIndex<E>> loadCached(
Class<E> clazz, Collection<String> foreignKeys, final DateTime now) {
if (!RegistryConfig.isEppResourceCachingEnabled()) {
return tm().doTransactionless(() -> load(clazz, foreignKeys, now));
return load(clazz, foreignKeys, now);
}
Class<? extends ForeignKeyIndex<?>> fkiClass = mapToFkiClass(clazz);
// Safe to cast VKey<FKI<E>> to VKey<FKI<?>>
@SuppressWarnings("unchecked")
ImmutableList<VKey<ForeignKeyIndex<?>>> fkiVKeys =
foreignKeys.stream()
.map(fk -> (VKey<ForeignKeyIndex<?>>) VKey.create(fkiClass, fk))
.map(fk -> (VKey<ForeignKeyIndex<?>>) VKey.createSql(fkiClass, fk))
.collect(toImmutableList());
// This cast is safe because when we loaded ForeignKeyIndexes above we used type clazz, which
// is scoped to E.

View File

@@ -34,7 +34,7 @@ import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
*
* <p>All first class entities are represented as a resource class - {@link
* google.registry.model.domain.Domain}, {@link google.registry.model.host.Host}, {@link
* google.registry.model.contact.ContactResource}, and {@link
* google.registry.model.contact.Contact}, and {@link
* google.registry.model.registrar.Registrar}. Resource objects are written in a single shared
* entity group per TLD. All commands that operate on those entities are grouped in a "Command"
* class- {@link google.registry.model.domain.DomainCommand}, {@link

View File

@@ -15,7 +15,6 @@
package google.registry.model.poll;
import com.google.common.annotations.VisibleForTesting;
import com.googlecode.objectify.annotation.Embed;
import google.registry.model.ImmutableObject;
import google.registry.model.UnsafeSerializable;
import google.registry.model.eppcommon.Trid;
@@ -36,7 +35,6 @@ public class PendingActionNotificationResponse extends ImmutableObject
implements ResponseData, UnsafeSerializable {
/** The inner name type that contains a name and the result boolean. */
@Embed
@Embeddable
static class NameOrId extends ImmutableObject implements UnsafeSerializable {
@XmlValue
@@ -80,7 +78,6 @@ public class PendingActionNotificationResponse extends ImmutableObject
}
/** An adapter to output the XML in response to resolving a pending command on a domain. */
@Embed
@XmlRootElement(name = "panData", namespace = "urn:ietf:params:xml:ns:domain-1.0")
@XmlType(
propOrder = {"name", "trid", "processedDate"},
@@ -105,7 +102,6 @@ public class PendingActionNotificationResponse extends ImmutableObject
}
/** An adapter to output the XML in response to resolving a pending command on a contact. */
@Embed
@XmlRootElement(name = "panData", namespace = "urn:ietf:params:xml:ns:contact-1.0")
@XmlType(
propOrder = {"id", "trid", "processedDate"},
@@ -130,7 +126,6 @@ public class PendingActionNotificationResponse extends ImmutableObject
}
/** An adapter to output the XML in response to resolving a pending command on a host. */
@Embed
@XmlRootElement(name = "panData", namespace = "urn:ietf:params:xml:ns:domain-1.0")
@XmlType(
propOrder = {"name", "trid", "processedDate"},

View File

@@ -20,21 +20,15 @@ import static google.registry.util.DateTimeUtils.END_OF_TIME;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import com.google.common.collect.ImmutableList;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.EntitySubclass;
import com.googlecode.objectify.annotation.Id;
import com.googlecode.objectify.annotation.Ignore;
import com.googlecode.objectify.annotation.Index;
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.ExternalMessagingName;
import google.registry.model.annotations.ReportedOn;
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.contact.ContactResource;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.DomainHistory.DomainHistoryId;
@@ -55,16 +49,19 @@ import google.registry.persistence.VKey;
import google.registry.persistence.WithLongVKey;
import google.registry.util.NullIgnoringCollectionBuilder;
import java.util.Optional;
import javax.annotation.Nullable;
import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.Column;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.PostLoad;
import javax.persistence.Table;
import org.joda.time.DateTime;
/**
@@ -76,10 +73,10 @@ import org.joda.time.DateTime;
* retained for historical purposes, poll messages are truly deleted once they have been delivered
* and ACKed.
*
* <p>Poll messages are parented off of the {@link HistoryEntry} that resulted in their creation.
* This means that poll messages are contained in the Datastore entity group of the parent {@link
* EppResource} (which can be a domain, contact, or host). It is thus possible to perform a strongly
* consistent query to find all poll messages associated with a given EPP resource.
* <p>Poll messages have a reference to the revision id of the {@link HistoryEntry} that resulted in
* their creation or re-creation, in case of transfer cancellation/rejection. The revision ids are
* not used directly by the Java code, but their presence in the database makes it easier to
* diagnose potential issues related to poll messages.
*
* <p>Poll messages are identified externally by registrars using the format defined in {@link
* PollMessageExternalKeyConverter}.
@@ -88,29 +85,23 @@ import org.joda.time.DateTime;
* Command</a>
*/
@Entity
@ReportedOn
@ExternalMessagingName("message")
@javax.persistence.Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "type")
@javax.persistence.Table(
indexes = {
@javax.persistence.Index(columnList = "registrar_id"),
@javax.persistence.Index(columnList = "eventTime")
})
@Table(indexes = {@Index(columnList = "registrar_id"), @Index(columnList = "eventTime")})
public abstract class PollMessage extends ImmutableObject
implements Buildable, TransferServerApproveEntity, UnsafeSerializable {
/** Indicates the type of entity the poll message is for. */
public enum Type {
DOMAIN(1L, Domain.class),
CONTACT(2L, ContactResource.class),
CONTACT(2L, Contact.class),
HOST(3L, Host.class);
private final long id;
private final Class<?> clazz;
private final Class<? extends EppResource> clazz;
Type(long id, Class<?> clazz) {
Type(long id, Class<? extends EppResource> clazz) {
this.id = id;
this.clazz = clazz;
}
@@ -124,58 +115,40 @@ public abstract class PollMessage extends ImmutableObject
}
/** Returns the class of the underlying resource for the poll message type. */
public Class<?> getResourceClass() {
public Class<? extends EppResource> getResourceClass() {
return clazz;
}
/**
* Returns the type specified by the identifer, {@code Optional.empty()} if the id is out of
* range.
*/
public static Optional<Type> fromId(long id) {
for (Type val : values()) {
if (val.id == id) {
return Optional.of(val);
}
}
return Optional.empty();
}
}
/** Entity id. */
@Id
@javax.persistence.Id
@OfyIdAllocation
@Column(name = "poll_message_id")
Long id;
/** The registrar that this poll message will be delivered to. */
@Index
@Column(name = "registrar_id", nullable = false)
String clientId;
/** The time when the poll message should be delivered. May be in the future. */
@Index
@Column(nullable = false)
DateTime eventTime;
/** Human readable message that will be returned with this poll message. */
/** Human-readable message that will be returned with this poll message. */
@Column(name = "message")
String msg;
// TODO(b/456803336): Replace these fields with {Domain,Contact,Host}HistoryId objects.
String domainRepoId;
@Ignore String domainRepoId;
String contactRepoId;
@Ignore String contactRepoId;
String hostRepoId;
@Ignore String hostRepoId;
Long domainHistoryRevisionId;
@Ignore Long domainHistoryRevisionId;
Long contactHistoryRevisionId;
@Ignore Long contactHistoryRevisionId;
@Ignore Long hostHistoryRevisionId;
Long hostHistoryRevisionId;
public Long getId() {
return id;
@@ -206,7 +179,7 @@ public abstract class PollMessage extends ImmutableObject
/**
* Returns the contact repo id.
*
* <p>This may only be used on a ContactResource poll event.
* <p>This may only be used on a {@link Contact} poll event.
*/
public String getContactRepoId() {
checkArgument(getType() == Type.CONTACT);
@@ -228,31 +201,24 @@ public abstract class PollMessage extends ImmutableObject
* the resource.
*/
public String getResourceName() {
return domainRepoId != null
? domainRepoId
: (contactRepoId != null ? contactRepoId : hostRepoId);
return domainRepoId != null ? domainRepoId : contactRepoId != null ? contactRepoId : hostRepoId;
}
/** Gets the underlying history revision id, regardless of the type of the resource. */
public Long getHistoryRevisionId() {
return domainHistoryRevisionId != null
? domainHistoryRevisionId
: (contactHistoryRevisionId != null ? contactHistoryRevisionId : hostHistoryRevisionId);
: contactHistoryRevisionId != null ? contactHistoryRevisionId : hostHistoryRevisionId;
}
public Type getType() {
return domainRepoId != null ? Type.DOMAIN : (contactRepoId != null ? Type.CONTACT : Type.HOST);
return domainRepoId != null ? Type.DOMAIN : contactRepoId != null ? Type.CONTACT : Type.HOST;
}
public abstract ImmutableList<ResponseData> getResponseData();
@Override
public abstract VKey<? extends PollMessage> createVKey();
/** Static VKey factory method for use by VKeyTranslatorFactory. */
public static VKey<PollMessage> createVKey(Key<PollMessage> key) {
return VKey.create(PollMessage.class, key.getId(), key);
}
public abstract ImmutableList<ResponseData> getResponseData();
/** Override Buildable.asBuilder() to give this method stronger typing. */
@Override
@@ -318,9 +284,11 @@ public abstract class PollMessage extends ImmutableObject
// Set the appropriate field based on the history entry type.
if (history instanceof DomainHistory) {
return setDomainHistoryId(((DomainHistory) history).getDomainHistoryId());
} else if (history instanceof ContactHistory) {
}
if (history instanceof ContactHistory) {
return setContactHistoryId(((ContactHistory) history).getContactHistoryId());
} else if (history instanceof HostHistory) {
}
if (history instanceof HostHistory) {
return setHostHistoryId(((HostHistory) history).getHostHistoryId());
}
return thisCastToDerived();
@@ -330,7 +298,7 @@ public abstract class PollMessage extends ImmutableObject
* Given an array containing pairs of objects, verifies that both members of exactly one of the
* pairs is non-null.
*/
private boolean exactlyOnePairNonNull(Object... pairs) {
private static boolean exactlyOnePairNonNull(Object... pairs) {
int count = 0;
checkArgument(pairs.length % 2 == 0, "Odd number of arguments provided.");
for (int i = 0; i < pairs.length; i += 2) {
@@ -360,9 +328,9 @@ public abstract class PollMessage extends ImmutableObject
instance.contactHistoryRevisionId,
instance.hostRepoId,
instance.hostHistoryRevisionId),
"the repo id and history revision id must be defined for exactly one of domain, "
+ "contact or host: "
+ instance);
"The repo id and history revision id must be defined for exactly one of domain, "
+ "contact or host: %s",
instance);
return super.build();
}
}
@@ -372,13 +340,11 @@ public abstract class PollMessage extends ImmutableObject
*
* <p>One-time poll messages are deleted from Datastore once they have been delivered and ACKed.
*/
@EntitySubclass(index = false)
@javax.persistence.Entity
@Entity
@DiscriminatorValue("ONE_TIME")
@WithLongVKey(compositeKey = true)
public static class OneTime extends PollMessage {
@Ignore
@Embedded
@AttributeOverrides({
@AttributeOverride(
@@ -399,7 +365,6 @@ public abstract class PollMessage extends ImmutableObject
})
PendingActionNotificationResponse pendingActionNotificationResponse;
@Ignore
@Embedded
@AttributeOverrides({
@AttributeOverride(
@@ -420,35 +385,21 @@ public abstract class PollMessage extends ImmutableObject
})
TransferResponse transferResponse;
@Ignore
@Column(name = "transfer_response_domain_name")
String fullyQualifiedDomainName;
@Ignore
@Column(name = "transfer_response_domain_expiration_time")
DateTime extendedRegistrationExpirationTime;
@Ignore
@Column(name = "transfer_response_contact_id")
String contactId;
@Ignore
@Column(name = "transfer_response_host_id")
String hostId;
@Override
public VKey<OneTime> createVKey() {
return VKey.create(OneTime.class, getId(), Key.create(this));
}
/** Converts an unspecialized VKey&lt;PollMessage&gt; to a VKey of the derived class. */
public static @Nullable VKey<OneTime> convertVKey(@Nullable VKey<? extends PollMessage> key) {
if (key == null) {
return null;
}
Key<OneTime> ofyKey =
Key.create(key.getOfyKey().getParent(), OneTime.class, key.getOfyKey().getId());
return VKey.create(OneTime.class, key.getSqlKey(), ofyKey);
return VKey.createSql(OneTime.class, getId());
}
@Override
@@ -525,8 +476,7 @@ public abstract class PollMessage extends ImmutableObject
/** A builder for {@link OneTime} since it is immutable. */
public static class Builder extends PollMessage.Builder<OneTime, Builder> {
public Builder() {
}
public Builder() {}
private Builder(OneTime instance) {
super(instance);
@@ -587,14 +537,13 @@ public abstract class PollMessage extends ImmutableObject
}
/**
* An auto-renew poll message which recurs annually.
* An autorenew poll message which recurs annually.
*
* <p>Auto-renew poll messages are not deleted until the registration of their parent domain has
* been canceled, because there will always be a speculative renewal for next year until that
* happens.
*/
@EntitySubclass(index = false)
@javax.persistence.Entity
@Entity
@DiscriminatorValue("AUTORENEW")
@WithLongVKey(compositeKey = true)
public static class Autorenew extends PollMessage {
@@ -604,7 +553,6 @@ public abstract class PollMessage extends ImmutableObject
String targetId;
/** The autorenew recurs annually between {@link #eventTime} and this time. */
@Index
DateTime autorenewEndTime;
public String getTargetId() {
@@ -617,25 +565,14 @@ public abstract class PollMessage extends ImmutableObject
@Override
public VKey<Autorenew> createVKey() {
return VKey.create(Autorenew.class, getId(), Key.create(this));
}
/** Converts an unspecialized VKey&lt;PollMessage&gt; to a VKey of the derived class. */
public static @Nullable VKey<Autorenew> convertVKey(VKey<? extends PollMessage> key) {
if (key == null) {
return null;
}
Key<Autorenew> ofyKey =
Key.create(key.getOfyKey().getParent(), Autorenew.class, key.getOfyKey().getId());
return VKey.create(Autorenew.class, key.getSqlKey(), ofyKey);
return VKey.createSql(Autorenew.class, getId());
}
@Override
public ImmutableList<ResponseData> getResponseData() {
// Note that the event time is when the auto-renew occured, so the expiration time in the
// Note that the event time is when the autorenew occurred, so the expiration time in the
// response should be 1 year past that, since it denotes the new expiration time.
return ImmutableList.of(
DomainRenewData.create(getTargetId(), getEventTime().plusYears(1)));
return ImmutableList.of(DomainRenewData.create(getTargetId(), getEventTime().plusYears(1)));
}
@Override

View File

@@ -14,48 +14,38 @@
package google.registry.model.poll;
import com.google.common.base.Splitter;
import com.googlecode.objectify.Key;
import google.registry.model.reporting.HistoryEntry;
import google.registry.persistence.VKey;
import java.util.List;
/**
* A converter between external key strings for {@link PollMessage}s (i.e. what registrars use to
* identify and ACK them) and Datastore keys to the resource.
* identify and ACK them) and {@link VKey}s to the resource.
*
* <p>The format of the key string is A-B-C-D-E-F as follows:
* <p>The format of the key string is A-B as follows:
*
* <pre>
* A = EppResource.typeId (decimal)
* B = EppResource.repoId prefix (STRING)
* C = EppResource.repoId suffix (STRING)
* D = HistoryEntry.id (decimal)
* E = PollMessage.id (decimal)
* F = PollMessage.eventTime (decimal, year only)
* A = PollMessage.id (decimal)
* B = PollMessage.eventTime (decimal, year only)
* </pre>
*
* <p>A typical poll message ID might thus look like: 1-FE0F22-TLD-10071463070-10072612074-2018
* <p>A typical poll message ID might thus look like: 10072612074-2018
*/
public class PollMessageExternalKeyConverter {
public final class PollMessageExternalKeyConverter {
/** An exception thrown when an external key cannot be parsed. */
public static class PollMessageExternalKeyParseException extends RuntimeException {}
public static class PollMessageExternalKeyParseException extends RuntimeException {
private static final long serialVersionUID = 9026397649048831777L;
}
/** Returns an external poll message ID for the given poll message. */
public static String makePollMessageExternalId(PollMessage pollMessage) {
return String.format(
"%d-%s-%d-%d-%d",
pollMessage.getType().getId(),
pollMessage.getResourceName(),
pollMessage.getHistoryRevisionId(),
pollMessage.getId(),
pollMessage.getEventTime().getYear());
return String.format("%d-%d", pollMessage.getId(), pollMessage.getEventTime().getYear());
}
/**
* Returns an Objectify Key to a PollMessage corresponding with the external ID.
* Returns a {@link VKey} to a {@link PollMessage} corresponding with the external ID.
*
* <p>Note that the year field that is included at the end of the poll message isn't actually used
* for anything; it exists solely to create unique externally visible IDs for autorenews. We thus
@@ -66,26 +56,13 @@ public class PollMessageExternalKeyConverter {
*/
public static VKey<PollMessage> parsePollMessageExternalId(String externalKey) {
List<String> idComponents = Splitter.on('-').splitToList(externalKey);
if (idComponents.size() != 6) {
if (idComponents.size() != 2) {
throw new PollMessageExternalKeyParseException();
}
try {
Class<?> resourceClazz =
PollMessage.Type.fromId(Long.parseLong(idComponents.get(0)))
.orElseThrow(() -> new PollMessageExternalKeyParseException())
.getResourceClass();
return VKey.from(
Key.create(
Key.create(
Key.create(
null,
resourceClazz,
String.format("%s-%s", idComponents.get(1), idComponents.get(2))),
HistoryEntry.class,
Long.parseLong(idComponents.get(3))),
PollMessage.class,
Long.parseLong(idComponents.get(4))));
// Note that idComponents.get(5) is entirely ignored; we never use the year field internally.
Long id = Long.parseLong(idComponents.get(0));
return VKey.createSql(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

@@ -19,10 +19,6 @@ import static google.registry.model.rde.RdeNamingUtils.makePartialName;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.google.common.base.VerifyException;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
import com.googlecode.objectify.annotation.Ignore;
import google.registry.model.BackupGroupRoot;
import google.registry.model.ImmutableObject;
import google.registry.model.rde.RdeRevision.RdeRevisionId;
@@ -32,10 +28,11 @@ import java.io.Serializable;
import java.util.Optional;
import javax.persistence.Column;
import javax.persistence.Convert;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.Transient;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
@@ -47,18 +44,14 @@ import org.joda.time.LocalDate;
* flag is included in the generated XML.
*/
@Entity
@javax.persistence.Entity
@IdClass(RdeRevisionId.class)
public final class RdeRevision extends BackupGroupRoot {
/** String triplet of tld, date, and mode, e.g. {@code soy_2015-09-01_full}. */
@Id @Transient String id;
@Id String tld;
@javax.persistence.Id @Ignore String tld;
@Id LocalDate date;
@javax.persistence.Id @Ignore LocalDate date;
@javax.persistence.Id @Ignore RdeMode mode;
@Id RdeMode mode;
/**
* Number of last revision successfully staged to GCS.
@@ -71,10 +64,8 @@ public final class RdeRevision extends BackupGroupRoot {
/** Hibernate requires an empty constructor. */
private RdeRevision() {}
public static RdeRevision create(
String id, String tld, LocalDate date, RdeMode mode, int revision) {
public static RdeRevision create(String tld, LocalDate date, RdeMode mode, int revision) {
RdeRevision instance = new RdeRevision();
instance.id = id;
instance.tld = tld;
instance.date = date;
instance.mode = mode;
@@ -92,12 +83,9 @@ public final class RdeRevision extends BackupGroupRoot {
* @return {@code 0} for first deposit generation and {@code >0} for resends
*/
public static int getNextRevision(String tld, DateTime date, RdeMode mode) {
String id = makePartialName(tld, date, mode);
RdeRevisionId sqlKey = RdeRevisionId.create(tld, date.toLocalDate(), mode);
Key<RdeRevision> ofyKey = Key.create(RdeRevision.class, id);
Optional<RdeRevision> revisionOptional =
tm().transact(
() -> tm().loadByKeyIfPresent(VKey.create(RdeRevision.class, sqlKey, ofyKey)));
tm().transact(() -> tm().loadByKeyIfPresent(VKey.createSql(RdeRevision.class, sqlKey)));
return revisionOptional.map(rdeRevision -> rdeRevision.revision + 1).orElse(0);
}
@@ -121,12 +109,10 @@ public final class RdeRevision extends BackupGroupRoot {
*/
public static void saveRevision(String tld, DateTime date, RdeMode mode, int revision) {
checkArgument(revision >= 0, "Negative revision: %s", revision);
String triplet = makePartialName(tld, date, mode);
tm().assertInTransaction();
RdeRevisionId sqlKey = RdeRevisionId.create(tld, date.toLocalDate(), mode);
Key<RdeRevision> ofyKey = Key.create(RdeRevision.class, triplet);
Optional<RdeRevision> revisionOptional =
tm().loadByKeyIfPresent(VKey.create(RdeRevision.class, sqlKey, ofyKey));
tm().loadByKeyIfPresent(VKey.createSql(RdeRevision.class, sqlKey));
if (revision == 0) {
revisionOptional.ifPresent(
rdeRevision -> {
@@ -139,7 +125,7 @@ public final class RdeRevision extends BackupGroupRoot {
checkArgument(
revisionOptional.isPresent(),
"Couldn't find existing RDE revision %s when trying to save new revision %s",
triplet,
makePartialName(tld, date, mode),
revision);
checkArgument(
revisionOptional.get().revision == revision - 1,
@@ -147,7 +133,7 @@ public final class RdeRevision extends BackupGroupRoot {
revision - 1,
revisionOptional.get());
}
RdeRevision object = RdeRevision.create(triplet, tld, date.toLocalDate(), mode, revision);
RdeRevision object = create(tld, date.toLocalDate(), mode, revision);
tm().put(object);
}

View File

@@ -26,8 +26,6 @@ import static com.google.common.collect.Sets.immutableEnumSet;
import static com.google.common.io.BaseEncoding.base64;
import static google.registry.config.RegistryConfig.getDefaultRegistrarWhoisServer;
import static google.registry.model.CacheUtils.memoizeWithShortExpiration;
import static google.registry.model.common.EntityGroupRoot.getCrossTldKey;
import static google.registry.model.ofy.ObjectifyService.auditedOfy;
import static google.registry.model.tld.Registries.assertTldsExist;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
@@ -44,7 +42,6 @@ import static java.util.function.Predicate.isEqual;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
@@ -55,14 +52,6 @@ import com.google.common.collect.Range;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import com.google.re2j.Pattern;
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.IgnoreSave;
import com.googlecode.objectify.annotation.Index;
import com.googlecode.objectify.annotation.Parent;
import com.googlecode.objectify.condition.IfNull;
import google.registry.model.Buildable;
import google.registry.model.CreateAutoTimestamp;
import google.registry.model.ImmutableObject;
@@ -70,9 +59,6 @@ import google.registry.model.JsonMapBuilder;
import google.registry.model.Jsonifiable;
import google.registry.model.UnsafeSerializable;
import google.registry.model.UpdateAutoTimestamp;
import google.registry.model.annotations.InCrossTld;
import google.registry.model.annotations.ReportedOn;
import google.registry.model.common.EntityGroupRoot;
import google.registry.model.tld.Registry;
import google.registry.model.tld.Registry.TldType;
import google.registry.persistence.VKey;
@@ -85,6 +71,7 @@ import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.function.Supplier;
import javax.annotation.Nullable;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
@@ -92,25 +79,22 @@ import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.Table;
import javax.persistence.Transient;
import org.joda.money.CurrencyUnit;
import org.joda.time.DateTime;
/** Information about a registrar. */
@ReportedOn
@Entity
@javax.persistence.Entity
@Table(
indexes = {
@javax.persistence.Index(columnList = "registrarName", name = "registrar_name_idx"),
@javax.persistence.Index(
columnList = "ianaIdentifier",
name = "registrar_iana_identifier_idx"),
@Index(columnList = "registrarName", name = "registrar_name_idx"),
@Index(columnList = "ianaIdentifier", name = "registrar_iana_identifier_idx"),
})
@InCrossTld
public class Registrar extends ImmutableObject
implements Buildable, Jsonifiable, UnsafeSerializable {
@@ -199,7 +183,7 @@ public class Registrar extends ImmutableObject
/** The states in which a {@link Registrar} is considered {@link #isLive live}. */
private static final ImmutableSet<State> LIVE_STATES =
Sets.immutableEnumSet(State.ACTIVE, State.SUSPENDED);
immutableEnumSet(State.ACTIVE, State.SUSPENDED);
/**
* The types for which a {@link Registrar} should be included in WHOIS and RDAP output. We exclude
@@ -215,18 +199,9 @@ public class Registrar extends ImmutableObject
private static final Comparator<RegistrarPoc> CONTACT_EMAIL_COMPARATOR =
comparing(RegistrarPoc::getEmailAddress, String::compareTo);
/**
* A caching {@link Supplier} of a registrarId to {@link Registrar} map.
*
* <p>The supplier's get() method enters a transactionless context briefly to avoid enrolling the
* query inside an unrelated client-affecting transaction.
*/
/** A caching {@link Supplier} of a registrarId to {@link Registrar} map. */
private static final Supplier<ImmutableMap<String, Registrar>> CACHE_BY_REGISTRAR_ID =
memoizeWithShortExpiration(
() ->
tm().doTransactionless(() -> Maps.uniqueIndex(loadAll(), Registrar::getRegistrarId)));
@Parent @Transient Key<EntityGroupRoot> parent = getCrossTldKey();
memoizeWithShortExpiration(() -> Maps.uniqueIndex(loadAll(), Registrar::getRegistrarId));
/**
* Unique registrar client id. Must conform to "clIDType" as defined in RFC5730.
@@ -235,7 +210,6 @@ public class Registrar extends ImmutableObject
* <p>TODO(b/177567432): Rename this field to registrarId.
*/
@Id
@javax.persistence.Id
@Column(name = "registrarId", nullable = false)
String clientIdentifier;
@@ -250,7 +224,6 @@ public class Registrar extends ImmutableObject
* @see <a href="http://www.icann.org/registrar-reports/accredited-list.html">ICANN-Accredited
* Registrars</a>
*/
@Index
@Column(nullable = false)
String registrarName;
@@ -315,7 +288,6 @@ public class Registrar extends ImmutableObject
* Localized {@link RegistrarAddress} for this registrar. Contents can be represented in
* unrestricted UTF-8.
*/
@IgnoreSave(IfNull.class)
@Embedded
@AttributeOverrides({
@AttributeOverride(
@@ -340,7 +312,6 @@ public class Registrar extends ImmutableObject
* Internationalized {@link RegistrarAddress} for this registrar. All contained values must be
* representable in the 7-bit US-ASCII character set.
*/
@IgnoreSave(IfNull.class)
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "streetLine1", column = @Column(name = "i18n_address_street_line1")),
@@ -369,14 +340,14 @@ public class Registrar extends ImmutableObject
*
* <ul>
* <li>8 is used for Testing Registrar.
* <li>9997 is used by ICAAN for SLA monitoring.
* <li>9997 is used by ICANN for SLA monitoring.
* <li>9999 is used for cases when the registry operator acts as registrar.
* </ul>
*
* @see <a href="http://www.iana.org/assignments/registrar-ids/registrar-ids.txt">Registrar
* IDs</a>
*/
@Index @Nullable Long ianaIdentifier;
@Nullable Long ianaIdentifier;
/** Purchase Order number used for invoices in external billing system, if applicable. */
@Nullable String poNumber;
@@ -397,7 +368,7 @@ public class Registrar extends ImmutableObject
/**
* ICANN referral email address.
*
* <p>This value is specified in the initial registrar contact. It can't be edited in the web GUI
* <p>This value is specified in the initial registrar contact. It can't be edited in the web GUI,
* and it must be specified when the registrar account is created.
*/
String icannReferralEmail;
@@ -408,10 +379,10 @@ public class Registrar extends ImmutableObject
// Metadata.
/** The time when this registrar was created. */
@Ignore CreateAutoTimestamp creationTime = CreateAutoTimestamp.create(null);
CreateAutoTimestamp creationTime = CreateAutoTimestamp.create(null);
/** An automatically managed last-saved timestamp. */
@Ignore UpdateAutoTimestamp lastUpdateTime = UpdateAutoTimestamp.create(null);
UpdateAutoTimestamp lastUpdateTime = UpdateAutoTimestamp.create(null);
/** The time that the certificate was last updated. */
DateTime lastCertificateUpdateTime;
@@ -612,18 +583,13 @@ public class Registrar extends ImmutableObject
}
private Iterable<RegistrarPoc> getContactsIterable() {
if (tm().isOfy()) {
return auditedOfy().load().type(RegistrarPoc.class).ancestor(Registrar.this);
} else {
return tm().transact(
() ->
jpaTm()
.query(
"FROM RegistrarPoc WHERE registrarId = :registrarId", RegistrarPoc.class)
.setParameter("registrarId", clientIdentifier)
.getResultStream()
.collect(toImmutableList()));
}
return tm().transact(
() ->
jpaTm()
.query("FROM RegistrarPoc WHERE registrarId = :registrarId", RegistrarPoc.class)
.setParameter("registrarId", clientIdentifier)
.getResultStream()
.collect(toImmutableList()));
}
@Override
@@ -689,18 +655,13 @@ public class Registrar extends ImmutableObject
/** Creates a {@link VKey} for this instance. */
@Override
public VKey<Registrar> createVKey() {
return createVKey(Key.create(this));
return createVKey(clientIdentifier);
}
/** Creates a {@link VKey} for the given {@code registrarId}. */
public static VKey<Registrar> createVKey(String registrarId) {
checkArgumentNotNull(registrarId, "registrarId must be specified");
return createVKey(Key.create(getCrossTldKey(), Registrar.class, registrarId));
}
/** Creates a {@link VKey} instance from a {@link Key} instance. */
public static VKey<Registrar> createVKey(Key<Registrar> key) {
return VKey.create(Registrar.class, key.getName(), key);
return VKey.createSql(Registrar.class, registrarId);
}
/** A builder for constructing {@link Registrar}, since it is immutable. */
@@ -776,7 +737,7 @@ public class Registrar extends ImmutableObject
Set<VKey<Registry>> missingTldKeys =
Sets.difference(
newTldKeys, tm().transact(() -> tm().loadByKeysIfPresent(newTldKeys)).keySet());
checkArgument(missingTldKeys.isEmpty(), "Trying to set nonexisting TLDs: %s", missingTldKeys);
checkArgument(missingTldKeys.isEmpty(), "Trying to set nonexistent TLDs: %s", missingTldKeys);
getInstance().allowedTlds = ImmutableSortedSet.copyOf(allowedTlds);
return this;
}

View File

@@ -18,7 +18,6 @@ import static com.google.common.base.Preconditions.checkNotNull;
import static google.registry.util.CollectionUtils.forceEmptyToNull;
import com.google.common.annotations.VisibleForTesting;
import com.googlecode.objectify.annotation.Embed;
import google.registry.model.eppcommon.Address;
import javax.persistence.Embeddable;
@@ -29,7 +28,6 @@ import javax.persistence.Embeddable;
* all defined in parent class {@link Address} so that it can share it with other similar address
* classes.
*/
@Embed
@Embeddable
public class RegistrarAddress extends Address {

View File

@@ -18,8 +18,6 @@ import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.annotation.Embed;
import com.googlecode.objectify.annotation.Ignore;
import google.registry.model.Buildable;
import google.registry.model.ImmutableObject;
import google.registry.model.UnsafeSerializable;
@@ -43,15 +41,13 @@ import org.joda.time.DateTime;
* only exception is for reportField = TRANSFER_LOSING_SUCCESSFUL or TRANSFER_LOSING_NACKED, which
* uses HistoryEntry.otherClientId because the losing party in a transfer is always the otherClient.
*/
@Embed
@Entity
public class DomainTransactionRecord extends ImmutableObject
implements Buildable, UnsafeSerializable {
@Id
@Ignore
@GeneratedValue(strategy = GenerationType.IDENTITY)
@ImmutableObject.Insignificant
@Insignificant
Long id;
/** The TLD this record operates on. */
@@ -62,9 +58,9 @@ public class DomainTransactionRecord extends ImmutableObject
// Datastore-SQL validation. They are excluded from equality check since they are not set in
// Datastore.
// TODO(b/203609782): post migration, decide whether to keep these two fields.
@Ignore @ImmutableObject.Insignificant String domainRepoId;
@Insignificant String domainRepoId;
@Ignore @ImmutableObject.Insignificant Long historyRevisionId;
@Insignificant Long historyRevisionId;
/**
* The time this Transaction takes effect (counting grace periods and other nuances).

View File

@@ -25,19 +25,18 @@ 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.IgnoreSave;
import com.googlecode.objectify.annotation.Ignore;
import com.googlecode.objectify.annotation.Index;
import com.googlecode.objectify.annotation.Parent;
import com.googlecode.objectify.condition.IfNull;
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.contact.ContactBase;
import google.registry.model.contact.ContactHistory;
import google.registry.model.contact.ContactHistory.ContactHistoryId;
import google.registry.model.contact.ContactResource;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainBase;
import google.registry.model.domain.DomainHistory;
@@ -155,8 +154,7 @@ public class HistoryEntry extends ImmutableObject implements Buildable, UnsafeSe
* The length of time that a create, allocate, renewal, or transfer request was issued for. Will
* be null for all other types.
*/
@IgnoreSave(IfNull.class)
@Transient // domain-specific
@Ignore @Transient // domain-specific
Period period;
/**
@@ -189,6 +187,7 @@ public class HistoryEntry extends ImmutableObject implements Buildable, UnsafeSe
/** Transaction id that made this change, or null if the entry was not created by a flow. */
@Nullable
@Ignore
@AttributeOverrides({
@AttributeOverride(
name = "clientTransactionId",
@@ -218,6 +217,7 @@ public class HistoryEntry extends ImmutableObject implements Buildable, UnsafeSe
* 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;
@@ -392,7 +392,7 @@ public class HistoryEntry extends ImmutableObject implements Buildable, UnsafeSe
} else if (parentKind.equals(getKind(Host.class))) {
resultEntity =
new HostHistory.Builder().copyFrom(this).setHostRepoId(parent.getName()).build();
} else if (parentKind.equals(getKind(ContactResource.class))) {
} else if (parentKind.equals(getKind(Contact.class))) {
resultEntity =
new ContactHistory.Builder().copyFrom(this).setContactRepoId(parent.getName()).build();
} else {
@@ -418,7 +418,7 @@ public class HistoryEntry extends ImmutableObject implements Buildable, UnsafeSe
HostHistory.class,
new HostHistoryId(repoId, id),
Key.create(parent, HostHistory.class, id));
} else if (parentKind.equals(getKind(ContactResource.class))) {
} else if (parentKind.equals(getKind(Contact.class))) {
return VKey.create(
ContactHistory.class,
new ContactHistoryId(repoId, id),

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