RDAP: Add boilerplate entries required by ICANN RDAP Profile

The ICANN RDAP Profile (dated 3 December 2015) requires certain boilerplate entries at the top level of the JSON object. Specifically:

1.4.4. The terms of service of the RDAP service MUST be specified in the
notices object in the topmost JSON object of the response. The notices
object MUST contain a links object [RFC7483]. The links object MUST
contain an URL of the contracted party providing the RDAP service.

1.4.10. An RDAP response MUST contain a remarks member with a description
containing the string “This response conforms to the RDAP Operational
Profile for gTLD Registries and Registrars version 1.0”.

1.5.18. A domain name RDAP response MUST contain a remarks member with
a title “EPP Status Codes”, a description containing the string “For
more information on domain status codes, please visit
https://icann.org/epp” and a links member with the
https://icann.org/epp URL.

1.5.20. A domain name RDAP response MUST contain a remarks member with
a title “Whois Inaccuracy Complaint Form”, a description containing
the string “URL of the ICANN Whois Inaccuracy Complaint Form:
https://www.icann.org/wicf” and a links member with the
https://www.icann.org/wicf URL.
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=116389950
This commit is contained in:
mountford 2016-03-04 12:36:00 -08:00 committed by Justine Tunney
parent 363c812d10
commit ab26b288c1
29 changed files with 804 additions and 90 deletions

View file

@ -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<String, Object> 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<String, Object> 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<String, Object> 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) {

View file

@ -48,8 +48,8 @@ public class RdapAutnumAction extends RdapActionBase {
}
@Override
public ImmutableMap<String, Object> getJsonObjectForResource(String pathSearchString)
throws HttpException {
public ImmutableMap<String, Object> getJsonObjectForResource(
String pathSearchString, boolean isHeadRequest, String linkBase) throws HttpException {
throw new NotImplementedException("Domain Name Registry information only");
}
}

View file

@ -49,8 +49,8 @@ public class RdapDomainAction extends RdapActionBase {
}
@Override
public ImmutableMap<String, Object> getJsonObjectForResource(String pathSearchString)
throws HttpException {
public ImmutableMap<String, Object> 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);
}
}

View file

@ -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<String, Object>
getJsonObjectForResource(final String pathSearchString) throws HttpException {
public ImmutableMap<String, Object> 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.<String, Object>of("domainSearchResults", results);
ImmutableMap.Builder<String, Object> 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<DomainResource> 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();

View file

@ -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<String, Object> getJsonObjectForResource(String pathSearchString) {
public ImmutableMap<String, Object> 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.<DesignatedContact.Type>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

View file

@ -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<String, Object>
getJsonObjectForResource(final String pathSearchString) throws HttpException {
public ImmutableMap<String, Object> 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.<String, Object>of("entitySearchResults", results);
ImmutableMap.Builder<String, Object> 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.<DesignatedContact.Type>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.<DesignatedContact.Type>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.

View file

@ -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<String, MakeRdapJsonNoticeParameters> 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<String, Object> getJsonObjectForResource(String pathSearchString)
throws HttpException {
public ImmutableMap<String, Object> 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<String, Object> builder = new ImmutableMap.Builder<>();
RdapJsonFormatter.addTopLevelEntries(
builder,
BoilerplateType.OTHER,
ImmutableList.of(getJsonHelpNotice(pathSearchString, rdapLinkBase)),
rdapLinkBase);
return builder.build();
}
static ImmutableMap<String, Object> 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);
}

View file

@ -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<String, Object> CONFORMANCE_REMARK =
ImmutableMap.<String, Object>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<String, Object> DOMAIN_STATUS_CODES_REMARK =
ImmutableMap.<String, Object> 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<String, Object> INACCURACY_COMPLAINT_FORM_REMARK =
ImmutableMap.<String, Object> 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<ImmutableMap<String, Object>> domainBoilerplateRemarks =
ImmutableList.of(
CONFORMANCE_REMARK, DOMAIN_STATUS_CODES_REMARK, INACCURACY_COMPLAINT_FORM_REMARK);
/** Boilerplate remarks required by non-domain responses. */
static final ImmutableList<ImmutableMap<String, Object>> nonDomainBoilerplateRemarks =
ImmutableList.of(CONFORMANCE_REMARK);
}

View file

@ -49,8 +49,8 @@ public class RdapIpAction extends RdapActionBase {
}
@Override
public ImmutableMap<String, Object> getJsonObjectForResource(String pathSearchString)
throws HttpException {
public ImmutableMap<String, Object> getJsonObjectForResource(
String pathSearchString, boolean isHeadRequest, String linkBase) throws HttpException {
throw new NotImplementedException("Domain Name Registry information only");
}
}

View file

