diff --git a/docs/flows.md b/docs/flows.md index d4641ebf1..1b6e1d6a3 100644 --- a/docs/flows.md +++ b/docs/flows.md @@ -231,6 +231,8 @@ An EPP flow that updates a contact. An EPP flow that allocates a new domain resource from a domain application. +Note that this flow is only run by superusers. + ### Errors @@ -295,7 +297,7 @@ An EPP flow that creates a new application for a domain resource. * Specified extension is not implemented. * 2201 * Registrar is not authorized to access this TLD. - * Registrar must be active in order to create domains or applications. + * Registrar must be active in order to perform this operation. * 2302 * Resource with this id already exists. * This name has already been claimed by a sunrise applicant. @@ -556,7 +558,7 @@ An EPP flow that creates a new domain resource. * 2201 * Only a tool can pass a metadata extension. * Registrar is not authorized to access this TLD. - * Registrar must be active in order to create domains or applications. + * Registrar must be active in order to perform this operation. * 2302 * Resource with this id already exists. * 2303 @@ -689,6 +691,7 @@ comes in at the exact millisecond that the domain would have expired. * 2201 * The specified resource belongs to another client. * Registrar is not authorized to access this TLD. + * Registrar must be active in order to perform this operation. * 2303 * Resource with this id does not exist. * 2304 @@ -745,6 +748,7 @@ regardless of what the original expiration time was. * 2201 * The specified resource belongs to another client. * Registrar is not authorized to access this TLD. + * Registrar must be active in order to perform this operation. * 2303 * Resource with this id does not exist. * 2304 @@ -907,6 +911,7 @@ new ones with the correct approval time). * 2201 * Authorization info is required to request a transfer. * Registrar is not authorized to access this TLD. + * Registrar must be active in order to perform this operation. * 2202 * Authorization information for accessing resource is invalid. * 2300 diff --git a/java/google/registry/flows/domain/DomainAllocateFlow.java b/java/google/registry/flows/domain/DomainAllocateFlow.java index 3812805d6..501d173f5 100644 --- a/java/google/registry/flows/domain/DomainAllocateFlow.java +++ b/java/google/registry/flows/domain/DomainAllocateFlow.java @@ -98,6 +98,8 @@ import org.joda.time.Duration; /** * An EPP flow that allocates a new domain resource from a domain application. * + *

