Flatten the domain and application create flows

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=136601151
This commit is contained in:
cgoldfeder 2016-10-19 09:00:30 -07:00 committed by Ben McIlwain
parent 43821f650e
commit 475203532e
31 changed files with 1151 additions and 1410 deletions

View file

@ -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.

View file

@ -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);
}

View file

@ -185,7 +185,13 @@ public class FlowModule {
@Provides
@FlowScope
static Optional<AuthInfo> provideAuthInfo(ResourceCommand resourceCommand) {
static AuthInfo provideAuthInfo(ResourceCommand resourceCommand) {
return ((SingleResourceCommand) resourceCommand).getAuthInfo();
}
@Provides
@FlowScope
static Optional<AuthInfo> provideOptionalAuthInfo(ResourceCommand resourceCommand) {
return Optional.fromNullable(((SingleResourceCommand) resourceCommand).getAuthInfo());
}

View file

@ -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 <R> the resource type being changed
* @param <B> a builder for the resource
* @param <C> the command type, marshalled directly from the epp xml
*/
public abstract class ResourceCreateFlow
<R extends EppResource,
B extends Builder<R, ?>,
C extends ResourceCreateOrChange<? super B> & SingleResourceCommand>
extends ResourceCreateOrMutateFlow<R, C> {
@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<B>(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;
}
}
}

View file

@ -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 <R> the resource type being changed
* @param <C> the command type, marshalled directly from the epp xml
*
* @error {@link OnlyToolCanPassMetadataException}
*/
public abstract class ResourceCreateOrMutateFlow
<R extends EppResource, C extends SingleResourceCommand> extends SingleResourceFlow<R, C>
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<EppResource> getResourceKey() {
checkState(repoId != null,
"RepoId hasn't been initialized yet; getResourceKey() called too early");
Class<R> resourceClazz = new TypeInstantiator<R>(getClass()){}.getExactType();
return Key.<EppResource>create(null, resourceClazz, repoId);
}
@Override
protected final EppOutput runResourceFlow() throws EppException {
newResource = createOrMutateResource();
verifyNewStateIsAllowed();
validateMetadataExtension();
modifyRelatedResources();
enqueueTasks();
ofy().save().<Object>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.");
}
}
}

View file

@ -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 <R> the resource type being manipulated
* @param <C> the command type doing the manipulation.
*/
public abstract class ResourceFlow<R extends EppResource, C extends ResourceCommand>
extends LoggedInFlow {
protected C command;
protected Class<R> resourceClass;
@Override
@SuppressWarnings("unchecked")
protected final void initLoggedInFlow() throws EppException {
this.command = (C) ((ResourceCommandWrapper) eppInput.getCommandWrapper().getCommand())
.getResourceCommand();
this.resourceClass = new TypeInstantiator<R>(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;
}

View file

@ -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 <R extends EppResource> void verifyResourceDoesNotExist(
Class<R> clazz, String targetId, DateTime now) throws EppException {
if (loadByForeignKey(clazz, targetId, now) != null) {
if (loadAndGetKey(clazz, targetId, now) != null) {
throw new ResourceAlreadyExistsException(targetId);
}
}

View file

@ -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 <R> the resource type being manipulated
* @param <C> the command type doing the manipulation.
*/
public abstract class SingleResourceFlow<R extends EppResource, C extends SingleResourceCommand>
extends ResourceFlow<R, C> {
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<StatusValue> 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<StatusValue> getDisallowedStatuses() {
return ImmutableSet.of();
}
/** Resource status prohibits this operation. */
public static class ResourceStatusProhibitsOperationException
extends StatusProhibitsOperationException {
public ResourceStatusProhibitsOperationException(Set<StatusValue> status) {
super("Operation disallowed by status: " + Joiner.on(", ").join(status));
}
}
}

View file

@ -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 <R> the resource type being created
* @param <B> a builder for the resource
*/
public abstract class BaseDomainCreateFlow<R extends DomainBase, B extends Builder<R, ?>>
extends ResourceCreateFlow<R, B, Create> {
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<LrpTokenEntity> lrpToken;
protected Optional<RegistryExtraFlowLogic> 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<InternetDomainName> 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.
*
* <p>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.
*
* <p>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.
*
* <p>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<DomainResource>() {
@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.<LrpTokenEntity>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<String> 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");
}
}
}

View file

@ -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");
}
}
}

