1
0
mirror of https://github.com/google/nomulus synced 2026-04-05 00:59:35 +00:00

Default to skipping optional domain RDAP events (#2995)

We don't need these, so there's no point in adding database load (but we
leave the option to include them in the future). Note that these are,
and were, only ever included for domains so we don't need to worry about
hosts.
This commit is contained in:
gbrodman
2026-04-02 15:12:17 -04:00
committed by GitHub
parent 60e84e72d7
commit 9f3dfec118
8 changed files with 342 additions and 47 deletions

View File

@@ -988,6 +988,19 @@ public final class RegistryConfig {
return 100;
}
/**
* Whether to include optional RDAP history results in domain responses.
*
* <p>The RDAP Response Profile (Feb 2024) section 2.3 specifies that while registration and
* expiration events are required, other types are optional. In an effort to reduce database
* load, we (by default) omit the optional events.
*/
@Provides
@Config("rdapIncludeOptionalHistoryResults")
public static boolean provideRdapIncludeOptionalHistoryResults() {
return false;
}
/**
* Maximum QPS for the Google Cloud Monitoring V3 (aka Stackdriver) API. The QPS limit can be
* adjusted by contacting Cloud Support.

View File

@@ -20,6 +20,8 @@ import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static google.registry.model.EppResourceUtils.isLinked;
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm;
import static google.registry.util.DateTimeUtils.toDateTime;
import static google.registry.util.DateTimeUtils.toInstant;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.google.common.annotations.VisibleForTesting;
@@ -114,6 +116,10 @@ public class RdapJsonFormatter {
@Nullable
String rdapTosStaticUrl;
@Inject
@Config("rdapIncludeOptionalHistoryResults")
boolean rdapIncludeOptionalHistoryResults;
@Inject @RequestServerName String serverName;
@Inject RdapAuthorization rdapAuthorization;
@Inject Clock clock;
@@ -328,9 +334,27 @@ public class RdapJsonFormatter {
.setEventAction(EventAction.LAST_UPDATE_OF_RDAP_DATABASE)
.setEventDate(getRequestTime())
.build());
// RDAP Response Profile section 2.3.2.2:
// "The event of eventAction type last changed MUST be omitted if the domain has not been
// updated since it was created." While it is possible for the domain to be changed out of band
// (i.e. without updating lastEppUpdateTime), that can only happen for domains that have already
// been modified in some way. As a result, we can ignore those cases here.
if (domain.getLastEppUpdateTime() != null
&& domain.getLastEppUpdateTime().isAfter(toInstant(domain.getCreationTime()))) {
// Creates an RDAP event object as defined by RFC 9083
builder
.eventsBuilder()
.add(
Event.builder()
.setEventAction(EventAction.LAST_CHANGED)
.setEventDate(toDateTime(domain.getLastEppUpdateTime()))
.build());
}
// 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));
if (rdapIncludeOptionalHistoryResults) {
builder.eventsBuilder().addAll(makeOptionalEvents(domain));
}
// RDAP Response Profile section 2.4.1:
// The domain object in the RDAP response MUST contain an entity with the Registrar role.
//
@@ -756,14 +780,9 @@ public class RdapJsonFormatter {
* that we don't need to load HistoryEntries for "summary" responses).
*/
private ImmutableList<Event> makeOptionalEvents(EppResource resource) {
ImmutableList.Builder<Event> eventsBuilder = new ImmutableList.Builder<>();
ImmutableMap<EventAction, HistoryTimeAndRegistrar> lastHistoryOfType =
getLastHistoryByType(resource);
ImmutableList.Builder<Event> eventsBuilder = new ImmutableList.Builder<>();
DateTime creationTime = resource.getCreationTime();
DateTime lastChangeTime =
resource.getLastEppUpdateDateTime() == null
? creationTime
: resource.getLastEppUpdateDateTime();
// 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()) {
@@ -772,34 +791,11 @@ public class RdapJsonFormatter {
if (historyTimeAndRegistrar == null) {
continue;
}
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)
if (modificationTime.isBefore(creationTime)) {
continue;
}
eventsBuilder.add(
Event.builder()
.setEventAction(rdapEventAction)
.setEventActor(historyTimeAndRegistrar.registrarId())
.setEventDate(modificationTime)
.build());
// The last change time might not be the lastEppUpdateTime, since some changes happen without
// any EPP update (for example, by the passage of time).
if (modificationTime.isAfter(lastChangeTime) && modificationTime.isBefore(getRequestTime())) {
lastChangeTime = modificationTime;
}
}
// 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)) {
// Creates an RDAP event object as defined by RFC 9083
eventsBuilder.add(
Event.builder()
.setEventAction(EventAction.LAST_CHANGED)
.setEventDate(lastChangeTime)
.setEventDate(historyTimeAndRegistrar.modificationTime())
.build());
}
return eventsBuilder.build();

