Reimplement the RDAP Json creation using Jsonables

Currently we try to reimplemnet the same behavior of the existing code as much
as possible.

We only fix issues that go against the RFC7483, but we don't yet update the
code to follow the latest (15feb19) RDAP Response Profile. That will require a
much bigger change especially for the test files, so it'll wait for a followup
CL.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=246948018
This commit is contained in:
guyben 2019-05-06 20:25:56 -07:00 committed by jianglai
parent e382299212
commit bdc41edd34
85 changed files with 2589 additions and 2367 deletions

View file

@ -1,52 +0,0 @@
// Copyright 2017 The Nomulus Authors. 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 google.registry.config;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import javax.annotation.Nullable;
/**
* AutoValue class describing an RDAP Notice object.
*
* <p>This is used for injecting RDAP help pages.
*/
@AutoValue
public abstract class RdapNoticeDescriptor {
public static Builder builder() {
return new AutoValue_RdapNoticeDescriptor.Builder();
}
@Nullable public abstract String getTitle();
public abstract ImmutableList<String> getDescription();
@Nullable public abstract String getTypeString();
@Nullable public abstract String getLinkValueSuffix();
@Nullable public abstract String getLinkHrefUrlString();
/** Builder class for {@link RdapNoticeDescriptor}. */
@AutoValue.Builder
public abstract static class Builder {
public abstract Builder setTitle(@Nullable String title);
public abstract Builder setDescription(Iterable<String> description);
public abstract Builder setTypeString(@Nullable String typeString);
public abstract Builder setLinkValueSuffix(@Nullable String linkValueSuffix);
public abstract Builder setLinkHrefUrlString(@Nullable String linkHrefUrlString);
public abstract RdapNoticeDescriptor build();
}
}

View file