View file

@ -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<ImmutableObject> entitiesToSave = new ImmutableSet.Builder<>();
HistoryEntry historyEntry = buildHistory(repoId, period);
entitiesToSave.add(historyEntry);
ImmutableSet<? extends ImmutableObject> 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.<StatusValue>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 extends ImmutableObject> T getOnly(
Iterable<? extends ImmutableObject> objects, Class<T> 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<Flag> 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<? extends ImmutableObject> 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().<Object>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<FeeTransformResponseExtension> 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. */

View file

@ -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<DomainApplication, Builder> {
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<AbstractSignedMark, EncodedSignedMark>() {
@Override
public EncodedSignedMark apply(AbstractSignedMark abstractSignedMark) {
return (EncodedSignedMark) abstractSignedMark;
}})
.toList());
DomainApplication newApplication = applicationBuilder.build();
HistoryEntry historyEntry = buildHistory(newApplication.getRepoId(), command.getPeriod());
ImmutableSet.Builder<ImmutableObject> 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<DomainAppl
throw new LandrushApplicationDisallowedDuringSunriseException();
}
} else {
if (launchCreate.getNotice() != null) { // Can't use a claims notice id with a signed mark.
if (hasClaimsNotice) { // Can't use a claims notice id with a signed mark.
throw new NoticeCannotBeUsedWithSignedMarkException();
}
if (tldState == TldState.LANDRUSH) {
throw new SunriseApplicationDisallowedDuringLandrushException();
}
}
String domainLabel = domainName.parts().get(0);
validateLaunchCreateNotice(launchCreate.getNotice(), domainLabel, isSuperuser, now);
// If a signed mark was provided, then it must match the desired domain label.
if (!launchCreate.getSignedMarks().isEmpty()) {
verifySignedMarks(launchCreate.getSignedMarks(), domainLabel, now);
}
}
@Override
protected void verifyDomainCreateIsAllowed() throws EppException {
validateFeeChallenge(
targetId, getTld(), now, feeCreate, commandOperations.getTotalCost());
if (!isSuperuser && tldState == TldState.LANDRUSH) {
// Prohibit creating a landrush application in LANDRUSH (but not in SUNRUSH) if there is
// exactly one sunrise application for the same name.
/**
* Prohibit creating a landrush application in LANDRUSH (but not in SUNRUSH) if there is exactly
* one sunrise application for the same name.
*/
private void prohibitLandrushIfExactlyOneSunrise(Registry registry)
throws UncontestedSunriseApplicationBlockedInLandrushException {
if (registry.getTldState(now) == TldState.LANDRUSH) {
ImmutableSet<DomainApplication> applications =
loadActiveApplicationsByDomainName(targetId, now);
if (applications.size() == 1
@ -150,70 +307,32 @@ public class DomainApplicationCreateFlow extends BaseDomainCreateFlow<DomainAppl
throw new UncontestedSunriseApplicationBlockedInLandrushException();
}
}
// Fail if the domain is already registered (e.g. this is a landrush application but the domain
// was awarded at the end of sunrise).
if (loadAndGetKey(DomainResource.class, targetId, now) != null) {
throw new ResourceAlreadyExistsException(targetId);
}
}
@Override
protected void setDomainCreateProperties(Builder builder) {
builder
.setCreationTrid(trid)
.setPhase(launchCreate.getPhase())
.setApplicationStatus(ApplicationStatus.VALIDATED)
.addStatusValue(StatusValue.PENDING_CREATE);
if (!launchCreate.getSignedMarks().isEmpty()) {
builder.setEncodedSignedMarks(FluentIterable
.from(launchCreate.getSignedMarks())
.transform(new Function<AbstractSignedMark, EncodedSignedMark>() {
@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<ResponseExtension> createResponseExtensions(
String applicationId,
LaunchPhase launchPhase,
FeeTransformCommandExtension feeCreate,
EppCommandOperations commandOperations) {
ImmutableList.Builder<ResponseExtension> 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. */

View file

@ -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();
}
}

View file

@ -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<TldState> 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<ImmutableObject> 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.<BillingEvent.Flag>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.<BillingEvent.Flag>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<RegistryExtraFlowLogic> 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<FeeTransformResponseExtension> createResponseExtensions(
FeeTransformCommandExtension feeCreate, EppCommandOperations commandOperations) {
return (feeCreate == null)
? null
: ImmutableList.of(createFeeCreateResponse(feeCreate, commandOperations));
}
/** There is an open application for this domain. */

View file

@ -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<DomainResource, Builder> {
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().<Object>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()));
}
}

View file

@ -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<String> 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.
*
* <p>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<DomainResource>() {
@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<LrpTokenEntity> 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");
}
}
}

View file

@ -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.
*
* <p>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<LrpTokenEntity> getMatchingLrpToken(Create createCommand, String tld) {
public static Optional<LrpTokenEntity> 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.<LrpTokenEntity>absent();

View file

@ -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();

View file

@ -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);
}

