Link the Registrar's RDAP server from RDAP domain replies

To do this we add a field of "rdapServers" in the Registrar object. Currently, we can only set this field manually, but a subsequent CL will add a cron-job to read these values from the ICANN servers.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=252438618
This commit is contained in:
guyben 2019-06-10 10:54:19 -07:00 committed by jianglai
parent 7c64992c73
commit 4110cae814
14 changed files with 186 additions and 30 deletions

View file

@ -242,6 +242,9 @@ public class Registrar extends ImmutableObject implements Buildable, Jsonifiable
/** Host name of WHOIS server. */ /** Host name of WHOIS server. */
String whoisServer; String whoisServer;
/** Base URLs for the registrar's RDAP servers. */
Set<String> rdapBaseUrls;
/** /**
* Whether registration of premium names should be blocked over EPP. If this is set to true, then * Whether registration of premium names should be blocked over EPP. If this is set to true, then
* the only way to register premium names is with the superuser flag. * the only way to register premium names is with the superuser flag.
@ -526,6 +529,10 @@ public class Registrar extends ImmutableObject implements Buildable, Jsonifiable
return firstNonNull(whoisServer, getDefaultRegistrarWhoisServer()); return firstNonNull(whoisServer, getDefaultRegistrarWhoisServer());
} }
public ImmutableSet<String> getRdapBaseUrls() {
return nullToEmptyImmutableSortedCopy(rdapBaseUrls);
}
public boolean getBlockPremiumNames() { public boolean getBlockPremiumNames() {
return blockPremiumNames; return blockPremiumNames;
} }
@ -603,6 +610,7 @@ public class Registrar extends ImmutableObject implements Buildable, Jsonifiable
.put("faxNumber", faxNumber) .put("faxNumber", faxNumber)
.put("emailAddress", emailAddress) .put("emailAddress", emailAddress)
.put("whoisServer", getWhoisServer()) .put("whoisServer", getWhoisServer())
.putListOfStrings("rdapBaseUrls", getRdapBaseUrls())
.put("blockPremiumNames", blockPremiumNames) .put("blockPremiumNames", blockPremiumNames)
.put("url", url) .put("url", url)
.put("icannReferralEmail", getIcannReferralEmail()) .put("icannReferralEmail", getIcannReferralEmail())
@ -837,6 +845,11 @@ public class Registrar extends ImmutableObject implements Buildable, Jsonifiable
return this; return this;
} }
public Builder setRdapBaseUrls(Set<String> rdapBaseUrls) {
getInstance().rdapBaseUrls = ImmutableSet.copyOf(rdapBaseUrls);
return this;
}
public Builder setBlockPremiumNames(boolean blockPremiumNames) { public Builder setBlockPremiumNames(boolean blockPremiumNames) {
getInstance().blockPremiumNames = blockPremiumNames; getInstance().blockPremiumNames = blockPremiumNames;
return this; return this;
@ -908,9 +921,24 @@ public class Registrar extends ImmutableObject implements Buildable, Jsonifiable
ofy().load().type(Registrar.class).parent(getCrossTldKey()).id(clientId).now()); ofy().load().type(Registrar.class).parent(getCrossTldKey()).id(clientId).now());
} }
/** Loads and returns a registrar entity by its client id using an in-memory cache. */ /**
* Loads and returns a registrar entity by its client id using an in-memory cache.
*
* <p>Returns empty if the registrar isn't found.
*/
public static Optional<Registrar> loadByClientIdCached(String clientId) { public static Optional<Registrar> loadByClientIdCached(String clientId) {
checkArgument(!Strings.isNullOrEmpty(clientId), "clientId must be specified"); checkArgument(!Strings.isNullOrEmpty(clientId), "clientId must be specified");
return Optional.ofNullable(CACHE_BY_CLIENT_ID.get().get(clientId)); return Optional.ofNullable(CACHE_BY_CLIENT_ID.get().get(clientId));
} }
/**
* Loads and returns a registrar entity by its client id using an in-memory cache.
*
* <p>Throws if the registrar isn't found.
*/
public static Registrar loadRequiredRegistrarCached(String clientId) {
Optional<Registrar> registrar = loadByClientIdCached(clientId);
checkArgument(registrar.isPresent(), "couldn't find registrar '%s'", clientId);
return registrar.get();
}
} }

View file