View File

@@ -143,7 +143,7 @@ class RdapJsonFormatterTest {
makeDomain("cat.みんな", hostIpv4, hostIpv6, registrar)
.asBuilder()
.setCreationTimeForTest(clock.nowUtc().minusMonths(4))
.setLastEppUpdateTime(clock.nowUtc().minusMonths(3))
.setLastEppUpdateTime(clock.nowUtc().minusMonths(1))
.build());
domainNoNameserversNoTransfers =
persistResource(
@@ -307,6 +307,14 @@ class RdapJsonFormatterTest {
.isEqualTo(loadJson("rdapjson_domain_full.json"));
}
@Test
void testDomain_full_withHistory() {
rdapJsonFormatter.rdapIncludeOptionalHistoryResults = true;
assertAboutJson()
.that(rdapJsonFormatter.createRdapDomain(domainFull, OutputDataType.FULL).toJson())
.isEqualTo(loadJson("rdapjson_domain_full_with_history.json"));
}
@Test
void testDomain_summary() {
assertAboutJson()
@@ -333,6 +341,15 @@ class RdapJsonFormatterTest {
.isEqualTo(loadJson("rdapjson_domain_logged_out.json"));
}
@Test
void testDomain_logged_out_withHistory() {
rdapJsonFormatter.rdapAuthorization = RdapAuthorization.PUBLIC_AUTHORIZATION;
rdapJsonFormatter.rdapIncludeOptionalHistoryResults = true;
assertAboutJson()
.that(rdapJsonFormatter.createRdapDomain(domainFull, OutputDataType.FULL).toJson())
.isEqualTo(loadJson("rdapjson_domain_logged_out_with_history.json"));
}
@Test
void testDomain_noNameserversNoTransfers() {
assertAboutJson()

View File

@@ -42,11 +42,6 @@
"eventAction": "last update of RDAP database",
"eventDate": "2000-01-01T00:00:00.000Z"
},
{
"eventAction": "deletion",
"eventActor": "evilregistrar",
"eventDate": "1999-07-01T00:00:00.000Z"
},
{
"eventAction": "last changed",
"eventDate": "2009-05-29T20:13:00.000Z"

View File

@@ -37,11 +37,6 @@
"eventAction": "last update of RDAP database",
"eventDate": "2000-01-01T00:00:00.000Z"
},
{
"eventAction": "transfer",
"eventActor": "unicoderegistrar",
"eventDate": "1999-12-01T00:00:00.000Z"
},
{
"eventAction": "last changed",
"eventDate": "1999-12-01T00:00:00.000Z"

View File

@@ -0,0 +1,144 @@
{
"objectClassName" : "domain",
"handle" : "F-Q9JYB4C",
"ldhName" : "cat.xn--q9jyb4c",
"unicodeName" : "cat.みんな",
"status" :
[
"client delete prohibited",
"client renew prohibited",
"client transfer prohibited",
"server update prohibited"
],
"links" :
[
{
"rel" : "self",
"href" : "https://example.tld/rdap/domain/cat.xn--q9jyb4c",
"type" : "application/rdap+json"
},
{
"rel" : "related",
"href" : "https://rdap.example.com/withSlash/domain/cat.xn--q9jyb4c",
"type" : "application/rdap+json"
}
],
"events": [
{
"eventAction": "registration",
"eventActor": "unicoderegistrar",
"eventDate": "1999-09-01T00:00:00.000Z"
},
{
"eventAction": "expiration",
"eventDate": "2110-10-08T00:44:59.000Z"
},
{
"eventAction": "last update of RDAP database",
"eventDate": "2000-01-01T00:00:00.000Z"
},
{
"eventAction": "last changed",
"eventDate": "1999-12-01T00:00:00.000Z"
},
{
"eventAction": "transfer",
"eventActor": "unicoderegistrar",
"eventDate": "1999-12-01T00:00:00.000Z"
}
],
"nameservers" :
[
{
"objectClassName" : "nameserver",
"handle" : "2-ROID",
"ldhName" : "ns1.cat.xn--q9jyb4c",
"unicodeName" : "ns1.cat.みんな",
"links" : [
{
"rel" : "self",
"href" : "https://example.tld/rdap/nameserver/ns1.cat.xn--q9jyb4c",
"type" : "application/rdap+json"
}
],
"remarks": [
{
"title": "Incomplete Data",
"type": "object truncated due to unexplainable reasons",
"description": ["Summary data only. For complete data, send a specific query for the object."]
}
]
},
{
"objectClassName" : "nameserver",
"handle" : "4-ROID",
"ldhName" : "ns2.cat.xn--q9jyb4c",
"unicodeName" : "ns2.cat.みんな",
"links" : [
{
"rel" : "self",
"href" : "https://example.tld/rdap/nameserver/ns2.cat.xn--q9jyb4c",
"type" : "application/rdap+json"
}
],
"remarks": [
{
"title": "Incomplete Data",
"type": "object truncated due to unexplainable reasons",
"description": ["Summary data only. For complete data, send a specific query for the object."]
}
]
}
],
"secureDNS": {
"delegationSigned": true,
"zoneSigned": true,
"dsData": [{"algorithm":2,"digest":"DEADFACE","digestType":3,"keyTag":1}]
},
"entities" :
[
{
"objectClassName" : "entity",
"handle" : "1",
"roles" : ["registrar"],
"links" :
[
{
"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" :
[
{
"type" : "IANA Registrar ID",
"identifier" : "1"
}
],
"vcardArray" :
[
"vcard",
[
["version", {}, "text", "4.0"],
["fn", {}, "text", "みんな"]
]
],
"remarks": [
{
"title": "Incomplete Data",
"description": [
"Summary data only. For complete data, send a specific query for the object."
],
"type": "object truncated due to unexplainable reasons"
}
]
}
]
}

View File

@@ -37,11 +37,6 @@
"eventAction": "last update of RDAP database",
"eventDate": "2000-01-01T00:00:00.000Z"
},
{
"eventAction": "transfer",
"eventActor": "unicoderegistrar",
"eventDate": "1999-12-01T00:00:00.000Z"
},
{
"eventAction": "last changed",
"eventDate": "1999-12-01T00:00:00.000Z"

View File

@@ -0,0 +1,140 @@
{
"objectClassName": "domain",
"handle": "F-Q9JYB4C",
"ldhName": "cat.xn--q9jyb4c",
"unicodeName": "cat.みんな",
"status":
[
"client delete prohibited",
"client renew prohibited",
"client transfer prohibited",
"server update prohibited"
],
"links":
[
{
"rel": "self",
"href": "https://example.tld/rdap/domain/cat.xn--q9jyb4c",
"type": "application/rdap+json"
},
{
"href": "https://rdap.example.com/withSlash/domain/cat.xn--q9jyb4c",
"type": "application/rdap+json",
"rel": "related"
}
],
"events": [
{
"eventAction": "registration",
"eventActor": "unicoderegistrar",
"eventDate": "1999-09-01T00:00:00.000Z"
},
{
"eventAction": "expiration",
"eventDate": "2110-10-08T00:44:59.000Z"
},
{
"eventAction": "last update of RDAP database",
"eventDate": "2000-01-01T00:00:00.000Z"
},
{
"eventAction": "last changed",
"eventDate": "1999-12-01T00:00:00.000Z"
},
{
"eventAction": "transfer",
"eventActor": "unicoderegistrar",
"eventDate": "1999-12-01T00:00:00.000Z"
}
],
"nameservers": [
{
"objectClassName": "nameserver",
"handle": "2-ROID",
"ldhName": "ns1.cat.xn--q9jyb4c",
"unicodeName": "ns1.cat.みんな",
"links": [
{
"rel": "self",
"href": "https://example.tld/rdap/nameserver/ns1.cat.xn--q9jyb4c",
"type": "application/rdap+json"
}
],
"remarks": [
{
"title": "Incomplete Data",
"type": "object truncated due to unexplainable reasons",
"description": ["Summary data only. For complete data, send a specific query for the object."]
}
]
},
{
"objectClassName": "nameserver",
"handle": "4-ROID",
"ldhName": "ns2.cat.xn--q9jyb4c",
"unicodeName": "ns2.cat.みんな",
"links": [
{
"rel": "self",
"href": "https://example.tld/rdap/nameserver/ns2.cat.xn--q9jyb4c",
"type": "application/rdap+json"
}
],
"remarks": [
{
"title": "Incomplete Data",
"type": "object truncated due to unexplainable reasons",
"description": ["Summary data only. For complete data, send a specific query for the object."]
}
]
}
],
"secureDNS": {
"delegationSigned": true,
"dsData": [{"algorithm":2,"digest":"DEADFACE","digestType":3,"keyTag":1}],
"zoneSigned": true
},
"entities": [
{
"objectClassName": "entity",
"handle": "1",
"roles": ["registrar"],
"links": [
{
"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": [
{
"type": "IANA Registrar ID",
"identifier": "1"
}
],
"vcardArray":
[
"vcard",
[
["version", {}, "text", "4.0"],
["fn", {}, "text", "みんな"]
]
],
"remarks": [
{
"title": "Incomplete Data",
"description": [
"Summary data only. For complete data, send a specific query for the object."
],
"type": "object truncated due to unexplainable reasons"
}
]
}
]
}