diff --git a/java/com/google/domain/registry/rdap/RdapActionBase.java b/java/com/google/domain/registry/rdap/RdapActionBase.java index bc67ced41..ff3ccf013 100644 --- a/java/com/google/domain/registry/rdap/RdapActionBase.java +++ b/java/com/google/domain/registry/rdap/RdapActionBase.java @@ -80,9 +80,20 @@ public abstract class RdapActionBase implements Runnable { /** Returns the servlet action path; used to extract the search string from the incoming path. */ abstract String getActionPath(); - /** Does the actual search and returns an RDAP JSON object. */ - abstract ImmutableMap getJsonObjectForResource(String searchString) - throws HttpException; + /** + * Does the actual search and returns an RDAP JSON object. + * + * @param pathSearchString the search string in the URL path + * @param isHeadRequest whether the returned map will actually be used. HTTP HEAD requests don't + * actually return anything. However, we usually still want to go through the process of + * building a map, to make sure that the request would return a 500 status if it were + * invoked using GET. So this field should usually be ignored, unless there's some + * expensive task required to create the map which will never result in a request failure. + * @param linkBase the base URL for RDAP link structures + * @return A map (probably containing nested maps and lists) with the final JSON response data. + */ + abstract ImmutableMap getJsonObjectForResource( + String pathSearchString, boolean isHeadRequest, String linkBase) throws HttpException; @Override public void run() { @@ -98,11 +109,13 @@ public abstract class RdapActionBase implements Runnable { pathProper.startsWith(getActionPath()), "%s doesn't start with %s", pathProper, getActionPath()); ImmutableMap rdapJson = - getJsonObjectForResource(pathProper.substring(getActionPath().length())); + getJsonObjectForResource( + pathProper.substring(getActionPath().length()), + requestMethod == Action.Method.HEAD, + rdapLinkBase); response.setStatus(SC_OK); if (requestMethod != Action.Method.HEAD) { - response.setPayload( - JSONValue.toJSONString(RdapJsonFormatter.makeFinalRdapJson(rdapJson))); + response.setPayload(JSONValue.toJSONString(rdapJson)); } response.setContentType(MediaType.create("application", "rdap+json")); } catch (HttpException e) { diff --git a/java/com/google/domain/registry/rdap/RdapAutnumAction.java b/java/com/google/domain/registry/rdap/RdapAutnumAction.java index 77c718a9a..824566fd5 100644 --- a/java/com/google/domain/registry/rdap/RdapAutnumAction.java +++ b/java/com/google/domain/registry/rdap/RdapAutnumAction.java @@ -48,8 +48,8 @@ public class RdapAutnumAction extends RdapActionBase { } @Override - public ImmutableMap getJsonObjectForResource(String pathSearchString) - throws HttpException { + public ImmutableMap getJsonObjectForResource( + String pathSearchString, boolean isHeadRequest, String linkBase) throws HttpException { throw new NotImplementedException("Domain Name Registry information only"); } } diff --git a/java/com/google/domain/registry/rdap/RdapDomainAction.java b/java/com/google/domain/registry/rdap/RdapDomainAction.java index 5bc4afcba..79f891b18 100644 --- a/java/com/google/domain/registry/rdap/RdapDomainAction.java +++ b/java/com/google/domain/registry/rdap/RdapDomainAction.java @@ -49,8 +49,8 @@ public class RdapDomainAction extends RdapActionBase { } @Override - public ImmutableMap getJsonObjectForResource(String pathSearchString) - throws HttpException { + public ImmutableMap getJsonObjectForResource( + String pathSearchString, boolean isHeadRequest, String linkBase) throws HttpException { pathSearchString = canonicalizeName(pathSearchString); validateDomainName(pathSearchString); // The query string is not used; the RDAP syntax is /rdap/domain/mydomain.com. @@ -59,6 +59,7 @@ public class RdapDomainAction extends RdapActionBase { if (domainResource == null) { throw new NotFoundException(pathSearchString + " not found"); } - return RdapJsonFormatter.makeRdapJsonForDomain(domainResource, rdapLinkBase, rdapWhoisServer); + return RdapJsonFormatter.makeRdapJsonForDomain( + domainResource, true, rdapLinkBase, rdapWhoisServer); } } diff --git a/java/com/google/domain/registry/rdap/RdapDomainSearchAction.java b/java/com/google/domain/registry/rdap/RdapDomainSearchAction.java index f67c19300..f26154de0 100644 --- a/java/com/google/domain/registry/rdap/RdapDomainSearchAction.java +++ b/java/com/google/domain/registry/rdap/RdapDomainSearchAction.java @@ -29,6 +29,7 @@ import com.google.common.collect.Iterables; import com.google.common.primitives.Booleans; import com.google.domain.registry.model.domain.DomainResource; import com.google.domain.registry.model.host.HostResource; +import com.google.domain.registry.rdap.RdapJsonFormatter.BoilerplateType; import com.google.domain.registry.request.Action; import com.google.domain.registry.request.HttpException; import com.google.domain.registry.request.HttpException.BadRequestException; @@ -82,8 +83,8 @@ public class RdapDomainSearchAction extends RdapActionBase { /** Parses the parameters and calls the appropriate search function. */ @Override - public ImmutableMap - getJsonObjectForResource(final String pathSearchString) throws HttpException { + public ImmutableMap getJsonObjectForResource( + String pathSearchString, boolean isHeadRequest, String linkBase) throws HttpException { DateTime now = clock.nowUtc(); // RDAP syntax example: /rdap/domains?name=exam*.com. // The pathSearchString is not used by search commands. @@ -122,7 +123,10 @@ public class RdapDomainSearchAction extends RdapActionBase { if (results.isEmpty()) { throw new NotFoundException("No domains found"); } - return ImmutableMap.of("domainSearchResults", results); + ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); + builder.put("domainSearchResults", results); + RdapJsonFormatter.addTopLevelEntries(builder, BoilerplateType.DOMAIN, null, rdapLinkBase); + return builder.build(); } /** Searches for domains by domain name, returning a JSON array of domain info maps. */ @@ -136,7 +140,8 @@ public class RdapDomainSearchAction extends RdapActionBase { return ImmutableList.of(); } return ImmutableList.of( - RdapJsonFormatter.makeRdapJsonForDomain(domainResource, rdapLinkBase, rdapWhoisServer)); + RdapJsonFormatter.makeRdapJsonForDomain( + domainResource, false, rdapLinkBase, rdapWhoisServer)); // Handle queries with a wildcard. } else { Query query = ofy().load() @@ -153,7 +158,7 @@ public class RdapDomainSearchAction extends RdapActionBase { if (domainResource.getDeletionTime().isAfter(now)) { builder.add( RdapJsonFormatter.makeRdapJsonForDomain( - domainResource, rdapLinkBase, rdapWhoisServer)); + domainResource, false, rdapLinkBase, rdapWhoisServer)); } } return builder.build(); @@ -262,7 +267,8 @@ public class RdapDomainSearchAction extends RdapActionBase { .limit(1000); for (DomainResource domainResource : query) { builder.add( - RdapJsonFormatter.makeRdapJsonForDomain(domainResource, rdapLinkBase, rdapWhoisServer)); + RdapJsonFormatter.makeRdapJsonForDomain( + domainResource, false, rdapLinkBase, rdapWhoisServer)); } } return builder.build(); diff --git a/java/com/google/domain/registry/rdap/RdapEntityAction.java b/java/com/google/domain/registry/rdap/RdapEntityAction.java index da5f93ba3..1b977b4ba 100644 --- a/java/com/google/domain/registry/rdap/RdapEntityAction.java +++ b/java/com/google/domain/registry/rdap/RdapEntityAction.java @@ -24,6 +24,7 @@ import com.google.domain.registry.model.contact.ContactResource; import com.google.domain.registry.model.domain.DesignatedContact; import com.google.domain.registry.model.registrar.Registrar; import com.google.domain.registry.request.Action; +import com.google.domain.registry.request.HttpException; import com.google.domain.registry.request.HttpException.BadRequestException; import com.google.domain.registry.request.HttpException.NotFoundException; import com.google.domain.registry.util.Clock; @@ -58,7 +59,8 @@ public class RdapEntityAction extends RdapActionBase { } @Override - public ImmutableMap getJsonObjectForResource(String pathSearchString) { + public ImmutableMap getJsonObjectForResource( + String pathSearchString, boolean isHeadRequest, String linkBase) throws HttpException { // 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. @@ -70,6 +72,7 @@ public class RdapEntityAction extends RdapActionBase { if ((contactResource != null) && clock.nowUtc().isBefore(contactResource.getDeletionTime())) { return RdapJsonFormatter.makeRdapJsonForContact( contactResource, + true, Optional.absent(), rdapLinkBase, rdapWhoisServer); @@ -80,7 +83,8 @@ public class RdapEntityAction extends RdapActionBase { wasValidKey = true; Registrar registrar = Registrar.loadByClientId(clientId); if ((registrar != null) && registrar.isActiveAndPubliclyVisible()) { - return RdapJsonFormatter.makeRdapJsonForRegistrar(registrar, rdapLinkBase, rdapWhoisServer); + return RdapJsonFormatter.makeRdapJsonForRegistrar( + registrar, true, rdapLinkBase, rdapWhoisServer); } } throw !wasValidKey diff --git a/java/com/google/domain/registry/rdap/RdapEntitySearchAction.java b/java/com/google/domain/registry/rdap/RdapEntitySearchAction.java index d67c03a94..ef9970439 100644 --- a/java/com/google/domain/registry/rdap/RdapEntitySearchAction.java +++ b/java/com/google/domain/registry/rdap/RdapEntitySearchAction.java @@ -27,6 +27,7 @@ import com.google.domain.registry.config.ConfigModule.Config; import com.google.domain.registry.model.contact.ContactResource; import com.google.domain.registry.model.domain.DesignatedContact; import com.google.domain.registry.model.registrar.Registrar; +import com.google.domain.registry.rdap.RdapJsonFormatter.BoilerplateType; import com.google.domain.registry.request.Action; import com.google.domain.registry.request.HttpException; import com.google.domain.registry.request.HttpException.BadRequestException; @@ -76,8 +77,8 @@ public class RdapEntitySearchAction extends RdapActionBase { /** Parses the parameters and calls the appropriate search function. */ @Override - public ImmutableMap - getJsonObjectForResource(final String pathSearchString) throws HttpException { + public ImmutableMap getJsonObjectForResource( + String pathSearchString, boolean isHeadRequest, String linkBase) throws HttpException { // RDAP syntax example: /rdap/entities?fn=Bobby%20Joe*. // The pathSearchString is not used by search commands. if (pathSearchString.length() > 0) { @@ -97,7 +98,10 @@ public class RdapEntitySearchAction extends RdapActionBase { if (results.isEmpty()) { throw new NotFoundException("No entities found"); } - return ImmutableMap.of("entitySearchResults", results); + ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); + builder.put("entitySearchResults", results); + RdapJsonFormatter.addTopLevelEntries(builder, BoilerplateType.OTHER, null, rdapLinkBase); + return builder.build(); } /** Searches for entities by handle, returning a JSON array of entity info maps. */ @@ -114,13 +118,14 @@ public class RdapEntitySearchAction extends RdapActionBase { if ((contactResource != null) && contactResource.getDeletionTime().isEqual(END_OF_TIME)) { builder.add(RdapJsonFormatter.makeRdapJsonForContact( contactResource, + false, Optional.absent(), rdapLinkBase, rdapWhoisServer)); } if ((registrar != null) && registrar.isActiveAndPubliclyVisible()) { builder.add(RdapJsonFormatter.makeRdapJsonForRegistrar( - registrar, rdapLinkBase, rdapWhoisServer)); + registrar, false, rdapLinkBase, rdapWhoisServer)); } return builder.build(); // Handle queries with a wildcard, but no suffix. For contact resources, the deletion time will @@ -139,6 +144,7 @@ public class RdapEntitySearchAction extends RdapActionBase { for (ContactResource contactResource : query) { builder.add(RdapJsonFormatter.makeRdapJsonForContact( contactResource, + false, Optional.absent(), rdapLinkBase, rdapWhoisServer)); @@ -150,7 +156,7 @@ public class RdapEntitySearchAction extends RdapActionBase { rdapResultSetMaxSize)) { if (registrar.isActiveAndPubliclyVisible()) { builder.add(RdapJsonFormatter.makeRdapJsonForRegistrar( - registrar, rdapLinkBase, rdapWhoisServer)); + registrar, false, rdapLinkBase, rdapWhoisServer)); } } // In theory, there could be more results than our max size, so limit the size. diff --git a/java/com/google/domain/registry/rdap/RdapHelpAction.java b/java/com/google/domain/registry/rdap/RdapHelpAction.java index 43b53ba7b..239b31b43 100644 --- a/java/com/google/domain/registry/rdap/RdapHelpAction.java +++ b/java/com/google/domain/registry/rdap/RdapHelpAction.java @@ -19,6 +19,7 @@ import static com.google.domain.registry.request.Action.Method.HEAD; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.domain.registry.rdap.RdapJsonFormatter.BoilerplateType; import com.google.domain.registry.rdap.RdapJsonFormatter.MakeRdapJsonNoticeParameters; import com.google.domain.registry.request.Action; import com.google.domain.registry.request.HttpException; @@ -36,6 +37,16 @@ public class RdapHelpAction extends RdapActionBase { public static final String PATH = "/rdap/help"; + /** + * Path for the terms of service. The terms of service are also used to create the required + * boilerplate notice, so we make it a publicly visible that we can use elsewhere to reference it. + */ + public static final String TERMS_OF_SERVICE_PATH = "/tos"; + + /** + * Map from a relative path underneath the RDAP root path to the appropriate + * {@link MakeRdapJsonNoticeParameters} object. + */ private static final ImmutableMap HELP_MAP = ImmutableMap.of( "/", @@ -76,7 +87,7 @@ public class RdapHelpAction extends RdapActionBase { .linkValueSuffix("help/syntax") .linkHrefUrlString("https://www.registry.google/about/rdap/syntax.html") .build(), - "/tos", + TERMS_OF_SERVICE_PATH, MakeRdapJsonNoticeParameters.builder() .title("RDAP Terms of Service") .description(ImmutableList.of( @@ -119,8 +130,21 @@ public class RdapHelpAction extends RdapActionBase { } @Override - public ImmutableMap getJsonObjectForResource(String pathSearchString) - throws HttpException { + public ImmutableMap getJsonObjectForResource( + String pathSearchString, boolean isHeadRequest, String linkBase) throws HttpException { + // We rely on addTopLevelEntries to notice if we are sending the TOS notice, and not add a + // duplicate boilerplate entry. + ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); + RdapJsonFormatter.addTopLevelEntries( + builder, + BoilerplateType.OTHER, + ImmutableList.of(getJsonHelpNotice(pathSearchString, rdapLinkBase)), + rdapLinkBase); + return builder.build(); + } + + static ImmutableMap getJsonHelpNotice( + String pathSearchString, String rdapLinkBase) { if (pathSearchString.isEmpty()) { pathSearchString = "/"; } @@ -128,10 +152,8 @@ public class RdapHelpAction extends RdapActionBase { throw new NotFoundException("no help found for " + pathSearchString); } try { - return ImmutableMap.of( - "notices", - (Object) ImmutableList.of(RdapJsonFormatter.makeRdapJsonNotice( - HELP_MAP.get(pathSearchString), rdapLinkBase))); + return RdapJsonFormatter.makeRdapJsonNotice( + HELP_MAP.get(pathSearchString), rdapLinkBase); } catch (Exception e) { throw new InternalServerErrorException("unable to read help for " + pathSearchString); } diff --git a/java/com/google/domain/registry/rdap/RdapIcannStandardInformation.java b/java/com/google/domain/registry/rdap/RdapIcannStandardInformation.java new file mode 100644 index 000000000..d2b95c5b0 --- /dev/null +++ b/java/com/google/domain/registry/rdap/RdapIcannStandardInformation.java @@ -0,0 +1,74 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.domain.registry.rdap; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +/** + * This file contains boilerplate required by the ICANN RDAP Profile. + * + * @see "https://whois.icann.org/sites/default/files/files/gtld-rdap-operational-profile-draft-03dec15-en.pdf" + */ + +public class RdapIcannStandardInformation { + + /** Required by ICANN RDAP Profile section 1.4.10. */ + private static final ImmutableMap CONFORMANCE_REMARK = + ImmutableMap.of( + "description", + ImmutableList.of( + "This response conforms to the RDAP Operational Profile for gTLD Registries and" + + " Registrars version 1.0")); + + /** Required by ICANN RDAP Profile section 1.5.18. */ + private static final ImmutableMap DOMAIN_STATUS_CODES_REMARK = + ImmutableMap. of( + "title", + "EPP Status Codes", + "description", + ImmutableList.of( + "For more information on domain status codes, please visit https://icann.org/epp"), + "links", + ImmutableList.of( + ImmutableMap.of( + "value", "https://icann.org/epp", + "rel", "alternate", + "href", "https://icann.org/epp", + "type", "text/html"))); + + /** Required by ICANN RDAP Profile section 1.5.20. */ + private static final ImmutableMap INACCURACY_COMPLAINT_FORM_REMARK = + ImmutableMap. of( + "description", + ImmutableList.of( + "URL of the ICANN Whois Inaccuracy Complaint Form: https://www.icann.org/wicf"), + "links", + ImmutableList.of( + ImmutableMap.of( + "value", "https://www.icann.org/wicf", + "rel", "alternate", + "href", "https://www.icann.org/wicf", + "type", "text/html"))); + + /** Boilerplate remarks required by domain responses. */ + static final ImmutableList> domainBoilerplateRemarks = + ImmutableList.of( + CONFORMANCE_REMARK, DOMAIN_STATUS_CODES_REMARK, INACCURACY_COMPLAINT_FORM_REMARK); + + /** Boilerplate remarks required by non-domain responses. */ + static final ImmutableList> nonDomainBoilerplateRemarks = + ImmutableList.of(CONFORMANCE_REMARK); +} diff --git a/java/com/google/domain/registry/rdap/RdapIpAction.java b/java/com/google/domain/registry/rdap/RdapIpAction.java index 0ce0e8d1f..30da5a63b 100644 --- a/java/com/google/domain/registry/rdap/RdapIpAction.java +++ b/java/com/google/domain/registry/rdap/RdapIpAction.java @@ -49,8 +49,8 @@ public class RdapIpAction extends RdapActionBase { } @Override - public ImmutableMap getJsonObjectForResource(String pathSearchString) - throws HttpException { + public ImmutableMap getJsonObjectForResource( + String pathSearchString, boolean isHeadRequest, String linkBase) throws HttpException { throw new NotImplementedException("Domain Name Registry information only"); } } diff --git a/java/com/google/domain/registry/rdap/RdapJsonFormatter.java b/java/com/google/domain/registry/rdap/RdapJsonFormatter.java index f74de9e32..b539fe723 100644 --- a/java/com/google/domain/registry/rdap/RdapJsonFormatter.java +++ b/java/com/google/domain/registry/rdap/RdapJsonFormatter.java @@ -72,8 +72,20 @@ import javax.annotation.Nullable; */ public class RdapJsonFormatter { + /** + * What type of boilerplate notices are required for the RDAP JSON messages? The ICANN RDAP + * Profile specifies that, for instance, domain name responses should include a remark about + * domain status codes. So we need to know when to include such boilerplate. + */ + public enum BoilerplateType { + DOMAIN, + OTHER + } + private static final String RDAP_CONFORMANCE_LEVEL = "rdap_level_0"; private static final String VCARD_VERSION_NUMBER = "4.0"; + static final String NOTICES = "notices"; + private static final String REMARKS = "remarks"; /** Status values specified in RFC 7483 10.2.2. */ private enum RdapStatus { @@ -183,19 +195,45 @@ public class RdapJsonFormatter { }}); /** - * Takes an object returned by one of the other methods, and creates a final JSON object suitable - * for conversion and transmission. This involves adding one top-level entry, since RFC 7483 - * specifies that the top-level object should include an entry indicating the conformance level. + * Adds the required top-level boilerplate. RFC 7483 specifies that the top-level object should + * include an entry indicating the conformance level. The ICANN RDAP Profile document (dated 3 + * December 2015) mandates several additional entries, in sections 1.4.4, 1.4.10, 1.5.18 and + * 1.5.20. Note that this method will only work if there are no object-specific remarks already in + * the JSON object being built. If there are, the boilerplate must be merged in. * - * @param rdapJson an RDAP JSON object created by one of the other methods - * @return an RDAP JSON object with same info as the input, with additional top-level fields + * @param builder a builder for a JSON map object + * @param boilerplateType type of boilerplate to be added; the ICANN RDAP Profile document + * mandates extra boilerplate for domain objects + * @param notices a list of notices to be inserted before the boilerplate notices. If the TOS + * notice is in this list, the method avoids adding a second copy. + * @param rdapLinkBase the base for link URLs */ - public static ImmutableMap makeFinalRdapJson( - ImmutableMap rdapJson) { - ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); - builder.putAll(rdapJson); + static void addTopLevelEntries( + ImmutableMap.Builder builder, + BoilerplateType boilerplateType, + @Nullable Iterable> notices, + String rdapLinkBase) { builder.put("rdapConformance", CONFORMANCE_LIST); - return builder.build(); + ImmutableList.Builder> noticesBuilder = ImmutableList.builder(); + ImmutableMap tosNotice = + RdapHelpAction.getJsonHelpNotice(RdapHelpAction.TERMS_OF_SERVICE_PATH, rdapLinkBase); + boolean tosNoticeFound = false; + if (notices != null) { + noticesBuilder.addAll(notices); + for (ImmutableMap notice : notices) { + if (notice.equals(tosNotice)) { + tosNoticeFound = true; + break; + } + } + } + if (!tosNoticeFound) { + noticesBuilder.add(tosNotice); + } + builder.put(NOTICES, noticesBuilder.build()); + builder.put(REMARKS, (boilerplateType == BoilerplateType.DOMAIN) + ? RdapIcannStandardInformation.domainBoilerplateRemarks + : RdapIcannStandardInformation.nonDomainBoilerplateRemarks); } /** AutoValue class to build parameters to {@link #makeRdapJsonNotice}. */ @@ -225,7 +263,7 @@ public class RdapJsonFormatter { /** * Creates a JSON object containing a notice or remark object, as defined by RFC 7483 section 4.3. - * The object should then be inserted into a "notices" or "remarks" array. The builder fields are: + * The object should then be inserted into a notices or remarks array. The builder fields are: * *

title: the title of the notice; if null, the notice will have no title * @@ -251,8 +289,8 @@ public class RdapJsonFormatter { * @see * RFC 7483: JSON Responses for the Registration Data Access Protocol (RDAP) */ - public static ImmutableMap - makeRdapJsonNotice(MakeRdapJsonNoticeParameters parameters, @Nullable String linkBase) { + static ImmutableMap makeRdapJsonNotice( + MakeRdapJsonNoticeParameters parameters, @Nullable String linkBase) { ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); if (parameters.title() != null) { builder.put("title", parameters.title()); @@ -293,8 +331,11 @@ public class RdapJsonFormatter { * @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 */ - public static ImmutableMap makeRdapJsonForDomain( - DomainResource domainResource, @Nullable String linkBase, @Nullable String whoisServer) { + static ImmutableMap makeRdapJsonForDomain( + DomainResource domainResource, + boolean isTopLevel, + @Nullable String linkBase, + @Nullable String whoisServer) { // Kick off the database loads of the nameservers that we will need. Set> hostRefs = new LinkedHashSet<>(); for (ReferenceUnion hostReference : domainResource.getNameservers()) { @@ -328,7 +369,7 @@ public class RdapJsonFormatter { ImmutableList.Builder nsBuilder = new ImmutableList.Builder<>(); for (HostResource hostResource : HOST_RESOURCE_ORDERING.immutableSortedCopy(loadedHosts.values())) { - nsBuilder.add(makeRdapJsonForHost(hostResource, linkBase, null)); + nsBuilder.add(makeRdapJsonForHost(hostResource, false, linkBase, null)); } ImmutableList ns = nsBuilder.build(); if (!ns.isEmpty()) { @@ -341,7 +382,7 @@ public class RdapJsonFormatter { ContactResource loadedContact = loadedContacts.get(designatedContact.getContactId().getLinked().key()); entitiesBuilder.add(makeRdapJsonForContact( - loadedContact, Optional.of(designatedContact.getType()), linkBase, null)); + loadedContact, false, Optional.of(designatedContact.getType()), linkBase, null)); } ImmutableList entities = entitiesBuilder.build(); if (!entities.isEmpty()) { @@ -350,6 +391,9 @@ public class RdapJsonFormatter { if (whoisServer != null) { builder.put("port43", whoisServer); } + if (isTopLevel) { + addTopLevelEntries(builder, BoilerplateType.DOMAIN, null, linkBase); + } return builder.build(); } @@ -361,8 +405,11 @@ public class RdapJsonFormatter { * @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 */ - public static ImmutableMap makeRdapJsonForHost( - HostResource hostResource, @Nullable String linkBase, @Nullable String whoisServer) { + static ImmutableMap makeRdapJsonForHost( + HostResource hostResource, + boolean isTopLevel, + @Nullable String linkBase, + @Nullable String whoisServer) { ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); builder.put("objectClassName", "nameserver"); builder.put("handle", hostResource.getRepoId()); @@ -403,6 +450,9 @@ public class RdapJsonFormatter { if (whoisServer != null) { builder.put("port43", whoisServer); } + if (isTopLevel) { + addTopLevelEntries(builder, BoilerplateType.OTHER, null, linkBase); + } return builder.build(); } @@ -415,8 +465,9 @@ public class RdapJsonFormatter { * @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 */ - public static ImmutableMap makeRdapJsonForContact( + static ImmutableMap makeRdapJsonForContact( ContactResource contactResource, + boolean isTopLevel, Optional contactType, @Nullable String linkBase, @Nullable String whoisServer) { @@ -464,6 +515,9 @@ public class RdapJsonFormatter { if (whoisServer != null) { builder.put("port43", whoisServer); } + if (isTopLevel) { + addTopLevelEntries(builder, BoilerplateType.OTHER, null, linkBase); + } return builder.build(); } @@ -475,8 +529,11 @@ public class RdapJsonFormatter { * @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 */ - public static ImmutableMap makeRdapJsonForRegistrar( - Registrar registrar, @Nullable String linkBase, @Nullable String whoisServer) { + static ImmutableMap makeRdapJsonForRegistrar( + Registrar registrar, + boolean isTopLevel, + @Nullable String linkBase, + @Nullable String whoisServer) { ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); builder.put("objectClassName", "entity"); builder.put("handle", registrar.getClientIdentifier()); @@ -534,6 +591,9 @@ public class RdapJsonFormatter { if (whoisServer != null) { builder.put("port43", whoisServer); } + if (isTopLevel) { + addTopLevelEntries(builder, BoilerplateType.OTHER, null, linkBase); + } return builder.build(); } @@ -544,7 +604,7 @@ public class RdapJsonFormatter { * @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 */ - public static ImmutableMap makeRdapJsonForRegistrarContact( + static ImmutableMap makeRdapJsonForRegistrarContact( RegistrarContact registrarContact, @Nullable String whoisServer) { ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); builder.put("objectClassName", "entity"); @@ -705,7 +765,7 @@ public class RdapJsonFormatter { * @see * RFC 7483: JSON Responses for the Registration Data Access Protocol (RDAP) */ - public static ImmutableMap makeError( + static ImmutableMap makeError( int status, String title, String description) { return ImmutableMap.of( "rdapConformance", CONFORMANCE_LIST, diff --git a/java/com/google/domain/registry/rdap/RdapNameserverAction.java b/java/com/google/domain/registry/rdap/RdapNameserverAction.java index 4f13e1393..44bcbd224 100644 --- a/java/com/google/domain/registry/rdap/RdapNameserverAction.java +++ b/java/com/google/domain/registry/rdap/RdapNameserverAction.java @@ -50,7 +50,7 @@ public class RdapNameserverAction extends RdapActionBase { @Override public ImmutableMap getJsonObjectForResource( - String pathSearchString) throws HttpException { + String pathSearchString, boolean isHeadRequest, String linkBase) throws HttpException { pathSearchString = canonicalizeName(pathSearchString); // The RDAP syntax is /rdap/nameserver/ns1.mydomain.com. validateDomainName(pathSearchString); @@ -59,6 +59,6 @@ public class RdapNameserverAction extends RdapActionBase { if (hostResource == null) { throw new NotFoundException(pathSearchString + " not found"); } - return RdapJsonFormatter.makeRdapJsonForHost(hostResource, rdapLinkBase, rdapWhoisServer); + return RdapJsonFormatter.makeRdapJsonForHost(hostResource, true, rdapLinkBase, rdapWhoisServer); } } diff --git a/java/com/google/domain/registry/rdap/RdapNameserverSearchAction.java b/java/com/google/domain/registry/rdap/RdapNameserverSearchAction.java index b1f511217..02ce7b45b 100644 --- a/java/com/google/domain/registry/rdap/RdapNameserverSearchAction.java +++ b/java/com/google/domain/registry/rdap/RdapNameserverSearchAction.java @@ -28,6 +28,7 @@ import com.google.common.primitives.Booleans; import com.google.domain.registry.config.ConfigModule.Config; import com.google.domain.registry.model.domain.DomainResource; import com.google.domain.registry.model.host.HostResource; +import com.google.domain.registry.rdap.RdapJsonFormatter.BoilerplateType; import com.google.domain.registry.request.Action; import com.google.domain.registry.request.HttpException; import com.google.domain.registry.request.HttpException.BadRequestException; @@ -77,8 +78,8 @@ public class RdapNameserverSearchAction extends RdapActionBase { /** Parses the parameters and calls the appropriate search function. */ @Override - public ImmutableMap - getJsonObjectForResource(final String pathSearchString) throws HttpException { + public ImmutableMap getJsonObjectForResource( + String pathSearchString, boolean isHeadRequest, String linkBase) throws HttpException { DateTime now = clock.nowUtc(); // RDAP syntax example: /rdap/nameservers?name=ns*.example.com. // The pathSearchString is not used by search commands. @@ -105,7 +106,10 @@ public class RdapNameserverSearchAction extends RdapActionBase { if (results.isEmpty()) { throw new NotFoundException("No nameservers found"); } - return ImmutableMap.of("nameserverSearchResults", results); + ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); + builder.put("nameserverSearchResults", results); + RdapJsonFormatter.addTopLevelEntries(builder, BoilerplateType.OTHER, null, rdapLinkBase); + return builder.build(); } /** Searches for nameservers by name, returning a JSON array of nameserver info maps. */ @@ -120,7 +124,8 @@ public class RdapNameserverSearchAction extends RdapActionBase { throw new NotFoundException("No nameservers found"); } return ImmutableList.of( - RdapJsonFormatter.makeRdapJsonForHost(hostResource, rdapLinkBase, rdapWhoisServer)); + RdapJsonFormatter.makeRdapJsonForHost( + hostResource, false, rdapLinkBase, rdapWhoisServer)); // Handle queries with a wildcard, but no suffix. There are no pending deletes for hosts, so we // can call queryUndeleted. } else if (partialStringQuery.getSuffix() == null) { @@ -131,7 +136,8 @@ public class RdapNameserverSearchAction extends RdapActionBase { ImmutableList.Builder> builder = new ImmutableList.Builder<>(); for (HostResource hostResource : query) { builder.add( - RdapJsonFormatter.makeRdapJsonForHost(hostResource, rdapLinkBase, rdapWhoisServer)); + RdapJsonFormatter.makeRdapJsonForHost( + hostResource, false, rdapLinkBase, rdapWhoisServer)); } return builder.build(); // Handle queries with a wildcard and a suffix. In this case, it is more efficient to do things @@ -151,7 +157,8 @@ public class RdapNameserverSearchAction extends RdapActionBase { HostResource hostResource = loadByUniqueId(HostResource.class, fqhn, clock.nowUtc()); if (hostResource != null) { builder.add( - RdapJsonFormatter.makeRdapJsonForHost(hostResource, rdapLinkBase, rdapWhoisServer)); + RdapJsonFormatter.makeRdapJsonForHost( + hostResource, false, rdapLinkBase, rdapWhoisServer)); } } } @@ -174,7 +181,8 @@ public class RdapNameserverSearchAction extends RdapActionBase { ImmutableList.Builder> builder = new ImmutableList.Builder<>(); for (HostResource hostResource : query) { builder.add( - RdapJsonFormatter.makeRdapJsonForHost(hostResource, rdapLinkBase, rdapWhoisServer)); + RdapJsonFormatter.makeRdapJsonForHost( + hostResource, false, rdapLinkBase, rdapWhoisServer)); } return builder.build(); } diff --git a/javatests/com/google/domain/registry/rdap/RdapActionBaseTest.java b/javatests/com/google/domain/registry/rdap/RdapActionBaseTest.java index 9af504118..57fa4efc8 100644 --- a/javatests/com/google/domain/registry/rdap/RdapActionBaseTest.java +++ b/javatests/com/google/domain/registry/rdap/RdapActionBaseTest.java @@ -19,9 +19,12 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.domain.registry.request.Action.Method.GET; import static com.google.domain.registry.request.Action.Method.HEAD; import static com.google.domain.registry.testing.DatastoreHelper.createTld; +import static com.google.domain.registry.testing.TestDataHelper.loadFileWithSubstitutions; import com.google.common.collect.ImmutableMap; import com.google.domain.registry.model.ofy.Ofy; +import com.google.domain.registry.rdap.RdapJsonFormatter.BoilerplateType; +import com.google.domain.registry.request.HttpException; import com.google.domain.registry.testing.AppEngineRule; import com.google.domain.registry.testing.FakeClock; import com.google.domain.registry.testing.FakeResponse; @@ -68,14 +71,22 @@ public class RdapActionBaseTest { } @Override - public ImmutableMap getJsonObjectForResource(String searchString) { - if (searchString.equals("IllegalArgumentException")) { + public ImmutableMap getJsonObjectForResource( + String pathSearchString, boolean isHeadRequest, String linkBase) throws HttpException { + if (pathSearchString.equals("IllegalArgumentException")) { throw new IllegalArgumentException(); } - if (searchString.equals("RuntimeException")) { + if (pathSearchString.equals("RuntimeException")) { throw new RuntimeException(); } - return ImmutableMap.of("key", "value"); + ImmutableMap.Builder builder = ImmutableMap.builder(); + builder.put("key", "value"); + RdapJsonFormatter.addTopLevelEntries( + builder, + BoilerplateType.OTHER, + null, + "http://myserver.google.com/"); + return builder.build(); } } @@ -92,6 +103,7 @@ public class RdapActionBaseTest { private Object generateActualJson(String domainName) { action.requestPath = RdapTestAction.PATH + domainName; action.requestMethod = GET; + action.rdapLinkBase = "http://myserver.google.com/"; action.run(); return JSONValue.parse(response.getPayload()); } @@ -99,6 +111,7 @@ public class RdapActionBaseTest { private String generateHeadPayload(String domainName) { action.requestPath = RdapTestAction.PATH + domainName; action.requestMethod = HEAD; + action.rdapLinkBase = "http://myserver.google.com/"; action.run(); return response.getPayload(); } @@ -124,7 +137,7 @@ public class RdapActionBaseTest { @Test public void testValidName_works() throws Exception { assertThat(generateActualJson("no.thing")).isEqualTo(JSONValue.parse( - "{\"rdapConformance\":[\"rdap_level_0\"], \"key\":\"value\"}")); + loadFileWithSubstitutions(this.getClass(), "rdapjson_toplevel.json", null))); assertThat(response.getStatus()).isEqualTo(200); } diff --git a/javatests/com/google/domain/registry/rdap/RdapDomainActionTest.java b/javatests/com/google/domain/registry/rdap/RdapDomainActionTest.java index 3fa90a48f..efd22c5bb 100644 --- a/javatests/com/google/domain/registry/rdap/RdapDomainActionTest.java +++ b/javatests/com/google/domain/registry/rdap/RdapDomainActionTest.java @@ -164,6 +164,12 @@ public class RdapDomainActionTest { if (!map.containsKey("rdapConformance")) { builder.put("rdapConformance", ImmutableList.of("rdap_level_0")); } + if (!map.containsKey("notices")) { + RdapTestHelper.addTermsOfServiceNotice(builder, "https://example.com/rdap/"); + } + if (!map.containsKey("remarks")) { + RdapTestHelper.addDomainBoilerplateRemarks(builder); + } if (!map.containsKey("port43")) { builder.put("port43", "whois.example.com"); } diff --git a/javatests/com/google/domain/registry/rdap/RdapDomainSearchActionTest.java b/javatests/com/google/domain/registry/rdap/RdapDomainSearchActionTest.java index ec574aad4..47101a6b3 100644 --- a/javatests/com/google/domain/registry/rdap/RdapDomainSearchActionTest.java +++ b/javatests/com/google/domain/registry/rdap/RdapDomainSearchActionTest.java @@ -210,6 +210,8 @@ public class RdapDomainSearchActionTest { ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); builder.put("domainSearchResults", ImmutableList.of(obj)); builder.put("rdapConformance", ImmutableList.of("rdap_level_0")); + RdapTestHelper.addTermsOfServiceNotice(builder, "https://example.com/rdap/"); + RdapTestHelper.addDomainBoilerplateRemarks(builder); return builder.build(); } diff --git a/javatests/com/google/domain/registry/rdap/RdapEntityActionTest.java b/javatests/com/google/domain/registry/rdap/RdapEntityActionTest.java index 6dfec7633..c916a6147 100644 --- a/javatests/com/google/domain/registry/rdap/RdapEntityActionTest.java +++ b/javatests/com/google/domain/registry/rdap/RdapEntityActionTest.java @@ -181,6 +181,12 @@ public class RdapEntityActionTest { if (!map.containsKey("port43")) { builder.put("port43", "whois.example.tld"); } + if (!map.containsKey("notices")) { + RdapTestHelper.addTermsOfServiceNotice(builder, "https://example.com/rdap/"); + } + if (!map.containsKey("remarks")) { + RdapTestHelper.addNonDomainBoilerplateRemarks(builder); + } obj = builder.build(); } return obj; diff --git a/javatests/com/google/domain/registry/rdap/RdapEntitySearchActionTest.java b/javatests/com/google/domain/registry/rdap/RdapEntitySearchActionTest.java index a0626f7ba..9584249b2 100644 --- a/javatests/com/google/domain/registry/rdap/RdapEntitySearchActionTest.java +++ b/javatests/com/google/domain/registry/rdap/RdapEntitySearchActionTest.java @@ -166,6 +166,8 @@ public class RdapEntitySearchActionTest { ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); builder.put("entitySearchResults", ImmutableList.of(obj)); builder.put("rdapConformance", ImmutableList.of("rdap_level_0")); + RdapTestHelper.addTermsOfServiceNotice(builder, "https://example.com/rdap/"); + RdapTestHelper.addNonDomainBoilerplateRemarks(builder); return builder.build(); } diff --git a/javatests/com/google/domain/registry/rdap/RdapHelpActionTest.java b/javatests/com/google/domain/registry/rdap/RdapHelpActionTest.java index b5a461081..ccdea5107 100644 --- a/javatests/com/google/domain/registry/rdap/RdapHelpActionTest.java +++ b/javatests/com/google/domain/registry/rdap/RdapHelpActionTest.java @@ -109,7 +109,8 @@ public class RdapHelpActionTest { @Test public void testHelpActionTos_works() throws Exception { - generateActualJson("/tos"); + assertThat(generateActualJson("/tos")) + .isEqualTo(generateExpectedJson("", "rdap_help_tos.json")); assertThat(response.getStatus()).isEqualTo(200); } } diff --git a/javatests/com/google/domain/registry/rdap/RdapJsonFormatterTest.java b/javatests/com/google/domain/registry/rdap/RdapJsonFormatterTest.java index 900923b18..77629dd47 100644 --- a/javatests/com/google/domain/registry/rdap/RdapJsonFormatterTest.java +++ b/javatests/com/google/domain/registry/rdap/RdapJsonFormatterTest.java @@ -187,32 +187,36 @@ public class RdapJsonFormatterTest { @Test public void testRegistrar() throws Exception { - assertThat(RdapJsonFormatter.makeRdapJsonForRegistrar(registrar, LINK_BASE, WHOIS_SERVER)) - .isEqualTo(loadJson("rdapjson_registrar.json")); + assertThat( + RdapJsonFormatter.makeRdapJsonForRegistrar(registrar, false, LINK_BASE, WHOIS_SERVER)) + .isEqualTo(loadJson("rdapjson_registrar.json")); } @Test public void testHost_ipv4() throws Exception { - assertThat(RdapJsonFormatter.makeRdapJsonForHost(hostResourceIpv4, LINK_BASE, WHOIS_SERVER)) - .isEqualTo(loadJson("rdapjson_host_ipv4.json")); + assertThat( + RdapJsonFormatter.makeRdapJsonForHost(hostResourceIpv4, false, LINK_BASE, WHOIS_SERVER)) + .isEqualTo(loadJson("rdapjson_host_ipv4.json")); } @Test public void testHost_ipv6() throws Exception { - assertThat(RdapJsonFormatter.makeRdapJsonForHost(hostResourceIpv6, LINK_BASE, WHOIS_SERVER)) - .isEqualTo(loadJson("rdapjson_host_ipv6.json")); + assertThat( + RdapJsonFormatter.makeRdapJsonForHost(hostResourceIpv6, false, LINK_BASE, WHOIS_SERVER)) + .isEqualTo(loadJson("rdapjson_host_ipv6.json")); } @Test public void testHost_both() throws Exception { - assertThat(RdapJsonFormatter.makeRdapJsonForHost(hostResourceBoth, LINK_BASE, WHOIS_SERVER)) - .isEqualTo(loadJson("rdapjson_host_both.json")); + assertThat( + RdapJsonFormatter.makeRdapJsonForHost(hostResourceBoth, false, LINK_BASE, WHOIS_SERVER)) + .isEqualTo(loadJson("rdapjson_host_both.json")); } @Test public void testHost_noAddresses() throws Exception { assertThat(RdapJsonFormatter.makeRdapJsonForHost( - hostResourceNoAddresses, LINK_BASE, WHOIS_SERVER)) + hostResourceNoAddresses, false, LINK_BASE, WHOIS_SERVER)) .isEqualTo(loadJson("rdapjson_host_no_addresses.json")); } @@ -221,6 +225,7 @@ public class RdapJsonFormatterTest { assertThat( RdapJsonFormatter.makeRdapJsonForContact( contactResourceRegistrant, + false, Optional.of(DesignatedContact.Type.REGISTRANT), LINK_BASE, WHOIS_SERVER)) @@ -232,6 +237,7 @@ public class RdapJsonFormatterTest { assertThat( RdapJsonFormatter.makeRdapJsonForContact( contactResourceRegistrant, + false, Optional.of(DesignatedContact.Type.REGISTRANT), LINK_BASE_NO_TRAILING_SLASH, WHOIS_SERVER)) @@ -243,6 +249,7 @@ public class RdapJsonFormatterTest { assertThat( RdapJsonFormatter.makeRdapJsonForContact( contactResourceRegistrant, + false, Optional.of(DesignatedContact.Type.REGISTRANT), null, WHOIS_SERVER)) @@ -254,6 +261,7 @@ public class RdapJsonFormatterTest { assertThat( RdapJsonFormatter.makeRdapJsonForContact( contactResourceAdmin, + false, Optional.of(DesignatedContact.Type.ADMIN), LINK_BASE, WHOIS_SERVER)) @@ -265,6 +273,7 @@ public class RdapJsonFormatterTest { assertThat( RdapJsonFormatter.makeRdapJsonForContact( contactResourceTech, + false, Optional.of(DesignatedContact.Type.TECH), LINK_BASE, WHOIS_SERVER)) @@ -274,7 +283,8 @@ public class RdapJsonFormatterTest { @Test public void testDomain_full() throws Exception { assertThat( - RdapJsonFormatter.makeRdapJsonForDomain(domainResourceFull, LINK_BASE, WHOIS_SERVER)) + RdapJsonFormatter.makeRdapJsonForDomain( + domainResourceFull, false, LINK_BASE, WHOIS_SERVER)) .isEqualTo(loadJson("rdapjson_domain_full.json")); } @@ -282,7 +292,7 @@ public class RdapJsonFormatterTest { public void testDomain_noRegistrant() throws Exception { assertThat( RdapJsonFormatter.makeRdapJsonForDomain( - domainResourceNoRegistrant, LINK_BASE, WHOIS_SERVER)) + domainResourceNoRegistrant, false, LINK_BASE, WHOIS_SERVER)) .isEqualTo(loadJson("rdapjson_domain_no_registrant.json")); } @@ -290,7 +300,7 @@ public class RdapJsonFormatterTest { public void testDomain_noContacts() throws Exception { assertThat( RdapJsonFormatter.makeRdapJsonForDomain( - domainResourceNoContacts, LINK_BASE, WHOIS_SERVER)) + domainResourceNoContacts, false, LINK_BASE, WHOIS_SERVER)) .isEqualTo(loadJson("rdapjson_domain_no_contacts.json")); } @@ -298,7 +308,7 @@ public class RdapJsonFormatterTest { public void testDomain_noNameservers() throws Exception { assertThat( RdapJsonFormatter.makeRdapJsonForDomain( - domainResourceNoNameservers, LINK_BASE, WHOIS_SERVER)) + domainResourceNoNameservers, false, LINK_BASE, WHOIS_SERVER)) .isEqualTo(loadJson("rdapjson_domain_no_nameservers.json")); } @@ -375,8 +385,49 @@ public class RdapJsonFormatterTest { @Test public void testTopLevel() throws Exception { - assertThat( - RdapJsonFormatter.makeFinalRdapJson(ImmutableMap.of("key", "value"))) - .isEqualTo(loadJson("rdapjson_toplevel.json")); + ImmutableMap.Builder builder = ImmutableMap.builder(); + builder.put("key", "value"); + RdapJsonFormatter.addTopLevelEntries( + builder, + RdapJsonFormatter.BoilerplateType.OTHER, + null, + LINK_BASE); + assertThat(builder.build()).isEqualTo(loadJson("rdapjson_toplevel.json")); + } + + @Test + public void testTopLevel_withTermsOfService() throws Exception { + ImmutableMap.Builder builder = ImmutableMap.builder(); + builder.put("key", "value"); + RdapJsonFormatter.addTopLevelEntries( + builder, + RdapJsonFormatter.BoilerplateType.OTHER, + ImmutableList.of(RdapHelpAction.getJsonHelpNotice("/tos", LINK_BASE)), + LINK_BASE); + assertThat(builder.build()).isEqualTo(loadJson("rdapjson_toplevel.json")); + } + + @Test + public void testTopLevel_domain() throws Exception { + ImmutableMap.Builder builder = ImmutableMap.builder(); + builder.put("key", "value"); + RdapJsonFormatter.addTopLevelEntries( + builder, + RdapJsonFormatter.BoilerplateType.DOMAIN, + null, + LINK_BASE); + assertThat(builder.build()).isEqualTo(loadJson("rdapjson_toplevel_domain.json")); + } + + @Test + public void testTopLevel_domainWithTermsOfService() throws Exception { + ImmutableMap.Builder builder = ImmutableMap.builder(); + builder.put("key", "value"); + RdapJsonFormatter.addTopLevelEntries( + builder, + RdapJsonFormatter.BoilerplateType.DOMAIN, + ImmutableList.of(RdapHelpAction.getJsonHelpNotice("/tos", LINK_BASE)), + LINK_BASE); + assertThat(builder.build()).isEqualTo(loadJson("rdapjson_toplevel_domain.json")); } } diff --git a/javatests/com/google/domain/registry/rdap/RdapNameserverActionTest.java b/javatests/com/google/domain/registry/rdap/RdapNameserverActionTest.java index c3415727e..9df434690 100644 --- a/javatests/com/google/domain/registry/rdap/RdapNameserverActionTest.java +++ b/javatests/com/google/domain/registry/rdap/RdapNameserverActionTest.java @@ -128,6 +128,12 @@ public class RdapNameserverActionTest { if (!map.containsKey("port43")) { builder.put("port43", "whois.example.tld"); } + if (!map.containsKey("notices")) { + RdapTestHelper.addTermsOfServiceNotice(builder, "https://example.tld/rdap/"); + } + if (!map.containsKey("remarks")) { + RdapTestHelper.addNonDomainBoilerplateRemarks(builder); + } obj = builder.build(); } return obj; diff --git a/javatests/com/google/domain/registry/rdap/RdapNameserverSearchActionTest.java b/javatests/com/google/domain/registry/rdap/RdapNameserverSearchActionTest.java index 58a987b1f..29e7c1065 100644 --- a/javatests/com/google/domain/registry/rdap/RdapNameserverSearchActionTest.java +++ b/javatests/com/google/domain/registry/rdap/RdapNameserverSearchActionTest.java @@ -177,6 +177,8 @@ public class RdapNameserverSearchActionTest { ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); builder.put("nameserverSearchResults", ImmutableList.of(obj)); builder.put("rdapConformance", ImmutableList.of("rdap_level_0")); + RdapTestHelper.addTermsOfServiceNotice(builder, "https://example.tld/rdap/"); + RdapTestHelper.addNonDomainBoilerplateRemarks(builder); return builder.build(); } diff --git a/javatests/com/google/domain/registry/rdap/RdapTestHelper.java b/javatests/com/google/domain/registry/rdap/RdapTestHelper.java new file mode 100644 index 000000000..ccfddbace --- /dev/null +++ b/javatests/com/google/domain/registry/rdap/RdapTestHelper.java @@ -0,0 +1,103 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.domain.registry.rdap; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +public class RdapTestHelper { + + static void addTermsOfServiceNotice( + ImmutableMap.Builder builder, String linkBase) { + builder.put("notices", + ImmutableList.of( + ImmutableMap.of( + "title", "RDAP Terms of Service", + "description", ImmutableList.of( + "By querying our Domain Database, you are agreeing to comply with these terms" + + " so please read them carefully.", + "Any information provided is 'as is' without any guarantee of accuracy.", + "Please do not misuse the Domain Database. It is intended solely for" + + " query-based access.", + "Don't use the Domain Database to allow, enable, or otherwise support the" + + " transmission of mass unsolicited, commercial advertising or" + + " solicitations.", + "Don't access our Domain Database through the use of high volume, automated" + + " electronic processes that send queries or data to the systems of" + + " Charleston Road Registry or any ICANN-accredited registrar.", + "You may only use the information contained in the Domain Database for lawful" + + " purposes.", + "Do not compile, repackage, disseminate, or otherwise use the information" + + " contained in the Domain Database in its entirety, or in any substantial" + + " portion, without our prior written permission.", + "We may retain certain details about queries to our Domain Database for the" + + " purposes of detecting and preventing misuse.", + "We reserve the right to restrict or deny your access to the database if we" + + " suspect that you have failed to comply with these terms.", + "We reserve the right to modify this agreement at any time."), + "links", ImmutableList.of( + ImmutableMap.of( + "value", linkBase + "help/tos", + "rel", "alternate", + "href", "https://www.registry.google/about/rdap/tos.html", + "type", "text/html"))))); + } + + static void addNonDomainBoilerplateRemarks(ImmutableMap.Builder builder) { + builder.put("remarks", + ImmutableList.of( + ImmutableMap.of( + "description", + ImmutableList.of( + "This response conforms to the RDAP Operational Profile for gTLD Registries and" + + " Registrars version 1.0")))); + } + + static void addDomainBoilerplateRemarks(ImmutableMap.Builder builder) { + builder.put("remarks", + ImmutableList.of( + ImmutableMap.of( + "description", + ImmutableList.of( + "This response conforms to the RDAP Operational Profile for gTLD Registries and" + + " Registrars version 1.0")), + ImmutableMap.of( + "title", + "EPP Status Codes", + "description", + ImmutableList.of( + "For more information on domain status codes, please visit" + + " https://icann.org/epp"), + "links", + ImmutableList.of( + ImmutableMap.of( + "value", "https://icann.org/epp", + "rel", "alternate", + "href", "https://icann.org/epp", + "type", "text/html"))), + ImmutableMap.of( + "description", + ImmutableList.of( + "URL of the ICANN Whois Inaccuracy Complaint Form: https://www.icann.org/wicf"), + "links", + ImmutableList.of( + ImmutableMap.of( + "value", "https://www.icann.org/wicf", + "rel", "alternate", + "href", "https://www.icann.org/wicf", + "type", "text/html"))))); + } +} + diff --git a/javatests/com/google/domain/registry/rdap/testdata/rdap_help_index.json b/javatests/com/google/domain/registry/rdap/testdata/rdap_help_index.json index ecfeddaf2..63b916c77 100644 --- a/javatests/com/google/domain/registry/rdap/testdata/rdap_help_index.json +++ b/javatests/com/google/domain/registry/rdap/testdata/rdap_help_index.json @@ -19,6 +19,40 @@ "href" : "https://www.registry.google/about/rdap/index.html" } ] + }, + { + "title" : "RDAP Terms of Service", + "description" : + [ + "By querying our Domain Database, you are agreeing to comply with these terms so please read them carefully.", + "Any information provided is 'as is' without any guarantee of accuracy.", + "Please do not misuse the Domain Database. It is intended solely for query-based access.", + "Don't use the Domain Database to allow, enable, or otherwise support the transmission of mass unsolicited, commercial advertising or solicitations.", + "Don't access our Domain Database through the use of high volume, automated electronic processes that send queries or data to the systems of Charleston Road Registry or any ICANN-accredited registrar.", + "You may only use the information contained in the Domain Database for lawful purposes.", + "Do not compile, repackage, disseminate, or otherwise use the information contained in the Domain Database in its entirety, or in any substantial portion, without our prior written permission.", + "We may retain certain details about queries to our Domain Database for the purposes of detecting and preventing misuse.", + "We reserve the right to restrict or deny your access to the database if we suspect that you have failed to comply with these terms.", + "We reserve the right to modify this agreement at any time." + ], + "links" : + [ + { + "value" : "https://example.tld/rdap/help/tos", + "rel" : "alternate", + "href" : "https://www.registry.google/about/rdap/tos.html", + "type" : "text/html" + } + ] + } + ], + "remarks" : + [ + { + "description" : + [ + "This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars version 1.0" + ] } ] } diff --git a/javatests/com/google/domain/registry/rdap/testdata/rdap_help_tos.json b/javatests/com/google/domain/registry/rdap/testdata/rdap_help_tos.json new file mode 100644 index 000000000..54dd9823f --- /dev/null +++ b/javatests/com/google/domain/registry/rdap/testdata/rdap_help_tos.json @@ -0,0 +1,40 @@ +{ + "rdapConformance" : ["rdap_level_0"], + "notices" : + [ + { + "title" : "RDAP Terms of Service", + "description" : + [ + "By querying our Domain Database, you are agreeing to comply with these terms so please read them carefully.", + "Any information provided is 'as is' without any guarantee of accuracy.", + "Please do not misuse the Domain Database. It is intended solely for query-based access.", + "Don't use the Domain Database to allow, enable, or otherwise support the transmission of mass unsolicited, commercial advertising or solicitations.", + "Don't access our Domain Database through the use of high volume, automated electronic processes that send queries or data to the systems of Charleston Road Registry or any ICANN-accredited registrar.", + "You may only use the information contained in the Domain Database for lawful purposes.", + "Do not compile, repackage, disseminate, or otherwise use the information contained in the Domain Database in its entirety, or in any substantial portion, without our prior written permission.", + "We may retain certain details about queries to our Domain Database for the purposes of detecting and preventing misuse.", + "We reserve the right to restrict or deny your access to the database if we suspect that you have failed to comply with these terms.", + "We reserve the right to modify this agreement at any time." + ], + "links" : + [ + { + "value" : "https://example.tld/rdap/help/tos", + "rel" : "alternate", + "href" : "https://www.registry.google/about/rdap/tos.html", + "type" : "text/html" + } + ] + } + ], + "remarks" : + [ + { + "description" : + [ + "This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars version 1.0" + ] + } + ] +} diff --git a/javatests/com/google/domain/registry/rdap/testdata/rdap_multiple_contacts.json b/javatests/com/google/domain/registry/rdap/testdata/rdap_multiple_contacts.json index 718b5de01..a2b9b47bc 100644 --- a/javatests/com/google/domain/registry/rdap/testdata/rdap_multiple_contacts.json +++ b/javatests/com/google/domain/registry/rdap/testdata/rdap_multiple_contacts.json @@ -120,5 +120,42 @@ "port43": "whois.example.tld" } ], - "rdapConformance": [ "rdap_level_0" ] + "rdapConformance": [ "rdap_level_0" ], + "notices" : + [ + { + "title" : "RDAP Terms of Service", + "description" : + [ + "By querying our Domain Database, you are agreeing to comply with these terms so please read them carefully.", + "Any information provided is 'as is' without any guarantee of accuracy.", + "Please do not misuse the Domain Database. It is intended solely for query-based access.", + "Don't use the Domain Database to allow, enable, or otherwise support the transmission of mass unsolicited, commercial advertising or solicitations.", + "Don't access our Domain Database through the use of high volume, automated electronic processes that send queries or data to the systems of Charleston Road Registry or any ICANN-accredited registrar.", + "You may only use the information contained in the Domain Database for lawful purposes.", + "Do not compile, repackage, disseminate, or otherwise use the information contained in the Domain Database in its entirety, or in any substantial portion, without our prior written permission.", + "We may retain certain details about queries to our Domain Database for the purposes of detecting and preventing misuse.", + "We reserve the right to restrict or deny your access to the database if we suspect that you have failed to comply with these terms.", + "We reserve the right to modify this agreement at any time." + ], + "links" : + [ + { + "value" : "https://example.com/rdap/help/tos", + "rel" : "alternate", + "href" : "https://www.registry.google/about/rdap/tos.html", + "type" : "text/html" + } + ] + } + ], + "remarks" : + [ + { + "description" : + [ + "This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars version 1.0" + ] + } + ] } diff --git a/javatests/com/google/domain/registry/rdap/testdata/rdap_multiple_domains.json b/javatests/com/google/domain/registry/rdap/testdata/rdap_multiple_domains.json index b94207d19..04cc2dcff 100644 --- a/javatests/com/google/domain/registry/rdap/testdata/rdap_multiple_domains.json +++ b/javatests/com/google/domain/registry/rdap/testdata/rdap_multiple_domains.json @@ -621,5 +621,73 @@ ], "rdapConformance": [ "rdap_level_0" + ], + "notices" : + [ + { + "title" : "RDAP Terms of Service", + "description" : + [ + "By querying our Domain Database, you are agreeing to comply with these terms so please read them carefully.", + "Any information provided is 'as is' without any guarantee of accuracy.", + "Please do not misuse the Domain Database. It is intended solely for query-based access.", + "Don't use the Domain Database to allow, enable, or otherwise support the transmission of mass unsolicited, commercial advertising or solicitations.", + "Don't access our Domain Database through the use of high volume, automated electronic processes that send queries or data to the systems of Charleston Road Registry or any ICANN-accredited registrar.", + "You may only use the information contained in the Domain Database for lawful purposes.", + "Do not compile, repackage, disseminate, or otherwise use the information contained in the Domain Database in its entirety, or in any substantial portion, without our prior written permission.", + "We may retain certain details about queries to our Domain Database for the purposes of detecting and preventing misuse.", + "We reserve the right to restrict or deny your access to the database if we suspect that you have failed to comply with these terms.", + "We reserve the right to modify this agreement at any time." + ], + "links" : + [ + { + "value" : "https://example.com/rdap/help/tos", + "rel" : "alternate", + "href" : "https://www.registry.google/about/rdap/tos.html", + "type" : "text/html" + } + ] + } + ], + "remarks" : + [ + { + "description" : + [ + "This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars version 1.0" + ] + }, + { + "title" : "EPP Status Codes", + "description" : + [ + "For more information on domain status codes, please visit https://icann.org/epp" + ], + "links" : + [ + { + "value" : "https://icann.org/epp", + "rel" : "alternate", + "href" : "https://icann.org/epp", + "type" : "text/html" + } + ] + }, + { + "description" : + [ + "URL of the ICANN Whois Inaccuracy Complaint Form: https://www.icann.org/wicf" + ], + "links" : + [ + { + "value" : "https://www.icann.org/wicf", + "rel" : "alternate", + "href" : "https://www.icann.org/wicf", + "type" : "text/html" + } + ] + } ] } diff --git a/javatests/com/google/domain/registry/rdap/testdata/rdap_multiple_hosts.json b/javatests/com/google/domain/registry/rdap/testdata/rdap_multiple_hosts.json index f233f7386..f25319b8c 100644 --- a/javatests/com/google/domain/registry/rdap/testdata/rdap_multiple_hosts.json +++ b/javatests/com/google/domain/registry/rdap/testdata/rdap_multiple_hosts.json @@ -43,5 +43,42 @@ "port43": "whois.example.tld" } ], - "rdapConformance" : ["rdap_level_0"] + "rdapConformance" : ["rdap_level_0"], + "notices" : + [ + { + "title" : "RDAP Terms of Service", + "description" : + [ + "By querying our Domain Database, you are agreeing to comply with these terms so please read them carefully.", + "Any information provided is 'as is' without any guarantee of accuracy.", + "Please do not misuse the Domain Database. It is intended solely for query-based access.", + "Don't use the Domain Database to allow, enable, or otherwise support the transmission of mass unsolicited, commercial advertising or solicitations.", + "Don't access our Domain Database through the use of high volume, automated electronic processes that send queries or data to the systems of Charleston Road Registry or any ICANN-accredited registrar.", + "You may only use the information contained in the Domain Database for lawful purposes.", + "Do not compile, repackage, disseminate, or otherwise use the information contained in the Domain Database in its entirety, or in any substantial portion, without our prior written permission.", + "We may retain certain details about queries to our Domain Database for the purposes of detecting and preventing misuse.", + "We reserve the right to restrict or deny your access to the database if we suspect that you have failed to comply with these terms.", + "We reserve the right to modify this agreement at any time." + ], + "links" : + [ + { + "value" : "https://example.tld/rdap/help/tos", + "rel" : "alternate", + "href" : "https://www.registry.google/about/rdap/tos.html", + "type" : "text/html" + } + ] + } + ], + "remarks" : + [ + { + "description" : + [ + "This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars version 1.0" + ] + } + ] } diff --git a/javatests/com/google/domain/registry/rdap/testdata/rdapjson_toplevel.json b/javatests/com/google/domain/registry/rdap/testdata/rdapjson_toplevel.json index 323efb662..9d681a921 100644 --- a/javatests/com/google/domain/registry/rdap/testdata/rdapjson_toplevel.json +++ b/javatests/com/google/domain/registry/rdap/testdata/rdapjson_toplevel.json @@ -3,5 +3,42 @@ "rdapConformance" : [ "rdap_level_0" + ], + "notices" : + [ + { + "title" : "RDAP Terms of Service", + "description" : + [ + "By querying our Domain Database, you are agreeing to comply with these terms so please read them carefully.", + "Any information provided is 'as is' without any guarantee of accuracy.", + "Please do not misuse the Domain Database. It is intended solely for query-based access.", + "Don't use the Domain Database to allow, enable, or otherwise support the transmission of mass unsolicited, commercial advertising or solicitations.", + "Don't access our Domain Database through the use of high volume, automated electronic processes that send queries or data to the systems of Charleston Road Registry or any ICANN-accredited registrar.", + "You may only use the information contained in the Domain Database for lawful purposes.", + "Do not compile, repackage, disseminate, or otherwise use the information contained in the Domain Database in its entirety, or in any substantial portion, without our prior written permission.", + "We may retain certain details about queries to our Domain Database for the purposes of detecting and preventing misuse.", + "We reserve the right to restrict or deny your access to the database if we suspect that you have failed to comply with these terms.", + "We reserve the right to modify this agreement at any time." + ], + "links" : + [ + { + "value" : "http://myserver.google.com/help/tos", + "rel" : "alternate", + "href" : "https://www.registry.google/about/rdap/tos.html", + "type" : "text/html" + } + ] + } + ], + "remarks" : + [ + { + "description" : + [ + "This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars version 1.0" + ] + } ] } diff --git a/javatests/com/google/domain/registry/rdap/testdata/rdapjson_toplevel_domain.json b/javatests/com/google/domain/registry/rdap/testdata/rdapjson_toplevel_domain.json new file mode 100644 index 000000000..ffceea619 --- /dev/null +++ b/javatests/com/google/domain/registry/rdap/testdata/rdapjson_toplevel_domain.json @@ -0,0 +1,75 @@ +{ + "key" : "value", + "rdapConformance" : + [ + "rdap_level_0" + ], + "notices" : + [ + { + "title" : "RDAP Terms of Service", + "description" : + [ + "By querying our Domain Database, you are agreeing to comply with these terms so please read them carefully.", + "Any information provided is 'as is' without any guarantee of accuracy.", + "Please do not misuse the Domain Database. It is intended solely for query-based access.", + "Don't use the Domain Database to allow, enable, or otherwise support the transmission of mass unsolicited, commercial advertising or solicitations.", + "Don't access our Domain Database through the use of high volume, automated electronic processes that send queries or data to the systems of Charleston Road Registry or any ICANN-accredited registrar.", + "You may only use the information contained in the Domain Database for lawful purposes.", + "Do not compile, repackage, disseminate, or otherwise use the information contained in the Domain Database in its entirety, or in any substantial portion, without our prior written permission.", + "We may retain certain details about queries to our Domain Database for the purposes of detecting and preventing misuse.", + "We reserve the right to restrict or deny your access to the database if we suspect that you have failed to comply with these terms.", + "We reserve the right to modify this agreement at any time." + ], + "links" : + [ + { + "value" : "http://myserver.google.com/help/tos", + "rel" : "alternate", + "href" : "https://www.registry.google/about/rdap/tos.html", + "type" : "text/html" + } + ] + } + ], + "remarks" : + [ + { + "description" : + [ + "This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars version 1.0" + ] + }, + { + "title" : "EPP Status Codes", + "description" : + [ + "For more information on domain status codes, please visit https://icann.org/epp" + ], + "links" : + [ + { + "value" : "https://icann.org/epp", + "rel" : "alternate", + "href" : "https://icann.org/epp", + "type" : "text/html" + } + ] + }, + { + "description" : + [ + "URL of the ICANN Whois Inaccuracy Complaint Form: https://www.icann.org/wicf" + ], + "links" : + [ + { + "value" : "https://www.icann.org/wicf", + "rel" : "alternate", + "href" : "https://www.icann.org/wicf", + "type" : "text/html" + } + ] + } + ] +}