@ -26,7 +26,6 @@ import com.google.common.base.Splitter;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.base.Supplier; import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
@ -1267,19 +1266,6 @@ public final class RegistryConfig {
return ImmutableList.copyOf(config.credentialOAuth.localCredentialOauthScopes); return ImmutableList.copyOf(config.credentialOAuth.localCredentialOauthScopes);
} }
/**
* Returns the help path for the RDAP terms of service.
*
* <p>Make sure that this path is equal to the key of the entry in the RDAP help map containing
* the terms of service. The ICANN operational profile requires that the TOS be included in all
* responses, and this string is used to find the TOS in the help map.
*/
@Provides
@Config("rdapTosPath")
public static String provideRdapTosPath() {
return "/tos";
}
/** OAuth client ID used by the nomulus tool. */ /** OAuth client ID used by the nomulus tool. */
@Provides @Provides
@Config("toolsClientId") @Config("toolsClientId")
@ -1311,51 +1297,6 @@ public final class RegistryConfig {
public static String provideRdapTosStaticUrl(RegistryConfigSettings config) { public static String provideRdapTosStaticUrl(RegistryConfigSettings config) {
return config.registryPolicy.rdapTosStaticUrl; return config.registryPolicy.rdapTosStaticUrl;
} }
/**
* Returns the help text to be used by RDAP.
*
* <p>Make sure that the map entry for the terms of service use the same key as specified in
* rdapTosPath above.
*/
@Singleton
@Provides
@Config("rdapHelpMap")
public static ImmutableMap<String, RdapNoticeDescriptor> provideRdapHelpMap(
@Config("rdapTos") ImmutableList<String> rdapTos,
@Config("rdapTosStaticUrl") @Nullable String rdapTosStaticUrl) {
return new ImmutableMap.Builder<String, RdapNoticeDescriptor>()
.put(
"/",
RdapNoticeDescriptor.builder()
.setTitle("RDAP Help")
.setDescription(
ImmutableList.of(
"domain/XXXX",
"nameserver/XXXX",
"entity/XXXX",
"domains?name=XXXX",
"domains?nsLdhName=XXXX",
"domains?nsIp=XXXX",
"nameservers?name=XXXX",
"nameservers?ip=XXXX",
"entities?fn=XXXX",
"entities?handle=XXXX",
"help/XXXX"))
.setLinkValueSuffix("help/")
.setLinkHrefUrlString(
"https://github.com/google/nomulus/blob/master/docs/rdap.md")
.build())
.put(
"/tos",
RdapNoticeDescriptor.builder()
.setTitle("RDAP Terms of Service")
.setDescription(rdapTos)
.setLinkValueSuffix("help/tos")
.setLinkHrefUrlString(rdapTosStaticUrl)
.build())
.build();
}
} }
/** /**

View file

@ -360,8 +360,6 @@ abstract class AbstractJsonableObject implements Jsonable {
* *
* <p>If not empty - the resulting list is the allowed names. If the name ends with [], it means * <p>If not empty - the resulting list is the allowed names. If the name ends with [], it means
* the class is an element in a array with this name. * the class is an element in a array with this name.
*
* <p>A name of "*" means this is allowed to merge.
*/ */
static Optional<ImmutableSet<String>> getNameRestriction(Class<?> clazz) { static Optional<ImmutableSet<String>> getNameRestriction(Class<?> clazz) {
// Find the first superclass that has an RestrictJsonNames annotation. // Find the first superclass that has an RestrictJsonNames annotation.

View file

@ -25,11 +25,11 @@ import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR; import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import static javax.servlet.http.HttpServletResponse.SC_OK; import static javax.servlet.http.HttpServletResponse.SC_OK;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.flogger.FluentLogger; import com.google.common.flogger.FluentLogger;
import com.google.common.net.MediaType; import com.google.common.net.MediaType;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.re2j.Pattern; import com.google.re2j.Pattern;
import com.googlecode.objectify.Key; import com.googlecode.objectify.Key;
import com.googlecode.objectify.cmd.Query; import com.googlecode.objectify.cmd.Query;
@ -38,9 +38,12 @@ import google.registry.model.EppResource;
import google.registry.model.registrar.Registrar; import google.registry.model.registrar.Registrar;
import google.registry.rdap.RdapMetrics.EndpointType; import google.registry.rdap.RdapMetrics.EndpointType;
import google.registry.rdap.RdapMetrics.WildcardType; import google.registry.rdap.RdapMetrics.WildcardType;
import google.registry.rdap.RdapObjectClasses.ErrorResponse;
import google.registry.rdap.RdapObjectClasses.ReplyPayloadBase;
import google.registry.rdap.RdapObjectClasses.TopLevelReplyObject;
import google.registry.rdap.RdapSearchResults.BaseSearchResponse;
import google.registry.rdap.RdapSearchResults.IncompletenessWarningType; import google.registry.rdap.RdapSearchResults.IncompletenessWarningType;
import google.registry.request.Action; import google.registry.request.Action;
import google.registry.request.FullServletPath;
import google.registry.request.HttpException; import google.registry.request.HttpException;
import google.registry.request.HttpException.UnprocessableEntityException; import google.registry.request.HttpException.UnprocessableEntityException;
import google.registry.request.Parameter; import google.registry.request.Parameter;
@ -51,7 +54,6 @@ import google.registry.request.auth.AuthResult;
import google.registry.request.auth.AuthenticatedRegistrarAccessor; import google.registry.request.auth.AuthenticatedRegistrarAccessor;
import google.registry.request.auth.UserAuthInfo; import google.registry.request.auth.UserAuthInfo;
import google.registry.util.Clock; import google.registry.util.Clock;
import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.ArrayList; import java.util.ArrayList;
@ -60,7 +62,6 @@ import java.util.Optional;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.json.simple.JSONValue;
/** /**
* Base RDAP (new WHOIS) action for all requests. * Base RDAP (new WHOIS) action for all requests.
@ -92,7 +93,6 @@ public abstract class RdapActionBase implements Runnable {
@Inject Clock clock; @Inject Clock clock;
@Inject @RequestMethod Action.Method requestMethod; @Inject @RequestMethod Action.Method requestMethod;
@Inject @RequestPath String requestPath; @Inject @RequestPath String requestPath;
@Inject @FullServletPath String fullServletPath;
@Inject AuthResult authResult; @Inject AuthResult authResult;
@Inject AuthenticatedRegistrarAccessor registrarAccessor; @Inject AuthenticatedRegistrarAccessor registrarAccessor;
@Inject RdapJsonFormatter rdapJsonFormatter; @Inject RdapJsonFormatter rdapJsonFormatter;
@ -138,7 +138,7 @@ public abstract class RdapActionBase implements Runnable {
* expensive task required to create the map which will never result in a request failure. * expensive task required to create the map which will never result in a request failure.
* @return A map (probably containing nested maps and lists) with the final JSON response data. * @return A map (probably containing nested maps and lists) with the final JSON response data.
*/ */
abstract ImmutableMap<String, Object> getJsonObjectForResource( abstract ReplyPayloadBase getJsonObjectForResource(
String pathSearchString, boolean isHeadRequest); String pathSearchString, boolean isHeadRequest);
@Override @Override
@ -159,12 +159,16 @@ public abstract class RdapActionBase implements Runnable {
checkArgument( checkArgument(
pathProper.startsWith(getActionPath()), pathProper.startsWith(getActionPath()),
"%s doesn't start with %s", pathProper, getActionPath()); "%s doesn't start with %s", pathProper, getActionPath());
ImmutableMap<String, Object> rdapJson = ReplyPayloadBase replyObject =
getJsonObjectForResource( getJsonObjectForResource(
pathProper.substring(getActionPath().length()), requestMethod == Action.Method.HEAD); pathProper.substring(getActionPath().length()), requestMethod == Action.Method.HEAD);
if (replyObject instanceof BaseSearchResponse) {
metricInformationBuilder.setIncompletenessWarningType(
((BaseSearchResponse) replyObject).incompletenessWarningType());
}
response.setStatus(SC_OK); response.setStatus(SC_OK);
response.setContentType(RESPONSE_MEDIA_TYPE); response.setContentType(RESPONSE_MEDIA_TYPE);
setPayload(rdapJson); setPayload(replyObject);
metricInformationBuilder.setStatusCode(SC_OK); metricInformationBuilder.setStatusCode(SC_OK);
} catch (HttpException e) { } catch (HttpException e) {
setError(e.getResponseCode(), e.getResponseCodeString(), e.getMessage()); setError(e.getResponseCode(), e.getResponseCodeString(), e.getMessage());
@ -182,26 +186,29 @@ public abstract class RdapActionBase implements Runnable {
response.setStatus(status); response.setStatus(status);
response.setContentType(RESPONSE_MEDIA_TYPE); response.setContentType(RESPONSE_MEDIA_TYPE);
try { try {
setPayload(rdapJsonFormatter.makeError(status, title, description)); setPayload(ErrorResponse.create(status, title, description));
} catch (Exception ex) { } catch (Exception ex) {
logger.atSevere().withCause(ex).log("Failed to create an error response.");
response.setPayload(""); response.setPayload("");
} }
} }
void setPayload(ImmutableMap<String, Object> rdapJson) { void setPayload(ReplyPayloadBase replyObject) {
if (requestMethod == Action.Method.HEAD) { if (requestMethod == Action.Method.HEAD) {
return; return;
} }
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.disableHtmlEscaping();
if (formatOutputParam.orElse(false)) { if (formatOutputParam.orElse(false)) {
try { gsonBuilder.setPrettyPrinting();
response.setPayload(new JacksonFactory().toPrettyString(rdapJson));
return;
} catch (IOException e) {
logger.atWarning().withCause(e).log(
"Unable to pretty-print RDAP JSON response; falling back to unformatted output.");
} }
} Gson gson = gsonBuilder.create();
response.setPayload(JSONValue.toJSONString(rdapJson));
TopLevelReplyObject topLevelObject =
TopLevelReplyObject.create(replyObject, rdapJsonFormatter.createTosNotice());
response.setPayload(gson.toJson(topLevelObject.toJson()));
} }
RdapAuthorization getAuthorization() { RdapAuthorization getAuthorization() {

View file

@ -17,8 +17,8 @@ package google.registry.rdap;
import static google.registry.request.Action.Method.GET; import static google.registry.request.Action.Method.GET;
import static google.registry.request.Action.Method.HEAD; import static google.registry.request.Action.Method.HEAD;
import com.google.common.collect.ImmutableMap;
import google.registry.rdap.RdapMetrics.EndpointType; import google.registry.rdap.RdapMetrics.EndpointType;
import google.registry.rdap.RdapObjectClasses.ReplyPayloadBase;
import google.registry.request.Action; import google.registry.request.Action;
import google.registry.request.HttpException.NotImplementedException; import google.registry.request.HttpException.NotImplementedException;
import google.registry.request.auth.Auth; import google.registry.request.auth.Auth;
@ -43,8 +43,7 @@ public class RdapAutnumAction extends RdapActionBase {
} }
@Override @Override
public ImmutableMap<String, Object> getJsonObjectForResource( public ReplyPayloadBase getJsonObjectForResource(String pathSearchString, boolean isHeadRequest) {
String pathSearchString, boolean isHeadRequest) {
throw new NotImplementedException("Domain Name Registry information only"); throw new NotImplementedException("Domain Name Registry information only");
} }
} }

View file

@ -0,0 +1,487 @@
// Copyright 2019 The Nomulus Authors. 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 google.registry.rdap;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.google.gson.JsonArray;
import com.google.gson.JsonPrimitive;
import google.registry.rdap.AbstractJsonableObject.RestrictJsonNames;
import java.util.Optional;
import org.joda.time.DateTime;
/**
* Data Structures defined in RFC7483 section 4.
*/
final class RdapDataStructures {
private RdapDataStructures() {}
/**
* RDAP conformance defined in 4.1 of RFC7483.
*/
@RestrictJsonNames("rdapConformance")
static final class RdapConformance implements Jsonable {
static final RdapConformance INSTANCE = new RdapConformance();
private RdapConformance() {}
@Override
public JsonArray toJson() {
JsonArray jsonArray = new JsonArray();
// Conformance to RFC7483
// TODO(b/127490882) check if we need to Add back the rdap_level_0 string, as I think that
// just means we conform to the RFC, which we do
// jsonArray.add("rdap_level_0");
// Conformance to the RDAP Response Profile V2.1
// (see section 1.3)
jsonArray.add("icann_rdap_response_profile_0");
return jsonArray;
}
}
/**
* Links defined in 4.2 of RFC7483.
*/
@RestrictJsonNames("links[]")
@AutoValue
abstract static class Link extends AbstractJsonableObject {
@JsonableElement abstract String href();
@JsonableElement abstract Optional<String> rel();
@JsonableElement abstract Optional<String> hreflang();
@JsonableElement abstract Optional<String> title();
@JsonableElement abstract Optional<String> media();
@JsonableElement abstract Optional<String> type();
@JsonableElement abstract Optional<String> value();
static Builder builder() {
return new AutoValue_RdapDataStructures_Link.Builder();
}
@AutoValue.Builder
abstract static class Builder {
abstract Builder setHref(String href);
abstract Builder setRel(String rel);
abstract Builder setHreflang(String hrefLang);
abstract Builder setTitle(String title);
abstract Builder setMedia(String media);
abstract Builder setType(String type);
abstract Builder setValue(String value);
abstract Link build();
}
}
/**
* Notices and Remarks defined in 4.3 of RFC7483.
*
* <p>Each has an optional "type" denoting a registered type string defined in 10.2.1. The type is
* defined as common to both Notices and Remarks, but each item is only appropriate to one of
* them. So we will divide all the "types" from the RFC to two enums - one for Notices and one for
* Remarks.
*/
private abstract static class NoticeOrRemark extends AbstractJsonableObject {
@JsonableElement abstract Optional<String> title();
@JsonableElement abstract ImmutableList<String> description();
@JsonableElement abstract ImmutableList<Link> links();
abstract static class Builder<B extends Builder<?>> {
abstract B setTitle(String title);
abstract B setDescription(ImmutableList<String> description);
abstract B setDescription(String... description);
abstract ImmutableList.Builder<Link> linksBuilder();
@SuppressWarnings("unchecked")
B addLink(Link link) {
linksBuilder().add(link);
return (B) this;
}
}
}
/**
* Notices defined in 4.3 of RFC7483.
*
* <p>A notice denotes information about the service itself or the entire response, and hence will
* only be in the top-most object.
*/
@AutoValue
@RestrictJsonNames("notices[]")
abstract static class Notice extends NoticeOrRemark {
/**
* Notice and Remark Type are defined in 10.2.1 of RFC7483.
*
* <p>We only keep the "service or entire response" values for Notice.Type.
*/
@RestrictJsonNames("type")
enum Type implements Jsonable {
RESULT_TRUNCATED_AUTHORIZATION("result set truncated due to authorization"),
RESULT_TRUNCATED_LOAD("result set truncated due to excessive load"),
RESULT_TRUNCATED_UNEXPLAINABLE("result set truncated due to unexplainable reasons");
private final String rfc7483String;
Type(String rfc7483String) {
this.rfc7483String = rfc7483String;
}
@Override
public JsonPrimitive toJson() {
return new JsonPrimitive(rfc7483String);
}
}
@JsonableElement
abstract Optional<Notice.Type> type();
static Builder builder() {
return new AutoValue_RdapDataStructures_Notice.Builder();
}
@AutoValue.Builder
abstract static class Builder extends NoticeOrRemark.Builder<Builder> {
abstract Builder setType(Notice.Type type);
abstract Notice build();
}
}
/**
* Remarks defined in 4.3 of RFC7483.
*
* <p>A remark denotes information about the specific object, and hence each object has its own
* "remarks" array.
*/
@AutoValue
@RestrictJsonNames("remarks[]")
abstract static class Remark extends NoticeOrRemark {
/**
* Notice and Remark Type are defined in 10.2.1 of RFC7483.
*
* <p>We only keep the "specific object" values for Remark.Type.
*/
@RestrictJsonNames("type")
enum Type implements Jsonable {
OBJECT_TRUNCATED_AUTHORIZATION("object truncated due to authorization"),
OBJECT_TRUNCATED_LOAD("object truncated due to excessive load"),
OBJECT_TRUNCATED_UNEXPLAINABLE("object truncated due to unexplainable reasons"),
// This one isn't in the "RDAP JSON Values registry", but it's in the RDAP Response Profile,
// so I'm adding it here, but we have to ask them about it...
OBJECT_REDACTED_AUTHORIZATION("object redacted due to authorization");
private final String rfc7483String;
Type(String rfc7483String) {
this.rfc7483String = rfc7483String;
}
@Override
public JsonPrimitive toJson() {
return new JsonPrimitive(rfc7483String);
}
}
@JsonableElement
abstract Optional<Remark.Type> type();
static Builder builder() {
return new AutoValue_RdapDataStructures_Remark.Builder();
}
@AutoValue.Builder
abstract static class Builder extends NoticeOrRemark.Builder<Builder> {
abstract Builder setType(Remark.Type type);
abstract Remark build();
}
}
/**
* Language Identifier defined in 4.4 of RFC7483.
*
* The allowed values are described in RFC5646.
*/
@RestrictJsonNames("lang")
enum LanguageIdentifier implements Jsonable {
EN("en");
private final String languageIdentifier;
LanguageIdentifier(String languageIdentifier) {
this.languageIdentifier = languageIdentifier;
}
@Override
public JsonPrimitive toJson() {
return new JsonPrimitive(languageIdentifier);
}
}
/**
* Events defined in 4.5 of RFC7483.
*
* <p>There's a type of Event that must not have the "eventActor" (see 5.1), so we create 2
* versions - one with and one without.
*/
private abstract static class EventBase extends AbstractJsonableObject {
@JsonableElement abstract EventAction eventAction();
@JsonableElement abstract DateTime eventDate();
@JsonableElement abstract ImmutableList<Link> links();
abstract static class Builder<B extends Builder<?>> {
abstract B setEventAction(EventAction eventAction);
abstract B setEventDate(DateTime eventDate);
abstract ImmutableList.Builder<Link> linksBuilder();
@SuppressWarnings("unchecked")
B addLink(Link link) {
linksBuilder().add(link);
return (B) this;
}
}
}
/** Status values for events specified in RFC 7483 § 10.2.3. */
enum EventAction implements Jsonable {
REGISTRATION("registration"),
REREGISTRATION("reregistration"),
LAST_CHANGED("last changed"),
EXPIRATION("expiration"),
DELETION("deletion"),
REINSTANTIATION("reinstantiation"),
TRANSFER("transfer"),
LOCKED("locked"),
UNLOCKED("unlocked"),
LAST_UPDATE_OF_RDAP_DATABASE("last update of RDAP database");
/** Value as it appears in RDAP messages. */
private final String rfc7483String;
EventAction(String rfc7483String) {
this.rfc7483String = rfc7483String;
}
String getDisplayName() {
return rfc7483String;
}
@Override
public JsonPrimitive toJson() {
return new JsonPrimitive(rfc7483String);
}
}
/**
* Events defined in 4.5 of RFC7483.
*
* <p>There's a type of Event that MUST NOT have the "eventActor" (see 5.1), so we have this
* object to enforce that.
*/
@RestrictJsonNames("asEventActor[]")
@AutoValue
abstract static class EventWithoutActor extends EventBase {
static Builder builder() {
return new AutoValue_RdapDataStructures_EventWithoutActor.Builder();
}
@AutoValue.Builder
abstract static class Builder extends EventBase.Builder<Builder> {
abstract EventWithoutActor build();
}
}
/**
* Events defined in 4.5 of RFC7483.
*/
@RestrictJsonNames("events[]")
@AutoValue
abstract static class Event extends EventBase {
@JsonableElement abstract Optional<String> eventActor();
static Builder builder() {
return new AutoValue_RdapDataStructures_Event.Builder();
}
@AutoValue.Builder
abstract static class Builder extends EventBase.Builder<Builder> {
abstract Builder setEventActor(String eventActor);
abstract Event build();
}
}
/**
* Status defined in 4.6 of RFC7483.
*
* <p>This indicates the state of the registered object.
*
* <p>The allowed values are in section 10.2.2.
*/
@RestrictJsonNames("status[]")
enum RdapStatus implements Jsonable {
// Status values specified in RFC 7483 § 10.2.2.
VALIDATED("validated"),
RENEW_PROHIBITED("renew prohibited"),
UPDATE_PROHIBITED("update prohibited"),
TRANSFER_PROHIBITED("transfer prohibited"),
DELETE_PROHIBITED("delete prohibited"),
PROXY("proxy"),
PRIVATE("private"),
REMOVED("removed"),
OBSCURED("obscured"),
ASSOCIATED("associated"),
ACTIVE("active"),
INACTIVE("inactive"),
LOCKED("locked"),
PENDING_CREATE("pending create"),
PENDING_RENEW("pending renew"),
PENDING_TRANSFER("pending transfer"),
PENDING_UPDATE("pending update"),
PENDING_DELETE("pending delete"),
// Additional status values defined in
// https://tools.ietf.org/html/draft-ietf-regext-epp-rdap-status-mapping-01.
ADD_PERIOD("add period"),
AUTO_RENEW_PERIOD("auto renew period"),
CLIENT_DELETE_PROHIBITED("client delete prohibited"),
CLIENT_HOLD("client hold"),
CLIENT_RENEW_PROHIBITED("client renew prohibited"),
CLIENT_TRANSFER_PROHIBITED("client transfer prohibited"),
CLIENT_UPDATE_PROHIBITED("client update prohibited"),
PENDING_RESTORE("pending restore"),
REDEMPTION_PERIOD("redemption period"),
RENEW_PERIOD("renew period"),
SERVER_DELETE_PROHIBITED("server deleted prohibited"),
SERVER_RENEW_PROHIBITED("server renew prohibited"),
SERVER_TRANSFER_PROHIBITED("server transfer prohibited"),
SERVER_UPDATE_PROHIBITED("server update prohibited"),
SERVER_HOLD("server hold"),
TRANSFER_PERIOD("transfer period");
/** Value as it appears in RDAP messages. */
private final String rfc7483String;
RdapStatus(String rfc7483String) {
this.rfc7483String = rfc7483String;
}
String getDisplayName() {
return rfc7483String;
}
@Override
public JsonPrimitive toJson() {
return new JsonPrimitive(rfc7483String);
}
}
/**
* Port 43 WHOIS Server defined in 4.7 of RFC7483.
*
* <p>This contains the fully qualifies host name of IP address of the WHOIS RFC3912 server where
* the containing object instance may be found.
*/
@RestrictJsonNames("port43")
@AutoValue
abstract static class Port43WhoisServer implements Jsonable {
abstract String port43();
@Override
public JsonPrimitive toJson() {
return new JsonPrimitive(port43());
}
static Port43WhoisServer create(String port43) {
return new AutoValue_RdapDataStructures_Port43WhoisServer(port43);
}
}
/**
* Public IDs defined in 4.8 of RFC7483.
*
* <p>Maps a public identifier to an object class.
*/
@RestrictJsonNames("publicIds[]")
@AutoValue
abstract static class PublicId extends AbstractJsonableObject {
@RestrictJsonNames("type")
enum Type implements Jsonable {
IANA_REGISTRAR_ID("IANA Registrar ID");
private final String rfc7483String;
Type(String rfc7483String) {
this.rfc7483String = rfc7483String;
}
@Override
public JsonPrimitive toJson() {
return new JsonPrimitive(rfc7483String);
}
}
@JsonableElement
abstract PublicId.Type type();
@JsonableElement abstract String identifier();
static PublicId create(PublicId.Type type, String identifier) {
return new AutoValue_RdapDataStructures_PublicId(type, identifier);
}
}
/**
* Object Class Name defined in 4.7 of RFC7483.
*
* <p>Identifies the type of the object being processed. Is REQUIRED in all RDAP response objects,
* but not so for internal objects whose type can be inferred by their key name in the enclosing
* object.
*/
@RestrictJsonNames("objectClassName")
enum ObjectClassName implements Jsonable {
/** Defined in 5.1 of RFC7483. */
ENTITY("entity"),
/** Defined in 5.2 of RFC7483. */
NAMESERVER("nameserver"),
/** Defined in 5.3 of RFC7483. */
DOMAIN("domain"),
/** Defined in 5.4 of RFC7483. Only relevant for Registrars, so isn't implemented here. */
IP_NETWORK("ip network"),
/** Defined in 5.5 of RFC7483. Only relevant for Registrars, so isn't implemented here. */
AUTONOMUS_SYSTEM("autnum");
private final String className;
ObjectClassName(String className) {
this.className = className;
}
@Override
public JsonPrimitive toJson() {
return new JsonPrimitive(className);
}
}
}

View file

@ -20,11 +20,11 @@ import static google.registry.request.Action.Method.GET;
import static google.registry.request.Action.Method.HEAD; import static google.registry.request.Action.Method.HEAD;
import static google.registry.util.DateTimeUtils.START_OF_TIME; import static google.registry.util.DateTimeUtils.START_OF_TIME;
import com.google.common.collect.ImmutableMap;
import google.registry.flows.EppException; import google.registry.flows.EppException;
import google.registry.model.domain.DomainBase; import google.registry.model.domain.DomainBase;
import google.registry.rdap.RdapJsonFormatter.OutputDataType; import google.registry.rdap.RdapJsonFormatter.OutputDataType;
import google.registry.rdap.RdapMetrics.EndpointType; import google.registry.rdap.RdapMetrics.EndpointType;
import google.registry.rdap.RdapObjectClasses.RdapDomain;
import google.registry.request.Action; import google.registry.request.Action;
import google.registry.request.HttpException.BadRequestException; import google.registry.request.HttpException.BadRequestException;
import google.registry.request.HttpException.NotFoundException; import google.registry.request.HttpException.NotFoundException;
@ -47,8 +47,7 @@ public class RdapDomainAction extends RdapActionBase {
} }
@Override @Override
public ImmutableMap<String, Object> getJsonObjectForResource( public RdapDomain getJsonObjectForResource(String pathSearchString, boolean isHeadRequest) {
String pathSearchString, boolean isHeadRequest) {
DateTime now = clock.nowUtc(); DateTime now = clock.nowUtc();
pathSearchString = canonicalizeName(pathSearchString); pathSearchString = canonicalizeName(pathSearchString);
try { try {
@ -68,8 +67,6 @@ public class RdapDomainAction extends RdapActionBase {
} }
return rdapJsonFormatter.makeRdapJsonForDomain( return rdapJsonFormatter.makeRdapJsonForDomain(
domainBase.get(), domainBase.get(),
true,
fullServletPath,
rdapWhoisServer, rdapWhoisServer,
now, now,
OutputDataType.FULL, OutputDataType.FULL,

View file

@ -22,7 +22,6 @@ import static google.registry.request.Action.Method.HEAD;
import static google.registry.util.DateTimeUtils.START_OF_TIME; import static google.registry.util.DateTimeUtils.START_OF_TIME;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Streams; import com.google.common.collect.Streams;
@ -33,11 +32,11 @@ import com.googlecode.objectify.Key;
import com.googlecode.objectify.cmd.Query; import com.googlecode.objectify.cmd.Query;
import google.registry.model.domain.DomainBase; import google.registry.model.domain.DomainBase;
import google.registry.model.host.HostResource; import google.registry.model.host.HostResource;
import google.registry.rdap.RdapJsonFormatter.BoilerplateType;
import google.registry.rdap.RdapJsonFormatter.OutputDataType; import google.registry.rdap.RdapJsonFormatter.OutputDataType;
import google.registry.rdap.RdapMetrics.EndpointType; import google.registry.rdap.RdapMetrics.EndpointType;
import google.registry.rdap.RdapMetrics.SearchType; import google.registry.rdap.RdapMetrics.SearchType;
import google.registry.rdap.RdapMetrics.WildcardType; import google.registry.rdap.RdapMetrics.WildcardType;
import google.registry.rdap.RdapSearchResults.DomainSearchResponse;
import google.registry.rdap.RdapSearchResults.IncompletenessWarningType; import google.registry.rdap.RdapSearchResults.IncompletenessWarningType;
import google.registry.request.Action; import google.registry.request.Action;
import google.registry.request.HttpException.BadRequestException; import google.registry.request.HttpException.BadRequestException;
@ -48,7 +47,6 @@ import google.registry.request.auth.Auth;
import google.registry.util.Idn; import google.registry.util.Idn;
import google.registry.util.NonFinalForTesting; import google.registry.util.NonFinalForTesting;
import java.net.InetAddress; import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -93,7 +91,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
* <p>The RDAP spec allows for domain search by domain name, nameserver name or nameserver IP. * <p>The RDAP spec allows for domain search by domain name, nameserver name or nameserver IP.
*/ */
@Override @Override
public ImmutableMap<String, Object> getJsonObjectForResource( public DomainSearchResponse getJsonObjectForResource(
String pathSearchString, boolean isHeadRequest) { String pathSearchString, boolean isHeadRequest) {
DateTime now = clock.nowUtc(); DateTime now = clock.nowUtc();
// RDAP syntax example: /rdap/domains?name=exam*.com. // RDAP syntax example: /rdap/domains?name=exam*.com.
@ -107,7 +105,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
"You must specify either name=XXXX, nsLdhName=YYYY or nsIp=ZZZZ"); "You must specify either name=XXXX, nsLdhName=YYYY or nsIp=ZZZZ");
} }
decodeCursorToken(); decodeCursorToken();
RdapSearchResults results; DomainSearchResponse results;
if (nameParam.isPresent()) { if (nameParam.isPresent()) {
metricInformationBuilder.setSearchType(SearchType.BY_DOMAIN_NAME); metricInformationBuilder.setSearchType(SearchType.BY_DOMAIN_NAME);
// syntax: /rdap/domains?name=exam*.com // syntax: /rdap/domains?name=exam*.com
@ -142,18 +140,10 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
} }
results = searchByNameserverIp(inetAddress, now); results = searchByNameserverIp(inetAddress, now);
} }
if (results.jsonList().isEmpty()) { if (results.domainSearchResults().isEmpty()) {
throw new NotFoundException("No domains found"); throw new NotFoundException("No domains found");
} }
ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>(); return results;
builder.put("domainSearchResults", results.jsonList());
rdapJsonFormatter.addTopLevelEntries(
builder,
BoilerplateType.DOMAIN,
getNotices(results),
ImmutableList.of(),
fullServletPath);
return builder.build();
} }
/** /**
@ -168,7 +158,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
* <p>Searches which include deleted entries are effectively treated as if they have a wildcard, * <p>Searches which include deleted entries are effectively treated as if they have a wildcard,
* since the same name can return multiple results. * since the same name can return multiple results.
*/ */
private RdapSearchResults searchByDomainName( private DomainSearchResponse searchByDomainName(
final RdapSearchPattern partialStringQuery, final DateTime now) { final RdapSearchPattern partialStringQuery, final DateTime now) {
// Handle queries without a wildcard -- just load by foreign key. We can't do this if deleted // Handle queries without a wildcard -- just load by foreign key. We can't do this if deleted
// entries are included, because there may be multiple nameservers with the same name. // entries are included, because there may be multiple nameservers with the same name.
@ -199,7 +189,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
/** /**
* Searches for domains by domain name without a wildcard or interest in deleted entries. * Searches for domains by domain name without a wildcard or interest in deleted entries.
*/ */
private RdapSearchResults searchByDomainNameWithoutWildcard( private DomainSearchResponse searchByDomainNameWithoutWildcard(
final RdapSearchPattern partialStringQuery, final DateTime now) { final RdapSearchPattern partialStringQuery, final DateTime now) {
Optional<DomainBase> domainBase = Optional<DomainBase> domainBase =
loadByForeignKey(DomainBase.class, partialStringQuery.getInitialString(), now); loadByForeignKey(DomainBase.class, partialStringQuery.getInitialString(), now);
@ -211,7 +201,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
} }
/** Searches for domains by domain name with an initial string, wildcard and possible suffix. */ /** Searches for domains by domain name with an initial string, wildcard and possible suffix. */
private RdapSearchResults searchByDomainNameWithInitialString( private DomainSearchResponse searchByDomainNameWithInitialString(
final RdapSearchPattern partialStringQuery, final DateTime now) { final RdapSearchPattern partialStringQuery, final DateTime now) {
// We can't query for undeleted domains as part of the query itself; that would require an // We can't query for undeleted domains as part of the query itself; that would require an
// inequality query on deletion time, and we are already using inequality queries on // inequality query on deletion time, and we are already using inequality queries on
@ -241,7 +231,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
} }
/** Searches for domains by domain name with a TLD suffix. */ /** Searches for domains by domain name with a TLD suffix. */
private RdapSearchResults searchByDomainNameByTld(String tld, DateTime now) { private DomainSearchResponse searchByDomainNameByTld(String tld, DateTime now) {
// Even though we are not searching on fullyQualifiedDomainName, we want the results to come // Even though we are not searching on fullyQualifiedDomainName, we want the results to come
// back ordered by name, so we are still in the same boat as // back ordered by name, so we are still in the same boat as
// searchByDomainNameWithInitialString, unable to perform an inequality query on deletion time. // searchByDomainNameWithInitialString, unable to perform an inequality query on deletion time.
@ -268,7 +258,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
* <p>The includeDeleted parameter does NOT cause deleted nameservers to be searched, only deleted * <p>The includeDeleted parameter does NOT cause deleted nameservers to be searched, only deleted
* domains which used to be connected to an undeleted nameserver. * domains which used to be connected to an undeleted nameserver.
*/ */
private RdapSearchResults searchByNameserverLdhName( private DomainSearchResponse searchByNameserverLdhName(
final RdapSearchPattern partialStringQuery, final DateTime now) { final RdapSearchPattern partialStringQuery, final DateTime now) {
Iterable<Key<HostResource>> hostKeys = getNameserverRefsByLdhName(partialStringQuery, now); Iterable<Key<HostResource>> hostKeys = getNameserverRefsByLdhName(partialStringQuery, now);
if (Iterables.isEmpty(hostKeys)) { if (Iterables.isEmpty(hostKeys)) {
@ -407,7 +397,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
* <p>The includeDeleted parameter does NOT cause deleted nameservers to be searched, only deleted * <p>The includeDeleted parameter does NOT cause deleted nameservers to be searched, only deleted
* domains which used to be connected to an undeleted nameserver. * domains which used to be connected to an undeleted nameserver.
*/ */
private RdapSearchResults searchByNameserverIp( private DomainSearchResponse searchByNameserverIp(
final InetAddress inetAddress, final DateTime now) { final InetAddress inetAddress, final DateTime now) {
Query<HostResource> query = Query<HostResource> query =
queryItems( queryItems(
@ -431,7 +421,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
* <p>This method is called by {@link #searchByNameserverLdhName} and {@link * <p>This method is called by {@link #searchByNameserverLdhName} and {@link
* #searchByNameserverIp} after they assemble the relevant host keys. * #searchByNameserverIp} after they assemble the relevant host keys.
*/ */
private RdapSearchResults searchByNameserverRefs( private DomainSearchResponse searchByNameserverRefs(
final Iterable<Key<HostResource>> hostKeys, final DateTime now) { final Iterable<Key<HostResource>> hostKeys, final DateTime now) {
// We must break the query up into chunks, because the in operator is limited to 30 subqueries. // We must break the query up into chunks, because the in operator is limited to 30 subqueries.
// Since it is possible for the same domain to show up more than once in our result list (if // Since it is possible for the same domain to show up more than once in our result list (if
@ -466,13 +456,6 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
} }
List<DomainBase> domains = domainSetBuilder.build().asList(); List<DomainBase> domains = domainSetBuilder.build().asList();
metricInformationBuilder.setNumHostsRetrieved(numHostKeysSearched); metricInformationBuilder.setNumHostsRetrieved(numHostKeysSearched);
if (domains.size() > rdapResultSetMaxSize) {
return makeSearchResults(
domains.subList(0, rdapResultSetMaxSize),
IncompletenessWarningType.TRUNCATED,
Optional.of((long) domains.size()),
now);
} else {
// If everything that we found will fit in the result, check whether there might have been // If everything that we found will fit in the result, check whether there might have been
// more results that got dropped because the first stage limit on number of nameservers. If // more results that got dropped because the first stage limit on number of nameservers. If
// so, indicate the result might be incomplete. // so, indicate the result might be incomplete.
@ -484,16 +467,15 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
(numHostKeysSearched > 0) ? Optional.of((long) domains.size()) : Optional.empty(), (numHostKeysSearched > 0) ? Optional.of((long) domains.size()) : Optional.empty(),
now); now);
} }
}
/** Output JSON for a list of domains, with no incompleteness warnings. */ /** Output JSON for a list of domains, with no incompleteness warnings. */
private RdapSearchResults makeSearchResults(List<DomainBase> domains, DateTime now) { private DomainSearchResponse makeSearchResults(List<DomainBase> domains, DateTime now) {
return makeSearchResults( return makeSearchResults(
domains, IncompletenessWarningType.COMPLETE, Optional.of((long) domains.size()), now); domains, IncompletenessWarningType.COMPLETE, Optional.of((long) domains.size()), now);
} }
/** Output JSON from data in an {@link RdapResultSet} object. */ /** Output JSON from data in an {@link RdapResultSet} object. */
private RdapSearchResults makeSearchResults( private DomainSearchResponse makeSearchResults(
RdapResultSet<DomainBase> resultSet, DateTime now) { RdapResultSet<DomainBase> resultSet, DateTime now) {
return makeSearchResults( return makeSearchResults(
resultSet.resources(), resultSet.resources(),
@ -509,7 +491,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
* than are in the list, or MIGHT_BE_INCOMPLETE if a search for domains by nameserver returned the * than are in the list, or MIGHT_BE_INCOMPLETE if a search for domains by nameserver returned the
* maximum number of nameservers in the first stage query. * maximum number of nameservers in the first stage query.
*/ */
private RdapSearchResults makeSearchResults( private DomainSearchResponse makeSearchResults(
List<DomainBase> domains, List<DomainBase> domains,
IncompletenessWarningType incompletenessWarningType, IncompletenessWarningType incompletenessWarningType,
Optional<Long> numDomainsRetrieved, Optional<Long> numDomainsRetrieved,
@ -517,28 +499,21 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
numDomainsRetrieved.ifPresent(metricInformationBuilder::setNumDomainsRetrieved); numDomainsRetrieved.ifPresent(metricInformationBuilder::setNumDomainsRetrieved);
OutputDataType outputDataType = OutputDataType outputDataType =
(domains.size() > 1) ? OutputDataType.SUMMARY : OutputDataType.FULL; (domains.size() > 1) ? OutputDataType.SUMMARY : OutputDataType.FULL;
DomainSearchResponse.Builder builder =
DomainSearchResponse.builder()
.setIncompletenessWarningType(incompletenessWarningType);
RdapAuthorization authorization = getAuthorization(); RdapAuthorization authorization = getAuthorization();
List<ImmutableMap<String, Object>> jsonList = new ArrayList<>();
Optional<String> newCursor = Optional.empty(); Optional<String> newCursor = Optional.empty();
for (DomainBase domain : domains) { for (DomainBase domain : Iterables.limit(domains, rdapResultSetMaxSize)) {
newCursor = Optional.of(domain.getFullyQualifiedDomainName()); newCursor = Optional.of(domain.getFullyQualifiedDomainName());
jsonList.add( builder.domainSearchResultsBuilder().add(
rdapJsonFormatter.makeRdapJsonForDomain( rdapJsonFormatter.makeRdapJsonForDomain(
domain, false, fullServletPath, rdapWhoisServer, now, outputDataType, authorization)); domain, rdapWhoisServer, now, outputDataType, authorization));
if (jsonList.size() >= rdapResultSetMaxSize) { }
break; if (rdapResultSetMaxSize < domains.size()) {
} builder.setNextPageUri(createNavigationUri(newCursor.get()));
} builder.setIncompletenessWarningType(IncompletenessWarningType.TRUNCATED);
IncompletenessWarningType finalIncompletenessWarningType = }
(jsonList.size() < domains.size()) return builder.build();
? IncompletenessWarningType.TRUNCATED
: incompletenessWarningType;
metricInformationBuilder.setIncompletenessWarningType(finalIncompletenessWarningType);
return RdapSearchResults.create(
ImmutableList.copyOf(jsonList),
finalIncompletenessWarningType,
(finalIncompletenessWarningType == IncompletenessWarningType.TRUNCATED)
? newCursor
: Optional.empty());
} }
} }

View file

@ -19,7 +19,6 @@ import static google.registry.rdap.RdapUtils.getRegistrarByIanaIdentifier;
import static google.registry.request.Action.Method.GET; import static google.registry.request.Action.Method.GET;
import static google.registry.request.Action.Method.HEAD; import static google.registry.request.Action.Method.HEAD;
import com.google.common.collect.ImmutableMap;
import com.google.common.primitives.Longs; import com.google.common.primitives.Longs;
import com.google.re2j.Pattern; import com.google.re2j.Pattern;
import com.googlecode.objectify.Key; import com.googlecode.objectify.Key;
@ -27,6 +26,7 @@ import google.registry.model.contact.ContactResource;
import google.registry.model.registrar.Registrar; import google.registry.model.registrar.Registrar;
import google.registry.rdap.RdapJsonFormatter.OutputDataType; import google.registry.rdap.RdapJsonFormatter.OutputDataType;
import google.registry.rdap.RdapMetrics.EndpointType; import google.registry.rdap.RdapMetrics.EndpointType;
import google.registry.rdap.RdapObjectClasses.RdapEntity;
import google.registry.request.Action; import google.registry.request.Action;
import google.registry.request.HttpException.BadRequestException; import google.registry.request.HttpException.BadRequestException;
import google.registry.request.HttpException.NotFoundException; import google.registry.request.HttpException.NotFoundException;
@ -60,7 +60,7 @@ public class RdapEntityAction extends RdapActionBase {
} }
@Override @Override
public ImmutableMap<String, Object> getJsonObjectForResource( public RdapEntity getJsonObjectForResource(
String pathSearchString, boolean isHeadRequest) { String pathSearchString, boolean isHeadRequest) {
DateTime now = clock.nowUtc(); DateTime now = clock.nowUtc();
// The query string is not used; the RDAP syntax is /rdap/entity/handle (the handle is the roid // The query string is not used; the RDAP syntax is /rdap/entity/handle (the handle is the roid
@ -76,9 +76,7 @@ public class RdapEntityAction extends RdapActionBase {
if ((contactResource != null) && shouldBeVisible(contactResource, now)) { if ((contactResource != null) && shouldBeVisible(contactResource, now)) {
return rdapJsonFormatter.makeRdapJsonForContact( return rdapJsonFormatter.makeRdapJsonForContact(
contactResource, contactResource,
true,
Optional.empty(), Optional.empty(),
fullServletPath,
rdapWhoisServer, rdapWhoisServer,
now, now,
OutputDataType.FULL, OutputDataType.FULL,
@ -91,7 +89,7 @@ public class RdapEntityAction extends RdapActionBase {
Optional<Registrar> registrar = getRegistrarByIanaIdentifier(ianaIdentifier); Optional<Registrar> registrar = getRegistrarByIanaIdentifier(ianaIdentifier);
if (registrar.isPresent() && shouldBeVisible(registrar.get())) { if (registrar.isPresent() && shouldBeVisible(registrar.get())) {
return rdapJsonFormatter.makeRdapJsonForRegistrar( return rdapJsonFormatter.makeRdapJsonForRegistrar(
registrar.get(), true, fullServletPath, rdapWhoisServer, now, OutputDataType.FULL); registrar.get(), rdapWhoisServer, now, OutputDataType.FULL);
} }
} }
// At this point, we have failed to find either a contact or a registrar. // At this point, we have failed to find either a contact or a registrar.

View file

@ -21,17 +21,17 @@ import static google.registry.request.Action.Method.GET;
import static google.registry.request.Action.Method.HEAD; import static google.registry.request.Action.Method.HEAD;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables;
import com.google.common.collect.Streams; import com.google.common.collect.Streams;
import com.google.common.primitives.Booleans; import com.google.common.primitives.Booleans;
import com.google.common.primitives.Longs; import com.google.common.primitives.Longs;
import com.googlecode.objectify.cmd.Query; import com.googlecode.objectify.cmd.Query;
import google.registry.model.contact.ContactResource; import google.registry.model.contact.ContactResource;
import google.registry.model.registrar.Registrar; import google.registry.model.registrar.Registrar;
import google.registry.rdap.RdapJsonFormatter.BoilerplateType;
import google.registry.rdap.RdapJsonFormatter.OutputDataType; import google.registry.rdap.RdapJsonFormatter.OutputDataType;
import google.registry.rdap.RdapMetrics.EndpointType; import google.registry.rdap.RdapMetrics.EndpointType;
import google.registry.rdap.RdapMetrics.SearchType; import google.registry.rdap.RdapMetrics.SearchType;
import google.registry.rdap.RdapSearchResults.EntitySearchResponse;
import google.registry.rdap.RdapSearchResults.IncompletenessWarningType; import google.registry.rdap.RdapSearchResults.IncompletenessWarningType;
import google.registry.request.Action; import google.registry.request.Action;
import google.registry.request.HttpException.BadRequestException; import google.registry.request.HttpException.BadRequestException;
@ -39,7 +39,6 @@ import google.registry.request.HttpException.NotFoundException;
import google.registry.request.HttpException.UnprocessableEntityException; import google.registry.request.HttpException.UnprocessableEntityException;
import google.registry.request.Parameter; import google.registry.request.Parameter;
import google.registry.request.auth.Auth; import google.registry.request.auth.Auth;
import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -110,7 +109,7 @@ public class RdapEntitySearchAction extends RdapSearchActionBase {
/** Parses the parameters and calls the appropriate search function. */ /** Parses the parameters and calls the appropriate search function. */
@Override @Override
public ImmutableMap<String, Object> getJsonObjectForResource( public EntitySearchResponse getJsonObjectForResource(
String pathSearchString, boolean isHeadRequest) { String pathSearchString, boolean isHeadRequest) {
DateTime now = clock.nowUtc(); DateTime now = clock.nowUtc();
@ -157,7 +156,7 @@ public class RdapEntitySearchAction extends RdapSearchActionBase {
} }
// Search by name. // Search by name.
RdapSearchResults results; EntitySearchResponse results;
if (fnParam.isPresent()) { if (fnParam.isPresent()) {
metricInformationBuilder.setSearchType(SearchType.BY_FULL_NAME); metricInformationBuilder.setSearchType(SearchType.BY_FULL_NAME);
// syntax: /rdap/entities?fn=Bobby%20Joe* // syntax: /rdap/entities?fn=Bobby%20Joe*
@ -185,18 +184,10 @@ public class RdapEntitySearchAction extends RdapSearchActionBase {
} }
// Build the result object and return it. // Build the result object and return it.
if (results.jsonList().isEmpty()) { if (results.entitySearchResults().isEmpty()) {
throw new NotFoundException("No entities found"); throw new NotFoundException("No entities found");
} }
ImmutableMap.Builder<String, Object> jsonBuilder = new ImmutableMap.Builder<>(); return results;
jsonBuilder.put("entitySearchResults", results.jsonList());
rdapJsonFormatter.addTopLevelEntries(
jsonBuilder,
BoilerplateType.ENTITY,
getNotices(results),
ImmutableList.of(),
fullServletPath);
return jsonBuilder.build();
} }
/** /**
@ -223,7 +214,7 @@ public class RdapEntitySearchAction extends RdapSearchActionBase {
* href="https://newgtlds.icann.org/sites/default/files/agreements/agreement-approved-09jan14-en.htm">1.6 * href="https://newgtlds.icann.org/sites/default/files/agreements/agreement-approved-09jan14-en.htm">1.6
* of Section 4 of the Base Registry Agreement</a> * of Section 4 of the Base Registry Agreement</a>
*/ */
private RdapSearchResults searchByName( private EntitySearchResponse searchByName(
final RdapSearchPattern partialStringQuery, final RdapSearchPattern partialStringQuery,
CursorType cursorType, CursorType cursorType,
Optional<String> cursorQueryString, Optional<String> cursorQueryString,
@ -308,7 +299,7 @@ public class RdapEntitySearchAction extends RdapSearchActionBase {
* In both cases, the suffix can be turned into an additional query filter field. For contacts, * In both cases, the suffix can be turned into an additional query filter field. For contacts,
* there is no equivalent string suffix that can be used as a query filter, so we disallow use. * there is no equivalent string suffix that can be used as a query filter, so we disallow use.
*/ */
private RdapSearchResults searchByHandle( private EntitySearchResponse searchByHandle(
final RdapSearchPattern partialStringQuery, final RdapSearchPattern partialStringQuery,
CursorType cursorType, CursorType cursorType,
Optional<String> cursorQueryString, Optional<String> cursorQueryString,
@ -424,7 +415,7 @@ public class RdapEntitySearchAction extends RdapSearchActionBase {
* <p>This is a convenience wrapper for the four-argument makeSearchResults; it unpacks the * <p>This is a convenience wrapper for the four-argument makeSearchResults; it unpacks the
* properties of the {@link RdapResultSet} structure and passes them as separate arguments. * properties of the {@link RdapResultSet} structure and passes them as separate arguments.
*/ */
private RdapSearchResults makeSearchResults( private EntitySearchResponse makeSearchResults(
RdapResultSet<ContactResource> resultSet, RdapResultSet<ContactResource> resultSet,
List<Registrar> registrars, List<Registrar> registrars,
QueryType queryType, QueryType queryType,
@ -454,7 +445,7 @@ public class RdapEntitySearchAction extends RdapSearchActionBase {
* @param now the current date and time * @param now the current date and time
* @return an {@link RdapSearchResults} object * @return an {@link RdapSearchResults} object
*/ */
private RdapSearchResults makeSearchResults( private EntitySearchResponse makeSearchResults(
List<ContactResource> contacts, List<ContactResource> contacts,
IncompletenessWarningType incompletenessWarningType, IncompletenessWarningType incompletenessWarningType,
int numContactsRetrieved, int numContactsRetrieved,
@ -473,25 +464,19 @@ public class RdapEntitySearchAction extends RdapSearchActionBase {
// (contacts and registrars), and partially because we try to fetch one more than the max size, // (contacts and registrars), and partially because we try to fetch one more than the max size,
// so we can tell whether to display the truncation notification. // so we can tell whether to display the truncation notification.
RdapAuthorization authorization = getAuthorization(); RdapAuthorization authorization = getAuthorization();
List<ImmutableMap<String, Object>> jsonOutputList = new ArrayList<>();
// Each time we add a contact or registrar to the output data set, remember what the appropriate // Each time we add a contact or registrar to the output data set, remember what the appropriate
// cursor would be if it were the last item returned. When we stop adding items, the last cursor // cursor would be if it were the last item returned. When we stop adding items, the last cursor
// value we remembered will be the right one to pass back. // value we remembered will be the right one to pass back.
EntitySearchResponse.Builder builder =
EntitySearchResponse.builder()
.setIncompletenessWarningType(incompletenessWarningType);
Optional<String> newCursor = Optional.empty(); Optional<String> newCursor = Optional.empty();
for (ContactResource contact : contacts) { for (ContactResource contact : Iterables.limit(contacts, rdapResultSetMaxSize)) {
if (jsonOutputList.size() >= rdapResultSetMaxSize) {
return RdapSearchResults.create(
ImmutableList.copyOf(jsonOutputList),
IncompletenessWarningType.TRUNCATED,
newCursor);
}
// As per Andy Newton on the regext mailing list, contacts by themselves have no role, since // As per Andy Newton on the regext mailing list, contacts by themselves have no role, since
// they are global, and might have different roles for different domains. // they are global, and might have different roles for different domains.
jsonOutputList.add(rdapJsonFormatter.makeRdapJsonForContact( builder.entitySearchResultsBuilder().add(rdapJsonFormatter.makeRdapJsonForContact(
contact, contact,
false,
Optional.empty(), Optional.empty(),
fullServletPath,
rdapWhoisServer, rdapWhoisServer,
now, now,
outputDataType, outputDataType,
@ -503,22 +488,22 @@ public class RdapEntitySearchAction extends RdapSearchActionBase {
? contact.getSearchName() ? contact.getSearchName()
: contact.getRepoId())); : contact.getRepoId()));
} }
for (Registrar registrar : registrars) { if (rdapResultSetMaxSize > contacts.size()) {
if (jsonOutputList.size() >= rdapResultSetMaxSize) { for (Registrar registrar :
return RdapSearchResults.create( Iterables.limit(registrars, rdapResultSetMaxSize - contacts.size())) {
ImmutableList.copyOf(jsonOutputList), builder
IncompletenessWarningType.TRUNCATED, .entitySearchResultsBuilder()
newCursor); .add(
} rdapJsonFormatter.makeRdapJsonForRegistrar(
jsonOutputList.add(rdapJsonFormatter.makeRdapJsonForRegistrar( registrar, rdapWhoisServer, now, outputDataType));
registrar, false, fullServletPath, rdapWhoisServer, now, outputDataType));
newCursor = Optional.of(REGISTRAR_CURSOR_PREFIX + registrar.getRegistrarName()); newCursor = Optional.of(REGISTRAR_CURSOR_PREFIX + registrar.getRegistrarName());
} }
return RdapSearchResults.create( }
ImmutableList.copyOf(jsonOutputList), if (rdapResultSetMaxSize < contacts.size() + registrars.size()) {
(jsonOutputList.size() < rdapResultSetMaxSize) builder.setNextPageUri(createNavigationUri(newCursor.get()));
? incompletenessWarningType builder.setIncompletenessWarningType(IncompletenessWarningType.TRUNCATED);
: IncompletenessWarningType.COMPLETE, return builder.build();
Optional.empty()); }
return builder.build();
} }
} }

View file

@ -17,12 +17,14 @@ package google.registry.rdap;
import static google.registry.request.Action.Method.GET; import static google.registry.request.Action.Method.GET;
import static google.registry.request.Action.Method.HEAD; import static google.registry.request.Action.Method.HEAD;
import com.google.common.collect.ImmutableList; import google.registry.rdap.RdapDataStructures.Link;
import com.google.common.collect.ImmutableMap; import google.registry.rdap.RdapDataStructures.Notice;
import google.registry.rdap.RdapJsonFormatter.BoilerplateType;
import google.registry.rdap.RdapMetrics.EndpointType; import google.registry.rdap.RdapMetrics.EndpointType;
import google.registry.rdap.RdapObjectClasses.HelpResponse;
import google.registry.request.Action; import google.registry.request.Action;
import google.registry.request.HttpException.NotFoundException;
import google.registry.request.auth.Auth; import google.registry.request.auth.Auth;
import java.util.Optional;
import javax.inject.Inject; import javax.inject.Inject;
/** RDAP (new WHOIS) action for help requests. */ /** RDAP (new WHOIS) action for help requests. */
@ -34,22 +36,53 @@ import javax.inject.Inject;
auth = Auth.AUTH_PUBLIC_ANONYMOUS) auth = Auth.AUTH_PUBLIC_ANONYMOUS)
public class RdapHelpAction extends RdapActionBase { public class RdapHelpAction extends RdapActionBase {
/** The help path for the RDAP terms of service. */
public static final String TOS_PATH = "/tos";
private static final String RDAP_HELP_LINK =
"https://github.com/google/nomulus/blob/master/docs/rdap.md";
@Inject public RdapHelpAction() { @Inject public RdapHelpAction() {
super("help", EndpointType.HELP); super("help", EndpointType.HELP);
} }
private Notice createHelpNotice() {
String linkValue = rdapJsonFormatter.makeRdapServletRelativeUrl("help");
Link.Builder linkBuilder =
Link.builder()
.setValue(linkValue)
.setRel("alternate")
.setHref(RDAP_HELP_LINK)
.setType("text/html");
return Notice.builder()
.setTitle("RDAP Help")
.setDescription(
"domain/XXXX",
"nameserver/XXXX",
"entity/XXXX",
"domains?name=XXXX",
"domains?nsLdhName=XXXX",
"domains?nsIp=XXXX",
"nameservers?name=XXXX",
"nameservers?ip=XXXX",
"entities?fn=XXXX",
"entities?handle=XXXX",
"help/XXXX")
.addLink(linkBuilder.build())
.build();
}
@Override @Override
public ImmutableMap<String, Object> getJsonObjectForResource( public HelpResponse getJsonObjectForResource(
String pathSearchString, boolean isHeadRequest) { String pathSearchString, boolean isHeadRequest) {
// We rely on addTopLevelEntries to notice if we are sending the TOS notice, and not add a if (pathSearchString.isEmpty() || pathSearchString.equals("/")) {
// duplicate boilerplate entry. return HelpResponse.create(Optional.of(createHelpNotice()));
ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>(); }
rdapJsonFormatter.addTopLevelEntries( if (pathSearchString.equals(TOS_PATH)) {
builder, // A TOS notice is added to every reply automatically, so we don't want to add another one
BoilerplateType.OTHER, // here
ImmutableList.of(rdapJsonFormatter.getJsonHelpNotice(pathSearchString, fullServletPath)), return HelpResponse.create(Optional.empty());
ImmutableList.of(), }
fullServletPath); throw new NotFoundException("no help found for " + pathSearchString);
return builder.build();
} }
} }

View file

@ -15,145 +15,143 @@
package google.registry.rdap; package google.registry.rdap;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import google.registry.rdap.RdapDataStructures.Link;
import google.registry.rdap.RdapDataStructures.Notice;
import google.registry.rdap.RdapDataStructures.Remark;
/** /**
* This file contains boilerplate required by the ICANN RDAP Profile. * This file contains boilerplate required by the ICANN RDAP Profile.
* *
* @see <a href="https://www.icann.org/resources/pages/rdap-operational-profile-2016-07-26-en">RDAP Operational Profile for gTLD Registries and Registrars</a> * @see <a href="https://www.icann.org/resources/pages/rdap-operational-profile-2016-07-26-en">RDAP
* Operational Profile for gTLD Registries and Registrars</a>
*/ */
public class RdapIcannStandardInformation { public class RdapIcannStandardInformation {
/** Required by ICANN RDAP Profile section 1.4.10. */ /** Required by ICANN RDAP Profile section 1.4.10. */
private static final ImmutableMap<String, Object> CONFORMANCE_NOTICE = private static final Notice CONFORMANCE_NOTICE =
ImmutableMap.of( Notice.builder()
"description", .setDescription(
ImmutableList.of(
"This response conforms to the RDAP Operational Profile for gTLD Registries and" "This response conforms to the RDAP Operational Profile for gTLD Registries and"
+ " Registrars version 1.0")); + " Registrars version 1.0")
.build();
/** Required by ICANN RDAP Profile section 1.5.18. */ /** Required by ICANN RDAP Profile section 1.5.18. */
private static final ImmutableMap<String, Object> DOMAIN_STATUS_CODES_NOTICE = private static final Notice DOMAIN_STATUS_CODES_NOTICE =
ImmutableMap.of( Notice.builder()
"title", .setTitle("Status Codes")
"Status Codes", .setDescription(
"description", "For more information on domain status codes, please visit"
ImmutableList.of( + " https://icann.org/epp")
"For more information on domain status codes, please visit https://icann.org/epp"), .addLink(
"links", Link.builder()
ImmutableList.of( .setValue("https://icann.org/epp")
ImmutableMap.of( .setRel("alternate")
"value", "https://icann.org/epp", .setHref("https://icann.org/epp")
"rel", "alternate", .setType("text/html")
"href", "https://icann.org/epp", .build())
"type", "text/html"))); .build();
/** Required by ICANN RDAP Profile section 1.5.20. */ /** Required by ICANN RDAP Profile section 1.5.20. */
private static final ImmutableMap<String, Object> INACCURACY_COMPLAINT_FORM_NOTICE = private static final Notice INACCURACY_COMPLAINT_FORM_NOTICE =
ImmutableMap.of( Notice.builder()
"description", .setDescription(
ImmutableList.of( "URL of the ICANN Whois Inaccuracy Complaint Form: https://www.icann.org/wicf")
"URL of the ICANN Whois Inaccuracy Complaint Form: https://www.icann.org/wicf"), .addLink(
"links", Link.builder()
ImmutableList.of( .setValue("https://www.icann.org/wicf")
ImmutableMap.of( .setRel("alternate")
"value", "https://www.icann.org/wicf", .setHref("https://www.icann.org/wicf")
"rel", "alternate", .setType("text/html")
"href", "https://www.icann.org/wicf", .build())
"type", "text/html"))); .build();
/** Boilerplate notices required by domain responses. */ /** Boilerplate notices required by domain responses. */
static final ImmutableList<ImmutableMap<String, Object>> domainBoilerplateNotices = static final ImmutableList<Notice> domainBoilerplateNotices =
ImmutableList.of( ImmutableList.of(
CONFORMANCE_NOTICE, DOMAIN_STATUS_CODES_NOTICE, INACCURACY_COMPLAINT_FORM_NOTICE); CONFORMANCE_NOTICE,
// RDAP Response Profile 2.6.3
DOMAIN_STATUS_CODES_NOTICE,
INACCURACY_COMPLAINT_FORM_NOTICE);
/** Boilerplate remarks required by nameserver and entity responses. */ /** Boilerplate remarks required by nameserver and entity responses. */
static final ImmutableList<ImmutableMap<String, Object>> nameserverAndEntityBoilerplateNotices = static final ImmutableList<Notice> nameserverAndEntityBoilerplateNotices =
ImmutableList.of(CONFORMANCE_NOTICE); ImmutableList.of(CONFORMANCE_NOTICE);
/** /**
* Required by ICANN RDAP Profile section 1.4.9, as corrected by Gustavo Lozano of ICANN. * Required by ICANN RDAP Profile section 1.4.9, as corrected by Gustavo Lozano of ICANN.
* *
* @see <a href="http://mm.icann.org/pipermail/gtld-tech/2016-October/000822.html">Questions about the ICANN RDAP Profile</a> * @see <a href="http://mm.icann.org/pipermail/gtld-tech/2016-October/000822.html">Questions about
* the ICANN RDAP Profile</a>
*/ */
static final ImmutableMap<String, Object> SUMMARY_DATA_REMARK = static final Remark SUMMARY_DATA_REMARK =
ImmutableMap.of( Remark.builder()
"title", .setTitle("Incomplete Data")
"Incomplete Data", .setDescription(
"description", "Summary data only. For complete data, send a specific query for the object.")
ImmutableList.of( .setType(Remark.Type.OBJECT_TRUNCATED_UNEXPLAINABLE)
"Summary data only. For complete data, send a specific query for the object."), .build();
"type",
"object truncated due to unexplainable reasons");
/** /**
* Required by ICANN RDAP Profile section 1.4.8, as corrected by Gustavo Lozano of ICANN. * Required by ICANN RDAP Profile section 1.4.8, as corrected by Gustavo Lozano of ICANN.
* *
* @see <a href="http://mm.icann.org/pipermail/gtld-tech/2016-October/000822.html">Questions about the ICANN RDAP Profile</a> * @see <a href="http://mm.icann.org/pipermail/gtld-tech/2016-October/000822.html">Questions about
* the ICANN RDAP Profile</a>
*/ */
static final ImmutableMap<String, Object> TRUNCATED_RESULT_SET_NOTICE = static final Notice TRUNCATED_RESULT_SET_NOTICE =
ImmutableMap.of( Notice.builder()
"title", .setTitle("Search Policy")
"Search Policy", .setDescription("Search results per query are limited.")
"description", .setType(Notice.Type.RESULT_TRUNCATED_UNEXPLAINABLE)
ImmutableList.of("Search results per query are limited."), .build();
"type",
"result set truncated due to unexplainable reasons");
/** Truncation notice as a singleton list, for easy use. */ /** Truncation notice as a singleton list, for easy use. */
static final ImmutableList<ImmutableMap<String, Object>> TRUNCATION_NOTICES = static final ImmutableList<Notice> TRUNCATION_NOTICES =
ImmutableList.of(TRUNCATED_RESULT_SET_NOTICE); ImmutableList.of(TRUNCATED_RESULT_SET_NOTICE);
/** /**
* Used when a search for domains by nameserver may have returned incomplete information because * Used when a search for domains by nameserver may have returned incomplete information because
* there were too many nameservers in the first stage results. * there were too many nameservers in the first stage results.
*/ */
static final ImmutableMap<String, Object> POSSIBLY_INCOMPLETE_RESULT_SET_NOTICE = static final Notice POSSIBLY_INCOMPLETE_RESULT_SET_NOTICE =
ImmutableMap.of( Notice.builder()
"title", .setTitle("Search Policy")
"Search Policy", .setDescription(
"description", "Search results may contain incomplete information due to first-stage query"
ImmutableList.of( + " limits.")
"Search results may contain incomplete information due to first-stage query limits."), .setType(Notice.Type.RESULT_TRUNCATED_UNEXPLAINABLE)
"type", .build();
"result set truncated due to unexplainable reasons");
/** Possibly incomplete notice as a singleton list, for easy use. */ /** Possibly incomplete notice as a singleton list, for easy use. */
static final ImmutableList<ImmutableMap<String, Object>> POSSIBLY_INCOMPLETE_NOTICES = static final ImmutableList<Notice> POSSIBLY_INCOMPLETE_NOTICES =
ImmutableList.of(POSSIBLY_INCOMPLETE_RESULT_SET_NOTICE); ImmutableList.of(POSSIBLY_INCOMPLETE_RESULT_SET_NOTICE);
/** Included when the requester is not logged in as the owner of the domain being returned. */ /** Included when the requester is not logged in as the owner of the domain being returned. */
static final ImmutableMap<String, Object> DOMAIN_CONTACTS_HIDDEN_DATA_REMARK = static final Remark DOMAIN_CONTACTS_HIDDEN_DATA_REMARK =
ImmutableMap.of( Remark.builder()
"title", .setTitle("Contacts Hidden")
"Contacts Hidden", .setDescription("Domain contacts are visible only to the owning registrar.")
"description", .setType(Remark.Type.OBJECT_TRUNCATED_UNEXPLAINABLE)
ImmutableList.of("Domain contacts are visible only to the owning registrar."), .build();
"type",
"object truncated due to unexplainable reasons");
/** /**
* Included when requester is not logged in as the owner of the contact being returned. Format * Included when requester is not logged in as the owner of the contact being returned. Format
* required by ICANN RDAP Pilot Profile draft section 1.4.11. * required by ICANN RDAP Response Profile 15feb19 section 2.7.4.3.
*/ */
static final ImmutableMap<String, Object> CONTACT_PERSONAL_DATA_HIDDEN_DATA_REMARK = static final Remark CONTACT_PERSONAL_DATA_HIDDEN_DATA_REMARK =
ImmutableMap.of( Remark.builder()
"title", .setTitle("Redacted for Privacy")
"Data Policy", .setDescription(
"description",
ImmutableList.of(
"Some of the data in this object has been removed.", "Some of the data in this object has been removed.",
"Contact personal data is visible only to the owning registrar."), "Contact personal data is visible only to the owning registrar.")
"type", .setType(Remark.Type.OBJECT_REDACTED_AUTHORIZATION)
"object truncated due to authorization", .addLink(
"links", Link.builder()
ImmutableList.of( .setValue(
ImmutableMap.of( "https://github.com/google/nomulus/blob/master/docs/rdap.md#authentication")
"value", .setRel("alternate")
"https://github.com/google/nomulus/blob/master/docs/rdap.md#authentication", .setHref(
"rel", "alternate", "https://github.com/google/nomulus/blob/master/docs/rdap.md#authentication")
"href", .setType("text/html")
"https://github.com/google/nomulus/blob/master/docs/rdap.md#authentication", .build())
"type", "text/html"))); .build();
} }

View file

@ -17,8 +17,8 @@ package google.registry.rdap;
import static google.registry.request.Action.Method.GET; import static google.registry.request.Action.Method.GET;
import static google.registry.request.Action.Method.HEAD; import static google.registry.request.Action.Method.HEAD;
import com.google.common.collect.ImmutableMap;
import google.registry.rdap.RdapMetrics.EndpointType; import google.registry.rdap.RdapMetrics.EndpointType;
import google.registry.rdap.RdapObjectClasses.ReplyPayloadBase;
import google.registry.request.Action; import google.registry.request.Action;
import google.registry.request.HttpException.NotImplementedException; import google.registry.request.HttpException.NotImplementedException;
import google.registry.request.auth.Auth; import google.registry.request.auth.Auth;
@ -43,8 +43,7 @@ public class RdapIpAction extends RdapActionBase {
} }
@Override @Override
public ImmutableMap<String, Object> getJsonObjectForResource( public ReplyPayloadBase getJsonObjectForResource(String pathSearchString, boolean isHeadRequest) {
String pathSearchString, boolean isHeadRequest) {
throw new NotImplementedException("Domain Name Registry information only"); throw new NotImplementedException("Domain Name Registry information only");
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -20,11 +20,11 @@ import static google.registry.request.Action.Method.GET;
import static google.registry.request.Action.Method.HEAD; import static google.registry.request.Action.Method.HEAD;
import static google.registry.util.DateTimeUtils.START_OF_TIME; import static google.registry.util.DateTimeUtils.START_OF_TIME;
import com.google.common.collect.ImmutableMap;
import google.registry.flows.EppException; import google.registry.flows.EppException;
import google.registry.model.host.HostResource; import google.registry.model.host.HostResource;
import google.registry.rdap.RdapJsonFormatter.OutputDataType; import google.registry.rdap.RdapJsonFormatter.OutputDataType;
import google.registry.rdap.RdapMetrics.EndpointType; import google.registry.rdap.RdapMetrics.EndpointType;
import google.registry.rdap.RdapObjectClasses.RdapNameserver;
import google.registry.request.Action; import google.registry.request.Action;
import google.registry.request.HttpException.BadRequestException; import google.registry.request.HttpException.BadRequestException;
import google.registry.request.HttpException.NotFoundException; import google.registry.request.HttpException.NotFoundException;
@ -47,8 +47,7 @@ public class RdapNameserverAction extends RdapActionBase {
} }
@Override @Override
public ImmutableMap<String, Object> getJsonObjectForResource( public RdapNameserver getJsonObjectForResource(String pathSearchString, boolean isHeadRequest) {
String pathSearchString, boolean isHeadRequest) {
DateTime now = clock.nowUtc(); DateTime now = clock.nowUtc();
pathSearchString = canonicalizeName(pathSearchString); pathSearchString = canonicalizeName(pathSearchString);
// The RDAP syntax is /rdap/nameserver/ns1.mydomain.com. // The RDAP syntax is /rdap/nameserver/ns1.mydomain.com.
@ -69,6 +68,6 @@ public class RdapNameserverAction extends RdapActionBase {
throw new NotFoundException(pathSearchString + " not found"); throw new NotFoundException(pathSearchString + " not found");
} }
return rdapJsonFormatter.makeRdapJsonForHost( return rdapJsonFormatter.makeRdapJsonForHost(
hostResource.get(), true, fullServletPath, rdapWhoisServer, now, OutputDataType.FULL); hostResource.get(), rdapWhoisServer, now, OutputDataType.FULL);
} }
} }

View file

@ -18,8 +18,6 @@ import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.request.Action.Method.GET; import static google.registry.request.Action.Method.GET;
import static google.registry.request.Action.Method.HEAD; import static google.registry.request.Action.Method.HEAD;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.net.InetAddresses; import com.google.common.net.InetAddresses;
@ -27,11 +25,11 @@ import com.google.common.primitives.Booleans;
import com.googlecode.objectify.cmd.Query; import com.googlecode.objectify.cmd.Query;
import google.registry.model.domain.DomainBase; import google.registry.model.domain.DomainBase;
import google.registry.model.host.HostResource; import google.registry.model.host.HostResource;
import google.registry.rdap.RdapJsonFormatter.BoilerplateType;
import google.registry.rdap.RdapJsonFormatter.OutputDataType; import google.registry.rdap.RdapJsonFormatter.OutputDataType;
import google.registry.rdap.RdapMetrics.EndpointType; import google.registry.rdap.RdapMetrics.EndpointType;
import google.registry.rdap.RdapMetrics.SearchType; import google.registry.rdap.RdapMetrics.SearchType;
import google.registry.rdap.RdapSearchResults.IncompletenessWarningType; import google.registry.rdap.RdapSearchResults.IncompletenessWarningType;
import google.registry.rdap.RdapSearchResults.NameserverSearchResponse;
import google.registry.request.Action; import google.registry.request.Action;
import google.registry.request.HttpException.BadRequestException; import google.registry.request.HttpException.BadRequestException;
import google.registry.request.HttpException.NotFoundException; import google.registry.request.HttpException.NotFoundException;
@ -82,7 +80,7 @@ public class RdapNameserverSearchAction extends RdapSearchActionBase {
* <p>The RDAP spec allows nameserver search by either name or IP address. * <p>The RDAP spec allows nameserver search by either name or IP address.
*/ */
@Override @Override
public ImmutableMap<String, Object> getJsonObjectForResource( public NameserverSearchResponse getJsonObjectForResource(
String pathSearchString, boolean isHeadRequest) { String pathSearchString, boolean isHeadRequest) {
DateTime now = clock.nowUtc(); DateTime now = clock.nowUtc();
// RDAP syntax example: /rdap/nameservers?name=ns*.example.com. // RDAP syntax example: /rdap/nameservers?name=ns*.example.com.
@ -94,7 +92,7 @@ public class RdapNameserverSearchAction extends RdapSearchActionBase {
throw new BadRequestException("You must specify either name=XXXX or ip=YYYY"); throw new BadRequestException("You must specify either name=XXXX or ip=YYYY");
} }
decodeCursorToken(); decodeCursorToken();
RdapSearchResults results; NameserverSearchResponse results;
if (nameParam.isPresent()) { if (nameParam.isPresent()) {
// syntax: /rdap/nameservers?name=exam*.com // syntax: /rdap/nameservers?name=exam*.com
metricInformationBuilder.setSearchType(SearchType.BY_NAMESERVER_NAME); metricInformationBuilder.setSearchType(SearchType.BY_NAMESERVER_NAME);
@ -118,19 +116,10 @@ public class RdapNameserverSearchAction extends RdapSearchActionBase {
} }
results = searchByIp(inetAddress, now); results = searchByIp(inetAddress, now);
} }
if (results.jsonList().isEmpty()) { if (results.nameserverSearchResults().isEmpty()) {
throw new NotFoundException("No nameservers found"); throw new NotFoundException("No nameservers found");
} }
ImmutableMap.Builder<String, Object> jsonBuilder = new ImmutableMap.Builder<>(); return results;
jsonBuilder.put("nameserverSearchResults", results.jsonList());
rdapJsonFormatter.addTopLevelEntries(
jsonBuilder,
BoilerplateType.NAMESERVER,
getNotices(results),
ImmutableList.of(),
fullServletPath);
return jsonBuilder.build();
} }
/** /**
@ -139,7 +128,7 @@ public class RdapNameserverSearchAction extends RdapSearchActionBase {
* <p>When deleted nameservers are included in the search, the search is treated as if it has a * <p>When deleted nameservers are included in the search, the search is treated as if it has a
* wildcard, because multiple results can be returned. * wildcard, because multiple results can be returned.
*/ */
private RdapSearchResults searchByName( private NameserverSearchResponse searchByName(
final RdapSearchPattern partialStringQuery, final DateTime now) { final RdapSearchPattern partialStringQuery, final DateTime now) {
// Handle queries without a wildcard -- just load by foreign key. We can't do this if deleted // Handle queries without a wildcard -- just load by foreign key. We can't do this if deleted
// nameservers are desired, because there may be multiple nameservers with the same name. // nameservers are desired, because there may be multiple nameservers with the same name.
@ -168,7 +157,7 @@ public class RdapNameserverSearchAction extends RdapSearchActionBase {
* *
* <p>In this case, we can load by foreign key. * <p>In this case, we can load by foreign key.
*/ */
private RdapSearchResults searchByNameUsingForeignKey( private NameserverSearchResponse searchByNameUsingForeignKey(
final RdapSearchPattern partialStringQuery, final DateTime now) { final RdapSearchPattern partialStringQuery, final DateTime now) {
Optional<HostResource> hostResource = Optional<HostResource> hostResource =
loadByForeignKey(HostResource.class, partialStringQuery.getInitialString(), now); loadByForeignKey(HostResource.class, partialStringQuery.getInitialString(), now);
@ -177,19 +166,21 @@ public class RdapNameserverSearchAction extends RdapSearchActionBase {
throw new NotFoundException("No nameservers found"); throw new NotFoundException("No nameservers found");
} }
metricInformationBuilder.setNumHostsRetrieved(1); metricInformationBuilder.setNumHostsRetrieved(1);
return RdapSearchResults.create(
ImmutableList.of( NameserverSearchResponse.Builder builder =
NameserverSearchResponse.builder()
.setIncompletenessWarningType(IncompletenessWarningType.COMPLETE);
builder.nameserverSearchResultsBuilder().add(
rdapJsonFormatter.makeRdapJsonForHost( rdapJsonFormatter.makeRdapJsonForHost(
hostResource.get(), hostResource.get(),
false,
fullServletPath,
rdapWhoisServer, rdapWhoisServer,
now, now,
OutputDataType.FULL))); OutputDataType.FULL));
return builder.build();
} }
/** Searches for nameservers by name using the superordinate domain as a suffix. */ /** Searches for nameservers by name using the superordinate domain as a suffix. */
private RdapSearchResults searchByNameUsingSuperordinateDomain( private NameserverSearchResponse searchByNameUsingSuperordinateDomain(
final RdapSearchPattern partialStringQuery, final DateTime now) { final RdapSearchPattern partialStringQuery, final DateTime now) {
Optional<DomainBase> domainBase = Optional<DomainBase> domainBase =
loadByForeignKey(DomainBase.class, partialStringQuery.getSuffix(), now); loadByForeignKey(DomainBase.class, partialStringQuery.getSuffix(), now);
@ -232,7 +223,7 @@ public class RdapNameserverSearchAction extends RdapSearchActionBase {
* *
* <p>There are no pending deletes for hosts, so we can call {@link RdapActionBase#queryItems}. * <p>There are no pending deletes for hosts, so we can call {@link RdapActionBase#queryItems}.
*/ */
private RdapSearchResults searchByNameUsingPrefix( private NameserverSearchResponse searchByNameUsingPrefix(
final RdapSearchPattern partialStringQuery, final DateTime now) { final RdapSearchPattern partialStringQuery, final DateTime now) {
// Add 1 so we can detect truncation. // Add 1 so we can detect truncation.
int querySizeLimit = getStandardQuerySizeLimit(); int querySizeLimit = getStandardQuerySizeLimit();
@ -251,7 +242,7 @@ public class RdapNameserverSearchAction extends RdapSearchActionBase {
} }
/** Searches for nameservers by IP address, returning a JSON array of nameserver info maps. */ /** Searches for nameservers by IP address, returning a JSON array of nameserver info maps. */
private RdapSearchResults searchByIp(final InetAddress inetAddress, DateTime now) { private NameserverSearchResponse searchByIp(final InetAddress inetAddress, DateTime now) {
// Add 1 so we can detect truncation. // Add 1 so we can detect truncation.
int querySizeLimit = getStandardQuerySizeLimit(); int querySizeLimit = getStandardQuerySizeLimit();
Query<HostResource> query = Query<HostResource> query =
@ -270,7 +261,7 @@ public class RdapNameserverSearchAction extends RdapSearchActionBase {
} }
/** Output JSON for a lists of hosts contained in an {@link RdapResultSet}. */ /** Output JSON for a lists of hosts contained in an {@link RdapResultSet}. */
private RdapSearchResults makeSearchResults( private NameserverSearchResponse makeSearchResults(
RdapResultSet<HostResource> resultSet, CursorType cursorType, DateTime now) { RdapResultSet<HostResource> resultSet, CursorType cursorType, DateTime now) {
return makeSearchResults( return makeSearchResults(
resultSet.resources(), resultSet.resources(),
@ -281,7 +272,7 @@ public class RdapNameserverSearchAction extends RdapSearchActionBase {
} }
/** Output JSON for a list of hosts. */ /** Output JSON for a list of hosts. */
private RdapSearchResults makeSearchResults( private NameserverSearchResponse makeSearchResults(
List<HostResource> hosts, List<HostResource> hosts,
IncompletenessWarningType incompletenessWarningType, IncompletenessWarningType incompletenessWarningType,
int numHostsRetrieved, int numHostsRetrieved,
@ -290,8 +281,8 @@ public class RdapNameserverSearchAction extends RdapSearchActionBase {
metricInformationBuilder.setNumHostsRetrieved(numHostsRetrieved); metricInformationBuilder.setNumHostsRetrieved(numHostsRetrieved);
OutputDataType outputDataType = OutputDataType outputDataType =
(hosts.size() > 1) ? OutputDataType.SUMMARY : OutputDataType.FULL; (hosts.size() > 1) ? OutputDataType.SUMMARY : OutputDataType.FULL;
ImmutableList.Builder<ImmutableMap<String, Object>> jsonListBuilder = NameserverSearchResponse.Builder builder =
new ImmutableList.Builder<>(); NameserverSearchResponse.builder().setIncompletenessWarningType(incompletenessWarningType);
Optional<String> newCursor = Optional.empty(); Optional<String> newCursor = Optional.empty();
for (HostResource host : Iterables.limit(hosts, rdapResultSetMaxSize)) { for (HostResource host : Iterables.limit(hosts, rdapResultSetMaxSize)) {
newCursor = newCursor =
@ -299,15 +290,14 @@ public class RdapNameserverSearchAction extends RdapSearchActionBase {
(cursorType == CursorType.NAME) (cursorType == CursorType.NAME)
? host.getFullyQualifiedHostName() ? host.getFullyQualifiedHostName()
: host.getRepoId()); : host.getRepoId());
jsonListBuilder.add( builder.nameserverSearchResultsBuilder().add(
rdapJsonFormatter.makeRdapJsonForHost( rdapJsonFormatter.makeRdapJsonForHost(
host, false, fullServletPath, rdapWhoisServer, now, outputDataType)); host, rdapWhoisServer, now, outputDataType));
}
ImmutableList<ImmutableMap<String, Object>> jsonList = jsonListBuilder.build();
if (jsonList.size() < hosts.size()) {
return RdapSearchResults.create(jsonList, IncompletenessWarningType.TRUNCATED, newCursor);
} else {
return RdapSearchResults.create(jsonList, incompletenessWarningType, Optional.empty());
} }
if (rdapResultSetMaxSize < hosts.size()) {
builder.setNextPageUri(createNavigationUri(newCursor.get()));
builder.setIncompletenessWarningType(IncompletenessWarningType.TRUNCATED);
}
return builder.build();
} }
} }

View file

@ -0,0 +1,442 @@
// Copyright 2019 The Nomulus Authors. 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 google.registry.rdap;
import static google.registry.util.DomainNameUtils.ACE_PREFIX;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Ordering;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
import google.registry.rdap.AbstractJsonableObject.RestrictJsonNames;
import google.registry.rdap.RdapDataStructures.Event;
import google.registry.rdap.RdapDataStructures.EventWithoutActor;
import google.registry.rdap.RdapDataStructures.LanguageIdentifier;
import google.registry.rdap.RdapDataStructures.Link;
import google.registry.rdap.RdapDataStructures.Notice;
import google.registry.rdap.RdapDataStructures.ObjectClassName;
import google.registry.rdap.RdapDataStructures.Port43WhoisServer;
import google.registry.rdap.RdapDataStructures.PublicId;
import google.registry.rdap.RdapDataStructures.RdapConformance;
import google.registry.rdap.RdapDataStructures.RdapStatus;
import google.registry.rdap.RdapDataStructures.Remark;
import google.registry.util.Idn;
import java.util.Optional;
/**
* Object Classes defined in RFC7483 section 5.
*/
final class RdapObjectClasses {
/**
* Temporary implementation of VCards.
*
* Will create a better implementation soon.
*/
@RestrictJsonNames({})
@AutoValue
abstract static class Vcard implements Jsonable {
abstract String property();
abstract ImmutableMap<String, ImmutableList<String>> parameters();
abstract String valueType();
abstract JsonElement value();
static Vcard create(
String property,
ImmutableMap<String, ImmutableList<String>> parameters,
String valueType,
JsonElement value) {
return new AutoValue_RdapObjectClasses_Vcard(property, parameters, valueType, value);
}
static Vcard create(
String property,
ImmutableMap<String, ImmutableList<String>> parameters,
String valueType,
String value) {
return create(property, parameters, valueType, new JsonPrimitive(value));
}
static Vcard create(String property, String valueType, JsonElement value) {
return create(property, ImmutableMap.of(), valueType, value);
}
static Vcard create(String property, String valueType, String value) {
return create(property, valueType, new JsonPrimitive(value));
}
@Override
public JsonArray toJson() {
JsonArray jsonArray = new JsonArray();
jsonArray.add(property());
jsonArray.add(new Gson().toJsonTree(parameters()));
jsonArray.add(valueType());
jsonArray.add(value());
return jsonArray;
}
}
@RestrictJsonNames("vcardArray")
@AutoValue
abstract static class VcardArray implements Jsonable {
private static final String VCARD_VERSION_NUMBER = "4.0";
private static final Vcard VCARD_ENTRY_VERSION =
Vcard.create("version", "text", VCARD_VERSION_NUMBER);
abstract ImmutableList<Vcard> vcards();
@Override
public JsonArray toJson() {
JsonArray jsonArray = new JsonArray();
jsonArray.add("vcard");
JsonArray jsonVcardsArray = new JsonArray();
jsonVcardsArray.add(VCARD_ENTRY_VERSION.toJson());
vcards().forEach(vcard -> jsonVcardsArray.add(vcard.toJson()));
jsonArray.add(jsonVcardsArray);
return jsonArray;
}
static Builder builder() {
return new AutoValue_RdapObjectClasses_VcardArray.Builder();
}
@AutoValue.Builder
abstract static class Builder {
abstract ImmutableList.Builder<Vcard> vcardsBuilder();
Builder add(Vcard vcard) {
vcardsBuilder().add(vcard);
return this;
}
abstract VcardArray build();
}
}
/**
* Indication of 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. On the other
* hand, remarks are not allowed except in domain, nameserver and entity objects, so we need to
* suppress them for other types of responses (e.g. help).
*/
public enum BoilerplateType {
DOMAIN,
NAMESERVER,
ENTITY,
OTHER
}
/**
* An object that can be used to create a TopLevelReply.
*
* All Actions need to return an object of this type.
*/
@RestrictJsonNames("*")
abstract static class ReplyPayloadBase extends AbstractJsonableObject {
final BoilerplateType boilerplateType;
ReplyPayloadBase(BoilerplateType boilerplateType) {
this.boilerplateType = boilerplateType;
}
}
/**
* The Top Level JSON reply, Adds the required top-level boilerplate to a ReplyPayloadBase.
*
* <p>RFC 7483 specifies that the top-level object should include an entry indicating the
* conformance level. ICANN RDAP spec for 15feb19 mandates several additional entries, in sections
* 2.6.3, 2.11 of the Response Profile and 3.3.2, 3.5, of the Technical Implementation Guide.
*/
@AutoValue
@RestrictJsonNames({})
abstract static class TopLevelReplyObject extends AbstractJsonableObject {
@JsonableElement("rdapConformance")
static final RdapConformance RDAP_CONFORMANCE = RdapConformance.INSTANCE;
@JsonableElement("*") abstract ReplyPayloadBase aAreplyObject();
@JsonableElement("notices[]") abstract Notice aTosNotice();
@JsonableElement("notices") ImmutableList<Notice> boilerplateNotices() {
switch (aAreplyObject().boilerplateType) {
case DOMAIN:
return RdapIcannStandardInformation.domainBoilerplateNotices;
case NAMESERVER:
case ENTITY:
return RdapIcannStandardInformation.nameserverAndEntityBoilerplateNotices;
default: // things other than domains, nameservers and entities do not yet have boilerplate
return ImmutableList.of();
}
}
static TopLevelReplyObject create(ReplyPayloadBase replyObject, Notice tosNotice) {
return new AutoValue_RdapObjectClasses_TopLevelReplyObject(replyObject, tosNotice);
}
}
/**
* A base object shared by Entity, Nameserver, and Domain.
*
* <p>Not part of the spec, but seems convenient.
*/
private abstract static class RdapObjectBase extends ReplyPayloadBase {
@JsonableElement final ObjectClassName objectClassName;
@JsonableElement abstract Optional<String> handle();
@JsonableElement abstract ImmutableList<PublicId> publicIds();
@JsonableElement abstract ImmutableList<RdapEntity> entities();
@JsonableElement abstract ImmutableList<RdapStatus> status();
@JsonableElement abstract ImmutableList<Remark> remarks();
@JsonableElement abstract ImmutableList<Link> links();
@JsonableElement abstract Optional<Port43WhoisServer> port43();
@JsonableElement abstract ImmutableList<Event> events();
RdapObjectBase(BoilerplateType boilerplateType, ObjectClassName objectClassName) {
super(boilerplateType);
this.objectClassName = objectClassName;
}
abstract static class Builder<B extends Builder<?>> {
abstract B setHandle(String handle);
abstract ImmutableList.Builder<PublicId> publicIdsBuilder();
abstract ImmutableList.Builder<RdapEntity> entitiesBuilder();
abstract ImmutableList.Builder<RdapStatus> statusBuilder();
abstract ImmutableList.Builder<Remark> remarksBuilder();
abstract ImmutableList.Builder<Link> linksBuilder();
abstract B setPort43(Port43WhoisServer port43);
abstract ImmutableList.Builder<Event> eventsBuilder();
}
}
/**
* The Entity Object Class defined in 5.1 of RFC7483.
*
* <p>We're missing the "autnums" and "networks" fields
*/
@RestrictJsonNames({"entities[]", "entitySearchResults[]"})
@AutoValue
abstract static class RdapEntity extends RdapObjectBase {
/** Role values specified in RFC 7483 § 10.2.4. */
@RestrictJsonNames("roles[]")
enum Role implements Jsonable {
REGISTRANT("registrant"),
TECH("technical"),
ADMIN("administrative"),
ABUSE("abuse"),
BILLING("billing"),
REGISTRAR("registrar"),
RESELLER("reseller"),
SPONSOR("sponsor"),
PROXY("proxy"),
NOTIFICATIONS("notifications"),
NOC("noc");
/** Value as it appears in RDAP messages. */
final String rfc7483String;
Role(String rfc7483String) {
this.rfc7483String = rfc7483String;
}
@Override
public JsonPrimitive toJson() {
return new JsonPrimitive(rfc7483String);
}
}
RdapEntity() {
super(BoilerplateType.ENTITY, ObjectClassName.ENTITY);
}
@JsonableElement abstract Optional<VcardArray> vcardArray();
@JsonableElement abstract ImmutableSet<Role> roles();
@JsonableElement abstract ImmutableList<EventWithoutActor> asEventActor();
static Builder builder() {
return new AutoValue_RdapObjectClasses_RdapEntity.Builder();
}
@AutoValue.Builder
abstract static class Builder extends RdapObjectBase.Builder<Builder> {
abstract Builder setVcardArray(VcardArray vcardArray);
abstract ImmutableSet.Builder<Role> rolesBuilder();
abstract ImmutableList.Builder<EventWithoutActor> asEventActorBuilder();
abstract RdapEntity build();
}
}
/**
* A base object shared by Nameserver, and Domain.
*
* <p>Takes care of the name and unicode field.
*
* <p>See RDAP Response Profile 15feb19 sections 2.1 and 4.1.
*
* <p>Not part of the spec, but seems convenient.
*/
private abstract static class RdapNamedObjectBase extends RdapObjectBase {
@JsonableElement abstract String ldhName();
@JsonableElement final Optional<String> unicodeName() {
// Only include the unicodeName field if there are unicode characters.
//
// TODO(b/127490882) Consider removing the condition (i.e. always having the unicodeName
// field)
if (!hasUnicodeComponents(ldhName())) {
return Optional.empty();
}
return Optional.of(Idn.toUnicode(ldhName()));
}
private static boolean hasUnicodeComponents(String fullyQualifiedName) {
return fullyQualifiedName.startsWith(ACE_PREFIX)
|| fullyQualifiedName.contains("." + ACE_PREFIX);
}
abstract static class Builder<B extends Builder<?>> extends RdapObjectBase.Builder<B> {
abstract B setLdhName(String ldhName);
}
RdapNamedObjectBase(BoilerplateType boilerplateType, ObjectClassName objectClassName) {
super(boilerplateType, objectClassName);
}
}
/**
* The Nameserver Object Class defined in 5.2 of RFC7483.
*/
@RestrictJsonNames({"nameservers[]", "nameserverSearchResults[]"})
@AutoValue
abstract static class RdapNameserver extends RdapNamedObjectBase {
@JsonableElement Optional<IpAddresses> ipAddresses() {
if (ipv6().isEmpty() && ipv4().isEmpty()) {
return Optional.empty();
}
return Optional.of(new IpAddresses());
}
abstract ImmutableList<String> ipv6();
abstract ImmutableList<String> ipv4();
class IpAddresses extends AbstractJsonableObject {
@JsonableElement ImmutableList<String> v6() {
return Ordering.natural().immutableSortedCopy(ipv6());
}
@JsonableElement ImmutableList<String> v4() {
return Ordering.natural().immutableSortedCopy(ipv4());
}
}
RdapNameserver() {
super(BoilerplateType.NAMESERVER, ObjectClassName.NAMESERVER);
}
static Builder builder() {
return new AutoValue_RdapObjectClasses_RdapNameserver.Builder();
}
@AutoValue.Builder
abstract static class Builder extends RdapNamedObjectBase.Builder<Builder> {
abstract ImmutableList.Builder<String> ipv6Builder();
abstract ImmutableList.Builder<String> ipv4Builder();
abstract RdapNameserver build();
}
}
/**
* The Domain Object Class defined in 5.3 of RFC7483.
*
* We're missing the "variants", "secureDNS", "network" fields
*/
@RestrictJsonNames("domainSearchResults[]")
@AutoValue
abstract static class RdapDomain extends RdapNamedObjectBase {
@JsonableElement abstract ImmutableList<RdapNameserver> nameservers();
RdapDomain() {
super(BoilerplateType.DOMAIN, ObjectClassName.DOMAIN);
}
static Builder builder() {
return new AutoValue_RdapObjectClasses_RdapDomain.Builder();
}
@AutoValue.Builder
abstract static class Builder extends RdapNamedObjectBase.Builder<Builder> {
abstract ImmutableList.Builder<RdapNameserver> nameserversBuilder();
abstract RdapDomain build();
}
}
/**
* Error Response Body defined in 6 of RFC7483.
*/
@RestrictJsonNames({})
@AutoValue
abstract static class ErrorResponse extends ReplyPayloadBase {
@JsonableElement final LanguageIdentifier lang = LanguageIdentifier.EN;
@JsonableElement abstract int errorCode();
@JsonableElement abstract String title();
@JsonableElement abstract ImmutableList<String> description();
ErrorResponse() {
super(BoilerplateType.OTHER);
}
static ErrorResponse create(int status, String title, String description) {
return new AutoValue_RdapObjectClasses_ErrorResponse(
status, title, ImmutableList.of(description));
}
}
/**
* Help Response defined in 7 of RFC7483.
*
* <p>The helpNotice field is optional, because if the user requests the TOS - that's already
* given by the boilerplate of TopLevelReplyObject so we don't want to give it again.
*/
@RestrictJsonNames({})
@AutoValue
abstract static class HelpResponse extends ReplyPayloadBase {
@JsonableElement("notices[]") abstract Optional<Notice> helpNotice();
HelpResponse() {
super(BoilerplateType.OTHER);
}
static HelpResponse create(Optional<Notice> helpNotice) {
return new AutoValue_RdapObjectClasses_HelpResponse(helpNotice);
}
}
private RdapObjectClasses() {}
}

View file

@ -18,12 +18,12 @@ import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMap;
import google.registry.rdap.RdapMetrics.EndpointType; import google.registry.rdap.RdapMetrics.EndpointType;
import google.registry.request.Parameter; import google.registry.request.Parameter;
import google.registry.request.ParameterMap; import google.registry.request.ParameterMap;
import google.registry.request.RequestUrl; import google.registry.request.RequestUrl;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.util.Base64; import java.util.Base64;
import java.util.List; import java.util.List;
@ -119,19 +119,8 @@ public abstract class RdapSearchActionBase extends RdapActionBase {
} }
} }
ImmutableList<ImmutableMap<String, Object>> getNotices(RdapSearchResults results) { /** Creates the URL for this same search with a different starting point cursor. */
ImmutableList<ImmutableMap<String, Object>> notices = results.getIncompletenessWarnings(); URI createNavigationUri(String cursor) {
if (results.nextCursor().isPresent()) { return URI.create(getRequestUrlWithExtraParameter("cursor", encodeCursorToken(cursor)));
ImmutableList.Builder<ImmutableMap<String, Object>> noticesBuilder =
new ImmutableList.Builder<>();
noticesBuilder.addAll(notices);
noticesBuilder.add(
RdapJsonFormatter.makeRdapJsonNavigationLinkNotice(
Optional.of(
getRequestUrlWithExtraParameter(
"cursor", encodeCursorToken(results.nextCursor().get())))));
notices = noticesBuilder.build();
}
return notices;
} }
} }

View file

@ -20,6 +20,14 @@ import static google.registry.rdap.RdapIcannStandardInformation.TRUNCATION_NOTIC
import com.google.auto.value.AutoValue; import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import google.registry.rdap.RdapDataStructures.Link;
import google.registry.rdap.RdapDataStructures.Notice;
import google.registry.rdap.RdapObjectClasses.BoilerplateType;
import google.registry.rdap.RdapObjectClasses.RdapDomain;
import google.registry.rdap.RdapObjectClasses.RdapEntity;
import google.registry.rdap.RdapObjectClasses.RdapNameserver;
import google.registry.rdap.RdapObjectClasses.ReplyPayloadBase;
import java.net.URI;
import java.util.Optional; import java.util.Optional;
/** /**
@ -31,6 +39,130 @@ import java.util.Optional;
@AutoValue @AutoValue
abstract class RdapSearchResults { abstract class RdapSearchResults {
/**
* Responding To Searches defined in 8 of RFC7483.
*/
abstract static class BaseSearchResponse extends ReplyPayloadBase {
abstract IncompletenessWarningType incompletenessWarningType();
abstract ImmutableMap<String, URI> navigationLinks();
@JsonableElement("notices") ImmutableList<Notice> getIncompletenessWarnings() {
switch (incompletenessWarningType()) {
case TRUNCATED:
return TRUNCATION_NOTICES;
case MIGHT_BE_INCOMPLETE:
return POSSIBLY_INCOMPLETE_NOTICES;
case COMPLETE:
break;
}
return ImmutableList.of();
}
/**
* Creates a JSON object containing a notice with page navigation links.
*
* <p>At the moment, only a next page link is supported. Other types of links (e.g. previous
* page) could be added in the future, but it's not clear how to generate such links, given the
* way we are querying the database.
*
* <p>This isn't part of the spec.
*/
@JsonableElement("notices[]")
Optional<Notice> getNavigationNotice() {
if (navigationLinks().isEmpty()) {
return Optional.empty();
}
Notice.Builder builder =
Notice.builder().setTitle("Navigation Links").setDescription("Links to related pages.");
navigationLinks().forEach((name, uri) ->
builder.linksBuilder()
.add(Link.builder()
.setRel(name)
.setHref(uri.toString())
.setType("application/rdap+json")
.build()));
return Optional.of(builder.build());
}
BaseSearchResponse(BoilerplateType boilerplateType) {
super(boilerplateType);
}
abstract static class Builder<B extends Builder<?>> {
abstract ImmutableMap.Builder<String, URI> navigationLinksBuilder();
abstract B setIncompletenessWarningType(IncompletenessWarningType type);
@SuppressWarnings("unchecked")
B setNextPageUri(URI uri) {
navigationLinksBuilder().put("next", uri);
return (B) this;
}
}
}
@AutoValue
abstract static class DomainSearchResponse extends BaseSearchResponse {
@JsonableElement abstract ImmutableList<RdapDomain> domainSearchResults();
DomainSearchResponse() {
super(BoilerplateType.DOMAIN);
}
static Builder builder() {
return new AutoValue_RdapSearchResults_DomainSearchResponse.Builder();
}
@AutoValue.Builder
abstract static class Builder extends BaseSearchResponse.Builder<Builder> {
abstract ImmutableList.Builder<RdapDomain> domainSearchResultsBuilder();
abstract DomainSearchResponse build();
}
}
@AutoValue
abstract static class EntitySearchResponse extends BaseSearchResponse {
@JsonableElement public abstract ImmutableList<RdapEntity> entitySearchResults();
EntitySearchResponse() {
super(BoilerplateType.ENTITY);
}
static Builder builder() {
return new AutoValue_RdapSearchResults_EntitySearchResponse.Builder();
}
@AutoValue.Builder
abstract static class Builder extends BaseSearchResponse.Builder<Builder> {
abstract ImmutableList.Builder<RdapEntity> entitySearchResultsBuilder();
abstract EntitySearchResponse build();
}
}
@AutoValue
abstract static class NameserverSearchResponse extends BaseSearchResponse {
@JsonableElement public abstract ImmutableList<RdapNameserver> nameserverSearchResults();
NameserverSearchResponse() {
super(BoilerplateType.NAMESERVER);
}
static Builder builder() {
return new AutoValue_RdapSearchResults_NameserverSearchResponse.Builder();
}
@AutoValue.Builder
abstract static class Builder extends BaseSearchResponse.Builder<Builder> {
abstract ImmutableList.Builder<RdapNameserver> nameserverSearchResultsBuilder();
abstract NameserverSearchResponse build();
}
}
enum IncompletenessWarningType { enum IncompletenessWarningType {
/** Result set is complete. */ /** Result set is complete. */
@ -45,35 +177,4 @@ abstract class RdapSearchResults {
*/ */
MIGHT_BE_INCOMPLETE MIGHT_BE_INCOMPLETE
} }
static RdapSearchResults create(ImmutableList<ImmutableMap<String, Object>> jsonList) {
return create(jsonList, IncompletenessWarningType.COMPLETE, Optional.empty());
}
static RdapSearchResults create(
ImmutableList<ImmutableMap<String, Object>> jsonList,
IncompletenessWarningType incompletenessWarningType,
Optional<String> nextCursor) {
return new AutoValue_RdapSearchResults(jsonList, incompletenessWarningType, nextCursor);
}
/** List of JSON result object representations. */
abstract ImmutableList<ImmutableMap<String, Object>> jsonList();
/** Type of warning to display regarding possible incomplete data. */
abstract IncompletenessWarningType incompletenessWarningType();
/** Cursor for fetching the next page of results, or empty() if there are no more. */
abstract Optional<String> nextCursor();
/** Convenience method to get the appropriate warnings for the incompleteness warning type. */
ImmutableList<ImmutableMap<String, Object>> getIncompletenessWarnings() {
if (incompletenessWarningType() == IncompletenessWarningType.TRUNCATED) {
return TRUNCATION_NOTICES;
}
if (incompletenessWarningType() == IncompletenessWarningType.MIGHT_BE_INCOMPLETE) {
return POSSIBLY_INCOMPLETE_NOTICES;
}
return ImmutableList.of();
}
} }

View file

@ -96,6 +96,12 @@ public final class RequestModule {
return req.getRequestURI(); return req.getRequestURI();
} }
/**
* Returns the part of this request's URL that calls the servlet.
*
* <p>This includes the path to the servlet, but does not include any extra path information or a
* query string.
*/
@Provides @Provides
@FullServletPath @FullServletPath
static String provideFullServletPath(HttpServletRequest req) { static String provideFullServletPath(HttpServletRequest req) {

View file

@ -19,15 +19,13 @@ import static com.google.common.truth.Truth.assertThat;
import static google.registry.request.Action.Method.GET; import static google.registry.request.Action.Method.GET;
import static google.registry.request.Action.Method.HEAD; import static google.registry.request.Action.Method.HEAD;
import static google.registry.testing.DatastoreHelper.createTld; import static google.registry.testing.DatastoreHelper.createTld;
import static google.registry.testing.TestDataHelper.loadFile;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import google.registry.rdap.RdapJsonFormatter.BoilerplateType;
import google.registry.rdap.RdapMetrics.EndpointType; import google.registry.rdap.RdapMetrics.EndpointType;
import google.registry.rdap.RdapMetrics.SearchType; import google.registry.rdap.RdapMetrics.SearchType;
import google.registry.rdap.RdapMetrics.WildcardType; import google.registry.rdap.RdapMetrics.WildcardType;
import google.registry.rdap.RdapObjectClasses.BoilerplateType;
import google.registry.rdap.RdapObjectClasses.ReplyPayloadBase;
import google.registry.rdap.RdapSearchResults.IncompletenessWarningType; import google.registry.rdap.RdapSearchResults.IncompletenessWarningType;
import google.registry.request.Action; import google.registry.request.Action;
import google.registry.request.auth.Auth; import google.registry.request.auth.Auth;
@ -59,7 +57,7 @@ public class RdapActionBaseTest extends RdapActionBaseTestCase<RdapActionBaseTes
} }
@Override @Override
public ImmutableMap<String, Object> getJsonObjectForResource( public ReplyPayloadBase getJsonObjectForResource(
String pathSearchString, boolean isHeadRequest) { String pathSearchString, boolean isHeadRequest) {
if (pathSearchString.equals("IllegalArgumentException")) { if (pathSearchString.equals("IllegalArgumentException")) {
throw new IllegalArgumentException(); throw new IllegalArgumentException();
@ -67,46 +65,36 @@ public class RdapActionBaseTest extends RdapActionBaseTestCase<RdapActionBaseTes
if (pathSearchString.equals("RuntimeException")) { if (pathSearchString.equals("RuntimeException")) {
throw new RuntimeException(); throw new RuntimeException();
} }
ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>(); return new ReplyPayloadBase(BoilerplateType.OTHER) {
builder.put("key", "value"); @JsonableElement public String key = "value";
rdapJsonFormatter.addTopLevelEntries( };
builder,
BoilerplateType.OTHER,
ImmutableList.of(),
ImmutableList.of(),
"http://myserver.example.com/");
return builder.build();
} }
} }
@Before @Before
public void setUp() { public void setUp() {
createTld("thing"); createTld("thing");
action.fullServletPath = "http://myserver.example.com" + actionPath; action.rdapJsonFormatter = RdapTestHelper.getTestRdapJsonFormatter();
} }
@Test @Test
public void testIllegalValue_showsReadableTypeName() { public void testIllegalValue_showsReadableTypeName() {
assertThat(generateActualJson("IllegalArgumentException")).isEqualTo(JSONValue.parse( assertThat(generateActualJson("IllegalArgumentException")).isEqualTo(generateExpectedJsonError(
"{\"lang\":\"en\", \"errorCode\":400, \"title\":\"Bad Request\"," "Not a valid human-readable string",
+ "\"rdapConformance\":[\"icann_rdap_response_profile_0\"]," 400));
+ "\"description\":[\"Not a valid human-readable string\"]}"));
assertThat(response.getStatus()).isEqualTo(400); assertThat(response.getStatus()).isEqualTo(400);
} }
@Test @Test
public void testRuntimeException_returns500Error() { public void testRuntimeException_returns500Error() {
assertThat(generateActualJson("RuntimeException")).isEqualTo(JSONValue.parse( assertThat(generateActualJson("RuntimeException"))
"{\"lang\":\"en\", \"errorCode\":500, \"title\":\"Internal Server Error\"," .isEqualTo(generateExpectedJsonError("An error was encountered", 500));
+ "\"rdapConformance\":[\"icann_rdap_response_profile_0\"],"
+ "\"description\":[\"An error was encountered\"]}"));
assertThat(response.getStatus()).isEqualTo(500); assertThat(response.getStatus()).isEqualTo(500);
} }
@Test @Test
public void testValidName_works() { public void testValidName_works() {
assertThat(generateActualJson("no.thing")).isEqualTo(JSONValue.parse( assertThat(generateActualJson("no.thing")).isEqualTo(loadJsonFile("rdapjson_toplevel.json"));
loadFile(this.getClass(), "rdapjson_toplevel.json")));
assertThat(response.getStatus()).isEqualTo(200); assertThat(response.getStatus()).isEqualTo(200);
} }
@ -173,18 +161,14 @@ public class RdapActionBaseTest extends RdapActionBaseTestCase<RdapActionBaseTes
.build()); .build());
} }
private String loadFileWithoutTrailingNewline(String fileName) {
String contents = loadFile(this.getClass(), fileName);
return contents.endsWith("\n") ? contents.substring(0, contents.length() - 1) : contents;
}
@Test @Test
public void testUnformatted() { public void testUnformatted() {
action.requestPath = actionPath + "no.thing"; action.requestPath = actionPath + "no.thing";
action.requestMethod = GET; action.requestMethod = GET;
action.run(); action.run();
assertThat(response.getPayload()) String payload = response.getPayload();
.isEqualTo(loadFileWithoutTrailingNewline("rdap_unformatted_output.json")); assertThat(payload).doesNotContain("\n");
assertThat(JSONValue.parse(payload)).isEqualTo(loadJsonFile("rdapjson_toplevel.json"));
} }
@Test @Test
@ -193,7 +177,8 @@ public class RdapActionBaseTest extends RdapActionBaseTestCase<RdapActionBaseTes
action.requestMethod = GET; action.requestMethod = GET;
action.formatOutputParam = Optional.of(true); action.formatOutputParam = Optional.of(true);
action.run(); action.run();
assertThat(response.getPayload()) String payload = response.getPayload();
.isEqualTo(loadFileWithoutTrailingNewline("rdap_formatted_output.json")); assertThat(payload).contains("\n");
assertThat(JSONValue.parse(payload)).isEqualTo(loadJsonFile("rdapjson_toplevel.json"));
} }
} }

View file

@ -14,16 +14,19 @@
package google.registry.rdap; package google.registry.rdap;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.rdap.RdapAuthorization.Role.ADMINISTRATOR; import static google.registry.rdap.RdapAuthorization.Role.ADMINISTRATOR;
import static google.registry.rdap.RdapAuthorization.Role.PUBLIC; import static google.registry.rdap.RdapAuthorization.Role.PUBLIC;
import static google.registry.rdap.RdapAuthorization.Role.REGISTRAR; import static google.registry.rdap.RdapAuthorization.Role.REGISTRAR;
import static google.registry.request.Action.Method.GET; import static google.registry.request.Action.Method.GET;
import static google.registry.request.Action.Method.HEAD; import static google.registry.request.Action.Method.HEAD;
import static google.registry.request.auth.AuthenticatedRegistrarAccessor.Role.OWNER; import static google.registry.request.auth.AuthenticatedRegistrarAccessor.Role.OWNER;
import static google.registry.testing.TestDataHelper.loadFile;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import com.google.appengine.api.users.User; import com.google.appengine.api.users.User;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSetMultimap; import com.google.common.collect.ImmutableSetMultimap;
import google.registry.model.ofy.Ofy; import google.registry.model.ofy.Ofy;
import google.registry.request.Action; import google.registry.request.Action;
@ -37,8 +40,10 @@ import google.registry.testing.FakeClock;
import google.registry.testing.FakeResponse; import google.registry.testing.FakeResponse;
import google.registry.testing.InjectRule; import google.registry.testing.InjectRule;
import google.registry.util.TypeUtils; import google.registry.util.TypeUtils;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue; import org.json.simple.JSONValue;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
@ -99,7 +104,6 @@ public class RdapActionBaseTestCase<A extends RdapActionBase> {
action.rdapJsonFormatter = RdapTestHelper.getTestRdapJsonFormatter(); action.rdapJsonFormatter = RdapTestHelper.getTestRdapJsonFormatter();
action.rdapMetrics = rdapMetrics; action.rdapMetrics = rdapMetrics;
action.requestMethod = Action.Method.GET; action.requestMethod = Action.Method.GET;
action.fullServletPath = "https://example.tld/rdap";
action.rdapWhoisServer = null; action.rdapWhoisServer = null;
logout(); logout();
} }
@ -126,18 +130,80 @@ public class RdapActionBaseTestCase<A extends RdapActionBase> {
metricRole = ADMINISTRATOR; metricRole = ADMINISTRATOR;
} }
protected Object generateActualJson(String domainName) { protected JSONObject generateActualJson(String domainName) {
action.requestPath = actionPath + domainName; action.requestPath = actionPath + domainName;
action.requestMethod = GET; action.requestMethod = GET;
action.run(); action.run();
return JSONValue.parse(response.getPayload()); return (JSONObject) JSONValue.parse(response.getPayload());
} }
protected String generateHeadPayload(String domainName) { protected String generateHeadPayload(String domainName) {
action.requestPath = actionPath + domainName; action.requestPath = actionPath + domainName;
action.fullServletPath = "http://myserver.example.com" + actionPath;
action.requestMethod = HEAD; action.requestMethod = HEAD;
action.run(); action.run();
return response.getPayload(); return response.getPayload();
} }
/**
* Loads a resource testdata JSON file, and applies substitutions.
*
* <p>{@code loadJsonFile("filename.json", "NANE", "something", "ID", "other")} is the same as
* {@code loadJsonFile("filename.json", ImmutableMap.of("NANE", "something", "ID", "other"))}.
*
* @param filename the name of the file from the testdata directory
* @param keysAndValues alternating substitution key and value. The substitutions are applied to
* the file before parsing it to JSON.
*/
protected JSONObject loadJsonFile(String filename, String... keysAndValues) {
checkArgument(keysAndValues.length % 2 == 0);
ImmutableMap.Builder<String, String> builder = new ImmutableMap.Builder<>();
for (int i = 0; i < keysAndValues.length; i += 2) {
if (keysAndValues[i + 1] != null) {
builder.put(keysAndValues[i], keysAndValues[i + 1]);
}
}
return loadJsonFile(filename, builder.build());
}
/**
* Loads a resource testdata JSON file, and applies substitutions.
*
* @param filename the name of the file from the testdata directory
* @param substitutions map of substitutions to apply to the file. The substitutions are applied
* to the file before parsing it to JSON.
*/
protected JSONObject loadJsonFile(String filename, Map<String, String> substitutions) {
return (JSONObject) JSONValue.parse(loadFile(this.getClass(), filename, substitutions));
}
protected JSONObject generateExpectedJsonError(
String description,
int code) {
String title;
switch (code) {
case 404:
title = "Not Found";
break;
case 500:
title = "Internal Server Error";
break;
case 501:
title = "Not Implemented";
break;
case 400:
title = "Bad Request";
break;
case 422:
title = "Unprocessable Entity";
break;
default:
title = "ERR";
break;
}
return loadJsonFile(
"rdap_error.json",
"DESCRIPTION", description,
"TITLE", title,
"CODE", String.valueOf(code));
}
} }

View file

@ -0,0 +1,177 @@
// Copyright 2019 The Nomulus Authors. 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 google.registry.rdap;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.rdap.RdapTestHelper.createJson;
import com.google.common.collect.ImmutableSet;
import google.registry.rdap.RdapDataStructures.Event;
import google.registry.rdap.RdapDataStructures.EventAction;
import google.registry.rdap.RdapDataStructures.EventWithoutActor;
import google.registry.rdap.RdapDataStructures.LanguageIdentifier;
import google.registry.rdap.RdapDataStructures.Link;
import google.registry.rdap.RdapDataStructures.Notice;
import google.registry.rdap.RdapDataStructures.ObjectClassName;
import google.registry.rdap.RdapDataStructures.Port43WhoisServer;
import google.registry.rdap.RdapDataStructures.PublicId;
import google.registry.rdap.RdapDataStructures.RdapConformance;
import google.registry.rdap.RdapDataStructures.RdapStatus;
import google.registry.rdap.RdapDataStructures.Remark;
import org.joda.time.DateTime;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public final class RdapDataStructuresTest {
private void assertRestrictedNames(Object object, String... names) {
assertThat(AbstractJsonableObject.getNameRestriction(object.getClass()).get())
.containsExactlyElementsIn(ImmutableSet.copyOf(names));
}
@Test
public void testRdapConformance() {
assertThat(RdapConformance.INSTANCE.toJson())
.isEqualTo(createJson("['icann_rdap_response_profile_0']"));
}
@Test
public void testLink() {
Link link =
Link.builder()
.setHref("myHref")
.setRel("myRel")
.setTitle("myTitle")
.setType("myType")
.build();
assertThat(link.toJson())
.isEqualTo(createJson("{'href':'myHref','rel':'myRel','title':'myTitle','type':'myType'}"));
assertRestrictedNames(link, "links[]");
}
@Test
public void testNotice() {
Notice notice = Notice.builder()
.setDescription("AAA", "BBB")
.setTitle("myTitle")
.addLink(Link.builder().setHref("myHref").setTitle("myLink").build())
.setType(Notice.Type.RESULT_TRUNCATED_AUTHORIZATION)
.build();
assertThat(notice.toJson())
.isEqualTo(
createJson(
"{",
" 'title':'myTitle',",
" 'type':'result set truncated due to authorization',",
" 'description':['AAA','BBB'],",
" 'links':[{'href':'myHref','title':'myLink'}]",
"}"));
assertRestrictedNames(notice, "notices[]");
}
@Test
public void testRemark() {
Remark remark = Remark.builder()
.setDescription("AAA", "BBB")
.setTitle("myTitle")
.addLink(Link.builder().setHref("myHref").setTitle("myLink").build())
.setType(Remark.Type.OBJECT_TRUNCATED_AUTHORIZATION)
.build();
assertThat(remark.toJson())
.isEqualTo(
createJson(
"{",
" 'title':'myTitle',",
" 'type':'object truncated due to authorization',",
" 'description':['AAA','BBB'],",
" 'links':[{'href':'myHref','title':'myLink'}]",
"}"));
assertRestrictedNames(remark, "remarks[]");
}
@Test
public void testLanguage() {
assertThat(LanguageIdentifier.EN.toJson()).isEqualTo(createJson("'en'"));
assertRestrictedNames(LanguageIdentifier.EN, "lang");
}
@Test
public void testEvent() {
Event event =
Event.builder()
.setEventAction(EventAction.REGISTRATION)
.setEventActor("Event Actor")
.setEventDate(DateTime.parse("2012-04-03T14:54Z"))
.addLink(Link.builder().setHref("myHref").build())
.build();
assertThat(event.toJson())
.isEqualTo(
createJson(
"{",
" 'eventAction':'registration',",
" 'eventActor':'Event Actor',",
" 'eventDate':'2012-04-03T14:54:00.000Z',",
" 'links':[{'href':'myHref'}]",
"}"));
assertRestrictedNames(event, "events[]");
}
@Test
public void testEventWithoutActor() {
EventWithoutActor event =
EventWithoutActor.builder()
.setEventAction(EventAction.REGISTRATION)
.setEventDate(DateTime.parse("2012-04-03T14:54Z"))
.addLink(Link.builder().setHref("myHref").build())
.build();
assertThat(event.toJson())
.isEqualTo(
createJson(
"{",
" 'eventAction':'registration',",
" 'eventDate':'2012-04-03T14:54:00.000Z',",
" 'links':[{'href':'myHref'}]",
"}"));
assertRestrictedNames(event, "asEventActor[]");
}
@Test
public void testRdapStatus() {
assertThat(RdapStatus.ACTIVE.toJson()).isEqualTo(createJson("'active'"));
assertRestrictedNames(RdapStatus.ACTIVE, "status[]");
}
@Test
public void testPort43() {
Port43WhoisServer port43 = Port43WhoisServer.create("myServer");
assertThat(port43.toJson()).isEqualTo(createJson("'myServer'"));
assertRestrictedNames(port43, "port43");
}
@Test
public void testPublicId() {
PublicId publicId = PublicId.create(PublicId.Type.IANA_REGISTRAR_ID, "myId");
assertThat(publicId.toJson())
.isEqualTo(createJson("{'identifier':'myId','type':'IANA Registrar ID'}"));
assertRestrictedNames(publicId, "publicIds[]");
}
@Test
public void testObjectClassName() {
assertThat(ObjectClassName.DOMAIN.toJson()).isEqualTo(createJson("'domain'"));
assertRestrictedNames(ObjectClassName.DOMAIN, "objectClassName");
}
}

View file

@ -24,7 +24,6 @@ import static google.registry.testing.FullFieldsTestEntityHelper.makeDomainBase;
import static google.registry.testing.FullFieldsTestEntityHelper.makeHistoryEntry; import static google.registry.testing.FullFieldsTestEntityHelper.makeHistoryEntry;
import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrar; import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrar;
import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrarContacts; import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrarContacts;
import static google.registry.testing.TestDataHelper.loadFile;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
@ -47,7 +46,6 @@ import java.util.Map;
import java.util.Optional; import java.util.Optional;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import org.json.simple.JSONObject; import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
@ -230,24 +228,15 @@ public class RdapDomainActionTest extends RdapActionBaseTestCase<RdapDomainActio
clock.nowUtc().minusMonths(6))); clock.nowUtc().minusMonths(6)));
} }
private Object generateExpectedJson( private JSONObject generateExpectedJson(
String name, String expectedOutputFile,
String punycodeName,
String handle,
String expectedOutputFile) {
return generateExpectedJson(
name, punycodeName, handle, null, null, null, null, expectedOutputFile);
}
private Object generateExpectedJson(
String name, String name,
String punycodeName, String punycodeName,
String handle, String handle,
@Nullable List<String> contactRoids, @Nullable List<String> contactRoids,
@Nullable List<String> nameserverRoids, @Nullable List<String> nameserverRoids,
@Nullable List<String> nameserverNames, @Nullable List<String> nameserverNames,
@Nullable String registrarName, @Nullable String registrarName) {
String expectedOutputFile) {
ImmutableMap.Builder<String, String> substitutionsBuilder = new ImmutableMap.Builder<>(); ImmutableMap.Builder<String, String> substitutionsBuilder = new ImmutableMap.Builder<>();
substitutionsBuilder.put("NAME", name); substitutionsBuilder.put("NAME", name);
substitutionsBuilder.put("PUNYCODENAME", (punycodeName == null) ? name : punycodeName); substitutionsBuilder.put("PUNYCODENAME", (punycodeName == null) ? name : punycodeName);
@ -282,11 +271,10 @@ public class RdapDomainActionTest extends RdapActionBaseTestCase<RdapDomainActio
substitutionsBuilder.put("NAMESERVER2NAME", "ns2.cat.lol"); substitutionsBuilder.put("NAMESERVER2NAME", "ns2.cat.lol");
substitutionsBuilder.put("NAMESERVER2PUNYCODENAME", "ns2.cat.lol"); substitutionsBuilder.put("NAMESERVER2PUNYCODENAME", "ns2.cat.lol");
} }
return JSONValue.parse( return loadJsonFile(expectedOutputFile, substitutionsBuilder.build());
loadFile(this.getClass(), expectedOutputFile, substitutionsBuilder.build()));
} }
private Object generateExpectedJsonWithTopLevelEntries( private JSONObject generateExpectedJsonWithTopLevelEntries(
String name, String name,
String punycodeName, String punycodeName,
String handle, String handle,
@ -304,7 +292,7 @@ public class RdapDomainActionTest extends RdapActionBaseTestCase<RdapDomainActio
registrarName, registrarName,
expectedOutputFile); expectedOutputFile);
} }
private Object generateExpectedJsonWithTopLevelEntries( private JSONObject generateExpectedJsonWithTopLevelEntries(
String name, String name,
String punycodeName, String punycodeName,
String handle, String handle,
@ -313,34 +301,23 @@ public class RdapDomainActionTest extends RdapActionBaseTestCase<RdapDomainActio
@Nullable List<String> nameserverNames, @Nullable List<String> nameserverNames,
@Nullable String registrarName, @Nullable String registrarName,
String expectedOutputFile) { String expectedOutputFile) {
Object obj = JSONObject obj =
generateExpectedJson( generateExpectedJson(
expectedOutputFile,
name, name,
punycodeName, punycodeName,
handle, handle,
contactRoids, contactRoids,
nameserverRoids, nameserverRoids,
nameserverNames, nameserverNames,
registrarName, registrarName);
expectedOutputFile);
if (obj instanceof Map) {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) obj; Map<String, Object> map = (Map<String, Object>) obj;
ImmutableMap.Builder<String, Object> builder = ImmutableMap.Builder<String, Object> builder =
RdapTestHelper.getBuilderExcluding( RdapTestHelper.getBuilderExcluding(map, ImmutableSet.of("notices"));
map, ImmutableSet.of("rdapConformance", "notices", "remarks"));
builder.put("rdapConformance", ImmutableList.of("icann_rdap_response_profile_0"));
RdapTestHelper.addDomainBoilerplateNotices( RdapTestHelper.addDomainBoilerplateNotices(
builder, builder, RdapTestHelper.createNotices("https://example.tld/rdap/", map.get("notices")));
false,
RdapTestHelper.createNotices(
"https://example.tld/rdap/",
(contactRoids == null)
? RdapTestHelper.ContactNoticeType.DOMAIN
: RdapTestHelper.ContactNoticeType.NONE,
map.get("notices")));
obj = new JSONObject(builder.build()); obj = new JSONObject(builder.build());
}
return obj; return obj;
} }
@ -361,9 +338,7 @@ public class RdapDomainActionTest extends RdapActionBaseTestCase<RdapDomainActio
"cat.lol", "cat.lol",
null, null,
"C-LOL", "C-LOL",
expectedOutputFile.equals("rdap_domain.json") ImmutableList.of("4-ROID", "6-ROID", "2-ROID"),
? ImmutableList.of("4-ROID", "6-ROID", "2-ROID")
: null,
ImmutableList.of("8-ROID", "A-ROID"), ImmutableList.of("8-ROID", "A-ROID"),
"Yes Virginia <script>", "Yes Virginia <script>",
expectedOutputFile)); expectedOutputFile));
@ -374,12 +349,10 @@ public class RdapDomainActionTest extends RdapActionBaseTestCase<RdapDomainActio
public void testInvalidDomain_returns400() { public void testInvalidDomain_returns400() {
assertJsonEqual( assertJsonEqual(
generateActualJson("invalid/domain/name"), generateActualJson("invalid/domain/name"),
generateExpectedJson( generateExpectedJsonError(
"invalid/domain/name is not a valid domain name: Domain names can only contain a-z," "invalid/domain/name is not a valid domain name: Domain names can only contain a-z,"
+ " 0-9, '.' and '-'", + " 0-9, '.' and '-'",
null, 400));
"1",
"rdap_error_400.json"));
assertThat(response.getStatus()).isEqualTo(400); assertThat(response.getStatus()).isEqualTo(400);
} }
@ -387,12 +360,10 @@ public class RdapDomainActionTest extends RdapActionBaseTestCase<RdapDomainActio
public void testUnknownDomain_returns400() { public void testUnknownDomain_returns400() {
assertJsonEqual( assertJsonEqual(
generateActualJson("missingdomain.com"), generateActualJson("missingdomain.com"),
generateExpectedJson( generateExpectedJsonError(
"missingdomain.com is not a valid domain name: Domain name is under tld com which" "missingdomain.com is not a valid domain name: Domain name is under tld com which"
+ " doesn't exist", + " doesn't exist",
null, 400));
"1",
"rdap_error_400.json"));
assertThat(response.getStatus()).isEqualTo(400); assertThat(response.getStatus()).isEqualTo(400);
} }
@ -424,28 +395,28 @@ public class RdapDomainActionTest extends RdapActionBaseTestCase<RdapDomainActio
@Test @Test
public void testValidDomain_notLoggedIn_noContacts() { public void testValidDomain_notLoggedIn_noContacts() {
assertProperResponseForCatLol("cat.lol", "rdap_domain_no_contacts.json"); assertProperResponseForCatLol("cat.lol", "rdap_domain_no_contacts_with_remark.json");
} }
@Test @Test
public void testValidDomain_loggedInAsOtherRegistrar_noContacts() { public void testValidDomain_loggedInAsOtherRegistrar_noContacts() {
login("idnregistrar"); login("idnregistrar");
assertProperResponseForCatLol("cat.lol", "rdap_domain_no_contacts.json"); assertProperResponseForCatLol("cat.lol", "rdap_domain_no_contacts_with_remark.json");
} }
@Test @Test
public void testUpperCase_ignored() { public void testUpperCase_ignored() {
assertProperResponseForCatLol("CaT.lOl", "rdap_domain_no_contacts.json"); assertProperResponseForCatLol("CaT.lOl", "rdap_domain_no_contacts_with_remark.json");
} }
@Test @Test
public void testTrailingDot_ignored() { public void testTrailingDot_ignored() {
assertProperResponseForCatLol("cat.lol.", "rdap_domain_no_contacts.json"); assertProperResponseForCatLol("cat.lol.", "rdap_domain_no_contacts_with_remark.json");
} }
@Test @Test
public void testQueryParameter_ignored() { public void testQueryParameter_ignored() {
assertProperResponseForCatLol("cat.lol?key=value", "rdap_domain_no_contacts.json"); assertProperResponseForCatLol("cat.lol?key=value", "rdap_domain_no_contacts_with_remark.json");
} }
@Test @Test
@ -525,7 +496,7 @@ public class RdapDomainActionTest extends RdapActionBaseTestCase<RdapDomainActio
public void testDeletedDomain_notFound() { public void testDeletedDomain_notFound() {
assertJsonEqual( assertJsonEqual(
generateActualJson("dodo.lol"), generateActualJson("dodo.lol"),
generateExpectedJson("dodo.lol not found", null, "1", "rdap_error_404.json")); generateExpectedJsonError("dodo.lol not found", 404));
assertThat(response.getStatus()).isEqualTo(404); assertThat(response.getStatus()).isEqualTo(404);
} }

