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"