google-nomulus/java/google/registry/rdap/RdapObjectClasses.java
guyben ca1d525e28 Conform to RDAP Technical Implementation Guide
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=251864499
2019-06-07 11:43:09 -04:00

582 lines
19 KiB
Java

// 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.model.domain.secdns.DelegationSignerData;
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.
*/
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, 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 ImmutableList<Event> events();
/**
* Required event for all response objects, but not for internal objects.
*
* <p>Meaning it's required in, e.g., an RdapNameserver object that is a response to a
* Nameserver query, but not to an RdapNameserver that's part of an RdapDomain response to a
* Domain query.
*
* <p>RDAP Response Profile 2.3.1.3, 3.3, 4.4
*/
@JsonableElement("events[]")
abstract Optional<Event> lastUpdateOfRdapDatabaseEvent();
/**
* WHOIS server displayed in RDAP query responses.
*
* <p>As per Gustavo Lozano of ICANN, this should be omitted, but the ICANN operational profile
* doesn't actually say that, so it's good to have the ability to reinstate this field if
* necessary.
*/
@JsonableElement
abstract Optional<Port43WhoisServer> port43();
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();
abstract B setLastUpdateOfRdapDatabaseEvent(Event event);
}
}
/**
* The Entity Object Class defined in 5.1 of RFC7483.
*
* <p>Entities are used both for Contacts and for Registrars. We will create different subobjects
* for each one for type safety.
*
* <p>We're missing the "autnums" and "networks" fields
*/
@RestrictJsonNames({"entities[]", "entitySearchResults[]"})
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();
private abstract static class Builder<B extends Builder<B>> extends RdapObjectBase.Builder<B> {
abstract B setVcardArray(VcardArray vcardArray);
abstract ImmutableSet.Builder<Role> rolesBuilder();
abstract ImmutableList.Builder<EventWithoutActor> asEventActorBuilder();
}
}
/**
* Registrar version of the Entity Object Class defined in 5.1 of RFC7483.
*
* <p>Entities are used both for Contacts and for Registrars. We will create different subobjects
* for each one for type safety.
*/
@AutoValue
abstract static class RdapRegistrarEntity extends RdapEntity {
static Builder builder() {
return new AutoValue_RdapObjectClasses_RdapRegistrarEntity.Builder();
}
@AutoValue.Builder
abstract static class Builder extends RdapEntity.Builder<Builder> {
abstract RdapRegistrarEntity build();
}
}
/**
* Contact version of the Entity Object Class defined in 5.1 of RFC7483.
*
* <p>Entities are used both for Contacts and for Registrars. We will create different subobjects
* for each one for type safety.
*/
@AutoValue
abstract static class RdapContactEntity extends RdapEntity {
static Builder builder() {
return new AutoValue_RdapObjectClasses_RdapContactEntity.Builder();
}
@AutoValue.Builder
abstract static class Builder extends RdapEntity.Builder<Builder> {
abstract RdapContactEntity 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>Note the ldhName field is only required for non-IDN names or IDN names when the query was an
* A-label. It is optional for IDN names when the query was a U-label. Because we don't want to
* remember the query when building the results, we always show it.
*
* <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();
}
}
/** Object defined in RFC7483 section 5.3, only used for RdapDomain. */
@RestrictJsonNames("secureDNS")
@AutoValue
abstract static class SecureDns extends AbstractJsonableObject {
@RestrictJsonNames("dsData[]")
@AutoValue
abstract static class DsData extends AbstractJsonableObject {
@JsonableElement
abstract int keyTag();
@JsonableElement
abstract int algorithm();
@JsonableElement
abstract String digest();
@JsonableElement
abstract int digestType();
static DsData create(DelegationSignerData dsData) {
return new AutoValue_RdapObjectClasses_SecureDns_DsData(
dsData.getKeyTag(),
dsData.getAlgorithm(),
dsData.getDigestAsString(),
dsData.getDigestType());
}
}
/** true if the zone has been signed, false otherwise. */
@JsonableElement
abstract boolean zoneSigned();
/** true if there are DS records in the parent, false otherwise. */
@JsonableElement
boolean delegationSigned() {
return !dsData().isEmpty();
}
/**
* an integer representing the signature lifetime in seconds to be used when creating the RRSIG
* DS record in the parent zone [RFC5910].
*
* <p>Note that although it isn't given as optional in RFC7483, in RFC5910 it's mentioned as
* optional. Also, our code doesn't support it at all - so it's set to always be empty.
*/
@JsonableElement
Optional<Integer> maxSigLife() {
return Optional.empty();
}
@JsonableElement
abstract ImmutableList<DsData> dsData();
static Builder builder() {
return new AutoValue_RdapObjectClasses_SecureDns.Builder();
}
abstract Builder toBuilder();
@AutoValue.Builder
abstract static class Builder {
abstract Builder setZoneSigned(boolean zoneSigned);
abstract ImmutableList.Builder<DsData> dsDataBuilder();
Builder addDsData(DelegationSignerData dsData) {
dsDataBuilder().add(DsData.create(dsData));
return this;
}
abstract SecureDns 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();
@JsonableElement("secureDNS")
abstract Optional<SecureDns> secureDns();
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 Builder setSecureDns(SecureDns secureDns);
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() {}
}