View file

@ -28,7 +28,6 @@ import static google.registry.testing.FullFieldsTestEntityHelper.makeDomainBase;
import static google.registry.testing.FullFieldsTestEntityHelper.makeHistoryEntry; import static google.registry.testing.FullFieldsTestEntityHelper.makeHistoryEntry;
import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrar; import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrar;
import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrarContacts; import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrarContacts;
import static google.registry.testing.TestDataHelper.loadFile;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ImmutableListMultimap;
@ -90,11 +89,11 @@ public class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDom
enum RequestType { NONE, NAME, NS_LDH_NAME, NS_IP } enum RequestType { NONE, NAME, NS_LDH_NAME, NS_IP }
private Object generateActualJson(RequestType requestType, String paramValue) { private JSONObject generateActualJson(RequestType requestType, String paramValue) {
return generateActualJson(requestType, paramValue, null); return generateActualJson(requestType, paramValue, null);
} }
private Object generateActualJson( private JSONObject generateActualJson(
RequestType requestType, String paramValue, String cursor) { RequestType requestType, String paramValue, String cursor) {
action.requestPath = actionPath; action.requestPath = actionPath;
action.requestMethod = POST; action.requestMethod = POST;
@ -136,7 +135,7 @@ public class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDom
} }
} }
action.run(); action.run();
return JSONValue.parse(response.getPayload()); return (JSONObject) JSONValue.parse(response.getPayload());
} }
private HostResource addHostToMap(HostResource host) { private HostResource addHostToMap(HostResource host) {
@ -363,27 +362,25 @@ public class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDom
action.requestMethod = POST; action.requestMethod = POST;
} }
private Object generateExpectedJsonForTwoDomains() { private JSONObject generateExpectedJsonForTwoDomains() {
return generateExpectedJsonForTwoDomains("cat.example", "21-EXAMPLE", "cat.lol", "C-LOL"); return generateExpectedJsonForTwoDomains("cat.example", "21-EXAMPLE", "cat.lol", "C-LOL");
} }
private Object generateExpectedJsonForTwoDomains( private JSONObject generateExpectedJsonForTwoDomains(
String domain1Name, String domain1Name,
String domain1Handle, String domain1Handle,
String domain2Name, String domain2Name,
String domain2Handle) { String domain2Handle) {
return JSONValue.parse(loadFile( return loadJsonFile(
this.getClass(),
"rdap_domains_two.json", "rdap_domains_two.json",
ImmutableMap.of(
"TYPE", "domain name", "TYPE", "domain name",
"DOMAINNAME1", domain1Name, "DOMAINNAME1", domain1Name,
"DOMAINHANDLE1", domain1Handle, "DOMAINHANDLE1", domain1Handle,
"DOMAINNAME2", domain2Name, "DOMAINNAME2", domain2Name,
"DOMAINHANDLE2", domain2Handle))); "DOMAINHANDLE2", domain2Handle);
} }
private Object generateExpectedJsonForFourDomains( private JSONObject generateExpectedJsonForFourDomains(
String domain1Name, String domain1Name,
String domain1Handle, String domain1Handle,
String domain2Name, String domain2Name,
@ -406,7 +403,7 @@ public class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDom
expectedOutputFile); expectedOutputFile);
} }
private Object generateExpectedJsonForFourDomains( private JSONObject generateExpectedJsonForFourDomains(
String domain1Name, String domain1Name,
String domain1Handle, String domain1Handle,
String domain2Name, String domain2Name,
@ -417,33 +414,25 @@ public class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDom
String domain4Handle, String domain4Handle,
String nextQuery, String nextQuery,
String expectedOutputFile) { String expectedOutputFile) {
return JSONValue.parse( return loadJsonFile(
loadFile(
this.getClass(),
expectedOutputFile, expectedOutputFile,
new ImmutableMap.Builder<String, String>() "TYPE", "domain name",
.put("TYPE", "domain name") "DOMAINPUNYCODENAME1", domain1Name,
.put("DOMAINPUNYCODENAME1", domain1Name) "DOMAINNAME1", IDN.toUnicode(domain1Name),
.put("DOMAINNAME1", IDN.toUnicode(domain1Name)) "DOMAINHANDLE1", domain1Handle,
.put("DOMAINHANDLE1", domain1Handle) "DOMAINPUNYCODENAME2", domain2Name,
.put("DOMAINPUNYCODENAME2", domain2Name) "DOMAINNAME2", IDN.toUnicode(domain2Name),
.put("DOMAINNAME2", IDN.toUnicode(domain2Name)) "DOMAINHANDLE2", domain2Handle,
.put("DOMAINHANDLE2", domain2Handle) "DOMAINPUNYCODENAME3", domain3Name,
.put("DOMAINPUNYCODENAME3", domain3Name) "DOMAINNAME3", IDN.toUnicode(domain3Name),
.put("DOMAINNAME3", IDN.toUnicode(domain3Name)) "DOMAINHANDLE3", domain3Handle,
.put("DOMAINHANDLE3", domain3Handle) "DOMAINPUNYCODENAME4", domain4Name,
.put("DOMAINPUNYCODENAME4", domain4Name) "DOMAINNAME4", IDN.toUnicode(domain4Name),
.put("DOMAINNAME4", IDN.toUnicode(domain4Name)) "DOMAINHANDLE4", domain4Handle,
.put("DOMAINHANDLE4", domain4Handle) "NEXT_QUERY", nextQuery);
.put("NEXT_QUERY", nextQuery)
.build()));
} }
private Object generateExpectedJson(String name, String expectedOutputFile) { private JSONObject generateExpectedJson(
return generateExpectedJson(name, null, null, null, null, null, expectedOutputFile);
}
private Object generateExpectedJson(
String name, String name,
String punycodeName, String punycodeName,
String handle, String handle,
@ -476,11 +465,10 @@ public class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDom
if (registrarName != null) { if (registrarName != null) {
substitutionsBuilder.put("REGISTRARNAME", registrarName); substitutionsBuilder.put("REGISTRARNAME", registrarName);
} }
return JSONValue.parse( return loadJsonFile(expectedOutputFile, substitutionsBuilder.build());
loadFile(this.getClass(), expectedOutputFile, substitutionsBuilder.build()));
} }
private Object generateExpectedJsonForDomain( private JSONObject generateExpectedJsonForDomain(
String name, String name,
String punycodeName, String punycodeName,
String handle, String handle,
@ -488,7 +476,7 @@ public class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDom
@Nullable List<String> nameservers, @Nullable List<String> nameservers,
@Nullable String registrarName, @Nullable String registrarName,
String expectedOutputFile) { String expectedOutputFile) {
Object obj = JSONObject obj =
generateExpectedJson( generateExpectedJson(
name, name,
punycodeName, punycodeName,
@ -497,6 +485,7 @@ public class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDom
nameservers, nameservers,
registrarName, registrarName,
expectedOutputFile); expectedOutputFile);
obj.remove("rdapConformance");
ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>(); ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>();
builder.put("domainSearchResults", ImmutableList.of(obj)); builder.put("domainSearchResults", ImmutableList.of(obj));
builder.put("rdapConformance", ImmutableList.of("icann_rdap_response_profile_0")); builder.put("rdapConformance", ImmutableList.of("icann_rdap_response_profile_0"));
@ -560,7 +549,7 @@ public class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDom
persistResources(domainsBuilder.build()); persistResources(domainsBuilder.build());
} }
private Object readMultiDomainFile( private JSONObject readMultiDomainFile(
String fileName, String fileName,
String domainName1, String domainName1,
String domainHandle1, String domainHandle1,
@ -583,7 +572,7 @@ public class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDom
"none"); "none");
} }
private Object readMultiDomainFile( private JSONObject readMultiDomainFile(
String fileName, String fileName,
String domainName1, String domainName1,
String domainHandle1, String domainHandle1,
@ -594,30 +583,24 @@ public class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDom
String domainName4, String domainName4,
String domainHandle4, String domainHandle4,
String nextQuery) { String nextQuery) {
return JSONValue.parse(loadFile( return loadJsonFile(
this.getClass(),
fileName, fileName,
new ImmutableMap.Builder<String, String>() "DOMAINNAME1", domainName1,
.put("DOMAINNAME1", domainName1) "DOMAINHANDLE1", domainHandle1,
.put("DOMAINHANDLE1", domainHandle1) "DOMAINNAME2", domainName2,
.put("DOMAINNAME2", domainName2) "DOMAINHANDLE2", domainHandle2,
.put("DOMAINHANDLE2", domainHandle2) "DOMAINNAME3", domainName3,
.put("DOMAINNAME3", domainName3) "DOMAINHANDLE3", domainHandle3,
.put("DOMAINHANDLE3", domainHandle3) "DOMAINNAME4", domainName4,
.put("DOMAINNAME4", domainName4) "DOMAINHANDLE4", domainHandle4,
.put("DOMAINHANDLE4", domainHandle4) "NEXT_QUERY", nextQuery);
.put("NEXT_QUERY", nextQuery)
.build()));
} }
private void checkNumberOfDomainsInResult(Object obj, int expected) { private void checkNumberOfDomainsInResult(JSONObject obj, int expected) {
assertThat(obj).isInstanceOf(Map.class); assertThat(obj).isInstanceOf(Map.class);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) obj; List<Object> domains = (List<Object>) obj.get("domainSearchResults");
@SuppressWarnings("unchecked")
List<Object> domains = (List<Object>) map.get("domainSearchResults");
assertThat(domains).hasSize(expected); assertThat(domains).hasSize(expected);
} }
@ -717,7 +700,7 @@ public class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDom
RequestType requestType, String queryString, String errorMessage) { RequestType requestType, String queryString, String errorMessage) {
rememberWildcardType(queryString); rememberWildcardType(queryString);
assertThat(generateActualJson(requestType, queryString)) assertThat(generateActualJson(requestType, queryString))
.isEqualTo(generateExpectedJson(errorMessage, "rdap_error_404.json")); .isEqualTo(generateExpectedJsonError(errorMessage, 404));
assertThat(response.getStatus()).isEqualTo(404); assertThat(response.getStatus()).isEqualTo(404);
} }
@ -801,7 +784,7 @@ public class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDom
int expectedPageCount = int expectedPageCount =
(expectedNames.size() + action.rdapResultSetMaxSize - 1) / action.rdapResultSetMaxSize; (expectedNames.size() + action.rdapResultSetMaxSize - 1) / action.rdapResultSetMaxSize;
for (int pageNum = 0; pageNum < expectedPageCount; pageNum++) { for (int pageNum = 0; pageNum < expectedPageCount; pageNum++) {
Object results = generateActualJson(requestType, paramValue, cursor); JSONObject results = generateActualJson(requestType, paramValue, cursor);
assertThat(response.getStatus()).isEqualTo(200); assertThat(response.getStatus()).isEqualTo(200);
String linkToNext = RdapTestHelper.getLinkToNext(results); String linkToNext = RdapTestHelper.getLinkToNext(results);
if (pageNum == expectedPageCount - 1) { if (pageNum == expectedPageCount - 1) {
@ -811,7 +794,7 @@ public class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDom
int pos = linkToNext.indexOf("cursor="); int pos = linkToNext.indexOf("cursor=");
assertThat(pos).isAtLeast(0); assertThat(pos).isAtLeast(0);
cursor = URLDecoder.decode(linkToNext.substring(pos + 7), "UTF-8"); cursor = URLDecoder.decode(linkToNext.substring(pos + 7), "UTF-8");
Object searchResults = ((JSONObject) results).get("domainSearchResults"); Object searchResults = results.get("domainSearchResults");
assertThat(searchResults).isInstanceOf(JSONArray.class); assertThat(searchResults).isInstanceOf(JSONArray.class);
assertThat(((JSONArray) searchResults)).hasSize(action.rdapResultSetMaxSize); assertThat(((JSONArray) searchResults)).hasSize(action.rdapResultSetMaxSize);
for (Object item : ((JSONArray) searchResults)) { for (Object item : ((JSONArray) searchResults)) {
@ -838,9 +821,9 @@ public class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDom
@Test @Test
public void testInvalidRequest_rejected() { public void testInvalidRequest_rejected() {
assertThat(generateActualJson(RequestType.NONE, null)) assertThat(generateActualJson(RequestType.NONE, null))
.isEqualTo(generateExpectedJson( .isEqualTo(generateExpectedJsonError(
"You must specify either name=XXXX, nsLdhName=YYYY or nsIp=ZZZZ", "You must specify either name=XXXX, nsLdhName=YYYY or nsIp=ZZZZ",
"rdap_error_400.json")); 400));
assertThat(response.getStatus()).isEqualTo(400); assertThat(response.getStatus()).isEqualTo(400);
verifyErrorMetrics(SearchType.NONE, Optional.empty(), 400); verifyErrorMetrics(SearchType.NONE, Optional.empty(), 400);
} }
@ -848,10 +831,10 @@ public class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDom
@Test @Test
public void testInvalidWildcard_rejected() { public void testInvalidWildcard_rejected() {
assertThat(generateActualJson(RequestType.NAME, "exam*ple")) assertThat(generateActualJson(RequestType.NAME, "exam*ple"))
.isEqualTo(generateExpectedJson( .isEqualTo(generateExpectedJsonError(
"Suffix after wildcard must be one or more domain" "Suffix after wildcard must be one or more domain"
+ " name labels, e.g. exam*.tld, ns*.example.tld", + " name labels, e.g. exam*.tld, ns*.example.tld",
"rdap_error_422.json")); 422));
assertThat(response.getStatus()).isEqualTo(422); assertThat(response.getStatus()).isEqualTo(422);
verifyErrorMetrics(SearchType.BY_DOMAIN_NAME, Optional.empty(), 422); verifyErrorMetrics(SearchType.BY_DOMAIN_NAME, Optional.empty(), 422);
} }
@ -859,7 +842,7 @@ public class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDom
@Test @Test
public void testMultipleWildcards_rejected() { public void testMultipleWildcards_rejected() {
assertThat(generateActualJson(RequestType.NAME, "*.*")) assertThat(generateActualJson(RequestType.NAME, "*.*"))
.isEqualTo(generateExpectedJson("Only one wildcard allowed", "rdap_error_422.json")); .isEqualTo(generateExpectedJsonError("Only one wildcard allowed", 422));
assertThat(response.getStatus()).isEqualTo(422); assertThat(response.getStatus()).isEqualTo(422);
verifyErrorMetrics(SearchType.BY_DOMAIN_NAME, Optional.empty(), 422); verifyErrorMetrics(SearchType.BY_DOMAIN_NAME, Optional.empty(), 422);
} }
@ -869,10 +852,10 @@ public class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDom
rememberWildcardType("*"); rememberWildcardType("*");
assertThat(generateActualJson(RequestType.NAME, "*")) assertThat(generateActualJson(RequestType.NAME, "*"))
.isEqualTo( .isEqualTo(
generateExpectedJson( generateExpectedJsonError(
"Initial search string is required for wildcard domain searches without a TLD" "Initial search string is required for wildcard domain searches without a TLD"
+ " suffix", + " suffix",
"rdap_error_422.json")); 422));
assertThat(response.getStatus()).isEqualTo(422); assertThat(response.getStatus()).isEqualTo(422);
verifyErrorMetrics(SearchType.BY_DOMAIN_NAME, Optional.empty(), 422); verifyErrorMetrics(SearchType.BY_DOMAIN_NAME, Optional.empty(), 422);
} }
@ -882,10 +865,10 @@ public class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDom
rememberWildcardType("a*"); rememberWildcardType("a*");
assertThat(generateActualJson(RequestType.NAME, "a*")) assertThat(generateActualJson(RequestType.NAME, "a*"))
.isEqualTo( .isEqualTo(
generateExpectedJson( generateExpectedJsonError(
"Initial search string must be at least 2 characters for wildcard domain searches" "Initial search string must be at least 2 characters for wildcard domain searches"
+ " without a TLD suffix", + " without a TLD suffix",
"rdap_error_422.json")); 422));
assertThat(response.getStatus()).isEqualTo(422); assertThat(response.getStatus()).isEqualTo(422);
verifyErrorMetrics(SearchType.BY_DOMAIN_NAME, Optional.empty(), 422); verifyErrorMetrics(SearchType.BY_DOMAIN_NAME, Optional.empty(), 422);
} }
@ -1226,7 +1209,7 @@ public class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDom
// There are enough domains to fill a full result set; deleted domains are ignored. // There are enough domains to fill a full result set; deleted domains are ignored.
createManyDomainsAndHosts(4, 4, 2); createManyDomainsAndHosts(4, 4, 2);
rememberWildcardType("domain*.lol"); rememberWildcardType("domain*.lol");
Object obj = generateActualJson(RequestType.NAME, "domain*.lol"); JSONObject obj = generateActualJson(RequestType.NAME, "domain*.lol");
assertThat(response.getStatus()).isEqualTo(200); assertThat(response.getStatus()).isEqualTo(200);
checkNumberOfDomainsInResult(obj, 4); checkNumberOfDomainsInResult(obj, 4);
verifyMetrics(SearchType.BY_DOMAIN_NAME, Optional.of(16L)); verifyMetrics(SearchType.BY_DOMAIN_NAME, Optional.of(16L));
@ -1237,7 +1220,7 @@ public class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDom
// There are not enough domains to fill a full result set. // There are not enough domains to fill a full result set.
createManyDomainsAndHosts(3, 20, 2); createManyDomainsAndHosts(3, 20, 2);
rememberWildcardType("domain*.lol"); rememberWildcardType("domain*.lol");
Object obj = generateActualJson(RequestType.NAME, "domain*.lol"); JSONObject obj = generateActualJson(RequestType.NAME, "domain*.lol");
assertThat(response.getStatus()).isEqualTo(200); assertThat(response.getStatus()).isEqualTo(200);
checkNumberOfDomainsInResult(obj, 3); checkNumberOfDomainsInResult(obj, 3);
verifyMetrics(SearchType.BY_DOMAIN_NAME, Optional.of(60L)); verifyMetrics(SearchType.BY_DOMAIN_NAME, Optional.of(60L));
@ -1682,7 +1665,7 @@ public class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDom
// each one references the nameserver. // each one references the nameserver.
createManyDomainsAndHosts(3, 1, 40); createManyDomainsAndHosts(3, 1, 40);
rememberWildcardType("ns1.domain1.lol"); rememberWildcardType("ns1.domain1.lol");
Object obj = generateActualJson(RequestType.NS_LDH_NAME, "ns1.domain1.lol"); JSONObject obj = generateActualJson(RequestType.NS_LDH_NAME, "ns1.domain1.lol");
assertThat(response.getStatus()).isEqualTo(200); assertThat(response.getStatus()).isEqualTo(200);
checkNumberOfDomainsInResult(obj, 3); checkNumberOfDomainsInResult(obj, 3);
verifyMetrics(SearchType.BY_NAMESERVER_NAME, Optional.of(3L), Optional.of(1L)); verifyMetrics(SearchType.BY_NAMESERVER_NAME, Optional.of(3L), Optional.of(1L));
@ -1693,7 +1676,7 @@ public class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDom
// Same as above, except with a wildcard (that still only finds one nameserver). // Same as above, except with a wildcard (that still only finds one nameserver).
createManyDomainsAndHosts(3, 1, 40); createManyDomainsAndHosts(3, 1, 40);
rememberWildcardType("ns1.domain1.l*"); rememberWildcardType("ns1.domain1.l*");
Object obj = generateActualJson(RequestType.NS_LDH_NAME, "ns1.domain1.l*"); JSONObject obj = generateActualJson(RequestType.NS_LDH_NAME, "ns1.domain1.l*");
assertThat(response.getStatus()).isEqualTo(200); assertThat(response.getStatus()).isEqualTo(200);
checkNumberOfDomainsInResult(obj, 3); checkNumberOfDomainsInResult(obj, 3);
verifyMetrics(SearchType.BY_NAMESERVER_NAME, Optional.of(3L), Optional.of(1L)); verifyMetrics(SearchType.BY_NAMESERVER_NAME, Optional.of(3L), Optional.of(1L));
@ -1707,7 +1690,7 @@ public class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDom
// have more than that number of nameservers for an effective test. // have more than that number of nameservers for an effective test.
createManyDomainsAndHosts(3, 1, 39); createManyDomainsAndHosts(3, 1, 39);
rememberWildcardType("ns*.domain1.lol"); rememberWildcardType("ns*.domain1.lol");
Object obj = generateActualJson(RequestType.NS_LDH_NAME, "ns*.domain1.lol"); JSONObject obj = generateActualJson(RequestType.NS_LDH_NAME, "ns*.domain1.lol");
assertThat(response.getStatus()).isEqualTo(200); assertThat(response.getStatus()).isEqualTo(200);
checkNumberOfDomainsInResult(obj, 3); checkNumberOfDomainsInResult(obj, 3);
verifyMetrics(SearchType.BY_NAMESERVER_NAME, Optional.of(3L), Optional.of(39L)); verifyMetrics(SearchType.BY_NAMESERVER_NAME, Optional.of(3L), Optional.of(39L));

View file

@ -24,7 +24,6 @@ import static google.registry.testing.FullFieldsTestEntityHelper.makeDomainBase;
import static google.registry.testing.FullFieldsTestEntityHelper.makeHostResource; import static google.registry.testing.FullFieldsTestEntityHelper.makeHostResource;
import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrar; import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrar;
import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrarContacts; import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrarContacts;
import static google.registry.testing.TestDataHelper.loadFile;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
@ -41,7 +40,7 @@ import google.registry.request.Action;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import org.json.simple.JSONValue; import org.json.simple.JSONObject;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -132,51 +131,27 @@ public class RdapEntityActionTest extends RdapActionBaseTestCase<RdapEntityActio
clock.nowUtc().minusMonths(6)); clock.nowUtc().minusMonths(6));
} }
private Object generateExpectedJson(String handle, String expectedOutputFile) {
return generateExpectedJson(handle, "(◕‿◕)", "", expectedOutputFile);
}
private Object generateExpectedJson(
String handle,
String status,
String expectedOutputFile) {
return generateExpectedJson(handle, "(◕‿◕)", status, expectedOutputFile);
}
private Object generateExpectedJson(
String handle,
String fullName,
String status,
String expectedOutputFile) {
return generateExpectedJson(
handle, fullName, status, null, expectedOutputFile);
}
private Object generateExpectedJson( private Object generateExpectedJson(
String handle, String handle,
String fullName, String fullName,
String status, String status,
@Nullable String address, @Nullable String address,
String expectedOutputFile) { String expectedOutputFile) {
return JSONValue.parse( return loadJsonFile(
loadFile(
this.getClass(),
expectedOutputFile, expectedOutputFile,
new ImmutableMap.Builder<String, String>() "NAME", handle,
.put("NAME", handle) "FULLNAME", fullName,
.put("FULLNAME", fullName) "ADDRESS", (address == null) ? "\"1 Smiley Row\", \"Suite みんな\"" : address,
.put("ADDRESS", (address == null) ? "\"1 Smiley Row\", \"Suite みんな\"" : address) "EMAIL", "lol@cat.みんな",
.put("EMAIL", "lol@cat.みんな") "TYPE", "entity",
.put("TYPE", "entity") "STATUS", status);
.put("STATUS", status)
.build()));
} }
private Object generateExpectedJsonWithTopLevelEntries( private Object generateExpectedJsonWithTopLevelEntries(
String handle, String handle,
String expectedOutputFile) { String expectedOutputFile) {
return generateExpectedJsonWithTopLevelEntries( return generateExpectedJsonWithTopLevelEntries(
handle, "(◕‿◕)", "active", null, false, expectedOutputFile); handle, "(◕‿◕)", "active", null, expectedOutputFile);
} }
private Object generateExpectedJsonWithTopLevelEntries( private Object generateExpectedJsonWithTopLevelEntries(
@ -184,35 +159,26 @@ public class RdapEntityActionTest extends RdapActionBaseTestCase<RdapEntityActio
String fullName, String fullName,
String status, String status,
String address, String address,
boolean addNoPersonalDataRemark,
String expectedOutputFile) { String expectedOutputFile) {
Object obj = generateExpectedJson(handle, fullName, status, address, expectedOutputFile); Object obj = generateExpectedJson(handle, fullName, status, address, expectedOutputFile);
if (obj instanceof Map) { if (obj instanceof Map) {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) obj; Map<String, Object> map = (Map<String, Object>) obj;
ImmutableMap.Builder<String, Object> builder = ImmutableMap.Builder<String, Object> builder =
RdapTestHelper.getBuilderExcluding( RdapTestHelper.getBuilderExcluding(map, ImmutableSet.of("notices"));
map, ImmutableSet.of("rdapConformance", "notices", "remarks"));
builder.put("rdapConformance", ImmutableList.of("icann_rdap_response_profile_0"));
RdapTestHelper.addNonDomainBoilerplateNotices( RdapTestHelper.addNonDomainBoilerplateNotices(
builder, builder, RdapTestHelper.createNotices("https://example.tld/rdap/", map.get("notices")));
RdapTestHelper.createNotices( obj = new JSONObject(builder.build());
"https://example.tld/rdap/",
addNoPersonalDataRemark
? RdapTestHelper.ContactNoticeType.CONTACT
: RdapTestHelper.ContactNoticeType.NONE,
map.get("notices")));
obj = builder.build();
} }
return obj; return obj;
} }
private void runSuccessfulTest(String queryString, String fileName) { private void runSuccessfulTest(String queryString, String fileName) {
runSuccessfulTest(queryString, "(◕‿◕)", "active", null, false, fileName); runSuccessfulTest(queryString, "(◕‿◕)", "active", null, fileName);
} }
private void runSuccessfulTest(String queryString, String fullName, String fileName) { private void runSuccessfulTest(String queryString, String fullName, String fileName) {
runSuccessfulTest(queryString, fullName, "active", null, false, fileName); runSuccessfulTest(queryString, fullName, "active", null, fileName);
} }
private void runSuccessfulTest( private void runSuccessfulTest(
@ -220,27 +186,26 @@ public class RdapEntityActionTest extends RdapActionBaseTestCase<RdapEntityActio
String fullName, String fullName,
String rdapStatus, String rdapStatus,
String address, String address,
boolean addNoPersonalDataRemark,
String fileName) { String fileName) {
assertThat(generateActualJson(queryString)) assertThat(generateActualJson(queryString))
.isEqualTo( .isEqualTo(
generateExpectedJsonWithTopLevelEntries( generateExpectedJsonWithTopLevelEntries(
queryString, fullName, rdapStatus, address, addNoPersonalDataRemark, fileName)); queryString, fullName, rdapStatus, address, fileName));
assertThat(response.getStatus()).isEqualTo(200); assertThat(response.getStatus()).isEqualTo(200);
} }
private void runNotFoundTest(String queryString) { private void runNotFoundTest(String queryString) {
assertThat(generateActualJson(queryString)) assertThat(generateActualJson(queryString))
.isEqualTo(generateExpectedJson(queryString + " not found", "", "rdap_error_404.json")); .isEqualTo(generateExpectedJsonError(queryString + " not found", 404));
assertThat(response.getStatus()).isEqualTo(404); assertThat(response.getStatus()).isEqualTo(404);
} }
@Test @Test
public void testInvalidEntity_returns400() { public void testInvalidEntity_returns400() {
assertThat(generateActualJson("invalid/entity/handle")).isEqualTo( assertThat(generateActualJson("invalid/entity/handle")).isEqualTo(
generateExpectedJson( generateExpectedJsonError(
"invalid/entity/handle is not a valid entity handle", "invalid/entity/handle is not a valid entity handle",
"rdap_error_400.json")); 400));
assertThat(response.getStatus()).isEqualTo(400); assertThat(response.getStatus()).isEqualTo(400);
} }
@ -282,7 +247,6 @@ public class RdapEntityActionTest extends RdapActionBaseTestCase<RdapEntityActio
"(◕‿◕)", "(◕‿◕)",
"active", "active",
null, null,
true,
"rdap_associated_contact_no_personal_data.json"); "rdap_associated_contact_no_personal_data.json");
} }
@ -294,7 +258,6 @@ public class RdapEntityActionTest extends RdapActionBaseTestCase<RdapEntityActio
"(◕‿◕)", "(◕‿◕)",
"active", "active",
null, null,
true,
"rdap_associated_contact_no_personal_data.json"); "rdap_associated_contact_no_personal_data.json");
} }
@ -349,7 +312,6 @@ public class RdapEntityActionTest extends RdapActionBaseTestCase<RdapEntityActio
"", "",
"inactive", "inactive",
"", "",
false,
"rdap_contact_deleted.json"); "rdap_contact_deleted.json");
} }
@ -362,7 +324,6 @@ public class RdapEntityActionTest extends RdapActionBaseTestCase<RdapEntityActio
"", "",
"inactive", "inactive",
"", "",
false,
"rdap_contact_deleted.json"); "rdap_contact_deleted.json");
} }
@ -409,7 +370,7 @@ public class RdapEntityActionTest extends RdapActionBaseTestCase<RdapEntityActio
login("deletedregistrar"); login("deletedregistrar");
action.includeDeletedParam = Optional.of(true); action.includeDeletedParam = Optional.of(true);
runSuccessfulTest( runSuccessfulTest(
"104", "Yes Virginia <script>", "inactive", null, false, "rdap_registrar.json"); "104", "Yes Virginia <script>", "inactive", null, "rdap_registrar.json");
} }
@Test @Test
@ -424,7 +385,7 @@ public class RdapEntityActionTest extends RdapActionBaseTestCase<RdapEntityActio
loginAsAdmin(); loginAsAdmin();
action.includeDeletedParam = Optional.of(true); action.includeDeletedParam = Optional.of(true);
runSuccessfulTest( runSuccessfulTest(
"104", "Yes Virginia <script>", "inactive", null, false, "rdap_registrar.json"); "104", "Yes Virginia <script>", "inactive", null, "rdap_registrar.json");
} }
@Test @Test

