diff --git a/docs/flows.md b/docs/flows.md index 7198970d7..d9a5b94ef 100644 --- a/docs/flows.md +++ b/docs/flows.md @@ -314,6 +314,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. * 2202 * Invalid limited registration period token. * 2302 @@ -546,6 +547,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. * 2202 * Invalid limited registration period token. * 2302 diff --git a/java/google/registry/export/SyncGroupMembersAction.java b/java/google/registry/export/SyncGroupMembersAction.java index 2d6f399e4..b81e2d730 100644 --- a/java/google/registry/export/SyncGroupMembersAction.java +++ b/java/google/registry/export/SyncGroupMembersAction.java @@ -123,7 +123,7 @@ public final class SyncGroupMembersAction implements Runnable { @Override public boolean apply(Registrar registrar) { // Only grab active registrars that require syncing and are of the correct type. - return registrar.isActive() + return registrar.isLive() && registrar.getContactsRequireSyncing() && registrar.getType() == Registrar.Type.REAL; }}) diff --git a/java/google/registry/flows/domain/DomainApplicationCreateFlow.java b/java/google/registry/flows/domain/DomainApplicationCreateFlow.java index 8c0012d86..25147280d 100644 --- a/java/google/registry/flows/domain/DomainApplicationCreateFlow.java +++ b/java/google/registry/flows/domain/DomainApplicationCreateFlow.java @@ -35,6 +35,7 @@ import static google.registry.flows.domain.DomainFlowUtils.verifyLaunchPhaseMatc import static google.registry.flows.domain.DomainFlowUtils.verifyNoCodeMarks; 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.flows.domain.DomainFlowUtils.verifyRegistryStateAllowsLaunchFlows; import static google.registry.flows.domain.DomainFlowUtils.verifyUnitIsYears; import static google.registry.model.EppResourceUtils.createDomainRepoId; @@ -147,6 +148,7 @@ import org.joda.time.DateTime; * @error {@link DomainFlowUtils.NotAuthorizedForTldException} * @error {@link DomainFlowUtils.PremiumNameBlockedException} * @error {@link DomainFlowUtils.RegistrantNotAllowedException} + * @error {@link DomainFlowUtils.RegistrarMustBeActiveToCreateDomainsException} * @error {@link DomainFlowTmchUtils.SignedMarksMustBeEncodedException} * @error {@link DomainFlowTmchUtils.SignedMarkCertificateExpiredException} * @error {@link DomainFlowTmchUtils.SignedMarkCertificateInvalidException} @@ -194,6 +196,7 @@ public final class DomainApplicationCreateFlow implements TransactionalFlow { customLogic.beforeValidation(); extensionManager.validate(); validateClientIsLoggedIn(clientId); + verifyRegistrarIsActive(clientId); DateTime now = ofy().getTransactionTime(); Create command = cloneAndLinkReferences((Create) resourceCommand, now); // Fail if the domain is already registered (e.g. this is a landrush application but the domain diff --git a/java/google/registry/flows/domain/DomainCreateFlow.java b/java/google/registry/flows/domain/DomainCreateFlow.java index c57de6250..21d83ddf6 100644 --- a/java/google/registry/flows/domain/DomainCreateFlow.java +++ b/java/google/registry/flows/domain/DomainCreateFlow.java @@ -35,6 +35,7 @@ import static google.registry.flows.domain.DomainFlowUtils.verifyLaunchPhaseMatc import static google.registry.flows.domain.DomainFlowUtils.verifyNoCodeMarks; 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.flows.domain.DomainFlowUtils.verifyUnitIsYears; import static google.registry.model.EppResourceUtils.createDomainRepoId; import static google.registry.model.eppcommon.StatusValue.SERVER_TRANSFER_PROHIBITED; @@ -155,6 +156,7 @@ import org.joda.time.Duration; * @error {@link NameserversNotSpecifiedForTldWithNameserverWhitelistException} * @error {@link DomainFlowUtils.PremiumNameBlockedException} * @error {@link DomainFlowUtils.RegistrantNotAllowedException} + * @error {@link DomainFlowUtils.RegistrarMustBeActiveToCreateDomainsException} * @error {@link DomainFlowUtils.TldDoesNotExistException} * @error {@link DomainFlowUtils.TooManyDsRecordsException} * @error {@link DomainFlowUtils.TooManyNameserversException} @@ -196,6 +198,7 @@ public class DomainCreateFlow implements TransactionalFlow { customLogic.beforeValidation(); extensionManager.validate(); validateClientIsLoggedIn(clientId); + verifyRegistrarIsActive(clientId); DateTime now = ofy().getTransactionTime(); Create command = cloneAndLinkReferences((Create) resourceCommand, now); Period period = command.getPeriod(); diff --git a/java/google/registry/flows/domain/DomainFlowUtils.java b/java/google/registry/flows/domain/DomainFlowUtils.java index 955d4618c..bc21e9f45 100644 --- a/java/google/registry/flows/domain/DomainFlowUtils.java +++ b/java/google/registry/flows/domain/DomainFlowUtils.java @@ -102,6 +102,7 @@ import google.registry.model.eppoutput.EppResponse.ResponseExtension; import google.registry.model.host.HostResource; import google.registry.model.poll.PollMessage; import google.registry.model.registrar.Registrar; +import google.registry.model.registrar.Registrar.State; import google.registry.model.registry.Registry; import google.registry.model.registry.Registry.TldState; import google.registry.model.registry.label.ReservationType; @@ -753,6 +754,19 @@ 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. + */ + static void verifyRegistrarIsActive(String clientId) + throws RegistrarMustBeActiveToCreateDomainsException { + Registrar registrar = Registrar.loadByClientIdCached(clientId).get(); + if (registrar.getState() != State.ACTIVE) { + throw new RegistrarMustBeActiveToCreateDomainsException(); + } + } + /** Check that the registry phase is not incompatible with launch extension flows. */ static void verifyRegistryStateAllowsLaunchFlows(Registry registry, DateTime now) throws BadCommandForRegistryPhaseException { @@ -1426,4 +1440,12 @@ public class DomainFlowUtils { MAX_REGISTRATION_YEARS)); } } + + /** 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"); + } + } + } diff --git a/java/google/registry/model/registrar/Registrar.java b/java/google/registry/model/registrar/Registrar.java index f6255733c..4d27b3a59 100644 --- a/java/google/registry/model/registrar/Registrar.java +++ b/java/google/registry/model/registrar/Registrar.java @@ -145,10 +145,8 @@ public class Registrar extends ImmutableObject implements Buildable, Jsonifiable /** Represents the state of a persisted registrar entity. */ public enum State { - /** - * This registrar is provisioned and may have access to the testing environment, but is not yet - * allowed to access the production environment. - */ + + /** This registrar is provisioned but not yet active, and cannot log in. */ PENDING, /** This is an active registrar account which is allowed to provision and modify domains. */ @@ -177,8 +175,8 @@ public class Registrar extends ImmutableObject implements Buildable, Jsonifiable /** Regex for telephone support passcode (5 digit string). */ public static final Pattern PHONE_PASSCODE_PATTERN = Pattern.compile("\\d{5}"); - /** The states in which a {@link Registrar} is considered {@link #isActive active}. */ - private static final ImmutableSet ACTIVE_STATES = + /** The states in which a {@link Registrar} is considered {@link #isLive live}. */ + private static final ImmutableSet LIVE_STATES = Sets.immutableEnumSet(State.ACTIVE, State.SUSPENDED); /** @@ -486,14 +484,19 @@ public class Registrar extends ImmutableObject implements Buildable, Jsonifiable return nullToEmptyImmutableSortedCopy(allowedTlds); } - /** Returns {@code true} if registrar is active. */ - public boolean isActive() { - return ACTIVE_STATES.contains(state); + /** + * Returns {@code true} if the registrar is live. + * + *

A live registrar is one that can have live domains/contacts/hosts in the registry, meaning + * that it is either currently active or used to be active (i.e. suspended). + */ + public boolean isLive() { + return LIVE_STATES.contains(state); } /** Returns {@code true} if registrar should be visible in WHOIS results. */ - public boolean isActiveAndPubliclyVisible() { - return ACTIVE_STATES.contains(state) && PUBLICLY_VISIBLE_TYPES.contains(type); + public boolean isLiveAndPubliclyVisible() { + return LIVE_STATES.contains(state) && PUBLICLY_VISIBLE_TYPES.contains(type); } public String getClientCertificate() { diff --git a/java/google/registry/rdap/RdapActionBase.java b/java/google/registry/rdap/RdapActionBase.java index af4a073f6..52ece11ed 100644 --- a/java/google/registry/rdap/RdapActionBase.java +++ b/java/google/registry/rdap/RdapActionBase.java @@ -239,7 +239,7 @@ public abstract class RdapActionBase implements Runnable { * 2. The request did not specify a registrar to filter on, or the registrar matches. */ boolean shouldBeVisible(Registrar registrar) { - return (registrar.isActiveAndPubliclyVisible() + return (registrar.isLiveAndPubliclyVisible() || (shouldIncludeDeleted() && getAuthorization().isAuthorizedForClientId(registrar.getClientId()))) && (!registrarParam.isPresent() || registrarParam.get().equals(registrar.getClientId())); diff --git a/java/google/registry/rdap/RdapEntityAction.java b/java/google/registry/rdap/RdapEntityAction.java index 3fd51f2ed..169656794 100644 --- a/java/google/registry/rdap/RdapEntityAction.java +++ b/java/google/registry/rdap/RdapEntityAction.java @@ -101,7 +101,7 @@ public class RdapEntityAction extends RdapActionBase { if (ianaIdentifier != null) { wasValidKey = true; Optional registrar = getRegistrarByIanaIdentifier(ianaIdentifier); - if ((registrar.isPresent()) && registrar.get().isActiveAndPubliclyVisible()) { + if ((registrar.isPresent()) && registrar.get().isLiveAndPubliclyVisible()) { return rdapJsonFormatter.makeRdapJsonForRegistrar( registrar.get(), true, rdapLinkBase, rdapWhoisServer, now, OutputDataType.FULL); } diff --git a/java/google/registry/rdap/RdapEntitySearchAction.java b/java/google/registry/rdap/RdapEntitySearchAction.java index e2c9c22ef..20868476e 100644 --- a/java/google/registry/rdap/RdapEntitySearchAction.java +++ b/java/google/registry/rdap/RdapEntitySearchAction.java @@ -243,7 +243,7 @@ public class RdapEntitySearchAction extends RdapActionBase { } else { outputDataType = OutputDataType.FULL; for (Registrar registrar : registrars) { - if (registrar.isActiveAndPubliclyVisible()) { + if (registrar.isLiveAndPubliclyVisible()) { numEntities++; if (numEntities > 1) { outputDataType = OutputDataType.SUMMARY; @@ -276,7 +276,7 @@ public class RdapEntitySearchAction extends RdapActionBase { authorization)); } for (Registrar registrar : registrars) { - if (registrar.isActiveAndPubliclyVisible()) { + if (registrar.isLiveAndPubliclyVisible()) { if (jsonOutputList.size() >= rdapResultSetMaxSize) { return RdapSearchResults.create( ImmutableList.copyOf(jsonOutputList), IncompletenessWarningType.TRUNCATED); diff --git a/java/google/registry/rdap/RdapJsonFormatter.java b/java/google/registry/rdap/RdapJsonFormatter.java index 270fc3b34..88338fb2b 100644 --- a/java/google/registry/rdap/RdapJsonFormatter.java +++ b/java/google/registry/rdap/RdapJsonFormatter.java @@ -768,7 +768,7 @@ public class RdapJsonFormatter { ImmutableMap.Builder jsonBuilder = new ImmutableMap.Builder<>(); jsonBuilder.put("objectClassName", "entity"); jsonBuilder.put("handle", registrar.getIanaIdentifier().toString()); - jsonBuilder.put("status", registrar.isActive() ? STATUS_LIST_ACTIVE : STATUS_LIST_REMOVED); + jsonBuilder.put("status", registrar.isLive() ? STATUS_LIST_ACTIVE : STATUS_LIST_REMOVED); jsonBuilder.put("roles", ImmutableList.of(RdapEntityRole.REGISTRAR.rfc7483String)); jsonBuilder.put("links", ImmutableList.of(makeLink("entity", registrar.getIanaIdentifier().toString(), linkBase))); diff --git a/java/google/registry/tools/VerifyOteCommand.java b/java/google/registry/tools/VerifyOteCommand.java index e76c255b3..3e258dd7b 100644 --- a/java/google/registry/tools/VerifyOteCommand.java +++ b/java/google/registry/tools/VerifyOteCommand.java @@ -109,7 +109,7 @@ final class VerifyOteCommand implements ServerSideCommand { .transform(new Function() { @Override public String apply(Registrar registrar) { - if (!registrar.isActive()) { + if (!registrar.isLive()) { return null; } String name = registrar.getClientId(); diff --git a/java/google/registry/whois/RegistrarLookupCommand.java b/java/google/registry/whois/RegistrarLookupCommand.java index 8f26dd0e8..65e4fa930 100644 --- a/java/google/registry/whois/RegistrarLookupCommand.java +++ b/java/google/registry/whois/RegistrarLookupCommand.java @@ -51,7 +51,7 @@ final class RegistrarLookupCommand implements WhoisCommand { Map map = new HashMap<>(); // Use the normalized registrar name as a key, and ignore inactive and hidden registrars. for (Registrar registrar : Registrar.loadAllCached()) { - if (!registrar.isActiveAndPubliclyVisible() || registrar.getRegistrarName() == null) { + if (!registrar.isLiveAndPubliclyVisible() || registrar.getRegistrarName() == null) { continue; } String normalized = normalizeRegistrarName(registrar.getRegistrarName()); diff --git a/javatests/google/registry/flows/domain/DomainApplicationCreateFlowTest.java b/javatests/google/registry/flows/domain/DomainApplicationCreateFlowTest.java index d1f2ce6c7..f9c2d7b0f 100644 --- a/javatests/google/registry/flows/domain/DomainApplicationCreateFlowTest.java +++ b/javatests/google/registry/flows/domain/DomainApplicationCreateFlowTest.java @@ -98,6 +98,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.TldDoesNotExistException; import google.registry.flows.domain.DomainFlowUtils.TooManyDsRecordsException; import google.registry.flows.domain.DomainFlowUtils.TooManyNameserversException; @@ -112,6 +113,8 @@ import google.registry.model.domain.launch.ApplicationStatus; import google.registry.model.domain.launch.LaunchNotice; import google.registry.model.domain.launch.LaunchPhase; import google.registry.model.domain.secdns.DelegationSignerData; +import google.registry.model.registrar.Registrar; +import google.registry.model.registrar.Registrar.State; import google.registry.model.registry.Registry; import google.registry.model.registry.Registry.TldState; import google.registry.model.registry.label.ReservedList; @@ -723,6 +726,22 @@ public class DomainApplicationCreateFlowTest runFlow(); } + @Test + public void testFailure_suspendedRegistrarCantCreateDomainApplication() throws Exception { + setEppInput("domain_create_sunrise_encoded_signed_mark.xml"); + persistContactsAndHosts(); + clock.advanceOneMilli(); + persistResource( + Registrar.loadByClientId("TheRegistrar") + .get() + .asBuilder() + .setState(State.SUSPENDED) + .build()); + clock.advanceOneMilli(); + thrown.expect(RegistrarMustBeActiveToCreateDomainsException.class); + runFlow(); + } + @Test public void testFailure_sunriseApplicationInLandrush() throws Exception { createTld("tld", TldState.LANDRUSH); diff --git a/javatests/google/registry/flows/domain/DomainCreateFlowTest.java b/javatests/google/registry/flows/domain/DomainCreateFlowTest.java index 4770342ad..4ecbef0f8 100644 --- a/javatests/google/registry/flows/domain/DomainCreateFlowTest.java +++ b/javatests/google/registry/flows/domain/DomainCreateFlowTest.java @@ -104,6 +104,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.TldDoesNotExistException; import google.registry.flows.domain.DomainFlowUtils.TooManyDsRecordsException; import google.registry.flows.domain.DomainFlowUtils.TooManyNameserversException; @@ -125,6 +126,8 @@ import google.registry.model.domain.rgp.GracePeriodStatus; import google.registry.model.domain.secdns.DelegationSignerData; import google.registry.model.eppcommon.StatusValue; import google.registry.model.poll.PollMessage; +import google.registry.model.registrar.Registrar; +import google.registry.model.registrar.Registrar.State; import google.registry.model.registry.Registry; import google.registry.model.registry.Registry.TldState; import google.registry.model.registry.Registry.TldType; @@ -132,7 +135,6 @@ import google.registry.model.reporting.DomainTransactionRecord; import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField; import google.registry.model.reporting.HistoryEntry; import google.registry.monitoring.whitebox.EppMetric; -import google.registry.testing.DatastoreHelper; import google.registry.testing.TaskQueueHelper.TaskMatcher; import java.util.Map; import javax.annotation.Nullable; @@ -1439,6 +1441,20 @@ public class DomainCreateFlowTest extends ResourceFlowTestCase exception) throws Exception { @@ -1579,7 +1595,7 @@ public class DomainCreateFlowTest extends ResourceFlowTestCaseof("irrelevant"))