// 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() { @Override public EncodedSignedMark apply(AbstractSignedMark abstractSignedMark) { return (EncodedSignedMark) abstractSignedMark; }}) .toList()) .build(); HistoryEntry historyEntry = buildHistoryEntry(newApplication.getRepoId(), command.getPeriod(), now); ImmutableSet.Builder entitiesToSave = new ImmutableSet.Builder<>(); entitiesToSave.add( newApplication, historyEntry, DomainApplicationIndex.createUpdatedInstance(newApplication), EppResourceIndex.create(Key.create(newApplication))); // Anchor tenant registrations override LRP, and landrush applications can skip it. // If 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 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 createResponseExtensions( String applicationId, LaunchPhase launchPhase, FeeTransformCommandExtension feeCreate, FeesAndCredits feesAndCredits) { ImmutableList.Builder 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"); } } }