From a0040c5eda133616aa44e51d671c9bb4329192c0 Mon Sep 17 00:00:00 2001 From: guyben Date: Fri, 10 May 2019 11:17:35 -0700 Subject: [PATCH] Save the RDAP request time globally instead of passing it around Also removed the rdapWhoisServer value, as it's just null and will likely stay that way (it isn't mentioned in the RDAP response profile) If it'll ever become required, we can add it back. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=247643981 --- .../registry/config/RegistryConfig.java | 14 -- java/google/registry/rdap/RdapActionBase.java | 26 ++-- .../registry/rdap/RdapDomainAction.java | 10 +- .../registry/rdap/RdapDomainSearchAction.java | 97 ++++++------ .../registry/rdap/RdapEntityAction.java | 8 +- .../registry/rdap/RdapEntitySearchAction.java | 41 ++---- .../registry/rdap/RdapJsonFormatter.java | 139 +++++++----------- .../registry/rdap/RdapNameserverAction.java | 11 +- .../rdap/RdapNameserverSearchAction.java | 66 ++++----- .../registry/rdap/RdapObjectClasses.java | 11 +- .../registry/request/RequestModule.java | 2 + .../registry/rdap/RdapActionBaseTest.java | 1 - .../registry/rdap/RdapActionBaseTestCase.java | 4 +- .../registry/rdap/RdapJsonFormatterTest.java | 83 ++--------- .../google/registry/rdap/RdapTestHelper.java | 4 +- 15 files changed, 197 insertions(+), 320 deletions(-) diff --git a/java/google/registry/config/RegistryConfig.java b/java/google/registry/config/RegistryConfig.java index edac2b415..3ce45e672 100644 --- a/java/google/registry/config/RegistryConfig.java +++ b/java/google/registry/config/RegistryConfig.java @@ -969,20 +969,6 @@ public final class RegistryConfig { return 100; } - /** - * WHOIS server displayed in RDAP query responses. As per Gustavo Lozano of ICANN, this should - * be omitted, but the ICANN operational profile doesn't actually say that, so it's good to have - * the ability to reinstate this field if necessary. - * - * @see google.registry.rdap.RdapActionBase - */ - @Nullable - @Provides - @Config("rdapWhoisServer") - public static String provideRdapWhoisServer() { - return null; - } - /** * Redaction text for email address in WHOIS * diff --git a/java/google/registry/rdap/RdapActionBase.java b/java/google/registry/rdap/RdapActionBase.java index 4e5a686e4..653750e21 100644 --- a/java/google/registry/rdap/RdapActionBase.java +++ b/java/google/registry/rdap/RdapActionBase.java @@ -49,13 +49,11 @@ import google.registry.request.Parameter; import google.registry.request.RequestMethod; import google.registry.request.RequestPath; import google.registry.request.Response; -import google.registry.util.Clock; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.List; import java.util.Optional; -import javax.annotation.Nullable; import javax.inject.Inject; import org.joda.time.DateTime; @@ -86,7 +84,6 @@ public abstract class RdapActionBase implements Runnable { } @Inject Response response; - @Inject Clock clock; @Inject @RequestMethod Action.Method requestMethod; @Inject @RequestPath String requestPath; @Inject RdapAuthorization rdapAuthorization; @@ -94,7 +91,6 @@ public abstract class RdapActionBase implements Runnable { @Inject @Parameter("registrar") Optional registrarParam; @Inject @Parameter("includeDeleted") Optional includeDeletedParam; @Inject @Parameter("formatOutput") Optional formatOutputParam; - @Inject @Config("rdapWhoisServer") @Nullable String rdapWhoisServer; @Inject @Config("rdapResultSetMaxSize") int rdapResultSetMaxSize; @Inject RdapMetrics rdapMetrics; @@ -242,8 +238,8 @@ public abstract class RdapActionBase implements Runnable { *

