google-nomulus/java/google/registry/flows/domain/DomainApplicationCreateFlow.java
cgoldfeder ae039aa0d8 Remove all vestiges of memcache
Memcache is already off but now it's not in the code anymore.

This includes removing domain creation failfast, since that is actually
slower now than just running the flow - all you gain is a non-transactional
read over a transactional read, but the cost is that you always pay that
read, which is going to drive up latency.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=158183506
2017-06-14 10:28:24 -04:00

419 lines
22 KiB
Java

// Copyright 2017 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 com.google.common.collect.Iterables.getOnlyElement;
import static google.registry.flows.FlowUtils.persistEntityChanges;
import static google.registry.flows.FlowUtils.validateClientIsLoggedIn;
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.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.validateRegistrationPeriod;
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.verifyUnitIsYears;
import static google.registry.model.EppResourceUtils.createDomainRepoId;
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 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.ExtensionManager;
import google.registry.flows.FlowModule.ClientId;
import google.registry.flows.FlowModule.Superuser;
import google.registry.flows.FlowModule.TargetId;
import google.registry.flows.TransactionalFlow;
import google.registry.flows.annotations.ReportingSpec;
import google.registry.flows.custom.DomainApplicationCreateFlowCustomLogic;
import google.registry.flows.custom.DomainApplicationCreateFlowCustomLogic.AfterValidationParameters;
import google.registry.flows.custom.DomainApplicationCreateFlowCustomLogic.BeforeResponseParameters;
import google.registry.flows.custom.DomainApplicationCreateFlowCustomLogic.BeforeResponseReturnData;
import google.registry.flows.custom.EntityChanges;
import google.registry.flows.domain.DomainFlowUtils.NameserversNotSpecifiedForNameserverRestrictedDomainException;
import google.registry.flows.domain.DomainFlowUtils.NameserversNotSpecifiedForTldWithNameserverWhitelistException;
import google.registry.model.ImmutableObject;
import google.registry.model.domain.DomainApplication;
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.FeeCreateCommandExtension;
import google.registry.model.domain.fee.FeeTransformCommandExtension;
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.eppcommon.Trid;
import google.registry.model.eppinput.EppInput;
import google.registry.model.eppinput.ResourceCommand;
import google.registry.model.eppoutput.CreateData.DomainCreateData;
import google.registry.model.eppoutput.EppResponse;
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.reporting.IcannReportingTypes.ActivityReportField;
import google.registry.model.smd.AbstractSignedMark;
import google.registry.model.smd.EncodedSignedMark;
import javax.inject.Inject;
import org.joda.time.DateTime;
/**
* 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 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 DomainFlowTmchUtils.Base64RequiredForEncodedSignedMarksException}
* @error {@link DomainFlowUtils.ClaimsPeriodEndedException}
* @error {@link DomainFlowUtils.CurrencyUnitMismatchException}
* @error {@link DomainFlowUtils.CurrencyValueScaleException}
* @error {@link DomainFlowUtils.DashesInThirdAndFourthException}
* @error {@link DomainFlowUtils.DomainLabelTooLongException}
* @error {@link DomainFlowUtils.DomainReservedException}
* @error {@link DomainFlowUtils.DuplicateContactForRoleException}
* @error {@link DomainFlowUtils.EmptyDomainNamePartException}
* @error {@link DomainFlowUtils.ExceedsMaxRegistrationYearsException}
* @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.NameserversNotAllowedForDomainException}
* @error {@link DomainFlowUtils.NameserversNotAllowedForTldException}
* @error {@link NameserversNotSpecifiedForNameserverRestrictedDomainException}
* @error {@link NameserversNotSpecifiedForTldWithNameserverWhitelistException}
* @error {@link DomainFlowTmchUtils.NoMarksFoundMatchingDomainException}
* @error {@link DomainFlowUtils.NotAuthorizedForTldException}
* @error {@link DomainFlowUtils.PremiumNameBlockedException}
* @error {@link DomainFlowUtils.RegistrantNotAllowedException}
* @error {@link DomainFlowTmchUtils.SignedMarksMustBeEncodedException}
* @error {@link DomainFlowTmchUtils.SignedMarkCertificateExpiredException}
* @error {@link DomainFlowTmchUtils.SignedMarkCertificateInvalidException}
* @error {@link DomainFlowTmchUtils.SignedMarkCertificateNotYetValidException}
* @error {@link DomainFlowTmchUtils.SignedMarkCertificateRevokedException}
* @error {@link DomainFlowTmchUtils.SignedMarkCertificateSignatureException}
* @error {@link DomainFlowTmchUtils.SignedMarkEncodingErrorException}
* @error {@link DomainFlowTmchUtils.SignedMarkParsingErrorException}
* @error {@link DomainFlowTmchUtils.SignedMarkRevokedErrorException}
* @error {@link DomainFlowTmchUtils.SignedMarkSignatureException}
* @error {@link DomainFlowUtils.TldDoesNotExistException}
* @error {@link DomainFlowUtils.TooManyDsRecordsException}
* @error {@link DomainFlowUtils.TooManyNameserversException}
* @error {@link DomainFlowTmchUtils.TooManySignedMarksException}
* @error {@link DomainFlowUtils.TrailingDashException}
* @error {@link DomainFlowUtils.UnexpectedClaimsNoticeException}
* @error {@link DomainFlowUtils.UnsupportedFeeAttributeException}
* @error {@link DomainFlowUtils.UnsupportedMarkTypeException}
*/
@ReportingSpec(ActivityReportField.DOMAIN_CREATE) // Applications are technically domains in EPP.
public final class DomainApplicationCreateFlow implements TransactionalFlow {
@Inject ExtensionManager extensionManager;
@Inject EppInput eppInput;
@Inject AuthInfo authInfo;
@Inject ResourceCommand resourceCommand;
@Inject @ClientId String clientId;
@Inject @TargetId String targetId;
@Inject @Superuser boolean isSuperuser;
@Inject HistoryEntry.Builder historyBuilder;
@Inject Trid trid;
@Inject EppResponse.Builder responseBuilder;
@Inject DomainApplicationCreateFlowCustomLogic customLogic;
@Inject DomainFlowTmchUtils tmchUtils;
@Inject DomainPricingLogic pricingLogic;
@Inject DomainApplicationCreateFlow() {}
@Override
public final EppResponse run() throws EppException {
extensionManager.register(
FeeCreateCommandExtension.class,
SecDnsCreateExtension.class,
MetadataExtension.class,
LaunchCreateExtension.class);
customLogic.beforeValidation();
extensionManager.validate();
validateClientIsLoggedIn(clientId);
DateTime now = ofy().getTransactionTime();
Create command = cloneAndLinkReferences((Create) resourceCommand, now);
// Fail if the domain is already registered (e.g. this is a landrush application but the domain
// 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(clientId, tld);
Registry registry = Registry.get(tld);
FeesAndCredits feesAndCredits =
pricingLogic.getCreatePrice(registry, targetId, now, command.getPeriod().getValue());
verifyUnitIsYears(command.getPeriod());
int years = command.getPeriod().getValue();
validateRegistrationPeriod(years);
validateCreateCommandContactsAndNameservers(command, registry, domainName);
LaunchCreateExtension launchCreate = eppInput.getSingleExtension(LaunchCreateExtension.class);
if (launchCreate != null) {
validateLaunchCreateExtension(launchCreate, registry, domainName, now);
}
boolean isAnchorTenant =
matchesAnchorTenantReservation(domainName, authInfo.getPw().getValue());
// 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) {
verifyPremiumNameIsNotBlocked(targetId, now, clientId);
prohibitLandrushIfExactlyOneSunrise(registry, now);
if (!isAnchorTenant) {
boolean isSunriseApplication = !launchCreate.getSignedMarks().isEmpty();
verifyNotReserved(domainName, isSunriseApplication);
}
}
FeeCreateCommandExtension feeCreate =
eppInput.getSingleExtension(FeeCreateCommandExtension.class);
validateFeeChallenge(targetId, tld, now, feeCreate, feesAndCredits);
SecDnsCreateExtension secDnsCreate =
validateSecDnsExtension(eppInput.getSingleExtension(SecDnsCreateExtension.class));
customLogic.afterValidation(
AfterValidationParameters.newBuilder()
.setDomainName(domainName)
.setYears(years)
.build());
DomainApplication newApplication = new DomainApplication.Builder()
.setCreationTrid(trid)
.setCreationClientId(clientId)
.setPersistedCurrentSponsorClientId(clientId)
.setRepoId(createDomainRepoId(ObjectifyService.allocateId(), tld))
.setLaunchNotice(launchCreate == null ? null : launchCreate.getNotice())
.setIdnTableName(idnTableName)
.setPhase(launchCreate.getPhase())
.setPeriod(command.getPeriod())
.setApplicationStatus(ApplicationStatus.VALIDATED)
.addStatusValue(StatusValue.PENDING_CREATE)
.setDsData(secDnsCreate == null ? null : secDnsCreate.getDsData())
.setRegistrant(command.getRegistrant())
.setAuthInfo(command.getAuthInfo())
.setFullyQualifiedDomainName(targetId)
.setNameservers(command.getNameservers())
.setContacts(command.getContacts())
.setEncodedSignedMarks(FluentIterable
.from(launchCreate.getSignedMarks())
.transform(new Function<AbstractSignedMark, EncodedSignedMark>() {
@Override
public EncodedSignedMark apply(AbstractSignedMark abstractSignedMark) {
return (EncodedSignedMark) abstractSignedMark;
}})
.toList())
.build();
HistoryEntry historyEntry = buildHistory(newApplication.getRepoId(), command.getPeriod(), now);
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 a token is passed in outside of an LRP phase, it is simply ignored (i.e. never redeemed).
if (registry.getLrpPeriod().contains(now) && !isAnchorTenant) {
entitiesToSave.add(
prepareMarkedLrpTokenEntity(authInfo.getPw().getValue(), domainName, historyEntry));
}
EntityChanges entityChanges =
customLogic.beforeSave(
DomainApplicationCreateFlowCustomLogic.BeforeSaveParameters.newBuilder()
.setNewApplication(newApplication)
.setHistoryEntry(historyEntry)
.setEntityChanges(
EntityChanges.newBuilder().setSaves(entitiesToSave.build()).build())
.setYears(years)
.build());
persistEntityChanges(entityChanges);
BeforeResponseReturnData responseData =
customLogic.beforeResponse(
BeforeResponseParameters.newBuilder()
.setResData(DomainCreateData.create(targetId, now, null))
.setResponseExtensions(
createResponseExtensions(
newApplication.getForeignKey(),
launchCreate.getPhase(),
feeCreate,
feesAndCredits))
.build());
return responseBuilder
.setResData(responseData.resData())
.setExtensions(responseData.responseExtensions())
.build();
}
private void validateLaunchCreateExtension(
LaunchCreateExtension launchCreate,
Registry registry,
InternetDomainName domainName,
DateTime now) 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).
if (tldState == TldState.SUNRISE) {
throw new LandrushApplicationDisallowedDuringSunriseException();
}
} else {
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()) {
tmchUtils.verifySignedMarks(launchCreate.getSignedMarks(), domainLabel, now);
}
}
/**
* 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, DateTime now)
throws UncontestedSunriseApplicationBlockedInLandrushException {
if (registry.getTldState(now) == TldState.LANDRUSH) {
ImmutableSet<DomainApplication> applications =
loadActiveApplicationsByDomainName(targetId, now);
if (applications.size() == 1
&& getOnlyElement(applications).getPhase().equals(LaunchPhase.SUNRISE)) {
throw new UncontestedSunriseApplicationBlockedInLandrushException();
}
}
}
private HistoryEntry buildHistory(String repoId, Period period, DateTime now) {
return historyBuilder
.setType(HistoryEntry.Type.DOMAIN_APPLICATION_CREATE)
.setPeriod(period)
.setModificationTime(now)
.setParent(Key.create(DomainApplication.class, repoId))
.build();
}
private static ImmutableList<ResponseExtension> createResponseExtensions(
String applicationId,
LaunchPhase launchPhase,
FeeTransformCommandExtension feeCreate,
FeesAndCredits feesAndCredits) {
ImmutableList.Builder<ResponseExtension> responseExtensionsBuilder =
new ImmutableList.Builder<>();
responseExtensionsBuilder.add(new LaunchCreateResponseExtension.Builder()
.setPhase(launchPhase)
.setApplicationId(applicationId)
.build());
if (feeCreate != null) {
responseExtensionsBuilder.add(createFeeCreateResponse(feeCreate, feesAndCredits));
}
return responseExtensionsBuilder.build();
}
/** Landrush applications are disallowed during sunrise. */
static class LandrushApplicationDisallowedDuringSunriseException
extends RequiredParameterMissingException {
public LandrushApplicationDisallowedDuringSunriseException() {
super("Landrush applications are disallowed during sunrise");
}
}
/** A notice cannot be specified when using a signed mark. */
static class NoticeCannotBeUsedWithSignedMarkException extends CommandUseErrorException {
public NoticeCannotBeUsedWithSignedMarkException() {
super("A notice cannot be specified when using a signed mark");
}
}
/** Sunrise applications are disallowed during landrush. */
static class SunriseApplicationDisallowedDuringLandrushException
extends CommandUseErrorException {
public SunriseApplicationDisallowedDuringLandrushException() {
super("Sunrise applications are disallowed during landrush");
}
}
/** This name has already been claimed by a sunrise applicant. */
static class UncontestedSunriseApplicationBlockedInLandrushException
extends ObjectAlreadyExistsException {
public UncontestedSunriseApplicationBlockedInLandrushException() {
super("This name has already been claimed by a sunrise applicant");
}
}
}