@ -66,7 +66,6 @@ import google.registry.rdap.RdapObjectClasses.SecureDns;
import google.registry.rdap.RdapObjectClasses.Vcard; import google.registry.rdap.RdapObjectClasses.Vcard;
import google.registry.rdap.RdapObjectClasses.VcardArray; import google.registry.rdap.RdapObjectClasses.VcardArray;
import google.registry.request.FullServletPath; import google.registry.request.FullServletPath;
import google.registry.request.HttpException.InternalServerErrorException;
import google.registry.util.Clock; import google.registry.util.Clock;
import java.net.Inet4Address; import java.net.Inet4Address;
import java.net.Inet6Address; import java.net.Inet6Address;
@ -301,11 +300,25 @@ public class RdapJsonFormatter {
// The domain object in the RDAP response MUST contain an entity with the Registrar role. // The domain object in the RDAP response MUST contain an entity with the Registrar role.
// //
// See {@link createRdapRegistrarEntity} for details of section 2.4 conformance // See {@link createRdapRegistrarEntity} for details of section 2.4 conformance
Registrar registrar =
Registrar.loadRequiredRegistrarCached(domainBase.getCurrentSponsorClientId());
builder.entitiesBuilder().add(createRdapRegistrarEntity(registrar, OutputDataType.INTERNAL));
// RDAP Technical Implementation Guide 3.2: must have link to the registrar's RDAP URL for this
// domain, with rel=related.
for (String registrarRdapBase : registrar.getRdapBaseUrls()) {
String href =
makeServerRelativeUrl(
registrarRdapBase, "domain", domainBase.getFullyQualifiedDomainName());
builder builder
.entitiesBuilder() .linksBuilder()
.add( .add(
createRdapRegistrarEntity( Link.builder()
domainBase.getCurrentSponsorClientId(), OutputDataType.INTERNAL)); .setHref(href)
.setValue(href)
.setRel("related")
.setType("application/rdap+json")
.build());
}
// RDAP Response Profile 2.6.1: must have at least one status member // RDAP Response Profile 2.6.1: must have at least one status member
// makeStatusValueList should in theory always contain one of either "active" or "inactive". // makeStatusValueList should in theory always contain one of either "active" or "inactive".
ImmutableSet<RdapStatus> status = ImmutableSet<RdapStatus> status =
@ -443,11 +456,9 @@ public class RdapJsonFormatter {
// RDAP Response Profile 4.3 - Registrar member is optional, so we only set it for FULL // RDAP Response Profile 4.3 - Registrar member is optional, so we only set it for FULL
if (outputDataType == OutputDataType.FULL) { if (outputDataType == OutputDataType.FULL) {
builder Registrar registrar =
.entitiesBuilder() Registrar.loadRequiredRegistrarCached(hostResource.getPersistedCurrentSponsorClientId());
.add( builder.entitiesBuilder().add(createRdapRegistrarEntity(registrar, OutputDataType.INTERNAL));
createRdapRegistrarEntity(
hostResource.getPersistedCurrentSponsorClientId(), OutputDataType.INTERNAL));
} }
if (outputDataType != OutputDataType.INTERNAL) { if (outputDataType != OutputDataType.INTERNAL) {
// Rdap Response Profile 4.4, must have "last update of RDAP database" response. But this is // Rdap Response Profile 4.4, must have "last update of RDAP database" response. But this is
@ -747,21 +758,6 @@ public class RdapJsonFormatter {
return builder.build(); return builder.build();
} }
/**
* Creates a JSON object for the desired registrar to an existing list of JSON objects.
*
* @param clientId the registrar client ID
* @param outputDataType whether to generate FULL, SUMMARY, or INTERNAL data.
*/
RdapRegistrarEntity createRdapRegistrarEntity(String clientId, OutputDataType outputDataType) {
Optional<Registrar> registrar = Registrar.loadByClientIdCached(clientId);
if (!registrar.isPresent()) {
throw new InternalServerErrorException(
String.format("Couldn't find registrar '%s'", clientId));
}
return createRdapRegistrarEntity(registrar.get(), outputDataType);
}
/** /**
* Creates a JSON object for a {@link RegistrarContact}. * Creates a JSON object for a {@link RegistrarContact}.
* *
@ -1059,11 +1055,18 @@ public class RdapJsonFormatter {
* Create a link relative to the RDAP server endpoint. * Create a link relative to the RDAP server endpoint.
*/ */
String makeRdapServletRelativeUrl(String part, String... moreParts) { String makeRdapServletRelativeUrl(String part, String... moreParts) {
String relativePath = Paths.get(part, moreParts).toString(); return makeServerRelativeUrl(fullServletPath, part, moreParts);
if (fullServletPath.endsWith("/")) {
return fullServletPath + relativePath;
} }
return fullServletPath + "/" + relativePath;
/**
* Create a link relative to some base server
*/
static String makeServerRelativeUrl(String baseServer, String part, String... moreParts) {
String relativePath = Paths.get(part, moreParts).toString();
if (baseServer.endsWith("/")) {
return baseServer + relativePath;
}
return baseServer + "/" + relativePath;
} }
/** /**

View file

@ -254,6 +254,11 @@ abstract class CreateOrUpdateRegistrarCommand extends MutatingCommand {
description = "Hostname of registrar WHOIS server. (Default: whois.nic.google)") description = "Hostname of registrar WHOIS server. (Default: whois.nic.google)")
String whoisServer; String whoisServer;
@Parameter(
names = "--rdap_servers",
description = "Comma-delimited list of RDAP servers. An empty argument clears the list")
List<String> rdapServers = new ArrayList<>();
/** Returns the existing registrar (for update) or null (for creates). */ /** Returns the existing registrar (for update) or null (for creates). */
@Nullable @Nullable
abstract Registrar getOldRegistrar(String clientId); abstract Registrar getOldRegistrar(String clientId);
@ -389,6 +394,15 @@ abstract class CreateOrUpdateRegistrarCommand extends MutatingCommand {
Optional.ofNullable(icannReferralEmail).ifPresent(builder::setIcannReferralEmail); Optional.ofNullable(icannReferralEmail).ifPresent(builder::setIcannReferralEmail);
Optional.ofNullable(whoisServer).ifPresent(builder::setWhoisServer); Optional.ofNullable(whoisServer).ifPresent(builder::setWhoisServer);
if (!rdapServers.isEmpty()) {
// If we only have empty strings, then remove all the RDAP servers
// This is to differentiate between "I didn't set the rdapServers because I don't want to
// change them" and "I set the RDAP servers to an empty string because I want no RDAP
// servers".
builder.setRdapBaseUrls(
rdapServers.stream().filter(server -> !server.isEmpty()).collect(toImmutableSet()));
}
// If the registrarName is being set, verify that it is either null or it normalizes uniquely. // If the registrarName is being set, verify that it is either null or it normalizes uniquely.
String oldRegistrarName = (oldRegistrar == null) ? null : oldRegistrar.getRegistrarName(); String oldRegistrarName = (oldRegistrar == null) ? null : oldRegistrar.getRegistrarName();
if (registrarName != null && !registrarName.equals(oldRegistrarName)) { if (registrarName != null && !registrarName.equals(oldRegistrarName)) {

View file

@ -436,6 +436,7 @@ class google.registry.model.registrar.Registrar {
java.util.List<google.registry.util.CidrAddressBlock> ipAddressWhitelist; java.util.List<google.registry.util.CidrAddressBlock> ipAddressWhitelist;
java.util.Map<org.joda.money.CurrencyUnit, google.registry.model.registrar.Registrar$BillingAccountEntry> billingAccountMap; java.util.Map<org.joda.money.CurrencyUnit, google.registry.model.registrar.Registrar$BillingAccountEntry> billingAccountMap;
java.util.Set<java.lang.String> allowedTlds; java.util.Set<java.lang.String> allowedTlds;
java.util.Set<java.lang.String> rdapBaseUrls;
org.joda.time.DateTime lastCertificateUpdateTime; org.joda.time.DateTime lastCertificateUpdateTime;
} }
class google.registry.model.registrar.Registrar$BillingAccountEntry { class google.registry.model.registrar.Registrar$BillingAccountEntry {

View file

@ -19,6 +19,18 @@
"type": "application/rdap+json", "type": "application/rdap+json",
"rel": "self", "rel": "self",
"value": "https://example.tld/rdap/domain/%DOMAIN_PUNYCODE_NAME_1%" "value": "https://example.tld/rdap/domain/%DOMAIN_PUNYCODE_NAME_1%"
},
{
"href": "https://rdap.example.com/withSlash/domain/%DOMAIN_PUNYCODE_NAME_1%",
"type": "application/rdap+json",
"rel": "related",
"value": "https://rdap.example.com/withSlash/domain/%DOMAIN_PUNYCODE_NAME_1%"
},
{
"href": "https://rdap.example.com/withoutSlash/domain/%DOMAIN_PUNYCODE_NAME_1%",
"type": "application/rdap+json",
"rel": "related",
"value": "https://rdap.example.com/withoutSlash/domain/%DOMAIN_PUNYCODE_NAME_1%"
} }
], ],
"events": [ "events": [

View file

@ -19,6 +19,18 @@
"type": "application/rdap+json", "type": "application/rdap+json",
"rel": "self", "rel": "self",
"value": "https://example.tld/rdap/domain/%DOMAIN_PUNYCODE_NAME_1%" "value": "https://example.tld/rdap/domain/%DOMAIN_PUNYCODE_NAME_1%"
},
{
"href": "https://rdap.example.com/withSlash/domain/%DOMAIN_PUNYCODE_NAME_1%",
"type": "application/rdap+json",
"rel": "related",
"value": "https://rdap.example.com/withSlash/domain/%DOMAIN_PUNYCODE_NAME_1%"
},
{
"href": "https://rdap.example.com/withoutSlash/domain/%DOMAIN_PUNYCODE_NAME_1%",
"type": "application/rdap+json",
"rel": "related",
"value": "https://rdap.example.com/withoutSlash/domain/%DOMAIN_PUNYCODE_NAME_1%"
} }
], ],
"events": [ "events": [

View file

@ -20,6 +20,18 @@
"type": "application/rdap+json", "type": "application/rdap+json",
"rel": "self", "rel": "self",
"value": "https://example.tld/rdap/domain/%DOMAIN_PUNYCODE_NAME_1%" "value": "https://example.tld/rdap/domain/%DOMAIN_PUNYCODE_NAME_1%"
},
{
"href": "https://rdap.example.com/withSlash/domain/%DOMAIN_PUNYCODE_NAME_1%",
"type": "application/rdap+json",
"rel": "related",
"value": "https://rdap.example.com/withSlash/domain/%DOMAIN_PUNYCODE_NAME_1%"
},
{
"href": "https://rdap.example.com/withoutSlash/domain/%DOMAIN_PUNYCODE_NAME_1%",
"type": "application/rdap+json",
"rel": "related",
"value": "https://rdap.example.com/withoutSlash/domain/%DOMAIN_PUNYCODE_NAME_1%"
} }
], ],
"events": [ "events": [

View file

@ -19,6 +19,18 @@
"type": "application/rdap+json", "type": "application/rdap+json",
"rel": "self", "rel": "self",
"value": "https://example.tld/rdap/domain/%DOMAIN_PUNYCODE_NAME_1%" "value": "https://example.tld/rdap/domain/%DOMAIN_PUNYCODE_NAME_1%"
},
{
"href": "https://rdap.example.com/withSlash/domain/%DOMAIN_PUNYCODE_NAME_1%",
"type": "application/rdap+json",
"rel": "related",
"value": "https://rdap.example.com/withSlash/domain/%DOMAIN_PUNYCODE_NAME_1%"
},
{
"href": "https://rdap.example.com/withoutSlash/domain/%DOMAIN_PUNYCODE_NAME_1%",
"type": "application/rdap+json",
"rel": "related",
"value": "https://rdap.example.com/withoutSlash/domain/%DOMAIN_PUNYCODE_NAME_1%"
} }
], ],
"events": [ "events": [

View file

@ -20,6 +20,18 @@
"type": "application/rdap+json", "type": "application/rdap+json",
"rel": "self", "rel": "self",
"value": "https://example.tld/rdap/domain/%DOMAIN_PUNYCODE_NAME_1%" "value": "https://example.tld/rdap/domain/%DOMAIN_PUNYCODE_NAME_1%"
},
{
"href": "https://rdap.example.com/withSlash/domain/%DOMAIN_PUNYCODE_NAME_1%",
"type": "application/rdap+json",
"rel": "related",
"value": "https://rdap.example.com/withSlash/domain/%DOMAIN_PUNYCODE_NAME_1%"
},
{
"href": "https://rdap.example.com/withoutSlash/domain/%DOMAIN_PUNYCODE_NAME_1%",
"type": "application/rdap+json",
"rel": "related",
"value": "https://rdap.example.com/withoutSlash/domain/%DOMAIN_PUNYCODE_NAME_1%"
} }
], ],
"events": [ "events": [

View file

@ -20,6 +20,18 @@
"type": "application/rdap+json", "type": "application/rdap+json",
"rel": "self", "rel": "self",
"value": "https://example.tld/rdap/domain/%DOMAIN_PUNYCODE_NAME_1%" "value": "https://example.tld/rdap/domain/%DOMAIN_PUNYCODE_NAME_1%"
},
{
"href": "https://rdap.example.com/withSlash/domain/%DOMAIN_PUNYCODE_NAME_1%",
"type": "application/rdap+json",
"rel": "related",
"value": "https://rdap.example.com/withSlash/domain/%DOMAIN_PUNYCODE_NAME_1%"
},
{
"href": "https://rdap.example.com/withoutSlash/domain/%DOMAIN_PUNYCODE_NAME_1%",
"type": "application/rdap+json",
"rel": "related",
"value": "https://rdap.example.com/withoutSlash/domain/%DOMAIN_PUNYCODE_NAME_1%"
} }
], ],
"events": [ "events": [

View file

@ -17,6 +17,18 @@
"rel" : "self", "rel" : "self",
"href" : "https://example.tld/rdap/domain/cat.xn--q9jyb4c", "href" : "https://example.tld/rdap/domain/cat.xn--q9jyb4c",
"type" : "application/rdap+json" "type" : "application/rdap+json"
},
{
"value" : "https://rdap.example.com/withSlash/domain/cat.xn--q9jyb4c",
"rel" : "related",
"href" : "https://rdap.example.com/withSlash/domain/cat.xn--q9jyb4c",
"type" : "application/rdap+json"
},
{
"value" : "https://rdap.example.com/withoutSlash/domain/cat.xn--q9jyb4c",
"rel" : "related",
"href" : "https://rdap.example.com/withoutSlash/domain/cat.xn--q9jyb4c",
"type" : "application/rdap+json"
} }
], ],
"events": [ "events": [

View file

@ -17,6 +17,18 @@
"rel": "self", "rel": "self",
"href": "https://example.tld/rdap/domain/cat.xn--q9jyb4c", "href": "https://example.tld/rdap/domain/cat.xn--q9jyb4c",
"type": "application/rdap+json" "type": "application/rdap+json"
},
{
"href": "https://rdap.example.com/withSlash/domain/cat.xn--q9jyb4c",
"type": "application/rdap+json",
"rel": "related",
"value": "https://rdap.example.com/withSlash/domain/cat.xn--q9jyb4c"
},
{
"href": "https://rdap.example.com/withoutSlash/domain/cat.xn--q9jyb4c",
"type": "application/rdap+json",
"rel": "related",
"value": "https://rdap.example.com/withoutSlash/domain/cat.xn--q9jyb4c"
} }
], ],
"events": [ "events": [

View file

@ -18,6 +18,18 @@
"rel": "self", "rel": "self",
"href": "https://example.tld/rdap/domain/fish.xn--q9jyb4c", "href": "https://example.tld/rdap/domain/fish.xn--q9jyb4c",
"type": "application/rdap+json" "type": "application/rdap+json"
},
{
"href": "https://rdap.example.com/withSlash/domain/fish.xn--q9jyb4c",
"type": "application/rdap+json",
"rel": "related",
"value": "https://rdap.example.com/withSlash/domain/fish.xn--q9jyb4c"
},
{
"href": "https://rdap.example.com/withoutSlash/domain/fish.xn--q9jyb4c",
"type": "application/rdap+json",
"rel": "related",
"value": "https://rdap.example.com/withoutSlash/domain/fish.xn--q9jyb4c"
} }
], ],
"events": [ "events": [

View file

@ -80,6 +80,8 @@ public final class FullFieldsTestEntityHelper {
.setFaxNumber("+1.2125551213") .setFaxNumber("+1.2125551213")
.setEmailAddress("contact-us@example.com") .setEmailAddress("contact-us@example.com")
.setWhoisServer("whois.example.com") .setWhoisServer("whois.example.com")
.setRdapBaseUrls(ImmutableSet.of(
"https://rdap.example.com/withSlash/", "https://rdap.example.com/withoutSlash"))
.setUrl("http://my.fake.url") .setUrl("http://my.fake.url")
.build(); .build();
} }