mirror of
https://github.com/google/nomulus.git
synced 2025-05-15 17:07:15 +02:00
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:
parent
067756722d
commit
94a2681127
3 changed files with 84 additions and 20 deletions
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
Loading…
Add table
Add a link
Reference in a new issue