Send deletion poll messages when requested by superuser

Otherwise, registrars will never receive a notification through EPP that a
domain has been synchronously deleted by us.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=234172289
This commit is contained in:
mcilwain 2019-02-15 10:44:52 -08:00 committed by jianglai
parent 067756722d
commit 94a2681127
3 changed files with 84 additions and 20 deletions

View file

@ -15,6 +15,7 @@
package google.registry.flows.domain; package google.registry.flows.domain;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Strings.isNullOrEmpty;
import static google.registry.flows.FlowUtils.persistEntityChanges; import static google.registry.flows.FlowUtils.persistEntityChanges;
import static google.registry.flows.FlowUtils.validateClientIsLoggedIn; import static google.registry.flows.FlowUtils.validateClientIsLoggedIn;
import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence; import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
@ -81,7 +82,6 @@ import google.registry.model.eppinput.EppInput;
import google.registry.model.eppoutput.EppResponse; import google.registry.model.eppoutput.EppResponse;
import google.registry.model.poll.PendingActionNotificationResponse.DomainPendingActionNotificationResponse; import google.registry.model.poll.PendingActionNotificationResponse.DomainPendingActionNotificationResponse;
import google.registry.model.poll.PollMessage; import google.registry.model.poll.PollMessage;
import google.registry.model.poll.PollMessage.OneTime;
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;
import google.registry.model.reporting.DomainTransactionRecord; import google.registry.model.reporting.DomainTransactionRecord;
@ -177,16 +177,13 @@ public final class DomainDeleteFlow implements TransactionalFlow {
? Duration.ZERO ? Duration.ZERO
// By default, this should be 30 days of grace, and 5 days of pending delete. // By default, this should be 30 days of grace, and 5 days of pending delete.
: redemptionGracePeriodLength.plus(pendingDeleteLength); : redemptionGracePeriodLength.plus(pendingDeleteLength);
HistoryEntry historyEntry = buildHistoryEntry( HistoryEntry historyEntry =
existingDomain, registry, now, durationUntilDelete, inAddGracePeriod); buildHistoryEntry(existingDomain, registry, now, durationUntilDelete, inAddGracePeriod);
DateTime deletionTime = now.plus(durationUntilDelete);
if (durationUntilDelete.equals(Duration.ZERO)) { if (durationUntilDelete.equals(Duration.ZERO)) {
builder.setDeletionTime(now).setStatusValues(null); builder.setDeletionTime(now).setStatusValues(null);
} else { } else {
DateTime deletionTime = now.plus(durationUntilDelete);
DateTime redemptionTime = now.plus(redemptionGracePeriodLength); DateTime redemptionTime = now.plus(redemptionGracePeriodLength);
PollMessage.OneTime deletePollMessage =
createDeletePollMessage(existingDomain, historyEntry, deletionTime);
entitiesToSave.add(deletePollMessage);
asyncTaskEnqueuer.enqueueAsyncResave( asyncTaskEnqueuer.enqueueAsyncResave(
existingDomain, now, ImmutableSortedSet.of(redemptionTime, deletionTime)); existingDomain, now, ImmutableSortedSet.of(redemptionTime, deletionTime));
builder.setDeletionTime(deletionTime) builder.setDeletionTime(deletionTime)
@ -196,13 +193,23 @@ public final class DomainDeleteFlow implements TransactionalFlow {
.setGracePeriods(ImmutableSet.of(GracePeriod.createWithoutBillingEvent( .setGracePeriods(ImmutableSet.of(GracePeriod.createWithoutBillingEvent(
GracePeriodStatus.REDEMPTION, GracePeriodStatus.REDEMPTION,
redemptionTime, redemptionTime,
clientId))) clientId)));
.setDeletePollMessage(Key.create(deletePollMessage));
// Note: The expiration time is unchanged, so if it's before the new deletion time, there will // Note: The expiration time is unchanged, so if it's before the new deletion time, there will
// be a "phantom autorenew" where the expiration time advances but no billing event or poll // be a "phantom autorenew" where the expiration time advances but no billing event or poll
// message are produced (since we are ending the autorenew recurrences at "now" below). For // message are produced (since we are ending the autorenew recurrences at "now" below). For
// now at least this is working as intended. // now at least this is working as intended.
} }
// 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
// it is synchronous).
if (!durationUntilDelete.equals(Duration.ZERO) || isSuperuser) {
PollMessage.OneTime deletePollMessage =
createDeletePollMessage(existingDomain, historyEntry, deletionTime);
entitiesToSave.add(deletePollMessage);
builder.setDeletePollMessage(Key.create(deletePollMessage));
}
DomainBase newDomain = builder.build(); DomainBase newDomain = builder.build();
updateForeignKeyIndexDeletionTime(newDomain); updateForeignKeyIndexDeletionTime(newDomain);
handlePendingTransferOnDelete(existingDomain, newDomain, now, historyEntry); handlePendingTransferOnDelete(existingDomain, newDomain, now, historyEntry);
@ -296,15 +303,26 @@ public final class DomainDeleteFlow implements TransactionalFlow {
.build(); .build();
} }
private OneTime createDeletePollMessage( private PollMessage.OneTime createDeletePollMessage(
DomainBase existingResource, HistoryEntry historyEntry, DateTime deletionTime) { DomainBase existingDomain, HistoryEntry historyEntry, DateTime deletionTime) {
Optional<MetadataExtension> metadataExtension =
eppInput.getSingleExtension(MetadataExtension.class);
boolean hasMetadataMessage =
metadataExtension.isPresent() && !isNullOrEmpty(metadataExtension.get().getReason());
String message =
isSuperuser
? (hasMetadataMessage
? metadataExtension.get().getReason()
: "Deleted by registry administrator.")
: "Domain deleted.";
return new PollMessage.OneTime.Builder() return new PollMessage.OneTime.Builder()
.setClientId(existingResource.getCurrentSponsorClientId()) .setClientId(existingDomain.getCurrentSponsorClientId())
.setEventTime(deletionTime) .setEventTime(deletionTime)
.setMsg("Domain deleted.") .setMsg(message)
.setResponseData(ImmutableList.of( .setResponseData(
DomainPendingActionNotificationResponse.create( ImmutableList.of(
existingResource.getFullyQualifiedDomainName(), true, trid, deletionTime))) DomainPendingActionNotificationResponse.create(
existingDomain.getFullyQualifiedDomainName(), true, trid, deletionTime)))
.setParent(historyEntry) .setParent(historyEntry)
.build(); .build();
} }

View file

@ -406,8 +406,6 @@ public class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow,
.plus(Registry.get("tld").getRedemptionGracePeriodLength()) .plus(Registry.get("tld").getRedemptionGracePeriodLength())
.plus(Registry.get("tld").getPendingDeleteLength())) .plus(Registry.get("tld").getPendingDeleteLength()))
.and() .and()
.hasDeletePollMessage()
.and()
.hasExactlyStatusValues(StatusValue.INACTIVE, StatusValue.PENDING_DELETE) .hasExactlyStatusValues(StatusValue.INACTIVE, StatusValue.PENDING_DELETE)
.and() .and()
.hasOneHistoryEntryEachOfTypes(DOMAIN_CREATE, DOMAIN_DELETE); .hasOneHistoryEntryEachOfTypes(DOMAIN_CREATE, DOMAIN_DELETE);
@ -427,13 +425,20 @@ public class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow,
clock.nowUtc().plus(Registry.get("tld").getRedemptionGracePeriodLength()), clock.nowUtc().plus(Registry.get("tld").getRedemptionGracePeriodLength()),
"TheRegistrar", "TheRegistrar",
null)); null));
assertDeletionPollMessageFor(resource, "Domain deleted.");
}
private void assertDeletionPollMessageFor(DomainBase domain, String expectedMessage) {
// There should be a future poll message at the deletion time. The previous autorenew poll // There should be a future poll message at the deletion time. The previous autorenew poll
// message should now be deleted. // message should now be deleted.
DateTime deletionTime = resource.getDeletionTime(); assertAboutDomains().that(domain).hasDeletePollMessage();
DateTime deletionTime = domain.getDeletionTime();
assertThat(getPollMessages("TheRegistrar", deletionTime.minusMinutes(1))).isEmpty(); assertThat(getPollMessages("TheRegistrar", deletionTime.minusMinutes(1))).isEmpty();
assertThat(getPollMessages("TheRegistrar", deletionTime)).hasSize(1); assertThat(getPollMessages("TheRegistrar", deletionTime)).hasSize(1);
assertThat(resource.getDeletePollMessage()) assertThat(domain.getDeletePollMessage())
.isEqualTo(Key.create(getOnlyPollMessage("TheRegistrar"))); .isEqualTo(Key.create(getOnlyPollMessage("TheRegistrar")));
PollMessage.OneTime deletePollMessage = ofy().load().key(domain.getDeletePollMessage()).now();
assertThat(deletePollMessage.getMsg()).isEqualTo(expectedMessage);
} }
@Test @Test
@ -1069,6 +1074,7 @@ public class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow,
clock.nowUtc().plus(standardDays(15)), clock.nowUtc().plus(standardDays(15)),
"TheRegistrar", "TheRegistrar",
null)); null));
assertDeletionPollMessageFor(resource, "Deleted by registry administrator.");
} }
@Test @Test
@ -1089,6 +1095,7 @@ public class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow,
.and() .and()
.hasDeletionTime(clock.nowUtc().plus(standardDays(4))); .hasDeletionTime(clock.nowUtc().plus(standardDays(4)));
assertThat(resource.getGracePeriods()).isEmpty(); assertThat(resource.getGracePeriods()).isEmpty();
assertDeletionPollMessageFor(resource, "Deleted by registry administrator.");
} }
@Test @Test
@ -1115,6 +1122,7 @@ public class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow,
clock.nowUtc().plus(standardDays(15)), clock.nowUtc().plus(standardDays(15)),
"TheRegistrar", "TheRegistrar",
null)); null));
assertDeletionPollMessageFor(resource, "Deleted by registry administrator.");
} }
@Test @Test
@ -1128,6 +1136,23 @@ public class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow,
runFlowAssertResponse( runFlowAssertResponse(
CommitMode.LIVE, UserPrivileges.SUPERUSER, loadFile("generic_success_response.xml")); CommitMode.LIVE, UserPrivileges.SUPERUSER, loadFile("generic_success_response.xml"));
assertThat(reloadResourceByForeignKey()).isNull(); assertThat(reloadResourceByForeignKey()).isNull();
DomainBase resavedDomain = ofy().load().entity(domain).now();
assertDeletionPollMessageFor(resavedDomain, "Deleted by registry administrator.");
}
@Test
public void testSuccess_immediateDelete_withSuperuserAndMetadataExtension() throws Exception {
eppRequestSource = EppRequestSource.TOOL;
setEppInput(
"domain_delete_superuser_and_metadata_extension.xml",
ImmutableMap.of("REDEMPTION_GRACE_PERIOD_DAYS", "0", "PENDING_DELETE_DAYS", "0"));
setUpSuccessfulTest();
clock.advanceOneMilli();
runFlowAssertResponse(
CommitMode.LIVE, UserPrivileges.SUPERUSER, loadFile("generic_success_response.xml"));
assertThat(reloadResourceByForeignKey()).isNull();
assertDeletionPollMessageFor(
ofy().load().entity(domain).now(), "Deleted by registry administrator: Broke world.");
} }
@Test @Test

View file

@ -0,0 +1,21 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<delete>
<domain:delete
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>example.tld</domain:name>
</domain:delete>
</delete>
<extension>
<metadata:metadata xmlns:metadata="urn:google:params:xml:ns:metadata-1.0">
<metadata:reason>Deleted by registry administrator: Broke world.</metadata:reason>
<metadata:requestedByRegistrar>false</metadata:requestedByRegistrar>
</metadata:metadata>
<superuser:domainDelete xmlns:superuser="urn:google:params:xml:ns:superuser-1.0">
<superuser:redemptionGracePeriodDays>%REDEMPTION_GRACE_PERIOD_DAYS%</superuser:redemptionGracePeriodDays>
<superuser:pendingDeleteDays>%PENDING_DELETE_DAYS%</superuser:pendingDeleteDays>
</superuser:domainDelete>
</extension>
<clTRID>ABC-12345</clTRID>
</command>
</epp>