diff --git a/docs/flows.md b/docs/flows.md index d5c383a24..d1f4e14e6 100644 --- a/docs/flows.md +++ b/docs/flows.md @@ -622,16 +622,16 @@ An EPP flow that creates a new domain resource. * The acceptance time specified in the claim notice is more than 48 hours in the past. * The expiration time specified in the claim notice has elapsed. - * The checksum in the specified TCNID does not validate. * The fees passed in the transform command do not match the fees that will be charged. * Domain label is not allowed by IDN table. + * The checksum in the specified TCNID does not validate. * Domain name is under tld which doesn't exist. * 2005 - * The specified TCNID is invalid. * Domain name must have exactly one part above the tld. * The requested fee is expressed in a scale that is invalid for the given currency. + * The specified TCNID is invalid. * 2102 * The 'maxSigLife' setting is not supported. * The 'grace-period', 'applied' and 'refundable' fields are disallowed by @@ -647,19 +647,17 @@ An EPP flow that creates a new domain resource. * Resource linked to this domain does not exist. * 2304 * The claims period for this TLD has ended. - * Requested domain requires a claims notice. - * Requested domain does not require a claims notice. * Requested domain is reserved. * Linked resource in pending delete prohibits operation. + * Requested domain requires a claims notice. * Nameservers are not whitelisted for this TLD. * Nameservers not specified for this TLD with whitelist. * The requested domain name is on the premium price list, and this registrar has blocked premium registrations. * Registrant is not whitelisted for this TLD. + * Requested domain does not require a claims notice. * There is an open application for this domain. * 2306 - * The specified trademark validator is not supported. - * Only encoded signed marks are supported. * Domain names can only contain a-z, 0-9, '.' and '-'. * Periods for domain registrations must be specified in years. * The requested fees cannot be provided in the requested currency. @@ -669,11 +667,13 @@ An EPP flow that creates a new domain resource. * More than one contact for a given role is not allowed. * No part of a domain name can be empty. * Domain name starts with xn-- but is not a valid IDN. + * The specified trademark validator is not supported. * Domain labels cannot begin with a dash. * Missing type attribute for contact. * Too many DS records set on a domain. * Too many nameservers set on this domain. * Domain labels cannot end with a dash. + * Only encoded signed marks are supported. ## DomainCheckFlow @@ -828,16 +828,16 @@ An EPP flow that creates a new application for a domain resource. * The acceptance time specified in the claim notice is more than 48 hours in the past. * The expiration time specified in the claim notice has elapsed. - * The checksum in the specified TCNID does not validate. * The fees passed in the transform command do not match the fees that will be charged. * Domain label is not allowed by IDN table. + * The checksum in the specified TCNID does not validate. * Domain name is under tld which doesn't exist. * 2005 - * The specified TCNID is invalid. * Domain name must have exactly one part above the tld. * The requested fee is expressed in a scale that is invalid for the given currency. + * The specified TCNID is invalid. * Signed mark data is improperly encoded. * Error while parsing encoded signed mark data. * 2102 @@ -849,7 +849,7 @@ An EPP flow that creates a new application for a domain resource. * 2201 * Registrar is not authorized to access this TLD. * 2202 - * Authorization information for accessing resource is invalid. + * Invalid limited registration period token. * 2302 * Resource with this id already exists. * This name has already been claimed by a sunrise applicant. @@ -857,17 +857,15 @@ An EPP flow that creates a new application for a domain resource. * Resource linked to this domain does not exist. * 2304 * The claims period for this TLD has ended. - * Requested domain requires a claims notice. - * Requested domain does not require a claims notice. * Requested domain is reserved. + * Requested domain requires a claims notice. * Nameservers are not whitelisted for this TLD. * Nameservers not specified for this TLD with whitelist. * The requested domain name is on the premium price list, and this registrar has blocked premium registrations. * Registrant is not whitelisted for this TLD. + * Requested domain does not require a claims notice. * 2306 - * The specified trademark validator is not supported. - * Only encoded signed marks are supported. * Domain names can only contain a-z, 0-9, '.' and '-'. * Periods for domain registrations must be specified in years. * Encoded signed marks must use base64 encoding. @@ -878,6 +876,7 @@ An EPP flow that creates a new application for a domain resource. * More than one contact for a given role is not allowed. * No part of a domain name can be empty. * Domain name starts with xn-- but is not a valid IDN. + * The specified trademark validator is not supported. * Declared launch extension phase does not match the current registry phase. * Domain labels cannot begin with a dash. @@ -894,6 +893,7 @@ An EPP flow that creates a new application for a domain resource. * Too many nameservers set on this domain. * Only one signed mark is allowed per application. * Domain labels cannot end with a dash. + * Only encoded signed marks are supported. ## DomainAllocateFlow @@ -905,7 +905,6 @@ An EPP flow that allocates a new domain resource from a domain application. ### Errors * 2201 - * Registrar is not authorized to access this TLD. * Only a superuser can allocate domains. * 2302 * Resource with this id already exists. @@ -926,11 +925,12 @@ An EPP flow that checks whether strings are trademarked. * 2002 * Command is not allowed in the current registry phase. * Claims checks are not allowed during sunrise. - * The claims period has ended. * 2004 * Domain name is under tld which doesn't exist. * 2201 * Registrar is not authorized to access this TLD. +* 2304 + * The claims period for this TLD has ended. * 2306 * Too many resource checks requested in one check command. diff --git a/java/google/registry/flows/Flow.java b/java/google/registry/flows/Flow.java index ba627528c..3fdd19b7e 100644 --- a/java/google/registry/flows/Flow.java +++ b/java/google/registry/flows/Flow.java @@ -59,10 +59,6 @@ public abstract class Flow { /** Execute the business logic for this flow. */ protected abstract EppOutput run() throws EppException; - protected String getClientId() { - return sessionMetadata.getClientId(); - } - protected EppOutput createOutput(Result.Code code) { return createOutput(code, null); } diff --git a/java/google/registry/flows/FlowModule.java b/java/google/registry/flows/FlowModule.java index 8bea789f7..4202f6ede 100644 --- a/java/google/registry/flows/FlowModule.java +++ b/java/google/registry/flows/FlowModule.java @@ -185,7 +185,13 @@ public class FlowModule { @Provides @FlowScope - static Optional provideAuthInfo(ResourceCommand resourceCommand) { + static AuthInfo provideAuthInfo(ResourceCommand resourceCommand) { + return ((SingleResourceCommand) resourceCommand).getAuthInfo(); + } + + @Provides + @FlowScope + static Optional provideOptionalAuthInfo(ResourceCommand resourceCommand) { return Optional.fromNullable(((SingleResourceCommand) resourceCommand).getAuthInfo()); } diff --git a/java/google/registry/flows/ResourceCreateFlow.java b/java/google/registry/flows/ResourceCreateFlow.java deleted file mode 100644 index 7799893cc..000000000 --- a/java/google/registry/flows/ResourceCreateFlow.java +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright 2016 The Nomulus Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package google.registry.flows; - -import static google.registry.model.ofy.ObjectifyService.ofy; - -import com.google.common.annotations.VisibleForTesting; -import com.googlecode.objectify.Key; -import google.registry.flows.EppException.ObjectAlreadyExistsException; -import google.registry.model.EppResource; -import google.registry.model.EppResource.Builder; -import google.registry.model.EppResource.ForeignKeyedEppResource; -import google.registry.model.domain.DomainApplication; -import google.registry.model.eppinput.ResourceCommand.ResourceCreateOrChange; -import google.registry.model.eppinput.ResourceCommand.SingleResourceCommand; -import google.registry.model.index.DomainApplicationIndex; -import google.registry.model.index.EppResourceIndex; -import google.registry.model.index.ForeignKeyIndex; -import google.registry.util.TypeUtils.TypeInstantiator; -import javax.annotation.Nullable; - -/** - * An EPP flow that creates a storable resource. - * - * @param the resource type being changed - * @param a builder for the resource - * @param the command type, marshalled directly from the epp xml - */ -public abstract class ResourceCreateFlow - , - C extends ResourceCreateOrChange & SingleResourceCommand> - extends ResourceCreateOrMutateFlow { - - @Override - protected void initRepoId() { - repoId = createFlowRepoId(); - } - - @Nullable - protected abstract String createFlowRepoId(); - - @Override - protected final void verifyIsAllowed() throws EppException { - if (existingResource != null) { - throw new ResourceAlreadyExistsException(targetId); - } - verifyCreateIsAllowed(); - } - - @Override - protected final R createOrMutateResource() throws EppException { - B builder = new TypeInstantiator(getClass()){}.instantiate(); - command.applyTo(builder); - builder - .setCreationClientId(getClientId()) - .setCurrentSponsorClientId(getClientId()) - .setRepoId(getResourceKey().getName()); - setCreateProperties(builder); - return builder.build(); - } - - /** - * Save a new or updated {@link ForeignKeyIndex} and {@link EppResourceIndex} pointing to what we - * created. - */ - @Override - protected final void modifyRelatedResources() { - if (newResource instanceof ForeignKeyedEppResource) { - ofy().save().entity(ForeignKeyIndex.create(newResource, newResource.getDeletionTime())); - } else if (newResource instanceof DomainApplication) { - ofy().save().entity( - DomainApplicationIndex.createUpdatedInstance((DomainApplication) newResource)); - } - ofy().save().entity(EppResourceIndex.create(Key.create(newResource))); - modifyCreateRelatedResources(); - } - - /** Modify any other resources that need to be informed of this create. */ - protected void modifyCreateRelatedResources() {} - - /** Check resource-specific invariants before allowing the create to proceed. */ - @SuppressWarnings("unused") - protected void verifyCreateIsAllowed() throws EppException {} - - /** Set any resource-specific properties before creating. */ - @SuppressWarnings("unused") - protected void setCreateProperties(B builder) throws EppException {} - - /** Resource with this id already exists. */ - public static class ResourceAlreadyExistsException extends ObjectAlreadyExistsException { - - /** Whether this was thrown from a "failfast" context. Useful for testing. */ - final boolean failfast; - - public ResourceAlreadyExistsException(String resourceId, boolean failfast) { - super(String.format("Object with given ID (%s) already exists", resourceId)); - this.failfast = failfast; - } - - public ResourceAlreadyExistsException(String resourceId) { - this(resourceId, false); - } - - @VisibleForTesting - public boolean isFailfast() { - return failfast; - } - } -} diff --git a/java/google/registry/flows/ResourceCreateOrMutateFlow.java b/java/google/registry/flows/ResourceCreateOrMutateFlow.java deleted file mode 100644 index 5d870d681..000000000 --- a/java/google/registry/flows/ResourceCreateOrMutateFlow.java +++ /dev/null @@ -1,171 +0,0 @@ -// Copyright 2016 The Nomulus Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package google.registry.flows; - -import static com.google.common.base.Preconditions.checkState; -import static google.registry.model.ofy.ObjectifyService.ofy; - -import com.googlecode.objectify.Key; -import google.registry.flows.EppException.AuthorizationErrorException; -import google.registry.flows.FlowModule.InputXml; -import google.registry.model.EppResource; -import google.registry.model.domain.Period; -import google.registry.model.domain.metadata.MetadataExtension; -import google.registry.model.eppinput.ResourceCommand.SingleResourceCommand; -import google.registry.model.eppoutput.EppOutput; -import google.registry.model.reporting.HistoryEntry; -import google.registry.util.TypeUtils.TypeInstantiator; -import javax.inject.Inject; - -/** - * An EPP flow that creates or mutates a single stored resource. - * - * @param the resource type being changed - * @param the command type, marshalled directly from the epp xml - * - * @error {@link OnlyToolCanPassMetadataException} - */ -public abstract class ResourceCreateOrMutateFlow - extends SingleResourceFlow - implements TransactionalFlow { - - @Inject EppRequestSource eppRequestSource; - @Inject @InputXml byte[] inputXmlBytes; - - String repoId; - protected R newResource; - protected HistoryEntry historyEntry; - protected MetadataExtension metadataExtension; - - @Override - protected final void initSingleResourceFlow() throws EppException { - registerExtensions(MetadataExtension.class); - metadataExtension = eppInput.getSingleExtension(MetadataExtension.class); - initRepoId(); - initHistoryEntry(); - initResourceCreateOrMutateFlow(); - } - - /** Subclasses can optionally override this for further initialization. */ - @SuppressWarnings("unused") - protected void initResourceCreateOrMutateFlow() throws EppException {} - - /** - * Initializes the repoId on the flow. For mutate flows, the repoId is the same as that of the - * existing resource. For create flows, a new repoId is allocated for the appropriate class. - */ - protected abstract void initRepoId(); - - /** - * Create the history entry associated with this resource create or mutate flow. - */ - private void initHistoryEntry() { - // Don't try to create a historyEntry for mutate flows that are failing because the - // existingResource doesn't actually exist. - historyEntry = (repoId == null) ? null : new HistoryEntry.Builder() - .setType(getHistoryEntryType()) - .setPeriod(getCommandPeriod()) - .setClientId(getClientId()) - .setTrid(trid) - .setModificationTime(now) - .setXmlBytes(storeXmlInHistoryEntry() ? inputXmlBytes : null) - .setBySuperuser(isSuperuser) - .setReason(getHistoryEntryReason()) - .setRequestedByRegistrar(getHistoryEntryRequestedByRegistrar()) - .setParent(getResourceKey()) - .build(); - } - - /** - * Returns a Key pointing to this resource, even if this resource hasn't been initialized or - * persisted yet. - */ - protected Key getResourceKey() { - checkState(repoId != null, - "RepoId hasn't been initialized yet; getResourceKey() called too early"); - Class resourceClazz = new TypeInstantiator(getClass()){}.getExactType(); - return Key.create(null, resourceClazz, repoId); - } - - @Override - protected final EppOutput runResourceFlow() throws EppException { - newResource = createOrMutateResource(); - verifyNewStateIsAllowed(); - validateMetadataExtension(); - modifyRelatedResources(); - enqueueTasks(); - ofy().save().entities(newResource, historyEntry); - return getOutput(); - } - - /** Execute the inner core of the command and returned the created or mutated resource. */ - protected abstract R createOrMutateResource() throws EppException; - - /** Check the new state before writing it. */ - @SuppressWarnings("unused") - protected void verifyNewStateIsAllowed() throws EppException {} - - /** Kick off any tasks that need to happen asynchronously. */ - @SuppressWarnings("unused") - protected void enqueueTasks() throws EppException {} - - /** Modify any other resources that need to be informed of this change. */ - @SuppressWarnings("unused") - protected void modifyRelatedResources() throws EppException {} - - /** Ensure that, if a metadata command exists, it is being passed from a tool-created session. */ - void validateMetadataExtension() throws EppException { - if (!(metadataExtension == null || eppRequestSource.equals(EppRequestSource.TOOL))) { - throw new OnlyToolCanPassMetadataException(); - } - } - - /** Subclasses must override this to specify the type set on the history entry. */ - protected abstract HistoryEntry.Type getHistoryEntryType(); - - /** Subclasses may override this if they do not wish to store the XML of a command. */ - protected boolean storeXmlInHistoryEntry() { return true; } - - /** Retrieve the reason for the history entry. */ - protected String getHistoryEntryReason() { - return metadataExtension != null - ? metadataExtension.getReason() - : null; - } - - /** Retrieve the requested by registrar flag for the history entry. */ - protected Boolean getHistoryEntryRequestedByRegistrar() { - return metadataExtension != null - ? metadataExtension.getRequestedByRegistrar() - : null; - } - - /** - * Subclasses that have a specified period for their command should override this to so that the - * history entry contains the correct data. - */ - protected Period getCommandPeriod() { return null; } - - /** Get the {@link EppOutput} to return. */ - protected abstract EppOutput getOutput() throws EppException; - - /** Only a tool can pass a metadata extension. */ - public static class OnlyToolCanPassMetadataException extends AuthorizationErrorException { - public OnlyToolCanPassMetadataException() { - super("Metadata extensions can only be passed by tools."); - } - } - -} diff --git a/java/google/registry/flows/ResourceFlow.java b/java/google/registry/flows/ResourceFlow.java deleted file mode 100644 index 66a6df60f..000000000 --- a/java/google/registry/flows/ResourceFlow.java +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2016 The Nomulus Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package google.registry.flows; - -import google.registry.model.EppResource; -import google.registry.model.eppinput.EppInput.ResourceCommandWrapper; -import google.registry.model.eppinput.ResourceCommand; -import google.registry.model.eppoutput.EppOutput; -import google.registry.util.TypeUtils.TypeInstantiator; - -/** - * An EPP flow that addresses a stored resource. - * - * @param the resource type being manipulated - * @param the command type doing the manipulation. - */ -public abstract class ResourceFlow - extends LoggedInFlow { - - protected C command; - protected Class resourceClass; - - @Override - @SuppressWarnings("unchecked") - protected final void initLoggedInFlow() throws EppException { - this.command = (C) ((ResourceCommandWrapper) eppInput.getCommandWrapper().getCommand()) - .getResourceCommand(); - this.resourceClass = new TypeInstantiator(getClass()){}.getExactType(); - initResourceFlow(); - } - - /** Resource flows can override this for custom initialization.*/ - protected abstract void initResourceFlow() throws EppException; - - /** - * Loads the target resource and performs authorization and state allowance checks on it before - * delegating to {@link #runResourceFlow()}. - * - * @throws EppException If an error occurred while manipulating the resource. - */ - @Override - public final EppOutput run() throws EppException { - verifyIsAllowed(); - return runResourceFlow(); - } - - /** - * Verifies that the command is allowed on the target resource. - * - * @throws EppException If the command is not allowed on this resource. - */ - protected abstract void verifyIsAllowed() throws EppException; - - /** - * Run the flow. - * - * @throws EppException If something fails while manipulating the resource. - */ - protected abstract EppOutput runResourceFlow() throws EppException; -} diff --git a/java/google/registry/flows/ResourceFlowUtils.java b/java/google/registry/flows/ResourceFlowUtils.java index 7a6b6d780..4ae804c19 100644 --- a/java/google/registry/flows/ResourceFlowUtils.java +++ b/java/google/registry/flows/ResourceFlowUtils.java @@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkState; import static google.registry.model.EppResourceUtils.loadByForeignKey; import static google.registry.model.EppResourceUtils.queryDomainsUsingResource; import static google.registry.model.domain.DomainResource.extendRegistrationWithCap; +import static google.registry.model.index.ForeignKeyIndex.loadAndGetKey; import static google.registry.model.ofy.ObjectifyService.ofy; import com.google.common.base.Function; @@ -300,7 +301,7 @@ public class ResourceFlowUtils { public static void verifyResourceDoesNotExist( Class clazz, String targetId, DateTime now) throws EppException { - if (loadByForeignKey(clazz, targetId, now) != null) { + if (loadAndGetKey(clazz, targetId, now) != null) { throw new ResourceAlreadyExistsException(targetId); } } diff --git a/java/google/registry/flows/SingleResourceFlow.java b/java/google/registry/flows/SingleResourceFlow.java deleted file mode 100644 index ebad3f002..000000000 --- a/java/google/registry/flows/SingleResourceFlow.java +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright 2016 The Nomulus Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package google.registry.flows; - -import static google.registry.model.EppResourceUtils.loadByForeignKey; -import static google.registry.model.EppResourceUtils.loadDomainApplication; - -import com.google.common.base.Joiner; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Sets; -import google.registry.flows.EppException.StatusProhibitsOperationException; -import google.registry.model.EppResource; -import google.registry.model.domain.DomainApplication; -import google.registry.model.domain.launch.ApplicationIdTargetExtension; -import google.registry.model.eppcommon.StatusValue; -import google.registry.model.eppinput.ResourceCommand.SingleResourceCommand; -import java.util.Set; - -/** - * An EPP flow that manipulates a single stored resource. - * - * @param the resource type being manipulated - * @param the command type doing the manipulation. - */ -public abstract class SingleResourceFlow - extends ResourceFlow { - - protected R existingResource; - protected String targetId; - - @SuppressWarnings("unchecked") - @Override - protected final void initResourceFlow() throws EppException { - targetId = getTargetId(); - // In a transactional flow, loading the resource will be expensive because it can't be cached. - // Allow flows to optionally fail fast here before loading. - failfast(); - // Loads the target resource if it exists - // Some flows such as DomainApplicationInfoFlow have the id marked as optional in the schema. - // We require it by policy in the relevant flow, but here we need to make sure not to NPE when - // initializing the (obviously nonexistent) existing resource. - if (targetId != null && tryToLoadExisting()) { - // This ugliness goes away once domain application flows are flattened. - if (resourceClass == DomainApplication.class) { - existingResource = (R) loadDomainApplication(targetId, now); - } else { - existingResource = loadByForeignKey(resourceClass, targetId, now); - } - } - if (existingResource != null) { - Set problems = Sets.intersection( - existingResource.getStatusValues(), getDisallowedStatuses()); - if (!problems.isEmpty()) { - throw new ResourceStatusProhibitsOperationException(problems); - } - } - initSingleResourceFlow(); - } - - /** - * Returns whether the resource flow should attempt to load an existing resource with the - * matching targetId. Defaults to true, but overriding flows can set to false to bypass loading - * of existing resources. - */ - protected boolean tryToLoadExisting() { - return true; - } - - /** - * Get the target id from {@link SingleResourceCommand}. If there is a launch extension present, - * it overrides that target id with its application id, so return that instead. There will never - * be more than one launch extension. - */ - protected final String getTargetId() { - ApplicationIdTargetExtension extension = - eppInput.getSingleExtension(ApplicationIdTargetExtension.class); - return extension == null ? command.getTargetId() : extension.getApplicationId(); - } - - /** Subclasses can optionally override this to fail before loading {@link #existingResource}. */ - @SuppressWarnings("unused") - protected void failfast() throws EppException {} - - /** Subclasses can optionally override this for further initialization. */ - @SuppressWarnings("unused") - protected void initSingleResourceFlow() throws EppException {} - - protected Set getDisallowedStatuses() { - return ImmutableSet.of(); - } - - /** Resource status prohibits this operation. */ - public static class ResourceStatusProhibitsOperationException - extends StatusProhibitsOperationException { - public ResourceStatusProhibitsOperationException(Set status) { - super("Operation disallowed by status: " + Joiner.on(", ").join(status)); - } - } -} diff --git a/java/google/registry/flows/domain/BaseDomainCreateFlow.java b/java/google/registry/flows/domain/BaseDomainCreateFlow.java deleted file mode 100644 index 7a815c235..000000000 --- a/java/google/registry/flows/domain/BaseDomainCreateFlow.java +++ /dev/null @@ -1,429 +0,0 @@ -// Copyright 2016 The Nomulus Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package google.registry.flows.domain; - -import static google.registry.flows.domain.DomainFlowUtils.checkAllowedAccessToTld; -import static google.registry.flows.domain.DomainFlowUtils.cloneAndLinkReferences; -import static google.registry.flows.domain.DomainFlowUtils.validateContactsHaveTypes; -import static google.registry.flows.domain.DomainFlowUtils.validateDomainName; -import static google.registry.flows.domain.DomainFlowUtils.validateDomainNameWithIdnTables; -import static google.registry.flows.domain.DomainFlowUtils.validateDsData; -import static google.registry.flows.domain.DomainFlowUtils.validateNameserversAllowedOnTld; -import static google.registry.flows.domain.DomainFlowUtils.validateNameserversCountForTld; -import static google.registry.flows.domain.DomainFlowUtils.validateNoDuplicateContacts; -import static google.registry.flows.domain.DomainFlowUtils.validateRegistrantAllowedOnTld; -import static google.registry.flows.domain.DomainFlowUtils.validateRequiredContactsPresent; -import static google.registry.flows.domain.DomainFlowUtils.verifyLaunchPhase; -import static google.registry.flows.domain.DomainFlowUtils.verifyNotInPendingDelete; -import static google.registry.flows.domain.DomainFlowUtils.verifyNotReserved; -import static google.registry.flows.domain.DomainFlowUtils.verifyPremiumNameIsNotBlocked; -import static google.registry.flows.domain.DomainFlowUtils.verifyRegistryStateAllowsLaunchFlows; -import static google.registry.flows.domain.DomainFlowUtils.verifySignedMarks; -import static google.registry.flows.domain.DomainFlowUtils.verifyUnitIsYears; -import static google.registry.model.EppResourceUtils.createDomainRoid; -import static google.registry.model.EppResourceUtils.loadByForeignKey; -import static google.registry.model.domain.fee.Fee.FEE_CREATE_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER; -import static google.registry.model.ofy.ObjectifyService.ofy; -import static google.registry.model.registry.Registries.findTldForName; -import static google.registry.model.registry.label.ReservedList.matchesAnchorTenantReservation; -import static google.registry.util.CollectionUtils.nullToEmpty; - -import com.google.common.base.Optional; -import com.google.common.net.InternetDomainName; -import com.googlecode.objectify.Key; -import com.googlecode.objectify.Work; -import google.registry.flows.EppException; -import google.registry.flows.EppException.ParameterValuePolicyErrorException; -import google.registry.flows.EppException.ParameterValueRangeErrorException; -import google.registry.flows.EppException.ParameterValueSyntaxErrorException; -import google.registry.flows.EppException.StatusProhibitsOperationException; -import google.registry.flows.EppException.UnimplementedOptionException; -import google.registry.flows.ResourceCreateFlow; -import google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException; -import google.registry.flows.domain.TldSpecificLogicProxy.EppCommandOperations; -import google.registry.model.domain.DomainBase; -import google.registry.model.domain.DomainBase.Builder; -import google.registry.model.domain.DomainCommand.Create; -import google.registry.model.domain.DomainResource; -import google.registry.model.domain.LrpTokenEntity; -import google.registry.model.domain.fee.FeeTransformCommandExtension; -import google.registry.model.domain.flags.FlagsCreateCommandExtension; -import google.registry.model.domain.launch.LaunchCreateExtension; -import google.registry.model.domain.launch.LaunchNotice; -import google.registry.model.domain.launch.LaunchNotice.InvalidChecksumException; -import google.registry.model.domain.rgp.GracePeriodStatus; -import google.registry.model.domain.secdns.SecDnsCreateExtension; -import google.registry.model.ofy.ObjectifyService; -import google.registry.model.registry.Registry; -import google.registry.model.registry.Registry.TldState; -import google.registry.model.smd.SignedMark; -import google.registry.model.tmch.ClaimsListShard; -import java.util.Set; -import javax.annotation.Nullable; - -/** - * An EPP flow that creates a new domain resource or application. - * - * @param the resource type being created - * @param a builder for the resource - */ -public abstract class BaseDomainCreateFlow> - extends ResourceCreateFlow { - - private SecDnsCreateExtension secDnsCreate; - - protected LaunchCreateExtension launchCreate; - protected String domainLabel; - protected InternetDomainName domainName; - protected String idnTableName; - protected FeeTransformCommandExtension feeCreate; - protected EppCommandOperations commandOperations; - protected boolean hasSignedMarks; - protected SignedMark signedMark; - protected boolean isAnchorTenantViaReservation; - protected TldState tldState; - protected Optional lrpToken; - - protected Optional extraFlowLogic; - - @Override - public final void initResourceCreateOrMutateFlow() throws EppException { - command = cloneAndLinkReferences(command, now); - registerExtensions(SecDnsCreateExtension.class, FlagsCreateCommandExtension.class); - registerExtensions(FEE_CREATE_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER); - secDnsCreate = eppInput.getSingleExtension(SecDnsCreateExtension.class); - launchCreate = eppInput.getSingleExtension(LaunchCreateExtension.class); - feeCreate = - eppInput.getFirstExtensionOfClasses(FEE_CREATE_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER); - hasSignedMarks = launchCreate != null && !launchCreate.getSignedMarks().isEmpty(); - initDomainCreateFlow(); - // We can't initialize extraFlowLogic here, because the TLD has not been checked yet. - } - - @Override - @Nullable - protected String createFlowRepoId() { - // The domain name hasn't been validated yet, so if it's invalid, instead of throwing an error, - // simply leave the repoId blank (it won't be needed anyway as the flow will fail when - // validation fails later). - if (!InternetDomainName.isValid(command.getFullyQualifiedDomainName())) { - return null; - } - Optional tldParsed = - findTldForName(InternetDomainName.from(command.getFullyQualifiedDomainName())); - return tldParsed.isPresent() - ? createDomainRoid(ObjectifyService.allocateId(), tldParsed.get().toString()) - : null; - } - - /** Subclasses may override this to do more specific initialization. */ - protected void initDomainCreateFlow() {} - - /** - * Returns the tld of the domain being created. - * - *

