From 4a34807b1def8eea178f252bb31cda3555c8f8e6 Mon Sep 17 00:00:00 2001 From: mountford Date: Tue, 16 Aug 2016 15:09:29 -0700 Subject: [PATCH] RDAP: Use IANA identifier as the registrar handle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit According to the ICAAN operation profile: 2.1.7. Registries MUST support lookup for entities with the registrar role within other objects using the handle (as described in 3.1.5 of RFC7482). The handle of the entity with the registrar role MUST be equal to IANA Registrar ID. The entity with the registrar role in the RDAP response MUST contain a publicIDs member to identify the IANA Registrar ID from the IANA’s Registrar ID registry. The type value of the publicID object MUST be equal to IANA Registrar ID. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=130452501 --- .../registry/model/registrar/Registrar.java | 24 ++++++++++++++ .../registry/rdap/RdapEntityAction.java | 28 ++++++++++++----- .../registry/rdap/RdapEntitySearchAction.java | 31 +++++++++++++------ .../registry/rdap/RdapJsonFormatter.java | 6 ++-- .../model/registrar/RegistrarTest.java | 2 +- .../registry/rdap/RdapEntityActionTest.java | 31 ++++++++++++++----- .../rdap/RdapEntitySearchActionTest.java | 28 ++++++----------- .../rdap/testdata/rdap_registrar.json | 2 +- .../rdap/testdata/rdapjson_registrar.json | 8 ++--- .../testing/FullFieldsTestEntityHelper.java | 7 ++++- 10 files changed, 114 insertions(+), 53 deletions(-) diff --git a/java/google/registry/model/registrar/Registrar.java b/java/google/registry/model/registrar/Registrar.java index 073e80302..b97431d23 100644 --- a/java/google/registry/model/registrar/Registrar.java +++ b/java/google/registry/model/registrar/Registrar.java @@ -286,6 +286,7 @@ public class Registrar extends ImmutableObject implements Buildable, Jsonifiable * * @see "http://www.iana.org/assignments/registrar-ids/registrar-ids.txt" */ + @Index Long ianaIdentifier; /** Identifier of registrar used in external billing system (e.g. Oracle). */ @@ -840,6 +841,29 @@ public class Registrar extends ImmutableObject implements Buildable, Jsonifiable }}); } + /** + * Load registrar entities by IANA identifier range outside of a transaction. + * + * @param ianaIdentifierStart returned registrars will have an IANA id greater than or equal to + * this + * @param ianaIdentifierAfterEnd returned registrars will have an IANA id less than this + * @param resultSetMaxSize the maximum number of registrar entities to be returned + */ + public static Iterable loadByIanaIdentifierRange( + final Long ianaIdentifierStart, + final Long ianaIdentifierAfterEnd, + final int resultSetMaxSize) { + return ofy().doTransactionless(new Work>() { + @Override + public Iterable run() { + return ofy().load() + .type(Registrar.class) + .filter("ianaIdentifier >=", ianaIdentifierStart) + .filter("ianaIdentifier <", ianaIdentifierAfterEnd) + .limit(resultSetMaxSize); + }}); + } + /** Loads all registrar entities. */ public static Iterable loadAll() { return ofy().load().type(Registrar.class).ancestor(getCrossTldKey()); diff --git a/java/google/registry/rdap/RdapEntityAction.java b/java/google/registry/rdap/RdapEntityAction.java index 87a0571ca..65fae9625 100644 --- a/java/google/registry/rdap/RdapEntityAction.java +++ b/java/google/registry/rdap/RdapEntityAction.java @@ -20,6 +20,7 @@ import static google.registry.request.Action.Method.HEAD; import com.google.common.base.Optional; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; import com.google.re2j.Pattern; import com.googlecode.objectify.Key; import google.registry.model.contact.ContactResource; @@ -34,7 +35,14 @@ import javax.inject.Inject; import org.joda.time.DateTime; /** - * RDAP (new WHOIS) action for entity (contact and registrar) requests. + * RDAP (new WHOIS) action for entity (contact and registrar) requests. the ICANN operational + * profile dictates that the "handle" for registrars is to be the IANA registrar ID: + * + *

2.8.3. Registries MUST support lookup for entities with the registrar role within other + * objects using the handle (as described in 3.1.5 of RFC7482). The handle of the entity with the + * registrar role MUST be equal to IANA Registrar ID. The entity with the registrar role in the RDAP + * response MUST contain a publicIDs member to identify the IANA Registrar ID from the IANA’s + * Registrar ID registry. The type value of the publicID object MUST be equal to IANA Registrar ID. */ @Action(path = RdapEntityAction.PATH, method = {GET, HEAD}, isPrefix = true) public class RdapEntityAction extends RdapActionBase { @@ -80,17 +88,23 @@ public class RdapEntityAction extends RdapActionBase { now); } } - String clientId = pathSearchString.trim(); - if ((clientId.length() >= 3) && (clientId.length() <= 16)) { + try { + Long ianaIdentifier = Long.parseLong(pathSearchString); wasValidKey = true; - Registrar registrar = Registrar.loadByClientId(clientId); + Registrar registrar = Iterables.getOnlyElement( + Registrar.loadByIanaIdentifierRange(ianaIdentifier, ianaIdentifier + 1, 1), null); if ((registrar != null) && registrar.isActiveAndPubliclyVisible()) { return RdapJsonFormatter.makeRdapJsonForRegistrar( registrar, true, rdapLinkBase, rdapWhoisServer, now); } + } catch (NumberFormatException e) { + // Although the search string was not a valid IANA identifier, it might still have been a + // valid ROID. } - throw !wasValidKey - ? new BadRequestException(pathSearchString + " is not a valid entity handle") - : new NotFoundException(pathSearchString + " not found"); + // At this point, we have failed to find either a contact or a registrar. + throw wasValidKey + ? new NotFoundException(pathSearchString + " not found") + : new BadRequestException(pathSearchString + " is not a valid entity handle"); } } + diff --git a/java/google/registry/rdap/RdapEntitySearchAction.java b/java/google/registry/rdap/RdapEntitySearchAction.java index c0e36a5d9..8a1608b0f 100644 --- a/java/google/registry/rdap/RdapEntitySearchAction.java +++ b/java/google/registry/rdap/RdapEntitySearchAction.java @@ -104,7 +104,7 @@ public class RdapEntitySearchAction extends RdapActionBase { /** * Searches for entities by name, returning a JSON array of entity info maps. - * + * *

As per Gustavo Lozano of ICANN, registrar name search should be by registrar name only, not * by registrar contact name: * @@ -112,7 +112,7 @@ public class RdapEntitySearchAction extends RdapActionBase { * in the Base Registry Agreement (see 1.6 of Section 4 of the Base Registry Agreement, * https://newgtlds.icann.org/sites/default/files/agreements/ * agreement-approved-09jan14-en.htm). - * + * *

According to RFC 7482 section 6.1, punycode is only used for domain name labels, so we can * assume that entity names are regular unicode. */ @@ -162,17 +162,18 @@ public class RdapEntitySearchAction extends RdapActionBase { .type(ContactResource.class) .id(partialStringQuery.getInitialString()) .now(); - Registrar registrar = Registrar.loadByClientId(partialStringQuery.getInitialString()); + ImmutableList registrars = getMatchingRegistrars(partialStringQuery); return makeSearchResults( ((contactResource == null) || !contactResource.getDeletionTime().isEqual(END_OF_TIME)) ? ImmutableList.of() : ImmutableList.of(contactResource), - (registrar == null) - ? ImmutableList.of() : ImmutableList.of(registrar), + registrars, now); // Handle queries with a wildcard, but no suffix. For contact resources, the deletion time will // always be END_OF_TIME for non-deleted records; unlike domain resources, we don't need to // worry about deletion times in the future. That allows us to use an equality query for the - // deletion time. + // deletion time. 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. } else if (partialStringQuery.getSuffix() == null) { return makeSearchResults( ofy().load() @@ -183,10 +184,7 @@ public class RdapEntitySearchAction extends RdapActionBase { "<", Key.create(ContactResource.class, partialStringQuery.getNextInitialString())) .filter("deletionTime", END_OF_TIME) .limit(rdapResultSetMaxSize), - Registrar.loadByClientIdRange( - partialStringQuery.getInitialString(), - partialStringQuery.getNextInitialString(), - rdapResultSetMaxSize), + ImmutableList.of(), now); // Don't allow suffixes in entity handle search queries. } else { @@ -194,6 +192,19 @@ public class RdapEntitySearchAction extends RdapActionBase { } } + /** Looks up registrars by handle (i.e. IANA identifier). */ + private ImmutableList + getMatchingRegistrars(final RdapSearchPattern partialStringQuery) { + Long ianaIdentifier; + try { + ianaIdentifier = Long.parseLong(partialStringQuery.getInitialString()); + } catch (NumberFormatException e) { + return ImmutableList.of(); + } + return ImmutableList.copyOf(Registrar.loadByIanaIdentifierRange( + ianaIdentifier, ianaIdentifier + 1, rdapResultSetMaxSize)); + } + /** Builds a JSON array of entity info maps based on the specified contacts and registrars. */ private ImmutableList> makeSearchResults( Iterable contactResources, Iterable registrars, DateTime now) diff --git a/java/google/registry/rdap/RdapJsonFormatter.java b/java/google/registry/rdap/RdapJsonFormatter.java index c0cb5b874..91a38a348 100644 --- a/java/google/registry/rdap/RdapJsonFormatter.java +++ b/java/google/registry/rdap/RdapJsonFormatter.java @@ -621,11 +621,11 @@ public class RdapJsonFormatter { DateTime now) { ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); builder.put("objectClassName", "entity"); - builder.put("handle", registrar.getClientIdentifier()); + builder.put("handle", registrar.getIanaIdentifier().toString()); builder.put("status", STATUS_LIST_ACTIVE); builder.put("roles", ImmutableList.of(RdapEntityRole.REGISTRAR.rfc7483String)); builder.put("links", - ImmutableList.of(makeLink("entity", registrar.getClientIdentifier(), linkBase))); + ImmutableList.of(makeLink("entity", registrar.getIanaIdentifier().toString(), linkBase))); builder.put("publicIds", ImmutableList.of( ImmutableMap.of( @@ -805,7 +805,7 @@ public class RdapJsonFormatter { ImmutableList.Builder eventsBuilder = new ImmutableList.Builder<>(); eventsBuilder.add(makeEvent( RdapEventAction.REGISTRATION, - registrar.getClientIdentifier(), + registrar.getIanaIdentifier().toString(), registrar.getCreationTime())); if ((registrar.getLastUpdateTime() != null) && registrar.getLastUpdateTime().isAfter(registrar.getCreationTime())) { diff --git a/javatests/google/registry/model/registrar/RegistrarTest.java b/javatests/google/registry/model/registrar/RegistrarTest.java index ba2300d57..e1452e5a5 100644 --- a/javatests/google/registry/model/registrar/RegistrarTest.java +++ b/javatests/google/registry/model/registrar/RegistrarTest.java @@ -125,7 +125,7 @@ public class RegistrarTest extends EntityTestCase { @Test public void testIndexing() throws Exception { - verifyIndexing(registrar, "registrarName"); + verifyIndexing(registrar, "registrarName", "ianaIdentifier"); } @Test diff --git a/javatests/google/registry/rdap/RdapEntityActionTest.java b/javatests/google/registry/rdap/RdapEntityActionTest.java index 597c04067..6a5b75895 100644 --- a/javatests/google/registry/rdap/RdapEntityActionTest.java +++ b/javatests/google/registry/rdap/RdapEntityActionTest.java @@ -75,7 +75,7 @@ public class RdapEntityActionTest { // lol createTld("lol"); registrarLol = persistResource(makeRegistrar( - "evilregistrar", "Yes Virginia