@ -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<String, Object> makeFinalRdapJson(
ImmutableMap<String, Object> rdapJson) {
ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>();
builder.putAll(rdapJson);
static void addTopLevelEntries(
ImmutableMap.Builder<String, Object> builder,
BoilerplateType boilerplateType,
@Nullable Iterable<ImmutableMap<String, Object>> notices,
String rdapLinkBase) {
builder.put("rdapConformance", CONFORMANCE_LIST);
return builder.build();
ImmutableList.Builder<ImmutableMap<String, Object>> noticesBuilder = ImmutableList.builder();
ImmutableMap<String, Object> tosNotice =
RdapHelpAction.getJsonHelpNotice(RdapHelpAction.TERMS_OF_SERVICE_PATH, rdapLinkBase);
boolean tosNoticeFound = false;
if (notices != null) {
noticesBuilder.addAll(notices);
for (ImmutableMap<String, Object> 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:
*
* <p>title: the title of the notice; if null, the notice will have no title
*
@ -251,8 +289,8 @@ public class RdapJsonFormatter {
* @see <a href="https://tools.ietf.org/html/rfc7483">
* RFC 7483: JSON Responses for the Registration Data Access Protocol (RDAP)</a>
*/
public static ImmutableMap<String, Object>
makeRdapJsonNotice(MakeRdapJsonNoticeParameters parameters, @Nullable String linkBase) {
static ImmutableMap<String, Object> makeRdapJsonNotice(
MakeRdapJsonNoticeParameters parameters, @Nullable String linkBase) {
ImmutableMap.Builder<String, Object> 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<String, Object> makeRdapJsonForDomain(
DomainResource domainResource, @Nullable String linkBase, @Nullable String whoisServer) {
static ImmutableMap<String, Object> makeRdapJsonForDomain(
DomainResource domainResource,
boolean isTopLevel,
@Nullable String linkBase,
@Nullable String whoisServer) {
// Kick off the database loads of the nameservers that we will need.
Set<Ref<HostResource>> hostRefs = new LinkedHashSet<>();
for (ReferenceUnion<HostResource> hostReference : domainResource.getNameservers()) {
@ -328,7 +369,7 @@ public class RdapJsonFormatter {
ImmutableList.Builder<Object> 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<Object> 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<Object> 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<String, Object> makeRdapJsonForHost(
HostResource hostResource, @Nullable String linkBase, @Nullable String whoisServer) {
static ImmutableMap<String, Object> makeRdapJsonForHost(
HostResource hostResource,
boolean isTopLevel,
@Nullable String linkBase,
@Nullable String whoisServer) {
ImmutableMap.Builder<String, Object> 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<String, Object> makeRdapJsonForContact(
static ImmutableMap<String, Object> makeRdapJsonForContact(
ContactResource contactResource,
boolean isTopLevel,
Optional<DesignatedContact.Type> 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<String, Object> makeRdapJsonForRegistrar(
Registrar registrar, @Nullable String linkBase, @Nullable String whoisServer) {
static ImmutableMap<String, Object> makeRdapJsonForRegistrar(
Registrar registrar,
boolean isTopLevel,
@Nullable String linkBase,
@Nullable String whoisServer) {
ImmutableMap.Builder<String, Object> 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<String, Object> makeRdapJsonForRegistrarContact(
static ImmutableMap<String, Object> makeRdapJsonForRegistrarContact(
RegistrarContact registrarContact, @Nullable String whoisServer) {
ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>();
builder.put("objectClassName", "entity");
@ -705,7 +765,7 @@ public class RdapJsonFormatter {
* @see <a href="https://tools.ietf.org/html/rfc7483">
* RFC 7483: JSON Responses for the Registration Data Access Protocol (RDAP)</a>
*/
public static ImmutableMap<String, Object> makeError(
static ImmutableMap<String, Object> makeError(
int status, String title, String description) {
return ImmutableMap.<String, Object>of(
"rdapConformance", CONFORMANCE_LIST,

View file

@ -50,7 +50,7 @@ public class RdapNameserverAction extends RdapActionBase {
@Override
public ImmutableMap<String, Object> 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);
}
}

View file

@ -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<String, Object>
getJsonObjectForResource(final String pathSearchString) throws HttpException {
public ImmutableMap<String, Object> 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.<String, Object>of("nameserverSearchResults", results);
ImmutableMap.Builder<String, Object> 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<ImmutableMap<String, Object>> 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<ImmutableMap<String, Object>> builder = new ImmutableList.Builder<>();
for (HostResource hostResource : query) {
builder.add(
RdapJsonFormatter.makeRdapJsonForHost(hostResource, rdapLinkBase, rdapWhoisServer));
RdapJsonFormatter.makeRdapJsonForHost(
hostResource, false, rdapLinkBase, rdapWhoisServer));
}
return builder.build();
}