Update/delete domain-related flows can simply grab the tld using existingResource.getTld(), - * but in the create flows, the resource doesn't exist yet. So we grab it off the domain name - * that the flow is attempting to create. - * - *

Note that it's not always safe to call this until after the domain name has been validated - * in verifyCreateIsAllowed(). - */ - protected String getTld() { - return domainName.parent().toString(); - } - - /** - * Fail the domain or application create very fast if the domain is already registered. - * - *

Try to load the domain non-transactionally, since this can hit memcache. If we succeed, and - * the domain is not in the ADD grace period (the only state that allows instantaneous transition - * to being deleted), we can assume that the domain will not be deleted (and therefore won't be - * creatable) until its deletion time. For repeated failed creates this means we can avoid the - * datastore lookup, which is very expensive (and first-seen failed creates are no worse than they - * otherwise would be). This comes at the cost of the extra lookup for successful creates (or - * rather, those that don't fail due to the domain existing) and also for failed creates within - * the existing domain's ADD grace period. - */ - @Override - protected final void failfast() throws EppException { - // Enter a transactionless context briefly. - DomainResource domain = ofy().doTransactionless(new Work() { - @Override - public DomainResource run() { - // This is cacheable because we are outside of a transaction. - return loadByForeignKey(DomainResource.class, targetId, now); - }}); - // If the domain exists already and isn't in the ADD grace period then there is no way it will - // be suddenly deleted and therefore the create must fail. - if (domain != null - && !domain.getGracePeriodStatuses().contains(GracePeriodStatus.ADD)) { - throw new ResourceAlreadyExistsException(targetId, true); - } - } - - /** Fail if the create command is somehow invalid. */ - @Override - protected final void verifyCreateIsAllowed() throws EppException { - // Validate that this is actually a legal domain name on a TLD that the registrar has access to. - domainName = validateDomainName(command.getFullyQualifiedDomainName()); - idnTableName = validateDomainNameWithIdnTables(domainName); - String tld = getTld(); - checkAllowedAccessToTld(getAllowedTlds(), tld); - Registry registry = Registry.get(tld); - tldState = registry.getTldState(now); - // Now that the TLD has been verified, we can go ahead and initialize extraFlowLogic. The - // initialization and matching commit are done at the topmost possible level in the flow - // hierarchy, but the actual processing takes place only when needed in the children, e.g. - // DomainCreateFlow. - extraFlowLogic = RegistryExtraFlowLogicProxy.newInstanceForTld(tld); - domainLabel = domainName.parts().get(0); - commandOperations = TldSpecificLogicProxy.getCreatePrice( - registry, - domainName.toString(), - getClientId(), - now, - command.getPeriod().getValue(), - eppInput); - // The TLD should always be the parent of the requested domain name. - isAnchorTenantViaReservation = matchesAnchorTenantReservation( - domainLabel, tld, command.getAuthInfo().getPw().getValue()); - boolean isLrpApplication = - registry.getLrpPeriod().contains(now) - && !command.getAuthInfo().getPw().getValue().isEmpty() - && !isAnchorTenantViaReservation; - lrpToken = isLrpApplication - ? TldSpecificLogicProxy.getMatchingLrpToken(command, tld) - : Optional.absent(); - // Superusers can create reserved domains, force creations on domains that require a claims - // notice without specifying a claims key, and override blocks on registering premium domains. - if (!isSuperuser) { - boolean isSunriseApplication = - launchCreate != null && !launchCreate.getSignedMarks().isEmpty(); - if (!isAnchorTenantViaReservation) { - verifyNotReserved(domainName, isSunriseApplication); - } - if (isLrpApplication && !lrpToken.isPresent()) { - throw new BadAuthInfoForResourceException(); - } - boolean isClaimsPeriod = now.isBefore(registry.getClaimsPeriodEnd()); - boolean isClaimsCreate = launchCreate != null && launchCreate.getNotice() != null; - if (isClaimsPeriod) { - boolean labelOnClaimsList = ClaimsListShard.get().getClaimKey(domainLabel) != null; - if (labelOnClaimsList && !isSunriseApplication && !isClaimsCreate) { - throw new MissingClaimsNoticeException(domainName.toString()); - } - if (!labelOnClaimsList && isClaimsCreate) { - throw new UnexpectedClaimsNoticeException(domainName.toString()); - } - } else if (isClaimsCreate) { - throw new ClaimsPeriodEndedException(tld); - } - verifyPremiumNameIsNotBlocked(targetId, now, getClientId()); - } - verifyUnitIsYears(command.getPeriod()); - verifyNotInPendingDelete( - command.getContacts(), - command.getRegistrant(), - command.getNameservers()); - validateContactsHaveTypes(command.getContacts()); - validateRegistrantAllowedOnTld(tld, command.getRegistrantContactId()); - validateNoDuplicateContacts(command.getContacts()); - validateRequiredContactsPresent(command.getRegistrant(), command.getContacts()); - Set fullyQualifiedHostNames = - nullToEmpty(command.getNameserverFullyQualifiedHostNames()); - validateNameserversCountForTld(tld, fullyQualifiedHostNames.size()); - validateNameserversAllowedOnTld(tld, fullyQualifiedHostNames); - // This check is a vile hack that will survive for only a day or so, as I work to flatten the - // domain and application create flows. Without it, the ordering of checks fails lots of tests. - if (!isSuperuser && this instanceof DomainApplicationCreateFlow) { - verifyRegistryStateAllowsLaunchFlows(Registry.get(getTld()), now); - } - validateLaunchCreateExtension(); - // If a signed mark was provided, then it must match the desired domain label. - // We do this after validating the launch create extension so that flows which don't allow any - // signed marks throw a more useful error message rather than complaining about specific issues - // with the signed marks. - if (hasSignedMarks) { - signedMark = verifySignedMarks(launchCreate.getSignedMarks(), domainLabel, now); - } - validateSecDnsExtension(); - verifyDomainCreateIsAllowed(); - } - - @Override - protected void modifyCreateRelatedResources() { - if (lrpToken.isPresent()) { - ofy().save().entity(lrpToken.get().asBuilder() - .setRedemptionHistoryEntry(Key.create(historyEntry)) - .build()); - } - if (extraFlowLogic.isPresent()) { - extraFlowLogic.get().commitAdditionalLogicChanges(); - } - } - - /** Validate the secDNS extension, if present. */ - private void validateSecDnsExtension() throws EppException { - if (secDnsCreate != null) { - if (secDnsCreate.getDsData() == null) { - throw new DsDataRequiredException(); - } - if (secDnsCreate.getMaxSigLife() != null) { - throw new MaxSigLifeNotSupportedException(); - } - validateDsData(secDnsCreate.getDsData()); - } - } - - /** - * If a launch create extension was given (always present for application creates, optional for - * domain creates) then validate it. - */ - private void validateLaunchCreateExtension() throws EppException { - if (launchCreate == null) { - return; - } - if (!isSuperuser) { // Superusers can ignore the phase. - verifyLaunchPhase(getTld(), launchCreate, now); - } - if (launchCreate.hasCodeMarks()) { - throw new UnsupportedMarkTypeException(); - } - validateDomainLaunchCreateExtension(); - LaunchNotice notice = launchCreate.getNotice(); - if (notice == null) { - return; - } - if (!notice.getNoticeId().getValidatorId().equals("tmch")) { - throw new InvalidTrademarkValidatorException(); - } - // Superuser can force domain creations regardless of the current date. - if (!isSuperuser) { - if (notice.getExpirationTime().isBefore(now)) { - throw new ExpiredClaimException(); - } - // An acceptance within the past 48 hours is mandated by the TMCH Functional Spec. - if (notice.getAcceptedTime().isBefore(now.minusHours(48))) { - throw new AcceptedTooLongAgoException(); - } - } - try { - notice.validate(domainLabel); - } catch (IllegalArgumentException e) { - throw new MalformedTcnIdException(); - } catch (InvalidChecksumException e) { - throw new InvalidTcnIdChecksumException(); - } - } - - /** Subclasses may override this to do more specific checks. */ - @SuppressWarnings("unused") - protected void verifyDomainCreateIsAllowed() throws EppException {} - - /** Subclasses may override this to do more specific validation of the launchCreate extension. */ - @SuppressWarnings("unused") - protected void validateDomainLaunchCreateExtension() throws EppException {} - - /** Handle the secDNS extension */ - @Override - protected final void setCreateProperties(B builder) throws EppException { - if (secDnsCreate != null) { - builder.setDsData(secDnsCreate.getDsData()); - } - builder.setLaunchNotice(launchCreate == null ? null : launchCreate.getNotice()); - setDomainCreateProperties(builder); - builder.setIdnTableName(idnTableName); - } - - protected abstract void setDomainCreateProperties(B builder) throws EppException; - - /** Requested domain requires a claims notice. */ - static class MissingClaimsNoticeException extends StatusProhibitsOperationException { - public MissingClaimsNoticeException(String domainName) { - super(String.format("%s requires a claims notice", domainName)); - } - } - - /** Requested domain does not require a claims notice. */ - static class UnexpectedClaimsNoticeException extends StatusProhibitsOperationException { - public UnexpectedClaimsNoticeException(String domainName) { - super(String.format("%s does not require a claims notice", domainName)); - } - } - - /** The claims period for this TLD has ended. */ - static class ClaimsPeriodEndedException extends StatusProhibitsOperationException { - public ClaimsPeriodEndedException(String tld) { - super(String.format("The claims period for %s has ended", tld)); - } - } - - /** The specified trademark validator is not supported. */ - static class InvalidTrademarkValidatorException extends ParameterValuePolicyErrorException { - public InvalidTrademarkValidatorException() { - super("The only supported validationID is 'tmch' for the ICANN Trademark Clearinghouse."); - } - } - - /** At least one dsData is required when using the secDNS extension. */ - static class DsDataRequiredException extends ParameterValuePolicyErrorException { - public DsDataRequiredException() { - super("At least one dsData is required when using the secDNS extension"); - } - } - - /** Only encoded signed marks are supported. */ - static class UnsupportedMarkTypeException extends ParameterValuePolicyErrorException { - public UnsupportedMarkTypeException() { - super("Only encoded signed marks are supported"); - } - } - - /** The 'maxSigLife' setting is not supported. */ - static class MaxSigLifeNotSupportedException extends UnimplementedOptionException { - public MaxSigLifeNotSupportedException() { - super("The 'maxSigLife' setting is not supported"); - } - } - - /** The expiration time specified in the claim notice has elapsed. */ - static class ExpiredClaimException extends ParameterValueRangeErrorException { - public ExpiredClaimException() { - super("The expiration time specified in the claim notice has elapsed"); - } - } - - /** The acceptance time specified in the claim notice is more than 48 hours in the past. */ - static class AcceptedTooLongAgoException extends ParameterValueRangeErrorException { - public AcceptedTooLongAgoException() { - super("The acceptance time specified in the claim notice is more than 48 hours in the past"); - } - } - - /** The specified TCNID is invalid. */ - static class MalformedTcnIdException extends ParameterValueSyntaxErrorException { - public MalformedTcnIdException() { - super("The specified TCNID is malformed"); - } - } - - /** The checksum in the specified TCNID does not validate. */ - static class InvalidTcnIdChecksumException extends ParameterValueRangeErrorException { - public InvalidTcnIdChecksumException() { - super("The checksum in the specified TCNID does not validate"); - } - } -} diff --git a/java/google/registry/flows/domain/ClaimsCheckFlow.java b/java/google/registry/flows/domain/ClaimsCheckFlow.java index cd4424b91..66aea6fbe 100644 --- a/java/google/registry/flows/domain/ClaimsCheckFlow.java +++ b/java/google/registry/flows/domain/ClaimsCheckFlow.java @@ -18,10 +18,10 @@ import static google.registry.flows.ResourceFlowUtils.verifyTargetIdCount; import static google.registry.flows.domain.DomainFlowUtils.checkAllowedAccessToTld; import static google.registry.flows.domain.DomainFlowUtils.validateDomainName; import static google.registry.flows.domain.DomainFlowUtils.validateDomainNameWithIdnTables; +import static google.registry.flows.domain.DomainFlowUtils.verifyClaimsPeriodNotEnded; import static google.registry.flows.domain.DomainFlowUtils.verifyNotInPredelegation; import static google.registry.model.domain.launch.LaunchPhase.CLAIMS; import static google.registry.model.eppoutput.Result.Code.SUCCESS; -import static google.registry.util.DateTimeUtils.isAtOrAfter; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -48,12 +48,12 @@ import javax.inject.Inject; /** * An EPP flow that checks whether strings are trademarked. * - * @error {@link google.registry.flows.domain.DomainFlowUtils.NotAuthorizedForTldException} * @error {@link google.registry.flows.exceptions.TooManyResourceChecksException} * @error {@link DomainFlowUtils.BadCommandForRegistryPhaseException} + * @error {@link DomainFlowUtils.ClaimsPeriodEndedException} + * @error {@link DomainFlowUtils.NotAuthorizedForTldException} * @error {@link DomainFlowUtils.TldDoesNotExistException} * @error {@link ClaimsCheckNotAllowedInSunrise} - * @error {@link ClaimsPeriodEndedException} */ public final class ClaimsCheckFlow extends LoggedInFlow { @@ -85,9 +85,7 @@ public final class ClaimsCheckFlow extends LoggedInFlow { if (registry.getTldState(now) == TldState.SUNRISE) { throw new ClaimsCheckNotAllowedInSunrise(); } - if (isAtOrAfter(now, registry.getClaimsPeriodEnd())) { - throw new ClaimsPeriodEndedException(); - } + verifyClaimsPeriodNotEnded(registry, now); } } String claimKey = ClaimsListShard.get().getClaimKey(domainName.parts().get(0)); @@ -107,11 +105,4 @@ public final class ClaimsCheckFlow extends LoggedInFlow { super("Claims checks are not allowed during sunrise"); } } - - /** The claims period has ended. */ - static class ClaimsPeriodEndedException extends CommandUseErrorException { - public ClaimsPeriodEndedException() { - super("The claims period has ended"); - } - } } diff --git a/java/google/registry/flows/domain/DomainAllocateFlow.java b/java/google/registry/flows/domain/DomainAllocateFlow.java index c25e6d72f..497ad03fe 100644 --- a/java/google/registry/flows/domain/DomainAllocateFlow.java +++ b/java/google/registry/flows/domain/DomainAllocateFlow.java @@ -14,30 +14,70 @@ package google.registry.flows.domain; +import static com.google.common.collect.Iterables.filter; +import static com.google.common.collect.Iterables.getOnlyElement; +import static google.registry.flows.ResourceFlowUtils.verifyResourceDoesNotExist; +import static google.registry.flows.domain.DomainFlowUtils.cloneAndLinkReferences; +import static google.registry.flows.domain.DomainFlowUtils.createFeeCreateResponse; +import static google.registry.flows.domain.DomainFlowUtils.failfastForCreate; import static google.registry.flows.domain.DomainFlowUtils.getReservationType; +import static google.registry.flows.domain.DomainFlowUtils.prepareMarkedLrpTokenEntity; +import static google.registry.flows.domain.DomainFlowUtils.validateCreateCommandContactsAndNameservers; +import static google.registry.flows.domain.DomainFlowUtils.validateDomainName; +import static google.registry.flows.domain.DomainFlowUtils.validateDomainNameWithIdnTables; +import static google.registry.flows.domain.DomainFlowUtils.validateSecDnsExtension; +import static google.registry.flows.domain.DomainFlowUtils.verifyUnitIsYears; +import static google.registry.model.EppResourceUtils.createDomainRoid; import static google.registry.model.EppResourceUtils.loadDomainApplication; +import static google.registry.model.domain.fee.Fee.FEE_CREATE_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER; import static google.registry.model.ofy.ObjectifyService.ofy; +import static google.registry.model.registry.label.ReservedList.matchesAnchorTenantReservation; import static google.registry.pricing.PricingEngineProxy.getDomainCreateCost; import static google.registry.util.CollectionUtils.isNullOrEmpty; +import static google.registry.util.DateTimeUtils.END_OF_TIME; +import static google.registry.util.DateTimeUtils.leapSafeAddYears; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import com.google.common.net.InternetDomainName; import com.googlecode.objectify.Key; +import google.registry.dns.DnsQueue; import google.registry.flows.EppException; import google.registry.flows.EppException.AuthorizationErrorException; import google.registry.flows.EppException.ObjectDoesNotExistException; import google.registry.flows.EppException.StatusProhibitsOperationException; +import google.registry.flows.FlowModule.ClientId; +import google.registry.flows.FlowModule.TargetId; +import google.registry.flows.LoggedInFlow; +import google.registry.flows.TransactionalFlow; +import google.registry.flows.domain.TldSpecificLogicProxy.EppCommandOperations; +import google.registry.model.ImmutableObject; import google.registry.model.billing.BillingEvent; import google.registry.model.billing.BillingEvent.Flag; import google.registry.model.billing.BillingEvent.Reason; import google.registry.model.domain.DomainApplication; -import google.registry.model.domain.DomainResource.Builder; +import google.registry.model.domain.DomainCommand.Create; +import google.registry.model.domain.DomainResource; import google.registry.model.domain.GracePeriod; +import google.registry.model.domain.Period; import google.registry.model.domain.allocate.AllocateCreateExtension; +import google.registry.model.domain.fee.FeeTransformCommandExtension; +import google.registry.model.domain.fee.FeeTransformResponseExtension; +import google.registry.model.domain.flags.FlagsCreateCommandExtension; import google.registry.model.domain.launch.ApplicationStatus; import google.registry.model.domain.launch.LaunchInfoResponseExtension; +import google.registry.model.domain.metadata.MetadataExtension; import google.registry.model.domain.rgp.GracePeriodStatus; +import google.registry.model.domain.secdns.SecDnsCreateExtension; +import google.registry.model.eppcommon.AuthInfo; import google.registry.model.eppcommon.StatusValue; +import google.registry.model.eppinput.ResourceCommand; +import google.registry.model.eppoutput.CreateData.DomainCreateData; +import google.registry.model.eppoutput.EppOutput; +import google.registry.model.eppoutput.Result; +import google.registry.model.index.EppResourceIndex; +import google.registry.model.index.ForeignKeyIndex; +import google.registry.model.ofy.ObjectifyService; import google.registry.model.poll.PendingActionNotificationResponse.DomainPendingActionNotificationResponse; import google.registry.model.poll.PollMessage; import google.registry.model.registry.Registry; @@ -45,140 +85,285 @@ import google.registry.model.registry.label.ReservationType; import google.registry.model.reporting.HistoryEntry; import google.registry.tmch.LordnTask; import javax.inject.Inject; +import org.joda.time.DateTime; /** * An EPP flow that allocates a new domain resource from a domain application. * - * @error {@link google.registry.flows.ResourceCreateFlow.ResourceAlreadyExistsException} - * @error {@link google.registry.flows.domain.DomainFlowUtils.NotAuthorizedForTldException} + * @error {@link google.registry.flows.exceptions.ResourceAlreadyExistsException} * @error {@link DomainAllocateFlow.HasFinalStatusException} * @error {@link DomainAllocateFlow.MissingApplicationException} * @error {@link DomainAllocateFlow.OnlySuperuserCanAllocateException} */ -public class DomainAllocateFlow extends DomainCreateOrAllocateFlow { +public class DomainAllocateFlow extends LoggedInFlow implements TransactionalFlow { - protected AllocateCreateExtension allocateCreate; - protected DomainApplication application; + private static final String COLLISION_MESSAGE = + "Domain on the name collision list was allocated. But by policy, the domain will not be " + + "delegated. Please visit https://www.icann.org/namecollision for more information on name " + + "collision."; + @Inject AuthInfo authInfo; + @Inject ResourceCommand resourceCommand; + @Inject @ClientId String clientId; + @Inject @TargetId String targetId; + @Inject HistoryEntry.Builder historyBuilder; @Inject DomainAllocateFlow() {} @Override - protected final void initDomainCreateOrAllocateFlow() { - registerExtensions(AllocateCreateExtension.class); - allocateCreate = eppInput.getSingleExtension(AllocateCreateExtension.class); + protected final void initLoggedInFlow() throws EppException { + registerExtensions(FEE_CREATE_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER); + registerExtensions( + SecDnsCreateExtension.class, + FlagsCreateCommandExtension.class, + MetadataExtension.class, + AllocateCreateExtension.class); } @Override - protected final void verifyDomainCreateIsAllowed() throws EppException { + public final EppOutput run() throws EppException { + verifyIsSuperuser(); + Create command = cloneAndLinkReferences((Create) resourceCommand, now); + failfastForCreate(targetId, now); + verifyResourceDoesNotExist(DomainResource.class, targetId, now); + InternetDomainName domainName = validateDomainName(command.getFullyQualifiedDomainName()); + Registry registry = Registry.get(domainName.parent().toString()); + Period period = command.getPeriod(); + Integer years = period.getValue(); + verifyUnitIsYears(period); + validateCreateCommandContactsAndNameservers(command, registry.getTldStr()); + SecDnsCreateExtension secDnsCreate = + validateSecDnsExtension(eppInput.getSingleExtension(SecDnsCreateExtension.class)); + boolean isSunrushAddGracePeriod = isNullOrEmpty(command.getNameservers()); + AllocateCreateExtension allocateCreate = + eppInput.getSingleExtension(AllocateCreateExtension.class); + DomainApplication application = loadAndValidateApplication(allocateCreate.getApplicationRoid()); + String repoId = createDomainRoid(ObjectifyService.allocateId(), registry.getTldStr()); + ImmutableSet.Builder entitiesToSave = new ImmutableSet.Builder<>(); + HistoryEntry historyEntry = buildHistory(repoId, period); + entitiesToSave.add(historyEntry); + ImmutableSet billsAndPolls = createBillingEventsAndPollMessages( + domainName, application, historyEntry, isSunrushAddGracePeriod, registry, years); + entitiesToSave.addAll(billsAndPolls); + DateTime registrationExpirationTime = leapSafeAddYears(now, years); + DomainResource.Builder domainBuilder = new DomainResource.Builder(); + command.applyTo(domainBuilder); + DomainResource newDomain = domainBuilder + .setCreationClientId(clientId) + .setCurrentSponsorClientId(clientId) + .setRepoId(repoId) + .setIdnTableName(validateDomainNameWithIdnTables(domainName)) + .setRegistrationExpirationTime(registrationExpirationTime) + .setAutorenewBillingEvent(Key.create(getOnly(billsAndPolls, BillingEvent.Recurring.class))) + .setAutorenewPollMessage(Key.create(getOnly(billsAndPolls, PollMessage.Autorenew.class))) + .setApplicationTime(allocateCreate.getApplicationTime()) + .setApplication(Key.create(application)) + .setSmdId(allocateCreate.getSmdId()) + .setLaunchNotice(allocateCreate.getNotice()) + .setDsData(secDnsCreate == null ? application.getDsData() : secDnsCreate.getDsData()) + .addGracePeriod(createGracePeriod( + isSunrushAddGracePeriod, getOnly(billsAndPolls, BillingEvent.OneTime.class))) + // Names on the collision list will not be delegated. Set server hold. + .setStatusValues(ReservationType.NAME_COLLISION == getReservationType(domainName) + ? ImmutableSet.of(StatusValue.SERVER_HOLD) + : ImmutableSet.of()) + .build(); + entitiesToSave.add( + newDomain, + buildApplicationHistory(application), + updateApplication(application), + ForeignKeyIndex.create(newDomain, newDomain.getDeletionTime()), + EppResourceIndex.create(Key.create(newDomain))); + // Anchor tenant registrations override LRP. + String authInfoToken = authInfo.getPw().getValue(); + if (hasLrpToken(domainName, registry, authInfoToken)) { + entitiesToSave.add(prepareMarkedLrpTokenEntity(authInfoToken, domainName, historyEntry)); + } + ofy().save().entities(entitiesToSave.build()); + enqueueTasks(allocateCreate, newDomain); + return createOutput( + Result.Code.SUCCESS, + DomainCreateData.create(targetId, now, registrationExpirationTime), + createResponseExtensions(registry, years)); + } + + private T getOnly( + Iterable objects, Class clazz) { + return getOnlyElement(filter(objects, clazz)); + } + + private void verifyIsSuperuser() throws OnlySuperuserCanAllocateException { if (!isSuperuser) { throw new OnlySuperuserCanAllocateException(); } - String applicationRoid = allocateCreate.getApplicationRoid(); - application = loadDomainApplication(applicationRoid, now); + } + + private DomainApplication loadAndValidateApplication(String applicationRoid) throws EppException { + DomainApplication application = loadDomainApplication(applicationRoid, now); if (application == null) { throw new MissingApplicationException(applicationRoid); } if (application.getApplicationStatus().isFinalStatus()) { throw new HasFinalStatusException(); } + return application; } - @Override - protected final void setDomainCreateOrAllocateProperties(Builder builder) { - boolean sunrushAddGracePeriod = isNullOrEmpty(command.getNameservers()); - Registry registry = Registry.get(getTld()); - ImmutableSet.Builder billingFlagsBuilder = new ImmutableSet.Builder<>(); - if (!application.getEncodedSignedMarks().isEmpty()) { - billingFlagsBuilder.add(Flag.SUNRISE); - } else { - billingFlagsBuilder.add(Flag.LANDRUSH); - } - BillingEvent.OneTime billingEvent = new BillingEvent.OneTime.Builder() + private HistoryEntry buildHistory(String repoId, Period period) { + return historyBuilder + .setType(HistoryEntry.Type.DOMAIN_ALLOCATE) + .setPeriod(period) + .setModificationTime(now) + .setParent(Key.create(DomainResource.class, repoId)) + .build(); + } + + private ImmutableSet createBillingEventsAndPollMessages( + InternetDomainName domainName, + DomainApplication application, + HistoryEntry historyEntry, + boolean isSunrushAddGracePeriod, + Registry registry, + int years) { + DateTime registrationExpirationTime = leapSafeAddYears(now, years); + BillingEvent.OneTime oneTimeBillingEvent = createOneTimeBillingEvent( + application, historyEntry, isSunrushAddGracePeriod, registry, years); + PollMessage.OneTime oneTimePollMessage = + createOneTimePollMessage(application, historyEntry, getReservationType(domainName)); + // Create a new autorenew billing event and poll message starting at the expiration time. + BillingEvent.Recurring autorenewBillingEvent = + createAutorenewBillingEvent(historyEntry, registrationExpirationTime); + PollMessage.Autorenew autorenewPollMessage = + createAutorenewPollMessage(historyEntry, registrationExpirationTime); + return ImmutableSet.of( + oneTimePollMessage, + oneTimeBillingEvent, + autorenewBillingEvent, + autorenewPollMessage); + } + + private BillingEvent.OneTime createOneTimeBillingEvent( + DomainApplication application, + HistoryEntry historyEntry, + boolean isSunrushAddGracePeriod, + Registry registry, + int years) { + return new BillingEvent.OneTime.Builder() .setReason(Reason.CREATE) - .setFlags(billingFlagsBuilder.add(Flag.ALLOCATION).build()) + .setFlags(ImmutableSet.of( + Flag.ALLOCATION, + application.getEncodedSignedMarks().isEmpty() ? Flag.LANDRUSH : Flag.SUNRISE)) .setTargetId(targetId) - .setClientId(getClientId()) + .setClientId(clientId) // Note that the cost is calculated as of now, i.e. the event time, not the billing time, // which may be some additional days into the future. - .setCost(getDomainCreateCost(targetId, now, command.getPeriod().getValue())) - .setPeriodYears(command.getPeriod().getValue()) + .setCost(getDomainCreateCost(targetId, now, years)) + .setPeriodYears(years) .setEventTime(now) // If there are no nameservers on the domain, then they get the benefit of the sunrush // add grace period, which is longer than the standard add grace period. - .setBillingTime( - now.plus( - sunrushAddGracePeriod - ? registry.getSunrushAddGracePeriodLength() - : registry.getAddGracePeriodLength())) + .setBillingTime(now.plus(isSunrushAddGracePeriod + ? registry.getSunrushAddGracePeriodLength() + : registry.getAddGracePeriodLength())) .setParent(historyEntry) .build(); - ReservationType reservationType = getReservationType(domainName); - ofy().save().entities( - // Save the billing event - billingEvent, - // Update the application itself. - application.asBuilder() - .setApplicationStatus(ApplicationStatus.ALLOCATED) - .removeStatusValue(StatusValue.PENDING_CREATE) - .build(), - // Create a poll message informing the registrar that the application status was updated. - new PollMessage.OneTime.Builder() - .setClientId(application.getCurrentSponsorClientId()) - .setEventTime(ofy().getTransactionTime()) - .setMsg(reservationType == ReservationType.NAME_COLLISION - // Change the poll message to remind the registrar of the name collision policy. - ? "Domain on the name collision list was allocated. " - + "But by policy, the domain will not be delegated. " - + "Please visit https://www.icann.org/namecollision " - + "for more information on name collision." - : "Domain was allocated") - .setResponseData(ImmutableList.of( - DomainPendingActionNotificationResponse.create( - application.getFullyQualifiedDomainName(), - true, - application.getCreationTrid(), - now))) - .setResponseExtensions(ImmutableList.of( - new LaunchInfoResponseExtension.Builder() - .setApplicationId(application.getForeignKey()) - .setPhase(application.getPhase()) - .setApplicationStatus(ApplicationStatus.ALLOCATED) - .build())) - .setParent(historyEntry) - .build(), - // Create a history entry (with no xml or trid) to record that we updated the application. - new HistoryEntry.Builder() - .setType(HistoryEntry.Type.DOMAIN_APPLICATION_STATUS_UPDATE) - .setParent(application) - .setModificationTime(now) - .setClientId(application.getCurrentSponsorClientId()) - .setBySuperuser(true) - .build()); - // Set the properties on the new domain. - builder - .addGracePeriod(GracePeriod.forBillingEvent( - sunrushAddGracePeriod ? GracePeriodStatus.SUNRUSH_ADD : GracePeriodStatus.ADD, - billingEvent)) - .setApplicationTime(allocateCreate.getApplicationTime()) - .setApplication(Key.create(application)) - .setSmdId(allocateCreate.getSmdId()) - .setLaunchNotice(allocateCreate.getNotice()); - // Names on the collision list will not be delegated. Set server hold. - if (ReservationType.NAME_COLLISION == reservationType) { - builder.addStatusValue(StatusValue.SERVER_HOLD); - } } - @Override - protected void enqueueLordnTaskIfNeeded() { + private BillingEvent.Recurring createAutorenewBillingEvent( + HistoryEntry historyEntry, DateTime registrationExpirationTime) { + return new BillingEvent.Recurring.Builder() + .setReason(Reason.RENEW) + .setFlags(ImmutableSet.of(Flag.AUTO_RENEW)) + .setTargetId(targetId) + .setClientId(clientId) + .setEventTime(registrationExpirationTime) + .setRecurrenceEndTime(END_OF_TIME) + .setParent(historyEntry) + .build(); + } + + private PollMessage.Autorenew createAutorenewPollMessage( + HistoryEntry historyEntry, DateTime registrationExpirationTime) { + return new PollMessage.Autorenew.Builder() + .setTargetId(targetId) + .setClientId(clientId) + .setEventTime(registrationExpirationTime) + .setMsg("Domain was auto-renewed.") + .setParent(historyEntry) + .build(); + } + + private GracePeriod createGracePeriod(boolean isSunrushAddGracePeriod, + BillingEvent.OneTime oneTimeBillingEvent) { + return GracePeriod.forBillingEvent( + isSunrushAddGracePeriod ? GracePeriodStatus.SUNRUSH_ADD : GracePeriodStatus.ADD, + oneTimeBillingEvent); + } + + /** Create a history entry (with no xml or trid) to record that we updated the application. */ + private HistoryEntry buildApplicationHistory(DomainApplication application) { + return new HistoryEntry.Builder() + .setType(HistoryEntry.Type.DOMAIN_APPLICATION_STATUS_UPDATE) + .setParent(application) + .setModificationTime(now) + .setClientId(application.getCurrentSponsorClientId()) + .setBySuperuser(true) + .build(); + } + + /** Update the application itself. */ + private DomainApplication updateApplication(DomainApplication application) { + return application.asBuilder() + .setApplicationStatus(ApplicationStatus.ALLOCATED) + .removeStatusValue(StatusValue.PENDING_CREATE) + .build(); + } + + /** Create a poll message informing the registrar that the application status was updated. */ + private PollMessage.OneTime createOneTimePollMessage( + DomainApplication application, HistoryEntry historyEntry, ReservationType reservationType) { + return new PollMessage.OneTime.Builder() + .setClientId(historyEntry.getClientId()) + .setEventTime(now) + .setMsg(reservationType == ReservationType.NAME_COLLISION + ? COLLISION_MESSAGE // Remind the registrar of the name collision policy. + : "Domain was allocated") + .setResponseData(ImmutableList.of( + DomainPendingActionNotificationResponse.create( + targetId, true, application.getCreationTrid(), now))) + .setResponseExtensions(ImmutableList.of( + new LaunchInfoResponseExtension.Builder() + .setApplicationId(application.getForeignKey()) + .setPhase(application.getPhase()) + .setApplicationStatus(ApplicationStatus.ALLOCATED) + .build())) + .setParent(historyEntry) + .build(); + } + + private boolean hasLrpToken( + InternetDomainName domainName, Registry registry, String authInfoToken) { + return registry.getLrpPeriod().contains(now) + && !matchesAnchorTenantReservation(domainName, authInfoToken); + } + + private void enqueueTasks(AllocateCreateExtension allocateCreate, DomainResource newDomain) { + if (newDomain.shouldPublishToDns()) { + DnsQueue.create().addDomainRefreshTask(newDomain.getFullyQualifiedDomainName()); + } if (allocateCreate.getSmdId() != null || allocateCreate.getNotice() != null) { - LordnTask.enqueueDomainResourceTask(newResource); + LordnTask.enqueueDomainResourceTask(newDomain); } } - @Override - protected final HistoryEntry.Type getHistoryEntryType() { - return HistoryEntry.Type.DOMAIN_ALLOCATE; + private ImmutableList createResponseExtensions( + Registry registry, int years) throws EppException { + EppCommandOperations commandOperations = TldSpecificLogicProxy.getCreatePrice( + registry, targetId, clientId, now, years, eppInput); + FeeTransformCommandExtension feeCreate = + eppInput.getFirstExtensionOfClasses(FEE_CREATE_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER); + return (feeCreate == null) + ? null + : ImmutableList.of(createFeeCreateResponse(feeCreate, commandOperations)); } /** Domain application with specific ROID does not exist. */ diff --git a/java/google/registry/flows/domain/DomainApplicationCreateFlow.java b/java/google/registry/flows/domain/DomainApplicationCreateFlow.java index 269f767b9..783f9af99 100644 --- a/java/google/registry/flows/domain/DomainApplicationCreateFlow.java +++ b/java/google/registry/flows/domain/DomainApplicationCreateFlow.java @@ -15,31 +15,72 @@ package google.registry.flows.domain; import static com.google.common.collect.Iterables.getOnlyElement; +import static google.registry.flows.ResourceFlowUtils.verifyResourceDoesNotExist; +import static google.registry.flows.domain.DomainFlowUtils.checkAllowedAccessToTld; +import static google.registry.flows.domain.DomainFlowUtils.cloneAndLinkReferences; +import static google.registry.flows.domain.DomainFlowUtils.createFeeCreateResponse; +import static google.registry.flows.domain.DomainFlowUtils.failfastForCreate; +import static google.registry.flows.domain.DomainFlowUtils.prepareMarkedLrpTokenEntity; +import static google.registry.flows.domain.DomainFlowUtils.validateCreateCommandContactsAndNameservers; +import static google.registry.flows.domain.DomainFlowUtils.validateDomainName; +import static google.registry.flows.domain.DomainFlowUtils.validateDomainNameWithIdnTables; import static google.registry.flows.domain.DomainFlowUtils.validateFeeChallenge; +import static google.registry.flows.domain.DomainFlowUtils.validateLaunchCreateNotice; +import static google.registry.flows.domain.DomainFlowUtils.validateSecDnsExtension; +import static google.registry.flows.domain.DomainFlowUtils.verifyClaimsNoticeIfAndOnlyIfNeeded; +import static google.registry.flows.domain.DomainFlowUtils.verifyClaimsPeriodNotEnded; +import static google.registry.flows.domain.DomainFlowUtils.verifyLaunchPhaseMatchesRegistryPhase; +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.verifyRegistryStateAllowsLaunchFlows; +import static google.registry.flows.domain.DomainFlowUtils.verifySignedMarks; +import static google.registry.flows.domain.DomainFlowUtils.verifyUnitIsYears; +import static google.registry.model.EppResourceUtils.createDomainRoid; +import static google.registry.model.domain.fee.Fee.FEE_CREATE_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER; import static google.registry.model.eppoutput.Result.Code.SUCCESS; import static google.registry.model.index.DomainApplicationIndex.loadActiveApplicationsByDomainName; -import static google.registry.model.index.ForeignKeyIndex.loadAndGetKey; +import static google.registry.model.ofy.ObjectifyService.ofy; +import static google.registry.model.registry.label.ReservedList.matchesAnchorTenantReservation; import com.google.common.base.Function; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import com.google.common.net.InternetDomainName; +import com.googlecode.objectify.Key; import google.registry.flows.EppException; import google.registry.flows.EppException.CommandUseErrorException; import google.registry.flows.EppException.ObjectAlreadyExistsException; import google.registry.flows.EppException.RequiredParameterMissingException; +import google.registry.flows.FlowModule.ClientId; +import google.registry.flows.FlowModule.TargetId; +import google.registry.flows.LoggedInFlow; +import google.registry.flows.TransactionalFlow; +import google.registry.flows.domain.TldSpecificLogicProxy.EppCommandOperations; +import google.registry.model.ImmutableObject; import google.registry.model.domain.DomainApplication; -import google.registry.model.domain.DomainApplication.Builder; +import google.registry.model.domain.DomainCommand.Create; import google.registry.model.domain.DomainResource; import google.registry.model.domain.Period; +import google.registry.model.domain.fee.FeeTransformCommandExtension; +import google.registry.model.domain.flags.FlagsCreateCommandExtension; import google.registry.model.domain.launch.ApplicationStatus; import google.registry.model.domain.launch.LaunchCreateExtension; import google.registry.model.domain.launch.LaunchCreateResponseExtension; import google.registry.model.domain.launch.LaunchPhase; +import google.registry.model.domain.metadata.MetadataExtension; +import google.registry.model.domain.secdns.SecDnsCreateExtension; +import google.registry.model.eppcommon.AuthInfo; import google.registry.model.eppcommon.StatusValue; +import google.registry.model.eppinput.ResourceCommand; import google.registry.model.eppoutput.CreateData.DomainCreateData; import google.registry.model.eppoutput.EppOutput; import google.registry.model.eppoutput.EppResponse.ResponseExtension; +import google.registry.model.index.DomainApplicationIndex; +import google.registry.model.index.EppResourceIndex; +import google.registry.model.ofy.ObjectifyService; +import google.registry.model.registry.Registry; import google.registry.model.registry.Registry.TldState; import google.registry.model.reporting.HistoryEntry; import google.registry.model.smd.AbstractSignedMark; @@ -49,28 +90,19 @@ import javax.inject.Inject; /** * An EPP flow that creates a new application for a domain resource. * + * @error {@link google.registry.flows.exceptions.ResourceAlreadyExistsException} * @error {@link google.registry.flows.EppException.UnimplementedExtensionException} - * @error {@link google.registry.flows.ResourceCreateFlow.ResourceAlreadyExistsException} - * @error {@link google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException} - * @error {@link BaseDomainCreateFlow.AcceptedTooLongAgoException} - * @error {@link BaseDomainCreateFlow.ClaimsPeriodEndedException} - * @error {@link BaseDomainCreateFlow.ExpiredClaimException} - * @error {@link BaseDomainCreateFlow.InvalidTcnIdChecksumException} - * @error {@link BaseDomainCreateFlow.InvalidTrademarkValidatorException} - * @error {@link BaseDomainCreateFlow.MalformedTcnIdException} - * @error {@link BaseDomainCreateFlow.MaxSigLifeNotSupportedException} - * @error {@link BaseDomainCreateFlow.MissingClaimsNoticeException} - * @error {@link BaseDomainCreateFlow.UnexpectedClaimsNoticeException} - * @error {@link BaseDomainCreateFlow.UnsupportedMarkTypeException} * @error {@link DomainApplicationCreateFlow.LandrushApplicationDisallowedDuringSunriseException} * @error {@link DomainApplicationCreateFlow.NoticeCannotBeUsedWithSignedMarkException} * @error {@link DomainApplicationCreateFlow.SunriseApplicationDisallowedDuringLandrushException} * @error {@link DomainApplicationCreateFlow.UncontestedSunriseApplicationBlockedInLandrushException} + * @error {@link DomainFlowUtils.AcceptedTooLongAgoException} * @error {@link DomainFlowUtils.BadCommandForRegistryPhaseException} * @error {@link DomainFlowUtils.BadDomainNameCharacterException} * @error {@link DomainFlowUtils.BadDomainNamePartsCountException} * @error {@link DomainFlowUtils.BadPeriodUnitException} * @error {@link DomainFlowUtils.Base64RequiredForEncodedSignedMarksException} + * @error {@link DomainFlowUtils.ClaimsPeriodEndedException} * @error {@link DomainFlowUtils.CurrencyUnitMismatchException} * @error {@link DomainFlowUtils.CurrencyValueScaleException} * @error {@link DomainFlowUtils.DashesInThirdAndFourthException} @@ -78,13 +110,20 @@ import javax.inject.Inject; * @error {@link DomainFlowUtils.DomainReservedException} * @error {@link DomainFlowUtils.DuplicateContactForRoleException} * @error {@link DomainFlowUtils.EmptyDomainNamePartException} + * @error {@link DomainFlowUtils.ExpiredClaimException} * @error {@link DomainFlowUtils.FeesMismatchException} * @error {@link DomainFlowUtils.FeesRequiredForPremiumNameException} * @error {@link DomainFlowUtils.InvalidIdnDomainLabelException} + * @error {@link DomainFlowUtils.InvalidLrpTokenException} * @error {@link DomainFlowUtils.InvalidPunycodeException} + * @error {@link DomainFlowUtils.InvalidTcnIdChecksumException} + * @error {@link DomainFlowUtils.InvalidTrademarkValidatorException} * @error {@link DomainFlowUtils.LaunchPhaseMismatchException} * @error {@link DomainFlowUtils.LeadingDashException} * @error {@link DomainFlowUtils.LinkedResourcesDoNotExistException} + * @error {@link DomainFlowUtils.MalformedTcnIdException} + * @error {@link DomainFlowUtils.MaxSigLifeNotSupportedException} + * @error {@link DomainFlowUtils.MissingClaimsNoticeException} * @error {@link DomainFlowUtils.MissingContactTypeException} * @error {@link DomainFlowUtils.NameserversNotAllowedException} * @error {@link DomainFlowUtils.NameserversNotSpecifiedException} @@ -107,19 +146,131 @@ import javax.inject.Inject; * @error {@link DomainFlowUtils.TooManyNameserversException} * @error {@link DomainFlowUtils.TooManySignedMarksException} * @error {@link DomainFlowUtils.TrailingDashException} + * @error {@link DomainFlowUtils.UnexpectedClaimsNoticeException} * @error {@link DomainFlowUtils.UnsupportedFeeAttributeException} + * @error {@link DomainFlowUtils.UnsupportedMarkTypeException} */ -public class DomainApplicationCreateFlow extends BaseDomainCreateFlow { +public final class DomainApplicationCreateFlow extends LoggedInFlow implements TransactionalFlow { + @Inject AuthInfo authInfo; + @Inject ResourceCommand resourceCommand; + @Inject @ClientId String clientId; + @Inject @TargetId String targetId; + @Inject HistoryEntry.Builder historyBuilder; @Inject DomainApplicationCreateFlow() {} @Override - protected void initDomainCreateFlow() { - registerExtensions(LaunchCreateExtension.class); + protected final void initLoggedInFlow() throws EppException { + registerExtensions(FEE_CREATE_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER); + registerExtensions( + SecDnsCreateExtension.class, + FlagsCreateCommandExtension.class, + MetadataExtension.class, + LaunchCreateExtension.class); } @Override - protected void validateDomainLaunchCreateExtension() throws EppException { + public final EppOutput run() throws EppException { + Create command = cloneAndLinkReferences((Create) resourceCommand, now); + failfastForCreate(targetId, now); + // Fail if the domain is already registered (e.g. this is a landrush application but the domain + // was awarded at the end of sunrise). However, multiple domain applications can be created for + // the same domain name, so don't try to load an existing application. + verifyResourceDoesNotExist(DomainResource.class, targetId, now); + // Validate that this is actually a legal domain name on a TLD that the registrar has access to. + InternetDomainName domainName = validateDomainName(targetId); + String idnTableName = validateDomainNameWithIdnTables(domainName); + String tld = domainName.parent().toString(); + checkAllowedAccessToTld(getAllowedTlds(), tld); + Registry registry = Registry.get(tld); + EppCommandOperations commandOperations = TldSpecificLogicProxy.getCreatePrice( + registry, targetId, clientId, now, command.getPeriod().getValue(), eppInput); + // Superusers can create reserved domains, force creations on domains that require a claims + // notice without specifying a claims key, and override blocks on registering premium domains. + verifyUnitIsYears(command.getPeriod()); + validateCreateCommandContactsAndNameservers(command, tld); + LaunchCreateExtension launchCreate = eppInput.getSingleExtension(LaunchCreateExtension.class); + if (launchCreate != null) { + validateLaunchCreateExtension(launchCreate, registry, domainName); + } + boolean isAnchorTenant = + matchesAnchorTenantReservation(domainName, authInfo.getPw().getValue()); + if (!isSuperuser) { + verifyPremiumNameIsNotBlocked(targetId, now, clientId); + prohibitLandrushIfExactlyOneSunrise(registry); + if (!isAnchorTenant) { + boolean isSunriseApplication = !launchCreate.getSignedMarks().isEmpty(); + verifyNotReserved(domainName, isSunriseApplication); + } + } + FeeTransformCommandExtension feeCreate = + eppInput.getFirstExtensionOfClasses(FEE_CREATE_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER); + validateFeeChallenge(targetId, tld, now, feeCreate, commandOperations.getTotalCost()); + SecDnsCreateExtension secDnsCreate = + validateSecDnsExtension(eppInput.getSingleExtension(SecDnsCreateExtension.class)); + DomainApplication.Builder applicationBuilder = new DomainApplication.Builder(); + command.applyTo(applicationBuilder); + applicationBuilder + .setCreationTrid(trid) + .setCreationClientId(clientId) + .setCurrentSponsorClientId(clientId) + .setRepoId(createDomainRoid(ObjectifyService.allocateId(), tld)) + .setLaunchNotice(launchCreate == null ? null : launchCreate.getNotice()) + .setIdnTableName(idnTableName) + .setPhase(launchCreate.getPhase()) + .setApplicationStatus(ApplicationStatus.VALIDATED) + .addStatusValue(StatusValue.PENDING_CREATE) + .setDsData(secDnsCreate == null ? null : secDnsCreate.getDsData()) + .setEncodedSignedMarks(FluentIterable + .from(launchCreate.getSignedMarks()) + .transform(new Function() { + @Override + public EncodedSignedMark apply(AbstractSignedMark abstractSignedMark) { + return (EncodedSignedMark) abstractSignedMark; + }}) + .toList()); + DomainApplication newApplication = applicationBuilder.build(); + HistoryEntry historyEntry = buildHistory(newApplication.getRepoId(), command.getPeriod()); + ImmutableSet.Builder entitiesToSave = new ImmutableSet.Builder<>(); + entitiesToSave.add( + newApplication, + historyEntry, + DomainApplicationIndex.createUpdatedInstance(newApplication), + EppResourceIndex.create(Key.create(newApplication))); + // Anchor tenant registrations override LRP, and landrush applications can skip it. + if (registry.getLrpPeriod().contains(now) && !isAnchorTenant) { + // TODO(b/32059212): This is a bug: empty tokens should still fail. Preserving to fix in a + // separate targeted change. + if (!authInfo.getPw().getValue().isEmpty()) { + entitiesToSave.add( + prepareMarkedLrpTokenEntity(authInfo.getPw().getValue(), domainName, historyEntry)); + } + } + ofy().save().entities(entitiesToSave.build()); + return createOutput( + SUCCESS, + DomainCreateData.create(targetId, now, null), + createResponseExtensions( + newApplication.getForeignKey(), launchCreate.getPhase(), feeCreate, commandOperations)); + } + + private void validateLaunchCreateExtension( + LaunchCreateExtension launchCreate, Registry registry, InternetDomainName domainName) + throws EppException { + verifyNoCodeMarks(launchCreate); + boolean hasClaimsNotice = launchCreate.getNotice() != null; + if (hasClaimsNotice) { + verifyClaimsPeriodNotEnded(registry, now); + } + boolean isSunriseApplication = !launchCreate.getSignedMarks().isEmpty(); + if (!isSuperuser) { // Superusers can ignore the phase. + verifyRegistryStateAllowsLaunchFlows(registry, now); + verifyLaunchPhaseMatchesRegistryPhase(registry, launchCreate, now); + } + if (now.isBefore(registry.getClaimsPeriodEnd())) { + verifyClaimsNoticeIfAndOnlyIfNeeded(domainName, isSunriseApplication, hasClaimsNotice); + } + TldState tldState = registry.getTldState(now); if (launchCreate.getSignedMarks().isEmpty()) { // During sunrise, a signed mark is required since only trademark holders are allowed to // create an application. However, we found no marks (ie, this was a landrush application). @@ -127,22 +278,28 @@ public class DomainApplicationCreateFlow extends BaseDomainCreateFlow applications = loadActiveApplicationsByDomainName(targetId, now); if (applications.size() == 1 @@ -150,70 +307,32 @@ public class DomainApplicationCreateFlow extends BaseDomainCreateFlow() { - @Override - public EncodedSignedMark apply(AbstractSignedMark abstractSignedMark) { - // We verified that this is the case in verifyDomainCreateIsAllowed(). - return (EncodedSignedMark) abstractSignedMark; - }}) - .toList()); - } + private HistoryEntry buildHistory(String repoId, Period period) { + return historyBuilder + .setType(HistoryEntry.Type.DOMAIN_APPLICATION_CREATE) + .setPeriod(period) + .setModificationTime(now) + .setParent(Key.create(DomainApplication.class, repoId)) + .build(); } - @Override - protected final HistoryEntry.Type getHistoryEntryType() { - return HistoryEntry.Type.DOMAIN_APPLICATION_CREATE; - } - - @Override - protected final Period getCommandPeriod() { - return command.getPeriod(); - } - - @Override - protected boolean tryToLoadExisting() { - // Multiple domain applications can be created for the same targetId (which is the fully - // qualified domain name), so don't try to load an existing resource with the same target id. - return false; - } - - @Override - protected final EppOutput getOutput() { + private ImmutableList createResponseExtensions( + String applicationId, + LaunchPhase launchPhase, + FeeTransformCommandExtension feeCreate, + EppCommandOperations commandOperations) { ImmutableList.Builder responseExtensionsBuilder = new ImmutableList.Builder<>(); responseExtensionsBuilder.add(new LaunchCreateResponseExtension.Builder() - .setPhase(launchCreate.getPhase()) - .setApplicationId(newResource.getForeignKey()) + .setPhase(launchPhase) + .setApplicationId(applicationId) .build()); if (feeCreate != null) { - responseExtensionsBuilder.add(feeCreate.createResponseBuilder() - .setCurrency(commandOperations.getCurrency()) - .setFees(commandOperations.getFees()) - .setCredits(commandOperations.getCredits()) - .build()); + responseExtensionsBuilder.add(createFeeCreateResponse(feeCreate, commandOperations)); } - - return createOutput( - SUCCESS, - DomainCreateData.create(newResource.getFullyQualifiedDomainName(), now, null), - responseExtensionsBuilder.build()); + return responseExtensionsBuilder.build(); } /** Landrush applications are disallowed during sunrise. */ diff --git a/java/google/registry/flows/domain/DomainApplicationDeleteFlow.java b/java/google/registry/flows/domain/DomainApplicationDeleteFlow.java index d0a186c7f..3a4edad5a 100644 --- a/java/google/registry/flows/domain/DomainApplicationDeleteFlow.java +++ b/java/google/registry/flows/domain/DomainApplicationDeleteFlow.java @@ -22,7 +22,7 @@ import static google.registry.flows.ResourceFlowUtils.verifyOptionalAuthInfoForR import static google.registry.flows.ResourceFlowUtils.verifyResourceOwnership; import static google.registry.flows.domain.DomainFlowUtils.checkAllowedAccessToTld; import static google.registry.flows.domain.DomainFlowUtils.verifyApplicationDomainMatchesTargetId; -import static google.registry.flows.domain.DomainFlowUtils.verifyLaunchPhase; +import static google.registry.flows.domain.DomainFlowUtils.verifyLaunchPhaseMatchesRegistryPhase; import static google.registry.flows.domain.DomainFlowUtils.verifyRegistryStateAllowsLaunchFlows; import static google.registry.model.EppResourceUtils.loadDomainApplication; import static google.registry.model.eppoutput.Result.Code.SUCCESS; @@ -84,12 +84,14 @@ public final class DomainApplicationDeleteFlow extends LoggedInFlow implements T String tld = existingApplication.getTld(); checkAllowedAccessToTld(getAllowedTlds(), tld); if (!isSuperuser) { - verifyRegistryStateAllowsLaunchFlows(Registry.get(tld), now); - verifyLaunchPhase(tld, eppInput.getSingleExtension(LaunchDeleteExtension.class), now); + Registry registry = Registry.get(tld); + verifyRegistryStateAllowsLaunchFlows(registry, now); + verifyLaunchPhaseMatchesRegistryPhase( + registry, eppInput.getSingleExtension(LaunchDeleteExtension.class), now); verifyResourceOwnership(clientId, existingApplication); // Don't allow deleting a sunrise application during landrush. if (existingApplication.getPhase().equals(LaunchPhase.SUNRISE) - && Registry.get(tld).getTldState(now).equals(TldState.LANDRUSH)) { + && registry.getTldState(now).equals(TldState.LANDRUSH)) { throw new SunriseApplicationCannotBeDeletedInLandrushException(); } } diff --git a/java/google/registry/flows/domain/DomainCreateFlow.java b/java/google/registry/flows/domain/DomainCreateFlow.java index 0800189c9..db0de9fe5 100644 --- a/java/google/registry/flows/domain/DomainCreateFlow.java +++ b/java/google/registry/flows/domain/DomainCreateFlow.java @@ -14,51 +14,99 @@ package google.registry.flows.domain; +import static google.registry.flows.ResourceFlowUtils.verifyResourceDoesNotExist; +import static google.registry.flows.domain.DomainFlowUtils.checkAllowedAccessToTld; +import static google.registry.flows.domain.DomainFlowUtils.cloneAndLinkReferences; +import static google.registry.flows.domain.DomainFlowUtils.createFeeCreateResponse; +import static google.registry.flows.domain.DomainFlowUtils.failfastForCreate; +import static google.registry.flows.domain.DomainFlowUtils.prepareMarkedLrpTokenEntity; +import static google.registry.flows.domain.DomainFlowUtils.validateCreateCommandContactsAndNameservers; +import static google.registry.flows.domain.DomainFlowUtils.validateDomainName; +import static google.registry.flows.domain.DomainFlowUtils.validateDomainNameWithIdnTables; import static google.registry.flows.domain.DomainFlowUtils.validateFeeChallenge; +import static google.registry.flows.domain.DomainFlowUtils.validateLaunchCreateNotice; +import static google.registry.flows.domain.DomainFlowUtils.validateSecDnsExtension; +import static google.registry.flows.domain.DomainFlowUtils.verifyClaimsNoticeIfAndOnlyIfNeeded; +import static google.registry.flows.domain.DomainFlowUtils.verifyClaimsPeriodNotEnded; +import static google.registry.flows.domain.DomainFlowUtils.verifyLaunchPhaseMatchesRegistryPhase; +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.verifySignedMarks; +import static google.registry.flows.domain.DomainFlowUtils.verifyUnitIsYears; +import static google.registry.model.EppResourceUtils.createDomainRoid; +import static google.registry.model.domain.fee.Fee.FEE_CREATE_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER; import static google.registry.model.index.DomainApplicationIndex.loadActiveApplicationsByDomainName; import static google.registry.model.ofy.ObjectifyService.ofy; +import static google.registry.model.registry.label.ReservedList.matchesAnchorTenantReservation; +import static google.registry.util.DateTimeUtils.END_OF_TIME; +import static google.registry.util.DateTimeUtils.leapSafeAddYears; +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; +import com.google.common.net.InternetDomainName; +import com.googlecode.objectify.Key; +import google.registry.dns.DnsQueue; import google.registry.flows.EppException; import google.registry.flows.EppException.CommandUseErrorException; import google.registry.flows.EppException.StatusProhibitsOperationException; +import google.registry.flows.FlowModule.ClientId; +import google.registry.flows.FlowModule.TargetId; +import google.registry.flows.LoggedInFlow; +import google.registry.flows.TransactionalFlow; +import google.registry.flows.domain.TldSpecificLogicProxy.EppCommandOperations; +import google.registry.model.ImmutableObject; import google.registry.model.billing.BillingEvent; +import google.registry.model.billing.BillingEvent.Flag; +import google.registry.model.billing.BillingEvent.OneTime; import google.registry.model.billing.BillingEvent.Reason; +import google.registry.model.billing.BillingEvent.Recurring; import google.registry.model.domain.DomainApplication; -import google.registry.model.domain.DomainResource.Builder; +import google.registry.model.domain.DomainCommand.Create; +import google.registry.model.domain.DomainResource; import google.registry.model.domain.GracePeriod; +import google.registry.model.domain.Period; +import google.registry.model.domain.fee.FeeTransformCommandExtension; +import google.registry.model.domain.fee.FeeTransformResponseExtension; +import google.registry.model.domain.flags.FlagsCreateCommandExtension; import google.registry.model.domain.launch.LaunchCreateExtension; +import google.registry.model.domain.metadata.MetadataExtension; import google.registry.model.domain.rgp.GracePeriodStatus; +import google.registry.model.domain.secdns.SecDnsCreateExtension; +import google.registry.model.eppcommon.AuthInfo; +import google.registry.model.eppinput.ResourceCommand; +import google.registry.model.eppoutput.CreateData.DomainCreateData; +import google.registry.model.eppoutput.EppOutput; +import google.registry.model.eppoutput.Result; +import google.registry.model.index.EppResourceIndex; +import google.registry.model.index.ForeignKeyIndex; +import google.registry.model.ofy.ObjectifyService; +import google.registry.model.poll.PollMessage; +import google.registry.model.poll.PollMessage.Autorenew; import google.registry.model.registry.Registry; import google.registry.model.registry.Registry.TldState; import google.registry.model.reporting.HistoryEntry; import google.registry.tmch.LordnTask; import java.util.Set; import javax.inject.Inject; +import org.joda.time.DateTime; /** * An EPP flow that creates a new domain resource. * + * @error {@link google.registry.flows.exceptions.OnlyToolCanPassMetadataException} + * @error {@link google.registry.flows.exceptions.ResourceAlreadyExistsException} * @error {@link google.registry.flows.EppException.UnimplementedExtensionException} * @error {@link google.registry.flows.LoggedInFlow.UndeclaredServiceExtensionException} - * @error {@link google.registry.flows.ResourceCreateFlow.ResourceAlreadyExistsException} - * @error {@link google.registry.flows.ResourceCreateOrMutateFlow.OnlyToolCanPassMetadataException} * @error {@link google.registry.flows.domain.DomainFlowUtils.NotAuthorizedForTldException} - * @error {@link BaseDomainCreateFlow.AcceptedTooLongAgoException} - * @error {@link BaseDomainCreateFlow.ClaimsPeriodEndedException} - * @error {@link BaseDomainCreateFlow.ExpiredClaimException} - * @error {@link BaseDomainCreateFlow.InvalidTcnIdChecksumException} - * @error {@link BaseDomainCreateFlow.InvalidTrademarkValidatorException} - * @error {@link BaseDomainCreateFlow.MalformedTcnIdException} - * @error {@link BaseDomainCreateFlow.MaxSigLifeNotSupportedException} - * @error {@link BaseDomainCreateFlow.MissingClaimsNoticeException} - * @error {@link BaseDomainCreateFlow.UnexpectedClaimsNoticeException} - * @error {@link BaseDomainCreateFlow.UnsupportedMarkTypeException} * @error {@link DomainCreateFlow.SignedMarksNotAcceptedInCurrentPhaseException} + * @error {@link DomainFlowUtils.AcceptedTooLongAgoException} * @error {@link DomainFlowUtils.BadDomainNameCharacterException} * @error {@link DomainFlowUtils.BadDomainNamePartsCountException} * @error {@link DomainFlowUtils.BadPeriodUnitException} + * @error {@link DomainFlowUtils.ClaimsPeriodEndedException} * @error {@link DomainFlowUtils.CurrencyUnitMismatchException} * @error {@link DomainFlowUtils.CurrencyValueScaleException} * @error {@link DomainFlowUtils.DashesInThirdAndFourthException} @@ -66,14 +114,20 @@ import javax.inject.Inject; * @error {@link DomainFlowUtils.DomainReservedException} * @error {@link DomainFlowUtils.DuplicateContactForRoleException} * @error {@link DomainFlowUtils.EmptyDomainNamePartException} + * @error {@link DomainFlowUtils.ExpiredClaimException} * @error {@link DomainFlowUtils.FeesMismatchException} * @error {@link DomainFlowUtils.FeesRequiredForPremiumNameException} * @error {@link DomainFlowUtils.InvalidIdnDomainLabelException} * @error {@link DomainFlowUtils.InvalidPunycodeException} + * @error {@link DomainFlowUtils.InvalidTcnIdChecksumException} + * @error {@link DomainFlowUtils.InvalidTrademarkValidatorException} * @error {@link DomainFlowUtils.LeadingDashException} * @error {@link DomainFlowUtils.LinkedResourcesDoNotExistException} * @error {@link DomainFlowUtils.LinkedResourceInPendingDeleteProhibitsOperationException} + * @error {@link DomainFlowUtils.MalformedTcnIdException} + * @error {@link DomainFlowUtils.MaxSigLifeNotSupportedException} * @error {@link DomainFlowUtils.MissingAdminContactException} + * @error {@link DomainFlowUtils.MissingClaimsNoticeException} * @error {@link DomainFlowUtils.MissingContactTypeException} * @error {@link DomainFlowUtils.MissingRegistrantException} * @error {@link DomainFlowUtils.MissingTechnicalContactException} @@ -85,127 +139,287 @@ import javax.inject.Inject; * @error {@link DomainFlowUtils.TooManyDsRecordsException} * @error {@link DomainFlowUtils.TooManyNameserversException} * @error {@link DomainFlowUtils.TrailingDashException} + * @error {@link DomainFlowUtils.UnexpectedClaimsNoticeException} * @error {@link DomainFlowUtils.UnsupportedFeeAttributeException} + * @error {@link DomainFlowUtils.UnsupportedMarkTypeException} * @error {@link DomainCreateFlow.DomainHasOpenApplicationsException} * @error {@link DomainCreateFlow.NoGeneralRegistrationsInCurrentPhaseException} */ -public class DomainCreateFlow extends DomainCreateOrAllocateFlow { +public class DomainCreateFlow extends LoggedInFlow implements TransactionalFlow { private static final Set QLP_SMD_ALLOWED_STATES = Sets.immutableEnumSet(TldState.SUNRISE, TldState.SUNRUSH); + @Inject AuthInfo authInfo; + @Inject ResourceCommand resourceCommand; + @Inject @ClientId String clientId; + @Inject @TargetId String targetId; + @Inject HistoryEntry.Builder historyBuilder; @Inject DomainCreateFlow() {} - private boolean isAnchorTenant() { - return isAnchorTenantViaReservation || isAnchorTenantViaExtension; + @Override + protected final void initLoggedInFlow() throws EppException { + registerExtensions(FEE_CREATE_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER); + registerExtensions( + SecDnsCreateExtension.class, + FlagsCreateCommandExtension.class, + MetadataExtension.class, + LaunchCreateExtension.class); } @Override - protected final void verifyDomainCreateIsAllowed() throws EppException { - String tld = getTld(); - validateFeeChallenge(targetId, tld, now, feeCreate, commandOperations.getTotalCost()); + public final EppOutput run() throws EppException { + Create command = cloneAndLinkReferences((Create) resourceCommand, now); + Period period = command.getPeriod(); + verifyUnitIsYears(period); + int years = period.getValue(); + failfastForCreate(targetId, now); + verifyResourceDoesNotExist(DomainResource.class, targetId, now); + // Validate that this is actually a legal domain name on a TLD that the registrar has access to. + InternetDomainName domainName = validateDomainName(command.getFullyQualifiedDomainName()); + String domainLabel = domainName.parts().get(0); + Registry registry = Registry.get(domainName.parent().toString()); + validateCreateCommandContactsAndNameservers(command, registry.getTldStr()); + TldState tldState = registry.getTldState(now); + boolean isAnchorTenant = isAnchorTenant(domainName); + LaunchCreateExtension launchCreate = eppInput.getSingleExtension(LaunchCreateExtension.class); + boolean hasSignedMarks = launchCreate != null && !launchCreate.getSignedMarks().isEmpty(); + boolean hasClaimsNotice = launchCreate != null && launchCreate.getNotice() != null; + if (launchCreate != null) { + verifyNoCodeMarks(launchCreate); + validateLaunchCreateNotice(launchCreate.getNotice(), domainLabel, isSuperuser, now); + } + if (hasSignedMarks) { + verifySignedMarksAllowed(tldState, isAnchorTenant); + } + FeeTransformCommandExtension feeCreate = + eppInput.getFirstExtensionOfClasses(FEE_CREATE_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER); + EppCommandOperations commandOperations = TldSpecificLogicProxy.getCreatePrice( + registry, targetId, clientId, now, years, eppInput); + validateFeeChallenge( + targetId, registry.getTldStr(), now, feeCreate, commandOperations.getTotalCost()); + // Superusers can create reserved domains, force creations on domains that require a claims + // notice without specifying a claims key, ignore the registry phase, and override blocks on + // registering premium domains. if (!isSuperuser) { - // Prohibit creating a domain if there is an open application for the same name. - for (DomainApplication application : loadActiveApplicationsByDomainName(targetId, now)) { - if (!application.getApplicationStatus().isFinalStatus()) { - throw new DomainHasOpenApplicationsException(); - } + checkAllowedAccessToTld(getAllowedTlds(), registry.getTldStr()); + if (launchCreate != null) { + verifyLaunchPhaseMatchesRegistryPhase(registry, launchCreate, now); } - // Prohibit registrations for non-qlp and non-superuser outside of GA. - if (!isAnchorTenant() - && Registry.get(tld).getTldState(now) != TldState.GENERAL_AVAILABILITY) { - throw new NoGeneralRegistrationsInCurrentPhaseException(); + if (!isAnchorTenant) { + verifyNotReserved(domainName, hasSignedMarks); + } + if (hasClaimsNotice) { + verifyClaimsPeriodNotEnded(registry, now); + } + if (now.isBefore(registry.getClaimsPeriodEnd())) { + verifyClaimsNoticeIfAndOnlyIfNeeded(domainName, hasSignedMarks, hasClaimsNotice); + } + verifyPremiumNameIsNotBlocked(targetId, now, clientId); + verifyNoOpenApplications(); + verifyIsGaOrIsSpecialCase(tldState, isAnchorTenant); + } + SecDnsCreateExtension secDnsCreate = + validateSecDnsExtension(eppInput.getSingleExtension(SecDnsCreateExtension.class)); + String repoId = createDomainRoid(ObjectifyService.allocateId(), registry.getTldStr()); + DateTime registrationExpirationTime = leapSafeAddYears(now, years); + HistoryEntry historyEntry = buildHistory(repoId, period); + // Bill for the create. + BillingEvent.OneTime createBillingEvent = + createOneTimeBillingEvent(registry, isAnchorTenant, years, commandOperations, historyEntry); + // Create a new autorenew billing event and poll message starting at the expiration time. + BillingEvent.Recurring autorenewBillingEvent = + createAutorenewBillingEvent(historyEntry, registrationExpirationTime); + PollMessage.Autorenew autorenewPollMessage = + createAutorenewPollMessage(historyEntry, registrationExpirationTime); + ImmutableSet.Builder entitiesToSave = new ImmutableSet.Builder<>(); + entitiesToSave.add( + historyEntry, + createBillingEvent, + autorenewBillingEvent, + autorenewPollMessage); + // Bill for EAP cost, if any. + if (!commandOperations.getEapCost().isZero()) { + entitiesToSave.add(createEapBillingEvent(commandOperations, createBillingEvent)); + } + DomainResource.Builder domainBuilder = new DomainResource.Builder(); + command.applyTo(domainBuilder); + DomainResource newDomain = domainBuilder + .setCreationClientId(clientId) + .setCurrentSponsorClientId(clientId) + .setRepoId(repoId) + .setIdnTableName(validateDomainNameWithIdnTables(domainName)) + .setRegistrationExpirationTime(registrationExpirationTime) + .setAutorenewBillingEvent(Key.create(autorenewBillingEvent)) + .setAutorenewPollMessage(Key.create(autorenewPollMessage)) + .setLaunchNotice(hasClaimsNotice ? launchCreate.getNotice() : null) + .setSmdId(hasSignedMarks + // If a signed mark was provided, then it must match the desired domain label. + ? verifySignedMarks(launchCreate.getSignedMarks(), domainLabel, now).getId() + : null) + .setDsData(secDnsCreate == null ? null : secDnsCreate.getDsData()) + .addGracePeriod(GracePeriod.forBillingEvent(GracePeriodStatus.ADD, createBillingEvent)) + .build(); + handleExtraFlowLogic(registry.getTldStr(), years, historyEntry, newDomain); + entitiesToSave.add( + newDomain, + ForeignKeyIndex.create(newDomain, newDomain.getDeletionTime()), + EppResourceIndex.create(Key.create(newDomain))); + // Anchor tenant registrations override LRP, and landrush applications can skip it. + if (hasLrpToken(registry, isAnchorTenant)) { + // TODO(b/32059212): This is a bug: empty tokens should still fail. Preserving to fix in a + // separate targeted change. + if (!authInfo.getPw().getValue().isEmpty()) { + entitiesToSave.add( + prepareMarkedLrpTokenEntity(authInfo.getPw().getValue(), domainName, historyEntry)); } } + enqueueTasks(hasSignedMarks, hasClaimsNotice, newDomain); + ofy().save().entities(entitiesToSave.build()); + return createOutput( + Result.Code.SUCCESS, + DomainCreateData.create(targetId, now, registrationExpirationTime), + createResponseExtensions(feeCreate, commandOperations)); } - @Override - protected final void initDomainCreateOrAllocateFlow() { - registerExtensions(LaunchCreateExtension.class); + private boolean isAnchorTenant(InternetDomainName domainName) { + MetadataExtension metadataExtension = eppInput.getSingleExtension(MetadataExtension.class); + return matchesAnchorTenantReservation(domainName, authInfo.getPw().getValue()) + || (metadataExtension != null && metadataExtension.getIsAnchorTenant()); } - @Override - protected final void validateDomainLaunchCreateExtension() throws EppException { - // We can assume launchCreate is not null here. - // Only QLP domains can have a signed mark on a domain create, and only in sunrise or sunrush. - if (hasSignedMarks) { - if (isAnchorTenant() && QLP_SMD_ALLOWED_STATES.contains( - Registry.get(getTld()).getTldState(now))) { - return; - } + /** Only QLP domains can have a signed mark on a domain create, and only in sunrise or sunrush. */ + private void verifySignedMarksAllowed(TldState tldState, boolean isAnchorTenant) + throws SignedMarksNotAcceptedInCurrentPhaseException { + if (!isAnchorTenant || !QLP_SMD_ALLOWED_STATES.contains(tldState)) { throw new SignedMarksNotAcceptedInCurrentPhaseException(); } } - @Override - protected final void setDomainCreateOrAllocateProperties(Builder builder) throws EppException { - Registry registry = Registry.get(getTld()); - - // Bill for the create. - BillingEvent.OneTime createEvent = - new BillingEvent.OneTime.Builder() - .setReason(Reason.CREATE) - .setTargetId(targetId) - .setClientId(getClientId()) - .setPeriodYears(command.getPeriod().getValue()) - .setCost(commandOperations.getCreateCost()) - .setEventTime(now) - .setBillingTime(now.plus(isAnchorTenant() - ? registry.getAnchorTenantAddGracePeriodLength() - : registry.getAddGracePeriodLength())) - .setFlags(isAnchorTenant() - ? ImmutableSet.of(BillingEvent.Flag.ANCHOR_TENANT) - : ImmutableSet.of()) - .setParent(historyEntry) - .build(); - ofy().save().entity(createEvent); - - // Bill for EAP cost, if any. - if (!commandOperations.getEapCost().isZero()) { - BillingEvent.OneTime eapEvent = - new BillingEvent.OneTime.Builder() - .setReason(Reason.FEE_EARLY_ACCESS) - .setTargetId(createEvent.getTargetId()) - .setClientId(createEvent.getClientId()) - .setCost(commandOperations.getEapCost()) - .setEventTime(createEvent.getEventTime()) - .setBillingTime(createEvent.getBillingTime()) - .setFlags(createEvent.getFlags()) - .setParent(createEvent.getParentKey()) - .build(); - ofy().save().entity(eapEvent); + /** Prohibit creating a domain if there is an open application for the same name. */ + private void verifyNoOpenApplications() throws DomainHasOpenApplicationsException { + for (DomainApplication application : loadActiveApplicationsByDomainName(targetId, now)) { + if (!application.getApplicationStatus().isFinalStatus()) { + throw new DomainHasOpenApplicationsException(); + } } + } - builder.addGracePeriod(GracePeriod.forBillingEvent(GracePeriodStatus.ADD, createEvent)); - if (launchCreate != null && (launchCreate.getNotice() != null || hasSignedMarks)) { - builder - .setLaunchNotice(launchCreate.getNotice()) - .setSmdId(signedMark == null ? null : signedMark.getId()); + /** Prohibit registrations for non-qlp and non-superuser outside of GA. **/ + private void verifyIsGaOrIsSpecialCase(TldState tldState, boolean isAnchorTenant) + throws NoGeneralRegistrationsInCurrentPhaseException { + if (!isAnchorTenant && tldState != TldState.GENERAL_AVAILABILITY) { + throw new NoGeneralRegistrationsInCurrentPhaseException(); } - // Handle extra flow logic, if any. The initialization and commit are performed higher up in the - // flow hierarchy, in BaseDomainCreateFlow. + } + + private HistoryEntry buildHistory(String repoId, Period period) { + return historyBuilder + .setType(HistoryEntry.Type.DOMAIN_CREATE) + .setPeriod(period) + .setModificationTime(now) + .setParent(Key.create(DomainResource.class, repoId)) + .build(); + } + + private OneTime createOneTimeBillingEvent( + Registry registry, + boolean isAnchorTenant, + int years, + EppCommandOperations commandOperations, + HistoryEntry historyEntry) { + return new BillingEvent.OneTime.Builder() + .setReason(Reason.CREATE) + .setTargetId(targetId) + .setClientId(clientId) + .setPeriodYears(years) + .setCost(commandOperations.getCreateCost()) + .setEventTime(now) + .setBillingTime(now.plus(isAnchorTenant + ? registry.getAnchorTenantAddGracePeriodLength() + : registry.getAddGracePeriodLength())) + .setFlags(isAnchorTenant + ? ImmutableSet.of(BillingEvent.Flag.ANCHOR_TENANT) + : ImmutableSet.of()) + .setParent(historyEntry) + .build(); + } + + private Recurring createAutorenewBillingEvent( + HistoryEntry historyEntry, DateTime registrationExpirationTime) { + return new BillingEvent.Recurring.Builder() + .setReason(Reason.RENEW) + .setFlags(ImmutableSet.of(Flag.AUTO_RENEW)) + .setTargetId(targetId) + .setClientId(clientId) + .setEventTime(registrationExpirationTime) + .setRecurrenceEndTime(END_OF_TIME) + .setParent(historyEntry) + .build(); + } + + private Autorenew createAutorenewPollMessage( + HistoryEntry historyEntry, DateTime registrationExpirationTime) { + return new PollMessage.Autorenew.Builder() + .setTargetId(targetId) + .setClientId(clientId) + .setEventTime(registrationExpirationTime) + .setMsg("Domain was auto-renewed.") + .setParent(historyEntry) + .build(); + } + + private OneTime createEapBillingEvent(EppCommandOperations commandOperations, + BillingEvent.OneTime createBillingEvent) { + return new BillingEvent.OneTime.Builder() + .setReason(Reason.FEE_EARLY_ACCESS) + .setTargetId(createBillingEvent.getTargetId()) + .setClientId(createBillingEvent.getClientId()) + .setCost(commandOperations.getEapCost()) + .setEventTime(createBillingEvent.getEventTime()) + .setBillingTime(createBillingEvent.getBillingTime()) + .setFlags(createBillingEvent.getFlags()) + .setParent(createBillingEvent.getParentKey()) + .build(); + } + + private boolean hasLrpToken(Registry registry, boolean isAnchorTenant) { + return registry.getLrpPeriod().contains(now) && !isAnchorTenant; + } + + private void handleExtraFlowLogic( + String tld, int years, HistoryEntry historyEntry, DomainResource newDomain) + throws EppException { + Optional extraFlowLogic = + RegistryExtraFlowLogicProxy.newInstanceForTld(tld); if (extraFlowLogic.isPresent()) { extraFlowLogic.get().performAdditionalDomainCreateLogic( - existingResource, - getClientId(), + newDomain, + clientId, now, - command.getPeriod().getValue(), + years, eppInput, historyEntry); + extraFlowLogic.get().commitAdditionalLogicChanges(); } } - @Override - protected void enqueueLordnTaskIfNeeded() { - if (launchCreate != null && (launchCreate.getNotice() != null || hasSignedMarks)) { - LordnTask.enqueueDomainResourceTask(newResource); + private void enqueueTasks( + boolean hasSignedMarks, boolean hasClaimsNotice, DomainResource newDomain) { + if (newDomain.shouldPublishToDns()) { + DnsQueue.create().addDomainRefreshTask(newDomain.getFullyQualifiedDomainName()); + } + if (hasClaimsNotice || hasSignedMarks) { + LordnTask.enqueueDomainResourceTask(newDomain); } } - @Override - protected final HistoryEntry.Type getHistoryEntryType() { - return HistoryEntry.Type.DOMAIN_CREATE; + private ImmutableList createResponseExtensions( + FeeTransformCommandExtension feeCreate, EppCommandOperations commandOperations) { + return (feeCreate == null) + ? null + : ImmutableList.of(createFeeCreateResponse(feeCreate, commandOperations)); } /** There is an open application for this domain. */ diff --git a/java/google/registry/flows/domain/DomainCreateOrAllocateFlow.java b/java/google/registry/flows/domain/DomainCreateOrAllocateFlow.java deleted file mode 100644 index 4966145f1..000000000 --- a/java/google/registry/flows/domain/DomainCreateOrAllocateFlow.java +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright 2016 The Nomulus Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package google.registry.flows.domain; - -import static google.registry.model.ofy.ObjectifyService.ofy; -import static google.registry.util.DateTimeUtils.END_OF_TIME; -import static google.registry.util.DateTimeUtils.leapSafeAddYears; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; -import com.googlecode.objectify.Key; -import google.registry.dns.DnsQueue; -import google.registry.flows.EppException; -import google.registry.model.billing.BillingEvent; -import google.registry.model.billing.BillingEvent.Flag; -import google.registry.model.billing.BillingEvent.Reason; -import google.registry.model.domain.DomainResource; -import google.registry.model.domain.DomainResource.Builder; -import google.registry.model.domain.Period; -import google.registry.model.eppoutput.CreateData.DomainCreateData; -import google.registry.model.eppoutput.EppOutput; -import google.registry.model.eppoutput.Result; -import google.registry.model.poll.PollMessage; -import javax.inject.Inject; -import org.joda.time.DateTime; - -/** An EPP flow that creates or allocates a new domain resource. */ -public abstract class DomainCreateOrAllocateFlow - extends BaseDomainCreateFlow { - - protected boolean isAnchorTenantViaExtension; - - @Inject DnsQueue dnsQueue; - - @Override - protected final void initDomainCreateFlow() { - isAnchorTenantViaExtension = - (metadataExtension != null && metadataExtension.getIsAnchorTenant()); - initDomainCreateOrAllocateFlow(); - } - - protected abstract void initDomainCreateOrAllocateFlow(); - - @Override - protected final void setDomainCreateProperties(Builder builder) throws EppException { - DateTime registrationExpirationTime = leapSafeAddYears(now, command.getPeriod().getValue()); - // Create a new autorenew billing event and poll message starting at the expiration time. - BillingEvent.Recurring autorenewEvent = new BillingEvent.Recurring.Builder() - .setReason(Reason.RENEW) - .setFlags(ImmutableSet.of(Flag.AUTO_RENEW)) - .setTargetId(targetId) - .setClientId(getClientId()) - .setEventTime(registrationExpirationTime) - .setRecurrenceEndTime(END_OF_TIME) - .setParent(historyEntry) - .build(); - PollMessage.Autorenew autorenewPollMessage = new PollMessage.Autorenew.Builder() - .setTargetId(targetId) - .setClientId(getClientId()) - .setEventTime(registrationExpirationTime) - .setMsg("Domain was auto-renewed.") - .setParent(historyEntry) - .build(); - ofy().save().entities(autorenewEvent, autorenewPollMessage); - - builder - .setRegistrationExpirationTime(registrationExpirationTime) - .setAutorenewBillingEvent(Key.create(autorenewEvent)) - .setAutorenewPollMessage(Key.create(autorenewPollMessage)); - setDomainCreateOrAllocateProperties(builder); - } - - /** Subclasses must override this to set more fields, like any grace period. */ - protected abstract void setDomainCreateOrAllocateProperties(Builder builder) throws EppException; - - @Override - protected final void enqueueTasks() { - if (newResource.shouldPublishToDns()) { - dnsQueue.addDomainRefreshTask(newResource.getFullyQualifiedDomainName()); - } - enqueueLordnTaskIfNeeded(); - } - - /** Subclasses must override this to enqueue any additional tasks. */ - protected abstract void enqueueLordnTaskIfNeeded(); - - @Override - protected final Period getCommandPeriod() { - return command.getPeriod(); - } - - @Override - protected final EppOutput getOutput() { - return createOutput( - Result.Code.SUCCESS, - DomainCreateData.create( - newResource.getFullyQualifiedDomainName(), - now, - newResource.getRegistrationExpirationTime()), - (feeCreate == null) ? null : ImmutableList.of( - feeCreate.createResponseBuilder() - .setCurrency(commandOperations.getCurrency()) - .setFees(commandOperations.getFees()) - .setCredits(commandOperations.getCredits()) - .build())); - } -} diff --git a/java/google/registry/flows/domain/DomainFlowUtils.java b/java/google/registry/flows/domain/DomainFlowUtils.java index 93902f6ae..94c6508b8 100644 --- a/java/google/registry/flows/domain/DomainFlowUtils.java +++ b/java/google/registry/flows/domain/DomainFlowUtils.java @@ -22,6 +22,8 @@ import static com.google.common.collect.Iterables.concat; import static com.google.common.collect.Sets.difference; import static com.google.common.collect.Sets.union; import static google.registry.flows.EppXmlTransformer.unmarshal; +import static google.registry.flows.domain.TldSpecificLogicProxy.getMatchingLrpToken; +import static google.registry.model.EppResourceUtils.loadByForeignKey; import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.model.registry.Registries.findTldForName; import static google.registry.model.registry.label.ReservedList.getReservation; @@ -42,9 +44,11 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import com.google.common.net.InternetDomainName; import com.googlecode.objectify.Key; +import com.googlecode.objectify.Work; import google.registry.flows.EppException; import google.registry.flows.EppException.AuthorizationErrorException; import google.registry.flows.EppException.CommandUseErrorException; +import google.registry.flows.EppException.InvalidAuthorizationInformationErrorException; import google.registry.flows.EppException.ObjectDoesNotExistException; import google.registry.flows.EppException.ParameterValuePolicyErrorException; import google.registry.flows.EppException.ParameterValueRangeErrorException; @@ -52,6 +56,8 @@ import google.registry.flows.EppException.ParameterValueSyntaxErrorException; import google.registry.flows.EppException.RequiredParameterMissingException; import google.registry.flows.EppException.StatusProhibitsOperationException; import google.registry.flows.EppException.UnimplementedOptionException; +import google.registry.flows.domain.TldSpecificLogicProxy.EppCommandOperations; +import google.registry.flows.exceptions.ResourceAlreadyExistsException; import google.registry.flows.exceptions.ResourceHasClientUpdateProhibitedException; import google.registry.flows.exceptions.StatusNotClientSettableException; import google.registry.model.EppResource; @@ -63,19 +69,27 @@ import google.registry.model.domain.DesignatedContact; import google.registry.model.domain.DesignatedContact.Type; import google.registry.model.domain.DomainApplication; import google.registry.model.domain.DomainBase; +import google.registry.model.domain.DomainCommand.Create; import google.registry.model.domain.DomainCommand.CreateOrUpdate; import google.registry.model.domain.DomainCommand.InvalidReferencesException; import google.registry.model.domain.DomainCommand.Update; import google.registry.model.domain.DomainResource; +import google.registry.model.domain.LrpTokenEntity; import google.registry.model.domain.Period; import google.registry.model.domain.fee.Credit; import google.registry.model.domain.fee.Fee; import google.registry.model.domain.fee.FeeQueryCommandExtensionItem; import google.registry.model.domain.fee.FeeQueryResponseExtensionItem; import google.registry.model.domain.fee.FeeTransformCommandExtension; +import google.registry.model.domain.fee.FeeTransformResponseExtension; +import google.registry.model.domain.launch.LaunchCreateExtension; import google.registry.model.domain.launch.LaunchExtension; +import google.registry.model.domain.launch.LaunchNotice; +import google.registry.model.domain.launch.LaunchNotice.InvalidChecksumException; import google.registry.model.domain.launch.LaunchPhase; +import google.registry.model.domain.rgp.GracePeriodStatus; import google.registry.model.domain.secdns.DelegationSignerData; +import google.registry.model.domain.secdns.SecDnsCreateExtension; import google.registry.model.domain.secdns.SecDnsInfoExtension; import google.registry.model.domain.secdns.SecDnsUpdateExtension; import google.registry.model.domain.secdns.SecDnsUpdateExtension.Add; @@ -98,6 +112,7 @@ import google.registry.model.smd.AbstractSignedMark; import google.registry.model.smd.EncodedSignedMark; import google.registry.model.smd.SignedMark; import google.registry.model.smd.SignedMarkRevocationList; +import google.registry.model.tmch.ClaimsListShard; import google.registry.model.transfer.TransferData; import google.registry.model.transfer.TransferResponse.DomainTransferResponse; import google.registry.tmch.TmchXmlSignature; @@ -373,11 +388,12 @@ public class DomainFlowUtils { } /** Verifies that a launch extension's specified phase matches the specified registry's phase. */ - static void verifyLaunchPhase( - String tld, LaunchExtension launchExtension, DateTime now) throws EppException { + static void verifyLaunchPhaseMatchesRegistryPhase( + Registry registry, LaunchExtension launchExtension, DateTime now) throws EppException { if (!Objects.equals( - Registry.get(tld).getTldState(now), + registry.getTldState(now), LAUNCH_PHASE_TO_TLD_STATE.get(launchExtension.getPhase()))) { + // No launch operations are allowed during the quiet period or predelegation. throw new LaunchPhaseMismatchException(); } } @@ -835,6 +851,7 @@ public class DomainFlowUtils { } } + /** Check that the registry phase is not incompatible with launch extension flows. */ static void verifyRegistryStateAllowsLaunchFlows(Registry registry, DateTime now) throws BadCommandForRegistryPhaseException { if (DISALLOWED_TLD_STATES_FOR_LAUNCH_FLOWS.contains(registry.getTldState(now))) { @@ -842,6 +859,7 @@ public class DomainFlowUtils { } } + /** Check that the registry phase is not predelegation, during which some flows are forbidden. */ static void verifyNotInPredelegation(Registry registry, DateTime now) throws BadCommandForRegistryPhaseException { if (registry.getTldState(now) == TldState.PREDELEGATION) { @@ -849,6 +867,154 @@ public class DomainFlowUtils { } } + /** Validate the contacts and nameservers specified in a domain or application create command. */ + static void validateCreateCommandContactsAndNameservers(Create command, String tld) + throws EppException { + verifyNotInPendingDelete( + command.getContacts(), + command.getRegistrant(), + command.getNameservers()); + validateContactsHaveTypes(command.getContacts()); + validateRegistrantAllowedOnTld(tld, command.getRegistrantContactId()); + validateNoDuplicateContacts(command.getContacts()); + validateRequiredContactsPresent(command.getRegistrant(), command.getContacts()); + Set fullyQualifiedHostNames = + nullToEmpty(command.getNameserverFullyQualifiedHostNames()); + validateNameserversCountForTld(tld, fullyQualifiedHostNames.size()); + validateNameserversAllowedOnTld(tld, fullyQualifiedHostNames); + } + + /** + * Fail a domain or application create very fast if the domain is already registered. + * + *