View file

@ -14,7 +14,6 @@
package google.registry.rdap; package google.registry.rdap;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static google.registry.request.Action.Method.GET; import static google.registry.request.Action.Method.GET;
import static google.registry.testing.DatastoreHelper.createTld; import static google.registry.testing.DatastoreHelper.createTld;
@ -27,7 +26,6 @@ import static google.registry.testing.FullFieldsTestEntityHelper.makeContactReso
import static google.registry.testing.FullFieldsTestEntityHelper.makeHistoryEntry; import static google.registry.testing.FullFieldsTestEntityHelper.makeHistoryEntry;
import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrar; import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrar;
import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrarContacts; import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrarContacts;
import static google.registry.testing.TestDataHelper.loadFile;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ImmutableListMultimap;
@ -70,11 +68,11 @@ public class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEnt
private Registrar registrarInactive; private Registrar registrarInactive;
private Registrar registrarTest; private Registrar registrarTest;
private Object generateActualJsonWithFullName(String fn) { private JSONObject generateActualJsonWithFullName(String fn) {
return generateActualJsonWithFullName(fn, null); return generateActualJsonWithFullName(fn, null);
} }
private Object generateActualJsonWithFullName(String fn, String cursor) { private JSONObject generateActualJsonWithFullName(String fn, String cursor) {
metricSearchType = SearchType.BY_FULL_NAME; metricSearchType = SearchType.BY_FULL_NAME;
action.fnParam = Optional.of(fn); action.fnParam = Optional.of(fn);
if (cursor == null) { if (cursor == null) {
@ -85,14 +83,14 @@ public class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEnt
action.cursorTokenParam = Optional.of(cursor); action.cursorTokenParam = Optional.of(cursor);
} }
action.run(); action.run();
return JSONValue.parse(response.getPayload()); return (JSONObject) JSONValue.parse(response.getPayload());
} }
private Object generateActualJsonWithHandle(String handle) { private JSONObject generateActualJsonWithHandle(String handle) {
return generateActualJsonWithHandle(handle, null); return generateActualJsonWithHandle(handle, null);
} }
private Object generateActualJsonWithHandle(String handle, String cursor) { private JSONObject generateActualJsonWithHandle(String handle, String cursor) {
metricSearchType = SearchType.BY_HANDLE; metricSearchType = SearchType.BY_HANDLE;
action.handleParam = Optional.of(handle); action.handleParam = Optional.of(handle);
if (cursor == null) { if (cursor == null) {
@ -103,7 +101,7 @@ public class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEnt
action.cursorTokenParam = Optional.of(cursor); action.cursorTokenParam = Optional.of(cursor);
} }
action.run(); action.run();
return JSONValue.parse(response.getPayload()); return (JSONObject) JSONValue.parse(response.getPayload());
} }
@Before @Before
@ -158,18 +156,17 @@ public class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEnt
action.subtypeParam = Optional.empty(); action.subtypeParam = Optional.empty();
} }
private Object generateExpectedJson(String expectedOutputFile) { private JSONObject generateExpectedJson(String expectedOutputFile) {
return JSONValue.parse( return loadJsonFile(expectedOutputFile, "TYPE", "entity");
loadFile(this.getClass(), expectedOutputFile, ImmutableMap.of("TYPE", "entity")));
} }
private Object generateExpectedJson( private JSONObject generateExpectedJson(
String handle, String handle,
String expectedOutputFile) { String expectedOutputFile) {
return generateExpectedJson(handle, null, "active", null, null, expectedOutputFile); return generateExpectedJson(handle, null, "active", null, null, expectedOutputFile);
} }
private Object generateExpectedJson( private JSONObject generateExpectedJson(
String handle, String handle,
@Nullable String fullName, @Nullable String fullName,
String status, String status,
@ -189,20 +186,19 @@ public class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEnt
} }
builder.put("TYPE", "entity"); builder.put("TYPE", "entity");
builder.put("STATUS", status); builder.put("STATUS", status);
String substitutedFile = loadFile(this.getClass(), expectedOutputFile, builder.build()); return loadJsonFile(expectedOutputFile, builder.build());
Object jsonObject = JSONValue.parse(substitutedFile);
checkNotNull(jsonObject, "substituted file is not valid JSON: %s", substitutedFile);
return jsonObject;
} }
private Object generateExpectedJsonForEntity( private JSONObject generateExpectedJsonForEntity(
String handle, String handle,
String fullName, String fullName,
String status, String status,
@Nullable String email, @Nullable String email,
@Nullable String address, @Nullable String address,
String expectedOutputFile) { String expectedOutputFile) {
Object obj = generateExpectedJson(handle, fullName, status, email, address, expectedOutputFile); JSONObject obj =
generateExpectedJson(handle, fullName, status, email, address, expectedOutputFile);
obj.remove("rdapConformance");
ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>(); ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>();
builder.put("entitySearchResults", ImmutableList.of(obj)); builder.put("entitySearchResults", ImmutableList.of(obj));
builder.put("rdapConformance", ImmutableList.of("icann_rdap_response_profile_0")); builder.put("rdapConformance", ImmutableList.of("icann_rdap_response_profile_0"));
@ -244,10 +240,20 @@ public class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEnt
} }
private void verifyMetrics(long numContactsRetrieved) { private void verifyMetrics(long numContactsRetrieved) {
verifyMetrics(Optional.of(numContactsRetrieved)); verifyMetrics(numContactsRetrieved, IncompletenessWarningType.COMPLETE);
}
private void verifyMetrics(
long numContactsRetrieved, IncompletenessWarningType incompletenessWarningType) {
verifyMetrics(Optional.of(numContactsRetrieved), incompletenessWarningType);
} }
private void verifyMetrics(Optional<Long> numContactsRetrieved) { private void verifyMetrics(Optional<Long> numContactsRetrieved) {
verifyMetrics(numContactsRetrieved, IncompletenessWarningType.COMPLETE);
}
private void verifyMetrics(
Optional<Long> numContactsRetrieved, IncompletenessWarningType incompletenessWarningType) {
verifyMetrics( verifyMetrics(
EndpointType.ENTITIES, EndpointType.ENTITIES,
GET, GET,
@ -256,7 +262,7 @@ public class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEnt
Optional.empty(), Optional.empty(),
Optional.empty(), Optional.empty(),
numContactsRetrieved, numContactsRetrieved,
IncompletenessWarningType.COMPLETE); incompletenessWarningType);
} }
private void verifyErrorMetrics(long numContactsRetrieved) { private void verifyErrorMetrics(long numContactsRetrieved) {
@ -269,14 +275,11 @@ public class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEnt
verifyMetrics(numContactsRetrieved); verifyMetrics(numContactsRetrieved);
} }
private void checkNumberOfEntitiesInResult(Object obj, int expected) { private void checkNumberOfEntitiesInResult(JSONObject obj, int expected) {
assertThat(obj).isInstanceOf(Map.class); assertThat(obj).isInstanceOf(Map.class);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) obj; List<Object> entities = (List<Object>) obj.get("entitySearchResults");
@SuppressWarnings("unchecked")
List<Object> entities = (List<Object>) map.get("entitySearchResults");
assertThat(entities).hasSize(expected); assertThat(entities).hasSize(expected);
} }
@ -318,7 +321,7 @@ public class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEnt
private void runNotFoundNameTest(String queryString) { private void runNotFoundNameTest(String queryString) {
rememberWildcardType(queryString); rememberWildcardType(queryString);
assertThat(generateActualJsonWithFullName(queryString)) assertThat(generateActualJsonWithFullName(queryString))
.isEqualTo(generateExpectedJson("No entities found", "rdap_error_404.json")); .isEqualTo(generateExpectedJsonError("No entities found", 404));
assertThat(response.getStatus()).isEqualTo(404); assertThat(response.getStatus()).isEqualTo(404);
} }
@ -359,7 +362,7 @@ public class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEnt
private void runNotFoundHandleTest(String queryString) { private void runNotFoundHandleTest(String queryString) {
rememberWildcardType(queryString); rememberWildcardType(queryString);
assertThat(generateActualJsonWithHandle(queryString)) assertThat(generateActualJsonWithHandle(queryString))
.isEqualTo(generateExpectedJson("No entities found", "rdap_error_404.json")); .isEqualTo(generateExpectedJsonError("No entities found", 404));
assertThat(response.getStatus()).isEqualTo(404); assertThat(response.getStatus()).isEqualTo(404);
} }
@ -381,7 +384,7 @@ public class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEnt
int expectedPageCount = int expectedPageCount =
(expectedNames.size() + action.rdapResultSetMaxSize - 1) / action.rdapResultSetMaxSize; (expectedNames.size() + action.rdapResultSetMaxSize - 1) / action.rdapResultSetMaxSize;
for (int pageNum = 0; pageNum < expectedPageCount; pageNum++) { for (int pageNum = 0; pageNum < expectedPageCount; pageNum++) {
Object results = JSONObject results =
(queryType == QueryType.FULL_NAME) (queryType == QueryType.FULL_NAME)
? generateActualJsonWithFullName(paramValue, cursor) ? generateActualJsonWithFullName(paramValue, cursor)
: generateActualJsonWithHandle(paramValue, cursor); : generateActualJsonWithHandle(paramValue, cursor);
@ -394,7 +397,7 @@ public class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEnt
int pos = linkToNext.indexOf("cursor="); int pos = linkToNext.indexOf("cursor=");
assertThat(pos).isAtLeast(0); assertThat(pos).isAtLeast(0);
cursor = URLDecoder.decode(linkToNext.substring(pos + 7), "UTF-8"); cursor = URLDecoder.decode(linkToNext.substring(pos + 7), "UTF-8");
Object searchResults = ((JSONObject) results).get("entitySearchResults"); Object searchResults = results.get("entitySearchResults");
assertThat(searchResults).isInstanceOf(JSONArray.class); assertThat(searchResults).isInstanceOf(JSONArray.class);
assertThat(((JSONArray) searchResults)).hasSize(action.rdapResultSetMaxSize); assertThat(((JSONArray) searchResults)).hasSize(action.rdapResultSetMaxSize);
for (Object item : ((JSONArray) searchResults)) { for (Object item : ((JSONArray) searchResults)) {
@ -429,8 +432,8 @@ public class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEnt
action.run(); action.run();
assertThat(JSONValue.parse(response.getPayload())) assertThat(JSONValue.parse(response.getPayload()))
.isEqualTo( .isEqualTo(
generateExpectedJson( generateExpectedJsonError(
"You must specify either fn=XXXX or handle=YYYY", "rdap_error_400.json")); "You must specify either fn=XXXX or handle=YYYY", 400));
assertThat(response.getStatus()).isEqualTo(400); assertThat(response.getStatus()).isEqualTo(400);
verifyErrorMetrics(Optional.empty(), 400); verifyErrorMetrics(Optional.empty(), 400);
} }
@ -439,7 +442,7 @@ public class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEnt
public void testNameMatch_suffixRejected() { public void testNameMatch_suffixRejected() {
assertThat(generateActualJsonWithFullName("exam*ple")) assertThat(generateActualJsonWithFullName("exam*ple"))
.isEqualTo( .isEqualTo(
generateExpectedJson("Suffix not allowed after wildcard", "rdap_error_422.json")); generateExpectedJsonError("Suffix not allowed after wildcard", 422));
assertThat(response.getStatus()).isEqualTo(422); assertThat(response.getStatus()).isEqualTo(422);
verifyErrorMetrics(Optional.empty(), 422); verifyErrorMetrics(Optional.empty(), 422);
} }
@ -448,7 +451,7 @@ public class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEnt
public void testHandleMatch_suffixRejected() { public void testHandleMatch_suffixRejected() {
assertThat(generateActualJsonWithHandle("exam*ple")) assertThat(generateActualJsonWithHandle("exam*ple"))
.isEqualTo( .isEqualTo(
generateExpectedJson("Suffix not allowed after wildcard", "rdap_error_422.json")); generateExpectedJsonError("Suffix not allowed after wildcard", 422));
assertThat(response.getStatus()).isEqualTo(422); assertThat(response.getStatus()).isEqualTo(422);
verifyErrorMetrics(Optional.empty(), 422); verifyErrorMetrics(Optional.empty(), 422);
} }
@ -456,7 +459,7 @@ public class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEnt
@Test @Test
public void testMultipleWildcards_rejected() { public void testMultipleWildcards_rejected() {
assertThat(generateActualJsonWithHandle("*.*")) assertThat(generateActualJsonWithHandle("*.*"))
.isEqualTo(generateExpectedJson("Only one wildcard allowed", "rdap_error_422.json")); .isEqualTo(generateExpectedJsonError("Only one wildcard allowed", 422));
assertThat(response.getStatus()).isEqualTo(422); assertThat(response.getStatus()).isEqualTo(422);
verifyErrorMetrics(Optional.empty(), 422); verifyErrorMetrics(Optional.empty(), 422);
} }
@ -466,9 +469,9 @@ public class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEnt
rememberWildcardType("*"); rememberWildcardType("*");
assertThat(generateActualJsonWithHandle("*")) assertThat(generateActualJsonWithHandle("*"))
.isEqualTo( .isEqualTo(
generateExpectedJson( generateExpectedJsonError(
"Initial search string must be at least 2 characters", "Initial search string must be at least 2 characters",
"rdap_error_422.json")); 422));
assertThat(response.getStatus()).isEqualTo(422); assertThat(response.getStatus()).isEqualTo(422);
verifyErrorMetrics(Optional.empty(), 422); verifyErrorMetrics(Optional.empty(), 422);
} }
@ -478,9 +481,9 @@ public class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEnt
rememberWildcardType("a*"); rememberWildcardType("a*");
assertThat(generateActualJsonWithHandle("a*")) assertThat(generateActualJsonWithHandle("a*"))
.isEqualTo( .isEqualTo(
generateExpectedJson( generateExpectedJsonError(
"Initial search string must be at least 2 characters", "Initial search string must be at least 2 characters",
"rdap_error_422.json")); 422));
assertThat(response.getStatus()).isEqualTo(422); assertThat(response.getStatus()).isEqualTo(422);
verifyErrorMetrics(Optional.empty(), 422); verifyErrorMetrics(Optional.empty(), 422);
} }
@ -490,9 +493,9 @@ public class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEnt
action.subtypeParam = Optional.of("Space Aliens"); action.subtypeParam = Optional.of("Space Aliens");
assertThat(generateActualJsonWithFullName("Blinky (赤ベイ)")) assertThat(generateActualJsonWithFullName("Blinky (赤ベイ)"))
.isEqualTo( .isEqualTo(
generateExpectedJson( generateExpectedJsonError(
"Subtype parameter must specify contacts, registrars or all", "Subtype parameter must specify contacts, registrars or all",
"rdap_error_400.json")); 400));
assertThat(response.getStatus()).isEqualTo(400); assertThat(response.getStatus()).isEqualTo(400);
metricSearchType = SearchType.NONE; // Error occurs before search type is set. metricSearchType = SearchType.NONE; // Error occurs before search type is set.
verifyErrorMetrics(Optional.empty(), 400); verifyErrorMetrics(Optional.empty(), 400);
@ -700,7 +703,7 @@ public class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEnt
generateExpectedJson( generateExpectedJson(
"fn=Entity+*&cursor=YzpFbnRpdHkgNA%3D%3D", "rdap_truncated_contacts.json")); "fn=Entity+*&cursor=YzpFbnRpdHkgNA%3D%3D", "rdap_truncated_contacts.json"));
assertThat(response.getStatus()).isEqualTo(200); assertThat(response.getStatus()).isEqualTo(200);
verifyMetrics(5); verifyMetrics(5, IncompletenessWarningType.TRUNCATED);
} }
@Test @Test
@ -714,7 +717,7 @@ public class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEnt
"fn=Entity+*&cursor=YzpFbnRpdHkgNA%3D%3D", "rdap_truncated_contacts.json")); "fn=Entity+*&cursor=YzpFbnRpdHkgNA%3D%3D", "rdap_truncated_contacts.json"));
assertThat(response.getStatus()).isEqualTo(200); assertThat(response.getStatus()).isEqualTo(200);
// For contacts, we only need to fetch one result set's worth (plus one). // For contacts, we only need to fetch one result set's worth (plus one).
verifyMetrics(5); verifyMetrics(5, IncompletenessWarningType.TRUNCATED);
} }
@Test @Test
@ -755,7 +758,7 @@ public class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEnt
generateExpectedJson( generateExpectedJson(
"fn=Entity+*&cursor=cjpFbnRpdHkgNA%3D%3D", "rdap_truncated_registrars.json")); "fn=Entity+*&cursor=cjpFbnRpdHkgNA%3D%3D", "rdap_truncated_registrars.json"));
assertThat(response.getStatus()).isEqualTo(200); assertThat(response.getStatus()).isEqualTo(200);
verifyMetrics(0); verifyMetrics(0, IncompletenessWarningType.TRUNCATED);
} }
@Test @Test
@ -767,7 +770,7 @@ public class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEnt
generateExpectedJson( generateExpectedJson(
"fn=Entity+*&cursor=cjpFbnRpdHkgNA%3D%3D", "rdap_truncated_registrars.json")); "fn=Entity+*&cursor=cjpFbnRpdHkgNA%3D%3D", "rdap_truncated_registrars.json"));
assertThat(response.getStatus()).isEqualTo(200); assertThat(response.getStatus()).isEqualTo(200);
verifyMetrics(0); verifyMetrics(0, IncompletenessWarningType.TRUNCATED);
} }
@Test @Test
@ -828,7 +831,7 @@ public class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEnt
generateExpectedJson( generateExpectedJson(
"fn=Entity+*&cursor=cjpFbnRpdHkgNA%3D%3D", "rdap_truncated_mixed_entities.json")); "fn=Entity+*&cursor=cjpFbnRpdHkgNA%3D%3D", "rdap_truncated_mixed_entities.json"));
assertThat(response.getStatus()).isEqualTo(200); assertThat(response.getStatus()).isEqualTo(200);
verifyMetrics(3); verifyMetrics(3, IncompletenessWarningType.TRUNCATED);
} }
@Test @Test
@ -1234,10 +1237,10 @@ public class RdapEntitySearchActionTest extends RdapSearchActionTestCase<RdapEnt
public void testHandleMatchMix_found_truncated() { public void testHandleMatchMix_found_truncated() {
createManyContactsAndRegistrars(30, 0, registrarTest); createManyContactsAndRegistrars(30, 0, registrarTest);
rememberWildcardType("00*"); rememberWildcardType("00*");
Object obj = generateActualJsonWithHandle("00*"); JSONObject obj = generateActualJsonWithHandle("00*");
assertThat(response.getStatus()).isEqualTo(200); assertThat(response.getStatus()).isEqualTo(200);
checkNumberOfEntitiesInResult(obj, 4); checkNumberOfEntitiesInResult(obj, 4);
verifyMetrics(5); verifyMetrics(5, IncompletenessWarningType.TRUNCATED);
} }
@Test @Test

