diff --git a/core/src/main/java/google/registry/flows/domain/DomainUpdateFlow.java b/core/src/main/java/google/registry/flows/domain/DomainUpdateFlow.java index 561c88436..07af4c385 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainUpdateFlow.java +++ b/core/src/main/java/google/registry/flows/domain/DomainUpdateFlow.java @@ -16,6 +16,7 @@ package google.registry.flows.domain; import static com.google.common.base.MoreObjects.firstNonNull; import static com.google.common.collect.ImmutableSet.toImmutableSet; +import static com.google.common.collect.ImmutableSortedSet.toImmutableSortedSet; import static com.google.common.collect.Sets.symmetricDifference; import static com.google.common.collect.Sets.union; import static google.registry.flows.FlowUtils.persistEntityChanges; @@ -43,6 +44,9 @@ import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_UPDATE; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSortedSet; +import com.google.common.collect.Ordering; +import com.google.common.collect.Sets; import com.google.common.net.InternetDomainName; import google.registry.dns.DnsQueue; import google.registry.flows.EppException; @@ -76,6 +80,7 @@ import google.registry.model.eppcommon.StatusValue; import google.registry.model.eppinput.EppInput; import google.registry.model.eppinput.ResourceCommand; import google.registry.model.eppoutput.EppResponse; +import google.registry.model.poll.PollMessage; import google.registry.model.reporting.IcannReportingTypes.ActivityReportField; import google.registry.model.tld.Registry; import java.util.Optional; @@ -175,6 +180,9 @@ public final class DomainUpdateFlow implements TransactionalFlow { Optional statusUpdateBillingEvent = createBillingEventForStatusUpdates(existingDomain, newDomain, domainHistory, now); statusUpdateBillingEvent.ifPresent(entitiesToSave::add); + Optional serverStatusUpdatePollMessage = + createPollMessageForServerStatusUpdates(existingDomain, newDomain, domainHistory, now); + serverStatusUpdatePollMessage.ifPresent(entitiesToSave::add); EntityChanges entityChanges = flowCustomLogic.beforeSave( BeforeSaveParameters.newBuilder() @@ -306,4 +314,50 @@ public final class DomainUpdateFlow implements TransactionalFlow { } return Optional.empty(); } + + /** Enqueues a poll message iff a superuser is adding/removing server statuses. */ + private Optional createPollMessageForServerStatusUpdates( + DomainBase existingDomain, DomainBase newDomain, DomainHistory historyEntry, DateTime now) { + if (registrarId.equals(existingDomain.getPersistedCurrentSponsorRegistrarId())) { + // Don't send a poll message when a superuser registrar is updating its own domain. + return Optional.empty(); + } + ImmutableSortedSet addedServerStatuses = + Sets.difference(newDomain.getStatusValues(), existingDomain.getStatusValues()).stream() + .filter(StatusValue::isServerSettable) + .map(StatusValue::getXmlName) + .collect(toImmutableSortedSet(Ordering.natural())); + ImmutableSortedSet removedServerStatuses = + Sets.difference(existingDomain.getStatusValues(), newDomain.getStatusValues()).stream() + .filter(StatusValue::isServerSettable) + .map(StatusValue::getXmlName) + .collect(toImmutableSortedSet(Ordering.natural())); + + String msg = ""; + if (addedServerStatuses.size() > 0 && removedServerStatuses.size() > 0) { + msg = + String.format( + "The registry administrator has added the status(es) %s and removed the status(es)" + + " %s.", + addedServerStatuses, removedServerStatuses); + } else if (addedServerStatuses.size() > 0) { + msg = + String.format( + "The registry administrator has added the status(es) %s.", addedServerStatuses); + } else if (removedServerStatuses.size() > 0) { + msg = + String.format( + "The registry administrator has removed the status(es) %s.", removedServerStatuses); + } else { + return Optional.empty(); + } + + return Optional.ofNullable( + new PollMessage.OneTime.Builder() + .setParent(historyEntry) + .setEventTime(now) + .setRegistrarId(existingDomain.getCurrentSponsorRegistrarId()) + .setMsg(msg) + .build()); + } } diff --git a/core/src/main/java/google/registry/model/eppcommon/StatusValue.java b/core/src/main/java/google/registry/model/eppcommon/StatusValue.java index 5aa27deee..eb1de6e73 100644 --- a/core/src/main/java/google/registry/model/eppcommon/StatusValue.java +++ b/core/src/main/java/google/registry/model/eppcommon/StatusValue.java @@ -112,7 +112,6 @@ public enum StatusValue implements EppEnum { */ PENDING_UPDATE(AllowedOn.NONE), - /** A non-client-settable status that prevents deletes of EPP resources. */ SERVER_DELETE_PROHIBITED(AllowedOn.ALL), @@ -162,6 +161,10 @@ public enum StatusValue implements EppEnum { return xmlName.startsWith("client"); } + public boolean isServerSettable() { + return xmlName.startsWith("server"); + } + public boolean isChargedStatus() { return xmlName.startsWith("server") && xmlName.endsWith("Prohibited"); } diff --git a/core/src/test/java/google/registry/flows/EppLifecycleDomainTest.java b/core/src/test/java/google/registry/flows/EppLifecycleDomainTest.java index fa1667f46..341792eed 100644 --- a/core/src/test/java/google/registry/flows/EppLifecycleDomainTest.java +++ b/core/src/test/java/google/registry/flows/EppLifecycleDomainTest.java @@ -1449,4 +1449,39 @@ class EppLifecycleDomainTest extends EppTestCase { .atTime("2001-01-08T00:00:00Z") .hasResponse("domain_transfer_query_response_completed_fakesite.xml"); } + + @TestOfyAndSql + void testDomainUpdateBySuperuser_sendsPollMessage() throws Exception { + setIsSuperuser(false); + assertThatLoginSucceeds("NewRegistrar", "foo-BAR2"); + createContacts(DateTime.parse("2000-06-01T00:00:00Z")); + + // Create domain example.tld + assertThatCommand( + "domain_create_no_hosts_or_dsdata.xml", ImmutableMap.of("DOMAIN", "example.tld")) + .atTime("2000-06-01T00:02:00Z") + .hasResponse( + "domain_create_response.xml", + ImmutableMap.of( + "DOMAIN", "example.tld", + "CRDATE", "2000-06-01T00:02:00Z", + "EXDATE", "2002-06-01T00:02:00Z")); + assertThatLogoutSucceeds(); + + setIsSuperuser(true); + assertThatLoginSucceeds("TheRegistrar", "password2"); + // Run domain update that adds server status as superuser + assertThatCommand("domain_update_server_hold.xml") + .atTime("2000-06-02T13:00:00Z") + .hasResponse("generic_success_response.xml"); + assertThatLogoutSucceeds(); + setIsSuperuser(false); + + assertThatLoginSucceeds("NewRegistrar", "foo-BAR2"); + // Verify that the owning registrar now has the correct poll message + assertThatCommand("poll.xml") + .atTime("2000-06-02T13:00:01Z") + .hasResponse("poll_response_server_hold.xml"); + assertThatLogoutSucceeds(); + } } diff --git a/core/src/test/java/google/registry/flows/domain/DomainUpdateFlowTest.java b/core/src/test/java/google/registry/flows/domain/DomainUpdateFlowTest.java index 36a949945..57986c1fd 100644 --- a/core/src/test/java/google/registry/flows/domain/DomainUpdateFlowTest.java +++ b/core/src/test/java/google/registry/flows/domain/DomainUpdateFlowTest.java @@ -19,12 +19,22 @@ import static com.google.common.collect.Sets.union; import static com.google.common.io.BaseEncoding.base16; import static com.google.common.truth.Truth.assertThat; import static google.registry.model.EppResourceUtils.loadByForeignKey; +import static google.registry.model.eppcommon.StatusValue.CLIENT_DELETE_PROHIBITED; +import static google.registry.model.eppcommon.StatusValue.CLIENT_HOLD; +import static google.registry.model.eppcommon.StatusValue.CLIENT_RENEW_PROHIBITED; +import static google.registry.model.eppcommon.StatusValue.SERVER_DELETE_PROHIBITED; +import static google.registry.model.eppcommon.StatusValue.SERVER_HOLD; +import static google.registry.model.eppcommon.StatusValue.SERVER_TRANSFER_PROHIBITED; import static google.registry.model.eppcommon.StatusValue.SERVER_UPDATE_PROHIBITED; +import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_CREATE; +import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_UPDATE; import static google.registry.model.tld.Registry.TldState.QUIET_PERIOD; import static google.registry.testing.DatabaseHelper.assertBillingEvents; import static google.registry.testing.DatabaseHelper.assertNoBillingEvents; +import static google.registry.testing.DatabaseHelper.assertPollMessagesForResource; import static google.registry.testing.DatabaseHelper.createTld; import static google.registry.testing.DatabaseHelper.getOnlyHistoryEntryOfType; +import static google.registry.testing.DatabaseHelper.getPollMessages; import static google.registry.testing.DatabaseHelper.loadByKey; import static google.registry.testing.DatabaseHelper.loadRegistrar; import static google.registry.testing.DatabaseHelper.newDomainBase; @@ -88,7 +98,7 @@ import google.registry.model.domain.DomainHistory; import google.registry.model.domain.secdns.DelegationSignerData; import google.registry.model.eppcommon.StatusValue; import google.registry.model.host.HostResource; -import google.registry.model.reporting.HistoryEntry; +import google.registry.model.poll.PollMessage; import google.registry.model.tld.Registry; import google.registry.persistence.VKey; import google.registry.testing.DualDatabaseTest; @@ -155,7 +165,7 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase + + + + example.tld + + + Payment overdue. + + + + + + + + + + + + ABC-12345 + + diff --git a/core/src/test/resources/google/registry/flows/domain/domain_update_remove_server_statuses.xml b/core/src/test/resources/google/registry/flows/domain/domain_update_remove_server_statuses.xml new file mode 100644 index 000000000..8fed8b225 --- /dev/null +++ b/core/src/test/resources/google/registry/flows/domain/domain_update_remove_server_statuses.xml @@ -0,0 +1,19 @@ + + + + + example.tld + + + + + + + + + + + ABC-12345 + + diff --git a/core/src/test/resources/google/registry/flows/domain_update_server_hold.xml b/core/src/test/resources/google/registry/flows/domain_update_server_hold.xml new file mode 100644 index 000000000..bf124b404 --- /dev/null +++ b/core/src/test/resources/google/registry/flows/domain_update_server_hold.xml @@ -0,0 +1,16 @@ + + + + + + example.tld + + + + + + ABC-12345 + + diff --git a/core/src/test/resources/google/registry/flows/poll_response_server_hold.xml b/core/src/test/resources/google/registry/flows/poll_response_server_hold.xml new file mode 100644 index 000000000..775fb249a --- /dev/null +++ b/core/src/test/resources/google/registry/flows/poll_response_server_hold.xml @@ -0,0 +1,16 @@ + + + + Command completed successfully; ack to dequeue + + + 2000-06-02T13:00:00Z + The registry administrator has added the status(es) [serverHold]. + + + + ABC-12345 + server-trid + + +