Note that this flow is only run by superusers. + * * @error {@link google.registry.flows.exceptions.ResourceAlreadyExistsException} * @error {@link DomainAllocateFlow.HasFinalStatusException} * @error {@link DomainAllocateFlow.MissingApplicationException} diff --git a/java/google/registry/flows/domain/DomainApplicationCreateFlow.java b/java/google/registry/flows/domain/DomainApplicationCreateFlow.java index 7cb9a8bf0..770099915 100644 --- a/java/google/registry/flows/domain/DomainApplicationCreateFlow.java +++ b/java/google/registry/flows/domain/DomainApplicationCreateFlow.java @@ -147,7 +147,7 @@ import org.joda.time.DateTime; * @error {@link DomainFlowUtils.NotAuthorizedForTldException} * @error {@link DomainFlowUtils.PremiumNameBlockedException} * @error {@link DomainFlowUtils.RegistrantNotAllowedException} - * @error {@link DomainFlowUtils.RegistrarMustBeActiveToCreateDomainsException} + * @error {@link DomainFlowUtils.RegistrarMustBeActiveForThisOperationException} * @error {@link DomainFlowTmchUtils.SignedMarksMustBeEncodedException} * @error {@link DomainFlowTmchUtils.SignedMarkCertificateExpiredException} * @error {@link DomainFlowTmchUtils.SignedMarkCertificateInvalidException} diff --git a/java/google/registry/flows/domain/DomainCreateFlow.java b/java/google/registry/flows/domain/DomainCreateFlow.java index 09dce93b1..3a566e81a 100644 --- a/java/google/registry/flows/domain/DomainCreateFlow.java +++ b/java/google/registry/flows/domain/DomainCreateFlow.java @@ -179,7 +179,7 @@ import org.joda.time.Duration; * @error {@link DomainFlowUtils.NameserversNotSpecifiedForTldWithNameserverWhitelistException} * @error {@link DomainFlowUtils.PremiumNameBlockedException} * @error {@link DomainFlowUtils.RegistrantNotAllowedException} - * @error {@link DomainFlowUtils.RegistrarMustBeActiveToCreateDomainsException} + * @error {@link DomainFlowUtils.RegistrarMustBeActiveForThisOperationException} * @error {@link DomainFlowUtils.TldDoesNotExistException} * @error {@link DomainFlowUtils.TooManyDsRecordsException} * @error {@link DomainFlowUtils.TooManyNameserversException} diff --git a/java/google/registry/flows/domain/DomainFlowUtils.java b/java/google/registry/flows/domain/DomainFlowUtils.java index 57ae9c3c8..476734247 100644 --- a/java/google/registry/flows/domain/DomainFlowUtils.java +++ b/java/google/registry/flows/domain/DomainFlowUtils.java @@ -892,13 +892,14 @@ public class DomainFlowUtils { /** * Check that the registrar with the given client ID is active. * - *

Non-active registrars are not allowed to create domain applications or domain resources. + *

Non-active registrars are not allowed to run operations that cost money, like domain creates + * or renews. */ static void verifyRegistrarIsActive(String clientId) - throws RegistrarMustBeActiveToCreateDomainsException { + throws RegistrarMustBeActiveForThisOperationException { Registrar registrar = Registrar.loadByClientIdCached(clientId).get(); if (registrar.getState() != State.ACTIVE) { - throw new RegistrarMustBeActiveToCreateDomainsException(); + throw new RegistrarMustBeActiveForThisOperationException(); } } @@ -1606,10 +1607,10 @@ public class DomainFlowUtils { } } - /** Registrar must be active in order to create domains or applications. */ - static class RegistrarMustBeActiveToCreateDomainsException extends AuthorizationErrorException { - public RegistrarMustBeActiveToCreateDomainsException() { - super("Registrar must be active in order to create domains or applications"); + /** Registrar must be active in order to perform this operation. */ + static class RegistrarMustBeActiveForThisOperationException extends AuthorizationErrorException { + public RegistrarMustBeActiveForThisOperationException() { + super("Registrar must be active in order to perform this operation"); } } } diff --git a/java/google/registry/flows/domain/DomainRenewFlow.java b/java/google/registry/flows/domain/DomainRenewFlow.java index e4bce5c94..c71493205 100644 --- a/java/google/registry/flows/domain/DomainRenewFlow.java +++ b/java/google/registry/flows/domain/DomainRenewFlow.java @@ -26,6 +26,7 @@ import static google.registry.flows.domain.DomainFlowUtils.newAutorenewPollMessa import static google.registry.flows.domain.DomainFlowUtils.updateAutorenewRecurrenceEndTime; import static google.registry.flows.domain.DomainFlowUtils.validateFeeChallenge; import static google.registry.flows.domain.DomainFlowUtils.validateRegistrationPeriod; +import static google.registry.flows.domain.DomainFlowUtils.verifyRegistrarIsActive; import static google.registry.flows.domain.DomainFlowUtils.verifyUnitIsYears; import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.util.DateTimeUtils.leapSafeAddYears; @@ -101,6 +102,7 @@ import org.joda.time.Duration; * @error {@link DomainFlowUtils.FeesMismatchException} * @error {@link DomainFlowUtils.FeesRequiredForPremiumNameException} * @error {@link DomainFlowUtils.NotAuthorizedForTldException} + * @error {@link DomainFlowUtils.RegistrarMustBeActiveForThisOperationException} * @error {@link DomainFlowUtils.UnsupportedFeeAttributeException} * @error {@link DomainRenewFlow.IncorrectCurrentExpirationDateException} */ @@ -133,6 +135,7 @@ public final class DomainRenewFlow implements TransactionalFlow { flowCustomLogic.beforeValidation(); extensionManager.validate(); validateClientIsLoggedIn(clientId); + verifyRegistrarIsActive(clientId); DateTime now = ofy().getTransactionTime(); Renew command = (Renew) resourceCommand; // Loads the target resource if it exists diff --git a/java/google/registry/flows/domain/DomainRestoreRequestFlow.java b/java/google/registry/flows/domain/DomainRestoreRequestFlow.java index f109feaa8..04aac8318 100644 --- a/java/google/registry/flows/domain/DomainRestoreRequestFlow.java +++ b/java/google/registry/flows/domain/DomainRestoreRequestFlow.java @@ -25,6 +25,7 @@ import static google.registry.flows.domain.DomainFlowUtils.newAutorenewPollMessa import static google.registry.flows.domain.DomainFlowUtils.validateFeeChallenge; import static google.registry.flows.domain.DomainFlowUtils.verifyNotReserved; import static google.registry.flows.domain.DomainFlowUtils.verifyPremiumNameIsNotBlocked; +import static google.registry.flows.domain.DomainFlowUtils.verifyRegistrarIsActive; import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.util.DateTimeUtils.END_OF_TIME; @@ -101,6 +102,7 @@ import org.joda.time.DateTime; * @error {@link DomainFlowUtils.FeesRequiredForPremiumNameException} * @error {@link DomainFlowUtils.NotAuthorizedForTldException} * @error {@link DomainFlowUtils.PremiumNameBlockedException} + * @error {@link DomainFlowUtils.RegistrarMustBeActiveForThisOperationException} * @error {@link DomainFlowUtils.UnsupportedFeeAttributeException} * @error {@link DomainRestoreRequestFlow.DomainNotEligibleForRestoreException} * @error {@link DomainRestoreRequestFlow.RestoreCommandIncludesChangesException} @@ -129,6 +131,7 @@ public final class DomainRestoreRequestFlow implements TransactionalFlow { RgpUpdateExtension.class); extensionManager.validate(); validateClientIsLoggedIn(clientId); + verifyRegistrarIsActive(clientId); Update command = (Update) resourceCommand; DateTime now = ofy().getTransactionTime(); DomainResource existingDomain = loadAndVerifyExistence(DomainResource.class, targetId, now); diff --git a/java/google/registry/flows/domain/DomainTransferRequestFlow.java b/java/google/registry/flows/domain/DomainTransferRequestFlow.java index ddff94e53..0c99a9e3f 100644 --- a/java/google/registry/flows/domain/DomainTransferRequestFlow.java +++ b/java/google/registry/flows/domain/DomainTransferRequestFlow.java @@ -23,6 +23,7 @@ import static google.registry.flows.domain.DomainFlowUtils.checkAllowedAccessToT import static google.registry.flows.domain.DomainFlowUtils.updateAutorenewRecurrenceEndTime; import static google.registry.flows.domain.DomainFlowUtils.validateFeeChallenge; import static google.registry.flows.domain.DomainFlowUtils.verifyPremiumNameIsNotBlocked; +import static google.registry.flows.domain.DomainFlowUtils.verifyRegistrarIsActive; import static google.registry.flows.domain.DomainFlowUtils.verifyUnitIsYears; import static google.registry.flows.domain.DomainTransferUtils.createLosingTransferPollMessage; import static google.registry.flows.domain.DomainTransferUtils.createPendingTransferData; @@ -107,6 +108,7 @@ import org.joda.time.DateTime; * @error {@link DomainFlowUtils.FeesRequiredForPremiumNameException} * @error {@link DomainFlowUtils.NotAuthorizedForTldException} * @error {@link DomainFlowUtils.PremiumNameBlockedException} + * @error {@link DomainFlowUtils.RegistrarMustBeActiveForThisOperationException} * @error {@link DomainFlowUtils.UnsupportedFeeAttributeException} */ @ReportingSpec(ActivityReportField.DOMAIN_TRANSFER_REQUEST) @@ -139,6 +141,7 @@ public final class DomainTransferRequestFlow implements TransactionalFlow { MetadataExtension.class); extensionManager.validate(); validateClientIsLoggedIn(gainingClientId); + verifyRegistrarIsActive(gainingClientId); DateTime now = ofy().getTransactionTime(); DomainResource existingDomain = loadAndVerifyExistence(DomainResource.class, targetId, now); Optional superuserExtension = diff --git a/javatests/google/registry/flows/domain/DomainApplicationCreateFlowTest.java b/javatests/google/registry/flows/domain/DomainApplicationCreateFlowTest.java index 9d4182ad4..ebeed3c7f 100644 --- a/javatests/google/registry/flows/domain/DomainApplicationCreateFlowTest.java +++ b/javatests/google/registry/flows/domain/DomainApplicationCreateFlowTest.java @@ -100,7 +100,7 @@ import google.registry.flows.domain.DomainFlowUtils.NameserversNotSpecifiedForTl import google.registry.flows.domain.DomainFlowUtils.NotAuthorizedForTldException; import google.registry.flows.domain.DomainFlowUtils.PremiumNameBlockedException; import google.registry.flows.domain.DomainFlowUtils.RegistrantNotAllowedException; -import google.registry.flows.domain.DomainFlowUtils.RegistrarMustBeActiveToCreateDomainsException; +import google.registry.flows.domain.DomainFlowUtils.RegistrarMustBeActiveForThisOperationException; import google.registry.flows.domain.DomainFlowUtils.TldDoesNotExistException; import google.registry.flows.domain.DomainFlowUtils.TooManyDsRecordsException; import google.registry.flows.domain.DomainFlowUtils.TooManyNameserversException; @@ -771,7 +771,24 @@ public class DomainApplicationCreateFlowTest .build()); clock.advanceOneMilli(); EppException thrown = - assertThrows(RegistrarMustBeActiveToCreateDomainsException.class, this::runFlow); + assertThrows(RegistrarMustBeActiveForThisOperationException.class, this::runFlow); + assertAboutEppExceptions().that(thrown).marshalsToXml(); + } + + @Test + public void testFailure_pendingRegistrarCantCreateDomainApplication() { + setEppInput("domain_create_sunrise_encoded_signed_mark.xml"); + persistContactsAndHosts(); + clock.advanceOneMilli(); + persistResource( + Registrar.loadByClientId("TheRegistrar") + .get() + .asBuilder() + .setState(State.PENDING) + .build()); + clock.advanceOneMilli(); + EppException thrown = + assertThrows(RegistrarMustBeActiveForThisOperationException.class, this::runFlow); assertAboutEppExceptions().that(thrown).marshalsToXml(); } diff --git a/javatests/google/registry/flows/domain/DomainCreateFlowTest.java b/javatests/google/registry/flows/domain/DomainCreateFlowTest.java index 33ab45c30..8562262dd 100644 --- a/javatests/google/registry/flows/domain/DomainCreateFlowTest.java +++ b/javatests/google/registry/flows/domain/DomainCreateFlowTest.java @@ -121,7 +121,7 @@ import google.registry.flows.domain.DomainFlowUtils.NameserversNotSpecifiedForTl import google.registry.flows.domain.DomainFlowUtils.NotAuthorizedForTldException; import google.registry.flows.domain.DomainFlowUtils.PremiumNameBlockedException; import google.registry.flows.domain.DomainFlowUtils.RegistrantNotAllowedException; -import google.registry.flows.domain.DomainFlowUtils.RegistrarMustBeActiveToCreateDomainsException; +import google.registry.flows.domain.DomainFlowUtils.RegistrarMustBeActiveForThisOperationException; import google.registry.flows.domain.DomainFlowUtils.TldDoesNotExistException; import google.registry.flows.domain.DomainFlowUtils.TooManyDsRecordsException; import google.registry.flows.domain.DomainFlowUtils.TooManyNameserversException; @@ -1618,7 +1618,21 @@ public class DomainCreateFlowTest extends ResourceFlowTestCase doFailingTest("domain_transfer_request.xml")); + assertAboutEppExceptions().that(thrown).marshalsToXml(); + } + + @Test + public void testFailure_pendingRegistrarCantTransferDomain() { + setupDomain("example", "tld"); + clock.advanceOneMilli(); + persistResource( + Registrar.loadByClientId("NewRegistrar") + .get() + .asBuilder() + .setState(State.PENDING) + .build()); + EppException thrown = + assertThrows( + RegistrarMustBeActiveForThisOperationException.class, + () -> doFailingTest("domain_transfer_request.xml")); + assertAboutEppExceptions().that(thrown).marshalsToXml(); + } + @Test public void testSuccess_nonDefaultAutomaticTransferLength() throws Exception { setupDomain("example", "tld"); @@ -805,6 +842,19 @@ public class DomainTransferRequestFlowTest "domain_transfer_request_missing_period.xml", "domain_transfer_request_response.xml"); } + @Test + public void testSuccess_canTransferAwayFromSuspendedRegistrar() throws Exception { + setupDomain("example", "tld"); + clock.advanceOneMilli(); + persistResource( + Registrar.loadByClientId("TheRegistrar") + .get() + .asBuilder() + .setState(State.SUSPENDED) + .build()); + doSuccessfulTest("domain_transfer_request.xml", "domain_transfer_request_response.xml"); + } + @Test public void testFailure_multiYearPeriod() { setupDomain("example", "tld");