Send an immediate poll message for superuser domain deletes (#1096)

* Send an immediate poll message for superuser domain deletes

This poll message is in addition to the normal poll message that is sent when
the domain's deletion is effective (typically 35 days later). It's needed
because, in the event of a superuser deletion, the owning registrar won't
otherwise necessarily know it's happening.

Note that, in the case of a --immediate superuser deletion, the normal poll
message is already being sent immediately, so this additional poll message is
not necessary.
This commit is contained in:
Ben McIlwain 2021-04-20 15:22:49 -04:00 committed by GitHub
parent aac952d6a3
commit 844b5ab713
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 61 additions and 15 deletions

View file

@ -208,13 +208,21 @@ public final class DomainDeleteFlow implements TransactionalFlow {
// Enqueue the deletion poll message if the delete is asynchronous or if requested by a // Enqueue the deletion poll message if the delete is asynchronous or if requested by a
// superuser (i.e. the registrar didn't request this delete and thus should be notified even if // superuser (i.e. the registrar didn't request this delete and thus should be notified even if
// it is synchronous). // it is synchronous).
if (!durationUntilDelete.equals(Duration.ZERO) || isSuperuser) { if (durationUntilDelete.isLongerThan(Duration.ZERO) || isSuperuser) {
PollMessage.OneTime deletePollMessage = PollMessage.OneTime deletePollMessage =
createDeletePollMessage(existingDomain, historyEntry, deletionTime); createDeletePollMessage(existingDomain, historyEntry, deletionTime);
entitiesToSave.add(deletePollMessage); entitiesToSave.add(deletePollMessage);
builder.setDeletePollMessage(deletePollMessage.createVKey()); builder.setDeletePollMessage(deletePollMessage.createVKey());
} }
// Send a second poll message immediately if the domain is being deleted asynchronously by a
// registrar other than the sponsoring registrar (which will necessarily be a superuser).
if (durationUntilDelete.isLongerThan(Duration.ZERO)
&& !clientId.equals(existingDomain.getPersistedCurrentSponsorClientId())) {
entitiesToSave.add(
createImmediateDeletePollMessage(existingDomain, historyEntry, now, deletionTime));
}
// Cancel any grace periods that were still active, and set the expiration time accordingly. // Cancel any grace periods that were still active, and set the expiration time accordingly.
DateTime newExpirationTime = existingDomain.getRegistrationExpirationTime(); DateTime newExpirationTime = existingDomain.getRegistrationExpirationTime();
for (GracePeriod gracePeriod : existingDomain.getGracePeriods()) { for (GracePeriod gracePeriod : existingDomain.getGracePeriods()) {
@ -346,6 +354,19 @@ public final class DomainDeleteFlow implements TransactionalFlow {
.build(); .build();
} }
private PollMessage.OneTime createImmediateDeletePollMessage(
DomainBase existingDomain, HistoryEntry historyEntry, DateTime now, DateTime deletionTime) {
return new PollMessage.OneTime.Builder()
.setClientId(existingDomain.getPersistedCurrentSponsorClientId())
.setEventTime(now)
.setParent(historyEntry)
.setMsg(
String.format(
"Domain %s was deleted by registry administrator with final deletion effective: %s",
existingDomain.getDomainName(), deletionTime))
.build();
}
@Nullable @Nullable
private ImmutableList<FeeTransformResponseExtension> getResponseExtensions( private ImmutableList<FeeTransformResponseExtension> getResponseExtensions(
DomainBase existingDomain, DateTime now) { DomainBase existingDomain, DateTime now) {

View file

@ -59,6 +59,7 @@ import static org.joda.time.Duration.standardDays;
import static org.joda.time.Duration.standardSeconds; import static org.joda.time.Duration.standardSeconds;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap; import com.google.common.collect.ImmutableSortedMap;
@ -87,6 +88,7 @@ import google.registry.model.eppcommon.StatusValue;
import google.registry.model.eppcommon.Trid; import google.registry.model.eppcommon.Trid;
import google.registry.model.host.HostResource; import google.registry.model.host.HostResource;
import google.registry.model.poll.PendingActionNotificationResponse; import google.registry.model.poll.PendingActionNotificationResponse;
import google.registry.model.poll.PendingActionNotificationResponse.DomainPendingActionNotificationResponse;
import google.registry.model.poll.PollMessage; import google.registry.model.poll.PollMessage;
import google.registry.model.registry.Registry; import google.registry.model.registry.Registry;
import google.registry.model.registry.Registry.TldType; import google.registry.model.registry.Registry.TldType;
@ -328,12 +330,12 @@ class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow, Domain
dryRunFlowAssertResponse(loadFile("domain_delete_response_pending.xml")); dryRunFlowAssertResponse(loadFile("domain_delete_response_pending.xml"));
} }
private void doImmediateDeleteTest(GracePeriodStatus gracePeriodStatus, String responseFilename) private void doAddGracePeriodDeleteTest(
throws Exception { GracePeriodStatus gracePeriodStatus, String responseFilename) throws Exception {
doImmediateDeleteTest(gracePeriodStatus, responseFilename, ImmutableMap.of()); doAddGracePeriodDeleteTest(gracePeriodStatus, responseFilename, ImmutableMap.of());
} }
private void doImmediateDeleteTest( private void doAddGracePeriodDeleteTest(
GracePeriodStatus gracePeriodStatus, GracePeriodStatus gracePeriodStatus,
String responseFilename, String responseFilename,
Map<String, String> substitutions) Map<String, String> substitutions)
@ -355,7 +357,7 @@ class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow, Domain
graceBillingEvent, getOnlyHistoryEntryOfType(domain, DOMAIN_DELETE)); graceBillingEvent, getOnlyHistoryEntryOfType(domain, DOMAIN_DELETE));
assertDnsTasksEnqueued("example.tld"); assertDnsTasksEnqueued("example.tld");
// There should be no poll messages. The previous autorenew poll message should now be deleted. // There should be no poll messages. The previous autorenew poll message should now be deleted.
assertThat(getPollMessages("TheRegistrar", A_MONTH_FROM_NOW)).isEmpty(); assertThat(getPollMessages("TheRegistrar")).isEmpty();
} }
@Test @Test
@ -385,25 +387,25 @@ class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow, Domain
@Test @Test
void testSuccess_addGracePeriodResultsInImmediateDelete() throws Exception { void testSuccess_addGracePeriodResultsInImmediateDelete() throws Exception {
sessionMetadata.setServiceExtensionUris(ImmutableSet.of()); sessionMetadata.setServiceExtensionUris(ImmutableSet.of());
doImmediateDeleteTest(GracePeriodStatus.ADD, "generic_success_response.xml"); doAddGracePeriodDeleteTest(GracePeriodStatus.ADD, "generic_success_response.xml");
} }
@Test @Test
void testSuccess_addGracePeriodCredit_v06() throws Exception { void testSuccess_addGracePeriodCredit_v06() throws Exception {
removeServiceExtensionUri(ServiceExtension.FEE_0_11.getUri()); removeServiceExtensionUri(ServiceExtension.FEE_0_11.getUri());
removeServiceExtensionUri(ServiceExtension.FEE_0_12.getUri()); removeServiceExtensionUri(ServiceExtension.FEE_0_12.getUri());
doImmediateDeleteTest(GracePeriodStatus.ADD, "domain_delete_response_fee.xml", FEE_06_MAP); doAddGracePeriodDeleteTest(GracePeriodStatus.ADD, "domain_delete_response_fee.xml", FEE_06_MAP);
} }
@Test @Test
void testSuccess_addGracePeriodCredit_v11() throws Exception { void testSuccess_addGracePeriodCredit_v11() throws Exception {
removeServiceExtensionUri(ServiceExtension.FEE_0_12.getUri()); removeServiceExtensionUri(ServiceExtension.FEE_0_12.getUri());
doImmediateDeleteTest(GracePeriodStatus.ADD, "domain_delete_response_fee.xml", FEE_11_MAP); doAddGracePeriodDeleteTest(GracePeriodStatus.ADD, "domain_delete_response_fee.xml", FEE_11_MAP);
} }
@Test @Test
void testSuccess_addGracePeriodCredit_v12() throws Exception { void testSuccess_addGracePeriodCredit_v12() throws Exception {
doImmediateDeleteTest(GracePeriodStatus.ADD, "domain_delete_response_fee.xml", FEE_12_MAP); doAddGracePeriodDeleteTest(GracePeriodStatus.ADD, "domain_delete_response_fee.xml", FEE_12_MAP);
} }
private void doSuccessfulTest_noAddGracePeriod(String responseFilename) throws Exception { private void doSuccessfulTest_noAddGracePeriod(String responseFilename) throws Exception {
@ -838,6 +840,30 @@ class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow, Domain
clock.advanceOneMilli(); clock.advanceOneMilli();
runFlowAssertResponse( runFlowAssertResponse(
CommitMode.LIVE, UserPrivileges.SUPERUSER, loadFile("domain_delete_response_pending.xml")); CommitMode.LIVE, UserPrivileges.SUPERUSER, loadFile("domain_delete_response_pending.xml"));
HistoryEntry deleteHistoryEntry = getOnlyHistoryEntryOfType(domain, DOMAIN_DELETE);
assertPollMessages(
new PollMessage.OneTime.Builder()
.setClientId("TheRegistrar")
.setParent(deleteHistoryEntry)
.setEventTime(clock.nowUtc())
.setMsg(
"Domain example.tld was deleted by registry administrator with final deletion"
+ " effective: 2000-07-11T22:00:00.013Z")
.build(),
new PollMessage.OneTime.Builder()
.setClientId("TheRegistrar")
.setParent(deleteHistoryEntry)
.setEventTime(DateTime.parse("2000-07-11T22:00:00.013Z"))
.setMsg("Deleted by registry administrator.")
.setResponseData(
ImmutableList.of(
DomainPendingActionNotificationResponse.create(
"example.tld",
true,
deleteHistoryEntry.getTrid(),
DateTime.parse("2000-07-11T22:00:00.013Z"))))
.build());
} }
@Test @Test
@ -1201,6 +1227,7 @@ class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow, Domain
@Test @Test
void testSuccess_immediateDelete_withSuperuserAndMetadataExtension() throws Exception { void testSuccess_immediateDelete_withSuperuserAndMetadataExtension() throws Exception {
sessionMetadata.setClientId("NewRegistrar");
eppRequestSource = EppRequestSource.TOOL; eppRequestSource = EppRequestSource.TOOL;
setEppInput( setEppInput(
"domain_delete_superuser_and_metadata_extension.xml", "domain_delete_superuser_and_metadata_extension.xml",

View file

@ -43,6 +43,7 @@ import static google.registry.util.CollectionUtils.difference;
import static google.registry.util.CollectionUtils.union; import static google.registry.util.CollectionUtils.union;
import static google.registry.util.DateTimeUtils.END_OF_TIME; import static google.registry.util.DateTimeUtils.END_OF_TIME;
import static google.registry.util.DateTimeUtils.START_OF_TIME; import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static google.registry.util.DateTimeUtils.isBeforeOrAt;
import static google.registry.util.DomainNameUtils.ACE_PREFIX_REGEX; import static google.registry.util.DomainNameUtils.ACE_PREFIX_REGEX;
import static google.registry.util.DomainNameUtils.getTldFromDomainName; import static google.registry.util.DomainNameUtils.getTldFromDomainName;
import static google.registry.util.PreconditionsUtils.checkArgumentPresent; import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
@ -919,15 +920,12 @@ public class DatabaseHelper {
.collect(toImmutableList())); .collect(toImmutableList()));
} }
public static ImmutableList<PollMessage> getPollMessages(String clientId, DateTime now) { public static ImmutableList<PollMessage> getPollMessages(String clientId, DateTime beforeOrAt) {
return transactIfJpaTm( return transactIfJpaTm(
() -> () ->
tm().loadAllOf(PollMessage.class).stream() tm().loadAllOf(PollMessage.class).stream()
.filter(pollMessage -> pollMessage.getClientId().equals(clientId)) .filter(pollMessage -> pollMessage.getClientId().equals(clientId))
.filter( .filter(pollMessage -> isBeforeOrAt(pollMessage.getEventTime(), beforeOrAt))
pollMessage ->
pollMessage.getEventTime().isEqual(now)
|| pollMessage.getEventTime().isBefore(now))
.collect(toImmutableList())); .collect(toImmutableList()));
} }