Try to load the domain non-transactionally, since this can hit memcache. If we succeed, and + * the domain is not in the add grace period (the only state that allows instantaneous transition + * to being deleted), we can assume that the domain will not be deleted (and therefore won't be + * creatable) until its deletion time. For repeated failed creates this means we can avoid the + * Datastore lookup, which is very expensive (and first-seen failed creates are no worse than they + * otherwise would be). This comes at the cost of the extra lookup for successful creates (or + * rather, those that don't fail due to the domain existing) and also for failed creates within + * the existing domain's add grace period. + */ + static void failfastForCreate(final String targetId, final DateTime now) throws EppException { + // Enter a transactionless context briefly. + DomainResource domain = ofy().doTransactionless(new Work() { + @Override + public DomainResource run() { + // This is cacheable because we are outside of a transaction. + return loadByForeignKey(DomainResource.class, targetId, now); + }}); + // If the domain exists already and isn't in the add grace period then there is no way it will + // be suddenly deleted and therefore the create must fail. + if (domain != null && !domain.getGracePeriodStatuses().contains(GracePeriodStatus.ADD)) { + throw new ResourceAlreadyExistsException(targetId, true); + } + } + + /** Validate the secDNS extension, if present. */ + static SecDnsCreateExtension validateSecDnsExtension(SecDnsCreateExtension secDnsCreate) + throws EppException { + if (secDnsCreate == null) { + return null; + } + if (secDnsCreate.getDsData() == null) { + throw new DsDataRequiredException(); + } + if (secDnsCreate.getMaxSigLife() != null) { + throw new MaxSigLifeNotSupportedException(); + } + validateDsData(secDnsCreate.getDsData()); + return secDnsCreate; + } + + /** Validate the notice from a launch create extension, allowing null as a valid notice. */ + static void validateLaunchCreateNotice( + @Nullable LaunchNotice notice, + String domainLabel, + boolean isSuperuser, + DateTime now) throws EppException { + if (notice == null) { + return; + } + if (!notice.getNoticeId().getValidatorId().equals("tmch")) { + throw new InvalidTrademarkValidatorException(); + } + // Superuser can force domain creations regardless of the current date. + if (!isSuperuser) { + if (notice.getExpirationTime().isBefore(now)) { + throw new ExpiredClaimException(); + } + // An acceptance within the past 48 hours is mandated by the TMCH Functional Spec. + if (notice.getAcceptedTime().isBefore(now.minusHours(48))) { + throw new AcceptedTooLongAgoException(); + } + } + try { + notice.validate(domainLabel); + } catch (IllegalArgumentException e) { + throw new MalformedTcnIdException(); + } catch (InvalidChecksumException e) { + throw new InvalidTcnIdChecksumException(); + } + } + + /** Check that the claims period hasn't ended. */ + static void verifyClaimsPeriodNotEnded(Registry registry, DateTime now) + throws ClaimsPeriodEndedException { + if (isAtOrAfter(now, registry.getClaimsPeriodEnd())) { + throw new ClaimsPeriodEndedException(registry.getTldStr()); + } + } + + /** + * Check that if there's a claims notice it's on the claims list, and that if there's not one it's + * not on the claims list and is a sunrise application. + */ + static void verifyClaimsNoticeIfAndOnlyIfNeeded( + InternetDomainName domainName, + boolean hasSignedMarks, + boolean hasClaimsNotice) throws EppException { + boolean isInClaimsList = ClaimsListShard.get().getClaimKey(domainName.parts().get(0)) != null; + if (hasClaimsNotice && !isInClaimsList) { + throw new UnexpectedClaimsNoticeException(domainName.toString()); + } + if (!hasClaimsNotice && isInClaimsList && !hasSignedMarks) { + throw new MissingClaimsNoticeException(domainName.toString()); + } + } + + /** Create a {@link LrpTokenEntity} object that records this LRP registration. */ + static LrpTokenEntity prepareMarkedLrpTokenEntity( + String lrpTokenString, InternetDomainName domainName, HistoryEntry historyEntry) + throws InvalidLrpTokenException { + Optional lrpToken = getMatchingLrpToken(lrpTokenString, domainName); + if (!lrpToken.isPresent()) { + throw new InvalidLrpTokenException(); + } + return lrpToken.get().asBuilder() + .setRedemptionHistoryEntry(Key.create(historyEntry)) + .build(); + } + + /** Check that there are no code marks, which is a type of mark we don't support. */ + static void verifyNoCodeMarks(LaunchCreateExtension launchCreate) + throws UnsupportedMarkTypeException { + if (launchCreate.hasCodeMarks()) { + throw new UnsupportedMarkTypeException(); + } + } + + /** Create a response extension listign the fees on a domain or application create. */ + static FeeTransformResponseExtension createFeeCreateResponse( + FeeTransformCommandExtension feeCreate, + EppCommandOperations commandOperations) { + return feeCreate.createResponseBuilder() + .setCurrency(commandOperations.getCurrency()) + .setFees(commandOperations.getFees()) + .setCredits(commandOperations.getCredits()) + .build(); + } + /** Encoded signed marks must use base64 encoding. */ static class Base64RequiredForEncodedSignedMarksException extends ParameterValuePolicyErrorException { @@ -1239,6 +1405,13 @@ public class DomainFlowUtils { } } + /** At least one dsData is required when using the secDNS extension. */ + static class DsDataRequiredException extends ParameterValuePolicyErrorException { + public DsDataRequiredException() { + super("At least one dsData is required when using the secDNS extension"); + } + } + /** The 'urgent' attribute is not supported. */ static class UrgentAttributeNotSupportedException extends UnimplementedOptionException { public UrgentAttributeNotSupportedException() { @@ -1246,10 +1419,88 @@ public class DomainFlowUtils { } } + /** The 'maxSigLife' setting is not supported. */ + static class MaxSigLifeNotSupportedException extends UnimplementedOptionException { + public MaxSigLifeNotSupportedException() { + super("The 'maxSigLife' setting is not supported"); + } + } + /** Changing 'maxSigLife' is not supported. */ static class MaxSigLifeChangeNotSupportedException extends UnimplementedOptionException { public MaxSigLifeChangeNotSupportedException() { super("Changing 'maxSigLife' is not supported"); } } + + /** The specified trademark validator is not supported. */ + static class InvalidTrademarkValidatorException extends ParameterValuePolicyErrorException { + public InvalidTrademarkValidatorException() { + super("The only supported validationID is 'tmch' for the ICANN Trademark Clearinghouse."); + } + } + + /** The expiration time specified in the claim notice has elapsed. */ + static class ExpiredClaimException extends ParameterValueRangeErrorException { + public ExpiredClaimException() { + super("The expiration time specified in the claim notice has elapsed"); + } + } + + /** The acceptance time specified in the claim notice is more than 48 hours in the past. */ + static class AcceptedTooLongAgoException extends ParameterValueRangeErrorException { + public AcceptedTooLongAgoException() { + super("The acceptance time specified in the claim notice is more than 48 hours in the past"); + } + } + + /** The specified TCNID is invalid. */ + static class MalformedTcnIdException extends ParameterValueSyntaxErrorException { + public MalformedTcnIdException() { + super("The specified TCNID is malformed"); + } + } + + /** The checksum in the specified TCNID does not validate. */ + static class InvalidTcnIdChecksumException extends ParameterValueRangeErrorException { + public InvalidTcnIdChecksumException() { + super("The checksum in the specified TCNID does not validate"); + } + } + + /** The claims period for this TLD has ended. */ + static class ClaimsPeriodEndedException extends StatusProhibitsOperationException { + public ClaimsPeriodEndedException(String tld) { + super(String.format("The claims period for %s has ended", tld)); + } + } + + /** Requested domain requires a claims notice. */ + static class MissingClaimsNoticeException extends StatusProhibitsOperationException { + public MissingClaimsNoticeException(String domainName) { + super(String.format("%s requires a claims notice", domainName)); + } + } + + /** Requested domain does not require a claims notice. */ + static class UnexpectedClaimsNoticeException extends StatusProhibitsOperationException { + public UnexpectedClaimsNoticeException(String domainName) { + super(String.format("%s does not require a claims notice", domainName)); + } + } + + /** Invalid limited registration period token. */ + static class InvalidLrpTokenException + extends InvalidAuthorizationInformationErrorException { + public InvalidLrpTokenException() { + super("Invalid limited registration period token"); + } + } + + /** Only encoded signed marks are supported. */ + static class UnsupportedMarkTypeException extends ParameterValuePolicyErrorException { + public UnsupportedMarkTypeException() { + super("Only encoded signed marks are supported"); + } + } } diff --git a/java/google/registry/flows/domain/TldSpecificLogicProxy.java b/java/google/registry/flows/domain/TldSpecificLogicProxy.java index cdd5af81b..ca8b6c81b 100644 --- a/java/google/registry/flows/domain/TldSpecificLogicProxy.java +++ b/java/google/registry/flows/domain/TldSpecificLogicProxy.java @@ -23,11 +23,11 @@ import static google.registry.util.PreconditionsUtils.checkArgumentNotNull; import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; +import com.google.common.net.InternetDomainName; import com.googlecode.objectify.Key; import google.registry.flows.EppException; import google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException; import google.registry.model.ImmutableObject; -import google.registry.model.domain.DomainCommand.Create; import google.registry.model.domain.DomainResource; import google.registry.model.domain.LrpTokenEntity; import google.registry.model.domain.fee.BaseFee; @@ -266,23 +266,25 @@ public final class TldSpecificLogicProxy { } /** - * Checks whether a {@link Create} command has a valid {@link LrpTokenEntity} for a particular - * TLD, and return that token (wrapped in an {@link Optional}) if one exists. + * Checks whether an LRP token String maps to a valid {@link LrpTokenEntity} for the domain name's + * TLD, and return that entity (wrapped in an {@link Optional}) if one exists. * *

