// 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> parameters(); abstract String valueType(); abstract JsonElement value(); static Vcard create( String property, ImmutableMap> parameters, String valueType, JsonElement value) { return new AutoValue_RdapObjectClasses_Vcard(property, parameters, valueType, value); } static Vcard create( String property, ImmutableMap> 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 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 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. * *

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 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. * *

Not part of the spec, but seems convenient. */ private abstract static class RdapObjectBase extends ReplyPayloadBase { @JsonableElement final ObjectClassName objectClassName; @JsonableElement abstract Optional handle(); @JsonableElement abstract ImmutableList publicIds(); @JsonableElement abstract ImmutableList entities(); @JsonableElement abstract ImmutableList status(); @JsonableElement abstract ImmutableList remarks(); @JsonableElement abstract ImmutableList links(); @JsonableElement abstract ImmutableList events(); /** * WHOIS server displayed in RDAP query responses. * *

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 port43(); RdapObjectBase(BoilerplateType boilerplateType, ObjectClassName objectClassName) { super(boilerplateType); this.objectClassName = objectClassName; } abstract static class Builder> { abstract B setHandle(String handle); abstract ImmutableList.Builder publicIdsBuilder(); abstract ImmutableList.Builder entitiesBuilder(); abstract ImmutableList.Builder statusBuilder(); abstract ImmutableList.Builder remarksBuilder(); abstract ImmutableList.Builder linksBuilder(); abstract B setPort43(Port43WhoisServer port43); abstract ImmutableList.Builder eventsBuilder(); } } /** * The Entity Object Class defined in 5.1 of RFC7483. * *

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(); @JsonableElement abstract ImmutableSet roles(); @JsonableElement abstract ImmutableList asEventActor(); static Builder builder() { return new AutoValue_RdapObjectClasses_RdapEntity.Builder(); } @AutoValue.Builder abstract static class Builder extends RdapObjectBase.Builder { abstract Builder setVcardArray(VcardArray vcardArray); abstract ImmutableSet.Builder rolesBuilder(); abstract ImmutableList.Builder asEventActorBuilder(); abstract RdapEntity build(); } } /** * A base object shared by Nameserver, and Domain. * *

Takes care of the name and unicode field. * *

See RDAP Response Profile 15feb19 sections 2.1 and 4.1. * *

Not part of the spec, but seems convenient. */ private abstract static class RdapNamedObjectBase extends RdapObjectBase { @JsonableElement abstract String ldhName(); @JsonableElement final Optional 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> extends RdapObjectBase.Builder { 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() { if (ipv6().isEmpty() && ipv4().isEmpty()) { return Optional.empty(); } return Optional.of(new IpAddresses()); } abstract ImmutableList ipv6(); abstract ImmutableList ipv4(); class IpAddresses extends AbstractJsonableObject { @JsonableElement ImmutableList v6() { return Ordering.natural().immutableSortedCopy(ipv6()); } @JsonableElement ImmutableList 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 { abstract ImmutableList.Builder ipv6Builder(); abstract ImmutableList.Builder 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 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 { abstract ImmutableList.Builder 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 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. * *

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 helpNotice(); HelpResponse() { super(BoilerplateType.OTHER); } static HelpResponse create(Optional helpNotice) { return new AutoValue_RdapObjectClasses_HelpResponse(helpNotice); } } private RdapObjectClasses() {} }