From 15e54f280334d82ce90c405071d56d9cfde6b8e3 Mon Sep 17 00:00:00 2001 From: guyben Date: Mon, 8 Apr 2019 07:46:01 -0700 Subject: [PATCH] Show only the last of each event type in RDAP domain response We also ignore events that happened before the domain was created (for example, in a previous incarnation of the same domain name) and we set the last changed event to be the later of the last EPP change and any other event that happened before "now". From RDAP response profile 2.3.2 The domain object in the RDAP response MAY contain the following events: 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. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=242461310 --- .../registry/rdap/RdapJsonFormatter.java | 85 +++++++++++++++---- .../registry/rdap/RdapJsonFormatterTest.java | 43 ++++++++-- .../rdap/testdata/rdap_contact_deleted.json | 4 + .../registry/rdap/testdata/rdap_domain.json | 4 - .../rdap/testdata/rdap_domain_cat2.json | 4 - .../rdap/testdata/rdap_domain_deleted.json | 2 +- .../testdata/rdap_domain_no_contacts.json | 4 - .../rdap_domain_no_contacts_with_remark.json | 4 - .../rdap/testdata/rdap_domain_unicode.json | 4 - ...omain_unicode_no_contacts_with_remark.json | 4 - .../rdap/testdata/rdapjson_domain_full.json | 9 +- .../testdata/rdapjson_domain_logged_out.json | 9 +- .../rdapjson_domain_no_nameservers.json | 4 - 13 files changed, 126 insertions(+), 54 deletions(-) diff --git a/java/google/registry/rdap/RdapJsonFormatter.java b/java/google/registry/rdap/RdapJsonFormatter.java index cf730a49d..7984856af 100644 --- a/java/google/registry/rdap/RdapJsonFormatter.java +++ b/java/google/registry/rdap/RdapJsonFormatter.java @@ -54,6 +54,7 @@ import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.URI; +import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; @@ -63,6 +64,7 @@ import javax.annotation.Nullable; import javax.inject.Inject; import javax.inject.Singleton; import org.joda.time.DateTime; +import org.joda.time.DateTimeComparator; /** * Helper class to create RDAP JSON objects for various registry entities and objects. @@ -1008,32 +1010,85 @@ public class RdapJsonFormatter { * Creates an event list for a domain, host or contact resource. */ private static ImmutableList makeEvents(EppResource resource, DateTime now) { - ImmutableList.Builder eventsBuilder = new ImmutableList.Builder<>(); - for (HistoryEntry historyEntry : ofy().load() - .type(HistoryEntry.class) - .ancestor(resource) - .order("modificationTime")) { - // Only create an event if this is a type we care about. - if (!historyEntryTypeToRdapEventActionMap.containsKey(historyEntry.getType())) { + HashMap lastEntryOfType = Maps.newHashMap(); + // Events (such as transfer, but also create) can appear multiple times. We only want the last + // time they appeared. + // + // We can have multiple create historyEntries if a domain was deleted, and then someone new + // bought it. + // + // From RDAP response profile + // 2.3.2 The domain object in the RDAP response MAY contain the following events: + // 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. + for (HistoryEntry historyEntry : + ofy().load().type(HistoryEntry.class).ancestor(resource).order("modificationTime")) { + RdapEventAction rdapEventAction = + historyEntryTypeToRdapEventActionMap.get(historyEntry.getType()); + // Only save the historyEntries if this is a type we care about. + if (rdapEventAction == null) { continue; } - RdapEventAction eventAction = - historyEntryTypeToRdapEventActionMap.get(historyEntry.getType()); - eventsBuilder.add(makeEvent( - eventAction, historyEntry.getClientId(), historyEntry.getModificationTime())); + lastEntryOfType.put(rdapEventAction, historyEntry); + } + ImmutableList.Builder eventsBuilder = new ImmutableList.Builder<>(); + // There are 2 possibly conflicting values for the creation time - either the + // resource.getCreationTime, or the REGISTRATION event created from a HistoryEntry + // + // We favor the HistoryEntry if it exists, since we show that value as REGISTRATION time in the + // reply, so the reply will be self-consistent. + // + // This is mostly an issue in the tests as in "reality" these two values should be the same. + // + DateTime creationTime = + Optional.ofNullable(lastEntryOfType.get(RdapEventAction.REGISTRATION)) + .map(historyEntry -> historyEntry.getModificationTime()) + .orElse(resource.getCreationTime()); + // TODO(b/129849684) remove this and use the events List defined above once we have Event + // objects + ImmutableList.Builder changeTimesBuilder = new ImmutableList.Builder<>(); + // The order of the elements is stable - it's the order in which the enum elements are defined + // in RdapEventAction + for (RdapEventAction rdapEventAction : RdapEventAction.values()) { + HistoryEntry historyEntry = lastEntryOfType.get(rdapEventAction); + // Check if there was any entry of this type + if (historyEntry == null) { + continue; + } + DateTime modificationTime = historyEntry.getModificationTime(); + // 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(makeEvent(rdapEventAction, historyEntry.getClientId(), modificationTime)); + changeTimesBuilder.add(modificationTime); } if (resource instanceof DomainBase) { DateTime expirationTime = ((DomainBase) resource).getRegistrationExpirationTime(); if (expirationTime != null) { eventsBuilder.add(makeEvent(RdapEventAction.EXPIRATION, null, expirationTime)); + changeTimesBuilder.add(expirationTime); } } - if ((resource.getLastEppUpdateTime() != null) - && resource.getLastEppUpdateTime().isAfter(resource.getCreationTime())) { - eventsBuilder.add(makeEvent( - RdapEventAction.LAST_CHANGED, null, resource.getLastEppUpdateTime())); + if (resource.getLastEppUpdateTime() != null) { + changeTimesBuilder.add(resource.getLastEppUpdateTime()); + } + // The last change time might not be the lastEppUpdateTime, since some changes happen without + // any EPP update (for example, by the passage of time). + DateTime lastChangeTime = + changeTimesBuilder.build().stream() + .filter(changeTime -> changeTime.isBefore(now)) + .max(DateTimeComparator.getInstance()) + .orElse(null); + if (lastChangeTime != null && lastChangeTime.isAfter(creationTime)) { + eventsBuilder.add(makeEvent(RdapEventAction.LAST_CHANGED, null, lastChangeTime)); } eventsBuilder.add(makeEvent(RdapEventAction.LAST_UPDATE_OF_RDAP_DATABASE, null, now)); + // TODO(b/129849684): sort events by their time once we return a list of Events instead of JSON + // objects. return eventsBuilder.build(); } diff --git a/javatests/google/registry/rdap/RdapJsonFormatterTest.java b/javatests/google/registry/rdap/RdapJsonFormatterTest.java index 501c22b7c..7fac6fd1e 100644 --- a/javatests/google/registry/rdap/RdapJsonFormatterTest.java +++ b/javatests/google/registry/rdap/RdapJsonFormatterTest.java @@ -72,7 +72,7 @@ public class RdapJsonFormatterTest { private Registrar registrar; private DomainBase domainBaseFull; - private DomainBase domainBaseNoNameservers; + private DomainBase domainBaseNoNameserversNoTransfers; private HostResource hostResourceIpv4; private HostResource hostResourceIpv6; private HostResource hostResourceBoth; @@ -195,7 +195,7 @@ public class RdapJsonFormatterTest { hostResourceIpv4, hostResourceIpv6, registrar)); - domainBaseNoNameservers = persistResource( + domainBaseNoNameserversNoTransfers = persistResource( makeDomainBase( "fish.みんな", contactResourceRegistrant, @@ -224,14 +224,45 @@ public class RdapJsonFormatterTest { HistoryEntry.Type.DOMAIN_CREATE, Period.create(1, Period.Unit.YEARS), "created", - clock.nowUtc())); + clock.nowUtc().minusMonths(4))); persistResource( makeHistoryEntry( - domainBaseNoNameservers, + domainBaseNoNameserversNoTransfers, HistoryEntry.Type.DOMAIN_CREATE, Period.create(1, Period.Unit.YEARS), "created", clock.nowUtc())); + // We create 3 "transfer approved" entries, to make sure we only save the last one + persistResource( + makeHistoryEntry( + domainBaseFull, + HistoryEntry.Type.DOMAIN_TRANSFER_APPROVE, + null, + null, + clock.nowUtc().minusMonths(3))); + persistResource( + makeHistoryEntry( + domainBaseFull, + HistoryEntry.Type.DOMAIN_TRANSFER_APPROVE, + null, + null, + clock.nowUtc().minusMonths(1))); + persistResource( + makeHistoryEntry( + domainBaseFull, + HistoryEntry.Type.DOMAIN_TRANSFER_APPROVE, + null, + null, + clock.nowUtc().minusMonths(2))); + // We create a "transfer approved" entry for domainBaseNoNameserversNoTransfers that happened + // before the domain was created, to make sure we don't show it + persistResource( + makeHistoryEntry( + domainBaseNoNameserversNoTransfers, + HistoryEntry.Type.DOMAIN_TRANSFER_APPROVE, + null, + null, + clock.nowUtc().minusMonths(3))); } public static ImmutableList makeMoreRegistrarContacts(Registrar registrar) { @@ -553,9 +584,9 @@ public class RdapJsonFormatterTest { } @Test - public void testDomain_noNameservers() { + public void testDomain_noNameserversNoTransfers() { assertThat(rdapJsonFormatter.makeRdapJsonForDomain( - domainBaseNoNameservers, + domainBaseNoNameserversNoTransfers, false, LINK_BASE, WHOIS_SERVER, diff --git a/javatests/google/registry/rdap/testdata/rdap_contact_deleted.json b/javatests/google/registry/rdap/testdata/rdap_contact_deleted.json index 3dba0de55..a477eaea3 100644 --- a/javatests/google/registry/rdap/testdata/rdap_contact_deleted.json +++ b/javatests/google/registry/rdap/testdata/rdap_contact_deleted.json @@ -23,6 +23,10 @@ "eventActor": "foo", "eventDate": "1999-07-01T00:00:00.000Z" }, + { + "eventAction": "last changed", + "eventDate": "1999-07-01T00:00:00.000Z" + }, { "eventAction": "last update of RDAP database", "eventDate": "2000-01-01T00:00:00.000Z" diff --git a/javatests/google/registry/rdap/testdata/rdap_domain.json b/javatests/google/registry/rdap/testdata/rdap_domain.json index f2aa4b6cc..b1310deac 100644 --- a/javatests/google/registry/rdap/testdata/rdap_domain.json +++ b/javatests/google/registry/rdap/testdata/rdap_domain.json @@ -24,10 +24,6 @@ "eventAction": "expiration", "eventDate": "2110-10-08T00:44:59.000Z" }, - { - "eventAction": "last changed", - "eventDate": "2009-05-29T20:13:00.000Z" - }, { "eventAction": "last update of RDAP database", "eventDate": "2000-01-01T00:00:00.000Z" diff --git a/javatests/google/registry/rdap/testdata/rdap_domain_cat2.json b/javatests/google/registry/rdap/testdata/rdap_domain_cat2.json index 5999d4bc0..3bfa1e8fe 100644 --- a/javatests/google/registry/rdap/testdata/rdap_domain_cat2.json +++ b/javatests/google/registry/rdap/testdata/rdap_domain_cat2.json @@ -24,10 +24,6 @@ "eventAction": "expiration", "eventDate": "2110-10-08T00:44:59.000Z" }, - { - "eventAction": "last changed", - "eventDate": "2009-05-29T20:13:00.000Z" - }, { "eventAction": "last update of RDAP database", "eventDate": "2000-01-01T00:00:00.000Z" diff --git a/javatests/google/registry/rdap/testdata/rdap_domain_deleted.json b/javatests/google/registry/rdap/testdata/rdap_domain_deleted.json index 0575d830c..0eec0690c 100644 --- a/javatests/google/registry/rdap/testdata/rdap_domain_deleted.json +++ b/javatests/google/registry/rdap/testdata/rdap_domain_deleted.json @@ -32,7 +32,7 @@ }, { "eventAction": "last changed", - "eventDate": "2009-05-29T20:13:00.000Z" + "eventDate": "1999-07-01T00:00:00.000Z" }, { "eventAction": "last update of RDAP database", diff --git a/javatests/google/registry/rdap/testdata/rdap_domain_no_contacts.json b/javatests/google/registry/rdap/testdata/rdap_domain_no_contacts.json index cfb1346c0..00c0164a4 100644 --- a/javatests/google/registry/rdap/testdata/rdap_domain_no_contacts.json +++ b/javatests/google/registry/rdap/testdata/rdap_domain_no_contacts.json @@ -26,10 +26,6 @@ "eventAction": "expiration", "eventDate": "2110-10-08T00:44:59.000Z" }, - { - "eventAction": "last changed", - "eventDate": "2009-05-29T20:13:00.000Z" - }, { "eventAction": "last update of RDAP database", "eventDate": "2000-01-01T00:00:00.000Z" diff --git a/javatests/google/registry/rdap/testdata/rdap_domain_no_contacts_with_remark.json b/javatests/google/registry/rdap/testdata/rdap_domain_no_contacts_with_remark.json index e9cffacab..5a8018592 100644 --- a/javatests/google/registry/rdap/testdata/rdap_domain_no_contacts_with_remark.json +++ b/javatests/google/registry/rdap/testdata/rdap_domain_no_contacts_with_remark.json @@ -26,10 +26,6 @@ "eventAction": "expiration", "eventDate": "2110-10-08T00:44:59.000Z" }, - { - "eventAction": "last changed", - "eventDate": "2009-05-29T20:13:00.000Z" - }, { "eventAction": "last update of RDAP database", "eventDate": "2000-01-01T00:00:00.000Z" diff --git a/javatests/google/registry/rdap/testdata/rdap_domain_unicode.json b/javatests/google/registry/rdap/testdata/rdap_domain_unicode.json index 1f228a31f..9e796f416 100644 --- a/javatests/google/registry/rdap/testdata/rdap_domain_unicode.json +++ b/javatests/google/registry/rdap/testdata/rdap_domain_unicode.json @@ -25,10 +25,6 @@ "eventAction": "expiration", "eventDate": "2110-10-08T00:44:59.000Z" }, - { - "eventAction": "last changed", - "eventDate": "2009-05-29T20:13:00.000Z" - }, { "eventAction": "last update of RDAP database", "eventDate": "2000-01-01T00:00:00.000Z" diff --git a/javatests/google/registry/rdap/testdata/rdap_domain_unicode_no_contacts_with_remark.json b/javatests/google/registry/rdap/testdata/rdap_domain_unicode_no_contacts_with_remark.json index 188eb77a1..9cc874db4 100644 --- a/javatests/google/registry/rdap/testdata/rdap_domain_unicode_no_contacts_with_remark.json +++ b/javatests/google/registry/rdap/testdata/rdap_domain_unicode_no_contacts_with_remark.json @@ -27,10 +27,6 @@ "eventAction": "expiration", "eventDate": "2110-10-08T00:44:59.000Z" }, - { - "eventAction": "last changed", - "eventDate": "2009-05-29T20:13:00.000Z" - }, { "eventAction": "last update of RDAP database", "eventDate": "2000-01-01T00:00:00.000Z" diff --git a/javatests/google/registry/rdap/testdata/rdapjson_domain_full.json b/javatests/google/registry/rdap/testdata/rdapjson_domain_full.json index 2671ecc6e..c36611577 100644 --- a/javatests/google/registry/rdap/testdata/rdapjson_domain_full.json +++ b/javatests/google/registry/rdap/testdata/rdapjson_domain_full.json @@ -23,7 +23,12 @@ { "eventAction": "registration", "eventActor": "foo", - "eventDate": "2000-01-01T00:00:00.000Z" + "eventDate": "1999-09-01T00:00:00.000Z" + }, + { + "eventAction": "transfer", + "eventActor": "foo", + "eventDate": "1999-12-01T00:00:00.000Z" }, { "eventAction": "expiration", @@ -31,7 +36,7 @@ }, { "eventAction": "last changed", - "eventDate": "2009-05-29T20:13:00.000Z" + "eventDate": "1999-12-01T00:00:00.000Z" }, { "eventAction": "last update of RDAP database", diff --git a/javatests/google/registry/rdap/testdata/rdapjson_domain_logged_out.json b/javatests/google/registry/rdap/testdata/rdapjson_domain_logged_out.json index 1070326ee..af948acf4 100644 --- a/javatests/google/registry/rdap/testdata/rdapjson_domain_logged_out.json +++ b/javatests/google/registry/rdap/testdata/rdapjson_domain_logged_out.json @@ -23,7 +23,12 @@ { "eventAction": "registration", "eventActor": "foo", - "eventDate": "2000-01-01T00:00:00.000Z" + "eventDate": "1999-09-01T00:00:00.000Z" + }, + { + "eventAction": "transfer", + "eventActor": "foo", + "eventDate": "1999-12-01T00:00:00.000Z" }, { "eventAction": "expiration", @@ -31,7 +36,7 @@ }, { "eventAction": "last changed", - "eventDate": "2009-05-29T20:13:00.000Z" + "eventDate": "1999-12-01T00:00:00.000Z" }, { "eventAction": "last update of RDAP database", diff --git a/javatests/google/registry/rdap/testdata/rdapjson_domain_no_nameservers.json b/javatests/google/registry/rdap/testdata/rdapjson_domain_no_nameservers.json index 00651e034..e08e1fd97 100644 --- a/javatests/google/registry/rdap/testdata/rdapjson_domain_no_nameservers.json +++ b/javatests/google/registry/rdap/testdata/rdapjson_domain_no_nameservers.json @@ -30,10 +30,6 @@ "eventAction": "expiration", "eventDate": "2110-10-08T00:44:59.000Z" }, - { - "eventAction": "last changed", - "eventDate": "2009-05-29T20:13:00.000Z" - }, { "eventAction": "last update of RDAP database", "eventDate": "2000-01-01T00:00:00.000Z"