google-nomulus/java/google/registry/flows/domain/DomainApplicationCreateFlow.java
mcilwain 1c4e79f99e Don't allow non-active registrars to create domains or applications
Specifically, this prevents suspended registrars from creating domains or applications. Pending registrars already can't perform these actions because they get an error message when attempting to log in.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=170481338
2017-10-04 16:16:45 -04:00

427 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.verifyRegistrarIsActive;
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.DomainNameExistsAsTldException}
* @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 DomainFlowUtils.RegistrarMustBeActiveToCreateDomainsException}
* @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);
verifyRegistrarIsActive(clientId);
DateTime now = ofy().getTransactionTime();
Create command = cloneAndLinkReferences((Create) resourceCommand, now);
// Fail if the domain is already registered (e.g. this is a landrush application but the domain
// 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();
if (!isSuperuser) {
// Access to the TLD should be checked before the subsequent checks as it is a greater concern
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 =
buildHistoryEntry(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 buildHistoryEntry(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");
}
}
}