View file

@ -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);

View file

@ -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"));

View file

@ -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;

View file

@ -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.<String>of())
.build());
thrown.expect(NotAuthorizedForTldException.class);
runFlow();
}
@Test
public void testFailure_onlySuperuserCanAllocate() throws Exception {
setupDomainApplication("tld", TldState.GENERAL_AVAILABILITY);

View file

@ -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();
}

View file

@ -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<DomainCreateFlow,
.hasSmdId("0000001761376042759136-65535").and()
.hasLaunchNotice(null);
TaskMatcher task = new TaskMatcher().payload(
"16-TLD,test-validate.tld,0000001761376042759136-65535,1,2014-09-09T09:09:09.001Z");
reloadResourceByForeignKey().getRepoId()
+ ",test-validate.tld,0000001761376042759136-65535,1,2014-09-09T09:09:09.001Z");
assertTasksEnqueued(QUEUE_SUNRISE, task);
}

View file

@ -19,7 +19,7 @@
</create>
<extension>
<allocate:create xmlns:allocate="urn:google:params:xml:ns:allocate-1.0">
<allocate:applicationRoid>A-EXAMPLE</allocate:applicationRoid>
<allocate:applicationRoid>B-EXAMPLE</allocate:applicationRoid>
<allocate:applicationTime>2014-01-01T00:00:00Z</allocate:applicationTime>
</allocate:create>
</extension>

View file

@ -14,7 +14,7 @@
<launch:creData
xmlns:launch="urn:ietf:params:xml:ns:launch-1.0">
<launch:phase>sunrise</launch:phase>
<launch:applicationID>A-EXAMPLE</launch:applicationID>
<launch:applicationID>B-EXAMPLE</launch:applicationID>
</launch:creData>
</extension>
<trID>

View file

@ -3,7 +3,7 @@
<result code="1301">
<msg>Command completed successfully; ack to dequeue</msg>
</result>
<msgQ count="1" id="1-B-EXAMPLE-17-23">
<msgQ count="1" id="1-C-EXAMPLE-17-23">
<qDate>2001-01-01T00:00:00Z</qDate>
<msg>Transfer requested.</msg>
</msgQ>

View file

@ -3,7 +3,7 @@
<result code="1301">
<msg>Command completed successfully; ack to dequeue</msg>
</result>
<msgQ count="1" id="1-B-EXAMPLE-17-22">
<msgQ count="1" id="1-C-EXAMPLE-17-22">
<qDate>2001-01-06T00:00:00Z</qDate>
<msg>Transfer approved.</msg>
</msgQ>

View file

@ -4,7 +4,7 @@
<result code="1301">
<msg>Command completed successfully; ack to dequeue</msg>
</result>
<msgQ count="1" id="1-B-EXAMPLE-17-21">
<msgQ count="1" id="1-C-EXAMPLE-17-21">
<qDate>2001-01-06T00:00:00Z</qDate>
<msg>Transfer approved.</msg>
</msgQ>

View file

@ -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