Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions core/src/main/java/google/registry/config/RegistryConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -988,6 +988,19 @@ public static int provideRdapResultSetMaxSize() {
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.
Expand Down
58 changes: 27 additions & 31 deletions core/src/main/java/google/registry/rdap/RdapJsonFormatter.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
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;
Expand Down Expand Up @@ -114,6 +116,10 @@ record HistoryTimeAndRegistrar(DateTime modificationTime, String registrarId) {}
@Nullable
String rdapTosStaticUrl;

@Inject
@Config("rdapIncludeOptionalHistoryResults")
boolean rdapIncludeOptionalHistoryResults;

@Inject @RequestServerName String serverName;
@Inject RdapAuthorization rdapAuthorization;
@Inject Clock clock;
Expand Down Expand Up @@ -328,9 +334,27 @@ RdapDomain createRdapDomain(Domain domain, OutputDataType outputDataType) {
.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.
//
Expand Down Expand Up @@ -756,14 +780,9 @@ private static ImmutableMap<EventAction, HistoryTimeAndRegistrar> getLastHistory
* 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()) {
Expand All @@ -772,34 +791,11 @@ private ImmutableList<Event> makeOptionalEvents(EppResource resource) {
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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ void beforeEach() {
makeDomain("cat.みんな", hostIpv4, hostIpv6, registrar)
.asBuilder()
.setCreationTimeForTest(clock.nowUtc().minusMonths(4))
.setLastEppUpdateTime(clock.nowUtc().minusMonths(3))
.setLastEppUpdateTime(clock.nowUtc().minusMonths(1))
.build());
domainNoNameserversNoTransfers =
persistResource(
Expand Down Expand Up @@ -307,6 +307,14 @@ void testDomain_full() {
.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()
Expand All @@ -333,6 +341,15 @@ void testDomain_logged_out() {
.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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
@@ -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"
}
]
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Loading
Loading