mirror of
https://github.com/google/nomulus
synced 2026-05-20 06:41:51 +00:00
Compare commits
30 Commits
nomulus-20
...
nomulus-20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17a21f3326 | ||
|
|
f623da9948 | ||
|
|
ddc4a615db | ||
|
|
06a1fc0022 | ||
|
|
eec272b6ba | ||
|
|
3d5b52b853 | ||
|
|
bd4af052a6 | ||
|
|
78249e1329 | ||
|
|
7aec579d96 | ||
|
|
b9f8faa165 | ||
|
|
b0e4e86586 | ||
|
|
3412f4417f | ||
|
|
db6329a070 | ||
|
|
02af277148 | ||
|
|
8b02f76ae9 | ||
|
|
6dd96c247a | ||
|
|
919c744d8c | ||
|
|
5bccd65bd7 | ||
|
|
5268e35155 | ||
|
|
6e201450f0 | ||
|
|
dda9a3ef7e | ||
|
|
8c1fb6bf00 | ||
|
|
4e21152f04 | ||
|
|
22193474d5 | ||
|
|
efd5244ebd | ||
|
|
87e5d19fe5 | ||
|
|
bbb6174c9f | ||
|
|
2b826651e6 | ||
|
|
e4132db8ed | ||
|
|
45d90e7c68 |
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -22,7 +22,7 @@ import com.google.common.collect.ImmutableSet;
|
||||
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;
|
||||
@@ -33,6 +33,7 @@ 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 +53,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
|
||||
@@ -98,13 +99,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<Contact, Contact> read =
|
||||
RegistryJpaIO.read(
|
||||
"FROM Contact WHERE transferData.transferStatus = 'PENDING' AND"
|
||||
+ " transferData.pendingTransferExpirationTime < current_timestamp()",
|
||||
ContactResource.class,
|
||||
Contact.class,
|
||||
c -> c);
|
||||
projectAndResaveResources(pipeline, ContactResource.class, read);
|
||||
projectAndResaveResources(pipeline, Contact.class, read);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -172,4 +173,13 @@ public class ResaveAllEppResourcesPipeline implements Serializable {
|
||||
resource.cloneProjectedAtTime(jpaTm().getTransactionTime()))));
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
PipelineOptionsFactory.register(ResaveAllEppResourcesPipelineOptions.class);
|
||||
ResaveAllEppResourcesPipelineOptions options =
|
||||
PipelineOptionsFactory.fromArgs(args)
|
||||
.withValidation()
|
||||
.as(ResaveAllEppResourcesPipelineOptions.class);
|
||||
new ResaveAllEppResourcesPipeline(options).run();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -33,9 +33,9 @@ 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;
|
||||
@@ -75,9 +75,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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -153,6 +153,7 @@ import org.joda.time.Duration;
|
||||
* @error {@link DomainCreateFlow.AnchorTenantCreatePeriodException}
|
||||
* @error {@link DomainCreateFlow.MustHaveSignedMarksInCurrentPhaseException}
|
||||
* @error {@link DomainCreateFlow.NoGeneralRegistrationsInCurrentPhaseException}
|
||||
* @error {@link DomainCreateFlow.NoTrademarkedRegistrationsBeforeSunriseException}
|
||||
* @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,11 @@ public final class DomainCreateFlow implements TransactionalFlow {
|
||||
.addGracePeriod(
|
||||
GracePeriod.forBillingEvent(GracePeriodStatus.ADD, repoId, createBillingEvent))
|
||||
.build();
|
||||
if (allocationToken.isPresent()
|
||||
&& allocationToken.get().getTokenType().equals(TokenType.PACKAGE)) {
|
||||
domain =
|
||||
domain.asBuilder().setCurrentPackageToken(allocationToken.get().createVKey()).build();
|
||||
}
|
||||
DomainHistory domainHistory =
|
||||
buildDomainHistory(domain, registry, now, period, registry.getAddGracePeriodLength());
|
||||
if (reservationTypes.contains(NAME_COLLISION)) {
|
||||
@@ -481,7 +486,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 +502,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 +737,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) {
|
||||
|
||||
@@ -263,7 +263,8 @@ public final class DomainDeleteFlow implements TransactionalFlow {
|
||||
// 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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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()))
|
||||
|
||||
@@ -251,8 +251,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())
|
||||
|
||||
@@ -56,7 +56,6 @@ 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.AllocationToken;
|
||||
import google.registry.model.domain.token.AllocationTokenExtension;
|
||||
import google.registry.model.eppcommon.AuthInfo;
|
||||
import google.registry.model.eppinput.EppInput;
|
||||
@@ -129,15 +128,12 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
|
||||
extensionManager.validate();
|
||||
DateTime now = tm().getTransactionTime();
|
||||
Domain existingDomain = loadAndVerifyExistence(Domain.class, targetId, now);
|
||||
// Currently we do not do anything with this allocation token, but just want it loaded and
|
||||
// available in this flow in case we use it in the future
|
||||
Optional<AllocationToken> allocationToken =
|
||||
allocationTokenFlowUtils.verifyAllocationTokenIfPresent(
|
||||
existingDomain,
|
||||
Registry.get(existingDomain.getTld()),
|
||||
registrarId,
|
||||
now,
|
||||
eppInput.getSingleExtension(AllocationTokenExtension.class));
|
||||
allocationTokenFlowUtils.verifyAllocationTokenIfPresent(
|
||||
existingDomain,
|
||||
Registry.get(existingDomain.getTld()),
|
||||
registrarId,
|
||||
now,
|
||||
eppInput.getSingleExtension(AllocationTokenExtension.class));
|
||||
verifyOptionalAuthInfo(authInfo, existingDomain);
|
||||
verifyHasPendingTransfer(existingDomain);
|
||||
verifyResourceOwnership(registrarId, existingDomain);
|
||||
@@ -182,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(
|
||||
@@ -198,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.
|
||||
@@ -244,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
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -57,12 +57,12 @@ 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.AllocationToken;
|
||||
import google.registry.model.domain.token.AllocationTokenExtension;
|
||||
import google.registry.model.eppcommon.AuthInfo;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
@@ -169,15 +169,12 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
|
||||
extensionManager.validate();
|
||||
DateTime now = tm().getTransactionTime();
|
||||
Domain existingDomain = loadAndVerifyExistence(Domain.class, targetId, now);
|
||||
// Currently we do not do anything with this allocation token, but just want it loaded and
|
||||
// available in this flow in case we use it in the future
|
||||
Optional<AllocationToken> allocationToken =
|
||||
allocationTokenFlowUtils.verifyAllocationTokenIfPresent(
|
||||
existingDomain,
|
||||
Registry.get(existingDomain.getTld()),
|
||||
gainingClientId,
|
||||
now,
|
||||
eppInput.getSingleExtension(AllocationTokenExtension.class));
|
||||
allocationTokenFlowUtils.verifyAllocationTokenIfPresent(
|
||||
existingDomain,
|
||||
Registry.get(existingDomain.getTld()),
|
||||
gainingClientId,
|
||||
now,
|
||||
eppInput.getSingleExtension(AllocationTokenExtension.class));
|
||||
Optional<DomainTransferRequestSuperuserExtension> superuserExtension =
|
||||
eppInput.getSingleExtension(DomainTransferRequestSuperuserExtension.class);
|
||||
Period period =
|
||||
@@ -210,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
|
||||
@@ -261,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()
|
||||
@@ -325,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.
|
||||
@@ -370,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);
|
||||
|
||||
@@ -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 =
|
||||
|
||||
Optional<AllocationToken> maybeTokenEntity = AllocationToken.maybeGetStaticTokenInstance(token);
|
||||
if (maybeTokenEntity.isPresent()) {
|
||||
return maybeTokenEntity.get();
|
||||
}
|
||||
|
||||
maybeTokenEntity =
|
||||
tm().transact(() -> tm().loadByKeyIfPresent(VKey.create(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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@ 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;
|
||||
@@ -28,7 +28,6 @@ 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;
|
||||
@@ -43,8 +42,8 @@ public final class EntityClasses {
|
||||
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,
|
||||
@@ -58,9 +57,6 @@ public final class EntityClasses {
|
||||
Host.class,
|
||||
HostHistory.class,
|
||||
Lock.class,
|
||||
PollMessage.class,
|
||||
PollMessage.Autorenew.class,
|
||||
PollMessage.OneTime.class,
|
||||
RdeRevision.class,
|
||||
Registrar.class,
|
||||
ServerSecret.class);
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -25,7 +25,7 @@ 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;
|
||||
@@ -60,7 +60,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 +93,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,7 +101,7 @@ public final class ResourceTransferUtils {
|
||||
}
|
||||
|
||||
private static void assertIsContactOrDomain(EppResource eppResource) {
|
||||
checkState(eppResource instanceof ContactResource || eppResource instanceof Domain);
|
||||
checkState(eppResource instanceof Contact || eppResource instanceof Domain);
|
||||
}
|
||||
|
||||
/** Update the relevant {@link ForeignKeyIndex} to cache the new deletion time. */
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -24,28 +24,36 @@ import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
|
||||
import google.registry.model.Buildable;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Index;
|
||||
import javax.persistence.Table;
|
||||
|
||||
/** A console user, either a registry employee or a registrar partner. */
|
||||
@Entity
|
||||
@Table(
|
||||
indexes = {
|
||||
@Index(columnList = "gaiaId", name = "user_gaia_id_idx"),
|
||||
@Index(columnList = "emailAddress", name = "user_email_address_idx")
|
||||
})
|
||||
public class User extends ImmutableObject implements Buildable {
|
||||
|
||||
/** Autogenerated unique ID of this user. */
|
||||
private long id;
|
||||
@Id 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 +63,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 +79,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 +93,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 +123,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 +148,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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,11 +124,11 @@ 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;
|
||||
|
||||
/** Contact’s voice number. Personal info; cleared by {@link ContactResource.Builder#wipeOut}. */
|
||||
/** Contact’s voice number. Personal info; cleared by {@link Contact.Builder#wipeOut}. */
|
||||
@IgnoreSave(IfNull.class)
|
||||
@Embedded
|
||||
@AttributeOverrides({
|
||||
@@ -136,7 +137,7 @@ public class ContactBase extends EppResource implements ResourceWithTransferData
|
||||
})
|
||||
ContactPhoneNumber voice;
|
||||
|
||||
/** Contact’s fax number. Personal info; cleared by {@link ContactResource.Builder#wipeOut}. */
|
||||
/** Contact’s fax number. Personal info; cleared by {@link Contact.Builder#wipeOut}. */
|
||||
@IgnoreSave(IfNull.class)
|
||||
@Embedded
|
||||
@AttributeOverrides({
|
||||
@@ -145,7 +146,7 @@ public class ContactBase extends EppResource implements ResourceWithTransferData
|
||||
})
|
||||
ContactPhoneNumber fax;
|
||||
|
||||
/** Contact’s email address. Personal info; cleared by {@link ContactResource.Builder#wipeOut}. */
|
||||
/** Contact’s email address. Personal info; cleared by {@link Contact.Builder#wipeOut}. */
|
||||
@IgnoreSave(IfNull.class)
|
||||
String email;
|
||||
|
||||
@@ -187,7 +188,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 +292,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> {
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,11 +21,11 @@ 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -22,7 +22,7 @@ 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;
|
||||
@@ -64,7 +64,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 +74,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -52,7 +52,7 @@ 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;
|
||||
@@ -132,11 +132,11 @@ 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. */
|
||||
@Embedded
|
||||
@@ -216,13 +216,6 @@ 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;
|
||||
|
||||
@@ -322,10 +315,6 @@ public class DomainBase extends EppResource
|
||||
return autorenewPollMessage;
|
||||
}
|
||||
|
||||
public Long getAutorenewPollMessageHistoryId() {
|
||||
return autorenewPollMessageHistoryId;
|
||||
}
|
||||
|
||||
public ImmutableSet<GracePeriod> getGracePeriods() {
|
||||
return nullToEmptyImmutableCopy(gracePeriods);
|
||||
}
|
||||
@@ -509,9 +498,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.
|
||||
@@ -615,19 +602,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;
|
||||
}
|
||||
|
||||
@@ -641,7 +628,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)
|
||||
@@ -784,7 +771,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();
|
||||
@@ -886,11 +873,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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 {}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -26,6 +26,7 @@ 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;
|
||||
@@ -79,6 +80,10 @@ import org.joda.time.DateTime;
|
||||
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 +92,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 +120,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. */
|
||||
@@ -251,8 +286,16 @@ 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() {
|
||||
if (!AllocationToken.TokenBehavior.DEFAULT.equals(getTokenBehavior())) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format("%s tokens are not stored in the database", getTokenBehavior()));
|
||||
}
|
||||
return VKey.create(AllocationToken.class, getToken(), Key.create(this));
|
||||
}
|
||||
|
||||
@@ -274,6 +317,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 +328,10 @@ 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.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");
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ 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;
|
||||
@@ -70,10 +70,10 @@ import org.joda.time.DateTime;
|
||||
@DeleteAfterMigration
|
||||
public abstract class ForeignKeyIndex<E extends EppResource> extends BackupGroupRoot {
|
||||
|
||||
/** The {@link ForeignKeyIndex} type for {@link ContactResource} entities. */
|
||||
/** The {@link ForeignKeyIndex} type for {@link Contact} entities. */
|
||||
@ReportedOn
|
||||
@Entity
|
||||
public static class ForeignKeyContactIndex extends ForeignKeyIndex<ContactResource> {}
|
||||
public static class ForeignKeyContactIndex extends ForeignKeyIndex<Contact> {}
|
||||
|
||||
/** The {@link ForeignKeyIndex} type for {@link Domain} entities. */
|
||||
@ReportedOn
|
||||
@@ -89,14 +89,14 @@ public abstract class ForeignKeyIndex<E extends EppResource> extends BackupGroup
|
||||
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");
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,32 +201,22 @@ 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);
|
||||
}
|
||||
|
||||
/** Override Buildable.asBuilder() to give this method stronger typing. */
|
||||
@Override
|
||||
public abstract Builder<?, ?> asBuilder();
|
||||
@@ -318,9 +281,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 +295,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 +325,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 +337,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 +362,6 @@ public abstract class PollMessage extends ImmutableObject
|
||||
})
|
||||
PendingActionNotificationResponse pendingActionNotificationResponse;
|
||||
|
||||
@Ignore
|
||||
@Embedded
|
||||
@AttributeOverrides({
|
||||
@AttributeOverride(
|
||||
@@ -420,35 +382,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<PollMessage> 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 +473,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 +534,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 +550,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 +562,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<PollMessage> 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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -59,10 +59,8 @@ 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;
|
||||
@@ -315,7 +313,7 @@ public class Registrar extends ImmutableObject
|
||||
* Localized {@link RegistrarAddress} for this registrar. Contents can be represented in
|
||||
* unrestricted UTF-8.
|
||||
*/
|
||||
@IgnoreSave(IfNull.class)
|
||||
@Ignore
|
||||
@Embedded
|
||||
@AttributeOverrides({
|
||||
@AttributeOverride(
|
||||
@@ -340,7 +338,7 @@ 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)
|
||||
@Ignore
|
||||
@Embedded
|
||||
@AttributeOverrides({
|
||||
@AttributeOverride(name = "streetLine1", column = @Column(name = "i18n_address_street_line1")),
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -34,10 +34,10 @@ 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;
|
||||
@@ -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),
|
||||
|
||||
@@ -25,8 +25,8 @@ import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Streams;
|
||||
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;
|
||||
@@ -51,7 +51,7 @@ public class HistoryEntryDao {
|
||||
public static ImmutableMap<Class<? extends EppResource>, Class<? extends HistoryEntry>>
|
||||
RESOURCE_TYPES_TO_HISTORY_TYPES =
|
||||
ImmutableMap.of(
|
||||
ContactResource.class,
|
||||
Contact.class,
|
||||
ContactHistory.class,
|
||||
Domain.class,
|
||||
DomainHistory.class,
|
||||
|
||||
@@ -40,7 +40,7 @@ public class EppHistoryVKeyTranslatorFactory
|
||||
// one dedicated VKey class, e.g. DomainHistoryVKey, for each such kind of entity, and we need
|
||||
// a way to map the raw Datastore key to its VKey class. So, we use the kind path as the key of
|
||||
// the map, and the kind path is created by concatenating all the kind strings in a raw Datastore
|
||||
// key, e.g. the map key for ContactPollMessageVKey is "ContactResource/HistoryEntry/PollMessage".
|
||||
// key, e.g. the map key for ContactPollMessageVKey is "Contact/HistoryEntry/PollMessage".
|
||||
@VisibleForTesting
|
||||
static final ImmutableMap<String, Class<? extends EppHistoryVKey>> kindPathToVKeyClass =
|
||||
ImmutableSet.of(DomainHistoryVKey.class).stream()
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
// 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.persistence.converter;
|
||||
|
||||
import google.registry.model.console.RegistrarRole;
|
||||
import java.util.Map;
|
||||
import javax.persistence.Converter;
|
||||
|
||||
/** JPA converter for storing / retrieving {@code Map<String, RegistrarRole>} objects. */
|
||||
@Converter(autoApply = true)
|
||||
public class RegistrarToRoleConverter
|
||||
extends StringMapConverterBase<String, RegistrarRole, Map<String, RegistrarRole>> {
|
||||
|
||||
@Override
|
||||
protected String convertKeyToString(String key) {
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String convertValueToString(RegistrarRole value) {
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String convertStringToKey(String string) {
|
||||
return string;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RegistrarRole convertStringToValue(String string) {
|
||||
return RegistrarRole.valueOf(string);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map<String, RegistrarRole> convertMapToDerivedType(Map<String, RegistrarRole> map) {
|
||||
return map;
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
package google.registry.rdap;
|
||||
|
||||
import static google.registry.flows.domain.DomainFlowUtils.validateDomainName;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKeyCached;
|
||||
import static google.registry.request.Action.Method.GET;
|
||||
import static google.registry.request.Action.Method.HEAD;
|
||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
@@ -60,7 +60,7 @@ public class RdapDomainAction extends RdapActionBase {
|
||||
}
|
||||
// The query string is not used; the RDAP syntax is /rdap/domain/mydomain.com.
|
||||
Optional<Domain> domain =
|
||||
loadByForeignKey(
|
||||
loadByForeignKeyCached(
|
||||
Domain.class,
|
||||
pathSearchString,
|
||||
shouldIncludeDeleted() ? START_OF_TIME : rdapJsonFormatter.getRequestTime());
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
package google.registry.rdap;
|
||||
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKeyCached;
|
||||
import static google.registry.model.index.ForeignKeyIndex.loadAndGetKey;
|
||||
import static google.registry.model.ofy.ObjectifyService.auditedOfy;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaJpaTm;
|
||||
@@ -188,7 +188,8 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
|
||||
private DomainSearchResponse searchByDomainNameWithoutWildcard(
|
||||
final RdapSearchPattern partialStringQuery) {
|
||||
Optional<Domain> domain =
|
||||
loadByForeignKey(Domain.class, partialStringQuery.getInitialString(), getRequestTime());
|
||||
loadByForeignKeyCached(
|
||||
Domain.class, partialStringQuery.getInitialString(), getRequestTime());
|
||||
return makeSearchResults(
|
||||
shouldBeVisible(domain) ? ImmutableList.of(domain.get()) : ImmutableList.of());
|
||||
}
|
||||
@@ -389,7 +390,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
|
||||
Optional<String> desiredRegistrar = getDesiredRegistrar();
|
||||
if (desiredRegistrar.isPresent()) {
|
||||
Optional<Host> host =
|
||||
loadByForeignKey(
|
||||
loadByForeignKeyCached(
|
||||
Host.class,
|
||||
partialStringQuery.getInitialString(),
|
||||
shouldIncludeDeleted() ? START_OF_TIME : getRequestTime());
|
||||
@@ -414,7 +415,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
|
||||
// through the subordinate hosts. This is more efficient, and lets us permit wildcard searches
|
||||
// with no initial string.
|
||||
Domain domain =
|
||||
loadByForeignKey(
|
||||
loadByForeignKeyCached(
|
||||
Domain.class,
|
||||
partialStringQuery.getSuffix(),
|
||||
shouldIncludeDeleted() ? START_OF_TIME : getRequestTime())
|
||||
@@ -431,7 +432,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
|
||||
if (partialStringQuery.matches(fqhn)) {
|
||||
if (desiredRegistrar.isPresent()) {
|
||||
Optional<Host> host =
|
||||
loadByForeignKey(
|
||||
loadByForeignKeyCached(
|
||||
Host.class, fqhn, shouldIncludeDeleted() ? START_OF_TIME : getRequestTime());
|
||||
if (host.isPresent()
|
||||
&& desiredRegistrar
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
package google.registry.rdap;
|
||||
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaJpaTm;
|
||||
import static google.registry.rdap.RdapUtils.getRegistrarByIanaIdentifier;
|
||||
import static google.registry.rdap.RdapUtils.getRegistrarByName;
|
||||
import static google.registry.request.Action.Method.GET;
|
||||
@@ -23,7 +23,7 @@ import static google.registry.request.Action.Method.HEAD;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.primitives.Longs;
|
||||
import com.google.re2j.Pattern;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.rdap.RdapJsonFormatter.OutputDataType;
|
||||
@@ -69,14 +69,14 @@ public class RdapEntityAction extends RdapActionBase {
|
||||
// RDAP Technical Implementation Guide 2.3.1 - MUST support contact entity lookup using the
|
||||
// handle
|
||||
if (ROID_PATTERN.matcher(pathSearchString).matches()) {
|
||||
VKey<ContactResource> contactVKey = VKey.create(ContactResource.class, pathSearchString);
|
||||
Optional<ContactResource> contactResource =
|
||||
tm().transact(() -> tm().loadByKeyIfPresent(contactVKey));
|
||||
VKey<Contact> contactVKey = VKey.create(Contact.class, pathSearchString);
|
||||
Optional<Contact> contact =
|
||||
replicaJpaTm().transact(() -> replicaJpaTm().loadByKeyIfPresent(contactVKey));
|
||||
// As per Andy Newton on the regext mailing list, contacts by themselves have no role, since
|
||||
// they are global, and might have different roles for different domains.
|
||||
if (contactResource.isPresent() && isAuthorized(contactResource.get())) {
|
||||
if (contact.isPresent() && isAuthorized(contact.get())) {
|
||||
return rdapJsonFormatter.createRdapContactEntity(
|
||||
contactResource.get(), ImmutableSet.of(), OutputDataType.FULL);
|
||||
contact.get(), ImmutableSet.of(), OutputDataType.FULL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ import com.google.common.collect.Streams;
|
||||
import com.google.common.primitives.Booleans;
|
||||
import com.google.common.primitives.Longs;
|
||||
import com.googlecode.objectify.cmd.Query;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.persistence.transaction.CriteriaQueryBuilder;
|
||||
@@ -253,7 +253,7 @@ public class RdapEntitySearchAction extends RdapSearchActionBase {
|
||||
// see any names anyway. Also, if a registrar cursor is present, we have already moved past the
|
||||
// contacts, and don't need to fetch them this time. We can skip contacts if subtype is
|
||||
// REGISTRARS.
|
||||
RdapResultSet<ContactResource> resultSet;
|
||||
RdapResultSet<Contact> resultSet;
|
||||
if (subtype == Subtype.REGISTRARS) {
|
||||
resultSet = RdapResultSet.create(ImmutableList.of());
|
||||
} else {
|
||||
@@ -262,9 +262,9 @@ public class RdapEntitySearchAction extends RdapSearchActionBase {
|
||||
resultSet = RdapResultSet.create(ImmutableList.of());
|
||||
} else {
|
||||
if (tm().isOfy()) {
|
||||
Query<ContactResource> query =
|
||||
Query<Contact> query =
|
||||
queryItems(
|
||||
ContactResource.class,
|
||||
Contact.class,
|
||||
"searchName",
|
||||
partialStringQuery,
|
||||
cursorQueryString, // if we get here and there's a cursor, it must be a contact
|
||||
@@ -279,9 +279,9 @@ public class RdapEntitySearchAction extends RdapSearchActionBase {
|
||||
replicaJpaTm()
|
||||
.transact(
|
||||
() -> {
|
||||
CriteriaQueryBuilder<ContactResource> builder =
|
||||
CriteriaQueryBuilder<Contact> builder =
|
||||
queryItemsSql(
|
||||
ContactResource.class,
|
||||
Contact.class,
|
||||
"searchName",
|
||||
partialStringQuery,
|
||||
cursorQueryString,
|
||||
@@ -319,19 +319,20 @@ public class RdapEntitySearchAction extends RdapSearchActionBase {
|
||||
}
|
||||
// Handle queries without a wildcard (and not including deleted) -- load by ID.
|
||||
if (!partialStringQuery.getHasWildcard() && !shouldIncludeDeleted()) {
|
||||
ImmutableList<ContactResource> contactResourceList;
|
||||
ImmutableList<Contact> contactList;
|
||||
if (subtype == Subtype.REGISTRARS) {
|
||||
contactResourceList = ImmutableList.of();
|
||||
contactList = ImmutableList.of();
|
||||
} else {
|
||||
Optional<ContactResource> contactResource =
|
||||
tm().transact(
|
||||
Optional<Contact> contact =
|
||||
replicaJpaTm()
|
||||
.transact(
|
||||
() ->
|
||||
tm().loadByKeyIfPresent(
|
||||
VKey.create(
|
||||
ContactResource.class, partialStringQuery.getInitialString())));
|
||||
contactResourceList =
|
||||
(contactResource.isPresent() && shouldBeVisible(contactResource.get()))
|
||||
? ImmutableList.of(contactResource.get())
|
||||
replicaJpaTm()
|
||||
.loadByKeyIfPresent(
|
||||
VKey.create(Contact.class, partialStringQuery.getInitialString())));
|
||||
contactList =
|
||||
(contact.isPresent() && shouldBeVisible(contact.get()))
|
||||
? ImmutableList.of(contact.get())
|
||||
: ImmutableList.of();
|
||||
}
|
||||
ImmutableList<Registrar> registrarList;
|
||||
@@ -341,9 +342,9 @@ public class RdapEntitySearchAction extends RdapSearchActionBase {
|
||||
registrarList = getMatchingRegistrars(partialStringQuery.getInitialString());
|
||||
}
|
||||
return makeSearchResults(
|
||||
contactResourceList,
|
||||
contactList,
|
||||
IncompletenessWarningType.COMPLETE,
|
||||
contactResourceList.size(),
|
||||
contactList.size(),
|
||||
registrarList,
|
||||
QueryType.HANDLE);
|
||||
// Handle queries with a wildcard (or including deleted), but no suffix. Because the handle
|
||||
@@ -381,7 +382,7 @@ public class RdapEntitySearchAction extends RdapSearchActionBase {
|
||||
// get excluded due to permissioning. Any cursor present must be a contact cursor, because we
|
||||
// would never return a registrar for this search.
|
||||
int querySizeLimit = getStandardQuerySizeLimit();
|
||||
RdapResultSet<ContactResource> contactResultSet;
|
||||
RdapResultSet<Contact> contactResultSet;
|
||||
if (subtype == Subtype.REGISTRARS) {
|
||||
contactResultSet = RdapResultSet.create(ImmutableList.of());
|
||||
} else {
|
||||
@@ -389,7 +390,7 @@ public class RdapEntitySearchAction extends RdapSearchActionBase {
|
||||
contactResultSet =
|
||||
getMatchingResources(
|
||||
queryItemsByKey(
|
||||
ContactResource.class,
|
||||
Contact.class,
|
||||
partialStringQuery,
|
||||
cursorQueryString,
|
||||
getDeletedItemHandling(),
|
||||
@@ -403,7 +404,7 @@ public class RdapEntitySearchAction extends RdapSearchActionBase {
|
||||
() ->
|
||||
getMatchingResourcesSql(
|
||||
queryItemsByKeySql(
|
||||
ContactResource.class,
|
||||
Contact.class,
|
||||
partialStringQuery,
|
||||
cursorQueryString,
|
||||
getDeletedItemHandling()),
|
||||
@@ -434,7 +435,7 @@ public class RdapEntitySearchAction extends RdapSearchActionBase {
|
||||
* properties of the {@link RdapResultSet} structure and passes them as separate arguments.
|
||||
*/
|
||||
private EntitySearchResponse makeSearchResults(
|
||||
RdapResultSet<ContactResource> resultSet, List<Registrar> registrars, QueryType queryType) {
|
||||
RdapResultSet<Contact> resultSet, List<Registrar> registrars, QueryType queryType) {
|
||||
return makeSearchResults(
|
||||
resultSet.resources(),
|
||||
resultSet.incompletenessWarningType(),
|
||||
@@ -459,7 +460,7 @@ public class RdapEntitySearchAction extends RdapSearchActionBase {
|
||||
* @return an {@link RdapSearchResults} object
|
||||
*/
|
||||
private EntitySearchResponse makeSearchResults(
|
||||
List<ContactResource> contacts,
|
||||
List<Contact> contacts,
|
||||
IncompletenessWarningType incompletenessWarningType,
|
||||
int numContactsRetrieved,
|
||||
List<Registrar> registrars,
|
||||
@@ -483,7 +484,7 @@ public class RdapEntitySearchAction extends RdapSearchActionBase {
|
||||
EntitySearchResponse.builder()
|
||||
.setIncompletenessWarningType(incompletenessWarningType);
|
||||
Optional<String> newCursor = Optional.empty();
|
||||
for (ContactResource contact : Iterables.limit(contacts, rdapResultSetMaxSize)) {
|
||||
for (Contact contact : Iterables.limit(contacts, rdapResultSetMaxSize)) {
|
||||
// As per Andy Newton on the regext mailing list, contacts by themselves have no role, since
|
||||
// they are global, and might have different roles for different domains.
|
||||
builder
|
||||
|
||||
@@ -20,7 +20,7 @@ import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static com.google.common.collect.ImmutableSetMultimap.toImmutableSetMultimap;
|
||||
import static google.registry.model.EppResourceUtils.isLinked;
|
||||
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.rdap.RdapIcannStandardInformation.CONTACT_REDACTED_VALUE;
|
||||
import static google.registry.util.CollectionUtils.union;
|
||||
@@ -39,9 +39,9 @@ import com.google.common.net.InetAddresses;
|
||||
import com.google.gson.JsonArray;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactAddress;
|
||||
import google.registry.model.contact.ContactPhoneNumber;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.contact.PostalInfo;
|
||||
import google.registry.model.domain.DesignatedContact;
|
||||
import google.registry.model.domain.DesignatedContact.Type;
|
||||
@@ -357,10 +357,15 @@ public class RdapJsonFormatter {
|
||||
// Kick off the database loads of the nameservers that we will need, so it can load
|
||||
// asynchronously while we load and process the contacts.
|
||||
ImmutableSet<Host> loadedHosts =
|
||||
tm().transact(() -> ImmutableSet.copyOf(tm().loadByKeys(domain.getNameservers()).values()));
|
||||
replicaJpaTm()
|
||||
.transact(
|
||||
() ->
|
||||
ImmutableSet.copyOf(
|
||||
replicaJpaTm().loadByKeys(domain.getNameservers()).values()));
|
||||
// Load the registrant and other contacts and add them to the data.
|
||||
ImmutableMap<VKey<? extends ContactResource>, ContactResource> loadedContacts =
|
||||
tm().transact(() -> tm().loadByKeysIfPresent(domain.getReferencedContacts()));
|
||||
ImmutableMap<VKey<? extends Contact>, Contact> loadedContacts =
|
||||
replicaJpaTm()
|
||||
.transact(() -> replicaJpaTm().loadByKeysIfPresent(domain.getReferencedContacts()));
|
||||
// RDAP Response Profile 2.7.3, A domain MUST have the REGISTRANT, ADMIN, TECH roles and MAY
|
||||
// have others. We also add the BILLING.
|
||||
//
|
||||
@@ -368,7 +373,7 @@ public class RdapJsonFormatter {
|
||||
// fields we don't want to show (as opposed to not having contacts at all) because of GDPR etc.
|
||||
//
|
||||
// the GDPR redaction is handled in createRdapContactEntity
|
||||
ImmutableSetMultimap<VKey<ContactResource>, Type> contactsToRoles =
|
||||
ImmutableSetMultimap<VKey<Contact>, Type> contactsToRoles =
|
||||
Streams.concat(
|
||||
domain.getContacts().stream(),
|
||||
Stream.of(DesignatedContact.create(Type.REGISTRANT, domain.getRegistrant())))
|
||||
@@ -377,7 +382,7 @@ public class RdapJsonFormatter {
|
||||
toImmutableSetMultimap(
|
||||
DesignatedContact::getContactKey, DesignatedContact::getType));
|
||||
|
||||
for (VKey<ContactResource> contactKey : contactsToRoles.keySet()) {
|
||||
for (VKey<Contact> contactKey : contactsToRoles.keySet()) {
|
||||
Set<RdapEntity.Role> roles =
|
||||
contactsToRoles.get(contactKey).stream()
|
||||
.map(RdapJsonFormatter::convertContactTypeToRdapRole)
|
||||
@@ -439,9 +444,11 @@ public class RdapJsonFormatter {
|
||||
statuses.add(StatusValue.LINKED);
|
||||
}
|
||||
if (host.isSubordinate()
|
||||
&& tm().transact(
|
||||
&& replicaJpaTm()
|
||||
.transact(
|
||||
() ->
|
||||
tm().loadByKey(host.getSuperordinateDomain())
|
||||
replicaJpaTm()
|
||||
.loadByKey(host.getSuperordinateDomain())
|
||||
.cloneProjectedAtTime(getRequestTime())
|
||||
.getStatusValues()
|
||||
.contains(StatusValue.PENDING_TRANSFER))) {
|
||||
@@ -488,16 +495,14 @@ public class RdapJsonFormatter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a JSON object for a {@link ContactResource} and associated contact type.
|
||||
* Creates a JSON object for a {@link Contact} and associated contact type.
|
||||
*
|
||||
* @param contactResource the contact resource object from which the JSON object should be created
|
||||
* @param contact the contact resource object from which the JSON object should be created
|
||||
* @param roles the roles of this contact
|
||||
* @param outputDataType whether to generate full or summary data
|
||||
*/
|
||||
RdapContactEntity createRdapContactEntity(
|
||||
ContactResource contactResource,
|
||||
Iterable<RdapEntity.Role> roles,
|
||||
OutputDataType outputDataType) {
|
||||
Contact contact, Iterable<RdapEntity.Role> roles, OutputDataType outputDataType) {
|
||||
RdapContactEntity.Builder contactBuilder = RdapContactEntity.builder();
|
||||
|
||||
// RDAP Response Profile 2.7.1, 2.7.3 - we MUST have the contacts. 2.7.4 discusses censoring of
|
||||
@@ -505,12 +510,12 @@ public class RdapJsonFormatter {
|
||||
//
|
||||
// 2.8 allows for unredacted output for authorized people.
|
||||
boolean isAuthorized =
|
||||
rdapAuthorization.isAuthorizedForRegistrar(contactResource.getCurrentSponsorRegistrarId());
|
||||
rdapAuthorization.isAuthorizedForRegistrar(contact.getCurrentSponsorRegistrarId());
|
||||
|
||||
// ROID needs to be redacted if we aren't authorized, so we can't have a self-link for
|
||||
// unauthorized users
|
||||
if (isAuthorized) {
|
||||
contactBuilder.linksBuilder().add(makeSelfLink("entity", contactResource.getRepoId()));
|
||||
contactBuilder.linksBuilder().add(makeSelfLink("entity", contact.getRepoId()));
|
||||
}
|
||||
|
||||
// Only show the "summary data remark" if the user is authorized to see this data - because
|
||||
@@ -537,10 +542,10 @@ public class RdapJsonFormatter {
|
||||
.remarksBuilder()
|
||||
.add(RdapIcannStandardInformation.CONTACT_PERSONAL_DATA_HIDDEN_DATA_REMARK);
|
||||
// to make sure we don't accidentally display data we shouldn't - we replace the
|
||||
// contactResource with a safe resource. Then we can add any information we need (e.g. the
|
||||
// contact with a safe resource. Then we can add any information we need (e.g. the
|
||||
// Organization / state / country of the registrant), although we currently don't do that.
|
||||
contactResource =
|
||||
new ContactResource.Builder()
|
||||
contact =
|
||||
new Contact.Builder()
|
||||
.setRepoId(CONTACT_REDACTED_VALUE)
|
||||
.setVoiceNumber(
|
||||
new ContactPhoneNumber.Builder().setPhoneNumber(CONTACT_REDACTED_VALUE).build())
|
||||
@@ -565,7 +570,7 @@ public class RdapJsonFormatter {
|
||||
|
||||
// RDAP Response Profile 2.7.3 - we MUST provide a handle set with the ROID, subject to the
|
||||
// redaction above.
|
||||
contactBuilder.setHandle(contactResource.getRepoId());
|
||||
contactBuilder.setHandle(contact.getRepoId());
|
||||
|
||||
// RDAP Response Profile doesn't mention status for contacts, so we only show it if we're both
|
||||
// FULL and Authorized.
|
||||
@@ -574,11 +579,11 @@ public class RdapJsonFormatter {
|
||||
.statusBuilder()
|
||||
.addAll(
|
||||
makeStatusValueList(
|
||||
isLinked(contactResource.createVKey(), getRequestTime())
|
||||
? union(contactResource.getStatusValues(), StatusValue.LINKED)
|
||||
: contactResource.getStatusValues(),
|
||||
isLinked(contact.createVKey(), getRequestTime())
|
||||
? union(contact.getStatusValues(), StatusValue.LINKED)
|
||||
: contact.getStatusValues(),
|
||||
false,
|
||||
contactResource.getDeletionTime().isBefore(getRequestTime())));
|
||||
contact.getDeletionTime().isBefore(getRequestTime())));
|
||||
}
|
||||
|
||||
contactBuilder.rolesBuilder().addAll(roles);
|
||||
@@ -589,9 +594,9 @@ public class RdapJsonFormatter {
|
||||
// RDAP Response Profile 2.7.3 - we MUST have FN, ADR, TEL, EMAIL.
|
||||
//
|
||||
// Note that 2.7.5 also says the EMAIL must be omitted, so we'll omit it
|
||||
PostalInfo postalInfo = contactResource.getInternationalizedPostalInfo();
|
||||
PostalInfo postalInfo = contact.getInternationalizedPostalInfo();
|
||||
if (postalInfo == null) {
|
||||
postalInfo = contactResource.getLocalizedPostalInfo();
|
||||
postalInfo = contact.getLocalizedPostalInfo();
|
||||
}
|
||||
if (postalInfo != null) {
|
||||
if (postalInfo.getName() != null) {
|
||||
@@ -602,11 +607,11 @@ public class RdapJsonFormatter {
|
||||
}
|
||||
addVCardAddressEntry(vcardBuilder, postalInfo.getAddress());
|
||||
}
|
||||
ContactPhoneNumber voicePhoneNumber = contactResource.getVoiceNumber();
|
||||
ContactPhoneNumber voicePhoneNumber = contact.getVoiceNumber();
|
||||
if (voicePhoneNumber != null) {
|
||||
vcardBuilder.add(makePhoneEntry(PHONE_TYPE_VOICE, makePhoneString(voicePhoneNumber)));
|
||||
}
|
||||
ContactPhoneNumber faxPhoneNumber = contactResource.getFaxNumber();
|
||||
ContactPhoneNumber faxPhoneNumber = contact.getFaxNumber();
|
||||
if (faxPhoneNumber != null) {
|
||||
vcardBuilder.add(makePhoneEntry(PHONE_TYPE_FAX, makePhoneString(faxPhoneNumber)));
|
||||
}
|
||||
@@ -634,7 +639,7 @@ public class RdapJsonFormatter {
|
||||
// We also only add it for authorized users because millisecond times can fingerprint a user
|
||||
// just as much as the handle can.
|
||||
if (outputDataType == OutputDataType.FULL && isAuthorized) {
|
||||
contactBuilder.eventsBuilder().addAll(makeOptionalEvents(contactResource));
|
||||
contactBuilder.eventsBuilder().addAll(makeOptionalEvents(contact));
|
||||
}
|
||||
return contactBuilder.build();
|
||||
}
|
||||
@@ -899,7 +904,8 @@ public class RdapJsonFormatter {
|
||||
.replace("%repoIdField%", repoIdFieldName)
|
||||
.replace("%repoIdValue%", resourceVkey.getSqlKey().toString());
|
||||
historyEntries =
|
||||
jpaTm().transact(() -> jpaTm().getEntityManager().createQuery(jpql).getResultList());
|
||||
replicaJpaTm()
|
||||
.transact(() -> replicaJpaTm().getEntityManager().createQuery(jpql).getResultList());
|
||||
}
|
||||
for (HistoryEntry historyEntry : historyEntries) {
|
||||
EventAction rdapEventAction =
|
||||
|
||||
@@ -108,7 +108,7 @@ public final class RdapModule {
|
||||
if (userAuthInfo.isUserAdmin()) {
|
||||
return RdapAuthorization.ADMINISTRATOR_AUTHORIZATION;
|
||||
}
|
||||
ImmutableSet<String> clientIds = registrarAccessor.getAllClientIdWithRoles().keySet();
|
||||
ImmutableSet<String> clientIds = registrarAccessor.getAllRegistrarIdsWithRoles().keySet();
|
||||
if (clientIds.isEmpty()) {
|
||||
logger.atWarning().log("Couldn't find registrar for User %s.", authResult.userIdForLogging());
|
||||
return RdapAuthorization.PUBLIC_AUTHORIZATION;
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
package google.registry.rdap;
|
||||
|
||||
import static google.registry.flows.host.HostFlowUtils.validateHostName;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKeyCached;
|
||||
import static google.registry.request.Action.Method.GET;
|
||||
import static google.registry.request.Action.Method.HEAD;
|
||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
@@ -62,7 +62,7 @@ public class RdapNameserverAction extends RdapActionBase {
|
||||
// If there are no undeleted nameservers with the given name, the foreign key should point to
|
||||
// the most recently deleted one.
|
||||
Optional<Host> host =
|
||||
loadByForeignKey(
|
||||
loadByForeignKeyCached(
|
||||
Host.class,
|
||||
pathSearchString,
|
||||
shouldIncludeDeleted() ? START_OF_TIME : getRequestTime());
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
package google.registry.rdap;
|
||||
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKeyCached;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaJpaTm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.request.Action.Method.GET;
|
||||
@@ -160,7 +160,7 @@ public class RdapNameserverSearchAction extends RdapSearchActionBase {
|
||||
.setIncompletenessWarningType(IncompletenessWarningType.COMPLETE);
|
||||
|
||||
Optional<Host> host =
|
||||
loadByForeignKey(Host.class, partialStringQuery.getInitialString(), getRequestTime());
|
||||
loadByForeignKeyCached(Host.class, partialStringQuery.getInitialString(), getRequestTime());
|
||||
|
||||
metricInformationBuilder.setNumHostsRetrieved(host.isPresent() ? 1 : 0);
|
||||
|
||||
@@ -176,7 +176,7 @@ public class RdapNameserverSearchAction extends RdapSearchActionBase {
|
||||
private NameserverSearchResponse searchByNameUsingSuperordinateDomain(
|
||||
RdapSearchPattern partialStringQuery) {
|
||||
Optional<Domain> domain =
|
||||
loadByForeignKey(Domain.class, partialStringQuery.getSuffix(), getRequestTime());
|
||||
loadByForeignKeyCached(Domain.class, partialStringQuery.getSuffix(), getRequestTime());
|
||||
if (!domain.isPresent()) {
|
||||
// Don't allow wildcards with suffixes which are not domains we manage. That would risk a
|
||||
// table scan in many easily foreseeable cases. The user might ask for ns*.zombo.com,
|
||||
@@ -194,7 +194,7 @@ public class RdapNameserverSearchAction extends RdapSearchActionBase {
|
||||
// We can't just check that the host name starts with the initial query string, because
|
||||
// then the query ns.exam*.example.com would match against nameserver ns.example.com.
|
||||
if (partialStringQuery.matches(fqhn)) {
|
||||
Optional<Host> host = loadByForeignKey(Host.class, fqhn, getRequestTime());
|
||||
Optional<Host> host = loadByForeignKeyCached(Host.class, fqhn, getRequestTime());
|
||||
if (shouldBeVisible(host)) {
|
||||
hostList.add(host.get());
|
||||
if (hostList.size() > rdapResultSetMaxSize) {
|
||||
|
||||
@@ -16,9 +16,9 @@ package google.registry.rde;
|
||||
|
||||
import static google.registry.util.XmlEnumUtils.enumToXml;
|
||||
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactAddress;
|
||||
import google.registry.model.contact.ContactPhoneNumber;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.contact.Disclose;
|
||||
import google.registry.model.contact.Disclose.PostalInfoChoice;
|
||||
import google.registry.model.contact.PostalInfo;
|
||||
@@ -39,16 +39,16 @@ import google.registry.xjc.rdecontact.XjcRdeContactTransferDataType;
|
||||
import javax.annotation.CheckForNull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/** Utility class that turns {@link ContactResource} as {@link XjcRdeContactElement}. */
|
||||
final class ContactResourceToXjcConverter {
|
||||
/** Utility class that turns {@link Contact} as {@link XjcRdeContactElement}. */
|
||||
final class ContactToXjcConverter {
|
||||
|
||||
/** Converts {@link ContactResource} to {@link XjcRdeContactElement}. */
|
||||
static XjcRdeContactElement convert(ContactResource host) {
|
||||
/** Converts {@link Contact} to {@link XjcRdeContactElement}. */
|
||||
static XjcRdeContactElement convert(Contact host) {
|
||||
return new XjcRdeContactElement(convertContact(host));
|
||||
}
|
||||
|
||||
/** Converts {@link ContactResource} to {@link XjcRdeContact}. */
|
||||
static XjcRdeContact convertContact(ContactResource model) {
|
||||
/** Converts {@link Contact} to {@link XjcRdeContact}. */
|
||||
static XjcRdeContact convertContact(Contact model) {
|
||||
XjcRdeContact bean = new XjcRdeContact();
|
||||
bean.setRoid(model.getRepoId());
|
||||
for (StatusValue status : model.getStatusValues()) {
|
||||
@@ -188,5 +188,5 @@ final class ContactResourceToXjcConverter {
|
||||
return bean;
|
||||
}
|
||||
|
||||
private ContactResourceToXjcConverter() {}
|
||||
private ContactToXjcConverter() {}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ import com.google.common.base.Ascii;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.domain.DesignatedContact;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.rgp.GracePeriodStatus;
|
||||
@@ -168,11 +168,11 @@ final class DomainToXjcConverter {
|
||||
// o An OPTIONAL <registrant> element that contain the identifier for
|
||||
// the human or organizational social information object associated
|
||||
// as the holder of the domain name object.
|
||||
VKey<ContactResource> registrant = model.getRegistrant();
|
||||
VKey<Contact> registrant = model.getRegistrant();
|
||||
if (registrant == null) {
|
||||
logger.atWarning().log("Domain %s has no registrant contact.", domainName);
|
||||
} else {
|
||||
ContactResource registrantContact = tm().transact(() -> tm().loadByKey(registrant));
|
||||
Contact registrantContact = tm().transact(() -> tm().loadByKey(registrant));
|
||||
checkState(
|
||||
registrantContact != null,
|
||||
"Registrant contact %s on domain %s does not exist",
|
||||
@@ -304,7 +304,7 @@ final class DomainToXjcConverter {
|
||||
"Contact key for type %s is null on domain %s",
|
||||
model.getType(),
|
||||
domainName);
|
||||
ContactResource contact = tm().transact(() -> tm().loadByKey(model.getContactKey()));
|
||||
Contact contact = tm().transact(() -> tm().loadByKey(model.getContactKey()));
|
||||
checkState(
|
||||
contact != null,
|
||||
"Contact %s on domain %s does not exist",
|
||||
|
||||
@@ -20,7 +20,7 @@ import static google.registry.persistence.transaction.TransactionManagerFactory.
|
||||
import com.google.auto.value.AutoValue;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
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.host.Host;
|
||||
import google.registry.model.rde.RdeMode;
|
||||
@@ -66,8 +66,8 @@ public class RdeFragmenter {
|
||||
result = Optional.of(marshaller.marshalDomain((Domain) resource, mode));
|
||||
cache.put(WatermarkModePair.create(watermark, mode), result);
|
||||
return result;
|
||||
} else if (resource instanceof ContactResource) {
|
||||
result = Optional.of(marshaller.marshalContact((ContactResource) resource));
|
||||
} else if (resource instanceof Contact) {
|
||||
result = Optional.of(marshaller.marshalContact((Contact) resource));
|
||||
cache.put(WatermarkModePair.create(watermark, RdeMode.FULL), result);
|
||||
cache.put(WatermarkModePair.create(watermark, RdeMode.THIN), result);
|
||||
return result;
|
||||
|
||||
@@ -20,7 +20,7 @@ import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.googlecode.objectify.Key;
|
||||
import google.registry.model.ImmutableObject;
|
||||
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.rde.RdeMode;
|
||||
@@ -117,10 +117,10 @@ public final class RdeMarshaller implements Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
/** Turns {@link ContactResource} object into an XML fragment. */
|
||||
public DepositFragment marshalContact(ContactResource contact) {
|
||||
return marshalResource(RdeResourceType.CONTACT, contact,
|
||||
ContactResourceToXjcConverter.convert(contact));
|
||||
/** Turns {@link Contact} object into an XML fragment. */
|
||||
public DepositFragment marshalContact(Contact contact) {
|
||||
return marshalResource(
|
||||
RdeResourceType.CONTACT, contact, ContactToXjcConverter.convert(contact));
|
||||
}
|
||||
|
||||
/** Turns {@link Domain} object into an XML fragment. */
|
||||
|
||||
@@ -45,7 +45,7 @@ import google.registry.gcs.GcsUtils;
|
||||
import google.registry.keyring.api.KeyModule.Key;
|
||||
import google.registry.model.common.Cursor;
|
||||
import google.registry.model.common.Cursor.CursorType;
|
||||
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.index.EppResourceIndex;
|
||||
@@ -82,8 +82,8 @@ import org.joda.time.Duration;
|
||||
* type and loads the embedded resource from it, which is then projected to watermark time to
|
||||
* account for things like pending transfer.
|
||||
*
|
||||
* <p>Only {@link ContactResource}s and {@link Host}s that are referenced by an included {@link
|
||||
* Domain} will be included in the corresponding pending deposit.
|
||||
* <p>Only {@link Contact}s and {@link Host}s that are referenced by an included {@link Domain} will
|
||||
* be included in the corresponding pending deposit.
|
||||
*
|
||||
* <p>{@link Registrar} entities, both active and inactive, are included in all deposits. They are
|
||||
* not rewinded point-in-time.
|
||||
|
||||
@@ -42,7 +42,7 @@ public abstract class AuthResult {
|
||||
userAuthInfo ->
|
||||
String.format(
|
||||
"%s %s",
|
||||
userAuthInfo.isUserAdmin() ? "admin" : "user", userAuthInfo.user().getEmail()))
|
||||
userAuthInfo.isUserAdmin() ? "admin" : "user", userAuthInfo.getEmailAddress()))
|
||||
.orElse("<logged-out user>");
|
||||
}
|
||||
|
||||
|
||||
@@ -40,8 +40,8 @@ import javax.inject.Inject;
|
||||
* <p>A user has OWNER role on a Registrar if there exists a {@link RegistrarPoc} with that user's
|
||||
* gaeId and the registrar as a parent.
|
||||
*
|
||||
* <p>An "admin" has in addition OWNER role on {@code #registryAdminClientId} and to all non-{@code
|
||||
* REAL} registrars (see {@link Registrar#getType}).
|
||||
* <p>An "admin" has in addition OWNER role on {@code #registryAdminRegistrarId} and to all
|
||||
* non-{@code REAL} registrars (see {@link Registrar#getType}).
|
||||
*
|
||||
* <p>An "admin" also has ADMIN role on ALL registrars.
|
||||
*
|
||||
@@ -76,7 +76,7 @@ public class AuthenticatedRegistrarAccessor {
|
||||
private final boolean isAdmin;
|
||||
|
||||
/**
|
||||
* Gives all roles a user has for a given clientId.
|
||||
* Gives all roles a user has for a given registrar ID.
|
||||
*
|
||||
* <p>The order is significant, with "more specific to this user" coming first.
|
||||
*
|
||||
@@ -107,13 +107,13 @@ public class AuthenticatedRegistrarAccessor {
|
||||
@Inject
|
||||
public AuthenticatedRegistrarAccessor(
|
||||
AuthResult authResult,
|
||||
@Config("registryAdminClientId") String registryAdminClientId,
|
||||
@Config("registryAdminClientId") String registryAdminRegistrarId,
|
||||
@Config("gSuiteSupportGroupEmailAddress") Optional<String> gSuiteSupportGroupEmailAddress,
|
||||
Lazy<GroupsConnection> lazyGroupsConnection) {
|
||||
this.isAdmin = userIsAdmin(authResult, gSuiteSupportGroupEmailAddress, lazyGroupsConnection);
|
||||
|
||||
this.userIdForLogging = authResult.userIdForLogging();
|
||||
this.roleMap = createRoleMap(authResult, this.isAdmin, registryAdminClientId);
|
||||
this.roleMap = createRoleMap(authResult, this.isAdmin, registryAdminRegistrarId);
|
||||
|
||||
logger.atInfo().log("%s has the following roles: %s", userIdForLogging(), roleMap);
|
||||
}
|
||||
@@ -129,7 +129,7 @@ public class AuthenticatedRegistrarAccessor {
|
||||
* Creates a "logged-in user" accessor with a given role map, used for tests.
|
||||
*
|
||||
* <p>The user will be allowed to create Registrars (and hence do OT&E setup) iff they have
|
||||
* the role of ADMIN for at least one clientId.
|
||||
* the role of ADMIN for at least one registrar ID.
|
||||
*
|
||||
* <p>The user's "name" in logs and exception messages is "TestUserId".
|
||||
*/
|
||||
@@ -148,59 +148,62 @@ public class AuthenticatedRegistrarAccessor {
|
||||
}
|
||||
|
||||
/**
|
||||
* A map that gives all roles a user has for a given clientId.
|
||||
* A map that gives all roles a user has for a given registrar ID.
|
||||
*
|
||||
* <p>Throws a {@link RegistrarAccessDeniedException} if the user is not logged in.
|
||||
*
|
||||
* <p>The result is ordered starting from "most specific to this user".
|
||||
*
|
||||
* <p>If you want to load the {@link Registrar} object from these (or any other) {@code clientId},
|
||||
* in order to perform actions on behalf of a user, you must use {@link #getRegistrar} which makes
|
||||
* sure the user has permissions.
|
||||
* <p>If you want to load the {@link Registrar} object from these (or any other) {@code
|
||||
* registrarId}, in order to perform actions on behalf of a user, you must use {@link
|
||||
* #getRegistrar} which makes sure the user has permissions.
|
||||
*
|
||||
* <p>Note that this is an OPTIONAL step in the authentication - only used if we don't have any
|
||||
* other clue as to the requested {@code clientId}. It is perfectly OK to get a {@code clientId}
|
||||
* from any other source, as long as the registrar is then loaded using {@link #getRegistrar}.
|
||||
* other clue as to the requested {@code registrarId}. It is perfectly OK to get a {@code
|
||||
* registrarId} from any other source, as long as the registrar is then loaded using {@link
|
||||
* #getRegistrar}.
|
||||
*/
|
||||
public ImmutableSetMultimap<String, Role> getAllClientIdWithRoles() {
|
||||
public ImmutableSetMultimap<String, Role> getAllRegistrarIdsWithRoles() {
|
||||
return roleMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the roles the current user has on the given registrar.
|
||||
*
|
||||
* <p>This is syntactic sugar for {@code getAllClientIdWithRoles().get(clientId)}.
|
||||
* <p>This is syntactic sugar for {@code getAllRegistrarIdsWithRoles().get(registrarId)}.
|
||||
*/
|
||||
public ImmutableSet<Role> getRolesForRegistrar(String clientId) {
|
||||
return getAllClientIdWithRoles().get(clientId);
|
||||
public ImmutableSet<Role> getRolesForRegistrar(String registrarId) {
|
||||
return getAllRegistrarIdsWithRoles().get(registrarId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we have a given role for a given registrar.
|
||||
*
|
||||
* <p>This is syntactic sugar for {@code getAllClientIdWithRoles().containsEntry(clientId, role)}.
|
||||
* <p>This is syntactic sugar for {@code getAllRegistrarIdsWithRoles().containsEntry(registrarId,
|
||||
* role)}.
|
||||
*/
|
||||
public boolean hasRoleOnRegistrar(Role role, String clientId) {
|
||||
return getAllClientIdWithRoles().containsEntry(clientId, role);
|
||||
public boolean hasRoleOnRegistrar(Role role, String registrarId) {
|
||||
return getAllRegistrarIdsWithRoles().containsEntry(registrarId, role);
|
||||
}
|
||||
|
||||
/**
|
||||
* "Guesses" which client ID the user wants from all those they have access to.
|
||||
*
|
||||
* <p>If no such ClientIds exist, throws a RegistrarAccessDeniedException.
|
||||
* <p>If no such registrar IDs exist, throws a RegistrarAccessDeniedException.
|
||||
*
|
||||
* <p>This should be the ClientId "most likely wanted by the user".
|
||||
* <p>This should be the registrar ID "most likely wanted by the user".
|
||||
*
|
||||
* <p>If you want to load the {@link Registrar} object from this (or any other) {@code clientId},
|
||||
* in order to perform actions on behalf of a user, you must use {@link #getRegistrar} which makes
|
||||
* sure the user has permissions.
|
||||
* <p>If you want to load the {@link Registrar} object from this (or any other) {@code
|
||||
* registrarId}, in order to perform actions on behalf of a user, you must use {@link
|
||||
* #getRegistrar} which makes sure the user has permissions.
|
||||
*
|
||||
* <p>Note that this is an OPTIONAL step in the authentication - only used if we don't have any
|
||||
* other clue as to the requested {@code clientId}. It is perfectly OK to get a {@code clientId}
|
||||
* from any other source, as long as the registrar is then loaded using {@link #getRegistrar}.
|
||||
* other clue as to the requested {@code registrarId}. It is perfectly OK to get a {@code
|
||||
* registrarId} from any other source, as long as the registrar is then loaded using {@link
|
||||
* #getRegistrar}.
|
||||
*/
|
||||
public String guessClientId() throws RegistrarAccessDeniedException {
|
||||
return getAllClientIdWithRoles().keySet().stream()
|
||||
public String guessRegistrarId() throws RegistrarAccessDeniedException {
|
||||
return getAllRegistrarIdsWithRoles().keySet().stream()
|
||||
.findFirst()
|
||||
.orElseThrow(
|
||||
() ->
|
||||
@@ -227,7 +230,7 @@ public class AuthenticatedRegistrarAccessor {
|
||||
|
||||
if (!registrarId.equals(registrar.getRegistrarId())) {
|
||||
logger.atSevere().log(
|
||||
"registrarLoader.apply(clientId) returned a Registrar with a different clientId. "
|
||||
"registrarLoader.apply(registrarId) returned a Registrar with a different registrarId. "
|
||||
+ "Requested: %s, returned: %s.",
|
||||
registrarId, registrar.getRegistrarId());
|
||||
throw new RegistrarAccessDeniedException("Internal error - please check logs");
|
||||
@@ -237,7 +240,7 @@ public class AuthenticatedRegistrarAccessor {
|
||||
}
|
||||
|
||||
public void verifyAccess(String registrarId) throws RegistrarAccessDeniedException {
|
||||
ImmutableSet<Role> roles = getAllClientIdWithRoles().get(registrarId);
|
||||
ImmutableSet<Role> roles = getAllRegistrarIdsWithRoles().get(registrarId);
|
||||
|
||||
if (roles.isEmpty()) {
|
||||
throw new RegistrarAccessDeniedException(
|
||||
@@ -279,53 +282,55 @@ public class AuthenticatedRegistrarAccessor {
|
||||
AuthResult authResult,
|
||||
Optional<String> gSuiteSupportGroupEmailAddress,
|
||||
Lazy<GroupsConnection> lazyGroupsConnection) {
|
||||
|
||||
if (!authResult.userAuthInfo().isPresent()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
UserAuthInfo userAuthInfo = authResult.userAuthInfo().get();
|
||||
|
||||
User user = userAuthInfo.user();
|
||||
|
||||
// both GAE project admin and members of the gSuiteSupportGroupEmailAddress are considered
|
||||
// admins for the RegistrarConsole.
|
||||
return !bypassAdminCheck
|
||||
&& (userAuthInfo.isUserAdmin()
|
||||
|| checkIsSupport(
|
||||
lazyGroupsConnection, user.getEmail(), gSuiteSupportGroupEmailAddress));
|
||||
lazyGroupsConnection,
|
||||
userAuthInfo.getEmailAddress(),
|
||||
gSuiteSupportGroupEmailAddress));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of registrar client IDs to roles for all registrars that the user has access to.
|
||||
*/
|
||||
/** Returns a map of registrar IDs to roles for all registrars that the user has access to. */
|
||||
private static ImmutableSetMultimap<String, Role> createRoleMap(
|
||||
AuthResult authResult,
|
||||
boolean isAdmin,
|
||||
String registryAdminClientId) {
|
||||
|
||||
AuthResult authResult, boolean isAdmin, String registryAdminRegistrarId) {
|
||||
if (!authResult.userAuthInfo().isPresent()) {
|
||||
return ImmutableSetMultimap.of();
|
||||
}
|
||||
UserAuthInfo userAuthInfo = authResult.userAuthInfo().get();
|
||||
User user = userAuthInfo.user();
|
||||
ImmutableSetMultimap.Builder<String, Role> builder = new ImmutableSetMultimap.Builder<>();
|
||||
logger.atInfo().log("Checking registrar contacts for user ID %s.", user.getUserId());
|
||||
UserAuthInfo userAuthInfo = authResult.userAuthInfo().get();
|
||||
if (userAuthInfo.appEngineUser().isPresent()) {
|
||||
User user = userAuthInfo.appEngineUser().get();
|
||||
logger.atInfo().log("Checking registrar contacts for user ID %s.", user.getUserId());
|
||||
|
||||
// Find all registrars that have a registrar contact with this user's ID.
|
||||
jpaTm()
|
||||
.transact(
|
||||
() ->
|
||||
jpaTm()
|
||||
.query(
|
||||
"SELECT r FROM Registrar r INNER JOIN RegistrarPoc rp ON "
|
||||
+ "r.clientIdentifier = rp.registrarId WHERE rp.gaeUserId = "
|
||||
+ ":gaeUserId AND r.state != :state",
|
||||
Registrar.class)
|
||||
.setParameter("gaeUserId", user.getUserId())
|
||||
.setParameter("state", State.DISABLED)
|
||||
.getResultStream()
|
||||
.forEach(registrar -> builder.put(registrar.getRegistrarId(), Role.OWNER)));
|
||||
// Find all registrars that have a registrar contact with this user's ID.
|
||||
jpaTm()
|
||||
.transact(
|
||||
() ->
|
||||
jpaTm()
|
||||
.query(
|
||||
"SELECT r FROM Registrar r INNER JOIN RegistrarPoc rp ON "
|
||||
+ "r.clientIdentifier = rp.registrarId WHERE rp.gaeUserId = "
|
||||
+ ":gaeUserId AND r.state != :state",
|
||||
Registrar.class)
|
||||
.setParameter("gaeUserId", user.getUserId())
|
||||
.setParameter("state", State.DISABLED)
|
||||
.getResultStream()
|
||||
.forEach(registrar -> builder.put(registrar.getRegistrarId(), Role.OWNER)));
|
||||
} else {
|
||||
userAuthInfo
|
||||
.consoleUser()
|
||||
.get()
|
||||
.getUserRoles()
|
||||
.getRegistrarRoles()
|
||||
.forEach((k, v) -> builder.put(k, Role.OWNER));
|
||||
}
|
||||
|
||||
// Admins have ADMIN access to all registrars, and also OWNER access to the registry registrar
|
||||
// and all non-REAL or non-live registrars.
|
||||
@@ -337,7 +342,7 @@ public class AuthenticatedRegistrarAccessor {
|
||||
registrar -> {
|
||||
if (registrar.getType() != Registrar.Type.REAL
|
||||
|| !registrar.isLive()
|
||||
|| registrar.getRegistrarId().equals(registryAdminClientId)) {
|
||||
|| registrar.getRegistrarId().equals(registryAdminRegistrarId)) {
|
||||
builder.put(registrar.getRegistrarId(), Role.OWNER);
|
||||
}
|
||||
builder.put(registrar.getRegistrarId(), Role.ADMIN);
|
||||
|
||||
@@ -23,7 +23,7 @@ import java.util.Optional;
|
||||
public abstract class UserAuthInfo {
|
||||
|
||||
/** User object from the AppEngine Users API. */
|
||||
public abstract User user();
|
||||
public abstract Optional<User> appEngineUser();
|
||||
|
||||
/**
|
||||
* Whether the user is an admin.
|
||||
@@ -34,16 +34,37 @@ public abstract class UserAuthInfo {
|
||||
*/
|
||||
public abstract boolean isUserAdmin();
|
||||
|
||||
public abstract Optional<google.registry.model.console.User> consoleUser();
|
||||
|
||||
/** Used by the OAuth authentication mechanism (only) to return information about the session. */
|
||||
public abstract Optional<OAuthTokenInfo> oauthTokenInfo();
|
||||
|
||||
public String getEmailAddress() {
|
||||
return appEngineUser()
|
||||
.map(User::getEmail)
|
||||
.orElseGet(() -> consoleUser().get().getEmailAddress());
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return appEngineUser()
|
||||
.map(User::getNickname)
|
||||
.orElseGet(() -> consoleUser().get().getEmailAddress());
|
||||
}
|
||||
|
||||
public static UserAuthInfo create(
|
||||
User user, boolean isUserAdmin) {
|
||||
return new AutoValue_UserAuthInfo(user, isUserAdmin, Optional.empty());
|
||||
return new AutoValue_UserAuthInfo(
|
||||
Optional.of(user), isUserAdmin, Optional.empty(), Optional.empty());
|
||||
}
|
||||
|
||||
public static UserAuthInfo create(
|
||||
User user, boolean isUserAdmin, OAuthTokenInfo oauthTokenInfo) {
|
||||
return new AutoValue_UserAuthInfo(user, isUserAdmin, Optional.of(oauthTokenInfo));
|
||||
return new AutoValue_UserAuthInfo(
|
||||
Optional.of(user), isUserAdmin, Optional.empty(), Optional.of(oauthTokenInfo));
|
||||
}
|
||||
|
||||
public static UserAuthInfo create(google.registry.model.console.User user) {
|
||||
return new AutoValue_UserAuthInfo(
|
||||
Optional.empty(), user.getUserRoles().isAdmin(), Optional.of(user), Optional.empty());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import static com.google.common.base.Preconditions.checkState;
|
||||
import com.google.common.base.Ascii;
|
||||
import com.google.common.base.Strings;
|
||||
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.host.Host;
|
||||
import google.registry.model.index.ForeignKeyIndex;
|
||||
@@ -31,7 +31,7 @@ class CommandUtilities {
|
||||
|
||||
/** A useful parameter enum for commands that operate on {@link EppResource} objects. */
|
||||
public enum ResourceType {
|
||||
CONTACT(ContactResource.class),
|
||||
CONTACT(Contact.class),
|
||||
HOST(Host.class),
|
||||
DOMAIN(Domain.class);
|
||||
|
||||
|
||||
@@ -46,6 +46,11 @@ final class CreateDomainCommand extends CreateOrUpdateDomainCommand
|
||||
description = "Force the creation of premium domains.")
|
||||
private boolean forcePremiums;
|
||||
|
||||
@Parameter(
|
||||
names = "--allocation_token",
|
||||
description = "Allocation token to use when creating the domain(s)")
|
||||
private String allocationToken;
|
||||
|
||||
@Inject
|
||||
@Named("base64StringGenerator")
|
||||
StringGenerator passwordGenerator;
|
||||
@@ -92,7 +97,8 @@ final class CreateDomainCommand extends CreateOrUpdateDomainCommand
|
||||
"currency", currency,
|
||||
"price", cost,
|
||||
"dsRecords", DsRecord.convertToSoy(dsRecords),
|
||||
"reason", reason);
|
||||
"reason", reason,
|
||||
"allocationToken", allocationToken);
|
||||
if (requestedByRegistrar != null) {
|
||||
soyMapData.put("requestedByRegistrar", requestedByRegistrar.toString());
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
||||
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.Parameters;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.contact.Contact;
|
||||
import java.util.List;
|
||||
|
||||
/** Command to show one or more contacts. */
|
||||
@@ -34,7 +34,7 @@ final class GetContactCommand extends GetEppResourceCommand {
|
||||
public void runAndPrint() {
|
||||
for (String contactId : mainParameters) {
|
||||
printResource(
|
||||
"Contact", contactId, loadByForeignKey(ContactResource.class, contactId, readTimestamp));
|
||||
"Contact", contactId, loadByForeignKey(Contact.class, contactId, readTimestamp));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ import org.joda.time.DateTime;
|
||||
* <p>This removes years off a domain's registration period. Note that the expiration time cannot be
|
||||
* set to prior than the present. Reversal of the charges for these years (if desired) must happen
|
||||
* out of band, as they may already have been billed out and thus cannot and won't be reversed in
|
||||
* Datastore.
|
||||
* the database.
|
||||
*/
|
||||
@Parameters(separators = " =", commandDescription = "Unrenew a domain.")
|
||||
@NonFinalForTesting
|
||||
@@ -217,7 +217,8 @@ class UnrenewDomainCommand extends ConfirmingCommand implements CommandWithRemot
|
||||
.build();
|
||||
// End the old autorenew billing event and poll message now.
|
||||
Recurring existingRecurring = tm().loadByKey(domain.getAutorenewBillingEvent());
|
||||
updateAutorenewRecurrenceEndTime(domain, existingRecurring, now);
|
||||
updateAutorenewRecurrenceEndTime(
|
||||
domain, existingRecurring, now, domainHistory.getDomainHistoryId());
|
||||
Domain newDomain =
|
||||
domain
|
||||
.asBuilder()
|
||||
@@ -225,9 +226,7 @@ class UnrenewDomainCommand extends ConfirmingCommand implements CommandWithRemot
|
||||
.setLastEppUpdateTime(now)
|
||||
.setLastEppUpdateRegistrarId(domain.getCurrentSponsorRegistrarId())
|
||||
.setAutorenewBillingEvent(newAutorenewEvent.createVKey())
|
||||
.setAutorenewPollMessage(
|
||||
newAutorenewPollMessage.createVKey(),
|
||||
newAutorenewPollMessage.getHistoryRevisionId())
|
||||
.setAutorenewPollMessage(newAutorenewPollMessage.createVKey())
|
||||
.build();
|
||||
// In order to do it'll need to write out a new HistoryEntry (likely of type SYNTHETIC), a new
|
||||
// autorenew billing event and poll message, and a new one time poll message at the present time
|
||||
|
||||
@@ -15,13 +15,13 @@
|
||||
package google.registry.tools.params;
|
||||
|
||||
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.host.Host;
|
||||
|
||||
/** Enum to make it easy for a command to accept a flag that specifies an EppResource subclass. */
|
||||
public enum EppResourceTypeParameter {
|
||||
CONTACT(ContactResource.class),
|
||||
CONTACT(Contact.class),
|
||||
DOMAIN(Domain.class),
|
||||
HOST(Host.class);
|
||||
|
||||
|
||||
@@ -109,13 +109,13 @@ public final class ConsoleUiAction extends HtmlAction {
|
||||
.render());
|
||||
return;
|
||||
}
|
||||
ImmutableSetMultimap<String, Role> roleMap = registrarAccessor.getAllClientIdWithRoles();
|
||||
ImmutableSetMultimap<String, Role> roleMap = registrarAccessor.getAllRegistrarIdsWithRoles();
|
||||
soyMapData.put("allClientIds", roleMap.keySet());
|
||||
soyMapData.put("environment", RegistryEnvironment.get().toString());
|
||||
// We set the initial value to the value that will show if guessClientId throws.
|
||||
String clientId = "<null>";
|
||||
try {
|
||||
clientId = paramClientId.orElse(registrarAccessor.guessClientId());
|
||||
clientId = paramClientId.orElse(registrarAccessor.guessRegistrarId());
|
||||
soyMapData.put("clientId", clientId);
|
||||
soyMapData.put("isOwner", roleMap.containsEntry(clientId, OWNER));
|
||||
soyMapData.put("isAdmin", roleMap.containsEntry(clientId, ADMIN));
|
||||
|
||||
@@ -18,7 +18,6 @@ import static com.google.common.net.HttpHeaders.LOCATION;
|
||||
import static com.google.common.net.HttpHeaders.X_FRAME_OPTIONS;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_MOVED_TEMPORARILY;
|
||||
|
||||
import com.google.appengine.api.users.User;
|
||||
import com.google.appengine.api.users.UserService;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.net.MediaType;
|
||||
@@ -27,6 +26,7 @@ import google.registry.request.Action;
|
||||
import google.registry.request.RequestMethod;
|
||||
import google.registry.request.Response;
|
||||
import google.registry.request.auth.AuthResult;
|
||||
import google.registry.request.auth.UserAuthInfo;
|
||||
import google.registry.security.XsrfTokenManager;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -86,16 +86,15 @@ public abstract class HtmlAction implements Runnable {
|
||||
}
|
||||
response.setContentType(MediaType.HTML_UTF_8);
|
||||
|
||||
User user = authResult.userAuthInfo().get().user();
|
||||
|
||||
UserAuthInfo authInfo = authResult.userAuthInfo().get();
|
||||
// Using HashMap to allow null values
|
||||
HashMap<String, Object> data = new HashMap<>();
|
||||
data.put("logoFilename", logoFilename);
|
||||
data.put("productName", productName);
|
||||
data.put("username", user.getNickname());
|
||||
data.put("username", authInfo.getUsername());
|
||||
data.put("logoutUrl", userService.createLogoutURL(getPath()));
|
||||
data.put("analyticsConfig", analyticsConfig);
|
||||
data.put("xsrfToken", xsrfTokenManager.generateToken(user.getEmail()));
|
||||
data.put("xsrfToken", xsrfTokenManager.generateToken(authInfo.getEmailAddress()));
|
||||
|
||||
logger.atInfo().log(
|
||||
"User %s is accessing %s with method %s.",
|
||||
|
||||
@@ -29,6 +29,7 @@ import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.net.MediaType;
|
||||
import com.google.gson.Gson;
|
||||
import google.registry.model.console.ConsolePermission;
|
||||
import google.registry.model.domain.RegistryLock;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarPoc;
|
||||
@@ -42,6 +43,7 @@ import google.registry.request.auth.Auth;
|
||||
import google.registry.request.auth.AuthResult;
|
||||
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
|
||||
import google.registry.request.auth.AuthenticatedRegistrarAccessor.RegistrarAccessDeniedException;
|
||||
import google.registry.request.auth.UserAuthInfo;
|
||||
import google.registry.security.JsonResponseHelper;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
@@ -153,24 +155,35 @@ public final class RegistryLockGetAction implements JsonGetAction {
|
||||
|
||||
boolean isAdmin = registrarAccessor.isAdmin();
|
||||
Registrar registrar = getRegistrarAndVerifyLockAccess(registrarAccessor, registrarId, isAdmin);
|
||||
User user = authResult.userAuthInfo().get().user();
|
||||
|
||||
Optional<RegistrarPoc> contactOptional = getContactMatchingLogin(user, registrar);
|
||||
boolean isRegistryLockAllowed =
|
||||
isAdmin || contactOptional.map(RegistrarPoc::isRegistryLockAllowed).orElse(false);
|
||||
UserAuthInfo userAuthInfo = authResult.userAuthInfo().get();
|
||||
// Split logic depending on whether we are using the old auth system or the new one
|
||||
boolean isRegistryLockAllowed;
|
||||
String relevantEmail;
|
||||
if (userAuthInfo.appEngineUser().isPresent()) {
|
||||
User user = userAuthInfo.appEngineUser().get();
|
||||
Optional<RegistrarPoc> contactOptional = getContactMatchingLogin(user, registrar);
|
||||
isRegistryLockAllowed =
|
||||
isAdmin || contactOptional.map(RegistrarPoc::isRegistryLockAllowed).orElse(false);
|
||||
relevantEmail =
|
||||
isAdmin
|
||||
? user.getEmail()
|
||||
// if the contact isn't present, we shouldn't display the email anyway
|
||||
: contactOptional.flatMap(RegistrarPoc::getRegistryLockEmailAddress).orElse("");
|
||||
} else {
|
||||
google.registry.model.console.User user = userAuthInfo.consoleUser().get();
|
||||
isRegistryLockAllowed =
|
||||
user.getUserRoles().hasPermission(registrarId, ConsolePermission.REGISTRY_LOCK);
|
||||
relevantEmail = user.getEmailAddress();
|
||||
}
|
||||
// Use the contact's registry lock email if it's present, else use the login email (for admins)
|
||||
String relevantEmail =
|
||||
isAdmin
|
||||
? user.getEmail()
|
||||
// if the contact isn't present, we shouldn't display the email anyway so empty is fine
|
||||
: contactOptional.flatMap(RegistrarPoc::getRegistryLockEmailAddress).orElse("");
|
||||
return ImmutableMap.of(
|
||||
LOCK_ENABLED_FOR_CONTACT_PARAM,
|
||||
isRegistryLockAllowed,
|
||||
EMAIL_PARAM,
|
||||
relevantEmail,
|
||||
PARAM_CLIENT_ID,
|
||||
registrar.getRegistrarId(),
|
||||
registrarId,
|
||||
LOCKS_PARAM,
|
||||
getLockedDomains(registrarId, isAdmin));
|
||||
}
|
||||
|
||||
@@ -184,10 +184,29 @@ public class RegistryLockPostAction implements Runnable, JsonActionRunner.JsonAc
|
||||
private String verifyPasswordAndGetEmail(
|
||||
UserAuthInfo userAuthInfo, RegistryLockPostInput postInput)
|
||||
throws RegistrarAccessDeniedException {
|
||||
User user = userAuthInfo.user();
|
||||
if (registrarAccessor.isAdmin()) {
|
||||
return user.getEmail();
|
||||
return userAuthInfo.getEmailAddress();
|
||||
}
|
||||
if (userAuthInfo.appEngineUser().isPresent()) {
|
||||
return verifyPasswordAndGetEmailLegacyUser(userAuthInfo.appEngineUser().get(), postInput);
|
||||
} else {
|
||||
return verifyPasswordAndGetEmailConsoleUser(userAuthInfo.consoleUser().get(), postInput);
|
||||
}
|
||||
}
|
||||
|
||||
private String verifyPasswordAndGetEmailConsoleUser(
|
||||
google.registry.model.console.User user, RegistryLockPostInput postInput)
|
||||
throws RegistrarAccessDeniedException {
|
||||
// Verify that the registrar has locking enabled
|
||||
getRegistrarAndVerifyLockAccess(registrarAccessor, postInput.registrarId, false);
|
||||
checkArgument(
|
||||
user.verifyRegistryLockPassword(postInput.password),
|
||||
"Incorrect registry lock password for user");
|
||||
return user.getEmailAddress();
|
||||
}
|
||||
|
||||
private String verifyPasswordAndGetEmailLegacyUser(User user, RegistryLockPostInput postInput)
|
||||
throws RegistrarAccessDeniedException {
|
||||
// Verify that the user can access the registrar, that the user has
|
||||
// registry lock enabled, and that the user provided a correct password
|
||||
Registrar registrar =
|
||||
|
||||
@@ -23,8 +23,8 @@ import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactPhoneNumber;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.contact.PostalInfo;
|
||||
import google.registry.model.domain.DesignatedContact;
|
||||
import google.registry.model.domain.DesignatedContact.Type;
|
||||
@@ -124,7 +124,7 @@ final class DomainWhoisResponse extends WhoisResponseImpl {
|
||||
}
|
||||
|
||||
/** Returns the contact of the given type. */
|
||||
private Optional<VKey<ContactResource>> getContactReference(Type type) {
|
||||
private Optional<VKey<Contact>> getContactReference(Type type) {
|
||||
Optional<DesignatedContact> contactOfType =
|
||||
domain.getContacts().stream().filter(d -> d.getType() == type).findFirst();
|
||||
return contactOfType.map(DesignatedContact::getContactKey);
|
||||
@@ -145,15 +145,15 @@ final class DomainWhoisResponse extends WhoisResponseImpl {
|
||||
|
||||
/** Emit the contact entry of the given type. */
|
||||
DomainEmitter emitContact(
|
||||
String contactType, Optional<VKey<ContactResource>> contact, boolean preferUnicode) {
|
||||
String contactType, Optional<VKey<Contact>> contact, boolean preferUnicode) {
|
||||
if (!contact.isPresent()) {
|
||||
return this;
|
||||
}
|
||||
// If we refer to a contact that doesn't exist, that's a bug. It means referential integrity
|
||||
// has somehow been broken. We skip the rest of this contact, but log it to hopefully bring it
|
||||
// someone's attention.
|
||||
ContactResource contactResource = EppResource.loadCached(contact.get());
|
||||
if (contactResource == null) {
|
||||
Contact contact1 = EppResource.loadCached(contact.get());
|
||||
if (contact1 == null) {
|
||||
logger.atSevere().log(
|
||||
"(BUG) Broken reference found from domain %s to contact %s.",
|
||||
domain.getDomainName(), contact);
|
||||
@@ -162,11 +162,10 @@ final class DomainWhoisResponse extends WhoisResponseImpl {
|
||||
PostalInfo postalInfo =
|
||||
chooseByUnicodePreference(
|
||||
preferUnicode,
|
||||
contactResource.getLocalizedPostalInfo(),
|
||||
contactResource.getInternationalizedPostalInfo());
|
||||
contact1.getLocalizedPostalInfo(),
|
||||
contact1.getInternationalizedPostalInfo());
|
||||
// ICANN Consistent Labeling & Display policy requires that this be the ROID.
|
||||
emitField(
|
||||
ImmutableList.of("Registry", contactType, "ID"), contactResource.getRepoId(), fullOutput);
|
||||
emitField(ImmutableList.of("Registry", contactType, "ID"), contact1.getRepoId(), fullOutput);
|
||||
if (postalInfo != null) {
|
||||
emitFieldIfDefined(ImmutableList.of(contactType, "Name"), postalInfo.getName(), fullOutput);
|
||||
emitFieldIfDefined(
|
||||
@@ -175,10 +174,9 @@ final class DomainWhoisResponse extends WhoisResponseImpl {
|
||||
fullOutput || contactType.equals("Registrant"));
|
||||
emitAddress(contactType, postalInfo.getAddress(), fullOutput);
|
||||
}
|
||||
emitPhone(contactType, "Phone", contactResource.getVoiceNumber());
|
||||
emitPhone(contactType, "Fax", contactResource.getFaxNumber());
|
||||
String emailFieldContent =
|
||||
fullOutput ? contactResource.getEmailAddress() : whoisRedactedEmailText;
|
||||
emitPhone(contactType, "Phone", contact1.getVoiceNumber());
|
||||
emitPhone(contactType, "Fax", contact1.getFaxNumber());
|
||||
String emailFieldContent = fullOutput ? contact1.getEmailAddress() : whoisRedactedEmailText;
|
||||
emitField(ImmutableList.of(contactType, "Email"), emailFieldContent);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ package google.registry.whois;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaJpaTm;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
@@ -53,12 +53,14 @@ final class NameserverWhoisResponse extends WhoisResponseImpl {
|
||||
ImmutableMap<Host, String> hostRegistrars =
|
||||
subordinateHosts.isEmpty()
|
||||
? ImmutableMap.of()
|
||||
: tm().transact(
|
||||
: replicaJpaTm()
|
||||
.transact(
|
||||
() ->
|
||||
Maps.toMap(
|
||||
subordinateHosts.iterator(),
|
||||
host ->
|
||||
tm().loadByKey(host.getSuperordinateDomain())
|
||||
replicaJpaTm()
|
||||
.loadByKey(host.getSuperordinateDomain())
|
||||
.cloneProjectedAtTime(getTimestamp())
|
||||
.getCurrentSponsorRegistrarId()));
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user