mirror of
https://github.com/google/nomulus
synced 2026-07-08 17:16:54 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c6868b771b | |||
| f34aec8b56 | |||
| b27b077638 | |||
| 0e8cd75a58 | |||
| 2a1748ba9c | |||
| f4889191a4 |
@@ -55,7 +55,7 @@ import java.util.Optional;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* Base RDAP (new WHOIS) action for all requests.
|
||||
* Base RDAP action for all requests.
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc9082">RFC 9082: Registration Data Access Protocol
|
||||
* (RDAP) Query Format</a>
|
||||
@@ -138,7 +138,7 @@ public abstract class RdapActionBase implements Runnable {
|
||||
// RFC7480 4.2 - servers receiving an RDAP request return an entity with a Content-Type header
|
||||
// containing the RDAP-specific JSON media type.
|
||||
response.setContentType(RESPONSE_MEDIA_TYPE);
|
||||
// RDAP Technical Implementation Guide 1.13 - when responding to RDAP valid requests, we MUST
|
||||
// RDAP Technical Implementation Guide 1.14 - when responding to RDAP valid requests, we MUST
|
||||
// include the Access-Control-Allow-Origin, which MUST be "*" unless otherwise specified.
|
||||
response.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN, "*");
|
||||
try {
|
||||
|
||||
@@ -26,7 +26,7 @@ import google.registry.request.auth.Auth;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
/**
|
||||
* RDAP (new WHOIS) action for RDAP autonomous system number requests.
|
||||
* RDAP action for RDAP autonomous system number requests.
|
||||
*
|
||||
* <p>This feature is not implemented because it's only necessary for <i>address</i> registries like
|
||||
* ARIN, not domain registries.
|
||||
|
||||
@@ -41,14 +41,13 @@ final class RdapDataStructures {
|
||||
// Conformance to RFC 9083
|
||||
jsonArray.add("rdap_level_0");
|
||||
|
||||
// Conformance to the RDAP Response Profile V2.1
|
||||
// Conformance to the RDAP Response Profile V2.2 (February 2024)
|
||||
// (see section 1.2)
|
||||
jsonArray.add("icann_rdap_response_profile_1");
|
||||
|
||||
// Conformance to the RDAP Technical Implementation Guide V2.2 (February 2024)
|
||||
// (see section 1.3)
|
||||
jsonArray.add("icann_rdap_response_profile_0");
|
||||
|
||||
// Conformance to the RDAP Technical Implementation Guide V2.1
|
||||
// (see section 1.14)
|
||||
jsonArray.add("icann_rdap_technical_implementation_guide_0");
|
||||
|
||||
jsonArray.add("icann_rdap_technical_implementation_guide_1");
|
||||
return jsonArray;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ import google.registry.request.auth.Auth;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Optional;
|
||||
|
||||
/** RDAP (new WHOIS) action for domain requests. */
|
||||
/** RDAP action for domain requests. */
|
||||
@Action(
|
||||
service = GaeService.PUBAPI,
|
||||
path = "/rdap/domain/",
|
||||
|
||||
@@ -51,6 +51,7 @@ import google.registry.request.Parameter;
|
||||
import google.registry.request.auth.Auth;
|
||||
import google.registry.util.NonFinalForTesting;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.persistence.Query;
|
||||
import jakarta.persistence.criteria.CriteriaBuilder;
|
||||
import java.net.InetAddress;
|
||||
import java.util.Comparator;
|
||||
@@ -60,17 +61,15 @@ import java.util.stream.Stream;
|
||||
import org.hibernate.Hibernate;
|
||||
|
||||
/**
|
||||
* RDAP (new WHOIS) action for domain search requests.
|
||||
* RDAP action for domain search requests.
|
||||
*
|
||||
* <p>All commands and responses conform to the RDAP spec as defined in RFCs 7480 through 7485.
|
||||
* <p>All commands and responses conform to the RDAP spec as defined in STD 95 and its RFCs.
|
||||
*
|
||||
* @see <a href="http://tools.ietf.org/html/rfc9082">RFC 9082: Registration Data Access Protocol
|
||||
* (RDAP) Query Format</a>
|
||||
* @see <a href="http://tools.ietf.org/html/rfc9083">RFC 9083: JSON Responses for the Registration
|
||||
* Data Access Protocol (RDAP)</a>
|
||||
*/
|
||||
// TODO: This isn't required by the RDAP Technical Implementation Guide, and hence should be
|
||||
// deleted, at least until it's actually required.
|
||||
@Action(
|
||||
service = GaeService.PUBAPI,
|
||||
path = "/rdap/domains",
|
||||
@@ -442,7 +441,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
|
||||
replicaTm()
|
||||
.transact(
|
||||
() -> {
|
||||
jakarta.persistence.Query query =
|
||||
Query query =
|
||||
replicaTm()
|
||||
.getEntityManager()
|
||||
.createNativeQuery(queryBuilder.toString())
|
||||
|
||||
@@ -37,14 +37,12 @@ import jakarta.inject.Inject;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* RDAP (new WHOIS) action for entity (contact and registrar) requests. the ICANN operational
|
||||
* profile dictates that the "handle" for registrars is to be the IANA registrar ID:
|
||||
* RDAP action for entity (contact and registrar) requests. the ICANN operational profile dictates
|
||||
* that the "handle" for registrars is to be the IANA registrar ID:
|
||||
*
|
||||
* <p>2.8.3. Registries MUST support lookup for entities with the registrar role within other
|
||||
* objects using the handle (as described in 3.1.5 of RFC 9082). The handle of the entity with the
|
||||
* registrar role MUST be equal to IANA Registrar ID. The entity with the registrar role in the RDAP
|
||||
* response MUST contain a publicIDs member to identify the IANA Registrar ID from the IANA’s
|
||||
* Registrar ID registry. The type value of the publicID object MUST be equal to IANA Registrar ID.
|
||||
* <p>2.4.1.Registry RDAP servers MUST support Registrar object lookup using an entity path request
|
||||
* for entities with the registrar role using the handle (as described in 3.1.5 of RFC9082) where
|
||||
* the handle of the entity with the registrar role is be [sic] equal to the IANA Registrar ID.
|
||||
*/
|
||||
@Action(
|
||||
service = GaeService.PUBAPI,
|
||||
@@ -104,7 +102,7 @@ public class RdapEntityAction extends RdapActionBase {
|
||||
// query, it MUST reply with 404 response code.
|
||||
//
|
||||
// Note we don't do RFC7480 5.3 - returning a different code if we wish to say "this info
|
||||
// exists but we don't want to show it to you", because we DON'T wish to say that.
|
||||
// exists, but we don't want to show it to you", because we DON'T wish to say that.
|
||||
throw new NotFoundException(pathSearchString + " not found");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,9 +49,9 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* RDAP (new WHOIS) action for entity (contact and registrar) search requests.
|
||||
* RDAP action for entity (contact and registrar) search requests.
|
||||
*
|
||||
* <p>All commands and responses conform to the RDAP spec as defined in RFCs 7480 through 7485.
|
||||
* <p>All commands and responses conform to the RDAP spec as defined in STD 95 and its RFCs.
|
||||
*
|
||||
* <p>The RDAP specification lumps contacts and registrars together and calls them "entities", which
|
||||
* is confusing for us, because "entity" means something else in SQL. But here, when we use the
|
||||
@@ -76,8 +76,6 @@ import java.util.Optional;
|
||||
* @see <a href="http://tools.ietf.org/html/rfc9083">RFC 9083: JSON Responses for the Registration
|
||||
* Data Access Protocol (RDAP)</a>
|
||||
*/
|
||||
// TODO: This isn't required by the RDAP Technical Implementation Guide, and hence should be
|
||||
// deleted, at least until it's actually required.
|
||||
@Action(
|
||||
service = GaeService.PUBAPI,
|
||||
path = "/rdap/entities",
|
||||
|
||||
@@ -28,7 +28,7 @@ import google.registry.request.auth.Auth;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Optional;
|
||||
|
||||
/** RDAP (new WHOIS) action for help requests. */
|
||||
/** RDAP action for help requests. */
|
||||
@Action(
|
||||
service = GaeService.PUBAPI,
|
||||
path = RdapHelpAction.PATH,
|
||||
|
||||
@@ -22,26 +22,18 @@ import google.registry.rdap.RdapDataStructures.Remark;
|
||||
/**
|
||||
* 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://itp.cdn.icann.org/en/files/registry-operators/rdap-response-profile-21feb24-en.pdf">
|
||||
* RDAP Response Profile</a>
|
||||
*/
|
||||
public class RdapIcannStandardInformation {
|
||||
|
||||
/** Required by ICANN RDAP Profile section 1.4.10. */
|
||||
private static final Notice CONFORMANCE_NOTICE =
|
||||
Notice.builder()
|
||||
.setDescription(
|
||||
"This response conforms to the RDAP Operational Profile for gTLD Registries and"
|
||||
+ " Registrars version 1.0")
|
||||
.build();
|
||||
|
||||
/** Required by ICANN RDAP Profile section 1.5.18. */
|
||||
/** Required by RDAP Response Profile section 2.6.3. */
|
||||
private static final Notice DOMAIN_STATUS_CODES_NOTICE =
|
||||
Notice.builder()
|
||||
.setTitle("Status Codes")
|
||||
.setDescription(
|
||||
"For more information on domain status codes, please visit"
|
||||
+ " https://icann.org/epp")
|
||||
"For more information on domain status codes, please visit https://icann.org/epp")
|
||||
.addLink(
|
||||
Link.builder()
|
||||
.setRel("glossary")
|
||||
@@ -50,7 +42,7 @@ public class RdapIcannStandardInformation {
|
||||
.build())
|
||||
.build();
|
||||
|
||||
/** Required by ICANN RDAP Response Profile section 2.11. */
|
||||
/** Required by RDAP Response Profile section 2.10. */
|
||||
private static final Notice INACCURACY_COMPLAINT_FORM_NOTICE =
|
||||
Notice.builder()
|
||||
.setTitle("RDDS Inaccuracy Complaint Form")
|
||||
@@ -79,28 +71,16 @@ public class RdapIcannStandardInformation {
|
||||
/** Boilerplate notices required by domain responses. */
|
||||
static final ImmutableList<Notice> DOMAIN_BOILERPLATE_NOTICES =
|
||||
ImmutableList.of(
|
||||
CONFORMANCE_NOTICE,
|
||||
// RDAP Response Profile 2.6.3
|
||||
DOMAIN_STATUS_CODES_NOTICE,
|
||||
// RDAP Response Profile 2.11
|
||||
// RDAP Response Profile 2.10
|
||||
INACCURACY_COMPLAINT_FORM_NOTICE);
|
||||
|
||||
/** Boilerplate notice for when a domain is blocked by BSA. */
|
||||
static final ImmutableList<Notice> DOMAIN_BLOCKED_BY_BSA_BOILERPLATE_NOTICES =
|
||||
ImmutableList.of(DOMAIN_BLOCKED_BY_BSA_NOTICE);
|
||||
|
||||
/** Boilerplate remarks required by nameserver and entity responses. */
|
||||
static final ImmutableList<Notice> NAMESERVER_AND_ENTITY_BOILERPLATE_NOTICES =
|
||||
ImmutableList.of(CONFORMANCE_NOTICE);
|
||||
|
||||
/**
|
||||
* Required by ICANN RDAP Profile section 1.4.9, as corrected by Gustavo Lozano of ICANN.
|
||||
*
|
||||
* <p>Also mentioned in the RDAP Technical Implementation Guide 3.6.
|
||||
*
|
||||
* @see <a href="http://mm.icann.org/pipermail/gtld-tech/2016-October/000822.html">Questions about
|
||||
* the ICANN RDAP Profile</a>
|
||||
*/
|
||||
/** Required by the RDAP Technical Implementation Guide 3.6. */
|
||||
static final Remark SUMMARY_DATA_REMARK =
|
||||
Remark.builder()
|
||||
.setTitle("Incomplete Data")
|
||||
@@ -109,14 +89,7 @@ public class RdapIcannStandardInformation {
|
||||
.setType(Remark.Type.OBJECT_TRUNCATED_UNEXPLAINABLE)
|
||||
.build();
|
||||
|
||||
/**
|
||||
* Required by ICANN RDAP Profile section 1.4.8, as corrected by Gustavo Lozano of ICANN.
|
||||
*
|
||||
* <p>Also mentioned in the RDAP Technical Implementation Guide 3.5.
|
||||
*
|
||||
* @see <a href="http://mm.icann.org/pipermail/gtld-tech/2016-October/000822.html">Questions about
|
||||
* the ICANN RDAP Profile</a>
|
||||
*/
|
||||
/** Required by the RDAP Technical Implementation Guide 3.5. */
|
||||
static final Notice TRUNCATED_RESULT_SET_NOTICE =
|
||||
Notice.builder()
|
||||
.setTitle("Search Policy")
|
||||
@@ -148,7 +121,9 @@ public class RdapIcannStandardInformation {
|
||||
/**
|
||||
* Included when requester is not logged in as the owner of the contact being returned.
|
||||
*
|
||||
* <p>Format required by ICANN RDAP Response Profile 15feb19 section 2.7.4.3.
|
||||
* <p>>Note: if we were keeping this around, we'd want/need to implement the <a
|
||||
* href="https://datatracker.ietf.org/doc/rfc9537/">official RDAP redaction spec</a> for contacts.
|
||||
* We are getting rid of contacts in 2025 though so this should be unnecessary.
|
||||
*/
|
||||
static final Remark CONTACT_PERSONAL_DATA_HIDDEN_DATA_REMARK =
|
||||
Remark.builder()
|
||||
@@ -169,10 +144,9 @@ public class RdapIcannStandardInformation {
|
||||
/**
|
||||
* Included in ALL contact responses, even if the user is authorized.
|
||||
*
|
||||
* <p>Format required by ICANN RDAP Response Profile 15feb19 section 2.7.5.3.
|
||||
*
|
||||
* <p>NOTE that unlike other redacted fields, there's no allowance to give the email to authorized
|
||||
* users or allow for registrar consent.
|
||||
* <p>>Note: if we were keeping this around, we'd want/need to implement the <a
|
||||
* href="https://datatracker.ietf.org/doc/rfc9537/">official RDAP redaction spec</a> for contacts.
|
||||
* We are getting rid of contacts in 2025 though so this should be unnecessary.
|
||||
*/
|
||||
static final Remark CONTACT_EMAIL_REDACTED_FOR_DOMAIN =
|
||||
Remark.builder()
|
||||
|
||||
@@ -26,7 +26,7 @@ import google.registry.request.auth.Auth;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
/**
|
||||
* RDAP (new WHOIS) action for RDAP IP address requests.
|
||||
* RDAP action for RDAP IP address requests.
|
||||
*
|
||||
* <p>This feature is not implemented because it's only necessary for <i>address</i> registries like
|
||||
* ARIN, not domain registries.
|
||||
|
||||
@@ -23,20 +23,21 @@ import static google.registry.model.EppResourceUtils.isLinked;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm;
|
||||
import static google.registry.util.CollectionUtils.union;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.LoadingCache;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSetMultimap;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Ordering;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.collect.Streams;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.net.InetAddresses;
|
||||
import com.google.gson.JsonArray;
|
||||
import google.registry.config.RegistryConfig;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.model.CacheUtils;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.adapters.EnumToAttributeAdapter.EppEnum;
|
||||
import google.registry.model.contact.Contact;
|
||||
@@ -73,13 +74,11 @@ import google.registry.rdap.RdapObjectClasses.VcardArray;
|
||||
import google.registry.request.RequestServerName;
|
||||
import google.registry.util.Clock;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.persistence.Entity;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
@@ -103,6 +102,16 @@ public class RdapJsonFormatter {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
@VisibleForTesting
|
||||
record HistoryTimeAndRegistrar(DateTime modificationTime, String registrarId) {}
|
||||
|
||||
private static final LoadingCache<String, ImmutableMap<EventAction, HistoryTimeAndRegistrar>>
|
||||
DOMAIN_HISTORIES_BY_REPO_ID =
|
||||
CacheUtils.newCacheBuilder(RegistryConfig.getEppResourceCachingDuration())
|
||||
// Cache more than the EPP resource cache because we're only caching small objects
|
||||
.maximumSize(RegistryConfig.getEppResourceMaxCachedEntries() * 4L)
|
||||
.build(repoId -> getLastHistoryByType(repoId, Domain.class));
|
||||
|
||||
private DateTime requestTime = null;
|
||||
|
||||
@Inject
|
||||
@@ -212,7 +221,7 @@ public class RdapJsonFormatter {
|
||||
* Map of EPP event values to the RDAP equivalents.
|
||||
*
|
||||
* <p>Only has entries for optional events, either stated as optional in the RDAP Response Profile
|
||||
* 15feb19, or not mentioned at all but thought to be useful anyway.
|
||||
* section 2.3.2, or not mentioned at all but thought to be useful anyway.
|
||||
*
|
||||
* <p>Any required event should be added elsewhere, preferably without using HistoryEntries (so
|
||||
* that we don't need to load HistoryEntries for "summary" responses).
|
||||
@@ -283,8 +292,8 @@ public class RdapJsonFormatter {
|
||||
* Creates a JSON object for a {@link Domain}.
|
||||
*
|
||||
* <p>NOTE that domain searches aren't in the spec yet - they're in the RFC 9082 that describes
|
||||
* the query format, but they aren't in the RDAP Technical Implementation Guide 15feb19, meaning
|
||||
* we don't have to implement them yet and the RDAP Response Profile doesn't apply to them.
|
||||
* the query format, but they aren't in the RDAP Technical Implementation Guide, meaning we don't
|
||||
* have to implement them yet and the RDAP Response Profile doesn't apply to them.
|
||||
*
|
||||
* <p>We're implementing domain searches anyway, BUT we won't have the response for searches
|
||||
* conform to the RDAP Response Profile.
|
||||
@@ -298,9 +307,9 @@ public class RdapJsonFormatter {
|
||||
if (outputDataType != OutputDataType.FULL) {
|
||||
builder.remarksBuilder().add(RdapIcannStandardInformation.SUMMARY_DATA_REMARK);
|
||||
}
|
||||
// RDAP Response Profile 15feb19 section 2.1 discusses the domain name.
|
||||
// RDAP Response Profile section 2.1 discusses the domain name.
|
||||
builder.setLdhName(domain.getDomainName());
|
||||
// RDAP Response Profile 15feb19 section 2.2:
|
||||
// RDAP Response Profile section 2.2:
|
||||
// The domain handle MUST be the ROID
|
||||
builder.setHandle(domain.getRepoId());
|
||||
// If this is a summary (search result) - we'll return now. Since there's no requirement for
|
||||
@@ -308,9 +317,9 @@ public class RdapJsonFormatter {
|
||||
if (outputDataType == OutputDataType.SUMMARY) {
|
||||
return builder.build();
|
||||
}
|
||||
// RDAP Response Profile 15feb19 section 2.3.1:
|
||||
// RDAP Response Profile section 2.3.1:
|
||||
// The domain object in the RDAP response MUST contain the following events:
|
||||
// [registration, expiration, last update of RDAP database]
|
||||
// [registration, expiration]
|
||||
builder
|
||||
.eventsBuilder()
|
||||
.add(
|
||||
@@ -324,14 +333,18 @@ public class RdapJsonFormatter {
|
||||
.setEventAction(EventAction.EXPIRATION)
|
||||
.setEventDate(domain.getRegistrationExpirationTime())
|
||||
.build(),
|
||||
// RDAP response profile section 1.5:
|
||||
// The topmost object in the RDAP response MUST contain an event of "eventAction" type
|
||||
// "last update of RDAP database" with a value equal to the timestamp when the RDAP
|
||||
// database was last updated
|
||||
Event.builder()
|
||||
.setEventAction(EventAction.LAST_UPDATE_OF_RDAP_DATABASE)
|
||||
.setEventDate(getRequestTime())
|
||||
.build());
|
||||
// RDAP Response Profile 15feb19 section 2.3.2 discusses optional events. We add some of those
|
||||
// RDAP Response Profile section 2.3.2 discusses optional events. We add some of those
|
||||
// here. We also add a few others we find interesting.
|
||||
builder.eventsBuilder().addAll(makeOptionalEvents(domain));
|
||||
// RDAP Response Profile 15feb19 section 2.4.1:
|
||||
// RDAP Response Profile section 2.4.1:
|
||||
// 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
|
||||
@@ -369,8 +382,6 @@ public class RdapJsonFormatter {
|
||||
// RDAP Response Profile 2.6.3, must have a notice about statuses. That is in {@link
|
||||
// RdapIcannStandardInformation#domainBoilerplateNotices}
|
||||
|
||||
// Kick off the database loads of the nameservers that we will need, so it can load
|
||||
// asynchronously while we load and process the contacts.
|
||||
ImmutableSet<Host> loadedHosts =
|
||||
replicaTm()
|
||||
.transact(
|
||||
@@ -415,12 +426,12 @@ public class RdapJsonFormatter {
|
||||
}
|
||||
|
||||
// Add the nameservers to the data; the load was kicked off above for efficiency.
|
||||
// RDAP Response Profile 2.9: we MUST have the nameservers
|
||||
// RDAP Response Profile 2.8: we MUST have the nameservers
|
||||
for (Host host : HOST_RESOURCE_ORDERING.immutableSortedCopy(loadedHosts)) {
|
||||
builder.nameserversBuilder().add(createRdapNameserver(host, OutputDataType.INTERNAL));
|
||||
}
|
||||
|
||||
// RDAP Response Profile 2.10 - MUST contain a secureDns member including at least a
|
||||
// RDAP Response Profile 2.9 - MUST contain a secureDns member including at least a
|
||||
// delegationSigned element. Other elements (e.g. dsData) MUST be included if the domain name is
|
||||
// signed and the elements are stored in the Registry
|
||||
//
|
||||
@@ -445,13 +456,13 @@ public class RdapJsonFormatter {
|
||||
builder.remarksBuilder().add(RdapIcannStandardInformation.SUMMARY_DATA_REMARK);
|
||||
}
|
||||
|
||||
// We need the ldhName: RDAP Response Profile 2.9.1, 4.1
|
||||
// We need the ldhName: RDAP Response Profile 2.8.1, 4.1
|
||||
builder.setLdhName(host.getHostName());
|
||||
// Handle is optional, but if given it MUST be the ROID.
|
||||
// We will set it always as it's important as a "self link"
|
||||
builder.setHandle(host.getRepoId());
|
||||
|
||||
// Status is optional for internal Nameservers - RDAP Response Profile 2.9.2
|
||||
// Status is optional for internal Nameservers - RDAP Response Profile 2.8.2
|
||||
// It isn't mentioned at all anywhere else. So we can just not put it at all?
|
||||
//
|
||||
// To be safe, we'll put it on the "FULL" version anyway
|
||||
@@ -483,7 +494,7 @@ public class RdapJsonFormatter {
|
||||
|
||||
// For query responses - we MUST have all the ip addresses: RDAP Response Profile 4.2.
|
||||
//
|
||||
// However, it is optional for internal responses: RDAP Response Profile 2.9.2
|
||||
// However, it is optional for internal responses: RDAP Response Profile 2.8.2
|
||||
if (outputDataType != OutputDataType.INTERNAL) {
|
||||
for (InetAddress inetAddress : host.getInetAddresses()) {
|
||||
if (inetAddress instanceof Inet4Address) {
|
||||
@@ -501,7 +512,7 @@ public class RdapJsonFormatter {
|
||||
builder.entitiesBuilder().add(createRdapRegistrarEntity(registrar, 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 1.5, must have "last update of RDAP database" response. But this is
|
||||
// only for direct query responses and not for internal objects.
|
||||
builder.setLastUpdateOfRdapDatabaseEvent(
|
||||
Event.builder()
|
||||
@@ -526,10 +537,7 @@ public class RdapJsonFormatter {
|
||||
Contact contact, Iterable<RdapEntity.Role> roles, OutputDataType outputDataType) {
|
||||
RdapContactEntity.Builder contactBuilder = RdapContactEntity.builder();
|
||||
|
||||
// RDAP Response Profile 2.7.1, 2.7.3 - we MUST have the contacts. 2.7.4 discusses censoring of
|
||||
// fields we don't want to show (as opposed to not having contacts at all) because of GDPR etc.
|
||||
//
|
||||
// 2.8 allows for unredacted output for authorized people.
|
||||
// RDAP Response Profile 2.7.1, 2.7.3 - we MUST have the contacts
|
||||
boolean isAuthorized =
|
||||
rdapAuthorization.isAuthorizedForRegistrar(contact.getCurrentSponsorRegistrarId());
|
||||
|
||||
@@ -569,7 +577,7 @@ public class RdapJsonFormatter {
|
||||
.add(RdapIcannStandardInformation.CONTACT_EMAIL_REDACTED_FOR_DOMAIN);
|
||||
|
||||
if (outputDataType != OutputDataType.INTERNAL) {
|
||||
// Rdap Response Profile 2.7.6 must have "last update of RDAP database" response. But this is
|
||||
// Rdap Response Profile 1.5 must have "last update of RDAP database" response. But this is
|
||||
// only for direct query responses and not for internal objects. I'm not sure why it's in that
|
||||
// section at all...
|
||||
contactBuilder.setLastUpdateOfRdapDatabaseEvent(
|
||||
@@ -647,8 +655,8 @@ public class RdapJsonFormatter {
|
||||
* Creates a JSON object for a {@link Registrar}.
|
||||
*
|
||||
* <p>This object can be INTERNAL to the Domain and Nameserver responses, with requirements
|
||||
* discussed in the RDAP Response Profile 15feb19 sections 2.4 (internal to Domain) and 4.3
|
||||
* (internal to Namesever)
|
||||
* discussed in the RDAP Response Profile sections 2.4 (internal to Domain) and 4.3 (internal to
|
||||
* Namesever)
|
||||
*
|
||||
* @param registrar the registrar object from which the RDAP response
|
||||
* @param outputDataType whether to generate FULL, SUMMARY, or INTERNAL data.
|
||||
@@ -712,6 +720,15 @@ public class RdapJsonFormatter {
|
||||
builder.linksBuilder().add(makeSelfLink("entity", ianaIdentifier.toString()));
|
||||
}
|
||||
|
||||
// RDAP Response Profile 2.4.6: must have a links entry pointing to the registrar URL, with a
|
||||
// rel:about and a value containing the registrar RDAP base URL (if present)
|
||||
if (registrar.getUrl() != null) {
|
||||
Link.Builder registrarLinkBuilder =
|
||||
Link.builder().setHref(registrar.getUrl()).setRel("about").setType("text/html");
|
||||
registrar.getRdapBaseUrls().stream().findFirst().ifPresent(registrarLinkBuilder::setValue);
|
||||
builder.linksBuilder().add(registrarLinkBuilder.build());
|
||||
}
|
||||
|
||||
// There's no mention of the registrar STATUS in the RDAP Response Profile, so we'll only add it
|
||||
// for FULL response
|
||||
// We could probably not add it at all, but it could be useful for us internally
|
||||
@@ -737,7 +754,6 @@ public class RdapJsonFormatter {
|
||||
//
|
||||
// Write the minimum, meaning only ABUSE for INTERNAL registrars, nothing for SUMMARY and
|
||||
// everything for FULL.
|
||||
//
|
||||
if (outputDataType != OutputDataType.SUMMARY) {
|
||||
ImmutableList<RdapContactEntity> registrarContacts =
|
||||
registrar.getContactsFromReplica().stream()
|
||||
@@ -758,7 +774,7 @@ public class RdapJsonFormatter {
|
||||
builder.entitiesBuilder().addAll(registrarContacts);
|
||||
}
|
||||
|
||||
// Rdap Response Profile 3.3, must have "last update of RDAP database" response. But this is
|
||||
// Rdap Response Profile 1.5, must have "last update of RDAP database" response. But this is
|
||||
// only for direct query responses and not for internal objects.
|
||||
if (outputDataType != OutputDataType.INTERNAL) {
|
||||
builder.setLastUpdateOfRdapDatabaseEvent(
|
||||
@@ -860,8 +876,18 @@ public class RdapJsonFormatter {
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
ImmutableMap<EventAction, HistoryEntry> getLastHistoryEntryByType(EppResource resource) {
|
||||
HashMap<EventAction, HistoryEntry> lastEntryOfType = Maps.newHashMap();
|
||||
static ImmutableMap<EventAction, HistoryTimeAndRegistrar> getLastHistoryByType(
|
||||
EppResource eppResource) {
|
||||
if (eppResource instanceof Domain) {
|
||||
return DOMAIN_HISTORIES_BY_REPO_ID.get(eppResource.getRepoId());
|
||||
}
|
||||
return getLastHistoryByType(eppResource.getRepoId(), eppResource.getClass());
|
||||
}
|
||||
|
||||
private static ImmutableMap<EventAction, HistoryTimeAndRegistrar> getLastHistoryByType(
|
||||
String repoId, Class<? extends EppResource> resourceType) {
|
||||
ImmutableMap.Builder<EventAction, HistoryTimeAndRegistrar> lastEntryOfType =
|
||||
new ImmutableMap.Builder<>();
|
||||
// Events (such as transfer, but also create) can appear multiple times. We only want the last
|
||||
// time they appeared.
|
||||
//
|
||||
@@ -873,49 +899,48 @@ public class RdapJsonFormatter {
|
||||
// 2.3.2.3 An event of *eventAction* type *transfer*, with the last date and time that the
|
||||
// domain was transferred. The event of *eventAction* type *transfer* MUST be omitted if the
|
||||
// domain name has not been transferred since it was created.
|
||||
VKey<? extends EppResource> resourceVkey = resource.createVKey();
|
||||
Class<? extends HistoryEntry> historyClass =
|
||||
HistoryEntryDao.getHistoryClassFromParent(resourceVkey.getKind());
|
||||
String entityName = historyClass.getAnnotation(Entity.class).name();
|
||||
if (Strings.isNullOrEmpty(entityName)) {
|
||||
entityName = historyClass.getSimpleName();
|
||||
}
|
||||
String entityName = HistoryEntryDao.getHistoryClassFromParent(resourceType).getSimpleName();
|
||||
String jpql =
|
||||
GET_LAST_HISTORY_BY_TYPE_JPQL_TEMPLATE
|
||||
.replace("%entityName%", entityName)
|
||||
.replace("%repoIdValue%", resourceVkey.getKey().toString());
|
||||
Iterable<HistoryEntry> historyEntries =
|
||||
replicaTm()
|
||||
.transact(
|
||||
() ->
|
||||
replicaTm()
|
||||
.getEntityManager()
|
||||
.createQuery(jpql, HistoryEntry.class)
|
||||
.getResultList());
|
||||
for (HistoryEntry historyEntry : historyEntries) {
|
||||
EventAction rdapEventAction =
|
||||
HISTORY_ENTRY_TYPE_TO_RDAP_EVENT_ACTION_MAP.get(historyEntry.getType());
|
||||
// Only save the historyEntries if this is a type we care about.
|
||||
if (rdapEventAction == null) {
|
||||
continue;
|
||||
}
|
||||
lastEntryOfType.put(rdapEventAction, historyEntry);
|
||||
}
|
||||
return ImmutableMap.copyOf(lastEntryOfType);
|
||||
.replace("%repoIdValue%", repoId);
|
||||
replicaTm()
|
||||
.transact(
|
||||
() ->
|
||||
replicaTm()
|
||||
.getEntityManager()
|
||||
.createQuery(jpql, HistoryEntry.class)
|
||||
.getResultStream()
|
||||
.forEach(
|
||||
historyEntry -> {
|
||||
EventAction rdapEventAction =
|
||||
HISTORY_ENTRY_TYPE_TO_RDAP_EVENT_ACTION_MAP.get(
|
||||
historyEntry.getType());
|
||||
// Only save the entries if this is a type we care about.
|
||||
if (rdapEventAction != null) {
|
||||
lastEntryOfType.put(
|
||||
rdapEventAction,
|
||||
new HistoryTimeAndRegistrar(
|
||||
historyEntry.getModificationTime(),
|
||||
historyEntry.getRegistrarId()));
|
||||
}
|
||||
}));
|
||||
return lastEntryOfType.buildKeepingLast();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the list of optional events to list in domain, nameserver, or contact replies.
|
||||
*
|
||||
* <p>Only has entries for optional events that won't be shown in "SUMMARY" versions of these
|
||||
* objects. These are either stated as optional in the RDAP Response Profile 15feb19, or not
|
||||
* mentioned at all but thought to be useful anyway.
|
||||
* objects. These are either stated as optional in the RDAP Response Profile, or not mentioned at
|
||||
* all but thought to be useful anyway.
|
||||
*
|
||||
* <p>Any required event should be added elsewhere, preferably without using HistoryEntries (so
|
||||
* that we don't need to load HistoryEntries for "summary" responses).
|
||||
*/
|
||||
private ImmutableList<Event> makeOptionalEvents(EppResource resource) {
|
||||
ImmutableMap<EventAction, HistoryEntry> lastEntryOfType = getLastHistoryEntryByType(resource);
|
||||
ImmutableMap<EventAction, HistoryTimeAndRegistrar> lastHistoryOfType =
|
||||
getLastHistoryByType(resource);
|
||||
ImmutableList.Builder<Event> eventsBuilder = new ImmutableList.Builder<>();
|
||||
DateTime creationTime = resource.getCreationTime();
|
||||
DateTime lastChangeTime =
|
||||
@@ -923,12 +948,12 @@ public class RdapJsonFormatter {
|
||||
// The order of the elements is stable - it's the order in which the enum elements are defined
|
||||
// in EventAction
|
||||
for (EventAction rdapEventAction : EventAction.values()) {
|
||||
HistoryEntry historyEntry = lastEntryOfType.get(rdapEventAction);
|
||||
HistoryTimeAndRegistrar historyTimeAndRegistrar = lastHistoryOfType.get(rdapEventAction);
|
||||
// Check if there was any entry of this type
|
||||
if (historyEntry == null) {
|
||||
if (historyTimeAndRegistrar == null) {
|
||||
continue;
|
||||
}
|
||||
DateTime modificationTime = historyEntry.getModificationTime();
|
||||
DateTime modificationTime = historyTimeAndRegistrar.modificationTime();
|
||||
// We will ignore all events that happened before the "creation time", since these events are
|
||||
// from a "previous incarnation of the domain" (for a domain that was owned by someone,
|
||||
// deleted, and then bought by someone else)
|
||||
@@ -938,7 +963,7 @@ public class RdapJsonFormatter {
|
||||
eventsBuilder.add(
|
||||
Event.builder()
|
||||
.setEventAction(rdapEventAction)
|
||||
.setEventActor(historyEntry.getRegistrarId())
|
||||
.setEventActor(historyTimeAndRegistrar.registrarId())
|
||||
.setEventDate(modificationTime)
|
||||
.build());
|
||||
// The last change time might not be the lastEppUpdateTime, since some changes happen without
|
||||
@@ -947,29 +972,24 @@ public class RdapJsonFormatter {
|
||||
lastChangeTime = modificationTime;
|
||||
}
|
||||
}
|
||||
// RDAP Response Profile 15feb19 section 2.3.2.2:
|
||||
// RDAP Response Profile section 2.3.2.2:
|
||||
// The event of eventAction type last changed MUST be omitted if the domain name has not been
|
||||
// updated since it was created
|
||||
if (lastChangeTime.isAfter(creationTime)) {
|
||||
eventsBuilder.add(makeEvent(EventAction.LAST_CHANGED, null, lastChangeTime));
|
||||
// Creates an RDAP event object as defined by RFC 9083
|
||||
eventsBuilder.add(
|
||||
Event.builder()
|
||||
.setEventAction(EventAction.LAST_CHANGED)
|
||||
.setEventDate(lastChangeTime)
|
||||
.build());
|
||||
}
|
||||
return eventsBuilder.build();
|
||||
}
|
||||
|
||||
/** Creates an RDAP event object as defined by RFC 9083. */
|
||||
private static Event makeEvent(
|
||||
EventAction eventAction, @Nullable String eventActor, DateTime eventDate) {
|
||||
Event.Builder builder = Event.builder().setEventAction(eventAction).setEventDate(eventDate);
|
||||
if (eventActor != null) {
|
||||
builder.setEventActor(eventActor);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a vCard address entry: array of strings specifying the components of the address.
|
||||
*
|
||||
* <p>Rdap Response Profile 3.1.1: MUST contain the following fields: Street, City, Country Rdap
|
||||
* <p>RDAP Response Profile 3.1.1: MUST contain the following fields: Street, City, Country Rdap
|
||||
* Response Profile 3.1.2: optional fields: State/Province, Postal Code, Fax Number
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc7095">RFC 7095: jCard: The JSON Format for
|
||||
|
||||
@@ -33,7 +33,7 @@ import google.registry.request.auth.Auth;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Optional;
|
||||
|
||||
/** RDAP (new WHOIS) action for nameserver requests. */
|
||||
/** RDAP action for nameserver requests. */
|
||||
@Action(
|
||||
service = GaeService.PUBAPI,
|
||||
path = "/rdap/nameserver/",
|
||||
@@ -48,7 +48,7 @@ public class RdapNameserverAction extends RdapActionBase {
|
||||
|
||||
@Override
|
||||
public RdapNameserver getJsonObjectForResource(String pathSearchString, boolean isHeadRequest) {
|
||||
// RDAP Technical Implementation Guide 2.2.1 - we must support A-label (Punycode) and U-label
|
||||
// RDAP Technical Implementation Guide 2.1.1 - we must support A-label (Punycode) and U-label
|
||||
// (Unicode) formats. canonicalizeName will transform Unicode to Punycode so we support both.
|
||||
pathSearchString = canonicalizeName(pathSearchString);
|
||||
// The RDAP syntax is /rdap/nameserver/ns1.mydomain.com.
|
||||
|
||||
@@ -47,9 +47,9 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* RDAP (new WHOIS) action for nameserver search requests.
|
||||
* RDAP action for nameserver search requests.
|
||||
*
|
||||
* <p>All commands and responses conform to the RDAP spec as defined in RFCs 7480 through 7485.
|
||||
* <p>All commands and responses conform to the RDAP spec as defined in STD 95 and its RFCs.
|
||||
*
|
||||
* @see <a href="http://tools.ietf.org/html/rfc9082">RFC 9082: Registration Data Access Protocol
|
||||
* (RDAP) Query Format</a>
|
||||
|
||||
@@ -45,11 +45,7 @@ import java.util.Optional;
|
||||
/** Object Classes defined in RFC 9083 section 5. */
|
||||
final class RdapObjectClasses {
|
||||
|
||||
/**
|
||||
* Temporary implementation of VCards.
|
||||
*
|
||||
* <p>Will create a better implementation soon.
|
||||
*/
|
||||
/** Rough implementation of VCards. */
|
||||
@RestrictJsonNames({})
|
||||
@AutoValue
|
||||
public abstract static class Vcard implements Jsonable {
|
||||
@@ -140,8 +136,8 @@ final class RdapObjectClasses {
|
||||
public enum BoilerplateType {
|
||||
DOMAIN(RdapIcannStandardInformation.DOMAIN_BOILERPLATE_NOTICES),
|
||||
DOMAIN_BLOCKED_BY_BSA(RdapIcannStandardInformation.DOMAIN_BLOCKED_BY_BSA_BOILERPLATE_NOTICES),
|
||||
NAMESERVER(RdapIcannStandardInformation.NAMESERVER_AND_ENTITY_BOILERPLATE_NOTICES),
|
||||
ENTITY(RdapIcannStandardInformation.NAMESERVER_AND_ENTITY_BOILERPLATE_NOTICES),
|
||||
NAMESERVER(ImmutableList.of()),
|
||||
ENTITY(ImmutableList.of()),
|
||||
OTHER(ImmutableList.of());
|
||||
|
||||
@SuppressWarnings("ImmutableEnumChecker") // immutable lists are, in fact, immutable
|
||||
@@ -173,8 +169,8 @@ final class RdapObjectClasses {
|
||||
* The Top Level JSON reply, Adds the required top-level boilerplate to a ReplyPayloadBase.
|
||||
*
|
||||
* <p>RFC 9083 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.
|
||||
* conformance level. The RDAP spec mandates several additional entries, in sections 2.6.3, 2.10
|
||||
* of the Response Profile and 3.3, 3.5, of the Technical Implementation Guide.
|
||||
*/
|
||||
@AutoValue
|
||||
@RestrictJsonNames({})
|
||||
@@ -353,7 +349,7 @@ final class RdapObjectClasses {
|
||||
*
|
||||
* <p>Takes care of the name and unicode field.
|
||||
*
|
||||
* <p>See RDAP Response Profile 15feb19 sections 2.1 and 4.1.
|
||||
* <p>See RDAP Response Profile 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
|
||||
@@ -471,7 +467,7 @@ final class RdapObjectClasses {
|
||||
}
|
||||
|
||||
/**
|
||||
* an integer representing the signature lifetime in seconds to be used when creating the RRSIG
|
||||
* 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 RFC 9083, in RFC5910 it's mentioned as
|
||||
|
||||
@@ -44,7 +44,7 @@ import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Base RDAP (new WHOIS) action for domain, nameserver and entity search requests.
|
||||
* Base RDAP action for domain, nameserver and entity search requests.
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc9082">RFC 9082: Registration Data Access Protocol
|
||||
* (RDAP) Query Format</a>
|
||||
@@ -155,7 +155,6 @@ public abstract class RdapSearchActionBase extends RdapActionBase {
|
||||
*/
|
||||
<T extends EppResource> RdapResultSet<T> getMatchingResources(
|
||||
CriteriaQueryBuilder<T> builder, boolean checkForVisibility, int querySizeLimit) {
|
||||
replicaTm().assertInTransaction();
|
||||
Optional<String> desiredRegistrar = getDesiredRegistrar();
|
||||
if (desiredRegistrar.isPresent()) {
|
||||
builder =
|
||||
|
||||
@@ -39,8 +39,8 @@ public final class RdapSearchPattern {
|
||||
/**
|
||||
* Pattern for allowed LDH searches.
|
||||
*
|
||||
* <p>Based on RFC 9082 4.1. Must contains only alphanumeric plus dots and hyphens. A single
|
||||
* whildcard asterix is allowed - but if exists must be the last character of a domain name label
|
||||
* <p>Based on RFC 9082 4.1. Must contain only alphanumeric plus dots and hyphens. A single
|
||||
* wildcard asterix is allowed - but if exists must be the last character of a domain name label
|
||||
* (so exam* and exam*.com are allowed, but exam*le.com isn't allowd)
|
||||
*
|
||||
* <p>The prefix is in group(1), and the suffix without the dot (if it exists) is in group(4). If
|
||||
@@ -123,7 +123,7 @@ public final class RdapSearchPattern {
|
||||
* Creates a SearchPattern using the provided domain search pattern in LDH format.
|
||||
*
|
||||
* <p>The domain search pattern can have a single wildcard asterix that can match 0 or more
|
||||
* charecters. If such an asterix exists - it must be at the end of a domain label.
|
||||
* characters. If such an asterix exists - it must be at the end of a domain label.
|
||||
*
|
||||
* @param searchQuery the string containing the partial match pattern
|
||||
* @throws UnprocessableEntityException if {@code pattern} does not meet the requirements of RFC
|
||||
@@ -150,7 +150,7 @@ public final class RdapSearchPattern {
|
||||
* Creates a SearchPattern using the provided domain search pattern in LDH or Unicode format.
|
||||
*
|
||||
* <p>The domain search pattern can have a single wildcard asterix that can match 0 or more
|
||||
* charecters. If such an asterix exists - it must be at the end of a domain label.
|
||||
* characters. If such an asterix exists - it must be at the end of a domain label.
|
||||
*
|
||||
* <p>In theory, according to RFC 9082 4.1 - we should make some checks about partial matching in
|
||||
* unicode queries. We don't, but we might want to just disable partial matches for unicode inputs
|
||||
|
||||
@@ -37,7 +37,7 @@ public final class RdapUtils {
|
||||
*
|
||||
* <p>Used for RDAP Technical Implementation Guide 2.4.2 - search of registrar by the fn element.
|
||||
*
|
||||
* <p>For convenience, we use case insensitive search.
|
||||
* <p>For convenience, we use case-insensitive search.
|
||||
*/
|
||||
static Optional<Registrar> getRegistrarByName(String registrarName) {
|
||||
return Streams.stream(Registrar.loadAllCached())
|
||||
|
||||
@@ -25,6 +25,7 @@ import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.persistence.PersistenceModule;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.HttpException.InternalServerErrorException;
|
||||
@@ -36,7 +37,7 @@ import jakarta.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.security.GeneralSecurityException;
|
||||
import org.apache.commons.csv.CSVFormat;
|
||||
import org.apache.commons.csv.CSVParser;
|
||||
@@ -72,7 +73,7 @@ public final class UpdateRegistrarRdapBaseUrlsAction implements Runnable {
|
||||
public void run() {
|
||||
try {
|
||||
ImmutableMap<String, String> ianaIdsToUrls = getIanaIdsToUrls();
|
||||
tm().transact(() -> processAllRegistrars(ianaIdsToUrls));
|
||||
processAllRegistrars(ianaIdsToUrls);
|
||||
} catch (Exception e) {
|
||||
throw new InternalServerErrorException("Error when retrieving RDAP base URL CSV file", e);
|
||||
}
|
||||
@@ -80,7 +81,14 @@ public final class UpdateRegistrarRdapBaseUrlsAction implements Runnable {
|
||||
|
||||
private static void processAllRegistrars(ImmutableMap<String, String> ianaIdsToUrls) {
|
||||
int nonUpdatedRegistrars = 0;
|
||||
for (Registrar registrar : Registrar.loadAll()) {
|
||||
// Split into multiple transactions to avoid load-save-reload conflicts. Re-building a registrar
|
||||
// requires a full (cached) load of all registrars to avoid IANA ID conflicts, so if multiple
|
||||
// registrars are modified in the same transaction, the second build call will fail.
|
||||
Iterable<Registrar> registrars =
|
||||
tm().transact(
|
||||
PersistenceModule.TransactionIsolationLevel.TRANSACTION_REPEATABLE_READ,
|
||||
Registrar::loadAll);
|
||||
for (Registrar registrar : registrars) {
|
||||
// Only update REAL registrars
|
||||
if (registrar.getType() != Registrar.Type.REAL) {
|
||||
continue;
|
||||
@@ -100,7 +108,12 @@ public final class UpdateRegistrarRdapBaseUrlsAction implements Runnable {
|
||||
"Updating RDAP base URLs for registrar %s from %s to %s",
|
||||
registrar.getRegistrarId(), registrar.getRdapBaseUrls(), baseUrls);
|
||||
}
|
||||
tm().put(registrar.asBuilder().setRdapBaseUrls(baseUrls).build());
|
||||
tm().transact(
|
||||
() -> {
|
||||
// Reload inside a transaction to avoid race conditions
|
||||
Registrar reloadedRegistrar = tm().loadByEntity(registrar);
|
||||
tm().put(reloadedRegistrar.asBuilder().setRdapBaseUrls(baseUrls).build());
|
||||
});
|
||||
}
|
||||
}
|
||||
logger.atInfo().log("No change in RDAP base URLs for %d registrars", nonUpdatedRegistrars);
|
||||
@@ -108,9 +121,9 @@ public final class UpdateRegistrarRdapBaseUrlsAction implements Runnable {
|
||||
|
||||
private ImmutableMap<String, String> getIanaIdsToUrls()
|
||||
throws IOException, GeneralSecurityException {
|
||||
CSVParser csv;
|
||||
HttpURLConnection connection = urlConnectionService.createConnection(new URL(RDAP_IDS_URL));
|
||||
// Explictly set the accepted encoding, as we know Brotli causes us problems when talking to
|
||||
HttpURLConnection connection =
|
||||
urlConnectionService.createConnection(URI.create(RDAP_IDS_URL).toURL());
|
||||
// Explicitly set the accepted encoding, as we know Brotli causes us problems when talking to
|
||||
// ICANN.
|
||||
connection.setRequestProperty(ACCEPT_ENCODING, "gzip");
|
||||
String csvString;
|
||||
@@ -128,11 +141,11 @@ public final class UpdateRegistrarRdapBaseUrlsAction implements Runnable {
|
||||
} finally {
|
||||
connection.disconnect();
|
||||
}
|
||||
csv =
|
||||
CSVParser csv =
|
||||
CSVFormat.Builder.create(CSVFormat.DEFAULT)
|
||||
.setHeader()
|
||||
.setSkipHeaderRecord(true)
|
||||
.build()
|
||||
.get()
|
||||
.parse(new StringReader(csvString));
|
||||
ImmutableMap.Builder<String, String> result = new ImmutableMap.Builder<>();
|
||||
for (CSVRecord record : csv) {
|
||||
|
||||
+2
@@ -379,6 +379,7 @@ public abstract class JpaTransactionManagerExtension
|
||||
.setPassword("foo-BAR2")
|
||||
.setPhoneNumber("+1.3334445555")
|
||||
.setPhonePasscode("12345")
|
||||
.setRdapBaseUrls(ImmutableSet.of("https://rdap.newregistrar.com/"))
|
||||
.setRegistryLockAllowed(false)
|
||||
.build();
|
||||
}
|
||||
@@ -393,6 +394,7 @@ public abstract class JpaTransactionManagerExtension
|
||||
.setPassword("password2")
|
||||
.setPhoneNumber("+1.2223334444")
|
||||
.setPhonePasscode("22222")
|
||||
.setRdapBaseUrls(ImmutableSet.of("https://rdap.theregistrar.com/"))
|
||||
.setRegistryLockAllowed(true)
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -205,16 +205,6 @@ have failed to comply with these terms.",
|
||||
}
|
||||
JsonArray notices = jsonObject.getAsJsonArray("notices");
|
||||
notices.add(createTosNotice());
|
||||
notices.add(
|
||||
JsonParser.parseString(
|
||||
"""
|
||||
{
|
||||
"description": [
|
||||
"This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars \
|
||||
version 1.0"
|
||||
]
|
||||
}
|
||||
"""));
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
|
||||
@@ -43,12 +43,13 @@ final class RdapDataStructuresTest {
|
||||
@Test
|
||||
void testRdapConformance() {
|
||||
assertThat(RdapConformance.INSTANCE.toJson())
|
||||
.isEqualTo(createJson(
|
||||
"[",
|
||||
" 'rdap_level_0',",
|
||||
" 'icann_rdap_response_profile_0',",
|
||||
" 'icann_rdap_technical_implementation_guide_0'",
|
||||
"]"));
|
||||
.isEqualTo(
|
||||
createJson(
|
||||
"[",
|
||||
" 'rdap_level_0',",
|
||||
" 'icann_rdap_response_profile_1',",
|
||||
" 'icann_rdap_technical_implementation_guide_1'",
|
||||
"]"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -462,12 +462,12 @@ class RdapJsonFormatterTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetLastHistoryEntryByType() {
|
||||
void testGetLastHistoryByType() {
|
||||
// Expected data are from "rdapjson_domain_summary.json"
|
||||
assertThat(
|
||||
Maps.transformValues(
|
||||
rdapJsonFormatter.getLastHistoryEntryByType(domainFull),
|
||||
HistoryEntry::getModificationTime))
|
||||
RdapJsonFormatter.getLastHistoryByType(domainFull),
|
||||
RdapJsonFormatter.HistoryTimeAndRegistrar::modificationTime))
|
||||
.containsExactlyEntriesIn(
|
||||
ImmutableMap.of(TRANSFER, DateTime.parse("1999-12-01T00:00:00.000Z")));
|
||||
}
|
||||
|
||||
@@ -84,9 +84,7 @@ public final class FullFieldsTestEntityHelper {
|
||||
.setFaxNumber("+1.2125551213")
|
||||
.setEmailAddress("contact-us@example.com")
|
||||
.setWhoisServer("whois.example.com")
|
||||
.setRdapBaseUrls(
|
||||
ImmutableSet.of(
|
||||
"https://rdap.example.com/withSlash/", "https://rdap.example.com/withoutSlash"))
|
||||
.setRdapBaseUrls(ImmutableSet.of("https://rdap.example.com/withSlash/"))
|
||||
.setUrl("http://my.fake.url")
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"objectClassName": "entity",
|
||||
"handle": "%CONTACT_HANDLE_1%",
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"objectClassName" : "entity",
|
||||
"handle" : "",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"objectClassName" : "entity",
|
||||
"handle" : "%CONTACT_HANDLE_1%",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"objectClassName" : "entity",
|
||||
"handle" : "%CONTACT_HANDLE_1%",
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"objectClassName" : "entity",
|
||||
"handle" : "",
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
"handle": "%DOMAIN_HANDLE_1%",
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"status": [
|
||||
"client delete prohibited",
|
||||
@@ -25,12 +25,6 @@
|
||||
"type": "application/rdap+json",
|
||||
"rel": "related",
|
||||
"value": "%REQUEST_URL%"
|
||||
},
|
||||
{
|
||||
"href": "https://rdap.example.com/withoutSlash/domain/%DOMAIN_PUNYCODE_NAME_1%",
|
||||
"type": "application/rdap+json",
|
||||
"rel": "related",
|
||||
"value": "%REQUEST_URL%"
|
||||
}
|
||||
],
|
||||
"events": [
|
||||
@@ -122,6 +116,12 @@
|
||||
"type": "application/rdap+json",
|
||||
"rel": "self",
|
||||
"value": "%REQUEST_URL%"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"vcardArray" : [
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"objectClassName": "domain",
|
||||
"entities": [
|
||||
@@ -15,6 +15,12 @@
|
||||
"rel": "self",
|
||||
"type": "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/domain/addgraceperiod.lol"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.theregistrar.com/"
|
||||
}
|
||||
],
|
||||
"publicIds": [
|
||||
@@ -128,6 +134,12 @@
|
||||
"rel": "self",
|
||||
"type": "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/domain/addgraceperiod.lol"
|
||||
},
|
||||
{
|
||||
"href": "https://rdap.theregistrar.com/domain/addgraceperiod.lol",
|
||||
"rel": "related",
|
||||
"type": "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/domain/addgraceperiod.lol"
|
||||
}
|
||||
],
|
||||
"nameservers": [
|
||||
|
||||
+14
-2
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"objectClassName": "domain",
|
||||
"entities": [
|
||||
@@ -15,6 +15,12 @@
|
||||
"rel": "self",
|
||||
"type": "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/domain/autorenew.lol"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.theregistrar.com/"
|
||||
}
|
||||
],
|
||||
"publicIds": [
|
||||
@@ -132,6 +138,12 @@
|
||||
"rel": "self",
|
||||
"type": "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/domain/autorenew.lol"
|
||||
},
|
||||
{
|
||||
"href": "https://rdap.theregistrar.com/domain/autorenew.lol",
|
||||
"rel": "related",
|
||||
"type": "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/domain/autorenew.lol"
|
||||
}
|
||||
],
|
||||
"nameservers": [
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"objectClassName": "domain",
|
||||
"handle": "%DOMAIN_HANDLE_1%",
|
||||
@@ -25,12 +25,6 @@
|
||||
"type": "application/rdap+json",
|
||||
"rel": "related",
|
||||
"value": "https://example.tld/rdap/domains"
|
||||
},
|
||||
{
|
||||
"href": "https://rdap.example.com/withoutSlash/domain/%DOMAIN_PUNYCODE_NAME_1%",
|
||||
"type": "application/rdap+json",
|
||||
"rel": "related",
|
||||
"value": "https://example.tld/rdap/domains"
|
||||
}
|
||||
],
|
||||
"events": [
|
||||
@@ -105,6 +99,12 @@
|
||||
"type": "application/rdap+json",
|
||||
"rel": "self",
|
||||
"value": "https://example.tld/rdap/domains"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"vcardArray" : [
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"objectClassName": "domain",
|
||||
"ldhName": "%DOMAIN_PUNYCODE_NAME_1%",
|
||||
@@ -26,12 +26,6 @@
|
||||
"type": "application/rdap+json",
|
||||
"rel": "related",
|
||||
"value": "%REQUEST_URL%"
|
||||
},
|
||||
{
|
||||
"href": "https://rdap.example.com/withoutSlash/domain/%DOMAIN_PUNYCODE_NAME_1%",
|
||||
"type": "application/rdap+json",
|
||||
"rel": "related",
|
||||
"value": "%REQUEST_URL%"
|
||||
}
|
||||
],
|
||||
"events": [
|
||||
@@ -117,6 +111,12 @@
|
||||
"type": "application/rdap+json",
|
||||
"rel": "self",
|
||||
"value": "%REQUEST_URL%"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"vcardArray" : [
|
||||
|
||||
+14
-2
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"objectClassName": "domain",
|
||||
"entities": [
|
||||
@@ -15,6 +15,12 @@
|
||||
"rel": "self",
|
||||
"type": "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/domain/renew.lol"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.theregistrar.com/"
|
||||
}
|
||||
],
|
||||
"publicIds": [
|
||||
@@ -128,6 +134,12 @@
|
||||
"rel": "self",
|
||||
"type": "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/domain/renew.lol"
|
||||
},
|
||||
{
|
||||
"href": "https://rdap.theregistrar.com/domain/renew.lol",
|
||||
"rel": "related",
|
||||
"type": "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/domain/renew.lol"
|
||||
}
|
||||
],
|
||||
"nameservers": [
|
||||
|
||||
+8
-8
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"objectClassName": "domain",
|
||||
"handle": "%DOMAIN_HANDLE_1%",
|
||||
@@ -25,12 +25,6 @@
|
||||
"type": "application/rdap+json",
|
||||
"rel": "related",
|
||||
"value": "https://example.tld/rdap/domain/cat.lol"
|
||||
},
|
||||
{
|
||||
"href": "https://rdap.example.com/withoutSlash/domain/%DOMAIN_PUNYCODE_NAME_1%",
|
||||
"type": "application/rdap+json",
|
||||
"rel": "related",
|
||||
"value": "https://example.tld/rdap/domain/cat.lol"
|
||||
}
|
||||
],
|
||||
"events": [
|
||||
@@ -112,6 +106,12 @@
|
||||
"href" : "https://example.tld/rdap/entity/1",
|
||||
"type" : "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/domain/cat.lol"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"publicIds" : [
|
||||
|
||||
+8
-8
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"objectClassName": "domain",
|
||||
"handle": "%DOMAIN_HANDLE_1%",
|
||||
@@ -25,12 +25,6 @@
|
||||
"type": "application/rdap+json",
|
||||
"rel": "related",
|
||||
"value": "https://example.tld/rdap/domain/cat.lol"
|
||||
},
|
||||
{
|
||||
"href": "https://rdap.example.com/withoutSlash/domain/%DOMAIN_PUNYCODE_NAME_1%",
|
||||
"type": "application/rdap+json",
|
||||
"rel": "related",
|
||||
"value": "https://example.tld/rdap/domain/cat.lol"
|
||||
}
|
||||
],
|
||||
"events": [
|
||||
@@ -112,6 +106,12 @@
|
||||
"href" : "https://example.tld/rdap/entity/1",
|
||||
"type" : "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/domain/cat.lol"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"publicIds" : [
|
||||
|
||||
+14
-2
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"objectClassName": "domain",
|
||||
"entities": [
|
||||
@@ -15,6 +15,12 @@
|
||||
"rel": "self",
|
||||
"type": "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/domain/redemption.lol"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.theregistrar.com/"
|
||||
}
|
||||
],
|
||||
"publicIds": [
|
||||
@@ -128,6 +134,12 @@
|
||||
"rel": "self",
|
||||
"type": "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/domain/redemption.lol"
|
||||
},
|
||||
{
|
||||
"href": "https://rdap.theregistrar.com/domain/redemption.lol",
|
||||
"rel": "related",
|
||||
"type": "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/domain/redemption.lol"
|
||||
}
|
||||
],
|
||||
"nameservers": [
|
||||
|
||||
+8
-8
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"objectClassName": "domain",
|
||||
"handle": "%DOMAIN_HANDLE_1%",
|
||||
@@ -25,12 +25,6 @@
|
||||
"type": "application/rdap+json",
|
||||
"rel": "related",
|
||||
"value": "%REQUEST_URL%"
|
||||
},
|
||||
{
|
||||
"href": "https://rdap.example.com/withoutSlash/domain/%DOMAIN_PUNYCODE_NAME_1%",
|
||||
"type": "application/rdap+json",
|
||||
"rel": "related",
|
||||
"value": "%REQUEST_URL%"
|
||||
}
|
||||
],
|
||||
"events": [
|
||||
@@ -112,6 +106,12 @@
|
||||
"href" : "https://example.tld/rdap/entity/1",
|
||||
"type" : "application/rdap+json",
|
||||
"value": "%REQUEST_URL%"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"publicIds" : [
|
||||
|
||||
+14
-2
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"objectClassName": "domain",
|
||||
"entities": [
|
||||
@@ -15,6 +15,12 @@
|
||||
"rel": "self",
|
||||
"type": "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/domain/transfer.lol"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.theregistrar.com/"
|
||||
}
|
||||
],
|
||||
"publicIds": [
|
||||
@@ -128,6 +134,12 @@
|
||||
"rel": "self",
|
||||
"type": "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/domain/transfer.lol"
|
||||
},
|
||||
{
|
||||
"href": "https://rdap.theregistrar.com/domain/transfer.lol",
|
||||
"rel": "related",
|
||||
"type": "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/domain/transfer.lol"
|
||||
}
|
||||
],
|
||||
"nameservers": [
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
"handle": "%DOMAIN_HANDLE_1%",
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"status": [
|
||||
"client delete prohibited",
|
||||
@@ -26,12 +26,6 @@
|
||||
"type": "application/rdap+json",
|
||||
"rel": "related",
|
||||
"value": "%REQUEST_URL%"
|
||||
},
|
||||
{
|
||||
"href": "https://rdap.example.com/withoutSlash/domain/%DOMAIN_PUNYCODE_NAME_1%",
|
||||
"type": "application/rdap+json",
|
||||
"rel": "related",
|
||||
"value": "%REQUEST_URL%"
|
||||
}
|
||||
],
|
||||
"events": [
|
||||
@@ -115,6 +109,12 @@
|
||||
"type": "application/rdap+json",
|
||||
"rel": "self",
|
||||
"value": "%REQUEST_URL%"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"vcardArray" : [
|
||||
|
||||
+8
-8
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"objectClassName": "domain",
|
||||
"handle": "%DOMAIN_HANDLE_1%",
|
||||
@@ -26,12 +26,6 @@
|
||||
"type": "application/rdap+json",
|
||||
"rel": "related",
|
||||
"value": "https://example.tld/rdap/domains"
|
||||
},
|
||||
{
|
||||
"href": "https://rdap.example.com/withoutSlash/domain/%DOMAIN_PUNYCODE_NAME_1%",
|
||||
"type": "application/rdap+json",
|
||||
"rel": "related",
|
||||
"value": "https://example.tld/rdap/domains"
|
||||
}
|
||||
],
|
||||
"events": [
|
||||
@@ -115,6 +109,12 @@
|
||||
"href" : "https://example.tld/rdap/entity/1",
|
||||
"type" : "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/domains"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"publicIds" : [
|
||||
|
||||
@@ -83,8 +83,8 @@
|
||||
],
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"notices" :
|
||||
[
|
||||
@@ -140,12 +140,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description" :
|
||||
[
|
||||
"This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars version 1.0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title" : "Status Codes",
|
||||
"description" :
|
||||
|
||||
+2
-8
@@ -84,8 +84,8 @@
|
||||
],
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"notices" :
|
||||
[
|
||||
@@ -120,12 +120,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description" :
|
||||
[
|
||||
"This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars version 1.0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title" : "Status Codes",
|
||||
"description" :
|
||||
|
||||
+2
-8
@@ -84,8 +84,8 @@
|
||||
],
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"notices" :
|
||||
[
|
||||
@@ -141,12 +141,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description" :
|
||||
[
|
||||
"This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars version 1.0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title" : "Status Codes",
|
||||
"description" :
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"domainSearchResults": [
|
||||
{
|
||||
@@ -82,9 +82,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": ["This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars version 1.0"]
|
||||
},
|
||||
{
|
||||
"title": "Status Codes",
|
||||
"description": ["For more information on domain status codes, please visit https://icann.org/epp"],
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
],
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"notices": [
|
||||
{
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance" : [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"notices" :
|
||||
[
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance" : [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"notices" :
|
||||
[
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"objectClassName": "nameserver",
|
||||
"handle": "%NAMESERVER_HANDLE_1%",
|
||||
@@ -39,6 +39,12 @@
|
||||
"href" : "https://example.tld/rdap/entity/1",
|
||||
"type" : "application/rdap+json",
|
||||
"value": "%REQUEST_URL%"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.theregistrar.com/"
|
||||
}
|
||||
],
|
||||
"publicIds" :
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"status": [
|
||||
"active"
|
||||
@@ -36,6 +36,12 @@
|
||||
"href" : "https://example.tld/rdap/entity/1",
|
||||
"type" : "application/rdap+json",
|
||||
"value": "%REQUEST_URL%"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.theregistrar.com/"
|
||||
}
|
||||
],
|
||||
"publicIds" :
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"objectClassName": "nameserver",
|
||||
"handle": "%NAMESERVER_HANDLE_1%",
|
||||
@@ -42,6 +42,12 @@
|
||||
"href" : "https://example.tld/rdap/entity/1",
|
||||
"type" : "application/rdap+json",
|
||||
"value": "%REQUEST_URL%"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.theregistrar.com/"
|
||||
}
|
||||
],
|
||||
"publicIds" :
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"objectClassName": "nameserver",
|
||||
"handle": "%NAMESERVER_HANDLE_1%",
|
||||
@@ -40,6 +40,12 @@
|
||||
"href" : "https://example.tld/rdap/entity/1",
|
||||
"type" : "application/rdap+json",
|
||||
"value": "%REQUEST_URL%"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.theregistrar.com/"
|
||||
}
|
||||
],
|
||||
"publicIds" :
|
||||
|
||||
@@ -112,12 +112,6 @@
|
||||
],
|
||||
"title":"RDAP Terms of Service"
|
||||
},
|
||||
{
|
||||
"description":
|
||||
[
|
||||
"This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars version 1.0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description":
|
||||
[
|
||||
@@ -150,7 +144,7 @@
|
||||
],
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -82,11 +82,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description":[
|
||||
"This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars version 1.0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title":"Status Codes",
|
||||
"description":[
|
||||
@@ -116,7 +111,7 @@
|
||||
],
|
||||
"rdapConformance":[
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -101,8 +101,8 @@
|
||||
],
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"notices" :
|
||||
[
|
||||
@@ -134,12 +134,6 @@
|
||||
"type": "text/html"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description" :
|
||||
[
|
||||
"This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars version 1.0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -118,8 +118,8 @@
|
||||
],
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"notices" :
|
||||
[
|
||||
@@ -153,12 +153,6 @@
|
||||
"value": "https://example.tld/rdap/entities"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description" :
|
||||
[
|
||||
"This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars version 1.0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -71,8 +71,8 @@
|
||||
],
|
||||
"rdapConformance" : [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"notices" :
|
||||
[
|
||||
@@ -106,12 +106,6 @@
|
||||
"value": "https://example.tld/rdap/nameservers"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description" :
|
||||
[
|
||||
"This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars version 1.0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -232,8 +232,8 @@
|
||||
],
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"notices" :
|
||||
[
|
||||
@@ -267,12 +267,6 @@
|
||||
"value": "https://example.tld/rdap/entities"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description" :
|
||||
[
|
||||
"This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars version 1.0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -91,8 +91,8 @@
|
||||
],
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"notices" :
|
||||
[
|
||||
@@ -127,12 +127,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description" :
|
||||
[
|
||||
"This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars version 1.0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title" : "Status Codes",
|
||||
"description" :
|
||||
|
||||
@@ -136,8 +136,8 @@
|
||||
],
|
||||
"rdapConformance" : [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"notices" :
|
||||
[
|
||||
@@ -171,12 +171,6 @@
|
||||
"value": "https://example.tld/rdap/nameservers"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description" :
|
||||
[
|
||||
"This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars version 1.0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -11,6 +11,12 @@
|
||||
"href": "https://example.tld/rdap/entity/301",
|
||||
"type": "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/entities"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"publicIds":
|
||||
@@ -69,6 +75,12 @@
|
||||
"href": "https://example.tld/rdap/entity/302",
|
||||
"type": "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/entities"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"publicIds":
|
||||
@@ -127,6 +139,12 @@
|
||||
"href": "https://example.tld/rdap/entity/303",
|
||||
"type": "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/entities"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"publicIds":
|
||||
@@ -185,6 +203,12 @@
|
||||
"href": "https://example.tld/rdap/entity/304",
|
||||
"type": "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/entities"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"publicIds":
|
||||
@@ -236,8 +260,8 @@
|
||||
],
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"notices":
|
||||
[
|
||||
@@ -271,12 +295,6 @@
|
||||
"value": "https://example.tld/rdap/entities"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description":
|
||||
[
|
||||
"This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars version 1.0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"objectClassName" : "entity",
|
||||
"handle" : "%REGISTRAR_HANDLE_1%",
|
||||
@@ -15,6 +15,12 @@
|
||||
"href": "https://example.tld/rdap/entity/%REGISTRAR_HANDLE_1%",
|
||||
"type" : "application/rdap+json",
|
||||
"value": "%REQUEST_URL%"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"publicIds" :
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
{
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"objectClassName" : "entity",
|
||||
"handle" : "%REGISTRAR_HANDLE_1%",
|
||||
"links" : [
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"status" : ["%STATUS_1%"],
|
||||
"roles" : ["registrar"],
|
||||
"events": [
|
||||
|
||||
@@ -232,8 +232,8 @@
|
||||
],
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"notices":
|
||||
[
|
||||
@@ -288,12 +288,6 @@
|
||||
"value": "https://example.tld/rdap/entities"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description":
|
||||
[
|
||||
"This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars version 1.0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -136,8 +136,8 @@
|
||||
],
|
||||
"rdapConformance" : [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"notices" :
|
||||
[
|
||||
@@ -192,12 +192,6 @@
|
||||
"value": "https://example.tld/rdap/nameservers"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description" :
|
||||
[
|
||||
"This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars version 1.0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -64,9 +64,9 @@
|
||||
"links" :
|
||||
[
|
||||
{
|
||||
"rel" : "self",
|
||||
"rel": "self",
|
||||
"href": "https://example.tld/rdap/entity/0002-ROID",
|
||||
"type" : "application/rdap+json",
|
||||
"type": "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/entities"
|
||||
}
|
||||
],
|
||||
@@ -182,6 +182,12 @@
|
||||
"href": "https://example.tld/rdap/entity/301",
|
||||
"type" : "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/entities"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"publicIds" :
|
||||
@@ -233,8 +239,8 @@
|
||||
],
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"notices" :
|
||||
[
|
||||
@@ -289,12 +295,6 @@
|
||||
"value": "https://example.tld/rdap/entities"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description" :
|
||||
[
|
||||
"This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars version 1.0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -11,6 +11,12 @@
|
||||
"href": "https://example.tld/rdap/entity/301",
|
||||
"type" : "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/entities"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"publicIds" :
|
||||
@@ -69,6 +75,12 @@
|
||||
"href": "https://example.tld/rdap/entity/302",
|
||||
"type" : "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/entities"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"publicIds" :
|
||||
@@ -127,6 +139,12 @@
|
||||
"href": "https://example.tld/rdap/entity/303",
|
||||
"type" : "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/entities"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"publicIds" :
|
||||
@@ -185,6 +203,12 @@
|
||||
"href": "https://example.tld/rdap/entity/304",
|
||||
"type" : "application/rdap+json",
|
||||
"value": "https://example.tld/rdap/entities"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"publicIds" :
|
||||
@@ -236,8 +260,8 @@
|
||||
],
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"notices" :
|
||||
[
|
||||
@@ -292,12 +316,6 @@
|
||||
"value": "https://example.tld/rdap/entities"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description" :
|
||||
[
|
||||
"This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars version 1.0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"key":"value","rdapConformance":["rdap_level_0","icann_rdap_response_profile_0","icann_rdap_technical_implementation_guide_0"],"notices":[{"title":"RDAP Terms of Service","links":[{"href":"https:\/\/www.example.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."]}]}
|
||||
{"key":"value","rdapConformance":["rdap_level_0","icann_rdap_response_profile_1","icann_rdap_technical_implementation_guide_1"],"notices":[{"title":"RDAP Terms of Service","links":[{"href":"https:\/\/www.example.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."]}]}
|
||||
|
||||
@@ -21,11 +21,6 @@
|
||||
"rel" : "related",
|
||||
"href" : "https://rdap.example.com/withSlash/domain/cat.xn--q9jyb4c",
|
||||
"type" : "application/rdap+json"
|
||||
},
|
||||
{
|
||||
"rel" : "related",
|
||||
"href" : "https://rdap.example.com/withoutSlash/domain/cat.xn--q9jyb4c",
|
||||
"type" : "application/rdap+json"
|
||||
}
|
||||
],
|
||||
"events": [
|
||||
@@ -112,6 +107,12 @@
|
||||
"rel" : "self",
|
||||
"href" : "https://example.tld/rdap/entity/1",
|
||||
"type" : "application/rdap+json"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"publicIds" :
|
||||
|
||||
@@ -21,11 +21,6 @@
|
||||
"href": "https://rdap.example.com/withSlash/domain/cat.xn--q9jyb4c",
|
||||
"type": "application/rdap+json",
|
||||
"rel": "related"
|
||||
},
|
||||
{
|
||||
"href": "https://rdap.example.com/withoutSlash/domain/cat.xn--q9jyb4c",
|
||||
"type": "application/rdap+json",
|
||||
"rel": "related"
|
||||
}
|
||||
],
|
||||
"events": [
|
||||
@@ -109,6 +104,12 @@
|
||||
"rel": "self",
|
||||
"href": "https://example.tld/rdap/entity/1",
|
||||
"type": "application/rdap+json"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"publicIds": [
|
||||
|
||||
@@ -22,11 +22,6 @@
|
||||
"href": "https://rdap.example.com/withSlash/domain/fish.xn--q9jyb4c",
|
||||
"type": "application/rdap+json",
|
||||
"rel": "related"
|
||||
},
|
||||
{
|
||||
"href": "https://rdap.example.com/withoutSlash/domain/fish.xn--q9jyb4c",
|
||||
"type": "application/rdap+json",
|
||||
"rel": "related"
|
||||
}
|
||||
],
|
||||
"events": [
|
||||
@@ -60,6 +55,12 @@
|
||||
"rel": "self",
|
||||
"href": "https://example.tld/rdap/entity/1",
|
||||
"type": "application/rdap+json"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"publicIds":
|
||||
|
||||
@@ -34,6 +34,12 @@
|
||||
"rel" : "self",
|
||||
"href" : "https://example.tld/rdap/entity/1",
|
||||
"type" : "application/rdap+json"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"publicIds" :
|
||||
|
||||
@@ -30,6 +30,12 @@
|
||||
"rel": "self",
|
||||
"href": "https://example.tld/rdap/entity/1",
|
||||
"type": "application/rdap+json"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"publicIds":
|
||||
|
||||
@@ -30,6 +30,12 @@
|
||||
"rel": "self",
|
||||
"href": "https://example.tld/rdap/entity/1",
|
||||
"type": "application/rdap+json"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"publicIds":
|
||||
|
||||
@@ -29,6 +29,12 @@
|
||||
"rel" : "self",
|
||||
"href" : "https://example.tld/rdap/entity/1",
|
||||
"type" : "application/rdap+json"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"publicIds" :
|
||||
|
||||
@@ -29,6 +29,12 @@
|
||||
"rel" : "self",
|
||||
"href" : "https://example.tld/rdap/entity/1",
|
||||
"type" : "application/rdap+json"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"publicIds" :
|
||||
|
||||
@@ -29,6 +29,12 @@
|
||||
"rel" : "self",
|
||||
"href" : "https://example.tld/rdap/entity/1",
|
||||
"type" : "application/rdap+json"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"publicIds" :
|
||||
|
||||
@@ -9,6 +9,12 @@
|
||||
"rel" : "self",
|
||||
"href" : "https://example.tld/rdap/entity/1",
|
||||
"type" : "application/rdap+json"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"events": [
|
||||
|
||||
@@ -8,6 +8,12 @@
|
||||
"rel" : "self",
|
||||
"href" : "https://example.tld/rdap/entity/1",
|
||||
"type" : "application/rdap+json"
|
||||
},
|
||||
{
|
||||
"rel": "about",
|
||||
"href": "http://my.fake.url",
|
||||
"type": "text/html",
|
||||
"value": "https://rdap.example.com/withSlash/"
|
||||
}
|
||||
],
|
||||
"events": [
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
"key" : "value",
|
||||
"rdapConformance" : [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"notices" :
|
||||
[
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
"key": "value",
|
||||
"rdapConformance": [
|
||||
"rdap_level_0",
|
||||
"icann_rdap_response_profile_0",
|
||||
"icann_rdap_technical_implementation_guide_0"
|
||||
"icann_rdap_response_profile_1",
|
||||
"icann_rdap_technical_implementation_guide_1"
|
||||
],
|
||||
"notices":
|
||||
[
|
||||
@@ -36,12 +36,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description":
|
||||
[
|
||||
"This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars version 1.0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Status Codes",
|
||||
"description":
|
||||
|
||||
+11
-13
@@ -48,23 +48,22 @@ much internally for this reason.
|
||||
|
||||
## Do you support RDAP?
|
||||
|
||||
We are working on an implementation of the Registry Data Access Protocol (RDAP),
|
||||
ICANN's proposed successor to WHOIS, which provides similar data to WHOIS, but
|
||||
in a structured format. The standard is defined in RFCs 7480 through 7484:
|
||||
We provide an implementation of the Registry Data Access Protocol (RDAP) which provides
|
||||
similar data to the outdated WHOIS protocol, but in a structured format. The
|
||||
standard is defined in STD 95 and its RFCs:
|
||||
|
||||
* [RFC 7480: HTTP Usage in the Registration Data Access Protocol
|
||||
(RDAP)](https://tools.ietf.org/html/rfc7480)
|
||||
* [RFC 7481: Security Services for the Registration Data Access Protocol
|
||||
(RDAP)](https://tools.ietf.org/html/rfc7481)
|
||||
* [RFC 7482: Registration Data Access Protocol (RDAP) Query
|
||||
Format](https://tools.ietf.org/html/rfc7482)
|
||||
* [RFC 7483: JSON Responses for the Registration Data Access Protocol
|
||||
(RDAP)](https://tools.ietf.org/html/rfc7483)
|
||||
* [RFC 7484: Finding the Authoritative Registration Data (RDAP)
|
||||
Service](https://tools.ietf.org/html/rfc7484)
|
||||
* [RFC 9082: Registration Data Access Protocol (RDAP) Query
|
||||
Format](https://tools.ietf.org/html/rfc9082)
|
||||
* [RFC 9083: JSON Responses for the Registration Data Access Protocol
|
||||
(RDAP)](https://tools.ietf.org/html/rfc9083)
|
||||
* [RFC 9224: Finding the Authoritative Registration Data (RDAP)
|
||||
Service](https://tools.ietf.org/html/rfc9224)
|
||||
|
||||
Some gaps in the implementation remain, but most of the functionality is
|
||||
currently available. If you access this endpoint on a running Nomulus system:
|
||||
If you access this endpoint on a running Nomulus system:
|
||||
|
||||
`https://{PROJECT-ID}.appspot.com/rdap/domains?name=ex*`
|
||||
|
||||
@@ -97,5 +96,4 @@ can specify a TLD after the domain name wildcard (e.g. abc*.tld), and you can
|
||||
specify .domain.tld after the nameserver wildcard (e.g. ns*.domain.tld). But you
|
||||
can't do anything else, like searching for nameservers with ns*.tld. When using
|
||||
a wildcard, we currently require a prefix of at least two characters, to avoid
|
||||
having someone search for *. There are other limitations to the system which we
|
||||
plan to address in the future.
|
||||
having someone search for *.
|
||||
|
||||
@@ -113,6 +113,8 @@ public class ProxyConfig {
|
||||
public int stackdriverMaxQps;
|
||||
public int stackdriverMaxPointsPerRequest;
|
||||
public int writeIntervalSeconds;
|
||||
public double frontendMetricsRatio;
|
||||
public double backendMetricsRatio;
|
||||
}
|
||||
|
||||
/** Configuration options that apply to quota management. */
|
||||
|
||||
@@ -61,6 +61,7 @@ import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
@@ -395,6 +396,26 @@ public class ProxyModule {
|
||||
return Duration.ofSeconds(config.serverCertificateCacheSeconds);
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@Named("frontendMetricsRatio")
|
||||
static double provideFrontendMetricsRatio(ProxyConfig config) {
|
||||
return config.metrics.frontendMetricsRatio;
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@Named("backendMetricsRatio")
|
||||
static double provideBackendMetricsRatio(ProxyConfig config) {
|
||||
return config.metrics.backendMetricsRatio;
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
static Random provideRandom() {
|
||||
return new Random();
|
||||
}
|
||||
|
||||
/** Root level component that exposes the port-to-protocol map. */
|
||||
@Singleton
|
||||
@Component(
|
||||
|
||||
@@ -200,3 +200,15 @@ metrics:
|
||||
|
||||
# How often metrics are written.
|
||||
writeIntervalSeconds: 60
|
||||
|
||||
# What ratio of frontend request metrics should be stochastically recorded
|
||||
# (0.0 means none, 1.0 means all). This is useful for reducing metrics volume,
|
||||
# and thus cost, while still recording some information for performance
|
||||
# monitoring purposes.
|
||||
frontendMetricsRatio: 1.0
|
||||
|
||||
# What ratio of backend request metrics should be stochastically recorded
|
||||
# (0.0 means none, 1.0 means all). This is useful for reducing metrics volume,
|
||||
# and thus cost, while still recording some information for performance
|
||||
# monitoring purposes.
|
||||
backendMetricsRatio: 1.0
|
||||
|
||||
@@ -22,7 +22,9 @@ import com.google.monitoring.metrics.MetricRegistryImpl;
|
||||
import google.registry.util.NonFinalForTesting;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
import jakarta.inject.Singleton;
|
||||
import java.util.Random;
|
||||
import org.joda.time.Duration;
|
||||
|
||||
/** Backend metrics instrumentation. */
|
||||
@@ -75,8 +77,14 @@ public class BackendMetrics extends BaseMetrics {
|
||||
LABELS,
|
||||
DEFAULT_LATENCY_FITTER);
|
||||
|
||||
private final Random random;
|
||||
private final double backendMetricsRatio;
|
||||
|
||||
@Inject
|
||||
BackendMetrics() {}
|
||||
BackendMetrics(@Named("backendMetricsRatio") double backendMetricsRatio, Random random) {
|
||||
this.backendMetricsRatio = backendMetricsRatio;
|
||||
this.random = random;
|
||||
}
|
||||
|
||||
@Override
|
||||
void resetMetrics() {
|
||||
@@ -89,15 +97,45 @@ public class BackendMetrics extends BaseMetrics {
|
||||
|
||||
@NonFinalForTesting
|
||||
public void requestSent(String protocol, String certHash, int bytes) {
|
||||
requestsCounter.increment(protocol, certHash);
|
||||
// Short-circuit metrics recording randomly according to the configured ratio.
|
||||
if (random.nextDouble() > backendMetricsRatio) {
|
||||
return;
|
||||
}
|
||||
requestsCounter.incrementBy(roundRatioReciprocal(), protocol, certHash);
|
||||
requestBytes.record(bytes, protocol, certHash);
|
||||
}
|
||||
|
||||
@NonFinalForTesting
|
||||
public void responseReceived(
|
||||
String protocol, String certHash, FullHttpResponse response, Duration latency) {
|
||||
// Short-circuit metrics recording randomly according to the configured ratio.
|
||||
if (random.nextDouble() > backendMetricsRatio) {
|
||||
return;
|
||||
}
|
||||
latencyMs.record(latency.getMillis(), protocol, certHash);
|
||||
responseBytes.record(response.content().readableBytes(), protocol, certHash);
|
||||
responsesCounter.increment(protocol, certHash, response.status().toString());
|
||||
responsesCounter.incrementBy(
|
||||
roundRatioReciprocal(), protocol, certHash, response.status().toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the reciprocal of the backend metrics ratio, stochastically rounded to the nearest int.
|
||||
*
|
||||
* <p>This is necessary because if we are only going to record a metric, say, 1/20th of the time,
|
||||
* then each time we do record it, we should increment it by 20 so that, modulo some randomness,
|
||||
* the total figures still add up to the same amount.
|
||||
*
|
||||
* <p>The stochastic rounding is necessary to prevent introducing errors stemming from rounding a
|
||||
* non-integer reciprocal consistently to the floor or ceiling. As an example, if the ratio is
|
||||
* .03, then the reciprocal would be 33.3..., so two-thirds of the time it should increment by 33
|
||||
* and one-third of the time it should increment by 34, calculated randomly, so that the overall
|
||||
* total adds up correctly.
|
||||
*/
|
||||
private long roundRatioReciprocal() {
|
||||
double reciprocal = 1 / backendMetricsRatio;
|
||||
return (long)
|
||||
((random.nextDouble() < reciprocal - Math.floor(reciprocal))
|
||||
? Math.ceil(reciprocal)
|
||||
: Math.floor(reciprocal));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,8 +26,10 @@ import io.netty.channel.group.ChannelGroup;
|
||||
import io.netty.channel.group.DefaultChannelGroup;
|
||||
import io.netty.util.concurrent.GlobalEventExecutor;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
import jakarta.inject.Singleton;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import org.joda.time.Duration;
|
||||
@@ -78,8 +80,14 @@ public class FrontendMetrics extends BaseMetrics {
|
||||
LABELS,
|
||||
DEFAULT_LATENCY_FITTER);
|
||||
|
||||
private final Random random;
|
||||
private final double frontendMetricsRatio;
|
||||
|
||||
@Inject
|
||||
public FrontendMetrics() {}
|
||||
FrontendMetrics(@Named("frontendMetricsRatio") double frontendMetricsRatio, Random random) {
|
||||
this.frontendMetricsRatio = frontendMetricsRatio;
|
||||
this.random = random;
|
||||
}
|
||||
|
||||
@Override
|
||||
void resetMetrics() {
|
||||
@@ -109,6 +117,10 @@ public class FrontendMetrics extends BaseMetrics {
|
||||
|
||||
@NonFinalForTesting
|
||||
public void responseSent(String protocol, String certHash, Duration latency) {
|
||||
// Short-circuit metrics recording randomly according to the configured ratio.
|
||||
if (random.nextDouble() > frontendMetricsRatio) {
|
||||
return;
|
||||
}
|
||||
latencyMs.record(latency.getMillis(), protocol, certHash);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ import jakarta.inject.Named;
|
||||
import jakarta.inject.Provider;
|
||||
import jakarta.inject.Singleton;
|
||||
import java.time.Duration;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
@@ -312,6 +313,26 @@ public abstract class ProtocolModuleTest {
|
||||
return Duration.ofHours(1);
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@Named("frontendMetricsRatio")
|
||||
static double provideFrontendMetricsRatio() {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@Named("backendMetricsRatio")
|
||||
static double providebackendMetricsRatio() {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
static Random provideRandom() {
|
||||
return new Random();
|
||||
}
|
||||
|
||||
// This method is only here to satisfy Dagger binding, but is never used. In test environment,
|
||||
// it is the self-signed certificate and its key that ends up being used.
|
||||
@Singleton
|
||||
|
||||
@@ -18,11 +18,14 @@ import static com.google.monitoring.metrics.contrib.DistributionMetricSubject.as
|
||||
import static com.google.monitoring.metrics.contrib.LongMetricSubject.assertThat;
|
||||
import static google.registry.proxy.TestUtils.makeHttpPostRequest;
|
||||
import static google.registry.proxy.TestUtils.makeHttpResponse;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import java.util.Random;
|
||||
import org.joda.time.Duration;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -34,10 +37,11 @@ class BackendMetricsTest {
|
||||
private final String certHash = "blah12345";
|
||||
private final String protocol = "frontend protocol";
|
||||
|
||||
private final BackendMetrics metrics = new BackendMetrics();
|
||||
private BackendMetrics metrics;
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
metrics = new BackendMetrics(1.0, new Random());
|
||||
metrics.resetMetrics();
|
||||
}
|
||||
|
||||
@@ -107,22 +111,37 @@ class BackendMetricsTest {
|
||||
|
||||
@Test
|
||||
void testSuccess_multipleResponses() {
|
||||
Random mockRandom = mock(Random.class);
|
||||
metrics = new BackendMetrics(0.3, mockRandom);
|
||||
// The reciprocal of this metrics ratio is 3.3..., so depending on stochastic rounding each
|
||||
// response will be recorded as either 3 or 4 (which we hard-code in test by mocking the RNG).
|
||||
when(mockRandom.nextDouble())
|
||||
.thenReturn(
|
||||
/* record response1 */ .1,
|
||||
/* ... as 3 */ .5,
|
||||
/* record response2 */ .04,
|
||||
/* ... as 4 */ .2,
|
||||
/* don't record response3 (skips stochastic rounding) */ .5,
|
||||
/* record response4 */ .15,
|
||||
/* ... as 3 */ .5);
|
||||
String content1 = "some response";
|
||||
String content2 = "other response";
|
||||
String content3 = "a very bad response";
|
||||
FullHttpResponse response1 = makeHttpResponse(content1, HttpResponseStatus.OK);
|
||||
FullHttpResponse response2 = makeHttpResponse(content2, HttpResponseStatus.OK);
|
||||
FullHttpResponse response3 = makeHttpResponse(content3, HttpResponseStatus.BAD_REQUEST);
|
||||
FullHttpResponse response3 = makeHttpResponse(content2, HttpResponseStatus.OK);
|
||||
FullHttpResponse response4 = makeHttpResponse(content3, HttpResponseStatus.BAD_REQUEST);
|
||||
metrics.responseReceived(protocol, certHash, response1, Duration.millis(5));
|
||||
metrics.responseReceived(protocol, certHash, response2, Duration.millis(8));
|
||||
metrics.responseReceived(protocol, certHash, response3, Duration.millis(2));
|
||||
metrics.responseReceived(protocol, certHash, response3, Duration.millis(15));
|
||||
metrics.responseReceived(protocol, certHash, response4, Duration.millis(2));
|
||||
|
||||
assertThat(BackendMetrics.requestsCounter).hasNoOtherValues();
|
||||
assertThat(BackendMetrics.requestBytes).hasNoOtherValues();
|
||||
assertThat(BackendMetrics.responsesCounter)
|
||||
.hasValueForLabels(2, protocol, certHash, "200 OK")
|
||||
.hasValueForLabels(7, protocol, certHash, "200 OK")
|
||||
.and()
|
||||
.hasValueForLabels(1, protocol, certHash, "400 Bad Request")
|
||||
.hasValueForLabels(3, protocol, certHash, "400 Bad Request")
|
||||
.and()
|
||||
.hasNoOtherValues();
|
||||
assertThat(BackendMetrics.responseBytes)
|
||||
|
||||
@@ -15,11 +15,17 @@
|
||||
package google.registry.proxy.metric;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static com.google.monitoring.metrics.contrib.DistributionMetricSubject.assertThat;
|
||||
import static com.google.monitoring.metrics.contrib.LongMetricSubject.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.DefaultChannelId;
|
||||
import io.netty.channel.embedded.EmbeddedChannel;
|
||||
import java.util.Random;
|
||||
import org.joda.time.Duration;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -28,10 +34,11 @@ class FrontendMetricsTest {
|
||||
|
||||
private static final String PROTOCOL = "some protocol";
|
||||
private static final String CERT_HASH = "abc_blah_1134zdf";
|
||||
private final FrontendMetrics metrics = new FrontendMetrics();
|
||||
private FrontendMetrics metrics;
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
metrics = new FrontendMetrics(1.0, new Random());
|
||||
metrics.resetMetrics();
|
||||
}
|
||||
|
||||
@@ -60,8 +67,13 @@ class FrontendMetricsTest {
|
||||
|
||||
@Test
|
||||
void testSuccess_twoConnections_sameClient() {
|
||||
Random mockRandom = mock(Random.class);
|
||||
metrics = new FrontendMetrics(0.2, mockRandom);
|
||||
// The third response won't be logged.
|
||||
when(mockRandom.nextDouble()).thenReturn(.1, .04, .5);
|
||||
EmbeddedChannel channel1 = new EmbeddedChannel();
|
||||
EmbeddedChannel channel2 = new EmbeddedChannel(DefaultChannelId.newInstance());
|
||||
EmbeddedChannel channel3 = new EmbeddedChannel();
|
||||
|
||||
metrics.registerActiveConnection(PROTOCOL, CERT_HASH, channel1);
|
||||
assertThat(channel1.isActive()).isTrue();
|
||||
@@ -85,6 +97,27 @@ class FrontendMetricsTest {
|
||||
.and()
|
||||
.hasNoOtherValues();
|
||||
|
||||
metrics.responseSent(PROTOCOL, CERT_HASH, Duration.millis(10));
|
||||
metrics.responseSent(PROTOCOL, CERT_HASH, Duration.millis(8));
|
||||
metrics.responseSent(PROTOCOL, CERT_HASH, Duration.millis(13));
|
||||
|
||||
metrics.registerActiveConnection(PROTOCOL, CERT_HASH, channel3);
|
||||
assertThat(channel3.isActive()).isTrue();
|
||||
assertThat(FrontendMetrics.activeConnectionsGauge)
|
||||
.hasValueForLabels(2, PROTOCOL, CERT_HASH)
|
||||
.and()
|
||||
.hasNoOtherValues();
|
||||
// All connection counts are recorded as metrics, but ...
|
||||
assertThat(FrontendMetrics.totalConnectionsCounter)
|
||||
.hasValueForLabels(3, PROTOCOL, CERT_HASH)
|
||||
.and()
|
||||
.hasNoOtherValues();
|
||||
// Latency stats are subject to the metrics ratio.
|
||||
assertThat(FrontendMetrics.latencyMs)
|
||||
.hasDataSetForLabels(ImmutableSet.of(10, 8), PROTOCOL, CERT_HASH)
|
||||
.and()
|
||||
.hasNoOtherValues();
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
ChannelFuture unusedFuture1 = channel1.close();
|
||||
assertThat(channel1.isActive()).isFalse();
|
||||
@@ -93,7 +126,7 @@ class FrontendMetricsTest {
|
||||
.and()
|
||||
.hasNoOtherValues();
|
||||
assertThat(FrontendMetrics.totalConnectionsCounter)
|
||||
.hasValueForLabels(2, PROTOCOL, CERT_HASH)
|
||||
.hasValueForLabels(3, PROTOCOL, CERT_HASH)
|
||||
.and()
|
||||
.hasNoOtherValues();
|
||||
|
||||
@@ -102,7 +135,16 @@ class FrontendMetricsTest {
|
||||
assertThat(channel2.isActive()).isFalse();
|
||||
assertThat(FrontendMetrics.activeConnectionsGauge).hasNoOtherValues();
|
||||
assertThat(FrontendMetrics.totalConnectionsCounter)
|
||||
.hasValueForLabels(2, PROTOCOL, CERT_HASH)
|
||||
.hasValueForLabels(3, PROTOCOL, CERT_HASH)
|
||||
.and()
|
||||
.hasNoOtherValues();
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
ChannelFuture unusedFuture3 = channel3.close();
|
||||
assertThat(channel3.isActive()).isFalse();
|
||||
assertThat(FrontendMetrics.activeConnectionsGauge).hasNoOtherValues();
|
||||
assertThat(FrontendMetrics.totalConnectionsCounter)
|
||||
.hasValueForLabels(3, PROTOCOL, CERT_HASH)
|
||||
.and()
|
||||
.hasNoOtherValues();
|
||||
}
|
||||
|
||||
@@ -196,6 +196,7 @@ artifacts:
|
||||
- 'release/cloudbuild-sync-and-tag.yaml'
|
||||
- 'release/cloudbuild-deploy-*.yaml'
|
||||
- 'release/cloudbuild-delete-*.yaml'
|
||||
- 'release/cloudbuild-renew-prober-certs-*.yaml'
|
||||
- 'release/cloudbuild-schema-deploy-*.yaml'
|
||||
- 'release/cloudbuild-schema-verify-*.yaml'
|
||||
- 'release/cloudbuild-restart-proxies-*.yaml'
|
||||
|
||||
@@ -166,6 +166,8 @@ steps:
|
||||
> release/cloudbuild-schema-deploy-${environment}.yaml
|
||||
sed s/'$${_ENV}'/${environment}/g release/cloudbuild-schema-verify.yaml \
|
||||
> release/cloudbuild-schema-verify-${environment}.yaml
|
||||
sed s/'$${_ENV}'/${environment}/g release/cloudbuild-renew-prober-certs.yaml \
|
||||
> release/cloudbuild-renew-prober-certs-${environment}.yaml
|
||||
done
|
||||
# Do text replacement in the k8s manifests.
|
||||
- name: 'gcr.io/cloud-builders/gcloud'
|
||||
|
||||
Reference in New Issue
Block a user