View file

@ -15,16 +15,13 @@
package google.registry.rdap; package google.registry.rdap;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static google.registry.testing.TestDataHelper.loadFile;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import com.google.common.collect.ImmutableMap;
import google.registry.rdap.RdapMetrics.EndpointType; import google.registry.rdap.RdapMetrics.EndpointType;
import google.registry.rdap.RdapMetrics.SearchType; import google.registry.rdap.RdapMetrics.SearchType;
import google.registry.rdap.RdapMetrics.WildcardType; import google.registry.rdap.RdapMetrics.WildcardType;
import google.registry.rdap.RdapSearchResults.IncompletenessWarningType; import google.registry.rdap.RdapSearchResults.IncompletenessWarningType;
import google.registry.request.Action; import google.registry.request.Action;
import org.json.simple.JSONValue;
import org.junit.Test; import org.junit.Test;
/** Unit tests for {@link RdapHelpAction}. */ /** Unit tests for {@link RdapHelpAction}. */
@ -34,44 +31,38 @@ public class RdapHelpActionTest extends RdapActionBaseTestCase<RdapHelpAction> {
super(RdapHelpAction.class); super(RdapHelpAction.class);
} }
private Object generateExpectedJson(String name, String expectedOutputFile) {
return JSONValue.parse(
loadFile(this.getClass(), expectedOutputFile, ImmutableMap.of("NAME", name)));
}
@Test @Test
public void testHelpActionMaliciousPath_notFound() { public void testHelpActionMaliciousPath_notFound() {
assertThat(generateActualJson("../passwd")).isEqualTo( assertThat(generateActualJson("../passwd"))
generateExpectedJson( .isEqualTo(generateExpectedJsonError("no help found for ../passwd", 404));
"no help found for ../passwd", "rdap_error_404.json"));
assertThat(response.getStatus()).isEqualTo(404); assertThat(response.getStatus()).isEqualTo(404);
} }
@Test @Test
public void testHelpActionUnknownPath_notFound() { public void testHelpActionUnknownPath_notFound() {
assertThat(generateActualJson("hlarg")).isEqualTo( assertThat(generateActualJson("hlarg")).isEqualTo(
generateExpectedJson("no help found for hlarg", "rdap_error_404.json")); generateExpectedJsonError("no help found for hlarg", 404));
assertThat(response.getStatus()).isEqualTo(404); assertThat(response.getStatus()).isEqualTo(404);
} }
@Test @Test
public void testHelpActionDefault_getsIndex() { public void testHelpActionDefault_getsIndex() {
assertThat(generateActualJson("")) assertThat(generateActualJson(""))
.isEqualTo(generateExpectedJson("", "rdap_help_index.json")); .isEqualTo(loadJsonFile("rdap_help_index.json"));
assertThat(response.getStatus()).isEqualTo(200); assertThat(response.getStatus()).isEqualTo(200);
} }
@Test @Test
public void testHelpActionSlash_getsIndex() { public void testHelpActionSlash_getsIndex() {
assertThat(generateActualJson("/")) assertThat(generateActualJson("/"))
.isEqualTo(generateExpectedJson("", "rdap_help_index.json")); .isEqualTo(loadJsonFile("rdap_help_index.json"));
assertThat(response.getStatus()).isEqualTo(200); assertThat(response.getStatus()).isEqualTo(200);
} }
@Test @Test
public void testHelpActionTos_works() { public void testHelpActionTos_works() {
assertThat(generateActualJson("/tos")) assertThat(generateActualJson("/tos"))
.isEqualTo(generateExpectedJson("", "rdap_help_tos.json")); .isEqualTo(loadJsonFile("rdap_help_tos.json"));
assertThat(response.getStatus()).isEqualTo(200); assertThat(response.getStatus()).isEqualTo(200);
} }