This method has no knowledge of whether or not an auth code (interpreted here as an LRP * token) has already been checked against the reserved list for QLP (anchor tenant), as auth * codes are used for both types of registrations. */ - public static Optional getMatchingLrpToken(Create createCommand, String tld) { + public static Optional getMatchingLrpToken( + String lrpToken, InternetDomainName domainName) { // Note that until the actual per-TLD logic is built out, what's being done here is a basic // domain-name-to-assignee match. - String lrpToken = createCommand.getAuthInfo().getPw().getValue(); - LrpTokenEntity token = ofy().load().key(Key.create(LrpTokenEntity.class, lrpToken)).now(); - if (token != null) { - if (token.getAssignee().equalsIgnoreCase(createCommand.getFullyQualifiedDomainName()) - && token.getRedemptionHistoryEntry() == null - && token.getValidTlds().contains(tld)) { - return Optional.of(token); + if (!lrpToken.isEmpty()) { + LrpTokenEntity token = ofy().load().key(Key.create(LrpTokenEntity.class, lrpToken)).now(); + if (token != null) { + if (token.getAssignee().equalsIgnoreCase(domainName.toString()) + && token.getRedemptionHistoryEntry() == null + && token.getValidTlds().contains(domainName.parent().toString())) { + return Optional.of(token); + } } } return Optional.absent(); diff --git a/java/google/registry/flows/session/LoginFlow.java b/java/google/registry/flows/session/LoginFlow.java index db6a924e2..5fe3cf085 100644 --- a/java/google/registry/flows/session/LoginFlow.java +++ b/java/google/registry/flows/session/LoginFlow.java @@ -28,6 +28,7 @@ import google.registry.flows.EppException.UnimplementedExtensionException; import google.registry.flows.EppException.UnimplementedObjectServiceException; import google.registry.flows.EppException.UnimplementedOptionException; import google.registry.flows.Flow; +import google.registry.flows.FlowModule.ClientId; import google.registry.model.eppcommon.ProtocolDefinition; import google.registry.model.eppcommon.ProtocolDefinition.ServiceExtension; import google.registry.model.eppinput.EppInput.Login; @@ -67,6 +68,7 @@ public class LoginFlow extends Flow { /** Maximum number of failed login attempts allowed per connection. */ private static final int MAX_FAILED_LOGIN_ATTEMPTS_PER_CONNECTION = 3; + @Inject @ClientId String clientId; @Inject LoginFlow() {} /** Run the flow and log errors. */ @@ -83,7 +85,7 @@ public class LoginFlow extends Flow { /** Run the flow without bothering to log errors. The {@link #run} method will do that for us. */ public final EppOutput runWithoutLogging() throws EppException { Login login = (Login) eppInput.getCommandWrapper().getCommand(); - if (getClientId() != null) { + if (!clientId.isEmpty()) { throw new AlreadyLoggedInException(); } Options options = login.getOptions(); diff --git a/java/google/registry/model/registry/Registry.java b/java/google/registry/model/registry/Registry.java index b9a34b5db..ba1de3e83 100644 --- a/java/google/registry/model/registry/Registry.java +++ b/java/google/registry/model/registry/Registry.java @@ -573,7 +573,7 @@ public class Registry extends ImmutableObject implements Buildable { public Interval getLrpPeriod() { return (lrpPeriodStart == null && lrpPeriodEnd == null) - ? new Interval(START_OF_TIME, Duration.ZERO) + ? new Interval(START_OF_TIME, Duration.ZERO) // An empty duration. : new Interval(lrpPeriodStart, lrpPeriodEnd); } diff --git a/java/google/registry/model/registry/label/ReservedList.java b/java/google/registry/model/registry/label/ReservedList.java index 79292d97f..413f337ca 100644 --- a/java/google/registry/model/registry/label/ReservedList.java +++ b/java/google/registry/model/registry/label/ReservedList.java @@ -33,6 +33,7 @@ import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import com.google.common.net.InternetDomainName; import com.google.common.util.concurrent.UncheckedExecutionException; import com.googlecode.objectify.Key; import com.googlecode.objectify.VoidWork; @@ -174,8 +175,10 @@ public final class ReservedList * Returns true if the given label and TLD is reserved for an anchor tenant, and the given * auth code matches the one set on the reservation. */ - public static boolean matchesAnchorTenantReservation(String label, String tld, String authCode) { - ReservedListEntry entry = getReservedListEntry(label, tld); + public static boolean matchesAnchorTenantReservation( + InternetDomainName domainName, String authCode) { + ReservedListEntry entry = + getReservedListEntry(domainName.parts().get(0), domainName.parent().toString()); return entry != null && entry.reservationType == RESERVED_FOR_ANCHOR_TENANT && Objects.equals(entry.getAuthCode(), authCode); diff --git a/javatests/google/registry/flows/EppLifecycleDomainTest.java b/javatests/google/registry/flows/EppLifecycleDomainTest.java index af1230f89..643f208b0 100644 --- a/javatests/google/registry/flows/EppLifecycleDomainTest.java +++ b/javatests/google/registry/flows/EppLifecycleDomainTest.java @@ -337,7 +337,7 @@ public class EppLifecycleDomainTest extends EppTestCase { DateTime.parse("2001-01-01T00:01:00Z")); assertCommandAndResponse( "poll_ack.xml", - ImmutableMap.of("ID", "1-B-EXAMPLE-17-23"), + ImmutableMap.of("ID", "1-C-EXAMPLE-17-23"), "poll_ack_response_empty.xml", null, DateTime.parse("2001-01-01T00:01:00Z")); @@ -349,7 +349,7 @@ public class EppLifecycleDomainTest extends EppTestCase { DateTime.parse("2001-01-06T00:01:00Z")); assertCommandAndResponse( "poll_ack.xml", - ImmutableMap.of("ID", "1-B-EXAMPLE-17-22"), + ImmutableMap.of("ID", "1-C-EXAMPLE-17-22"), "poll_ack_response_empty.xml", null, DateTime.parse("2001-01-06T00:01:00Z")); @@ -365,7 +365,7 @@ public class EppLifecycleDomainTest extends EppTestCase { DateTime.parse("2001-01-06T00:02:00Z")); assertCommandAndResponse( "poll_ack.xml", - ImmutableMap.of("ID", "1-B-EXAMPLE-17-21"), + ImmutableMap.of("ID", "1-C-EXAMPLE-17-21"), "poll_ack_response_empty.xml", null, DateTime.parse("2001-01-06T00:02:00Z")); diff --git a/javatests/google/registry/flows/domain/ClaimsCheckFlowTest.java b/javatests/google/registry/flows/domain/ClaimsCheckFlowTest.java index dffe725a7..78861225c 100644 --- a/javatests/google/registry/flows/domain/ClaimsCheckFlowTest.java +++ b/javatests/google/registry/flows/domain/ClaimsCheckFlowTest.java @@ -22,8 +22,8 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import google.registry.flows.ResourceFlowTestCase; import google.registry.flows.domain.ClaimsCheckFlow.ClaimsCheckNotAllowedInSunrise; -import google.registry.flows.domain.ClaimsCheckFlow.ClaimsPeriodEndedException; import google.registry.flows.domain.DomainFlowUtils.BadCommandForRegistryPhaseException; +import google.registry.flows.domain.DomainFlowUtils.ClaimsPeriodEndedException; import google.registry.flows.domain.DomainFlowUtils.NotAuthorizedForTldException; import google.registry.flows.domain.DomainFlowUtils.TldDoesNotExistException; import google.registry.flows.exceptions.TooManyResourceChecksException; diff --git a/javatests/google/registry/flows/domain/DomainAllocateFlowTest.java b/javatests/google/registry/flows/domain/DomainAllocateFlowTest.java index 53972f752..5286258de 100644 --- a/javatests/google/registry/flows/domain/DomainAllocateFlowTest.java +++ b/javatests/google/registry/flows/domain/DomainAllocateFlowTest.java @@ -41,12 +41,11 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.googlecode.objectify.Key; -import google.registry.flows.ResourceCreateFlow.ResourceAlreadyExistsException; import google.registry.flows.ResourceFlowTestCase; import google.registry.flows.domain.DomainAllocateFlow.HasFinalStatusException; import google.registry.flows.domain.DomainAllocateFlow.MissingApplicationException; import google.registry.flows.domain.DomainAllocateFlow.OnlySuperuserCanAllocateException; -import google.registry.flows.domain.DomainFlowUtils.NotAuthorizedForTldException; +import google.registry.flows.exceptions.ResourceAlreadyExistsException; import google.registry.model.billing.BillingEvent; import google.registry.model.billing.BillingEvent.Flag; import google.registry.model.billing.BillingEvent.Reason; @@ -64,12 +63,10 @@ import google.registry.model.eppcommon.Trid; import google.registry.model.ofy.ObjectifyService; import google.registry.model.poll.PendingActionNotificationResponse.DomainPendingActionNotificationResponse; import google.registry.model.poll.PollMessage; -import google.registry.model.registrar.Registrar; import google.registry.model.registry.Registry; import google.registry.model.registry.Registry.TldState; import google.registry.model.reporting.HistoryEntry; import google.registry.model.smd.EncodedSignedMark; -import google.registry.testing.DatastoreHelper; import google.registry.testing.TaskQueueHelper.TaskMatcher; import org.joda.money.Money; import org.joda.time.DateTime; @@ -109,6 +106,7 @@ public class DomainAllocateFlowTest application = persistResource(newDomainApplication(domainName).asBuilder() .setCreationTrid(TRID) .setEncodedSignedMarks(ImmutableList.of(EncodedSignedMark.create("base64", "abcdef"))) + .setCreationTrid(TRID) .build()); for (int i = 1; i <= 14; ++i) { persistActiveHost(String.format("ns%d.example.net", i)); @@ -478,18 +476,6 @@ public class DomainAllocateFlowTest runFlowAsSuperuser(); } - @Test - public void testFailure_notAuthorizedForTld() throws Exception { - setupDomainApplication("tld", TldState.QUIET_PERIOD); - DatastoreHelper.persistResource( - Registrar.loadByClientId("TheRegistrar") - .asBuilder() - .setAllowedTlds(ImmutableSet.of()) - .build()); - thrown.expect(NotAuthorizedForTldException.class); - runFlow(); - } - @Test public void testFailure_onlySuperuserCanAllocate() throws Exception { setupDomainApplication("tld", TldState.GENERAL_AVAILABILITY); diff --git a/javatests/google/registry/flows/domain/DomainApplicationCreateFlowTest.java b/javatests/google/registry/flows/domain/DomainApplicationCreateFlowTest.java index b9285e9f7..b47b831e7 100644 --- a/javatests/google/registry/flows/domain/DomainApplicationCreateFlowTest.java +++ b/javatests/google/registry/flows/domain/DomainApplicationCreateFlowTest.java @@ -43,28 +43,18 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedMap; import com.googlecode.objectify.Key; import google.registry.flows.EppException.UnimplementedExtensionException; -import google.registry.flows.ResourceCreateFlow.ResourceAlreadyExistsException; import google.registry.flows.ResourceFlowTestCase; -import google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException; -import google.registry.flows.domain.BaseDomainCreateFlow.AcceptedTooLongAgoException; -import google.registry.flows.domain.BaseDomainCreateFlow.ClaimsPeriodEndedException; -import google.registry.flows.domain.BaseDomainCreateFlow.ExpiredClaimException; -import google.registry.flows.domain.BaseDomainCreateFlow.InvalidTcnIdChecksumException; -import google.registry.flows.domain.BaseDomainCreateFlow.InvalidTrademarkValidatorException; -import google.registry.flows.domain.BaseDomainCreateFlow.MalformedTcnIdException; -import google.registry.flows.domain.BaseDomainCreateFlow.MaxSigLifeNotSupportedException; -import google.registry.flows.domain.BaseDomainCreateFlow.MissingClaimsNoticeException; -import google.registry.flows.domain.BaseDomainCreateFlow.UnexpectedClaimsNoticeException; -import google.registry.flows.domain.BaseDomainCreateFlow.UnsupportedMarkTypeException; import google.registry.flows.domain.DomainApplicationCreateFlow.LandrushApplicationDisallowedDuringSunriseException; import google.registry.flows.domain.DomainApplicationCreateFlow.NoticeCannotBeUsedWithSignedMarkException; import google.registry.flows.domain.DomainApplicationCreateFlow.SunriseApplicationDisallowedDuringLandrushException; import google.registry.flows.domain.DomainApplicationCreateFlow.UncontestedSunriseApplicationBlockedInLandrushException; +import google.registry.flows.domain.DomainFlowUtils.AcceptedTooLongAgoException; import google.registry.flows.domain.DomainFlowUtils.BadCommandForRegistryPhaseException; import google.registry.flows.domain.DomainFlowUtils.BadDomainNameCharacterException; import google.registry.flows.domain.DomainFlowUtils.BadDomainNamePartsCountException; import google.registry.flows.domain.DomainFlowUtils.BadPeriodUnitException; import google.registry.flows.domain.DomainFlowUtils.Base64RequiredForEncodedSignedMarksException; +import google.registry.flows.domain.DomainFlowUtils.ClaimsPeriodEndedException; import google.registry.flows.domain.DomainFlowUtils.CurrencyUnitMismatchException; import google.registry.flows.domain.DomainFlowUtils.CurrencyValueScaleException; import google.registry.flows.domain.DomainFlowUtils.DashesInThirdAndFourthException; @@ -72,13 +62,20 @@ import google.registry.flows.domain.DomainFlowUtils.DomainLabelTooLongException; import google.registry.flows.domain.DomainFlowUtils.DomainReservedException; import google.registry.flows.domain.DomainFlowUtils.DuplicateContactForRoleException; import google.registry.flows.domain.DomainFlowUtils.EmptyDomainNamePartException; +import google.registry.flows.domain.DomainFlowUtils.ExpiredClaimException; import google.registry.flows.domain.DomainFlowUtils.FeesMismatchException; import google.registry.flows.domain.DomainFlowUtils.FeesRequiredForPremiumNameException; import google.registry.flows.domain.DomainFlowUtils.InvalidIdnDomainLabelException; +import google.registry.flows.domain.DomainFlowUtils.InvalidLrpTokenException; import google.registry.flows.domain.DomainFlowUtils.InvalidPunycodeException; +import google.registry.flows.domain.DomainFlowUtils.InvalidTcnIdChecksumException; +import google.registry.flows.domain.DomainFlowUtils.InvalidTrademarkValidatorException; import google.registry.flows.domain.DomainFlowUtils.LaunchPhaseMismatchException; import google.registry.flows.domain.DomainFlowUtils.LeadingDashException; import google.registry.flows.domain.DomainFlowUtils.LinkedResourcesDoNotExistException; +import google.registry.flows.domain.DomainFlowUtils.MalformedTcnIdException; +import google.registry.flows.domain.DomainFlowUtils.MaxSigLifeNotSupportedException; +import google.registry.flows.domain.DomainFlowUtils.MissingClaimsNoticeException; import google.registry.flows.domain.DomainFlowUtils.MissingContactTypeException; import google.registry.flows.domain.DomainFlowUtils.NameserversNotAllowedException; import google.registry.flows.domain.DomainFlowUtils.NameserversNotSpecifiedException; @@ -101,7 +98,10 @@ import google.registry.flows.domain.DomainFlowUtils.TooManyDsRecordsException; import google.registry.flows.domain.DomainFlowUtils.TooManyNameserversException; import google.registry.flows.domain.DomainFlowUtils.TooManySignedMarksException; import google.registry.flows.domain.DomainFlowUtils.TrailingDashException; +import google.registry.flows.domain.DomainFlowUtils.UnexpectedClaimsNoticeException; import google.registry.flows.domain.DomainFlowUtils.UnsupportedFeeAttributeException; +import google.registry.flows.domain.DomainFlowUtils.UnsupportedMarkTypeException; +import google.registry.flows.exceptions.ResourceAlreadyExistsException; import google.registry.model.domain.DomainApplication; import google.registry.model.domain.GracePeriod; import google.registry.model.domain.LrpTokenEntity; @@ -939,7 +939,7 @@ public class DomainApplicationCreateFlowTest setEppInput("domain_create_landrush_lrp.xml"); persistContactsAndHosts(); clock.advanceOneMilli(); - thrown.expect(BadAuthInfoForResourceException.class); + thrown.expect(InvalidLrpTokenException.class); runFlow(); } @@ -960,7 +960,7 @@ public class DomainApplicationCreateFlowTest setEppInput("domain_create_landrush_lrp.xml"); persistContactsAndHosts(); clock.advanceOneMilli(); - thrown.expect(BadAuthInfoForResourceException.class); + thrown.expect(InvalidLrpTokenException.class); runFlow(); } @@ -980,7 +980,7 @@ public class DomainApplicationCreateFlowTest setEppInput("domain_create_landrush_lrp.xml"); persistContactsAndHosts(); clock.advanceOneMilli(); - thrown.expect(BadAuthInfoForResourceException.class); + thrown.expect(InvalidLrpTokenException.class); runFlow(); } diff --git a/javatests/google/registry/flows/domain/DomainCreateFlowTest.java b/javatests/google/registry/flows/domain/DomainCreateFlowTest.java index b3ad10b0f..a9b0f5a8b 100644 --- a/javatests/google/registry/flows/domain/DomainCreateFlowTest.java +++ b/javatests/google/registry/flows/domain/DomainCreateFlowTest.java @@ -55,25 +55,15 @@ import com.google.common.collect.ImmutableSortedMap; import google.registry.flows.EppException.UnimplementedExtensionException; import google.registry.flows.EppRequestSource; import google.registry.flows.LoggedInFlow.UndeclaredServiceExtensionException; -import google.registry.flows.ResourceCreateFlow.ResourceAlreadyExistsException; -import google.registry.flows.ResourceCreateOrMutateFlow.OnlyToolCanPassMetadataException; import google.registry.flows.ResourceFlowTestCase; -import google.registry.flows.domain.BaseDomainCreateFlow.AcceptedTooLongAgoException; -import google.registry.flows.domain.BaseDomainCreateFlow.ClaimsPeriodEndedException; -import google.registry.flows.domain.BaseDomainCreateFlow.ExpiredClaimException; -import google.registry.flows.domain.BaseDomainCreateFlow.InvalidTcnIdChecksumException; -import google.registry.flows.domain.BaseDomainCreateFlow.InvalidTrademarkValidatorException; -import google.registry.flows.domain.BaseDomainCreateFlow.MalformedTcnIdException; -import google.registry.flows.domain.BaseDomainCreateFlow.MaxSigLifeNotSupportedException; -import google.registry.flows.domain.BaseDomainCreateFlow.MissingClaimsNoticeException; -import google.registry.flows.domain.BaseDomainCreateFlow.UnexpectedClaimsNoticeException; -import google.registry.flows.domain.BaseDomainCreateFlow.UnsupportedMarkTypeException; import google.registry.flows.domain.DomainCreateFlow.DomainHasOpenApplicationsException; import google.registry.flows.domain.DomainCreateFlow.NoGeneralRegistrationsInCurrentPhaseException; import google.registry.flows.domain.DomainCreateFlow.SignedMarksNotAcceptedInCurrentPhaseException; +import google.registry.flows.domain.DomainFlowUtils.AcceptedTooLongAgoException; import google.registry.flows.domain.DomainFlowUtils.BadDomainNameCharacterException; import google.registry.flows.domain.DomainFlowUtils.BadDomainNamePartsCountException; import google.registry.flows.domain.DomainFlowUtils.BadPeriodUnitException; +import google.registry.flows.domain.DomainFlowUtils.ClaimsPeriodEndedException; import google.registry.flows.domain.DomainFlowUtils.CurrencyUnitMismatchException; import google.registry.flows.domain.DomainFlowUtils.CurrencyValueScaleException; import google.registry.flows.domain.DomainFlowUtils.DashesInThirdAndFourthException; @@ -81,14 +71,20 @@ import google.registry.flows.domain.DomainFlowUtils.DomainLabelTooLongException; import google.registry.flows.domain.DomainFlowUtils.DomainReservedException; import google.registry.flows.domain.DomainFlowUtils.DuplicateContactForRoleException; import google.registry.flows.domain.DomainFlowUtils.EmptyDomainNamePartException; +import google.registry.flows.domain.DomainFlowUtils.ExpiredClaimException; import google.registry.flows.domain.DomainFlowUtils.FeesMismatchException; import google.registry.flows.domain.DomainFlowUtils.FeesRequiredForPremiumNameException; import google.registry.flows.domain.DomainFlowUtils.InvalidIdnDomainLabelException; import google.registry.flows.domain.DomainFlowUtils.InvalidPunycodeException; +import google.registry.flows.domain.DomainFlowUtils.InvalidTcnIdChecksumException; +import google.registry.flows.domain.DomainFlowUtils.InvalidTrademarkValidatorException; import google.registry.flows.domain.DomainFlowUtils.LeadingDashException; import google.registry.flows.domain.DomainFlowUtils.LinkedResourceInPendingDeleteProhibitsOperationException; import google.registry.flows.domain.DomainFlowUtils.LinkedResourcesDoNotExistException; +import google.registry.flows.domain.DomainFlowUtils.MalformedTcnIdException; +import google.registry.flows.domain.DomainFlowUtils.MaxSigLifeNotSupportedException; import google.registry.flows.domain.DomainFlowUtils.MissingAdminContactException; +import google.registry.flows.domain.DomainFlowUtils.MissingClaimsNoticeException; import google.registry.flows.domain.DomainFlowUtils.MissingContactTypeException; import google.registry.flows.domain.DomainFlowUtils.MissingRegistrantException; import google.registry.flows.domain.DomainFlowUtils.MissingTechnicalContactException; @@ -101,7 +97,11 @@ import google.registry.flows.domain.DomainFlowUtils.TldDoesNotExistException; import google.registry.flows.domain.DomainFlowUtils.TooManyDsRecordsException; import google.registry.flows.domain.DomainFlowUtils.TooManyNameserversException; import google.registry.flows.domain.DomainFlowUtils.TrailingDashException; +import google.registry.flows.domain.DomainFlowUtils.UnexpectedClaimsNoticeException; import google.registry.flows.domain.DomainFlowUtils.UnsupportedFeeAttributeException; +import google.registry.flows.domain.DomainFlowUtils.UnsupportedMarkTypeException; +import google.registry.flows.exceptions.OnlyToolCanPassMetadataException; +import google.registry.flows.exceptions.ResourceAlreadyExistsException; import google.registry.model.billing.BillingEvent; import google.registry.model.billing.BillingEvent.Flag; import google.registry.model.billing.BillingEvent.Reason; @@ -262,7 +262,8 @@ public class DomainCreateFlowTest extends ResourceFlowTestCase - A-EXAMPLE + B-EXAMPLE 2014-01-01T00:00:00Z diff --git a/javatests/google/registry/flows/testdata/domain_create_sunrise_encoded_signed_mark_response.xml b/javatests/google/registry/flows/testdata/domain_create_sunrise_encoded_signed_mark_response.xml index 79311e553..40a250fdd 100644 --- a/javatests/google/registry/flows/testdata/domain_create_sunrise_encoded_signed_mark_response.xml +++ b/javatests/google/registry/flows/testdata/domain_create_sunrise_encoded_signed_mark_response.xml @@ -14,7 +14,7 @@ sunrise - A-EXAMPLE + B-EXAMPLE diff --git a/javatests/google/registry/flows/testdata/poll_response_domain_transfer_request.xml b/javatests/google/registry/flows/testdata/poll_response_domain_transfer_request.xml index 21da8507a..d833b884b 100644 --- a/javatests/google/registry/flows/testdata/poll_response_domain_transfer_request.xml +++ b/javatests/google/registry/flows/testdata/poll_response_domain_transfer_request.xml @@ -3,7 +3,7 @@ Command completed successfully; ack to dequeue - + 2001-01-01T00:00:00Z Transfer requested. diff --git a/javatests/google/registry/flows/testdata/poll_response_domain_transfer_server_approve_loser.xml b/javatests/google/registry/flows/testdata/poll_response_domain_transfer_server_approve_loser.xml index 881e70f3a..173a77dda 100644 --- a/javatests/google/registry/flows/testdata/poll_response_domain_transfer_server_approve_loser.xml +++ b/javatests/google/registry/flows/testdata/poll_response_domain_transfer_server_approve_loser.xml @@ -3,7 +3,7 @@ Command completed successfully; ack to dequeue - + 2001-01-06T00:00:00Z Transfer approved. diff --git a/javatests/google/registry/flows/testdata/poll_response_domain_transfer_server_approve_winner.xml b/javatests/google/registry/flows/testdata/poll_response_domain_transfer_server_approve_winner.xml index 8e3e7785c..270222758 100644 --- a/javatests/google/registry/flows/testdata/poll_response_domain_transfer_server_approve_winner.xml +++ b/javatests/google/registry/flows/testdata/poll_response_domain_transfer_server_approve_winner.xml @@ -4,7 +4,7 @@ Command completed successfully; ack to dequeue - + 2001-01-06T00:00:00Z Transfer approved. diff --git a/javatests/google/registry/model/registry/label/ReservedListTest.java b/javatests/google/registry/model/registry/label/ReservedListTest.java index 54c0fea97..0d7c09163 100644 --- a/javatests/google/registry/model/registry/label/ReservedListTest.java +++ b/javatests/google/registry/model/registry/label/ReservedListTest.java @@ -29,6 +29,7 @@ import static google.registry.testing.DatastoreHelper.persistResource; import com.google.common.base.Function; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import com.google.common.net.InternetDomainName; import com.googlecode.objectify.Key; import google.registry.model.ofy.Ofy; import google.registry.model.registry.Registry; @@ -108,11 +109,16 @@ public class ReservedListTest { .build()); assertThat(getReservation("lol", "tld")).isEqualTo(RESERVED_FOR_ANCHOR_TENANT); assertThat(getReservation("lol2", "tld")).isEqualTo(RESERVED_FOR_ANCHOR_TENANT); - assertThat(matchesAnchorTenantReservation("lol", "tld", "foobar1")).isTrue(); - assertThat(matchesAnchorTenantReservation("lol", "tld", "foobar")).isFalse(); - assertThat(matchesAnchorTenantReservation("lol2", "tld", "abcdefg")).isTrue(); - assertThat(matchesAnchorTenantReservation("lol2", "tld", "abcdefg ")).isFalse(); - assertThat(matchesAnchorTenantReservation("random", "tld", "abcdefg ")).isFalse(); + assertThat(matchesAnchorTenantReservation(InternetDomainName.from("lol.tld"), "foobar1")) + .isTrue(); + assertThat(matchesAnchorTenantReservation(InternetDomainName.from("lol.tld"), "foobar")) + .isFalse(); + assertThat(matchesAnchorTenantReservation(InternetDomainName.from("lol2.tld"), "abcdefg")) + .isTrue(); + assertThat(matchesAnchorTenantReservation(InternetDomainName.from("lol2.tld"), "abcdefg ")) + .isFalse(); + assertThat(matchesAnchorTenantReservation(InternetDomainName.from("random.tld"), "abcdefg")) + .isFalse(); } @Test @@ -126,11 +132,11 @@ public class ReservedListTest { "lol3,MISTAKEN_PREMIUM", "lol4,ALLOWED_IN_SUNRISE"))) .build()); - assertThat(matchesAnchorTenantReservation("lol", "tld", "")).isFalse(); - assertThat(matchesAnchorTenantReservation("lol2", "tld", "")).isFalse(); - assertThat(matchesAnchorTenantReservation("lol3", "tld", "")).isFalse(); - assertThat(matchesAnchorTenantReservation("lol4", "tld", "")).isFalse(); - assertThat(matchesAnchorTenantReservation("lol5", "tld", "")).isFalse(); + assertThat(matchesAnchorTenantReservation(InternetDomainName.from("lol.tld"), "")).isFalse(); + assertThat(matchesAnchorTenantReservation(InternetDomainName.from("lol2.tld"), "")).isFalse(); + assertThat(matchesAnchorTenantReservation(InternetDomainName.from("lol3.tld"), "")).isFalse(); + assertThat(matchesAnchorTenantReservation(InternetDomainName.from("lol4.tld"), "")).isFalse(); + assertThat(matchesAnchorTenantReservation(InternetDomainName.from("lol5.tld"), "")).isFalse(); } @Test