diff --git a/core/src/main/java/google/registry/beam/rde/RdePipeline.java b/core/src/main/java/google/registry/beam/rde/RdePipeline.java index 9a5983395..a44d030ea 100644 --- a/core/src/main/java/google/registry/beam/rde/RdePipeline.java +++ b/core/src/main/java/google/registry/beam/rde/RdePipeline.java @@ -468,7 +468,7 @@ public class RdePipeline implements Serializable { HashSet contacts = new HashSet<>(); contacts.add(domain.getAdminContact().getKey()); contacts.add(domain.getTechContact().getKey()); - contacts.add(domain.getRegistrant().getKey()); + domain.getRegistrant().ifPresent(r -> contacts.add(r.getKey())); // Billing contact is not mandatory. if (domain.getBillingContact() != null) { contacts.add(domain.getBillingContact().getKey()); diff --git a/core/src/main/java/google/registry/flows/domain/DomainFlowUtils.java b/core/src/main/java/google/registry/flows/domain/DomainFlowUtils.java index ba3d2373d..f7c9c135d 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainFlowUtils.java +++ b/core/src/main/java/google/registry/flows/domain/DomainFlowUtils.java @@ -412,11 +412,13 @@ public class DomainFlowUtils { /** Verify that no linked resources have disallowed statuses. */ static void verifyNotInPendingDelete( - Set contacts, VKey registrant, Set> nameservers) + Set contacts, + Optional> registrant, + Set> nameservers) throws EppException { ImmutableList.Builder> keysToLoad = new ImmutableList.Builder<>(); contacts.stream().map(DesignatedContact::getContactKey).forEach(keysToLoad::add); - Optional.ofNullable(registrant).ifPresent(keysToLoad::add); + registrant.ifPresent(keysToLoad::add); keysToLoad.addAll(nameservers); verifyNotInPendingDelete(EppResource.loadCached(keysToLoad.build()).values()); } @@ -480,9 +482,10 @@ public class DomainFlowUtils { } static void validateRequiredContactsPresent( - @Nullable VKey registrant, Set contacts) + Optional> registrant, Set contacts) throws RequiredParameterMissingException { - if (registrant == null) { + // TODO: Check minimum reg data set migration schedule here and don't throw when any are empty. + if (registrant.isEmpty()) { throw new MissingRegistrantException(); } @@ -498,14 +501,14 @@ public class DomainFlowUtils { } } - static void validateRegistrantAllowedOnTld(String tld, String registrantContactId) + static void validateRegistrantAllowedOnTld(String tld, Optional registrantContactId) throws RegistrantNotAllowedException { ImmutableSet allowedRegistrants = Tld.get(tld).getAllowedRegistrantContactIds(); // Empty allow list or null registrantContactId are ignored. - if (registrantContactId != null + if (registrantContactId.isPresent() && !allowedRegistrants.isEmpty() - && !allowedRegistrants.contains(registrantContactId)) { - throw new RegistrantNotAllowedException(registrantContactId); + && !allowedRegistrants.contains(registrantContactId.get())) { + throw new RegistrantNotAllowedException(registrantContactId.get()); } } diff --git a/core/src/main/java/google/registry/flows/domain/DomainInfoFlow.java b/core/src/main/java/google/registry/flows/domain/DomainInfoFlow.java index 8b52185cf..e4640e6d4 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainInfoFlow.java +++ b/core/src/main/java/google/registry/flows/domain/DomainInfoFlow.java @@ -119,8 +119,11 @@ public final class DomainInfoFlow implements TransactionalFlow { .setCreationTime(domain.getCreationTime()) .setLastEppUpdateTime(domain.getLastEppUpdateTime()) .setRegistrationExpirationTime(domain.getRegistrationExpirationTime()) - .setLastTransferTime(domain.getLastTransferTime()) - .setRegistrant(tm().loadByKey(domain.getRegistrant()).getContactId()); + .setLastTransferTime(domain.getLastTransferTime()); + domain + .getRegistrant() + .ifPresent(r -> infoBuilder.setRegistrant(tm().loadByKey(r).getContactId())); + // If authInfo is non-null, then the caller is authorized to see the full information since we // will have already verified the authInfo is valid. if (registrarId.equals(domain.getCurrentSponsorRegistrarId()) || authInfo.isPresent()) { diff --git a/core/src/main/java/google/registry/flows/domain/DomainUpdateFlow.java b/core/src/main/java/google/registry/flows/domain/DomainUpdateFlow.java index f12dda3e3..2d66e54a6 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainUpdateFlow.java +++ b/core/src/main/java/google/registry/flows/domain/DomainUpdateFlow.java @@ -278,7 +278,7 @@ public final class DomainUpdateFlow implements MutatingFlow { .removeStatusValues(remove.getStatusValues()) .removeContacts(remove.getContacts()) .addContacts(add.getContacts()) - .setRegistrant(firstNonNull(change.getRegistrant(), domain.getRegistrant())) + .setRegistrant(change.getRegistrant().or(domain::getRegistrant)) .setAuthInfo(firstNonNull(change.getAuthInfo(), domain.getAuthInfo())); if (!add.getNameservers().isEmpty()) { @@ -301,7 +301,10 @@ public final class DomainUpdateFlow implements MutatingFlow { } private static void validateRegistrantIsntBeingRemoved(Change change) throws EppException { - if (change.getRegistrantContactId() != null && change.getRegistrantContactId().isEmpty()) { + // TODO(mcilwain): Make this check the minimum registration data set migration schedule + // and not require presence of a registrant in later stages. + if (change.getRegistrantContactId().isPresent() + && change.getRegistrantContactId().get().isEmpty()) { throw new MissingRegistrantException(); } } diff --git a/core/src/main/java/google/registry/model/domain/DomainBase.java b/core/src/main/java/google/registry/model/domain/DomainBase.java index 83ce948f8..721463b66 100644 --- a/core/src/main/java/google/registry/model/domain/DomainBase.java +++ b/core/src/main/java/google/registry/model/domain/DomainBase.java @@ -135,7 +135,7 @@ public class DomainBase extends EppResource @Expose VKey billingContact; @Expose VKey techContact; - @Expose VKey registrantContact; + @Expose @Nullable VKey registrantContact; /** Authorization info (aka transfer secret) of the domain. */ @Embedded @@ -585,8 +585,8 @@ public class DomainBase extends EppResource } /** A key to the registrant who registered this domain. */ - public VKey getRegistrant() { - return registrantContact; + public Optional> getRegistrant() { + return Optional.ofNullable(registrantContact); } public VKey getAdminContact() { @@ -606,6 +606,11 @@ public class DomainBase extends EppResource return getAllContacts(false); } + /** Gets all associated contacts for the domain, including the registrant. */ + public ImmutableSet getAllContacts() { + return getAllContacts(true); + } + public DomainAuthInfo getAuthInfo() { return authInfo; } @@ -717,7 +722,6 @@ public class DomainBase extends EppResource instance.autorenewEndTime = firstNonNull(getInstance().autorenewEndTime, END_OF_TIME); checkArgumentNotNull(emptyToNull(instance.domainName), "Missing domainName"); - checkArgumentNotNull(instance.getRegistrant(), "Missing registrant"); instance.tld = getTldFromDomainName(instance.domainName); T newDomain = super.build(); @@ -749,9 +753,9 @@ public class DomainBase extends EppResource return thisCastToDerived(); } - public B setRegistrant(VKey registrant) { + public B setRegistrant(Optional> registrant) { // Set the registrant field specifically. - getInstance().registrantContact = registrant; + getInstance().registrantContact = registrant.orElse(null); return thisCastToDerived(); } diff --git a/core/src/main/java/google/registry/model/domain/DomainCommand.java b/core/src/main/java/google/registry/model/domain/DomainCommand.java index 02be7109a..d9b02fbf2 100644 --- a/core/src/main/java/google/registry/model/domain/DomainCommand.java +++ b/core/src/main/java/google/registry/model/domain/DomainCommand.java @@ -40,6 +40,7 @@ import google.registry.model.eppinput.ResourceCommand.ResourceUpdate; import google.registry.model.eppinput.ResourceCommand.SingleResourceCommand; import google.registry.model.host.Host; import google.registry.persistence.VKey; +import java.util.Optional; import java.util.Set; import javax.annotation.Nullable; import javax.xml.bind.annotation.XmlAttribute; @@ -76,21 +77,21 @@ public class DomainCommand { /** The contactId of the registrant who registered this domain. */ @XmlElement(name = "registrant") + @Nullable String registrantContactId; /** A resolved key to the registrant who registered this domain. */ - @XmlTransient VKey registrant; + @Nullable @XmlTransient VKey registrant; /** Authorization info (aka transfer secret) of the domain. */ DomainAuthInfo authInfo; - public String getRegistrantContactId() { - return registrantContactId; + public Optional getRegistrantContactId() { + return Optional.ofNullable(registrantContactId); } - @Nullable - public VKey getRegistrant() { - return registrant; + public Optional> getRegistrant() { + return Optional.ofNullable(registrant); } public DomainAuthInfo getAuthInfo() { diff --git a/core/src/main/java/google/registry/model/domain/DomainInfoData.java b/core/src/main/java/google/registry/model/domain/DomainInfoData.java index 7ba90e76f..4b1417c9b 100644 --- a/core/src/main/java/google/registry/model/domain/DomainInfoData.java +++ b/core/src/main/java/google/registry/model/domain/DomainInfoData.java @@ -63,6 +63,7 @@ public abstract class DomainInfoData implements ResponseData { abstract ImmutableSet getStatusValues(); @XmlElement(name = "registrant") + @Nullable abstract String getRegistrant(); @XmlElement(name = "contact") diff --git a/core/src/main/java/google/registry/rdap/RdapEntityAction.java b/core/src/main/java/google/registry/rdap/RdapEntityAction.java index f4c24adb6..2a24ca90b 100644 --- a/core/src/main/java/google/registry/rdap/RdapEntityAction.java +++ b/core/src/main/java/google/registry/rdap/RdapEntityAction.java @@ -76,7 +76,7 @@ public class RdapEntityAction extends RdapActionBase { // they are global, and might have different roles for different domains. if (contact.isPresent() && isAuthorized(contact.get())) { return rdapJsonFormatter.createRdapContactEntity( - contact.get(), ImmutableSet.of(), OutputDataType.FULL); + contact, ImmutableSet.of(), OutputDataType.FULL); } } diff --git a/core/src/main/java/google/registry/rdap/RdapEntitySearchAction.java b/core/src/main/java/google/registry/rdap/RdapEntitySearchAction.java index 7314a37d1..e338df6ec 100644 --- a/core/src/main/java/google/registry/rdap/RdapEntitySearchAction.java +++ b/core/src/main/java/google/registry/rdap/RdapEntitySearchAction.java @@ -461,7 +461,7 @@ public class RdapEntitySearchAction extends RdapSearchActionBase { .entitySearchResultsBuilder() .add( rdapJsonFormatter.createRdapContactEntity( - contact, ImmutableSet.of(), outputDataType)); + Optional.of(contact), ImmutableSet.of(), outputDataType)); newCursor = Optional.of( CONTACT_CURSOR_PREFIX diff --git a/core/src/main/java/google/registry/rdap/RdapJsonFormatter.java b/core/src/main/java/google/registry/rdap/RdapJsonFormatter.java index 1aa027ec1..ef7fcbcf6 100644 --- a/core/src/main/java/google/registry/rdap/RdapJsonFormatter.java +++ b/core/src/main/java/google/registry/rdap/RdapJsonFormatter.java @@ -61,6 +61,7 @@ import google.registry.rdap.RdapDataStructures.RdapStatus; import google.registry.rdap.RdapObjectClasses.RdapContactEntity; import google.registry.rdap.RdapObjectClasses.RdapDomain; import google.registry.rdap.RdapObjectClasses.RdapEntity; +import google.registry.rdap.RdapObjectClasses.RdapEntity.Role; import google.registry.rdap.RdapObjectClasses.RdapNameserver; import google.registry.rdap.RdapObjectClasses.RdapRegistrarEntity; import google.registry.rdap.RdapObjectClasses.SecureDns; @@ -74,6 +75,7 @@ import java.net.InetAddress; import java.net.URI; import java.nio.file.Paths; import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.Locale; import java.util.Optional; import java.util.Set; @@ -249,6 +251,16 @@ public class RdapJsonFormatter { private static final Ordering DESIGNATED_CONTACT_ORDERING = Ordering.natural().onResultOf(DesignatedContact::getType); + /** + * The list of RDAP contact roles that are required to be present on each domain. + * + *

Per RDAP Response Profile 2.7.3, A domain MUST have the REGISTRANT, ADMIN, TECH roles and + * MAY have others. We also have the BILLING role in our system but it isn't required and is only + * listed if actually present. + */ + private static final ImmutableSet REQUIRED_CONTACT_ROLES = + ImmutableSet.of(Role.REGISTRANT, Role.ADMIN, Role.TECH); + /** Creates the TOS notice that is added to every reply. */ Notice createTosNotice() { String linkValue = makeRdapServletRelativeUrl("help", RdapHelpAction.TOS_PATH); @@ -371,36 +383,48 @@ public class RdapJsonFormatter { // Load the registrant and other contacts and add them to the data. ImmutableMap, Contact> loadedContacts = replicaTm().transact(() -> replicaTm().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. - // + // RDAP Response Profile 2.7.1, 2.7.3 - we MUST have the contacts. 2.7.4 discusses redaction of // 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 + // The GDPR redaction is handled in createRdapContactEntity. + + // Load all contacts that are present and group them by type (it is common for a single contact + // entity to be used across multiple contact types on domain, e.g. registrant and admin). ImmutableSetMultimap, Type> contactsToRoles = - Streams.concat( - domain.getContacts().stream(), - Stream.of(DesignatedContact.create(Type.REGISTRANT, domain.getRegistrant()))) + domain.getAllContacts().stream() .sorted(DESIGNATED_CONTACT_ORDERING) .collect( toImmutableSetMultimap( DesignatedContact::getContactKey, DesignatedContact::getType)); + // Convert the contact entities to RDAP output contacts (this also converts the contact types + // to RDAP roles). + Set rdapContacts = new LinkedHashSet<>(); for (VKey contactKey : contactsToRoles.keySet()) { - Set roles = + Set roles = contactsToRoles.get(contactKey).stream() .map(RdapJsonFormatter::convertContactTypeToRdapRole) .collect(toImmutableSet()); if (roles.isEmpty()) { continue; } - builder - .entitiesBuilder() - .add( - createRdapContactEntity( - loadedContacts.get(contactKey), roles, OutputDataType.INTERNAL)); + rdapContacts.add( + createRdapContactEntity( + Optional.ofNullable(loadedContacts.get(contactKey)), roles, OutputDataType.INTERNAL)); } + + // Loop through all required contact roles and fill in placeholder REDACTED info for any + // required ones that are missing, i.e. because of minimum registration data set. + for (Role role : REQUIRED_CONTACT_ROLES) { + if (rdapContacts.stream().noneMatch(c -> c.roles().contains(role))) { + rdapContacts.add( + createRdapContactEntity( + Optional.empty(), ImmutableSet.of(role), OutputDataType.INTERNAL)); + } + } + builder.entitiesBuilder().addAll(rdapContacts); + // Add the nameservers to the data; the load was kicked off above for efficiency. // RDAP Response Profile 2.9: we MUST have the nameservers for (Host host : HOST_RESOURCE_ORDERING.immutableSortedCopy(loadedHosts)) { @@ -502,25 +526,35 @@ public class RdapJsonFormatter { /** * Creates a JSON object for a {@link Contact} and associated contact type. * + *

If the contact isn't present (i.e. because of minimum registration data set), then always + * show all of its fields as if they were redacted, and always deny RDAP authorization. + * * @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( - Contact contact, Iterable roles, OutputDataType outputDataType) { + Optional contact, Iterable 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 // fields we don't want to show (as opposed to not having contacts at all) because of GDPR etc. // // 2.8 allows for unredacted output for authorized people. + // TODO(mcilwain): Once the RDAP profile is fully updated for minimum registration data set, + // we will want to not include non-existent contacts at all, rather than + // pretending they exist and just showing REDACTED info. This is especially + // important for authorized flows, where you wouldn't expect to see redaction. boolean isAuthorized = - rdapAuthorization.isAuthorizedForRegistrar(contact.getCurrentSponsorRegistrarId()); + contact.isPresent() + && rdapAuthorization.isAuthorizedForRegistrar( + contact.get().getCurrentSponsorRegistrarId()); VcardArray.Builder vcardBuilder = VcardArray.builder(); if (isAuthorized) { - fillRdapContactEntityWhenAuthorized(contactBuilder, vcardBuilder, contact, outputDataType); + fillRdapContactEntityWhenAuthorized( + contactBuilder, vcardBuilder, contact.get(), outputDataType); } else { // GTLD Registration Data Temp Spec 17may18, Appendix A, 2.3, 2.4 and RDAP Response Profile // 2.7.4.1, 2.7.4.2 - the following fields must be redacted: diff --git a/core/src/main/java/google/registry/rde/DomainToXjcConverter.java b/core/src/main/java/google/registry/rde/DomainToXjcConverter.java index ae5a91d58..fa7167b63 100644 --- a/core/src/main/java/google/registry/rde/DomainToXjcConverter.java +++ b/core/src/main/java/google/registry/rde/DomainToXjcConverter.java @@ -20,7 +20,6 @@ import static google.registry.persistence.transaction.TransactionManagerFactory. 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.Contact; import google.registry.model.domain.DesignatedContact; import google.registry.model.domain.Domain; @@ -45,12 +44,11 @@ import google.registry.xjc.rgp.XjcRgpStatusType; import google.registry.xjc.rgp.XjcRgpStatusValueType; import google.registry.xjc.secdns.XjcSecdnsDsDataType; import google.registry.xjc.secdns.XjcSecdnsDsOrKeyType; +import java.util.Optional; /** Utility class that turns {@link Domain} as {@link XjcRdeDomainElement}. */ final class DomainToXjcConverter { - private static final FluentLogger logger = FluentLogger.forEnclosingClass(); - /** Converts {@link Domain} to {@link XjcRdeDomainElement}. */ static XjcRdeDomainElement convert(Domain domain, RdeMode mode) { return new XjcRdeDomainElement(convertDomain(domain, mode)); @@ -168,11 +166,9 @@ final class DomainToXjcConverter { // o An OPTIONAL element that contain the identifier for // the human or organizational social information object associated // as the holder of the domain name object. - VKey registrant = model.getRegistrant(); - if (registrant == null) { - logger.atWarning().log("Domain %s has no registrant contact.", domainName); - } else { - Contact registrantContact = tm().transact(() -> tm().loadByKey(registrant)); + Optional> registrant = model.getRegistrant(); + if (registrant.isPresent()) { + Contact registrantContact = tm().transact(() -> tm().loadByKey(registrant.get())); checkState( registrantContact != null, "Registrant contact %s on domain %s does not exist", diff --git a/core/src/main/java/google/registry/ui/server/console/ConsoleApiAction.java b/core/src/main/java/google/registry/ui/server/console/ConsoleApiAction.java index 63fba82b3..d4d3a3bc6 100644 --- a/core/src/main/java/google/registry/ui/server/console/ConsoleApiAction.java +++ b/core/src/main/java/google/registry/ui/server/console/ConsoleApiAction.java @@ -254,5 +254,4 @@ public abstract class ConsoleApiAction implements Runnable { super(message); } } - } diff --git a/core/src/main/java/google/registry/ui/server/console/ConsoleUpdateRegistrarAction.java b/core/src/main/java/google/registry/ui/server/console/ConsoleUpdateRegistrarAction.java index b94b6b374..56bca601f 100644 --- a/core/src/main/java/google/registry/ui/server/console/ConsoleUpdateRegistrarAction.java +++ b/core/src/main/java/google/registry/ui/server/console/ConsoleUpdateRegistrarAction.java @@ -47,8 +47,7 @@ public class ConsoleUpdateRegistrarAction extends ConsoleApiAction { @Inject ConsoleUpdateRegistrarAction( - ConsoleApiParams consoleApiParams, - @Parameter("registrar") Optional registrar) { + ConsoleApiParams consoleApiParams, @Parameter("registrar") Optional registrar) { super(consoleApiParams); this.registrar = registrar; } @@ -108,5 +107,4 @@ public class ConsoleUpdateRegistrarAction extends ConsoleApiAction { consoleApiParams.response().setStatus(SC_OK); } - } diff --git a/core/src/main/java/google/registry/whois/DomainWhoisResponse.java b/core/src/main/java/google/registry/whois/DomainWhoisResponse.java index b275ed50c..6fc033e67 100644 --- a/core/src/main/java/google/registry/whois/DomainWhoisResponse.java +++ b/core/src/main/java/google/registry/whois/DomainWhoisResponse.java @@ -105,7 +105,9 @@ final class DomainWhoisResponse extends WhoisResponseImpl { "Registrar Abuse Contact Phone", abuseContact.map(RegistrarPoc::getPhoneNumber).orElse("")) .emitStatusValues(domain.getStatusValues(), domain.getGracePeriods()) - .emitContact("Registrant", Optional.of(domain.getRegistrant()), preferUnicode) + // TODO(mcilwain): Investigate if the WHOIS spec requires us to always output REDACTED + // text in WHOIS even if the contact is not present, and if so, do so. + .emitContact("Registrant", domain.getRegistrant(), preferUnicode) .emitContact("Admin", getContactReference(Type.ADMIN), preferUnicode) .emitContact("Tech", getContactReference(Type.TECH), preferUnicode) .emitContact("Billing", getContactReference(Type.BILLING), preferUnicode) diff --git a/core/src/test/java/google/registry/beam/common/RegistryJpaReadTest.java b/core/src/test/java/google/registry/beam/common/RegistryJpaReadTest.java index c5d67d080..e47700104 100644 --- a/core/src/test/java/google/registry/beam/common/RegistryJpaReadTest.java +++ b/core/src/test/java/google/registry/beam/common/RegistryJpaReadTest.java @@ -43,6 +43,7 @@ import google.registry.persistence.transaction.CriteriaQueryBuilder; import google.registry.persistence.transaction.JpaTestExtensions; import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension; import google.registry.testing.FakeClock; +import java.util.Optional; import org.apache.beam.sdk.coders.StringUtf8Coder; import org.apache.beam.sdk.testing.PAssert; import org.apache.beam.sdk.values.PCollection; @@ -191,7 +192,7 @@ public class RegistryJpaReadTest { StatusValue.SERVER_UPDATE_PROHIBITED, StatusValue.SERVER_RENEW_PROHIBITED, StatusValue.SERVER_HOLD)) - .setRegistrant(contact.createVKey()) + .setRegistrant(Optional.of(contact.createVKey())) .setContacts(ImmutableSet.of()) .setSubordinateHosts(ImmutableSet.of("ns1.example.com")) .setPersistedCurrentSponsorRegistrarId(registrar.getRegistrarId()) diff --git a/core/src/test/java/google/registry/beam/rde/RdePipelineTest.java b/core/src/test/java/google/registry/beam/rde/RdePipelineTest.java index 99ded6601..1744a2005 100644 --- a/core/src/test/java/google/registry/beam/rde/RdePipelineTest.java +++ b/core/src/test/java/google/registry/beam/rde/RdePipelineTest.java @@ -31,6 +31,7 @@ import static google.registry.rde.RdeResourceType.HOST; import static google.registry.rde.RdeResourceType.REGISTRAR; import static google.registry.testing.DatabaseHelper.createTld; import static google.registry.testing.DatabaseHelper.insertSimpleResources; +import static google.registry.testing.DatabaseHelper.newDomain; import static google.registry.testing.DatabaseHelper.persistActiveContact; import static google.registry.testing.DatabaseHelper.persistActiveDomain; import static google.registry.testing.DatabaseHelper.persistActiveHost; @@ -83,10 +84,10 @@ import google.registry.rde.PendingDeposit; import google.registry.rde.RdeResourceType; import google.registry.testing.CloudTasksHelper; import google.registry.testing.CloudTasksHelper.TaskMatcher; -import google.registry.testing.DatabaseHelper; import google.registry.testing.FakeClock; import google.registry.testing.FakeKeyringModule; import java.io.IOException; +import java.util.Optional; import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -266,23 +267,23 @@ public class RdePipelineTest { persistHostHistory(host1); Domain helloDomain = persistEppResource( - DatabaseHelper.newDomain("hello.soy", contact1) - .asBuilder() - .addNameserver(host1.createVKey()) - .build()); + newDomain("hello.soy", contact1).asBuilder().addNameserver(host1.createVKey()).build()); persistDomainHistory(helloDomain); persistHostHistory(persistActiveHost("not-used-subordinate.hello.soy")); Host host2 = persistActiveHost("ns1.hello.soy"); persistHostHistory(host2); + + // This domain has no registrant. Domain kittyDomain = persistEppResource( - DatabaseHelper.newDomain("kitty.fun", contact2) + newDomain("kitty.fun", contact2) .asBuilder() .addNameservers(ImmutableSet.of(host1.createVKey(), host2.createVKey())) + .setRegistrant(Optional.empty()) .build()); persistDomainHistory(kittyDomain); // Should not appear because the TLD is not included in a pending deposit. - persistDomainHistory(persistEppResource(DatabaseHelper.newDomain("lol.cat", contact1))); + persistDomainHistory(persistEppResource(newDomain("lol.cat", contact1))); // To be deleted. Domain deletedDomain = persistActiveDomain("deleted.soy"); persistDomainHistory(deletedDomain); @@ -325,7 +326,7 @@ public class RdePipelineTest { persistHostHistory(futureHost); persistDomainHistory( persistEppResource( - DatabaseHelper.newDomain("future.soy", futureContact) + newDomain("future.soy", futureContact) .asBuilder() .setNameservers(futureHost.createVKey()) .build())); @@ -379,18 +380,30 @@ public class RdePipelineTest { // The same registrars are attached to all the pending deposits. .containsExactly("New Registrar", "The Registrar", "external_monitoring"); // Domain fragments. - if ("soy".equals(kv.getKey().tld())) { - assertThat( - getFragmentForType(kv, DOMAIN) - .map(getXmlElement(DOMAIN_NAME_PATTERN)) - .collect(toImmutableSet())) - .containsExactly("hello.soy"); - } else { - assertThat( - getFragmentForType(kv, DOMAIN) - .map(getXmlElement(DOMAIN_NAME_PATTERN)) - .collect(toImmutableSet())) - .containsExactly("cat.fun"); + ImmutableSet domainFrags = + getFragmentForType(kv, DOMAIN).collect(toImmutableSet()); + assertThat(domainFrags).hasSize(1); + if ("fun".equals(kv.getKey().tld())) { + // Note that this fragment contains no registrant (which is valid). + assertThat(domainFrags.stream().findFirst().get().xml().strip()) + .isEqualTo( + """ + + cat.fun + 15-FUN + cat.fun + + contact456 + contact456 + + ns1.external.tld + ns1.hello.soy + + TheRegistrar + TheRegistrar + 1970-01-01T00:00:00Z + 294247-01-10T04:00:54Z + """); } if (kv.getKey().mode().equals(FULL)) { // Contact fragments for hello.soy. @@ -400,12 +413,35 @@ public class RdePipelineTest { .map(getXmlElement(CONTACT_ID_PATTERN)) .collect(toImmutableSet())) .containsExactly("contact1234", "contact789"); + // Host fragments for hello.soy. assertThat( getFragmentForType(kv, HOST) .map(getXmlElement(HOST_NAME_PATTERN)) .collect(toImmutableSet())) .containsExactly("ns1.external.tld", "ns1.lol.cat"); + + // Domain fragments for hello.soy: Note that this contains a registrant. + assertThat(domainFrags.stream().findFirst().get().xml().strip()) + .isEqualTo( + """ + + hello.soy + E-SOY + hello.soy + + contact1234 + contact789 + contact1234 + + ns1.external.tld + ns1.lol.cat + + TheRegistrar + TheRegistrar + 1970-01-01T00:00:00Z + 294247-01-10T04:00:54Z + """); } else { // Contact fragments for cat.fun. assertThat( @@ -413,6 +449,7 @@ public class RdePipelineTest { .map(getXmlElement(CONTACT_ID_PATTERN)) .collect(toImmutableSet())) .containsExactly("contactABC"); + // Host fragments for cat.soy. assertThat( getFragmentForType(kv, HOST) @@ -429,6 +466,25 @@ public class RdePipelineTest { fragment.type().equals(CONTACT) || fragment.type().equals(HOST))) .isFalse(); + + // Domain fragments for hello.soy: Note that this contains no contact info. + assertThat(domainFrags.stream().findFirst().get().xml().strip()) + .isEqualTo( + """ + + hello.soy + E-SOY + hello.soy + + + ns1.external.tld + ns1.lol.cat + + TheRegistrar + TheRegistrar + 1970-01-01T00:00:00Z + 294247-01-10T04:00:54Z + """); } }); return null; diff --git a/core/src/test/java/google/registry/beam/spec11/Spec11PipelineTest.java b/core/src/test/java/google/registry/beam/spec11/Spec11PipelineTest.java index ee34b7807..b73a93919 100644 --- a/core/src/test/java/google/registry/beam/spec11/Spec11PipelineTest.java +++ b/core/src/test/java/google/registry/beam/spec11/Spec11PipelineTest.java @@ -54,6 +54,7 @@ import google.registry.util.Retrier; import java.io.File; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Optional; import org.apache.beam.sdk.coders.KvCoder; import org.apache.beam.sdk.coders.SerializableCoder; import org.apache.beam.sdk.options.PipelineOptionsFactory; @@ -297,7 +298,7 @@ class Spec11PipelineTest { .setLastEppUpdateTime(fakeClock.nowUtc()) .setLastEppUpdateRegistrarId(registrar.getRegistrarId()) .setLastTransferTime(fakeClock.nowUtc()) - .setRegistrant(contact.createVKey()) + .setRegistrant(Optional.of(contact.createVKey())) .setPersistedCurrentSponsorRegistrarId(registrar.getRegistrarId()) .setRegistrationExpirationTime(fakeClock.nowUtc().plusYears(1)) .setAuthInfo(DomainAuthInfo.create(PasswordAuth.create("password"))) diff --git a/core/src/test/java/google/registry/flows/domain/DomainDeleteFlowTest.java b/core/src/test/java/google/registry/flows/domain/DomainDeleteFlowTest.java index 1c39f2d36..fcd167f22 100644 --- a/core/src/test/java/google/registry/flows/domain/DomainDeleteFlowTest.java +++ b/core/src/test/java/google/registry/flows/domain/DomainDeleteFlowTest.java @@ -103,6 +103,7 @@ import google.registry.model.transfer.TransferStatus; import google.registry.testing.CloudTasksHelper.TaskMatcher; import google.registry.testing.DatabaseHelper; import java.util.Map; +import java.util.Optional; import org.joda.money.Money; import org.joda.time.DateTime; import org.joda.time.Duration; @@ -162,7 +163,7 @@ class DomainDeleteFlowTest extends ResourceFlowTestCase { .setLastEppUpdateTime(DateTime.parse("1999-12-03T09:00:00.0Z")) .setLastTransferTime(DateTime.parse("2000-04-08T09:00:00.0Z")) .setRegistrationExpirationTime(DateTime.parse("2005-04-03T22:00:00.0Z")) - .setRegistrant(registrant.createVKey()) + .setRegistrant(Optional.of(registrant.createVKey())) .setContacts( ImmutableSet.of( DesignatedContact.create(Type.ADMIN, contact.createVKey()), @@ -227,6 +228,13 @@ class DomainInfoFlowTest extends ResourceFlowTestCase { doSuccessfulTest("domain_info_response.xml"); } + @Test + void testSuccess_noRegistrant() throws Exception { + persistTestEntities(false); + domain = persistResource(domain.asBuilder().setRegistrant(Optional.empty()).build()); + doSuccessfulTest("domain_info_response_no_registrant.xml", false); + } + @Test void testSuccess_clTridNotSpecified() throws Exception { setEppInput("domain_info_no_cltrid.xml"); diff --git a/core/src/test/java/google/registry/flows/domain/DomainUpdateFlowTest.java b/core/src/test/java/google/registry/flows/domain/DomainUpdateFlowTest.java index 8583682fd..bfb61e6d5 100644 --- a/core/src/test/java/google/registry/flows/domain/DomainUpdateFlowTest.java +++ b/core/src/test/java/google/registry/flows/domain/DomainUpdateFlowTest.java @@ -162,7 +162,7 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase assertThat(loadByKey(contact.getContactKey()).getContactId()).isEqualTo("mak21")); - assertThat(loadByKey(reloadResourceByForeignKey().getRegistrant()).getContactId()) + assertThat(loadByKey(reloadResourceByForeignKey().getRegistrant().get()).getContactId()) .isEqualTo("mak21"); runFlow(); @@ -1615,7 +1615,7 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase assertThat(loadByKey(contact.getContactKey()).getContactId()).isEqualTo("sh8013")); - assertThat(loadByKey(reloadResourceByForeignKey().getRegistrant()).getContactId()) + assertThat(loadByKey(reloadResourceByForeignKey().getRegistrant().get()).getContactId()) .isEqualTo("sh8013"); } diff --git a/core/src/test/java/google/registry/model/domain/DomainSqlTest.java b/core/src/test/java/google/registry/model/domain/DomainSqlTest.java index 66396a037..fef1474ab 100644 --- a/core/src/test/java/google/registry/model/domain/DomainSqlTest.java +++ b/core/src/test/java/google/registry/model/domain/DomainSqlTest.java @@ -54,6 +54,7 @@ import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationW import google.registry.testing.FakeClock; import google.registry.util.SerializeUtils; import java.util.Arrays; +import java.util.Optional; import org.joda.time.DateTime; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -69,7 +70,7 @@ public class DomainSqlTest { new JpaTestExtensions.Builder().withClock(fakeClock).buildIntegrationWithCoverageExtension(); private Domain domain; - private VKey contactKey; + private Optional> contactKey; private VKey contact2Key; private VKey host1VKey; private Host host; @@ -82,7 +83,7 @@ public class DomainSqlTest { saveRegistrar("registrar1"); saveRegistrar("registrar2"); saveRegistrar("registrar3"); - contactKey = createKey(Contact.class, "contact_id1"); + contactKey = Optional.of(createKey(Contact.class, "contact_id1")); contact2Key = createKey(Contact.class, "contact_id2"); host1VKey = createKey(Host.class, "host1"); diff --git a/core/src/test/java/google/registry/model/domain/DomainTest.java b/core/src/test/java/google/registry/model/domain/DomainTest.java index 81842b542..1307bffb1 100644 --- a/core/src/test/java/google/registry/model/domain/DomainTest.java +++ b/core/src/test/java/google/registry/model/domain/DomainTest.java @@ -185,7 +185,7 @@ public class DomainTest { StatusValue.SERVER_UPDATE_PROHIBITED, StatusValue.SERVER_RENEW_PROHIBITED, StatusValue.SERVER_HOLD)) - .setRegistrant(contact1Key) + .setRegistrant(Optional.of(contact1Key)) .setNameservers(ImmutableSet.of(hostKey)) .setSubordinateHosts(ImmutableSet.of("ns1.example.com")) .setPersistedCurrentSponsorRegistrarId("NewRegistrar") @@ -242,6 +242,16 @@ public class DomainTest { .hasValue(domain); } + @Test + void testRegistrantNotRequired() { + persistResource(domain.asBuilder().setRegistrant(Optional.empty()).build()); + assertThat( + loadByForeignKey(Domain.class, domain.getForeignKey(), fakeClock.nowUtc()) + .get() + .getRegistrant()) + .isEmpty(); + } + @Test void testEmptyStringsBecomeNull() { assertThat( @@ -1012,14 +1022,14 @@ public class DomainTest { DesignatedContact.create(Type.BILLING, contact3Key), DesignatedContact.create(Type.TECH, contact4Key)), true); - assertThat(domain.getRegistrant()).isEqualTo(contact1Key); + assertThat(domain.getRegistrant()).hasValue(contact1Key); assertThat(domain.getAdminContact()).isEqualTo(contact2Key); assertThat(domain.getBillingContact()).isEqualTo(contact3Key); assertThat(domain.getTechContact()).isEqualTo(contact4Key); // Make sure everything gets nulled out. domain.setContactFields(ImmutableSet.of(), true); - assertThat(domain.getRegistrant()).isNull(); + assertThat(domain.getRegistrant()).isEmpty(); assertThat(domain.getAdminContact()).isNull(); assertThat(domain.getBillingContact()).isNull(); assertThat(domain.getTechContact()).isNull(); @@ -1032,13 +1042,13 @@ public class DomainTest { DesignatedContact.create(Type.BILLING, contact3Key), DesignatedContact.create(Type.TECH, contact4Key)), false); - assertThat(domain.getRegistrant()).isNull(); + assertThat(domain.getRegistrant()).isEmpty(); assertThat(domain.getAdminContact()).isEqualTo(contact2Key); assertThat(domain.getBillingContact()).isEqualTo(contact3Key); assertThat(domain.getTechContact()).isEqualTo(contact4Key); - domain = domain.asBuilder().setRegistrant(contact1Key).build(); + domain = domain.asBuilder().setRegistrant(Optional.of(contact1Key)).build(); domain.setContactFields(ImmutableSet.of(), false); - assertThat(domain.getRegistrant()).isEqualTo(contact1Key); + assertThat(domain.getRegistrant()).hasValue(contact1Key); assertThat(domain.getAdminContact()).isNull(); assertThat(domain.getBillingContact()).isNull(); assertThat(domain.getTechContact()).isNull(); diff --git a/core/src/test/java/google/registry/model/reporting/Spec11ThreatMatchTest.java b/core/src/test/java/google/registry/model/reporting/Spec11ThreatMatchTest.java index f4d90268c..fdc16488b 100644 --- a/core/src/test/java/google/registry/model/reporting/Spec11ThreatMatchTest.java +++ b/core/src/test/java/google/registry/model/reporting/Spec11ThreatMatchTest.java @@ -31,6 +31,7 @@ import google.registry.model.domain.Domain; import google.registry.model.host.Host; import google.registry.model.transfer.ContactTransferData; import google.registry.persistence.VKey; +import java.util.Optional; import org.joda.time.LocalDate; import org.joda.time.format.ISODateTimeFormat; import org.junit.jupiter.api.BeforeEach; @@ -68,7 +69,7 @@ public final class Spec11ThreatMatchTest extends EntityTestCase { .setDomainName("foo.tld") .setRepoId(domainRepoId) .setNameservers(hostVKey) - .setRegistrant(registrantContactVKey) + .setRegistrant(Optional.of(registrantContactVKey)) .setContacts(ImmutableSet.of()) .build(); diff --git a/core/src/test/java/google/registry/rdap/RdapDomainActionTest.java b/core/src/test/java/google/registry/rdap/RdapDomainActionTest.java index 1972d73b3..b834fb0a4 100644 --- a/core/src/test/java/google/registry/rdap/RdapDomainActionTest.java +++ b/core/src/test/java/google/registry/rdap/RdapDomainActionTest.java @@ -15,6 +15,7 @@ package google.registry.rdap; import static com.google.common.truth.Truth.assertThat; +import static google.registry.model.EppResourceUtils.loadByForeignKey; import static google.registry.testing.DatabaseHelper.createTld; import static google.registry.testing.DatabaseHelper.persistResource; import static google.registry.testing.DatabaseHelper.persistSimpleResources; @@ -278,6 +279,19 @@ class RdapDomainActionTest extends RdapActionBaseTestCase { assertProperResponseForCatLol("cat.lol", "rdap_domain_no_contacts_with_remark.json"); } + @Test + void testValidDomain_notLoggedIn_contactsShowRedacted_evenWhenRegistrantDoesntExist() { + // Even though the registrant is empty on this domain, it still shows a full set of REDACTED + // fields through RDAP. + persistResource( + loadByForeignKey(Domain.class, "cat.lol", clock.nowUtc()) + .get() + .asBuilder() + .setRegistrant(Optional.empty()) + .build()); + assertProperResponseForCatLol("cat.lol", "rdap_domain_no_contacts_with_remark.json"); + } + @Test void testValidDomain_loggedInAsOtherRegistrar_noContacts() { login("idnregistrar"); diff --git a/core/src/test/java/google/registry/rdap/RdapJsonFormatterTest.java b/core/src/test/java/google/registry/rdap/RdapJsonFormatterTest.java index acca40c8c..54431185d 100644 --- a/core/src/test/java/google/registry/rdap/RdapJsonFormatterTest.java +++ b/core/src/test/java/google/registry/rdap/RdapJsonFormatterTest.java @@ -52,6 +52,8 @@ import google.registry.rdap.RdapObjectClasses.ReplyPayloadBase; import google.registry.rdap.RdapObjectClasses.TopLevelReplyObject; import google.registry.testing.FakeClock; import google.registry.testing.FullFieldsTestEntityHelper; +import java.util.Optional; +import javax.annotation.Nullable; import org.joda.time.DateTime; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -77,7 +79,7 @@ class RdapJsonFormatterTest { private Host hostNoAddresses; private Host hostNotLinked; private Host hostSuperordinatePendingTransfer; - private Contact contactRegistrant; + @Nullable private Contact contactRegistrant; private Contact contactAdmin; private Contact contactTech; private Contact contactNotLinked; @@ -371,7 +373,7 @@ class RdapJsonFormatterTest { .that( rdapJsonFormatter .createRdapContactEntity( - contactRegistrant, + Optional.of(contactRegistrant), ImmutableSet.of(RdapEntity.Role.REGISTRANT), OutputDataType.FULL) .toJson()) @@ -384,7 +386,7 @@ class RdapJsonFormatterTest { .that( rdapJsonFormatter .createRdapContactEntity( - contactRegistrant, + Optional.of(contactRegistrant), ImmutableSet.of(RdapEntity.Role.REGISTRANT), OutputDataType.SUMMARY) .toJson()) @@ -398,7 +400,7 @@ class RdapJsonFormatterTest { .that( rdapJsonFormatter .createRdapContactEntity( - contactRegistrant, + Optional.of(contactRegistrant), ImmutableSet.of(RdapEntity.Role.REGISTRANT), OutputDataType.FULL) .toJson()) @@ -418,7 +420,7 @@ class RdapJsonFormatterTest { .that( rdapJsonFormatter .createRdapContactEntity( - contactRegistrant, + Optional.of(contactRegistrant), ImmutableSet.of(RdapEntity.Role.REGISTRANT), OutputDataType.FULL) .toJson()) @@ -431,7 +433,9 @@ class RdapJsonFormatterTest { .that( rdapJsonFormatter .createRdapContactEntity( - contactAdmin, ImmutableSet.of(RdapEntity.Role.ADMIN), OutputDataType.FULL) + Optional.of(contactAdmin), + ImmutableSet.of(RdapEntity.Role.ADMIN), + OutputDataType.FULL) .toJson()) .isEqualTo(loadJson("rdapjson_admincontact.json")); } @@ -442,7 +446,9 @@ class RdapJsonFormatterTest { .that( rdapJsonFormatter .createRdapContactEntity( - contactTech, ImmutableSet.of(RdapEntity.Role.TECH), OutputDataType.FULL) + Optional.of(contactTech), + ImmutableSet.of(RdapEntity.Role.TECH), + OutputDataType.FULL) .toJson()) .isEqualTo(loadJson("rdapjson_techcontact.json")); } @@ -452,7 +458,8 @@ class RdapJsonFormatterTest { assertAboutJson() .that( rdapJsonFormatter - .createRdapContactEntity(contactTech, ImmutableSet.of(), OutputDataType.FULL) + .createRdapContactEntity( + Optional.of(contactTech), ImmutableSet.of(), OutputDataType.FULL) .toJson()) .isEqualTo(loadJson("rdapjson_rolelesscontact.json")); } @@ -462,7 +469,8 @@ class RdapJsonFormatterTest { assertAboutJson() .that( rdapJsonFormatter - .createRdapContactEntity(contactNotLinked, ImmutableSet.of(), OutputDataType.FULL) + .createRdapContactEntity( + Optional.of(contactNotLinked), ImmutableSet.of(), OutputDataType.FULL) .toJson()) .isEqualTo(loadJson("rdapjson_unlinkedcontact.json")); } diff --git a/core/src/test/java/google/registry/rde/DomainToXjcConverterTest.java b/core/src/test/java/google/registry/rde/DomainToXjcConverterTest.java index e8e6eb53f..620453331 100644 --- a/core/src/test/java/google/registry/rde/DomainToXjcConverterTest.java +++ b/core/src/test/java/google/registry/rde/DomainToXjcConverterTest.java @@ -70,6 +70,7 @@ import google.registry.xjc.rdedomain.XjcRdeDomainElement; import google.registry.xjc.rgp.XjcRgpStatusType; import google.registry.xjc.secdns.XjcSecdnsDsDataType; import java.io.ByteArrayOutputStream; +import java.util.Optional; import org.joda.money.Money; import org.joda.time.DateTime; import org.junit.jupiter.api.BeforeEach; @@ -280,9 +281,14 @@ public class DomainToXjcConverterTest { makeHost(clock, "4-Q9JYB4C", "ns2.cat.みんな", "bad:f00d:cafe::15:beef") .createVKey())) .setRegistrant( - makeContact( - clock, "12-Q9JYB4C", "5372808-ERL", "(◕‿◕) nevermore", "prophet@evil.みんな") - .createVKey()) + Optional.of( + makeContact( + clock, + "12-Q9JYB4C", + "5372808-ERL", + "(◕‿◕) nevermore", + "prophet@evil.みんな") + .createVKey())) .setRegistrationExpirationTime(DateTime.parse("1930-01-01T00:00:00Z")) .setGracePeriods( ImmutableSet.of( diff --git a/core/src/test/java/google/registry/rde/RdeFixtures.java b/core/src/test/java/google/registry/rde/RdeFixtures.java index 5b2c62ff6..27d0469f7 100644 --- a/core/src/test/java/google/registry/rde/RdeFixtures.java +++ b/core/src/test/java/google/registry/rde/RdeFixtures.java @@ -51,6 +51,7 @@ import google.registry.model.transfer.DomainTransferData; import google.registry.model.transfer.TransferStatus; import google.registry.testing.FakeClock; import google.registry.util.Idn; +import java.util.Optional; import org.joda.money.Money; import org.joda.time.DateTime; @@ -63,8 +64,9 @@ final class RdeFixtures { .setDomainName("example." + tld) .setRepoId(generateNewDomainRoid(tld)) .setRegistrant( - makeContact(clock, "5372808-ERL", "(◕‿◕) nevermore", "prophet@evil.みんな") - .createVKey()) + Optional.of( + makeContact(clock, "5372808-ERL", "(◕‿◕) nevermore", "prophet@evil.みんな") + .createVKey())) .build(); DomainHistory historyEntry = persistResource( diff --git a/core/src/test/java/google/registry/testing/DatabaseHelper.java b/core/src/test/java/google/registry/testing/DatabaseHelper.java index c5666a552..3d11ed54c 100644 --- a/core/src/test/java/google/registry/testing/DatabaseHelper.java +++ b/core/src/test/java/google/registry/testing/DatabaseHelper.java @@ -190,7 +190,7 @@ public final class DatabaseHelper { .setPersistedCurrentSponsorRegistrarId("TheRegistrar") .setCreationTimeForTest(START_OF_TIME) .setAuthInfo(DomainAuthInfo.create(PasswordAuth.create("2fooBAR"))) - .setRegistrant(contactKey) + .setRegistrant(Optional.of(contactKey)) .setContacts( ImmutableSet.of( DesignatedContact.create(Type.ADMIN, contactKey), @@ -603,7 +603,7 @@ public final class DatabaseHelper { .setCreationRegistrarId("TheRegistrar") .setCreationTimeForTest(creationTime) .setRegistrationExpirationTime(expirationTime) - .setRegistrant(contact.createVKey()) + .setRegistrant(Optional.of(contact.createVKey())) .setContacts( ImmutableSet.of( DesignatedContact.create(Type.ADMIN, contact.createVKey()), diff --git a/core/src/test/java/google/registry/testing/FullFieldsTestEntityHelper.java b/core/src/test/java/google/registry/testing/FullFieldsTestEntityHelper.java index 096ec693c..973eb424d 100644 --- a/core/src/test/java/google/registry/testing/FullFieldsTestEntityHelper.java +++ b/core/src/test/java/google/registry/testing/FullFieldsTestEntityHelper.java @@ -46,6 +46,7 @@ import google.registry.persistence.VKey; import google.registry.util.Idn; import java.net.InetAddress; import java.util.List; +import java.util.Optional; import javax.annotation.Nullable; import org.joda.time.DateTime; @@ -351,7 +352,7 @@ public final class FullFieldsTestEntityHelper { StatusValue.SERVER_UPDATE_PROHIBITED)) .setDsData(ImmutableSet.of(DomainDsData.create(1, 2, 3, "deadface"))); if (registrant != null) { - builder.setRegistrant(registrant.createVKey()); + builder.setRegistrant(Optional.of(registrant.createVKey())); } if ((admin != null) || (tech != null)) { ImmutableSet.Builder contactsBuilder = new ImmutableSet.Builder<>(); diff --git a/core/src/test/java/google/registry/whois/DomainWhoisResponseTest.java b/core/src/test/java/google/registry/whois/DomainWhoisResponseTest.java index b8654a76d..54f334fe1 100644 --- a/core/src/test/java/google/registry/whois/DomainWhoisResponseTest.java +++ b/core/src/test/java/google/registry/whois/DomainWhoisResponseTest.java @@ -41,6 +41,7 @@ import google.registry.persistence.transaction.JpaTestExtensions; import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension; import google.registry.testing.FakeClock; import google.registry.whois.WhoisResponse.WhoisResponseResults; +import java.util.Optional; import org.joda.time.DateTime; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -264,7 +265,7 @@ class DomainWhoisResponseTest { StatusValue.CLIENT_RENEW_PROHIBITED, StatusValue.CLIENT_TRANSFER_PROHIBITED, StatusValue.SERVER_UPDATE_PROHIBITED)) - .setRegistrant(registrantResourceKey) + .setRegistrant(Optional.of(registrantResourceKey)) .setContacts( ImmutableSet.of( DesignatedContact.create(DesignatedContact.Type.ADMIN, adminResourceKey), @@ -291,6 +292,21 @@ class DomainWhoisResponseTest { .isEqualTo(WhoisResponseResults.create(loadFile("whois_domain.txt"), 1)); } + @Test + void getPlainTextOutputTest_noRegistrant() { + DomainWhoisResponse domainWhoisResponse = + new DomainWhoisResponse( + domain.asBuilder().setRegistrant(Optional.empty()).build(), + false, + "Please contact registrar", + clock.nowUtc()); + assertThat( + domainWhoisResponse.getResponse( + false, + "Doodle Disclaimer\nI exist so that carriage return\nin disclaimer can be tested.")) + .isEqualTo(WhoisResponseResults.create(loadFile("whois_domain_no_registrant.txt"), 1)); + } + @Test void getPlainTextOutputTest_registrarAbuseInfoMissing() { persistResource(abuseContact.asBuilder().setVisibleInDomainWhoisAsAbuse(false).build()); diff --git a/core/src/test/resources/google/registry/flows/domain/domain_info_response_no_registrant.xml b/core/src/test/resources/google/registry/flows/domain/domain_info_response_no_registrant.xml new file mode 100644 index 000000000..a596e58f7 --- /dev/null +++ b/core/src/test/resources/google/registry/flows/domain/domain_info_response_no_registrant.xml @@ -0,0 +1,37 @@ + + + + Command completed successfully + + + + example.tld + %ROID% + + sh8013 + sh8013 + + ns1.example.tld + ns1.example.net + + ns1.example.tld + ns2.example.tld + NewRegistrar + TheRegistrar + 1999-04-03T22:00:00.0Z + NewRegistrar + 1999-12-03T09:00:00.0Z + 2005-04-03T22:00:00.0Z + 2000-04-08T09:00:00.0Z + + 2fooBAR + + + + + ABC-12345 + server-trid + + + diff --git a/core/src/test/resources/google/registry/whois/whois_domain_no_registrant.txt b/core/src/test/resources/google/registry/whois/whois_domain_no_registrant.txt new file mode 100644 index 000000000..9b2568642 --- /dev/null +++ b/core/src/test/resources/google/registry/whois/whois_domain_no_registrant.txt @@ -0,0 +1,53 @@ +Domain Name: example.tld +Registry Domain ID: 3-TLD +Registrar WHOIS Server: whois.nic.fakewhois.example +Registrar URL: http://my.fake.url +Updated Date: 2009-05-29T20:13:00Z +Creation Date: 2000-10-08T00:45:00Z +Registry Expiry Date: 2010-10-08T00:44:59Z +Registrar: New Registrar +Registrar IANA ID: 5555555 +Registrar Abuse Contact Email: jakedoe@theregistrar.com +Registrar Abuse Contact Phone: +1.2125551216 +Domain Status: addPeriod https://icann.org/epp#addPeriod +Domain Status: clientDeleteProhibited https://icann.org/epp#clientDeleteProhibited +Domain Status: clientRenewProhibited https://icann.org/epp#clientRenewProhibited +Domain Status: clientTransferProhibited https://icann.org/epp#clientTransferProhibited +Domain Status: serverUpdateProhibited https://icann.org/epp#serverUpdateProhibited +Domain Status: transferPeriod https://icann.org/epp#transferPeriod +Registry Admin ID: REDACTED FOR PRIVACY +Admin Name: REDACTED FOR PRIVACY +Admin Organization: REDACTED FOR PRIVACY +Admin Street: REDACTED FOR PRIVACY +Admin City: REDACTED FOR PRIVACY +Admin State/Province: REDACTED FOR PRIVACY +Admin Postal Code: REDACTED FOR PRIVACY +Admin Country: REDACTED FOR PRIVACY +Admin Phone: REDACTED FOR PRIVACY +Admin Phone Ext: REDACTED FOR PRIVACY +Admin Fax: REDACTED FOR PRIVACY +Admin Email: Please contact registrar +Registry Tech ID: REDACTED FOR PRIVACY +Tech Name: REDACTED FOR PRIVACY +Tech Organization: REDACTED FOR PRIVACY +Tech Street: REDACTED FOR PRIVACY +Tech City: REDACTED FOR PRIVACY +Tech State/Province: REDACTED FOR PRIVACY +Tech Postal Code: REDACTED FOR PRIVACY +Tech Country: REDACTED FOR PRIVACY +Tech Phone: REDACTED FOR PRIVACY +Tech Phone Ext: REDACTED FOR PRIVACY +Tech Fax: REDACTED FOR PRIVACY +Tech Fax Ext: REDACTED FOR PRIVACY +Tech Email: Please contact registrar +Name Server: ns01.exampleregistrar.tld +Name Server: ns02.exampleregistrar.tld +DNSSEC: signedDelegation +URL of the ICANN Whois Inaccuracy Complaint Form: https://www.icann.org/wicf/ +>>> Last update of WHOIS database: 2009-05-29T20:15:00Z <<< + +For more information on Whois status codes, please visit https://icann.org/epp + +Doodle Disclaimer +I exist so that carriage return +in disclaimer can be tested.