View file

@ -27,10 +27,10 @@ import static google.registry.testing.TestDataHelper.loadFile;
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST; import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.googlecode.objectify.Key; import com.googlecode.objectify.Key;
import google.registry.config.RdapNoticeDescriptor;
import google.registry.model.contact.ContactResource; import google.registry.model.contact.ContactResource;
import google.registry.model.domain.DesignatedContact; import google.registry.model.domain.DesignatedContact;
import google.registry.model.domain.DomainBase; import google.registry.model.domain.DomainBase;
@ -44,12 +44,14 @@ import google.registry.model.reporting.HistoryEntry;
import google.registry.model.transfer.TransferData; import google.registry.model.transfer.TransferData;
import google.registry.model.transfer.TransferStatus; import google.registry.model.transfer.TransferStatus;
import google.registry.rdap.RdapJsonFormatter.OutputDataType; import google.registry.rdap.RdapJsonFormatter.OutputDataType;
import google.registry.rdap.RdapObjectClasses.BoilerplateType;
import google.registry.rdap.RdapObjectClasses.ReplyPayloadBase;
import google.registry.rdap.RdapObjectClasses.TopLevelReplyObject;
import google.registry.testing.AppEngineRule; import google.registry.testing.AppEngineRule;
import google.registry.testing.FakeClock; import google.registry.testing.FakeClock;
import google.registry.testing.InjectRule; import google.registry.testing.InjectRule;
import java.util.Optional; import java.util.Optional;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.json.simple.JSONValue;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
@ -84,8 +86,6 @@ public class RdapJsonFormatterTest {
private ContactResource contactResourceTech; private ContactResource contactResourceTech;
private ContactResource contactResourceNotLinked; private ContactResource contactResourceNotLinked;
private static final String LINK_BASE = "http://myserver.example.com/";
private static final String LINK_BASE_NO_TRAILING_SLASH = "http://myserver.example.com";
// Do not set a port43 whois server, as per Gustavo Lozano. // Do not set a port43 whois server, as per Gustavo Lozano.
private static final String WHOIS_SERVER = null; private static final String WHOIS_SERVER = null;
@ -307,415 +307,316 @@ public class RdapJsonFormatterTest {
.build()); .build());
} }
private Object loadJson(String expectedFileName) { private JsonElement loadJson(String expectedFileName) {
return JSONValue.parse(loadFile(this.getClass(), expectedFileName)); return new Gson().fromJson(loadFile(this.getClass(), expectedFileName), JsonElement.class);
} }
@Test @Test
public void testRegistrar() { public void testRegistrar() {
assertThat(rdapJsonFormatter.makeRdapJsonForRegistrar( assertThat(
registrar, false, LINK_BASE, WHOIS_SERVER, clock.nowUtc(), OutputDataType.FULL)) rdapJsonFormatter
.makeRdapJsonForRegistrar(
registrar, WHOIS_SERVER, clock.nowUtc(), OutputDataType.FULL)
.toJson())
.isEqualTo(loadJson("rdapjson_registrar.json")); .isEqualTo(loadJson("rdapjson_registrar.json"));
} }
@Test @Test
public void testRegistrar_summary() { public void testRegistrar_summary() {
assertThat(rdapJsonFormatter.makeRdapJsonForRegistrar( assertThat(
registrar, false, LINK_BASE, WHOIS_SERVER, clock.nowUtc(), OutputDataType.SUMMARY)) rdapJsonFormatter
.makeRdapJsonForRegistrar(
registrar, WHOIS_SERVER, clock.nowUtc(), OutputDataType.SUMMARY)
.toJson())
.isEqualTo(loadJson("rdapjson_registrar_summary.json")); .isEqualTo(loadJson("rdapjson_registrar_summary.json"));
} }
@Test @Test
public void testHost_ipv4() { public void testHost_ipv4() {
assertThat(rdapJsonFormatter.makeRdapJsonForHost( assertThat(
hostResourceIpv4, rdapJsonFormatter
false, .makeRdapJsonForHost(
LINK_BASE, hostResourceIpv4, WHOIS_SERVER, clock.nowUtc(), OutputDataType.FULL)
WHOIS_SERVER, .toJson())
clock.nowUtc(),
OutputDataType.FULL))
.isEqualTo(loadJson("rdapjson_host_ipv4.json")); .isEqualTo(loadJson("rdapjson_host_ipv4.json"));
} }
@Test @Test
public void testHost_ipv6() { public void testHost_ipv6() {
assertThat(rdapJsonFormatter.makeRdapJsonForHost( assertThat(
hostResourceIpv6, rdapJsonFormatter
false, .makeRdapJsonForHost(
LINK_BASE, hostResourceIpv6, WHOIS_SERVER, clock.nowUtc(), OutputDataType.FULL)
WHOIS_SERVER, .toJson())
clock.nowUtc(),
OutputDataType.FULL))
.isEqualTo(loadJson("rdapjson_host_ipv6.json")); .isEqualTo(loadJson("rdapjson_host_ipv6.json"));
} }
@Test @Test
public void testHost_both() { public void testHost_both() {
assertThat(rdapJsonFormatter.makeRdapJsonForHost( assertThat(
hostResourceBoth, rdapJsonFormatter
false, .makeRdapJsonForHost(
LINK_BASE, hostResourceBoth, WHOIS_SERVER, clock.nowUtc(), OutputDataType.FULL)
WHOIS_SERVER, .toJson())
clock.nowUtc(),
OutputDataType.FULL))
.isEqualTo(loadJson("rdapjson_host_both.json")); .isEqualTo(loadJson("rdapjson_host_both.json"));
} }
@Test @Test
public void testHost_both_summary() { public void testHost_both_summary() {
assertThat(rdapJsonFormatter.makeRdapJsonForHost( assertThat(
hostResourceBoth, rdapJsonFormatter
false, .makeRdapJsonForHost(
LINK_BASE, hostResourceBoth, WHOIS_SERVER, clock.nowUtc(), OutputDataType.SUMMARY)
WHOIS_SERVER, .toJson())
clock.nowUtc(),
OutputDataType.SUMMARY))
.isEqualTo(loadJson("rdapjson_host_both_summary.json")); .isEqualTo(loadJson("rdapjson_host_both_summary.json"));
} }
@Test @Test
public void testHost_noAddresses() { public void testHost_noAddresses() {
assertThat(rdapJsonFormatter.makeRdapJsonForHost( assertThat(
hostResourceNoAddresses, rdapJsonFormatter
false, .makeRdapJsonForHost(
LINK_BASE, hostResourceNoAddresses, WHOIS_SERVER, clock.nowUtc(), OutputDataType.FULL)
WHOIS_SERVER, .toJson())
clock.nowUtc(),
OutputDataType.FULL))
.isEqualTo(loadJson("rdapjson_host_no_addresses.json")); .isEqualTo(loadJson("rdapjson_host_no_addresses.json"));
} }
@Test @Test
public void testHost_notLinked() { public void testHost_notLinked() {
assertThat(rdapJsonFormatter.makeRdapJsonForHost( assertThat(
hostResourceNotLinked, rdapJsonFormatter
false, .makeRdapJsonForHost(
LINK_BASE, hostResourceNotLinked, WHOIS_SERVER, clock.nowUtc(), OutputDataType.FULL)
WHOIS_SERVER, .toJson())
clock.nowUtc(),
OutputDataType.FULL))
.isEqualTo(loadJson("rdapjson_host_not_linked.json")); .isEqualTo(loadJson("rdapjson_host_not_linked.json"));
} }
@Test @Test
public void testHost_superordinateHasPendingTransfer() { public void testHost_superordinateHasPendingTransfer() {
assertThat(rdapJsonFormatter.makeRdapJsonForHost( assertThat(
rdapJsonFormatter
.makeRdapJsonForHost(
hostResourceSuperordinatePendingTransfer, hostResourceSuperordinatePendingTransfer,
false,
LINK_BASE,
WHOIS_SERVER, WHOIS_SERVER,
clock.nowUtc(), clock.nowUtc(),
OutputDataType.FULL)) OutputDataType.FULL)
.toJson())
.isEqualTo(loadJson("rdapjson_host_pending_transfer.json")); .isEqualTo(loadJson("rdapjson_host_pending_transfer.json"));
} }
@Test @Test
public void testRegistrant() { public void testRegistrant() {
assertThat( assertThat(
rdapJsonFormatter.makeRdapJsonForContact( rdapJsonFormatter
.makeRdapJsonForContact(
contactResourceRegistrant, contactResourceRegistrant,
false,
Optional.of(DesignatedContact.Type.REGISTRANT), Optional.of(DesignatedContact.Type.REGISTRANT),
LINK_BASE,
WHOIS_SERVER, WHOIS_SERVER,
clock.nowUtc(), clock.nowUtc(),
OutputDataType.FULL, OutputDataType.FULL,
RdapAuthorization.create(RdapAuthorization.Role.REGISTRAR, "unicoderegistrar"))) RdapAuthorization.create(RdapAuthorization.Role.REGISTRAR, "unicoderegistrar"))
.toJson())
.isEqualTo(loadJson("rdapjson_registrant.json")); .isEqualTo(loadJson("rdapjson_registrant.json"));
} }
@Test @Test
public void testRegistrant_summary() { public void testRegistrant_summary() {
assertThat( assertThat(
rdapJsonFormatter.makeRdapJsonForContact( rdapJsonFormatter
.makeRdapJsonForContact(
contactResourceRegistrant, contactResourceRegistrant,
false,
Optional.of(DesignatedContact.Type.REGISTRANT), Optional.of(DesignatedContact.Type.REGISTRANT),
LINK_BASE,
WHOIS_SERVER, WHOIS_SERVER,
clock.nowUtc(), clock.nowUtc(),
OutputDataType.SUMMARY, OutputDataType.SUMMARY,
RdapAuthorization.create(RdapAuthorization.Role.REGISTRAR, "unicoderegistrar"))) RdapAuthorization.create(RdapAuthorization.Role.REGISTRAR, "unicoderegistrar"))
.toJson())
.isEqualTo(loadJson("rdapjson_registrant_summary.json")); .isEqualTo(loadJson("rdapjson_registrant_summary.json"));
} }
@Test @Test
public void testRegistrant_loggedOut() { public void testRegistrant_loggedOut() {
assertThat( assertThat(
rdapJsonFormatter.makeRdapJsonForContact( rdapJsonFormatter
.makeRdapJsonForContact(
contactResourceRegistrant, contactResourceRegistrant,
false,
Optional.of(DesignatedContact.Type.REGISTRANT), Optional.of(DesignatedContact.Type.REGISTRANT),
LINK_BASE,
WHOIS_SERVER, WHOIS_SERVER,
clock.nowUtc(), clock.nowUtc(),
OutputDataType.FULL, OutputDataType.FULL,
RdapAuthorization.PUBLIC_AUTHORIZATION)) RdapAuthorization.PUBLIC_AUTHORIZATION)
.toJson())
.isEqualTo(loadJson("rdapjson_registrant_logged_out.json")); .isEqualTo(loadJson("rdapjson_registrant_logged_out.json"));
} }
@Test @Test
public void testRegistrant_baseHasNoTrailingSlash() { public void testRegistrant_baseHasNoTrailingSlash() {
// First, make sure we have a trailing slash at the end by default!
// This test tries to change the default state, if the default doesn't have a /, then this test
// doesn't help.
assertThat(rdapJsonFormatter.fullServletPath).endsWith("/");
rdapJsonFormatter.fullServletPath =
rdapJsonFormatter.fullServletPath.substring(
0, rdapJsonFormatter.fullServletPath.length() - 1);
assertThat( assertThat(
rdapJsonFormatter.makeRdapJsonForContact( rdapJsonFormatter
.makeRdapJsonForContact(
contactResourceRegistrant, contactResourceRegistrant,
false,
Optional.of(DesignatedContact.Type.REGISTRANT), Optional.of(DesignatedContact.Type.REGISTRANT),
LINK_BASE_NO_TRAILING_SLASH,
WHOIS_SERVER, WHOIS_SERVER,
clock.nowUtc(), clock.nowUtc(),
OutputDataType.FULL, OutputDataType.FULL,
RdapAuthorization.create(RdapAuthorization.Role.REGISTRAR, "unicoderegistrar"))) RdapAuthorization.create(RdapAuthorization.Role.REGISTRAR, "unicoderegistrar"))
.toJson())
.isEqualTo(loadJson("rdapjson_registrant.json")); .isEqualTo(loadJson("rdapjson_registrant.json"));
} }
@Test
public void testRegistrant_noBase() {
assertThat(
rdapJsonFormatter.makeRdapJsonForContact(
contactResourceRegistrant,
false,
Optional.of(DesignatedContact.Type.REGISTRANT),
null,
WHOIS_SERVER,
clock.nowUtc(),
OutputDataType.FULL,
RdapAuthorization.create(RdapAuthorization.Role.REGISTRAR, "unicoderegistrar")))
.isEqualTo(loadJson("rdapjson_registrant_nobase.json"));
}
@Test @Test
public void testAdmin() { public void testAdmin() {
assertThat( assertThat(
rdapJsonFormatter.makeRdapJsonForContact( rdapJsonFormatter
.makeRdapJsonForContact(
contactResourceAdmin, contactResourceAdmin,
false,
Optional.of(DesignatedContact.Type.ADMIN), Optional.of(DesignatedContact.Type.ADMIN),
LINK_BASE,
WHOIS_SERVER, WHOIS_SERVER,
clock.nowUtc(), clock.nowUtc(),
OutputDataType.FULL, OutputDataType.FULL,
RdapAuthorization.create(RdapAuthorization.Role.REGISTRAR, "unicoderegistrar"))) RdapAuthorization.create(RdapAuthorization.Role.REGISTRAR, "unicoderegistrar"))
.toJson())
.isEqualTo(loadJson("rdapjson_admincontact.json")); .isEqualTo(loadJson("rdapjson_admincontact.json"));
} }
@Test @Test
public void testTech() { public void testTech() {
assertThat( assertThat(
rdapJsonFormatter.makeRdapJsonForContact( rdapJsonFormatter
.makeRdapJsonForContact(
contactResourceTech, contactResourceTech,
false,
Optional.of(DesignatedContact.Type.TECH), Optional.of(DesignatedContact.Type.TECH),
LINK_BASE,
WHOIS_SERVER, WHOIS_SERVER,
clock.nowUtc(), clock.nowUtc(),
OutputDataType.FULL, OutputDataType.FULL,
RdapAuthorization.create(RdapAuthorization.Role.REGISTRAR, "unicoderegistrar"))) RdapAuthorization.create(RdapAuthorization.Role.REGISTRAR, "unicoderegistrar"))
.toJson())
.isEqualTo(loadJson("rdapjson_techcontact.json")); .isEqualTo(loadJson("rdapjson_techcontact.json"));
} }
@Test @Test
public void testRolelessContact() { public void testRolelessContact() {
assertThat( assertThat(
rdapJsonFormatter.makeRdapJsonForContact( rdapJsonFormatter
.makeRdapJsonForContact(
contactResourceTech, contactResourceTech,
false,
Optional.empty(), Optional.empty(),
LINK_BASE,
WHOIS_SERVER, WHOIS_SERVER,
clock.nowUtc(), clock.nowUtc(),
OutputDataType.FULL, OutputDataType.FULL,
RdapAuthorization.create(RdapAuthorization.Role.REGISTRAR, "unicoderegistrar"))) RdapAuthorization.create(RdapAuthorization.Role.REGISTRAR, "unicoderegistrar"))
.toJson())
.isEqualTo(loadJson("rdapjson_rolelesscontact.json")); .isEqualTo(loadJson("rdapjson_rolelesscontact.json"));
} }
@Test @Test
public void testUnlinkedContact() { public void testUnlinkedContact() {
assertThat( assertThat(
rdapJsonFormatter.makeRdapJsonForContact( rdapJsonFormatter
.makeRdapJsonForContact(
contactResourceNotLinked, contactResourceNotLinked,
false,
Optional.empty(), Optional.empty(),
LINK_BASE,
WHOIS_SERVER, WHOIS_SERVER,
clock.nowUtc(), clock.nowUtc(),
OutputDataType.FULL, OutputDataType.FULL,
RdapAuthorization.create(RdapAuthorization.Role.REGISTRAR, "unicoderegistrar"))) RdapAuthorization.create(RdapAuthorization.Role.REGISTRAR, "unicoderegistrar"))
.toJson())
.isEqualTo(loadJson("rdapjson_unlinkedcontact.json")); .isEqualTo(loadJson("rdapjson_unlinkedcontact.json"));
} }
@Test @Test
public void testDomain_full() { public void testDomain_full() {
assertThat(rdapJsonFormatter.makeRdapJsonForDomain( assertThat(
rdapJsonFormatter
.makeRdapJsonForDomain(
domainBaseFull, domainBaseFull,
false,
LINK_BASE,
WHOIS_SERVER, WHOIS_SERVER,
clock.nowUtc(), clock.nowUtc(),
OutputDataType.FULL, OutputDataType.FULL,
RdapAuthorization.create(RdapAuthorization.Role.REGISTRAR, "unicoderegistrar"))) RdapAuthorization.create(RdapAuthorization.Role.REGISTRAR, "unicoderegistrar"))
.toJson())
.isEqualTo(loadJson("rdapjson_domain_full.json")); .isEqualTo(loadJson("rdapjson_domain_full.json"));
} }
@Test @Test
public void testDomain_summary() { public void testDomain_summary() {
assertThat(rdapJsonFormatter.makeRdapJsonForDomain( assertThat(
rdapJsonFormatter
.makeRdapJsonForDomain(
domainBaseFull, domainBaseFull,
false,
LINK_BASE,
WHOIS_SERVER, WHOIS_SERVER,
clock.nowUtc(), clock.nowUtc(),
OutputDataType.SUMMARY, OutputDataType.SUMMARY,
RdapAuthorization.create(RdapAuthorization.Role.REGISTRAR, "unicoderegistrar"))) RdapAuthorization.create(RdapAuthorization.Role.REGISTRAR, "unicoderegistrar"))
.toJson())
.isEqualTo(loadJson("rdapjson_domain_summary.json")); .isEqualTo(loadJson("rdapjson_domain_summary.json"));
} }
@Test @Test
public void testDomain_logged_out() { public void testDomain_logged_out() {
assertThat(rdapJsonFormatter.makeRdapJsonForDomain( assertThat(
rdapJsonFormatter
.makeRdapJsonForDomain(
domainBaseFull, domainBaseFull,
false,
LINK_BASE,
WHOIS_SERVER, WHOIS_SERVER,
clock.nowUtc(), clock.nowUtc(),
OutputDataType.FULL, OutputDataType.FULL,
RdapAuthorization.PUBLIC_AUTHORIZATION)) RdapAuthorization.PUBLIC_AUTHORIZATION)
.toJson())
.isEqualTo(loadJson("rdapjson_domain_logged_out.json")); .isEqualTo(loadJson("rdapjson_domain_logged_out.json"));
} }
@Test @Test
public void testDomain_noNameserversNoTransfers() { public void testDomain_noNameserversNoTransfers() {
assertThat(rdapJsonFormatter.makeRdapJsonForDomain( assertThat(
rdapJsonFormatter
.makeRdapJsonForDomain(
domainBaseNoNameserversNoTransfers, domainBaseNoNameserversNoTransfers,
false,
LINK_BASE,
WHOIS_SERVER, WHOIS_SERVER,
clock.nowUtc(), clock.nowUtc(),
OutputDataType.FULL, OutputDataType.FULL,
RdapAuthorization.create(RdapAuthorization.Role.REGISTRAR, "unicoderegistrar"))) RdapAuthorization.create(RdapAuthorization.Role.REGISTRAR, "unicoderegistrar"))
.toJson())
.isEqualTo(loadJson("rdapjson_domain_no_nameservers.json")); .isEqualTo(loadJson("rdapjson_domain_no_nameservers.json"));
} }
@Test @Test
public void testError() { public void testError() {
assertThat( assertThat(
rdapJsonFormatter RdapObjectClasses.ErrorResponse.create(
.makeError(SC_BAD_REQUEST, "Invalid Domain Name", "Not a valid domain name")) SC_BAD_REQUEST, "Invalid Domain Name", "Not a valid domain name")
.toJson())
.isEqualTo(loadJson("rdapjson_error.json")); .isEqualTo(loadJson("rdapjson_error.json"));
} }
@Test
public void testHelp_absoluteHtmlUrl() {
assertThat(RdapJsonFormatter.makeRdapJsonNotice(
RdapNoticeDescriptor.builder()
.setTitle("RDAP Help")
.setDescription(ImmutableList.of(
"RDAP Help Topics (use /help/topic for information)",
"syntax",
"tos (Terms of Service)"))
.setLinkValueSuffix("help/index")
.setLinkHrefUrlString(LINK_BASE + "about/rdap/index.html")
.build(),
LINK_BASE))
.isEqualTo(loadJson("rdapjson_notice_alternate_link.json"));
}
@Test
public void testHelp_relativeHtmlUrlWithStartingSlash() {
assertThat(RdapJsonFormatter.makeRdapJsonNotice(
RdapNoticeDescriptor.builder()
.setTitle("RDAP Help")
.setDescription(ImmutableList.of(
"RDAP Help Topics (use /help/topic for information)",
"syntax",
"tos (Terms of Service)"))
.setLinkValueSuffix("help/index")
.setLinkHrefUrlString("/about/rdap/index.html")
.build(),
LINK_BASE))
.isEqualTo(loadJson("rdapjson_notice_alternate_link.json"));
}
@Test
public void testHelp_relativeHtmlUrlWithoutStartingSlash() {
assertThat(RdapJsonFormatter.makeRdapJsonNotice(
RdapNoticeDescriptor.builder()
.setTitle("RDAP Help")
.setDescription(ImmutableList.of(
"RDAP Help Topics (use /help/topic for information)",
"syntax",
"tos (Terms of Service)"))
.setLinkValueSuffix("help/index")
.setLinkHrefUrlString("about/rdap/index.html")
.build(),
LINK_BASE))
.isEqualTo(loadJson("rdapjson_notice_alternate_link.json"));
}
@Test
public void testHelp_noHtmlUrl() {
assertThat(RdapJsonFormatter.makeRdapJsonNotice(
RdapNoticeDescriptor.builder()
.setTitle("RDAP Help")
.setDescription(ImmutableList.of(
"RDAP Help Topics (use /help/topic for information)",
"syntax",
"tos (Terms of Service)"))
.setLinkValueSuffix("help/index")
.build(),
LINK_BASE))
.isEqualTo(loadJson("rdapjson_notice_self_link.json"));
}
@Test @Test
public void testTopLevel() { public void testTopLevel() {
ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>(); assertThat(
builder.put("key", "value"); TopLevelReplyObject.create(
rdapJsonFormatter.addTopLevelEntries( new ReplyPayloadBase(BoilerplateType.OTHER) {
builder, @JsonableElement public String key = "value";
RdapJsonFormatter.BoilerplateType.OTHER, },
ImmutableList.of(), rdapJsonFormatter.createTosNotice())
ImmutableList.of(), .toJson())
LINK_BASE); .isEqualTo(loadJson("rdapjson_toplevel.json"));
assertThat(builder.build()).isEqualTo(loadJson("rdapjson_toplevel.json"));
}
@Test
public void testTopLevel_withTermsOfService() {
ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>();
builder.put("key", "value");
rdapJsonFormatter.addTopLevelEntries(
builder,
RdapJsonFormatter.BoilerplateType.OTHER,
ImmutableList.of(rdapJsonFormatter.getJsonHelpNotice("/tos", LINK_BASE)),
ImmutableList.of(),
LINK_BASE);
assertThat(builder.build()).isEqualTo(loadJson("rdapjson_toplevel.json"));
} }
@Test @Test
public void testTopLevel_domain() { public void testTopLevel_domain() {
ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>(); assertThat(
builder.put("key", "value"); TopLevelReplyObject.create(
rdapJsonFormatter.addTopLevelEntries( new ReplyPayloadBase(BoilerplateType.DOMAIN) {
builder, @JsonableElement public String key = "value";
RdapJsonFormatter.BoilerplateType.DOMAIN, },
ImmutableList.of(), rdapJsonFormatter.createTosNotice())
ImmutableList.of(), .toJson())
LINK_BASE); .isEqualTo(loadJson("rdapjson_toplevel_domain.json"));
assertThat(builder.build()).isEqualTo(loadJson("rdapjson_toplevel_domain.json"));
}
@Test
public void testTopLevel_domainWithTermsOfService() {
ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>();
builder.put("key", "value");
rdapJsonFormatter.addTopLevelEntries(
builder,
RdapJsonFormatter.BoilerplateType.DOMAIN,
ImmutableList.of(rdapJsonFormatter.getJsonHelpNotice("/tos", LINK_BASE)),
ImmutableList.of(),
LINK_BASE);
assertThat(builder.build()).isEqualTo(loadJson("rdapjson_toplevel_domain.json"));
} }
} }