This is true if the resource is not deleted, or the request wants to see deleted items, and * is authorized to do so. */ - boolean isAuthorized(EppResource eppResource, DateTime now) { - return now.isBefore(eppResource.getDeletionTime()) + boolean isAuthorized(EppResource eppResource) { + return getRequestTime().isBefore(eppResource.getDeletionTime()) || (shouldIncludeDeleted() && rdapAuthorization .isAuthorizedForClientId(eppResource.getPersistedCurrentSponsorClientId())); @@ -257,8 +253,8 @@ public abstract class RdapActionBase implements Runnable { * do so, and: * 2. The request did not specify a registrar to filter on, or the registrar matches. */ - boolean shouldBeVisible(EppResource eppResource, DateTime now) { - return isAuthorized(eppResource, now) + boolean shouldBeVisible(EppResource eppResource) { + return isAuthorized(eppResource) && (!registrarParam.isPresent() || registrarParam.get().equals(eppResource.getPersistedCurrentSponsorClientId())); } @@ -271,8 +267,8 @@ public abstract class RdapActionBase implements Runnable { * forward in time to empty), * 2. The request did not specify a registrar to filter on, or the registrar matches. */ - boolean shouldBeVisible(Optional eppResource, DateTime now) { - return eppResource.isPresent() && shouldBeVisible(eppResource.get(), now); + boolean shouldBeVisible(Optional eppResource) { + return eppResource.isPresent() && shouldBeVisible(eppResource.get()); } /** @@ -468,7 +464,6 @@ public abstract class RdapActionBase implements Runnable { * * @param query an already-defined query to be run; a filter on currentSponsorClientId will be * added if appropriate - * @param now the time as of which to evaluate the query * @param checkForVisibility true if the results should be checked to make sure they are visible; * normally this should be equal to the shouldIncludeDeleted setting, but in cases where * the query could not check deletion status (due to Datastore limitations such as the @@ -483,7 +478,7 @@ public abstract class RdapActionBase implements Runnable { * query is greater than or equal to the maximum number we might have expected */ RdapResultSet getMatchingResources( - Query query, boolean checkForVisibility, DateTime now, int querySizeLimit) { + Query query, boolean checkForVisibility, int querySizeLimit) { Optional desiredRegistrar = getDesiredRegistrar(); if (desiredRegistrar.isPresent()) { query = query.filter("currentSponsorClientId", desiredRegistrar.get()); @@ -496,7 +491,7 @@ public abstract class RdapActionBase implements Runnable { int numResourcesQueried = 0; boolean someExcluded = false; for (T resource : query) { - if (shouldBeVisible(resource, now)) { + if (shouldBeVisible(resource)) { resources.add(resource); } else { someExcluded = true; @@ -542,4 +537,9 @@ public abstract class RdapActionBase implements Runnable { metricInformationBuilder.setPrefixLength(partialStringQuery.getInitialString().length()); return partialStringQuery; } + + /** Returns the DateTime this request took place. */ + DateTime getRequestTime() { + return rdapJsonFormatter.getRequestTime(); + } } diff --git a/java/google/registry/rdap/RdapDomainAction.java b/java/google/registry/rdap/RdapDomainAction.java index 4ec869745..41f3f690d 100644 --- a/java/google/registry/rdap/RdapDomainAction.java +++ b/java/google/registry/rdap/RdapDomainAction.java @@ -31,7 +31,6 @@ import google.registry.request.HttpException.NotFoundException; import google.registry.request.auth.Auth; import java.util.Optional; import javax.inject.Inject; -import org.joda.time.DateTime; /** RDAP (new WHOIS) action for domain requests. */ @Action( @@ -48,7 +47,6 @@ public class RdapDomainAction extends RdapActionBase { @Override public RdapDomain getJsonObjectForResource(String pathSearchString, boolean isHeadRequest) { - DateTime now = clock.nowUtc(); pathSearchString = canonicalizeName(pathSearchString); try { validateDomainName(pathSearchString); @@ -61,14 +59,14 @@ public class RdapDomainAction extends RdapActionBase { // The query string is not used; the RDAP syntax is /rdap/domain/mydomain.com. Optional domainBase = loadByForeignKey( - DomainBase.class, pathSearchString, shouldIncludeDeleted() ? START_OF_TIME : now); - if (!shouldBeVisible(domainBase, now)) { + DomainBase.class, + pathSearchString, + shouldIncludeDeleted() ? START_OF_TIME : rdapJsonFormatter.getRequestTime()); + if (!shouldBeVisible(domainBase)) { throw new NotFoundException(pathSearchString + " not found"); } return rdapJsonFormatter.makeRdapJsonForDomain( domainBase.get(), - rdapWhoisServer, - now, OutputDataType.FULL); } } diff --git a/java/google/registry/rdap/RdapDomainSearchAction.java b/java/google/registry/rdap/RdapDomainSearchAction.java index db81b91db..733a50b3b 100644 --- a/java/google/registry/rdap/RdapDomainSearchAction.java +++ b/java/google/registry/rdap/RdapDomainSearchAction.java @@ -52,7 +52,6 @@ import java.util.List; import java.util.Optional; import java.util.stream.Stream; import javax.inject.Inject; -import org.joda.time.DateTime; /** * RDAP (new WHOIS) action for domain search requests. @@ -93,7 +92,6 @@ public class RdapDomainSearchAction extends RdapSearchActionBase { @Override public DomainSearchResponse getJsonObjectForResource( String pathSearchString, boolean isHeadRequest) { - DateTime now = clock.nowUtc(); // RDAP syntax example: /rdap/domains?name=exam*.com. // The pathSearchString is not used by search commands. if (pathSearchString.length() > 0) { @@ -115,8 +113,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase { } catch (Exception e) { throw new BadRequestException("Invalid value of nsLdhName parameter"); } - results = searchByDomainName( - recordWildcardType(RdapSearchPattern.create(asciiName, true)), now); + results = searchByDomainName(recordWildcardType(RdapSearchPattern.create(asciiName, true))); } else if (nsLdhNameParam.isPresent()) { metricInformationBuilder.setSearchType(SearchType.BY_NAMESERVER_NAME); // syntax: /rdap/domains?nsLdhName=ns1.exam*.com @@ -126,7 +123,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase { throw new BadRequestException("Invalid value of nsLdhName parameter"); } results = searchByNameserverLdhName( - recordWildcardType(RdapSearchPattern.create(nsLdhNameParam.get(), true)), now); + recordWildcardType(RdapSearchPattern.create(nsLdhNameParam.get(), true))); } else { metricInformationBuilder.setSearchType(SearchType.BY_NAMESERVER_ADDRESS); metricInformationBuilder.setWildcardType(WildcardType.NO_WILDCARD); @@ -138,7 +135,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase { } catch (IllegalArgumentException e) { throw new BadRequestException("Invalid value of nsIp parameter"); } - results = searchByNameserverIp(inetAddress, now); + results = searchByNameserverIp(inetAddress); } if (results.domainSearchResults().isEmpty()) { throw new NotFoundException("No domains found"); @@ -158,12 +155,11 @@ public class RdapDomainSearchAction extends RdapSearchActionBase { *

Searches which include deleted entries are effectively treated as if they have a wildcard, * since the same name can return multiple results. */ - private DomainSearchResponse searchByDomainName( - final RdapSearchPattern partialStringQuery, final DateTime now) { + private DomainSearchResponse searchByDomainName(final RdapSearchPattern partialStringQuery) { // Handle queries without a wildcard -- just load by foreign key. We can't do this if deleted // entries are included, because there may be multiple nameservers with the same name. if (!partialStringQuery.getHasWildcard() && !shouldIncludeDeleted()) { - return searchByDomainNameWithoutWildcard(partialStringQuery, now); + return searchByDomainNameWithoutWildcard(partialStringQuery); } // Handle queries with a wildcard and initial search string. We require either a TLD or an // initial string at least MIN_INITIAL_STRING_LENGTH long. @@ -177,32 +173,29 @@ public class RdapDomainSearchAction extends RdapSearchActionBase { + " without a TLD suffix", RdapSearchPattern.MIN_INITIAL_STRING_LENGTH)); } - return searchByDomainNameWithInitialString(partialStringQuery, now); + return searchByDomainNameWithInitialString(partialStringQuery); } if (partialStringQuery.getSuffix() == null) { throw new UnprocessableEntityException( "Initial search string is required for wildcard domain searches without a TLD suffix"); } - return searchByDomainNameByTld(partialStringQuery.getSuffix(), now); + return searchByDomainNameByTld(partialStringQuery.getSuffix()); } /** * Searches for domains by domain name without a wildcard or interest in deleted entries. */ private DomainSearchResponse searchByDomainNameWithoutWildcard( - final RdapSearchPattern partialStringQuery, final DateTime now) { + final RdapSearchPattern partialStringQuery) { Optional domainBase = - loadByForeignKey(DomainBase.class, partialStringQuery.getInitialString(), now); + loadByForeignKey(DomainBase.class, partialStringQuery.getInitialString(), getRequestTime()); return makeSearchResults( - shouldBeVisible(domainBase, now) - ? ImmutableList.of(domainBase.get()) - : ImmutableList.of(), - now); + shouldBeVisible(domainBase) ? ImmutableList.of(domainBase.get()) : ImmutableList.of()); } /** Searches for domains by domain name with an initial string, wildcard and possible suffix. */ private DomainSearchResponse searchByDomainNameWithInitialString( - final RdapSearchPattern partialStringQuery, final DateTime now) { + final RdapSearchPattern partialStringQuery) { // We can't query for undeleted domains as part of the query itself; that would require an // inequality query on deletion time, and we are already using inequality queries on // fullyQualifiedDomainName. So we instead pick an arbitrary limit of @@ -227,11 +220,11 @@ public class RdapDomainSearchAction extends RdapSearchActionBase { } query = query.limit(querySizeLimit); // Always check for visibility, because we couldn't look at the deletionTime in the query. - return makeSearchResults(getMatchingResources(query, true, now, querySizeLimit), now); + return makeSearchResults(getMatchingResources(query, true, querySizeLimit)); } /** Searches for domains by domain name with a TLD suffix. */ - private DomainSearchResponse searchByDomainNameByTld(String tld, DateTime now) { + private DomainSearchResponse searchByDomainNameByTld(String tld) { // Even though we are not searching on fullyQualifiedDomainName, we want the results to come // back ordered by name, so we are still in the same boat as // searchByDomainNameWithInitialString, unable to perform an inequality query on deletion time. @@ -246,7 +239,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase { query = query.filter("fullyQualifiedDomainName >", cursorString.get()); } query = query.order("fullyQualifiedDomainName").limit(querySizeLimit); - return makeSearchResults(getMatchingResources(query, true, now, querySizeLimit), now); + return makeSearchResults(getMatchingResources(query, true, querySizeLimit)); } /** @@ -259,13 +252,13 @@ public class RdapDomainSearchAction extends RdapSearchActionBase { * domains which used to be connected to an undeleted nameserver. */ private DomainSearchResponse searchByNameserverLdhName( - final RdapSearchPattern partialStringQuery, final DateTime now) { - Iterable> hostKeys = getNameserverRefsByLdhName(partialStringQuery, now); + final RdapSearchPattern partialStringQuery) { + Iterable> hostKeys = getNameserverRefsByLdhName(partialStringQuery); if (Iterables.isEmpty(hostKeys)) { metricInformationBuilder.setNumHostsRetrieved(0); throw new NotFoundException("No matching nameservers found"); } - return searchByNameserverRefs(hostKeys, now); + return searchByNameserverRefs(hostKeys); } /** @@ -279,14 +272,14 @@ public class RdapDomainSearchAction extends RdapSearchActionBase { * domain and just list all of its subordinate hosts. */ private Iterable> getNameserverRefsByLdhName( - final RdapSearchPattern partialStringQuery, final DateTime now) { + final RdapSearchPattern partialStringQuery) { // Handle queries without a wildcard. if (!partialStringQuery.getHasWildcard()) { - return getNameserverRefsByLdhNameWithoutWildcard(partialStringQuery, now); + return getNameserverRefsByLdhNameWithoutWildcard(partialStringQuery); } // Handle queries with a wildcard and suffix (specifying a suprerordinate domain). if (partialStringQuery.getSuffix() != null) { - return getNameserverRefsByLdhNameWithSuffix(partialStringQuery, now); + return getNameserverRefsByLdhNameWithSuffix(partialStringQuery); } // If there's no suffix, query the host resources. Query the resources themselves, rather than // the foreign key indexes, because then we have an index on fully qualified host name and @@ -313,7 +306,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase { /** Assembles a list of {@link HostResource} keys by name when the pattern has no wildcard. */ private Iterable> getNameserverRefsByLdhNameWithoutWildcard( - final RdapSearchPattern partialStringQuery, final DateTime now) { + final RdapSearchPattern partialStringQuery) { // If we need to check the sponsoring registrar, we need to load the resource rather than just // the key. Optional desiredRegistrar = getDesiredRegistrar(); @@ -322,7 +315,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase { loadByForeignKey( HostResource.class, partialStringQuery.getInitialString(), - shouldIncludeDeleted() ? START_OF_TIME : now); + shouldIncludeDeleted() ? START_OF_TIME : getRequestTime()); return (!host.isPresent() || !desiredRegistrar.get().equals(host.get().getPersistedCurrentSponsorClientId())) ? ImmutableList.of() @@ -332,14 +325,14 @@ public class RdapDomainSearchAction extends RdapSearchActionBase { loadAndGetKey( HostResource.class, partialStringQuery.getInitialString(), - shouldIncludeDeleted() ? START_OF_TIME : now); + shouldIncludeDeleted() ? START_OF_TIME : getRequestTime()); return (hostKey == null) ? ImmutableList.of() : ImmutableList.of(hostKey); } } /** Assembles a list of {@link HostResource} keys by name using a superordinate domain suffix. */ private Iterable> getNameserverRefsByLdhNameWithSuffix( - final RdapSearchPattern partialStringQuery, final DateTime now) { + final RdapSearchPattern partialStringQuery) { // The suffix must be a domain that we manage. That way, we can look up the domain and search // through the subordinate hosts. This is more efficient, and lets us permit wildcard searches // with no initial string. @@ -347,7 +340,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase { loadByForeignKey( DomainBase.class, partialStringQuery.getSuffix(), - shouldIncludeDeleted() ? START_OF_TIME : now) + shouldIncludeDeleted() ? START_OF_TIME : getRequestTime()) .orElseThrow( () -> new UnprocessableEntityException( @@ -362,14 +355,19 @@ public class RdapDomainSearchAction extends RdapSearchActionBase { if (desiredRegistrar.isPresent()) { Optional host = loadByForeignKey( - HostResource.class, fqhn, shouldIncludeDeleted() ? START_OF_TIME : now); + HostResource.class, + fqhn, + shouldIncludeDeleted() ? START_OF_TIME : getRequestTime()); if (host.isPresent() && desiredRegistrar.get().equals(host.get().getPersistedCurrentSponsorClientId())) { builder.add(Key.create(host.get())); } } else { Key hostKey = - loadAndGetKey(HostResource.class, fqhn, shouldIncludeDeleted() ? START_OF_TIME : now); + loadAndGetKey( + HostResource.class, + fqhn, + shouldIncludeDeleted() ? START_OF_TIME : getRequestTime()); if (hostKey != null) { builder.add(hostKey); } else { @@ -398,7 +396,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase { * domains which used to be connected to an undeleted nameserver. */ private DomainSearchResponse searchByNameserverIp( - final InetAddress inetAddress, final DateTime now) { + final InetAddress inetAddress) { Query query = queryItems( HostResource.class, @@ -412,7 +410,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase { if (desiredRegistrar.isPresent()) { query = query.filter("currentSponsorClientId", desiredRegistrar.get()); } - return searchByNameserverRefs(query.keys(), now); + return searchByNameserverRefs(query.keys()); } /** @@ -422,7 +420,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase { * #searchByNameserverIp} after they assemble the relevant host keys. */ private DomainSearchResponse searchByNameserverRefs( - final Iterable> hostKeys, final DateTime now) { + final Iterable> hostKeys) { // We must break the query up into chunks, because the in operator is limited to 30 subqueries. // Since it is possible for the same domain to show up more than once in our result list (if // we do a wildcard nameserver search that returns multiple nameservers used by the same @@ -439,14 +437,13 @@ public class RdapDomainSearchAction extends RdapSearchActionBase { .type(DomainBase.class) .filter("nsHosts in", chunk); if (!shouldIncludeDeleted()) { - query = query.filter("deletionTime >", now); + query = query.filter("deletionTime >", getRequestTime()); // If we are not performing an inequality query, we can filter on the cursor in the query. // Otherwise, we will need to filter the results afterward. } else if (cursorString.isPresent()) { query = query.filter("fullyQualifiedDomainName >", cursorString.get()); } - Stream stream = - Streams.stream(query).filter(domain -> isAuthorized(domain, now)); + Stream stream = Streams.stream(query).filter(domain -> isAuthorized(domain)); if (cursorString.isPresent()) { stream = stream.filter( @@ -464,24 +461,22 @@ public class RdapDomainSearchAction extends RdapSearchActionBase { (numHostKeysSearched >= maxNameserversInFirstStage) ? IncompletenessWarningType.MIGHT_BE_INCOMPLETE : IncompletenessWarningType.COMPLETE, - (numHostKeysSearched > 0) ? Optional.of((long) domains.size()) : Optional.empty(), - now); + (numHostKeysSearched > 0) ? Optional.of((long) domains.size()) : Optional.empty()); } /** Output JSON for a list of domains, with no incompleteness warnings. */ - private DomainSearchResponse makeSearchResults(List domains, DateTime now) { + private DomainSearchResponse makeSearchResults(List domains) { return makeSearchResults( - domains, IncompletenessWarningType.COMPLETE, Optional.of((long) domains.size()), now); + domains, IncompletenessWarningType.COMPLETE, Optional.of((long) domains.size())); } /** Output JSON from data in an {@link RdapResultSet} object. */ private DomainSearchResponse makeSearchResults( - RdapResultSet resultSet, DateTime now) { + RdapResultSet resultSet) { return makeSearchResults( resultSet.resources(), resultSet.incompletenessWarningType(), - Optional.of((long) resultSet.numResourcesRetrieved()), - now); + Optional.of((long) resultSet.numResourcesRetrieved())); } /** @@ -494,8 +489,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase { private DomainSearchResponse makeSearchResults( List domains, IncompletenessWarningType incompletenessWarningType, - Optional numDomainsRetrieved, - DateTime now) { + Optional numDomainsRetrieved) { numDomainsRetrieved.ifPresent(metricInformationBuilder::setNumDomainsRetrieved); OutputDataType outputDataType = (domains.size() > 1) ? OutputDataType.SUMMARY : OutputDataType.FULL; @@ -505,8 +499,9 @@ public class RdapDomainSearchAction extends RdapSearchActionBase { Optional newCursor = Optional.empty(); for (DomainBase domain : Iterables.limit(domains, rdapResultSetMaxSize)) { newCursor = Optional.of(domain.getFullyQualifiedDomainName()); - builder.domainSearchResultsBuilder().add( - rdapJsonFormatter.makeRdapJsonForDomain(domain, rdapWhoisServer, now, outputDataType)); + builder + .domainSearchResultsBuilder() + .add(rdapJsonFormatter.makeRdapJsonForDomain(domain, outputDataType)); } if (rdapResultSetMaxSize < domains.size()) { builder.setNextPageUri(createNavigationUri(newCursor.get())); diff --git a/java/google/registry/rdap/RdapEntityAction.java b/java/google/registry/rdap/RdapEntityAction.java index 8638134d4..03db2fa23 100644 --- a/java/google/registry/rdap/RdapEntityAction.java +++ b/java/google/registry/rdap/RdapEntityAction.java @@ -33,7 +33,6 @@ import google.registry.request.HttpException.NotFoundException; import google.registry.request.auth.Auth; import java.util.Optional; import javax.inject.Inject; -import org.joda.time.DateTime; /** * RDAP (new WHOIS) action for entity (contact and registrar) requests. the ICANN operational @@ -62,7 +61,6 @@ public class RdapEntityAction extends RdapActionBase { @Override public RdapEntity getJsonObjectForResource( String pathSearchString, boolean isHeadRequest) { - DateTime now = clock.nowUtc(); // The query string is not used; the RDAP syntax is /rdap/entity/handle (the handle is the roid // for contacts and the client identifier for registrars). Since RDAP's concept of an entity // includes both contacts and registrars, search for one first, then the other. @@ -73,12 +71,10 @@ public class RdapEntityAction extends RdapActionBase { ContactResource contactResource = ofy().load().key(contactKey).now(); // 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 != null) && shouldBeVisible(contactResource, now)) { + if ((contactResource != null) && shouldBeVisible(contactResource)) { return rdapJsonFormatter.makeRdapJsonForContact( contactResource, Optional.empty(), - rdapWhoisServer, - now, OutputDataType.FULL); } } @@ -88,7 +84,7 @@ public class RdapEntityAction extends RdapActionBase { Optional registrar = getRegistrarByIanaIdentifier(ianaIdentifier); if (registrar.isPresent() && shouldBeVisible(registrar.get())) { return rdapJsonFormatter.makeRdapJsonForRegistrar( - registrar.get(), rdapWhoisServer, now, OutputDataType.FULL); + registrar.get(), OutputDataType.FULL); } } // At this point, we have failed to find either a contact or a registrar. diff --git a/java/google/registry/rdap/RdapEntitySearchAction.java b/java/google/registry/rdap/RdapEntitySearchAction.java index 4b5cdccd5..ab3be11a2 100644 --- a/java/google/registry/rdap/RdapEntitySearchAction.java +++ b/java/google/registry/rdap/RdapEntitySearchAction.java @@ -43,7 +43,6 @@ import java.util.Comparator; import java.util.List; import java.util.Optional; import javax.inject.Inject; -import org.joda.time.DateTime; /** * RDAP (new WHOIS) action for entity (contact and registrar) search requests. @@ -111,7 +110,6 @@ public class RdapEntitySearchAction extends RdapSearchActionBase { @Override public EntitySearchResponse getJsonObjectForResource( String pathSearchString, boolean isHeadRequest) { - DateTime now = clock.nowUtc(); // RDAP syntax example: /rdap/entities?fn=Bobby%20Joe*. // The pathSearchString is not used by search commands. @@ -166,8 +164,7 @@ public class RdapEntitySearchAction extends RdapSearchActionBase { recordWildcardType(RdapSearchPattern.create(fnParam.get(), false)), cursorType, cursorQueryString, - subtype, - now); + subtype); // Search by handle. } else { @@ -179,8 +176,7 @@ public class RdapEntitySearchAction extends RdapSearchActionBase { recordWildcardType(RdapSearchPattern.create(handleParam.get(), false)), cursorType, cursorQueryString, - subtype, - now); + subtype); } // Build the result object and return it. @@ -218,8 +214,7 @@ public class RdapEntitySearchAction extends RdapSearchActionBase { final RdapSearchPattern partialStringQuery, CursorType cursorType, Optional cursorQueryString, - Subtype subtype, - DateTime now) { + Subtype subtype) { // Don't allow wildcard suffixes when searching for entities. if (partialStringQuery.getHasWildcard() && (partialStringQuery.getSuffix() != null)) { throw new UnprocessableEntityException( @@ -282,10 +277,10 @@ public class RdapEntitySearchAction extends RdapSearchActionBase { if (rdapAuthorization.role() != RdapAuthorization.Role.ADMINISTRATOR) { query = query.filter("currentSponsorClientId in", rdapAuthorization.clientIds()); } - resultSet = getMatchingResources(query, false, now, rdapResultSetMaxSize + 1); + resultSet = getMatchingResources(query, false, rdapResultSetMaxSize + 1); } } - return makeSearchResults(resultSet, registrars, QueryType.FULL_NAME, now); + return makeSearchResults(resultSet, registrars, QueryType.FULL_NAME); } /** @@ -302,8 +297,7 @@ public class RdapEntitySearchAction extends RdapSearchActionBase { final RdapSearchPattern partialStringQuery, CursorType cursorType, Optional cursorQueryString, - Subtype subtype, - DateTime now) { + Subtype subtype) { if (partialStringQuery.getSuffix() != null) { throw new UnprocessableEntityException("Suffixes not allowed in entity handle searches"); } @@ -320,7 +314,7 @@ public class RdapEntitySearchAction extends RdapSearchActionBase { .id(partialStringQuery.getInitialString()) .now(); contactResourceList = - ((contactResource != null) && shouldBeVisible(contactResource, now)) + ((contactResource != null) && shouldBeVisible(contactResource)) ? ImmutableList.of(contactResource) : ImmutableList.of(); } @@ -335,8 +329,7 @@ public class RdapEntitySearchAction extends RdapSearchActionBase { IncompletenessWarningType.COMPLETE, contactResourceList.size(), registrarList, - QueryType.HANDLE, - now); + QueryType.HANDLE); // Handle queries with a wildcard (or including deleted), but no suffix. Because the handle // for registrars is the IANA identifier number, don't allow wildcard searches for registrars, // by simply not searching for registrars if a wildcard is present (unless the request is for @@ -385,14 +378,12 @@ public class RdapEntitySearchAction extends RdapSearchActionBase { getDeletedItemHandling(), querySizeLimit), shouldIncludeDeleted(), - now, querySizeLimit); } return makeSearchResults( contactResultSet, registrars, - QueryType.HANDLE, - now); + QueryType.HANDLE); } } @@ -417,15 +408,13 @@ public class RdapEntitySearchAction extends RdapSearchActionBase { private EntitySearchResponse makeSearchResults( RdapResultSet resultSet, List registrars, - QueryType queryType, - DateTime now) { + QueryType queryType) { return makeSearchResults( resultSet.resources(), resultSet.incompletenessWarningType(), resultSet.numResourcesRetrieved(), registrars, - queryType, - now); + queryType); } /** @@ -441,7 +430,6 @@ public class RdapEntitySearchAction extends RdapSearchActionBase { * results * @param registrars the list of registrars which can be returned * @param queryType whether the query was by full name or by handle - * @param now the current date and time * @return an {@link RdapSearchResults} object */ private EntitySearchResponse makeSearchResults( @@ -449,8 +437,7 @@ public class RdapEntitySearchAction extends RdapSearchActionBase { IncompletenessWarningType incompletenessWarningType, int numContactsRetrieved, List registrars, - QueryType queryType, - DateTime now) { + QueryType queryType) { metricInformationBuilder.setNumContactsRetrieved(numContactsRetrieved); @@ -476,8 +463,6 @@ public class RdapEntitySearchAction extends RdapSearchActionBase { builder.entitySearchResultsBuilder().add(rdapJsonFormatter.makeRdapJsonForContact( contact, Optional.empty(), - rdapWhoisServer, - now, outputDataType)); newCursor = Optional.of( @@ -493,7 +478,7 @@ public class RdapEntitySearchAction extends RdapSearchActionBase { .entitySearchResultsBuilder() .add( rdapJsonFormatter.makeRdapJsonForRegistrar( - registrar, rdapWhoisServer, now, outputDataType)); + registrar, outputDataType)); newCursor = Optional.of(REGISTRAR_CURSOR_PREFIX + registrar.getRegistrarName()); } } diff --git a/java/google/registry/rdap/RdapJsonFormatter.java b/java/google/registry/rdap/RdapJsonFormatter.java index fdcee018e..527eadcc0 100644 --- a/java/google/registry/rdap/RdapJsonFormatter.java +++ b/java/google/registry/rdap/RdapJsonFormatter.java @@ -49,7 +49,6 @@ import google.registry.rdap.RdapDataStructures.Event; import google.registry.rdap.RdapDataStructures.EventAction; import google.registry.rdap.RdapDataStructures.Link; import google.registry.rdap.RdapDataStructures.Notice; -import google.registry.rdap.RdapDataStructures.Port43WhoisServer; import google.registry.rdap.RdapDataStructures.PublicId; import google.registry.rdap.RdapDataStructures.RdapStatus; import google.registry.rdap.RdapObjectClasses.RdapDomain; @@ -59,6 +58,7 @@ import google.registry.rdap.RdapObjectClasses.Vcard; import google.registry.rdap.RdapObjectClasses.VcardArray; import google.registry.request.FullServletPath; import google.registry.request.HttpException.InternalServerErrorException; +import google.registry.util.Clock; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; @@ -87,11 +87,13 @@ import org.joda.time.DateTimeComparator; */ public class RdapJsonFormatter { + private DateTime requestTime = null; @Inject @Config("rdapTos") ImmutableList rdapTos; @Inject @Config("rdapTosStaticUrl") @Nullable String rdapTosStaticUrl; @Inject @FullServletPath String fullServletPath; @Inject RdapAuthorization rdapAuthorization; + @Inject Clock clock; @Inject RdapJsonFormatter() {} /** @@ -213,15 +215,10 @@ public class RdapJsonFormatter { * Creates a JSON object for a {@link DomainBase}. * * @param domainBase the domain resource object from which the JSON object should be created - * @param whoisServer the fully-qualified domain name of the WHOIS server to be listed in the - * port43 field; if null, port43 is not added to the object - * @param now the as-date * @param outputDataType whether to generate full or summary data */ RdapDomain makeRdapJsonForDomain( DomainBase domainBase, - @Nullable String whoisServer, - DateTime now, OutputDataType outputDataType) { RdapDomain.Builder builder = RdapDomain.builder(); // RDAP Response Profile 15feb19 section 2.2: @@ -232,7 +229,7 @@ public class RdapJsonFormatter { makeStatusValueList( domainBase.getStatusValues(), false, // isRedacted - domainBase.getDeletionTime().isBefore(now))); + domainBase.getDeletionTime().isBefore(getRequestTime()))); builder.linksBuilder().add( makeSelfLink("domain", domainBase.getFullyQualifiedDomainName())); boolean displayContacts = @@ -243,7 +240,7 @@ public class RdapJsonFormatter { if (outputDataType == OutputDataType.SUMMARY) { builder.remarksBuilder().add(RdapIcannStandardInformation.SUMMARY_DATA_REMARK); } else { - ImmutableList events = makeEvents(domainBase, now); + ImmutableList events = makeEvents(domainBase); builder.eventsBuilder().addAll(events); // Kick off the database loads of the nameservers that we will need, so it can load // asynchronously while we load and process the contacts. @@ -267,26 +264,18 @@ public class RdapJsonFormatter { makeRdapJsonForContact( loadedContacts.get(designatedContact.getContactKey()), Optional.of(designatedContact.getType()), - null, - now, outputDataType)) .forEach(builder.entitiesBuilder()::add); } builder .entitiesBuilder() - .add( - createInternalRegistrarEntity( - domainBase.getCurrentSponsorClientId(), whoisServer, now)); + .add(createInternalRegistrarEntity(domainBase.getCurrentSponsorClientId())); // Add the nameservers to the data; the load was kicked off above for efficiency. for (HostResource hostResource : HOST_RESOURCE_ORDERING.immutableSortedCopy(loadedHosts.values())) { - builder.nameserversBuilder().add(makeRdapJsonForHost( - hostResource, null, now, outputDataType)); + builder.nameserversBuilder().add(makeRdapJsonForHost(hostResource, outputDataType)); } } - if (whoisServer != null) { - builder.setPort43(Port43WhoisServer.create(whoisServer)); - } return builder.build(); } @@ -294,14 +283,8 @@ public class RdapJsonFormatter { * Creates a JSON object for the desired registrar to an existing list of JSON objects. * * @param clientId the registrar client ID - * @param whoisServer the fully-qualified domain name of the WHOIS server to be listed in the - * port43 field; if null, port43 is not added to the object - * @param now the as-date */ - RdapEntity createInternalRegistrarEntity( - String clientId, - @Nullable String whoisServer, - DateTime now) { + RdapEntity createInternalRegistrarEntity(String clientId) { Optional registrar = Registrar.loadByClientIdCached(clientId); if (!registrar.isPresent()) { throw new InternalServerErrorException( @@ -309,26 +292,17 @@ public class RdapJsonFormatter { } // TODO(b/130150723): we need to display the ABUSE contact for registrar object inside of Domain // responses. Currently, we use summary for any "internal" registrar. - return makeRdapJsonForRegistrar( - registrar.get(), - whoisServer, - now, - OutputDataType.SUMMARY); + return makeRdapJsonForRegistrar(registrar.get(), OutputDataType.SUMMARY); } /** * Creates a JSON object for a {@link HostResource}. * * @param hostResource the host resource object from which the JSON object should be created - * @param whoisServer the fully-qualified domain name of the WHOIS server to be listed in the - * port43 field; if null, port43 is not added to the object - * @param now the as-date * @param outputDataType whether to generate full or summary data */ RdapNameserver makeRdapJsonForHost( HostResource hostResource, - @Nullable String whoisServer, - DateTime now, OutputDataType outputDataType) { RdapNameserver.Builder builder = RdapNameserver.builder() .setHandle(hostResource.getRepoId()) @@ -336,13 +310,17 @@ public class RdapJsonFormatter { ImmutableSet.Builder statuses = new ImmutableSet.Builder<>(); statuses.addAll(hostResource.getStatusValues()); - if (isLinked(Key.create(hostResource), now)) { + if (isLinked(Key.create(hostResource), getRequestTime())) { statuses.add(StatusValue.LINKED); } if (hostResource.isSubordinate() - && ofy().load().key(hostResource.getSuperordinateDomain()).now().cloneProjectedAtTime(now) + && ofy() + .load() + .key(hostResource.getSuperordinateDomain()) + .now() + .cloneProjectedAtTime(getRequestTime()) .getStatusValues() - .contains(StatusValue.PENDING_TRANSFER)) { + .contains(StatusValue.PENDING_TRANSFER)) { statuses.add(StatusValue.PENDING_TRANSFER); } builder @@ -351,7 +329,7 @@ public class RdapJsonFormatter { makeStatusValueList( statuses.build(), false, // isRedacted - hostResource.getDeletionTime().isBefore(now))); + hostResource.getDeletionTime().isBefore(getRequestTime()))); builder .linksBuilder() .add(makeSelfLink("nameserver", hostResource.getFullyQualifiedHostName())); @@ -361,7 +339,7 @@ public class RdapJsonFormatter { if (outputDataType == OutputDataType.SUMMARY) { builder.remarksBuilder().add(RdapIcannStandardInformation.SUMMARY_DATA_REMARK); } else { - builder.eventsBuilder().addAll(makeEvents(hostResource, now)); + builder.eventsBuilder().addAll(makeEvents(hostResource)); } // We MUST have the ip addresses: RDAP Response Profile 4.2. @@ -372,13 +350,9 @@ public class RdapJsonFormatter { builder.ipv6Builder().add(InetAddresses.toAddrString(inetAddress)); } } - builder.entitiesBuilder().add(createInternalRegistrarEntity( - hostResource.getPersistedCurrentSponsorClientId(), - whoisServer, - now)); - if (whoisServer != null) { - builder.setPort43(Port43WhoisServer.create(whoisServer)); - } + builder + .entitiesBuilder() + .add(createInternalRegistrarEntity(hostResource.getPersistedCurrentSponsorClientId())); return builder.build(); } @@ -387,16 +361,11 @@ public class RdapJsonFormatter { * * @param contactResource the contact resource object from which the JSON object should be created * @param contactType the contact type to map to an RDAP role; if absent, no role is listed - * @param whoisServer the fully-qualified domain name of the WHOIS server to be listed in the - * port43 field; if null, port43 is not added to the object - * @param now the as-date * @param outputDataType whether to generate full or summary data */ RdapEntity makeRdapJsonForContact( ContactResource contactResource, Optional contactType, - @Nullable String whoisServer, - DateTime now, OutputDataType outputDataType) { boolean isAuthorized = rdapAuthorization.isAuthorizedForClientId(contactResource.getCurrentSponsorClientId()); @@ -408,11 +377,11 @@ public class RdapJsonFormatter { .statusBuilder() .addAll( makeStatusValueList( - isLinked(Key.create(contactResource), now) + isLinked(Key.create(contactResource), getRequestTime()) ? union(contactResource.getStatusValues(), StatusValue.LINKED) : contactResource.getStatusValues(), !isAuthorized, - contactResource.getDeletionTime().isBefore(now))); + contactResource.getDeletionTime().isBefore(getRequestTime()))); contactType.ifPresent( type -> entityBuilder.rolesBuilder().add(convertContactTypeToRdapRole(type))); @@ -456,10 +425,7 @@ public class RdapJsonFormatter { if (outputDataType == OutputDataType.SUMMARY) { entityBuilder.remarksBuilder().add(RdapIcannStandardInformation.SUMMARY_DATA_REMARK); } else { - entityBuilder.eventsBuilder().addAll(makeEvents(contactResource, now)); - } - if (whoisServer != null) { - entityBuilder.setPort43(Port43WhoisServer.create(whoisServer)); + entityBuilder.eventsBuilder().addAll(makeEvents(contactResource)); } return entityBuilder.build(); } @@ -468,15 +434,10 @@ public class RdapJsonFormatter { * Creates a JSON object for a {@link Registrar}. * * @param registrar the registrar object from which the JSON object should be created - * @param whoisServer the fully-qualified domain name of the WHOIS server to be listed in the - * port43 field; if null, port43 is not added to the object - * @param now the as-date * @param outputDataType whether to generate full or summary data */ RdapEntity makeRdapJsonForRegistrar( Registrar registrar, - @Nullable String whoisServer, - DateTime now, OutputDataType outputDataType) { RdapEntity.Builder builder = RdapEntity.builder(); Long ianaIdentifier = registrar.getIanaIdentifier(); @@ -521,21 +482,18 @@ public class RdapJsonFormatter { if (outputDataType == OutputDataType.SUMMARY) { builder.remarksBuilder().add(RdapIcannStandardInformation.SUMMARY_DATA_REMARK); } else { - builder.eventsBuilder().addAll(makeEvents(registrar, now)); + builder.eventsBuilder().addAll(makeEvents(registrar)); // include the registrar contacts as subentities ImmutableList registrarContacts = registrar.getContacts().stream() - .map(registrarContact -> makeRdapJsonForRegistrarContact(registrarContact, null)) - .filter(optional -> optional.isPresent()) - .map(optional -> optional.get()) - .collect(toImmutableList()); + .map(registrarContact -> makeRdapJsonForRegistrarContact(registrarContact)) + .filter(optional -> optional.isPresent()) + .map(optional -> optional.get()) + .collect(toImmutableList()); // TODO(b/117242274): add a warning (severe?) log if registrar has no ABUSE contact, as having // one is required by the RDAP response profile builder.entitiesBuilder().addAll(registrarContacts); } - if (whoisServer != null) { - builder.setPort43(Port43WhoisServer.create(whoisServer)); - } return builder.build(); } @@ -545,11 +503,9 @@ public class RdapJsonFormatter { *

Returns empty if this contact shouldn't be visible (doesn't have a role). * * @param registrarContact the registrar contact for which the JSON object should be created - * @param whoisServer the fully-qualified domain name of the WHOIS server to be listed in the - * port43 field; if null, port43 is not added to the object */ static Optional makeRdapJsonForRegistrarContact( - RegistrarContact registrarContact, @Nullable String whoisServer) { + RegistrarContact registrarContact) { ImmutableList roles = makeRdapRoleList(registrarContact); if (roles.isEmpty()) { return Optional.empty(); @@ -576,9 +532,6 @@ public class RdapJsonFormatter { vcardBuilder.add(Vcard.create("email", "text", emailAddress)); } builder.setVcardArray(vcardBuilder.build()); - if (whoisServer != null) { - builder.setPort43(Port43WhoisServer.create(whoisServer)); - } return Optional.of(builder.build()); } @@ -627,7 +580,7 @@ public class RdapJsonFormatter { /** * Creates an event list for a domain, host or contact resource. */ - private static ImmutableList makeEvents(EppResource resource, DateTime now) { + private ImmutableList makeEvents(EppResource resource) { HashMap lastEntryOfType = Maps.newHashMap(); // Events (such as transfer, but also create) can appear multiple times. We only want the last // time they appeared. @@ -707,13 +660,13 @@ public class RdapJsonFormatter { // any EPP update (for example, by the passage of time). DateTime lastChangeTime = changeTimesBuilder.build().stream() - .filter(changeTime -> changeTime.isBefore(now)) + .filter(changeTime -> changeTime.isBefore(getRequestTime())) .max(DateTimeComparator.getInstance()) .orElse(null); if (lastChangeTime != null && lastChangeTime.isAfter(creationTime)) { eventsBuilder.add(makeEvent(EventAction.LAST_CHANGED, null, lastChangeTime)); } - eventsBuilder.add(makeEvent(EventAction.LAST_UPDATE_OF_RDAP_DATABASE, null, now)); + eventsBuilder.add(makeEvent(EventAction.LAST_UPDATE_OF_RDAP_DATABASE, null, getRequestTime())); // TODO(b/129849684): sort events by their time once we return a list of Events instead of JSON // objects. return eventsBuilder.build(); @@ -722,7 +675,7 @@ public class RdapJsonFormatter { /** * Creates an event list for a {@link Registrar}. */ - private static ImmutableList makeEvents(Registrar registrar, DateTime now) { + private ImmutableList makeEvents(Registrar registrar) { ImmutableList.Builder eventsBuilder = new ImmutableList.Builder<>(); Long ianaIdentifier = registrar.getIanaIdentifier(); eventsBuilder.add(makeEvent( @@ -734,7 +687,7 @@ public class RdapJsonFormatter { eventsBuilder.add(makeEvent( EventAction.LAST_CHANGED, null, registrar.getLastUpdateTime())); } - eventsBuilder.add(makeEvent(EventAction.LAST_UPDATE_OF_RDAP_DATABASE, null, now)); + eventsBuilder.add(makeEvent(EventAction.LAST_UPDATE_OF_RDAP_DATABASE, null, getRequestTime())); return eventsBuilder.build(); } @@ -884,4 +837,26 @@ public class RdapJsonFormatter { .setType("application/rdap+json") .build(); } + + /** + * Returns the DateTime this request took place. + * + *

The RDAP reply is large with a lot of different object in them. We want to make sure that + * all these objects are projected to the same "now". + * + *

This "now" will also be considered the time of the "last update of RDAP database" event that + * RDAP sepc requires. + * + *

We would have set this during the constructor, but the clock is injected after construction. + * So instead we set the time during the first call to this function. + * + *

We would like even more to just inject it in RequestModule and use it in many places in our + * codebase that just need a general "now" of the request, but that's a lot of work. + */ + DateTime getRequestTime() { + if (requestTime == null) { + requestTime = clock.nowUtc(); + } + return requestTime; + } } diff --git a/java/google/registry/rdap/RdapNameserverAction.java b/java/google/registry/rdap/RdapNameserverAction.java index 95592e133..7d1468357 100644 --- a/java/google/registry/rdap/RdapNameserverAction.java +++ b/java/google/registry/rdap/RdapNameserverAction.java @@ -31,7 +31,6 @@ import google.registry.request.HttpException.NotFoundException; import google.registry.request.auth.Auth; import java.util.Optional; import javax.inject.Inject; -import org.joda.time.DateTime; /** RDAP (new WHOIS) action for nameserver requests. */ @Action( @@ -48,7 +47,6 @@ public class RdapNameserverAction extends RdapActionBase { @Override public RdapNameserver getJsonObjectForResource(String pathSearchString, boolean isHeadRequest) { - DateTime now = clock.nowUtc(); pathSearchString = canonicalizeName(pathSearchString); // The RDAP syntax is /rdap/nameserver/ns1.mydomain.com. try { @@ -63,11 +61,12 @@ public class RdapNameserverAction extends RdapActionBase { // the most recently deleted one. Optional hostResource = loadByForeignKey( - HostResource.class, pathSearchString, shouldIncludeDeleted() ? START_OF_TIME : now); - if (!shouldBeVisible(hostResource, now)) { + HostResource.class, + pathSearchString, + shouldIncludeDeleted() ? START_OF_TIME : getRequestTime()); + if (!shouldBeVisible(hostResource)) { throw new NotFoundException(pathSearchString + " not found"); } - return rdapJsonFormatter.makeRdapJsonForHost( - hostResource.get(), rdapWhoisServer, now, OutputDataType.FULL); + return rdapJsonFormatter.makeRdapJsonForHost(hostResource.get(), OutputDataType.FULL); } } diff --git a/java/google/registry/rdap/RdapNameserverSearchAction.java b/java/google/registry/rdap/RdapNameserverSearchAction.java index 96116fc08..3405d1a28 100644 --- a/java/google/registry/rdap/RdapNameserverSearchAction.java +++ b/java/google/registry/rdap/RdapNameserverSearchAction.java @@ -42,7 +42,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; import javax.inject.Inject; -import org.joda.time.DateTime; /** * RDAP (new WHOIS) action for nameserver search requests. @@ -82,7 +81,6 @@ public class RdapNameserverSearchAction extends RdapSearchActionBase { @Override public NameserverSearchResponse getJsonObjectForResource( String pathSearchString, boolean isHeadRequest) { - DateTime now = clock.nowUtc(); // RDAP syntax example: /rdap/nameservers?name=ns*.example.com. // The pathSearchString is not used by search commands. if (pathSearchString.length() > 0) { @@ -103,8 +101,7 @@ public class RdapNameserverSearchAction extends RdapSearchActionBase { } results = searchByName( - recordWildcardType(RdapSearchPattern.create(Idn.toASCII(nameParam.get()), true)), - now); + recordWildcardType(RdapSearchPattern.create(Idn.toASCII(nameParam.get()), true))); } else { // syntax: /rdap/nameservers?ip=1.2.3.4 metricInformationBuilder.setSearchType(SearchType.BY_NAMESERVER_ADDRESS); @@ -114,7 +111,7 @@ public class RdapNameserverSearchAction extends RdapSearchActionBase { } catch (IllegalArgumentException e) { throw new BadRequestException("Invalid value of ip parameter"); } - results = searchByIp(inetAddress, now); + results = searchByIp(inetAddress); } if (results.nameserverSearchResults().isEmpty()) { throw new NotFoundException("No nameservers found"); @@ -128,12 +125,11 @@ public class RdapNameserverSearchAction extends RdapSearchActionBase { *

When deleted nameservers are included in the search, the search is treated as if it has a * wildcard, because multiple results can be returned. */ - private NameserverSearchResponse searchByName( - final RdapSearchPattern partialStringQuery, final DateTime now) { + private NameserverSearchResponse searchByName(final RdapSearchPattern partialStringQuery) { // Handle queries without a wildcard -- just load by foreign key. We can't do this if deleted // nameservers are desired, because there may be multiple nameservers with the same name. if (!partialStringQuery.getHasWildcard() && !shouldIncludeDeleted()) { - return searchByNameUsingForeignKey(partialStringQuery, now); + return searchByNameUsingForeignKey(partialStringQuery); // Handle queries with a wildcard (or including deleted entries). If there is a suffix, it // should be a domain that we manage, so we can look up the domain and search through the // subordinate hosts. This is more efficient, and lets us permit wildcard searches with no @@ -145,10 +141,10 @@ public class RdapNameserverSearchAction extends RdapSearchActionBase { throw new UnprocessableEntityException( "A suffix after a wildcard is not allowed when searching for deleted nameservers"); } - return searchByNameUsingSuperordinateDomain(partialStringQuery, now); + return searchByNameUsingSuperordinateDomain(partialStringQuery); // Handle queries with a wildcard (or deleted entries included), but no suffix. } else { - return searchByNameUsingPrefix(partialStringQuery, now); + return searchByNameUsingPrefix(partialStringQuery); } } @@ -158,10 +154,11 @@ public class RdapNameserverSearchAction extends RdapSearchActionBase { *

In this case, we can load by foreign key. */ private NameserverSearchResponse searchByNameUsingForeignKey( - final RdapSearchPattern partialStringQuery, final DateTime now) { + RdapSearchPattern partialStringQuery) { Optional hostResource = - loadByForeignKey(HostResource.class, partialStringQuery.getInitialString(), now); - if (!shouldBeVisible(hostResource, now)) { + loadByForeignKey( + HostResource.class, partialStringQuery.getInitialString(), getRequestTime()); + if (!shouldBeVisible(hostResource)) { metricInformationBuilder.setNumHostsRetrieved(0); throw new NotFoundException("No nameservers found"); } @@ -173,17 +170,15 @@ public class RdapNameserverSearchAction extends RdapSearchActionBase { builder.nameserverSearchResultsBuilder().add( rdapJsonFormatter.makeRdapJsonForHost( hostResource.get(), - rdapWhoisServer, - now, OutputDataType.FULL)); return builder.build(); } /** Searches for nameservers by name using the superordinate domain as a suffix. */ private NameserverSearchResponse searchByNameUsingSuperordinateDomain( - final RdapSearchPattern partialStringQuery, final DateTime now) { + RdapSearchPattern partialStringQuery) { Optional domainBase = - loadByForeignKey(DomainBase.class, partialStringQuery.getSuffix(), now); + loadByForeignKey(DomainBase.class, partialStringQuery.getSuffix(), getRequestTime()); if (!domainBase.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, @@ -201,8 +196,9 @@ 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 hostResource = loadByForeignKey(HostResource.class, fqhn, now); - if (shouldBeVisible(hostResource, now)) { + Optional hostResource = + loadByForeignKey(HostResource.class, fqhn, getRequestTime()); + if (shouldBeVisible(hostResource)) { hostList.add(hostResource.get()); if (hostList.size() > rdapResultSetMaxSize) { break; @@ -214,8 +210,7 @@ public class RdapNameserverSearchAction extends RdapSearchActionBase { hostList, IncompletenessWarningType.COMPLETE, domainBase.get().getSubordinateHosts().size(), - CursorType.NAME, - now); + CursorType.NAME); } /** @@ -223,8 +218,7 @@ public class RdapNameserverSearchAction extends RdapSearchActionBase { * *

There are no pending deletes for hosts, so we can call {@link RdapActionBase#queryItems}. */ - private NameserverSearchResponse searchByNameUsingPrefix( - final RdapSearchPattern partialStringQuery, final DateTime now) { + private NameserverSearchResponse searchByNameUsingPrefix(RdapSearchPattern partialStringQuery) { // Add 1 so we can detect truncation. int querySizeLimit = getStandardQuerySizeLimit(); Query query = @@ -236,13 +230,12 @@ public class RdapNameserverSearchAction extends RdapSearchActionBase { getDeletedItemHandling(), querySizeLimit); return makeSearchResults( - getMatchingResources(query, shouldIncludeDeleted(), now, querySizeLimit), - CursorType.NAME, - now); + getMatchingResources(query, shouldIncludeDeleted(), querySizeLimit), + CursorType.NAME); } /** Searches for nameservers by IP address, returning a JSON array of nameserver info maps. */ - private NameserverSearchResponse searchByIp(final InetAddress inetAddress, DateTime now) { + private NameserverSearchResponse searchByIp(InetAddress inetAddress) { // Add 1 so we can detect truncation. int querySizeLimit = getStandardQuerySizeLimit(); Query query = @@ -255,20 +248,18 @@ public class RdapNameserverSearchAction extends RdapSearchActionBase { getDeletedItemHandling(), querySizeLimit); return makeSearchResults( - getMatchingResources(query, shouldIncludeDeleted(), now, querySizeLimit), - CursorType.ADDRESS, - now); + getMatchingResources(query, shouldIncludeDeleted(), querySizeLimit), + CursorType.ADDRESS); } /** Output JSON for a lists of hosts contained in an {@link RdapResultSet}. */ private NameserverSearchResponse makeSearchResults( - RdapResultSet resultSet, CursorType cursorType, DateTime now) { + RdapResultSet resultSet, CursorType cursorType) { return makeSearchResults( resultSet.resources(), resultSet.incompletenessWarningType(), resultSet.numResourcesRetrieved(), - cursorType, - now); + cursorType); } /** Output JSON for a list of hosts. */ @@ -276,8 +267,7 @@ public class RdapNameserverSearchAction extends RdapSearchActionBase { List hosts, IncompletenessWarningType incompletenessWarningType, int numHostsRetrieved, - CursorType cursorType, - DateTime now) { + CursorType cursorType) { metricInformationBuilder.setNumHostsRetrieved(numHostsRetrieved); OutputDataType outputDataType = (hosts.size() > 1) ? OutputDataType.SUMMARY : OutputDataType.FULL; @@ -290,9 +280,9 @@ public class RdapNameserverSearchAction extends RdapSearchActionBase { (cursorType == CursorType.NAME) ? host.getFullyQualifiedHostName() : host.getRepoId()); - builder.nameserverSearchResultsBuilder().add( - rdapJsonFormatter.makeRdapJsonForHost( - host, rdapWhoisServer, now, outputDataType)); + builder + .nameserverSearchResultsBuilder() + .add(rdapJsonFormatter.makeRdapJsonForHost(host, outputDataType)); } if (rdapResultSetMaxSize < hosts.size()) { builder.setNextPageUri(createNavigationUri(newCursor.get())); diff --git a/java/google/registry/rdap/RdapObjectClasses.java b/java/google/registry/rdap/RdapObjectClasses.java index fa10a5416..b989431e7 100644 --- a/java/google/registry/rdap/RdapObjectClasses.java +++ b/java/google/registry/rdap/RdapObjectClasses.java @@ -205,9 +205,18 @@ final class RdapObjectClasses { @JsonableElement abstract ImmutableList status(); @JsonableElement abstract ImmutableList remarks(); @JsonableElement abstract ImmutableList links(); - @JsonableElement abstract Optional port43(); @JsonableElement abstract ImmutableList events(); + /** + * WHOIS server displayed in RDAP query responses. + * + *

As per Gustavo Lozano of ICANN, this should be omitted, but the ICANN operational profile + * doesn't actually say that, so it's good to have the ability to reinstate this field if + * necessary. + */ + @JsonableElement + abstract Optional port43(); + RdapObjectBase(BoilerplateType boilerplateType, ObjectClassName objectClassName) { super(boilerplateType); this.objectClassName = objectClassName; diff --git a/java/google/registry/request/RequestModule.java b/java/google/registry/request/RequestModule.java index f2b380d41..0725eab57 100644 --- a/java/google/registry/request/RequestModule.java +++ b/java/google/registry/request/RequestModule.java @@ -17,6 +17,7 @@ package google.registry.request; import static com.google.common.net.MediaType.JSON_UTF_8; import static java.nio.charset.StandardCharsets.UTF_8; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.VerifyException; import com.google.common.collect.ImmutableListMultimap; import com.google.common.io.ByteStreams; @@ -47,6 +48,7 @@ public final class RequestModule { private final HttpServletResponse rsp; private final AuthResult authResult; + @VisibleForTesting public RequestModule( HttpServletRequest req, HttpServletResponse rsp) { this(req, rsp, AuthResult.NOT_AUTHENTICATED); diff --git a/javatests/google/registry/rdap/RdapActionBaseTest.java b/javatests/google/registry/rdap/RdapActionBaseTest.java index f42764f98..e69b9d894 100644 --- a/javatests/google/registry/rdap/RdapActionBaseTest.java +++ b/javatests/google/registry/rdap/RdapActionBaseTest.java @@ -73,7 +73,6 @@ public class RdapActionBaseTest extends RdapActionBaseTestCase { public void baseSetUp() { inject.setStaticField(Ofy.class, "clock", clock); action = TypeUtils.instantiate(rdapActionClass); - action.clock = clock; action.includeDeletedParam = Optional.empty(); action.registrarParam = Optional.empty(); action.formatOutputParam = Optional.empty(); action.response = response; - action.rdapJsonFormatter = RdapTestHelper.getTestRdapJsonFormatter(); + action.rdapJsonFormatter = RdapTestHelper.getTestRdapJsonFormatter(clock); action.rdapMetrics = rdapMetrics; action.requestMethod = Action.Method.GET; - action.rdapWhoisServer = null; logout(); } diff --git a/javatests/google/registry/rdap/RdapJsonFormatterTest.java b/javatests/google/registry/rdap/RdapJsonFormatterTest.java index 565b0119a..b11eaed0c 100644 --- a/javatests/google/registry/rdap/RdapJsonFormatterTest.java +++ b/javatests/google/registry/rdap/RdapJsonFormatterTest.java @@ -86,14 +86,12 @@ public class RdapJsonFormatterTest { private ContactResource contactResourceTech; private ContactResource contactResourceNotLinked; - // Do not set a port43 whois server, as per Gustavo Lozano. - private static final String WHOIS_SERVER = null; @Before public void setUp() { inject.setStaticField(Ofy.class, "clock", clock); - rdapJsonFormatter = RdapTestHelper.getTestRdapJsonFormatter(); + rdapJsonFormatter = RdapTestHelper.getTestRdapJsonFormatter(clock); rdapJsonFormatter.rdapAuthorization = RdapAuthorization.create(RdapAuthorization.Role.REGISTRAR, "unicoderegistrar"); @@ -315,51 +313,35 @@ public class RdapJsonFormatterTest { @Test public void testRegistrar() { - assertThat( - rdapJsonFormatter - .makeRdapJsonForRegistrar( - registrar, WHOIS_SERVER, clock.nowUtc(), OutputDataType.FULL) - .toJson()) + assertThat(rdapJsonFormatter.makeRdapJsonForRegistrar(registrar, OutputDataType.FULL).toJson()) .isEqualTo(loadJson("rdapjson_registrar.json")); } @Test public void testRegistrar_summary() { assertThat( - rdapJsonFormatter - .makeRdapJsonForRegistrar( - registrar, WHOIS_SERVER, clock.nowUtc(), OutputDataType.SUMMARY) - .toJson()) + rdapJsonFormatter.makeRdapJsonForRegistrar(registrar, OutputDataType.SUMMARY).toJson()) .isEqualTo(loadJson("rdapjson_registrar_summary.json")); } @Test public void testHost_ipv4() { assertThat( - rdapJsonFormatter - .makeRdapJsonForHost( - hostResourceIpv4, WHOIS_SERVER, clock.nowUtc(), OutputDataType.FULL) - .toJson()) + rdapJsonFormatter.makeRdapJsonForHost(hostResourceIpv4, OutputDataType.FULL).toJson()) .isEqualTo(loadJson("rdapjson_host_ipv4.json")); } @Test public void testHost_ipv6() { assertThat( - rdapJsonFormatter - .makeRdapJsonForHost( - hostResourceIpv6, WHOIS_SERVER, clock.nowUtc(), OutputDataType.FULL) - .toJson()) + rdapJsonFormatter.makeRdapJsonForHost(hostResourceIpv6, OutputDataType.FULL).toJson()) .isEqualTo(loadJson("rdapjson_host_ipv6.json")); } @Test public void testHost_both() { assertThat( - rdapJsonFormatter - .makeRdapJsonForHost( - hostResourceBoth, WHOIS_SERVER, clock.nowUtc(), OutputDataType.FULL) - .toJson()) + rdapJsonFormatter.makeRdapJsonForHost(hostResourceBoth, OutputDataType.FULL).toJson()) .isEqualTo(loadJson("rdapjson_host_both.json")); } @@ -367,8 +349,7 @@ public class RdapJsonFormatterTest { public void testHost_both_summary() { assertThat( rdapJsonFormatter - .makeRdapJsonForHost( - hostResourceBoth, WHOIS_SERVER, clock.nowUtc(), OutputDataType.SUMMARY) + .makeRdapJsonForHost(hostResourceBoth, OutputDataType.SUMMARY) .toJson()) .isEqualTo(loadJson("rdapjson_host_both_summary.json")); } @@ -377,8 +358,7 @@ public class RdapJsonFormatterTest { public void testHost_noAddresses() { assertThat( rdapJsonFormatter - .makeRdapJsonForHost( - hostResourceNoAddresses, WHOIS_SERVER, clock.nowUtc(), OutputDataType.FULL) + .makeRdapJsonForHost(hostResourceNoAddresses, OutputDataType.FULL) .toJson()) .isEqualTo(loadJson("rdapjson_host_no_addresses.json")); } @@ -387,8 +367,7 @@ public class RdapJsonFormatterTest { public void testHost_notLinked() { assertThat( rdapJsonFormatter - .makeRdapJsonForHost( - hostResourceNotLinked, WHOIS_SERVER, clock.nowUtc(), OutputDataType.FULL) + .makeRdapJsonForHost(hostResourceNotLinked, OutputDataType.FULL) .toJson()) .isEqualTo(loadJson("rdapjson_host_not_linked.json")); } @@ -397,11 +376,7 @@ public class RdapJsonFormatterTest { public void testHost_superordinateHasPendingTransfer() { assertThat( rdapJsonFormatter - .makeRdapJsonForHost( - hostResourceSuperordinatePendingTransfer, - WHOIS_SERVER, - clock.nowUtc(), - OutputDataType.FULL) + .makeRdapJsonForHost(hostResourceSuperordinatePendingTransfer, OutputDataType.FULL) .toJson()) .isEqualTo(loadJson("rdapjson_host_pending_transfer.json")); } @@ -413,8 +388,6 @@ public class RdapJsonFormatterTest { .makeRdapJsonForContact( contactResourceRegistrant, Optional.of(DesignatedContact.Type.REGISTRANT), - WHOIS_SERVER, - clock.nowUtc(), OutputDataType.FULL) .toJson()) .isEqualTo(loadJson("rdapjson_registrant.json")); @@ -427,8 +400,6 @@ public class RdapJsonFormatterTest { .makeRdapJsonForContact( contactResourceRegistrant, Optional.of(DesignatedContact.Type.REGISTRANT), - WHOIS_SERVER, - clock.nowUtc(), OutputDataType.SUMMARY) .toJson()) .isEqualTo(loadJson("rdapjson_registrant_summary.json")); @@ -442,8 +413,6 @@ public class RdapJsonFormatterTest { .makeRdapJsonForContact( contactResourceRegistrant, Optional.of(DesignatedContact.Type.REGISTRANT), - WHOIS_SERVER, - clock.nowUtc(), OutputDataType.FULL) .toJson()) .isEqualTo(loadJson("rdapjson_registrant_logged_out.json")); @@ -463,8 +432,6 @@ public class RdapJsonFormatterTest { .makeRdapJsonForContact( contactResourceRegistrant, Optional.of(DesignatedContact.Type.REGISTRANT), - WHOIS_SERVER, - clock.nowUtc(), OutputDataType.FULL) .toJson()) .isEqualTo(loadJson("rdapjson_registrant.json")); @@ -477,8 +444,6 @@ public class RdapJsonFormatterTest { .makeRdapJsonForContact( contactResourceAdmin, Optional.of(DesignatedContact.Type.ADMIN), - WHOIS_SERVER, - clock.nowUtc(), OutputDataType.FULL) .toJson()) .isEqualTo(loadJson("rdapjson_admincontact.json")); @@ -491,8 +456,6 @@ public class RdapJsonFormatterTest { .makeRdapJsonForContact( contactResourceTech, Optional.of(DesignatedContact.Type.TECH), - WHOIS_SERVER, - clock.nowUtc(), OutputDataType.FULL) .toJson()) .isEqualTo(loadJson("rdapjson_techcontact.json")); @@ -505,8 +468,6 @@ public class RdapJsonFormatterTest { .makeRdapJsonForContact( contactResourceTech, Optional.empty(), - WHOIS_SERVER, - clock.nowUtc(), OutputDataType.FULL) .toJson()) .isEqualTo(loadJson("rdapjson_rolelesscontact.json")); @@ -519,8 +480,6 @@ public class RdapJsonFormatterTest { .makeRdapJsonForContact( contactResourceNotLinked, Optional.empty(), - WHOIS_SERVER, - clock.nowUtc(), OutputDataType.FULL) .toJson()) .isEqualTo(loadJson("rdapjson_unlinkedcontact.json")); @@ -532,8 +491,6 @@ public class RdapJsonFormatterTest { rdapJsonFormatter .makeRdapJsonForDomain( domainBaseFull, - WHOIS_SERVER, - clock.nowUtc(), OutputDataType.FULL) .toJson()) .isEqualTo(loadJson("rdapjson_domain_full.json")); @@ -543,11 +500,7 @@ public class RdapJsonFormatterTest { public void testDomain_summary() { assertThat( rdapJsonFormatter - .makeRdapJsonForDomain( - domainBaseFull, - WHOIS_SERVER, - clock.nowUtc(), - OutputDataType.SUMMARY) + .makeRdapJsonForDomain(domainBaseFull, OutputDataType.SUMMARY) .toJson()) .isEqualTo(loadJson("rdapjson_domain_summary.json")); } @@ -556,13 +509,7 @@ public class RdapJsonFormatterTest { public void testDomain_logged_out() { rdapJsonFormatter.rdapAuthorization = RdapAuthorization.PUBLIC_AUTHORIZATION; assertThat( - rdapJsonFormatter - .makeRdapJsonForDomain( - domainBaseFull, - WHOIS_SERVER, - clock.nowUtc(), - OutputDataType.FULL) - .toJson()) + rdapJsonFormatter.makeRdapJsonForDomain(domainBaseFull, OutputDataType.FULL).toJson()) .isEqualTo(loadJson("rdapjson_domain_logged_out.json")); } @@ -570,11 +517,7 @@ public class RdapJsonFormatterTest { public void testDomain_noNameserversNoTransfers() { assertThat( rdapJsonFormatter - .makeRdapJsonForDomain( - domainBaseNoNameserversNoTransfers, - WHOIS_SERVER, - clock.nowUtc(), - OutputDataType.FULL) + .makeRdapJsonForDomain(domainBaseNoNameserversNoTransfers, OutputDataType.FULL) .toJson()) .isEqualTo(loadJson("rdapjson_domain_no_nameservers.json")); } diff --git a/javatests/google/registry/rdap/RdapTestHelper.java b/javatests/google/registry/rdap/RdapTestHelper.java index bb6ece4a1..c0b038bff 100644 --- a/javatests/google/registry/rdap/RdapTestHelper.java +++ b/javatests/google/registry/rdap/RdapTestHelper.java @@ -25,6 +25,7 @@ import com.google.gson.GsonBuilder; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import google.registry.util.Clock; public class RdapTestHelper { @@ -142,10 +143,11 @@ public class RdapTestHelper { "type", "text/html"))))); } - static RdapJsonFormatter getTestRdapJsonFormatter() { + static RdapJsonFormatter getTestRdapJsonFormatter(Clock clock) { RdapJsonFormatter rdapJsonFormatter = new RdapJsonFormatter(); rdapJsonFormatter.rdapAuthorization = RdapAuthorization.PUBLIC_AUTHORIZATION; rdapJsonFormatter.fullServletPath = "https://example.tld/rdap/"; + rdapJsonFormatter.clock = clock; rdapJsonFormatter.rdapTos = ImmutableList.of( "By querying our Domain Database, you are agreeing to comply with these"