View file

@ -19,7 +19,6 @@ import static google.registry.testing.DatastoreHelper.createTld;
import static google.registry.testing.DatastoreHelper.persistResource; import static google.registry.testing.DatastoreHelper.persistResource;
import static google.registry.testing.FullFieldsTestEntityHelper.makeAndPersistHostResource; import static google.registry.testing.FullFieldsTestEntityHelper.makeAndPersistHostResource;
import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrar; import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrar;
import static google.registry.testing.TestDataHelper.loadFile;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
@ -34,7 +33,7 @@ import google.registry.request.Action;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import org.json.simple.JSONValue; import org.json.simple.JSONObject;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -92,10 +91,9 @@ public class RdapNameserverActionTest extends RdapActionBaseTestCase<RdapNameser
if (!punycodeSet) { if (!punycodeSet) {
builder.put("PUNYCODENAME", name); builder.put("PUNYCODENAME", name);
} }
return JSONValue.parse(loadFile( return loadJsonFile(
this.getClass(),
expectedOutputFile, expectedOutputFile,
builder.build())); builder.build());
} }
private Object generateExpectedJsonWithTopLevelEntries( private Object generateExpectedJsonWithTopLevelEntries(
@ -114,9 +112,8 @@ public class RdapNameserverActionTest extends RdapActionBaseTestCase<RdapNameser
builder, builder,
RdapTestHelper.createNotices( RdapTestHelper.createNotices(
"https://example.tld/rdap/", "https://example.tld/rdap/",
RdapTestHelper.ContactNoticeType.NONE,
map.get("notices"))); map.get("notices")));
obj = builder.build(); obj = new JSONObject(builder.build());
} }
return obj; return obj;
} }
@ -125,17 +122,16 @@ public class RdapNameserverActionTest extends RdapActionBaseTestCase<RdapNameser
public void testInvalidNameserver_returns400() { public void testInvalidNameserver_returns400() {
assertThat(generateActualJson("invalid/host/name")) assertThat(generateActualJson("invalid/host/name"))
.isEqualTo( .isEqualTo(
generateExpectedJson( generateExpectedJsonError(
"invalid/host/name is not a valid nameserver: Invalid host name", "invalid/host/name is not a valid nameserver: Invalid host name",
null, 400));
"rdap_error_400.json"));
assertThat(response.getStatus()).isEqualTo(400); assertThat(response.getStatus()).isEqualTo(400);
} }
@Test @Test
public void testUnknownNameserver_returns404() { public void testUnknownNameserver_returns404() {
assertThat(generateActualJson("ns1.missing.com")).isEqualTo( assertThat(generateActualJson("ns1.missing.com")).isEqualTo(
generateExpectedJson("ns1.missing.com not found", null, "rdap_error_404.json")); generateExpectedJsonError("ns1.missing.com not found", 404));
assertThat(response.getStatus()).isEqualTo(404); assertThat(response.getStatus()).isEqualTo(404);
} }

View file

@ -26,7 +26,6 @@ import static google.registry.testing.FullFieldsTestEntityHelper.makeDomainBase;
import static google.registry.testing.FullFieldsTestEntityHelper.makeHostResource; import static google.registry.testing.FullFieldsTestEntityHelper.makeHostResource;
import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrar; import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrar;
import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrarContacts; import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrarContacts;
import static google.registry.testing.TestDataHelper.loadFile;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ImmutableListMultimap;
@ -43,7 +42,6 @@ import google.registry.rdap.RdapSearchResults.IncompletenessWarningType;
import google.registry.testing.FakeResponse; import google.registry.testing.FakeResponse;
import java.net.URLDecoder; import java.net.URLDecoder;
import java.util.Optional; import java.util.Optional;
import javax.annotation.Nullable;
import org.json.simple.JSONArray; import org.json.simple.JSONArray;
import org.json.simple.JSONObject; import org.json.simple.JSONObject;
import org.json.simple.JSONValue; import org.json.simple.JSONValue;
@ -65,11 +63,11 @@ public class RdapNameserverSearchActionTest
private HostResource hostNs1CatLol; private HostResource hostNs1CatLol;
private HostResource hostNs2CatLol; private HostResource hostNs2CatLol;
private Object generateActualJsonWithName(String name) { private JSONObject generateActualJsonWithName(String name) {
return generateActualJsonWithName(name, null); return generateActualJsonWithName(name, null);
} }
private Object generateActualJsonWithName(String name, String cursor) { private JSONObject generateActualJsonWithName(String name, String cursor) {
metricSearchType = SearchType.BY_NAMESERVER_NAME; metricSearchType = SearchType.BY_NAMESERVER_NAME;
rememberWildcardType(name); rememberWildcardType(name);
action.nameParam = Optional.of(name); action.nameParam = Optional.of(name);
@ -81,14 +79,14 @@ public class RdapNameserverSearchActionTest
action.cursorTokenParam = Optional.of(cursor); action.cursorTokenParam = Optional.of(cursor);
} }
action.run(); action.run();
return JSONValue.parse(response.getPayload()); return (JSONObject) JSONValue.parse(response.getPayload());
} }
private Object generateActualJsonWithIp(String ipString) { private JSONObject generateActualJsonWithIp(String ipString) {
return generateActualJsonWithIp(ipString, null); return generateActualJsonWithIp(ipString, null);
} }
private Object generateActualJsonWithIp(String ipString, String cursor) { private JSONObject generateActualJsonWithIp(String ipString, String cursor) {
metricSearchType = SearchType.BY_NAMESERVER_ADDRESS; metricSearchType = SearchType.BY_NAMESERVER_ADDRESS;
action.parameterMap = ImmutableListMultimap.of("ip", ipString); action.parameterMap = ImmutableListMultimap.of("ip", ipString);
action.ipParam = Optional.of(ipString); action.ipParam = Optional.of(ipString);
@ -100,7 +98,7 @@ public class RdapNameserverSearchActionTest
action.cursorTokenParam = Optional.of(cursor); action.cursorTokenParam = Optional.of(cursor);
} }
action.run(); action.run();
return JSONValue.parse(response.getPayload()); return (JSONObject) JSONValue.parse(response.getPayload());
} }
@Before @Before
@ -156,59 +154,30 @@ public class RdapNameserverSearchActionTest
action.nameParam = Optional.empty(); action.nameParam = Optional.empty();
} }
private Object generateExpectedJson(String expectedOutputFile) { private JSONObject generateExpectedJsonForNameserver(
return generateExpectedJson(null, null, null, null, null, expectedOutputFile);
}
private Object generateExpectedJson(String name, String expectedOutputFile) {
return generateExpectedJson(name, null, null, null, null, expectedOutputFile);
}
private Object generateExpectedJson(
@Nullable String name,
@Nullable String punycodeName,
@Nullable String handle,
@Nullable String ipAddressType,
@Nullable String ipAddress,
String expectedOutputFile) {
ImmutableMap.Builder<String, String> builder = new ImmutableMap.Builder<>();
if (name != null) {
builder.put("NAME", name);
}
if ((name != null) || (punycodeName != null)) {
builder.put("PUNYCODENAME", (punycodeName == null) ? name : punycodeName);
}
if (handle != null) {
builder.put("HANDLE", handle);
}
if (ipAddressType != null) {
builder.put("ADDRESSTYPE", ipAddressType);
}
if (ipAddress != null) {
builder.put("ADDRESS", ipAddress);
}
builder.put("STATUS", "active");
builder.put("TYPE", "nameserver");
return JSONValue.parse(
loadFile(this.getClass(), expectedOutputFile, builder.build()));
}
private Object generateExpectedJsonForNameserver(
String name, String name,
String punycodeName, String punycodeName,
String handle, String handle,
String ipAddressType, String ipAddressType,
String ipAddress, String ipAddress,
String expectedOutputFile) { String expectedOutputFile) {
Object obj = JSONObject obj =
generateExpectedJson( loadJsonFile(
name, punycodeName, handle, ipAddressType, ipAddress, expectedOutputFile); expectedOutputFile,
"NAME", name,
"PUNYCODENAME", punycodeName,
"HANDLE", handle,
"ADDRESSTYPE", ipAddressType,
"ADDRESS", ipAddress,
"STATUS", "active",
"TYPE", "nameserver");
obj.remove("rdapConformance");
ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>(); ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>();
builder.put("nameserverSearchResults", ImmutableList.of(obj)); builder.put("nameserverSearchResults", ImmutableList.of(obj));
builder.put("rdapConformance", ImmutableList.of("icann_rdap_response_profile_0")); builder.put("rdapConformance", ImmutableList.of("icann_rdap_response_profile_0"));
RdapTestHelper.addNonDomainBoilerplateNotices( RdapTestHelper.addNonDomainBoilerplateNotices(
builder, RdapTestHelper.createNotices("https://example.tld/rdap/")); builder, RdapTestHelper.createNotices("https://example.tld/rdap/"));
return builder.build(); return new JSONObject(builder.build());
} }
private void createManyHosts(int numHosts) { private void createManyHosts(int numHosts) {
@ -234,6 +203,11 @@ public class RdapNameserverSearchActionTest
.build()); .build());
} }
private void verifyMetrics(
long numHostsRetrieved, IncompletenessWarningType incompletenessWarningType) {
verifyMetrics(Optional.of(numHostsRetrieved), incompletenessWarningType);
}
private void verifyMetrics(long numHostsRetrieved) { private void verifyMetrics(long numHostsRetrieved) {
verifyMetrics(Optional.of(numHostsRetrieved), IncompletenessWarningType.COMPLETE); verifyMetrics(Optional.of(numHostsRetrieved), IncompletenessWarningType.COMPLETE);
} }
@ -274,8 +248,9 @@ public class RdapNameserverSearchActionTest
action.run(); action.run();
assertThat(JSONValue.parse(response.getPayload())) assertThat(JSONValue.parse(response.getPayload()))
.isEqualTo( .isEqualTo(
generateExpectedJson( generateExpectedJsonError(
"You must specify either name=XXXX or ip=YYYY", "rdap_error_400.json")); "You must specify either name=XXXX or ip=YYYY",
400));
assertThat(response.getStatus()).isEqualTo(400); assertThat(response.getStatus()).isEqualTo(400);
verifyErrorMetrics(Optional.empty(), 400); verifyErrorMetrics(Optional.empty(), 400);
} }
@ -284,10 +259,10 @@ public class RdapNameserverSearchActionTest
public void testInvalidSuffix_rejected() { public void testInvalidSuffix_rejected() {
assertThat(generateActualJsonWithName("exam*ple")) assertThat(generateActualJsonWithName("exam*ple"))
.isEqualTo( .isEqualTo(
generateExpectedJson( generateExpectedJsonError(
"Suffix after wildcard must be one or more domain" "Suffix after wildcard must be one or more domain"
+ " name labels, e.g. exam*.tld, ns*.example.tld", + " name labels, e.g. exam*.tld, ns*.example.tld",
"rdap_error_422.json")); 422));
assertThat(response.getStatus()).isEqualTo(422); assertThat(response.getStatus()).isEqualTo(422);
verifyErrorMetrics(Optional.empty(), 422); verifyErrorMetrics(Optional.empty(), 422);
} }
@ -296,9 +271,9 @@ public class RdapNameserverSearchActionTest
public void testNonexistentDomainSuffix_unprocessable() { public void testNonexistentDomainSuffix_unprocessable() {
assertThat(generateActualJsonWithName("exam*.foo.bar")) assertThat(generateActualJsonWithName("exam*.foo.bar"))
.isEqualTo( .isEqualTo(
generateExpectedJson( generateExpectedJsonError(
"A suffix after a wildcard in a nameserver lookup must be an in-bailiwick domain", "A suffix after a wildcard in a nameserver lookup must be an in-bailiwick domain",
"rdap_error_422.json")); 422));
assertThat(response.getStatus()).isEqualTo(422); assertThat(response.getStatus()).isEqualTo(422);
verifyErrorMetrics(Optional.empty(), 422); verifyErrorMetrics(Optional.empty(), 422);
} }
@ -306,7 +281,7 @@ public class RdapNameserverSearchActionTest
@Test @Test
public void testMultipleWildcards_rejected() { public void testMultipleWildcards_rejected() {
assertThat(generateActualJsonWithName("*.*")) assertThat(generateActualJsonWithName("*.*"))
.isEqualTo(generateExpectedJson("Only one wildcard allowed", "rdap_error_422.json")); .isEqualTo(generateExpectedJsonError("Only one wildcard allowed", 422));
assertThat(response.getStatus()).isEqualTo(422); assertThat(response.getStatus()).isEqualTo(422);
verifyErrorMetrics(Optional.empty(), 422); verifyErrorMetrics(Optional.empty(), 422);
} }
@ -315,8 +290,8 @@ public class RdapNameserverSearchActionTest
public void testNoCharactersToMatch_rejected() { public void testNoCharactersToMatch_rejected() {
assertThat(generateActualJsonWithName("*")) assertThat(generateActualJsonWithName("*"))
.isEqualTo( .isEqualTo(
generateExpectedJson( generateExpectedJsonError(
"Initial search string must be at least 2 characters", "rdap_error_422.json")); "Initial search string must be at least 2 characters", 422));
assertThat(response.getStatus()).isEqualTo(422); assertThat(response.getStatus()).isEqualTo(422);
verifyErrorMetrics(Optional.empty(), 422); verifyErrorMetrics(Optional.empty(), 422);
} }
@ -325,8 +300,8 @@ public class RdapNameserverSearchActionTest
public void testFewerThanTwoCharactersToMatch_rejected() { public void testFewerThanTwoCharactersToMatch_rejected() {
assertThat(generateActualJsonWithName("a*")) assertThat(generateActualJsonWithName("a*"))
.isEqualTo( .isEqualTo(
generateExpectedJson( generateExpectedJsonError(
"Initial search string must be at least 2 characters", "rdap_error_422.json")); "Initial search string must be at least 2 characters", 422));
assertThat(response.getStatus()).isEqualTo(422); assertThat(response.getStatus()).isEqualTo(422);
verifyErrorMetrics(Optional.empty(), 422); verifyErrorMetrics(Optional.empty(), 422);
} }
@ -491,7 +466,7 @@ public class RdapNameserverSearchActionTest
public void testNameMatch_nsstar_found() { public void testNameMatch_nsstar_found() {
generateActualJsonWithName("ns*"); generateActualJsonWithName("ns*");
assertThat(response.getStatus()).isEqualTo(200); assertThat(response.getStatus()).isEqualTo(200);
verifyMetrics(5); verifyMetrics(5, IncompletenessWarningType.TRUNCATED);
} }
@Test @Test
@ -505,7 +480,7 @@ public class RdapNameserverSearchActionTest
public void testNameMatch_ns1_castar_found() { public void testNameMatch_ns1_castar_found() {
generateActualJsonWithName("ns1.ca*"); generateActualJsonWithName("ns1.ca*");
assertThat(response.getStatus()).isEqualTo(200); assertThat(response.getStatus()).isEqualTo(200);
verifyMetrics(5); verifyMetrics(5, IncompletenessWarningType.TRUNCATED);
} }
@Test @Test
@ -519,7 +494,7 @@ public class RdapNameserverSearchActionTest
public void testNameMatch_nontruncatedResultSet() { public void testNameMatch_nontruncatedResultSet() {
createManyHosts(4); createManyHosts(4);
assertThat(generateActualJsonWithName("nsx*.cat.lol")) assertThat(generateActualJsonWithName("nsx*.cat.lol"))
.isEqualTo(generateExpectedJson("rdap_nontruncated_hosts.json")); .isEqualTo(loadJsonFile("rdap_nontruncated_hosts.json"));
assertThat(response.getStatus()).isEqualTo(200); assertThat(response.getStatus()).isEqualTo(200);
verifyMetrics(4); verifyMetrics(4);
} }
@ -529,10 +504,10 @@ public class RdapNameserverSearchActionTest
createManyHosts(5); createManyHosts(5);
assertThat(generateActualJsonWithName("nsx*.cat.lol")) assertThat(generateActualJsonWithName("nsx*.cat.lol"))
.isEqualTo( .isEqualTo(
generateExpectedJson( loadJsonFile(
"name=nsx*.cat.lol&cursor=bnN4NC5jYXQubG9s", "rdap_truncated_hosts.json")); "rdap_truncated_hosts.json", "QUERY", "name=nsx*.cat.lol&cursor=bnN4NC5jYXQubG9s"));
assertThat(response.getStatus()).isEqualTo(200); assertThat(response.getStatus()).isEqualTo(200);
verifyMetrics(5); verifyMetrics(5, IncompletenessWarningType.TRUNCATED);
} }
@Test @Test
@ -540,11 +515,11 @@ public class RdapNameserverSearchActionTest
createManyHosts(9); createManyHosts(9);
assertThat(generateActualJsonWithName("nsx*.cat.lol")) assertThat(generateActualJsonWithName("nsx*.cat.lol"))
.isEqualTo( .isEqualTo(
generateExpectedJson( loadJsonFile(
"name=nsx*.cat.lol&cursor=bnN4NC5jYXQubG9s", "rdap_truncated_hosts.json")); "rdap_truncated_hosts.json", "QUERY", "name=nsx*.cat.lol&cursor=bnN4NC5jYXQubG9s"));
assertThat(response.getStatus()).isEqualTo(200); assertThat(response.getStatus()).isEqualTo(200);
// When searching names, we look for additional matches, in case some are not visible. // When searching names, we look for additional matches, in case some are not visible.
verifyMetrics(9); verifyMetrics(9, IncompletenessWarningType.TRUNCATED);
} }
@Test @Test
@ -568,7 +543,7 @@ public class RdapNameserverSearchActionTest
public void testNameMatchDeletedHost_notFound() { public void testNameMatchDeletedHost_notFound() {
persistResource(hostNs1CatLol.asBuilder().setDeletionTime(clock.nowUtc().minusDays(1)).build()); persistResource(hostNs1CatLol.asBuilder().setDeletionTime(clock.nowUtc().minusDays(1)).build());
assertThat(generateActualJsonWithName("ns1.cat.lol")) assertThat(generateActualJsonWithName("ns1.cat.lol"))
.isEqualTo(generateExpectedJson("No nameservers found", "rdap_error_404.json")); .isEqualTo(generateExpectedJsonError("No nameservers found", 404));
assertThat(response.getStatus()).isEqualTo(404); assertThat(response.getStatus()).isEqualTo(404);
verifyErrorMetrics(); verifyErrorMetrics();
} }
@ -577,7 +552,7 @@ public class RdapNameserverSearchActionTest
public void testNameMatchDeletedHostWithWildcard_notFound() { public void testNameMatchDeletedHostWithWildcard_notFound() {
persistResource(hostNs1CatLol.asBuilder().setDeletionTime(clock.nowUtc().minusDays(1)).build()); persistResource(hostNs1CatLol.asBuilder().setDeletionTime(clock.nowUtc().minusDays(1)).build());
assertThat(generateActualJsonWithName("cat.lo*")) assertThat(generateActualJsonWithName("cat.lo*"))
.isEqualTo(generateExpectedJson("No nameservers found", "rdap_error_404.json")); .isEqualTo(generateExpectedJsonError("No nameservers found", 404));
assertThat(response.getStatus()).isEqualTo(404); assertThat(response.getStatus()).isEqualTo(404);
verifyErrorMetrics(); verifyErrorMetrics();
} }
@ -669,7 +644,7 @@ public class RdapNameserverSearchActionTest
int expectedPageCount = int expectedPageCount =
(expectedNames.size() + action.rdapResultSetMaxSize - 1) / action.rdapResultSetMaxSize; (expectedNames.size() + action.rdapResultSetMaxSize - 1) / action.rdapResultSetMaxSize;
for (int pageNum = 0; pageNum < expectedPageCount; pageNum++) { for (int pageNum = 0; pageNum < expectedPageCount; pageNum++) {
Object results = JSONObject results =
byName byName
? generateActualJsonWithName(paramValue, cursor) ? generateActualJsonWithName(paramValue, cursor)
: generateActualJsonWithIp(paramValue, cursor); : generateActualJsonWithIp(paramValue, cursor);
@ -682,7 +657,7 @@ public class RdapNameserverSearchActionTest
int pos = linkToNext.indexOf("cursor="); int pos = linkToNext.indexOf("cursor=");
assertThat(pos).isAtLeast(0); assertThat(pos).isAtLeast(0);
cursor = URLDecoder.decode(linkToNext.substring(pos + 7), "UTF-8"); cursor = URLDecoder.decode(linkToNext.substring(pos + 7), "UTF-8");
Object searchResults = ((JSONObject) results).get("nameserverSearchResults"); Object searchResults = results.get("nameserverSearchResults");
assertThat(searchResults).isInstanceOf(JSONArray.class); assertThat(searchResults).isInstanceOf(JSONArray.class);
assertThat(((JSONArray) searchResults)).hasSize(action.rdapResultSetMaxSize); assertThat(((JSONArray) searchResults)).hasSize(action.rdapResultSetMaxSize);
for (Object item : ((JSONArray) searchResults)) { for (Object item : ((JSONArray) searchResults)) {
@ -776,7 +751,7 @@ public class RdapNameserverSearchActionTest
@Test @Test
public void testAddressMatchV6Address_foundMultiple() { public void testAddressMatchV6Address_foundMultiple() {
assertThat(generateActualJsonWithIp("bad:f00d:cafe::15:beef")) assertThat(generateActualJsonWithIp("bad:f00d:cafe::15:beef"))
.isEqualTo(generateExpectedJson("rdap_multiple_hosts.json")); .isEqualTo(loadJsonFile("rdap_multiple_hosts.json"));
assertThat(response.getStatus()).isEqualTo(200); assertThat(response.getStatus()).isEqualTo(200);
verifyMetrics(2); verifyMetrics(2);
} }
@ -792,7 +767,7 @@ public class RdapNameserverSearchActionTest
public void testAddressMatchDeletedHost_notFound() { public void testAddressMatchDeletedHost_notFound() {
persistResource(hostNs1CatLol.asBuilder().setDeletionTime(clock.nowUtc().minusDays(1)).build()); persistResource(hostNs1CatLol.asBuilder().setDeletionTime(clock.nowUtc().minusDays(1)).build());
assertThat(generateActualJsonWithIp("1.2.3.4")) assertThat(generateActualJsonWithIp("1.2.3.4"))
.isEqualTo(generateExpectedJson("No nameservers found", "rdap_error_404.json")); .isEqualTo(generateExpectedJsonError("No nameservers found", 404));
assertThat(response.getStatus()).isEqualTo(404); assertThat(response.getStatus()).isEqualTo(404);
verifyErrorMetrics(); verifyErrorMetrics();
} }
@ -801,7 +776,7 @@ public class RdapNameserverSearchActionTest
public void testAddressMatch_nontruncatedResultSet() { public void testAddressMatch_nontruncatedResultSet() {
createManyHosts(4); createManyHosts(4);
assertThat(generateActualJsonWithIp("5.5.5.1")) assertThat(generateActualJsonWithIp("5.5.5.1"))
.isEqualTo(generateExpectedJson("rdap_nontruncated_hosts.json")); .isEqualTo(loadJsonFile("rdap_nontruncated_hosts.json"));
assertThat(response.getStatus()).isEqualTo(200); assertThat(response.getStatus()).isEqualTo(200);
verifyMetrics(4); verifyMetrics(4);
} }
@ -811,10 +786,10 @@ public class RdapNameserverSearchActionTest
createManyHosts(5); createManyHosts(5);
assertThat(generateActualJsonWithIp("5.5.5.1")) assertThat(generateActualJsonWithIp("5.5.5.1"))
.isEqualTo( .isEqualTo(
generateExpectedJson( loadJsonFile(
"ip=5.5.5.1&cursor=MTctUk9JRA%3D%3D", "rdap_truncated_hosts.json")); "rdap_truncated_hosts.json", "QUERY", "ip=5.5.5.1&cursor=MTctUk9JRA%3D%3D"));
assertThat(response.getStatus()).isEqualTo(200); assertThat(response.getStatus()).isEqualTo(200);
verifyMetrics(5); verifyMetrics(5, IncompletenessWarningType.TRUNCATED);
} }
@Test @Test
@ -822,12 +797,12 @@ public class RdapNameserverSearchActionTest
createManyHosts(9); createManyHosts(9);
assertThat(generateActualJsonWithIp("5.5.5.1")) assertThat(generateActualJsonWithIp("5.5.5.1"))
.isEqualTo( .isEqualTo(
generateExpectedJson( loadJsonFile(
"ip=5.5.5.1&cursor=MTctUk9JRA%3D%3D", "rdap_truncated_hosts.json")); "rdap_truncated_hosts.json", "QUERY", "ip=5.5.5.1&cursor=MTctUk9JRA%3D%3D"));
assertThat(response.getStatus()).isEqualTo(200); assertThat(response.getStatus()).isEqualTo(200);
// When searching by address and not including deleted, we don't need to search for extra // When searching by address and not including deleted, we don't need to search for extra
// matches. // matches.
verifyMetrics(5); verifyMetrics(5, IncompletenessWarningType.TRUNCATED);
} }
@Test @Test

View file

@ -16,9 +16,12 @@ package google.registry.rdap;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import google.registry.config.RdapNoticeDescriptor; import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
@ -28,6 +31,12 @@ import org.json.simple.JSONObject;
public class RdapTestHelper { public class RdapTestHelper {
private static final Gson GSON = new GsonBuilder().create();
static JsonElement createJson(String... lines) {
return GSON.fromJson(Joiner.on("\n").join(lines), JsonElement.class);
}
enum ContactNoticeType { enum ContactNoticeType {
NONE, NONE,
DOMAIN, DOMAIN,
@ -46,48 +55,13 @@ public class RdapTestHelper {
} }
static ImmutableList<ImmutableMap<String, Object>> createNotices(String linkBase) { static ImmutableList<ImmutableMap<String, Object>> createNotices(String linkBase) {
return createNotices(linkBase, ContactNoticeType.NONE, null); return createNotices(linkBase, null);
} }
static ImmutableList<ImmutableMap<String, Object>> createNotices( static ImmutableList<ImmutableMap<String, Object>> createNotices(
String linkBase, ContactNoticeType contactNoticeType, @Nullable Object otherNotices) { String linkBase, @Nullable Object otherNotices) {
ImmutableList.Builder<ImmutableMap<String, Object>> noticesBuilder = ImmutableList.Builder<ImmutableMap<String, Object>> noticesBuilder =
getBuilderWithOthersAdded(otherNotices); getBuilderWithOthersAdded(otherNotices);
switch (contactNoticeType) {
case DOMAIN:
noticesBuilder.add(
ImmutableMap.of(
"title", "Contacts Hidden",
"description",
ImmutableList.of("Domain contacts are visible only to the owning registrar."),
"type", "object truncated due to unexplainable reasons"));
break;
case CONTACT:
noticesBuilder.add(
ImmutableMap.of(
"title",
"Data Policy",
"description",
ImmutableList.of(
"Some of the data in this object has been removed.",
"Contact personal data is visible only to the owning registrar."),
"type",
"object truncated due to authorization",
"links",
ImmutableList.of(
ImmutableMap.of(
"value",
"https://github.com/google/nomulus/blob/master/docs/rdap.md#authentication",
"rel",
"alternate",
"href",
"https://github.com/google/nomulus/blob/master/docs/rdap.md#authentication",
"type",
"text/html"))));
break;
default:
break;
}
noticesBuilder.add( noticesBuilder.add(
ImmutableMap.of( ImmutableMap.of(
"title", "RDAP Terms of Service", "title", "RDAP Terms of Service",
@ -142,28 +116,13 @@ public class RdapTestHelper {
} }
static void addDomainBoilerplateNotices(ImmutableMap.Builder<String, Object> builder) { static void addDomainBoilerplateNotices(ImmutableMap.Builder<String, Object> builder) {
addDomainBoilerplateNotices(builder, false, null); addDomainBoilerplateNotices(builder, null);
} }
static void addDomainBoilerplateNotices( static void addDomainBoilerplateNotices(
ImmutableMap.Builder<String, Object> builder, @Nullable Object otherNotices) { ImmutableMap.Builder<String, Object> builder, @Nullable Object otherNotices) {
addDomainBoilerplateNotices(builder, false, otherNotices);
}
static void addDomainBoilerplateNotices(
ImmutableMap.Builder<String, Object> builder,
boolean addNoContactRemark,
@Nullable Object otherNotices) {
ImmutableList.Builder<ImmutableMap<String, Object>> noticesBuilder = ImmutableList.Builder<ImmutableMap<String, Object>> noticesBuilder =
getBuilderWithOthersAdded(otherNotices); getBuilderWithOthersAdded(otherNotices);
if (addNoContactRemark) {
noticesBuilder.add(
ImmutableMap.of(
"title", "Contacts Hidden",
"description",
ImmutableList.of("Domain contacts are visible only to the owning registrar."),
"type", "object truncated due to unexplainable reasons"));
}
noticesBuilder.add( noticesBuilder.add(
ImmutableMap.of( ImmutableMap.of(
"description", "description",
@ -214,32 +173,8 @@ public class RdapTestHelper {
static RdapJsonFormatter getTestRdapJsonFormatter() { static RdapJsonFormatter getTestRdapJsonFormatter() {
RdapJsonFormatter rdapJsonFormatter = new RdapJsonFormatter(); RdapJsonFormatter rdapJsonFormatter = new RdapJsonFormatter();
rdapJsonFormatter.rdapTosPath = "/tos"; rdapJsonFormatter.fullServletPath = "https://example.tld/rdap/";
rdapJsonFormatter.rdapHelpMap = rdapJsonFormatter.rdapTos =
ImmutableMap.of(
"/",
RdapNoticeDescriptor.builder()
.setTitle("RDAP Help")
.setDescription(
ImmutableList.of(
"domain/XXXX",
"nameserver/XXXX",
"entity/XXXX",
"domains?name=XXXX",
"domains?nsLdhName=XXXX",
"domains?nsIp=XXXX",
"nameservers?name=XXXX",
"nameservers?ip=XXXX",
"entities?fn=XXXX",
"entities?handle=XXXX",
"help/XXXX"))
.setLinkValueSuffix("help/")
.setLinkHrefUrlString("https://github.com/google/nomulus/blob/master/docs/rdap.md")
.build(),
"/tos",
RdapNoticeDescriptor.builder()
.setTitle("RDAP Terms of Service")
.setDescription(
ImmutableList.of( ImmutableList.of(
"By querying our Domain Database, you are agreeing to comply with these" "By querying our Domain Database, you are agreeing to comply with these"
+ " terms so please read them carefully.", + " terms so please read them carefully.",
@ -261,10 +196,8 @@ public class RdapTestHelper {
+ " purposes of detecting and preventing misuse.", + " purposes of detecting and preventing misuse.",
"We reserve the right to restrict or deny your access to the database if we" "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.", + " suspect that you have failed to comply with these terms.",
"We reserve the right to modify this agreement at any time.")) "We reserve the right to modify this agreement at any time.");
.setLinkValueSuffix("help/tos") rdapJsonFormatter.rdapTosStaticUrl = "https://www.registry.tld/about/rdap/tos.html";
.setLinkHrefUrlString("https://www.registry.tld/about/rdap/tos.html")
.build());
return rdapJsonFormatter; return rdapJsonFormatter;
} }

View file

@ -1,4 +1,7 @@
{ {
"rdapConformance": [
"icann_rdap_response_profile_0"
],
"objectClassName" : "entity", "objectClassName" : "entity",
"handle" : "%NAME%", "handle" : "%NAME%",
"status" : ["active", "associated"], "status" : ["active", "associated"],

View file

@ -1,4 +1,7 @@
{ {
"rdapConformance": [
"icann_rdap_response_profile_0"
],
"objectClassName" : "entity", "objectClassName" : "entity",
"handle" : "%NAME%", "handle" : "%NAME%",
"status" : ["active", "associated", "removed"], "status" : ["active", "associated", "removed"],
@ -21,5 +24,23 @@
"eventAction": "last update of RDAP database", "eventAction": "last update of RDAP database",
"eventDate": "2000-01-01T00:00:00.000Z" "eventDate": "2000-01-01T00:00:00.000Z"
} }
],
"remarks": [
{
"title": "Redacted for Privacy",
"type": "object redacted due to authorization",
"links": [
{
"type":"text\/html",
"value": "https:\/\/github.com\/google\/nomulus\/blob\/master\/docs\/rdap.md#authentication",
"href": "https:\/\/github.com\/google\/nomulus\/blob\/master\/docs\/rdap.md#authentication",
"rel": "alternate"
}
],
"description": [
"Some of the data in this object has been removed.",
"Contact personal data is visible only to the owning registrar."
]
}
] ]
} }

View file

@ -1,4 +1,7 @@
{ {
"rdapConformance": [
"icann_rdap_response_profile_0"
],
"objectClassName" : "entity", "objectClassName" : "entity",
"handle" : "%NAME%", "handle" : "%NAME%",
"status" : ["%STATUS%"], "status" : ["%STATUS%"],

View file

@ -1,4 +1,7 @@
{ {
"rdapConformance": [
"icann_rdap_response_profile_0"
],
"objectClassName" : "entity", "objectClassName" : "entity",
"handle" : "%NAME%", "handle" : "%NAME%",
"status" : ["inactive"], "status" : ["inactive"],

View file

@ -1,4 +1,7 @@
{ {
"rdapConformance": [
"icann_rdap_response_profile_0"
],
"objectClassName" : "entity", "objectClassName" : "entity",
"handle" : "%NAME%", "handle" : "%NAME%",
"status" : ["active", "removed"], "status" : ["active", "removed"],
@ -24,12 +27,12 @@
], ],
"remarks": [ "remarks": [
{ {
"title": "Data Policy", "title": "Redacted for Privacy",
"description": [ "description": [
"Some of the data in this object has been removed.", "Some of the data in this object has been removed.",
"Contact personal data is visible only to the owning registrar." "Contact personal data is visible only to the owning registrar."
], ],
"type": "object truncated due to authorization", "type": "object redacted due to authorization",
"links" : "links" :
[ [
{ {

View file

@ -1,4 +1,7 @@
{ {
"rdapConformance": [
"icann_rdap_response_profile_0"
],
"status": [ "status": [
"client delete prohibited", "client delete prohibited",
"client renew prohibited", "client renew prohibited",

View file

@ -1,4 +1,7 @@
{ {
"rdapConformance": [
"icann_rdap_response_profile_0"
],
"status": [ "status": [
"client delete prohibited", "client delete prohibited",
"client renew prohibited", "client renew prohibited",

View file

@ -1,4 +1,7 @@
{ {
"rdapConformance": [
"icann_rdap_response_profile_0"
],
"status": [ "status": [
"client delete prohibited", "client delete prohibited",
"client renew prohibited", "client renew prohibited",

View file

@ -1,262 +0,0 @@
{
"objectClassName": "domain",
"handle": "%HANDLE%",
"ldhName": "%NAME%",
"status": [
"client delete prohibited",
"client renew prohibited",
"client transfer prohibited",
"server update prohibited"
],
"links": [
{
"href": "https://example.tld/rdap/domain/%NAME%",
"type": "application/rdap+json",
"rel": "self",
"value": "https://example.tld/rdap/domain/%NAME%"
}
],
"events": [
{
"eventAction": "registration",
"eventActor": "foo",
"eventDate": "2000-01-01T00:00:00.000Z"
},
{
"eventAction": "expiration",
"eventDate": "2110-10-08T00:44:59.000Z"
},
{
"eventAction": "last update of RDAP database",
"eventDate": "2000-01-01T00:00:00.000Z"
}
],
"nameservers": [
{
"status": [
"active",
"associated"
],
"handle": "%NAMESERVER1ROID%",
"links": [
{
"href": "https://example.tld/rdap/nameserver/%NAMESERVER1NAME%",
"type": "application/rdap+json",
"rel": "self",
"value": "https://example.tld/rdap/nameserver/%NAMESERVER1NAME%"
}
],
"ldhName": "%NAMESERVER1NAME%",
"ipAddresses": {
"v4": [
"%NAMESERVER1ADDRESS%"
]
},
"events": [
{
"eventAction": "registration",
"eventActor": "foo",
"eventDate": "1999-01-01T00:00:00.000Z"
},
{
"eventAction": "last update of RDAP database",
"eventDate": "2000-01-01T00:00:00.000Z"
}
],
"objectClassName": "nameserver",
"entities": [
{
"objectClassName": "entity",
"status": [ "active" ],
"handle": "1",
"roles": [ "registrar" ],
"links": [
{
"href": "https://example.tld/rdap/entity/1",
"type": "application/rdap+json",
"rel": "self",
"value": "https://example.tld/rdap/entity/1"
}
],
"vcardArray" : [
"vcard",
[
["version", {}, "text", "4.0"],
["fn", {}, "text", "The Registrar"],
[
"adr",
{},
"text",
[
"",
"",
"123 Example Boulevard",
"Williamsburg",
"NY",
"11211",
"United States"
]
],
["tel", {"type":["voice"]}, "uri", "tel:+1.2223334444"],
["email", {}, "text", "the.registrar@example.com"]
]
],
"publicIds" : [
{
"type": "IANA Registrar ID",
"identifier":"1"
}
],
"remarks": [
{
"title": "Incomplete Data",
"description": [
"Summary data only. For complete data, send a specific query for the object."
],
"type": "object truncated due to unexplainable reasons"
}
]
}
]
},
{
"status": [
"active",
"associated"
],
"handle": "%NAMESERVER2ROID%",
"links": [
{
"href": "https://example.tld/rdap/nameserver/%NAMESERVER2NAME%",
"type": "application/rdap+json",
"rel": "self",
"value": "https://example.tld/rdap/nameserver/%NAMESERVER2NAME%"
}
],
"ldhName": "%NAMESERVER2NAME%",
"ipAddresses": {
"v6": [
"%NAMESERVER2ADDRESS%"
]
},
"events": [
{
"eventAction": "registration",
"eventActor": "foo",
"eventDate": "1998-01-01T00:00:00.000Z"
},
{
"eventAction": "last update of RDAP database",
"eventDate": "2000-01-01T00:00:00.000Z"
}
],
"objectClassName": "nameserver",
"entities": [
{
"objectClassName": "entity",
"status": [ "active" ],
"handle": "1",
"roles": [ "registrar" ],
"links": [
{
"href": "https://example.tld/rdap/entity/1",
"type": "application/rdap+json",
"rel": "self",
"value": "https://example.tld/rdap/entity/1"
}
],
"vcardArray" : [
"vcard",
[
["version", {}, "text", "4.0"],
["fn", {}, "text", "The Registrar"],
[
"adr",
{},
"text",
[
"",
"",
"123 Example Boulevard",
"Williamsburg",
"NY",
"11211",
"United States"
]
],
["tel", {"type":["voice"]}, "uri", "tel:+1.2223334444"],
["email", {}, "text", "the.registrar@example.com"]
]
],
"publicIds" : [
{
"type": "IANA Registrar ID",
"identifier":"1"
}
],
"remarks": [
{
"title": "Incomplete Data",
"description": [
"Summary data only. For complete data, send a specific query for the object."
],
"type": "object truncated due to unexplainable reasons"
}
]
}
]
}
],
"entities": [
{
"objectClassName" : "entity",
"handle" : "1",
"status" : ["active"],
"roles" : ["registrar"],
"links" :
[
{
"value" : "https://example.tld/rdap/entity/1",
"rel" : "self",
"href" : "https://example.tld/rdap/entity/1",
"type" : "application/rdap+json"
}
],
"publicIds" :
[
{
"type" : "IANA Registrar ID",
"identifier" : "1"
}
],
"vcardArray" :
[
"vcard",
[
["version", {}, "text", "4.0"],
["fn", {}, "text", "%REGISTRARNAME%"],
["adr", {}, "text", [
"",
"",
"123 Example Boulevard <script>",
"Williamsburg <script>",
"NY",
"11211",
"United States"]],
["tel", {"type" : ["voice"]}, "uri", "tel:+1.2125551212"],
["tel", {"type" : ["fax"]}, "uri", "tel:+1.2125551213"],
["email", {}, "text", "contact-us@example.com"]
]
],
"remarks": [
{
"title": "Incomplete Data",
"description": [
"Summary data only. For complete data, send a specific query for the object."
],
"type": "object truncated due to unexplainable reasons"
}
]
}
]
}

View file

@ -2,6 +2,9 @@
"objectClassName": "domain", "objectClassName": "domain",
"handle": "%HANDLE%", "handle": "%HANDLE%",
"ldhName": "%NAME%", "ldhName": "%NAME%",
"rdapConformance": [
"icann_rdap_response_profile_0"
],
"status": [ "status": [
"client delete prohibited", "client delete prohibited",
"client renew prohibited", "client renew prohibited",

View file

@ -1,4 +1,7 @@
{ {
"rdapConformance": [
"icann_rdap_response_profile_0"
],
"status": [ "status": [
"client delete prohibited", "client delete prohibited",
"client renew prohibited", "client renew prohibited",

View file

@ -1,4 +1,7 @@
{ {
"rdapConformance": [
"icann_rdap_response_profile_0"
],
"objectClassName": "domain", "objectClassName": "domain",
"handle": "%HANDLE%", "handle": "%HANDLE%",
"ldhName": "%PUNYCODENAME%", "ldhName": "%PUNYCODENAME%",

View file

@ -0,0 +1,36 @@
{
"lang": "en",
"errorCode": %CODE%,
"title": "%TITLE%",
"description": [
"%DESCRIPTION%"
],
"rdapConformance": [
"icann_rdap_response_profile_0"
],
"notices": [
{
"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 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": [
{
"href": "https://www.registry.tld/about/rdap/tos.html",
"rel": "alternate",
"type": "text/html",
"value": "https://example.tld/rdap/help/tos"
}
],
"title": "RDAP Terms of Service"
}
]
}

View file

@ -1,11 +0,0 @@
{
"lang": "en",
"errorCode": 400,
"title": "Bad Request",
"description": [
"%NAME%"
],
"rdapConformance": [
"icann_rdap_response_profile_0"
]
}

View file

@ -1,11 +0,0 @@
{
"lang": "en",
"errorCode": 404,
"title": "Not Found",
"description": [
"%NAME%"
],
"rdapConformance": [
"icann_rdap_response_profile_0"
]
}

View file

@ -1,11 +0,0 @@
{
"lang": "en",
"errorCode": 422,
"title": "Unprocessable Entity",
"description": [
"%NAME%"
],
"rdapConformance": [
"icann_rdap_response_profile_0"
]
}

View file

@ -1,11 +0,0 @@
{
"lang": "en",
"errorCode": 501,
"title": "Not Implemented",
"description": [
"%NAME%"
],
"rdapConformance": [
"icann_rdap_response_profile_0"
]
}

View file

@ -1,14 +0,0 @@
{
"key" : "value",
"rdapConformance" : [ "icann_rdap_response_profile_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 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.example.com/help/tos",
"rel" : "alternate",
"href" : "https://www.registry.tld/about/rdap/tos.html",
"type" : "text/html"
} ]
} ]
}

View file

@ -21,7 +21,7 @@
"links" : "links" :
[ [
{ {
"value" : "https://example.tld/rdap/help/%NAME%", "value" : "https://example.tld/rdap/help",
"rel" : "alternate", "rel" : "alternate",
"type" : "text/html", "type" : "text/html",
"href" : "https://github.com/google/nomulus/blob/master/docs/rdap.md" "href" : "https://github.com/google/nomulus/blob/master/docs/rdap.md"

View file

@ -1,4 +1,7 @@
{ {
"rdapConformance": [
"icann_rdap_response_profile_0"
],
"status": [ "status": [
"%STATUS%" "%STATUS%"
], ],

View file

@ -1,4 +1,7 @@
{ {
"rdapConformance": [
"icann_rdap_response_profile_0"
],
"status": [ "status": [
"active" "active"
], ],

View file

@ -1,4 +1,7 @@
{ {
"rdapConformance": [
"icann_rdap_response_profile_0"
],
"status": [ "status": [
"active", "active",
"associated" "associated"

View file

@ -1,4 +1,7 @@
{ {
"rdapConformance": [
"icann_rdap_response_profile_0"
],
"status": [ "status": [
"active" "active"
], ],

View file

@ -1,4 +1,7 @@
{ {
"rdapConformance": [
"icann_rdap_response_profile_0"
],
"objectClassName" : "entity", "objectClassName" : "entity",
"handle" : "%NAME%", "handle" : "%NAME%",
"status" : ["%STATUS%"], "status" : ["%STATUS%"],

View file

@ -1,4 +1,7 @@
{ {
"rdapConformance": [
"icann_rdap_response_profile_0"
],
"objectClassName" : "entity", "objectClassName" : "entity",
"handle" : "%NAME%", "handle" : "%NAME%",
"status" : ["%STATUS%"], "status" : ["%STATUS%"],

View file

@ -337,7 +337,7 @@
{ {
"type" : "application/rdap+json", "type" : "application/rdap+json",
"rel" : "next", "rel" : "next",
"href" : "https://example.tld/rdap/nameservers?%NAME%" "href" : "https://example.tld/rdap/nameservers?%QUERY%"
} }
] ]
}, },

View file

@ -1 +1 @@
{"key":"value","rdapConformance":["icann_rdap_response_profile_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 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.example.com\/help\/tos","rel":"alternate","href":"https:\/\/www.registry.tld\/about\/rdap\/tos.html","type":"text\/html"}]}]} {"key":"value","rdapConformance":["icann_rdap_response_profile_0"],"notices":[{"title":"RDAP Terms of Service","links":[{"href":"https:\/\/www.registry.tld\/about\/rdap\/tos.html","rel":"alternate","type":"text\/html","value":"http:\/\/myserver.example.com\/help\/tos"}],"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 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."]}]}

View file

@ -6,9 +6,9 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/entity/4-ROID", "value" : "https://example.tld/rdap/entity/4-ROID",
"rel" : "self", "rel" : "self",
"href" : "http://myserver.example.com/entity/4-ROID", "href" : "https://example.tld/rdap/entity/4-ROID",
"type" : "application/rdap+json" "type" : "application/rdap+json"
} }
], ],

View file

@ -13,9 +13,9 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/domain/cat.xn--q9jyb4c", "value" : "https://example.tld/rdap/domain/cat.xn--q9jyb4c",
"rel" : "self", "rel" : "self",
"href" : "http://myserver.example.com/domain/cat.xn--q9jyb4c", "href" : "https://example.tld/rdap/domain/cat.xn--q9jyb4c",
"type" : "application/rdap+json" "type" : "application/rdap+json"
} }
], ],
@ -54,9 +54,9 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/nameserver/ns1.cat.xn--q9jyb4c", "value" : "https://example.tld/rdap/nameserver/ns1.cat.xn--q9jyb4c",
"rel" : "self", "rel" : "self",
"href" : "http://myserver.example.com/nameserver/ns1.cat.xn--q9jyb4c", "href" : "https://example.tld/rdap/nameserver/ns1.cat.xn--q9jyb4c",
"type" : "application/rdap+json" "type" : "application/rdap+json"
} }
], ],
@ -84,9 +84,9 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/entity/1", "value" : "https://example.tld/rdap/entity/1",
"rel" : "self", "rel" : "self",
"href" : "http://myserver.example.com/entity/1", "href" : "https://example.tld/rdap/entity/1",
"type" : "application/rdap+json" "type" : "application/rdap+json"
} }
], ],
@ -137,9 +137,9 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/nameserver/ns2.cat.xn--q9jyb4c", "value" : "https://example.tld/rdap/nameserver/ns2.cat.xn--q9jyb4c",
"rel" : "self", "rel" : "self",
"href" : "http://myserver.example.com/nameserver/ns2.cat.xn--q9jyb4c", "href" : "https://example.tld/rdap/nameserver/ns2.cat.xn--q9jyb4c",
"type" : "application/rdap+json" "type" : "application/rdap+json"
} }
], ],
@ -167,9 +167,9 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/entity/1", "value" : "https://example.tld/rdap/entity/1",
"rel" : "self", "rel" : "self",
"href" : "http://myserver.example.com/entity/1", "href" : "https://example.tld/rdap/entity/1",
"type" : "application/rdap+json" "type" : "application/rdap+json"
} }
], ],
@ -222,9 +222,9 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/entity/4-ROID", "value" : "https://example.tld/rdap/entity/4-ROID",
"rel" : "self", "rel" : "self",
"href" : "http://myserver.example.com/entity/4-ROID", "href" : "https://example.tld/rdap/entity/4-ROID",
"type" : "application/rdap+json" "type" : "application/rdap+json"
} }
], ],
@ -271,9 +271,9 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/entity/6-ROID", "value" : "https://example.tld/rdap/entity/6-ROID",
"rel" : "self", "rel" : "self",
"href" : "http://myserver.example.com/entity/6-ROID", "href" : "https://example.tld/rdap/entity/6-ROID",
"type" : "application/rdap+json" "type" : "application/rdap+json"
} }
], ],
@ -320,9 +320,9 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/entity/2-ROID", "value" : "https://example.tld/rdap/entity/2-ROID",
"rel" : "self", "rel" : "self",
"href" : "http://myserver.example.com/entity/2-ROID", "href" : "https://example.tld/rdap/entity/2-ROID",
"type" : "application/rdap+json" "type" : "application/rdap+json"
} }
], ],
@ -345,7 +345,7 @@
["fn", {}, "text", "(◕‿◕)"], ["fn", {}, "text", "(◕‿◕)"],
["org", {}, "text", "GOOGLE INCORPORATED <script>"], ["org", {}, "text", "GOOGLE INCORPORATED <script>"],
["tel", {"type" : ["voice"]}, "uri", "tel:+1.2126660420"], ["tel", {"type" : ["voice"]}, "uri", "tel:+1.2126660420"],
["tel", {"type" : ["fax"]}, "uri", "tel:+1.2126660420"] ["tel", {"type" : ["fax"]}, "uri", "tel:+1.2126660420"],
["email", {}, "text", "lol@cat.みんな"] ["email", {}, "text", "lol@cat.みんな"]
] ]
] ]
@ -358,9 +358,9 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/entity/1", "value" : "https://example.tld/rdap/entity/1",
"rel" : "self", "rel" : "self",
"href" : "http://myserver.example.com/entity/1", "href" : "https://example.tld/rdap/entity/1",
"type" : "application/rdap+json" "type" : "application/rdap+json"
} }
], ],

View file

@ -13,9 +13,9 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/domain/cat.xn--q9jyb4c", "value" : "https://example.tld/rdap/domain/cat.xn--q9jyb4c",
"rel" : "self", "rel" : "self",
"href" : "http://myserver.example.com/domain/cat.xn--q9jyb4c", "href" : "https://example.tld/rdap/domain/cat.xn--q9jyb4c",
"type" : "application/rdap+json" "type" : "application/rdap+json"
} }
], ],
@ -54,9 +54,9 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/nameserver/ns1.cat.xn--q9jyb4c", "value" : "https://example.tld/rdap/nameserver/ns1.cat.xn--q9jyb4c",
"rel" : "self", "rel" : "self",
"href" : "http://myserver.example.com/nameserver/ns1.cat.xn--q9jyb4c", "href" : "https://example.tld/rdap/nameserver/ns1.cat.xn--q9jyb4c",
"type" : "application/rdap+json" "type" : "application/rdap+json"
} }
], ],
@ -84,9 +84,9 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/entity/1", "value" : "https://example.tld/rdap/entity/1",
"rel" : "self", "rel" : "self",
"href" : "http://myserver.example.com/entity/1", "href" : "https://example.tld/rdap/entity/1",
"type" : "application/rdap+json" "type" : "application/rdap+json"
} }
], ],
@ -137,9 +137,9 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/nameserver/ns2.cat.xn--q9jyb4c", "value" : "https://example.tld/rdap/nameserver/ns2.cat.xn--q9jyb4c",
"rel" : "self", "rel" : "self",
"href" : "http://myserver.example.com/nameserver/ns2.cat.xn--q9jyb4c", "href" : "https://example.tld/rdap/nameserver/ns2.cat.xn--q9jyb4c",
"type" : "application/rdap+json" "type" : "application/rdap+json"
} }
], ],
@ -167,9 +167,9 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/entity/1", "value" : "https://example.tld/rdap/entity/1",
"rel" : "self", "rel" : "self",
"href" : "http://myserver.example.com/entity/1", "href" : "https://example.tld/rdap/entity/1",
"type" : "application/rdap+json" "type" : "application/rdap+json"
} }
], ],
@ -221,9 +221,9 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/entity/1", "value" : "https://example.tld/rdap/entity/1",
"rel" : "self", "rel" : "self",
"href" : "http://myserver.example.com/entity/1", "href" : "https://example.tld/rdap/entity/1",
"type" : "application/rdap+json" "type" : "application/rdap+json"
} }
], ],

View file

@ -14,9 +14,9 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/domain/fish.xn--q9jyb4c", "value" : "https://example.tld/rdap/domain/fish.xn--q9jyb4c",
"rel" : "self", "rel" : "self",
"href" : "http://myserver.example.com/domain/fish.xn--q9jyb4c", "href" : "https://example.tld/rdap/domain/fish.xn--q9jyb4c",
"type" : "application/rdap+json" "type" : "application/rdap+json"
} }
], ],
@ -45,9 +45,9 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/entity/4-ROID", "value" : "https://example.tld/rdap/entity/4-ROID",
"rel" : "self", "rel" : "self",
"href" : "http://myserver.example.com/entity/4-ROID", "href" : "https://example.tld/rdap/entity/4-ROID",
"type" : "application/rdap+json" "type" : "application/rdap+json"
} }
], ],
@ -94,9 +94,9 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/entity/6-ROID", "value" : "https://example.tld/rdap/entity/6-ROID",
"rel" : "self", "rel" : "self",
"href" : "http://myserver.example.com/entity/6-ROID", "href" : "https://example.tld/rdap/entity/6-ROID",
"type" : "application/rdap+json" "type" : "application/rdap+json"
} }
], ],
@ -143,9 +143,9 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/entity/2-ROID", "value" : "https://example.tld/rdap/entity/2-ROID",
"rel" : "self", "rel" : "self",
"href" : "http://myserver.example.com/entity/2-ROID", "href" : "https://example.tld/rdap/entity/2-ROID",
"type" : "application/rdap+json" "type" : "application/rdap+json"
} }
], ],
@ -181,9 +181,9 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/entity/1", "value" : "https://example.tld/rdap/entity/1",
"rel" : "self", "rel" : "self",
"href" : "http://myserver.example.com/entity/1", "href" : "https://example.tld/rdap/entity/1",
"type" : "application/rdap+json" "type" : "application/rdap+json"
} }
], ],

View file

@ -13,9 +13,9 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/domain/cat.xn--q9jyb4c", "value" : "https://example.tld/rdap/domain/cat.xn--q9jyb4c",
"rel" : "self", "rel" : "self",
"href" : "http://myserver.example.com/domain/cat.xn--q9jyb4c", "href" : "https://example.tld/rdap/domain/cat.xn--q9jyb4c",
"type" : "application/rdap+json" "type" : "application/rdap+json"
} }
], ],

View file

@ -1,9 +1,5 @@
{ {
"rdapConformance" : "lang" : "en",
[
"icann_rdap_response_profile_0"
],
"lang" : "en"
"errorCode" : 400, "errorCode" : 400,
"title" : "Invalid Domain Name", "title" : "Invalid Domain Name",
"description" : "description" :

View file

@ -7,9 +7,9 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/nameserver/ns3.cat.xn--q9jyb4c", "value" : "https://example.tld/rdap/nameserver/ns3.cat.xn--q9jyb4c",
"rel" : "self", "rel" : "self",
"href" : "http://myserver.example.com/nameserver/ns3.cat.xn--q9jyb4c", "href" : "https://example.tld/rdap/nameserver/ns3.cat.xn--q9jyb4c",
"type" : "application/rdap+json" "type" : "application/rdap+json"
} }
], ],
@ -38,9 +38,9 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/entity/1", "value" : "https://example.tld/rdap/entity/1",
"rel" : "self", "rel" : "self",
"href" : "http://myserver.example.com/entity/1", "href" : "https://example.tld/rdap/entity/1",
"type" : "application/rdap+json" "type" : "application/rdap+json"
} }
], ],

View file

@ -7,9 +7,9 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/nameserver/ns3.cat.xn--q9jyb4c", "value" : "https://example.tld/rdap/nameserver/ns3.cat.xn--q9jyb4c",
"rel" : "self", "rel" : "self",
"href" : "http://myserver.example.com/nameserver/ns3.cat.xn--q9jyb4c", "href" : "https://example.tld/rdap/nameserver/ns3.cat.xn--q9jyb4c",
"type" : "application/rdap+json" "type" : "application/rdap+json"
} }
], ],
@ -36,9 +36,9 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/entity/1", "value" : "https://example.tld/rdap/entity/1",
"rel" : "self", "rel" : "self",
"href" : "http://myserver.example.com/entity/1", "href" : "https://example.tld/rdap/entity/1",
"type" : "application/rdap+json" "type" : "application/rdap+json"
} }
], ],

View file

@ -7,9 +7,9 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/nameserver/ns1.cat.xn--q9jyb4c", "value" : "https://example.tld/rdap/nameserver/ns1.cat.xn--q9jyb4c",
"rel" : "self", "rel" : "self",
"href" : "http://myserver.example.com/nameserver/ns1.cat.xn--q9jyb4c", "href" : "https://example.tld/rdap/nameserver/ns1.cat.xn--q9jyb4c",
"type" : "application/rdap+json" "type" : "application/rdap+json"
} }
], ],
@ -37,9 +37,9 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/entity/1", "value" : "https://example.tld/rdap/entity/1",
"rel" : "self", "rel" : "self",
"href" : "http://myserver.example.com/entity/1", "href" : "https://example.tld/rdap/entity/1",
"type" : "application/rdap+json" "type" : "application/rdap+json"
} }
], ],

View file

@ -7,9 +7,9 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/nameserver/ns2.cat.xn--q9jyb4c", "value" : "https://example.tld/rdap/nameserver/ns2.cat.xn--q9jyb4c",
"rel" : "self", "rel" : "self",
"href" : "http://myserver.example.com/nameserver/ns2.cat.xn--q9jyb4c", "href" : "https://example.tld/rdap/nameserver/ns2.cat.xn--q9jyb4c",
"type" : "application/rdap+json" "type" : "application/rdap+json"
} }
], ],
@ -37,9 +37,9 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/entity/1", "value" : "https://example.tld/rdap/entity/1",
"rel" : "self", "rel" : "self",
"href" : "http://myserver.example.com/entity/1", "href" : "https://example.tld/rdap/entity/1",
"type" : "application/rdap+json" "type" : "application/rdap+json"
} }
], ],

View file

@ -7,9 +7,9 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/nameserver/ns4.cat.xn--q9jyb4c", "value" : "https://example.tld/rdap/nameserver/ns4.cat.xn--q9jyb4c",
"rel" : "self", "rel" : "self",
"href" : "http://myserver.example.com/nameserver/ns4.cat.xn--q9jyb4c", "href" : "https://example.tld/rdap/nameserver/ns4.cat.xn--q9jyb4c",
"type" : "application/rdap+json" "type" : "application/rdap+json"
} }
], ],
@ -33,9 +33,9 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/entity/1", "value" : "https://example.tld/rdap/entity/1",
"rel" : "self", "rel" : "self",
"href" : "http://myserver.example.com/entity/1", "href" : "https://example.tld/rdap/entity/1",
"type" : "application/rdap+json" "type" : "application/rdap+json"
} }
], ],

View file

@ -7,9 +7,9 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/nameserver/ns5.cat.xn--q9jyb4c", "value" : "https://example.tld/rdap/nameserver/ns5.cat.xn--q9jyb4c",
"rel" : "self", "rel" : "self",
"href" : "http://myserver.example.com/nameserver/ns5.cat.xn--q9jyb4c", "href" : "https://example.tld/rdap/nameserver/ns5.cat.xn--q9jyb4c",
"type" : "application/rdap+json" "type" : "application/rdap+json"
} }
], ],
@ -33,9 +33,9 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/entity/1", "value" : "https://example.tld/rdap/entity/1",
"rel" : "self", "rel" : "self",
"href" : "http://myserver.example.com/entity/1", "href" : "https://example.tld/rdap/entity/1",
"type" : "application/rdap+json" "type" : "application/rdap+json"
} }
], ],

View file

@ -7,9 +7,9 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/nameserver/ns1.dog.xn--q9jyb4c", "value" : "https://example.tld/rdap/nameserver/ns1.dog.xn--q9jyb4c",
"rel" : "self", "rel" : "self",
"href" : "http://myserver.example.com/nameserver/ns1.dog.xn--q9jyb4c", "href" : "https://example.tld/rdap/nameserver/ns1.dog.xn--q9jyb4c",
"type" : "application/rdap+json" "type" : "application/rdap+json"
} }
], ],
@ -33,9 +33,9 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/entity/1", "value" : "https://example.tld/rdap/entity/1",
"rel" : "self", "rel" : "self",
"href" : "http://myserver.example.com/entity/1", "href" : "https://example.tld/rdap/entity/1",
"type" : "application/rdap+json" "type" : "application/rdap+json"
} }
], ],

View file

@ -9,10 +9,10 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/help/index", "value" : "https://example.tld/rdap/help/index",
"rel" : "alternate", "rel" : "alternate",
"type" : "text/html", "type" : "text/html",
"href" : "http://myserver.example.com/about/rdap/index.html" "href" : "https://example.tld/rdap/about/rdap/index.html"
} }
] ]
} }

View file

@ -9,10 +9,10 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/help/index", "value" : "https://example.tld/rdap/help/index",
"rel" : "self", "rel" : "self",
"type" : "application/rdap+json", "type" : "application/rdap+json",
"href" : "http://myserver.example.com/help/index", "href" : "https://example.tld/rdap/help/index"
} }
] ]
} }

View file

@ -6,9 +6,9 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/entity/2-ROID", "value" : "https://example.tld/rdap/entity/2-ROID",
"rel" : "self", "rel" : "self",
"href" : "http://myserver.example.com/entity/2-ROID", "href" : "https://example.tld/rdap/entity/2-ROID",
"type" : "application/rdap+json" "type" : "application/rdap+json"
} }
], ],

View file

@ -6,9 +6,9 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/entity/2-ROID", "value" : "https://example.tld/rdap/entity/2-ROID",
"rel" : "self", "rel" : "self",
"href" : "http://myserver.example.com/entity/2-ROID", "href" : "https://example.tld/rdap/entity/2-ROID",
"type" : "application/rdap+json" "type" : "application/rdap+json"
} }
], ],
@ -25,12 +25,12 @@
], ],
"remarks": [ "remarks": [
{ {
"title": "Data Policy", "title": "Redacted for Privacy",
"description": [ "description": [
"Some of the data in this object has been removed.", "Some of the data in this object has been removed.",
"Contact personal data is visible only to the owning registrar." "Contact personal data is visible only to the owning registrar."
], ],
"type": "object truncated due to authorization", "type": "object redacted due to authorization",
"links" : "links" :
[ [
{ {

View file

@ -6,9 +6,9 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/entity/2-ROID", "value" : "https://example.tld/rdap/entity/2-ROID",
"rel" : "self", "rel" : "self",
"href" : "http://myserver.example.com/entity/2-ROID", "href" : "https://example.tld/rdap/entity/2-ROID",
"type" : "application/rdap+json" "type" : "application/rdap+json"
} }
], ],

View file

@ -6,9 +6,9 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/entity/2-ROID", "value" : "https://example.tld/rdap/entity/2-ROID",
"rel" : "self", "rel" : "self",
"href" : "http://myserver.example.com/entity/2-ROID", "href" : "https://example.tld/rdap/entity/2-ROID",
"type" : "application/rdap+json" "type" : "application/rdap+json"
} }
], ],
@ -33,12 +33,12 @@
"type": "object truncated due to unexplainable reasons" "type": "object truncated due to unexplainable reasons"
}, },
{ {
"title": "Data Policy", "title": "Redacted for Privacy",
"description": [ "description": [
"Some of the data in this object has been removed.", "Some of the data in this object has been removed.",
"Contact personal data is visible only to the owning registrar." "Contact personal data is visible only to the owning registrar."
], ],
"type": "object truncated due to authorization", "type": "object redacted due to authorization",
"links" : "links" :
[ [
{ {

View file

@ -6,9 +6,9 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/entity/1", "value" : "https://example.tld/rdap/entity/1",
"rel" : "self", "rel" : "self",
"href" : "http://myserver.example.com/entity/1", "href" : "https://example.tld/rdap/entity/1",
"type" : "application/rdap+json" "type" : "application/rdap+json"
} }
], ],
@ -68,7 +68,7 @@
["tel", {"type" : ["voice"]}, "uri", "tel:+1.2125551215"], ["tel", {"type" : ["voice"]}, "uri", "tel:+1.2125551215"],
["email", {}, "text", "janedoe@example.com"] ["email", {}, "text", "janedoe@example.com"]
] ]
], ]
}, },
{ {
"objectClassName" : "entity", "objectClassName" : "entity",
@ -83,7 +83,7 @@
["tel", {"type" : ["fax"]}, "uri", "tel:+1.2125551213"], ["tel", {"type" : ["fax"]}, "uri", "tel:+1.2125551213"],
["email", {}, "text", "johndoe@example.com"] ["email", {}, "text", "johndoe@example.com"]
] ]
], ]
}, },
{ {
"objectClassName" : "entity", "objectClassName" : "entity",
@ -99,7 +99,7 @@
["tel", {"type" : ["fax"]}, "uri", "tel:+1.2125551218"], ["tel", {"type" : ["fax"]}, "uri", "tel:+1.2125551218"],
["email", {}, "text", "playdoe@example.com"] ["email", {}, "text", "playdoe@example.com"]
] ]
], ]
} }
] ]
} }

View file

@ -6,9 +6,9 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/entity/1", "value" : "https://example.tld/rdap/entity/1",
"rel" : "self", "rel" : "self",
"href" : "http://myserver.example.com/entity/1", "href" : "https://example.tld/rdap/entity/1",
"type" : "application/rdap+json" "type" : "application/rdap+json"
} }
], ],

View file

@ -5,9 +5,9 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/entity/6-ROID", "value" : "https://example.tld/rdap/entity/6-ROID",
"rel" : "self", "rel" : "self",
"href" : "http://myserver.example.com/entity/6-ROID", "href" : "https://example.tld/rdap/entity/6-ROID",
"type" : "application/rdap+json" "type" : "application/rdap+json"
} }
], ],

View file

@ -6,9 +6,9 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/entity/6-ROID", "value" : "https://example.tld/rdap/entity/6-ROID",
"rel" : "self", "rel" : "self",
"href" : "http://myserver.example.com/entity/6-ROID", "href" : "https://example.tld/rdap/entity/6-ROID",
"type" : "application/rdap+json" "type" : "application/rdap+json"
} }
], ],

View file

@ -24,7 +24,7 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/help/tos", "value" : "https://example.tld/rdap/help/tos",
"rel" : "alternate", "rel" : "alternate",
"href" : "https://www.registry.tld/about/rdap/tos.html", "href" : "https://www.registry.tld/about/rdap/tos.html",
"type" : "text/html" "type" : "text/html"

View file

@ -24,7 +24,7 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/help/tos", "value" : "https://example.tld/rdap/help/tos",
"rel" : "alternate", "rel" : "alternate",
"href" : "https://www.registry.tld/about/rdap/tos.html", "href" : "https://www.registry.tld/about/rdap/tos.html",
"type" : "text/html" "type" : "text/html"

View file

@ -5,9 +5,9 @@
"links" : "links" :
[ [
{ {
"value" : "http://myserver.example.com/entity/8-ROID", "value" : "https://example.tld/rdap/entity/8-ROID",
"rel" : "self", "rel" : "self",
"href" : "http://myserver.example.com/entity/8-ROID", "href" : "https://example.tld/rdap/entity/8-ROID",
"type" : "application/rdap+json" "type" : "application/rdap+json"
} }
], ],