diff --git a/java/google/registry/config/RegistryConfig.java b/java/google/registry/config/RegistryConfig.java index a9b40eb98..bef7249ff 100644 --- a/java/google/registry/config/RegistryConfig.java +++ b/java/google/registry/config/RegistryConfig.java @@ -1047,6 +1047,12 @@ public final class RegistryConfig { return config.registryPolicy.whoisCommandFactoryClass; } + @Provides + @Config("allocationTokenCustomLogicClass") + public static String provideAllocationTokenCustomLogicClass(RegistryConfigSettings config) { + return config.registryPolicy.allocationTokenCustomLogicClass; + } + /** * Returns the header text at the top of the reserved terms exported list. * diff --git a/java/google/registry/config/RegistryConfigSettings.java b/java/google/registry/config/RegistryConfigSettings.java index 2fbf1008b..bda47062c 100644 --- a/java/google/registry/config/RegistryConfigSettings.java +++ b/java/google/registry/config/RegistryConfigSettings.java @@ -70,6 +70,7 @@ public class RegistryConfigSettings { public String productName; public String customLogicFactoryClass; public String whoisCommandFactoryClass; + public String allocationTokenCustomLogicClass; public int contactAutomaticTransferDays; public String greetingServerId; public List registrarChangesNotificationEmailAddresses; diff --git a/java/google/registry/config/files/default-config.yaml b/java/google/registry/config/files/default-config.yaml index 336527aac..582c811f1 100644 --- a/java/google/registry/config/files/default-config.yaml +++ b/java/google/registry/config/files/default-config.yaml @@ -41,6 +41,10 @@ registryPolicy: # See whois/WhoisCommandFactory.java whoisCommandFactoryClass: google.registry.whois.WhoisCommandFactory + # Custom logic class for handling allocation tokens. + # See flows/domain/token/AllocationTokenCustomLogic.java + allocationTokenCustomLogicClass: google.registry.flows.domain.token.AllocationTokenCustomLogic + # Length of time after which contact transfers automatically conclude. contactAutomaticTransferDays: 5 diff --git a/java/google/registry/flows/FlowComponent.java b/java/google/registry/flows/FlowComponent.java index b3f0f6b15..c9b7b1596 100644 --- a/java/google/registry/flows/FlowComponent.java +++ b/java/google/registry/flows/FlowComponent.java @@ -48,6 +48,7 @@ import google.registry.flows.domain.DomainTransferQueryFlow; import google.registry.flows.domain.DomainTransferRejectFlow; import google.registry.flows.domain.DomainTransferRequestFlow; import google.registry.flows.domain.DomainUpdateFlow; +import google.registry.flows.domain.token.AllocationTokenModule; import google.registry.flows.host.HostCheckFlow; import google.registry.flows.host.HostCreateFlow; import google.registry.flows.host.HostDeleteFlow; @@ -63,6 +64,7 @@ import google.registry.model.eppcommon.Trid; /** Dagger component for flow classes. */ @FlowScope @Subcomponent(modules = { + AllocationTokenModule.class, AsyncFlowsModule.class, CustomLogicModule.class, DnsModule.class, diff --git a/java/google/registry/flows/domain/DomainApplicationCreateFlow.java b/java/google/registry/flows/domain/DomainApplicationCreateFlow.java index 3acdfb240..1b9da10d5 100644 --- a/java/google/registry/flows/domain/DomainApplicationCreateFlow.java +++ b/java/google/registry/flows/domain/DomainApplicationCreateFlow.java @@ -180,7 +180,7 @@ public final class DomainApplicationCreateFlow implements TransactionalFlow { @Inject HistoryEntry.Builder historyBuilder; @Inject Trid trid; @Inject EppResponse.Builder responseBuilder; - @Inject DomainApplicationCreateFlowCustomLogic customLogic; + @Inject DomainApplicationCreateFlowCustomLogic flowCustomLogic; @Inject DomainFlowTmchUtils tmchUtils; @Inject DomainPricingLogic pricingLogic; @Inject DomainApplicationCreateFlow() {} @@ -192,7 +192,7 @@ public final class DomainApplicationCreateFlow implements TransactionalFlow { SecDnsCreateExtension.class, MetadataExtension.class, LaunchCreateExtension.class); - customLogic.beforeValidation(); + flowCustomLogic.beforeValidation(); extensionManager.validate(); validateClientIsLoggedIn(clientId); verifyRegistrarIsActive(clientId); @@ -237,7 +237,7 @@ public final class DomainApplicationCreateFlow implements TransactionalFlow { validateFeeChallenge(targetId, tld, now, feeCreate, feesAndCredits); Optional secDnsCreate = validateSecDnsExtension(eppInput.getSingleExtension(SecDnsCreateExtension.class)); - customLogic.afterValidation( + flowCustomLogic.afterValidation( AfterValidationParameters.newBuilder() .setDomainName(domainName) .setYears(years) @@ -282,7 +282,7 @@ public final class DomainApplicationCreateFlow implements TransactionalFlow { prepareMarkedLrpTokenEntity(authInfo.getPw().getValue(), domainName, historyEntry)); } EntityChanges entityChanges = - customLogic.beforeSave( + flowCustomLogic.beforeSave( DomainApplicationCreateFlowCustomLogic.BeforeSaveParameters.newBuilder() .setNewApplication(newApplication) .setHistoryEntry(historyEntry) @@ -292,7 +292,7 @@ public final class DomainApplicationCreateFlow implements TransactionalFlow { .build()); persistEntityChanges(entityChanges); BeforeResponseReturnData responseData = - customLogic.beforeResponse( + flowCustomLogic.beforeResponse( BeforeResponseParameters.newBuilder() .setResData(DomainCreateData.create(targetId, now, null)) .setResponseExtensions( diff --git a/java/google/registry/flows/domain/DomainCheckFlow.java b/java/google/registry/flows/domain/DomainCheckFlow.java index 1647c8f38..a360570f3 100644 --- a/java/google/registry/flows/domain/DomainCheckFlow.java +++ b/java/google/registry/flows/domain/DomainCheckFlow.java @@ -14,9 +14,9 @@ package google.registry.flows.domain; +import static com.google.common.base.Strings.emptyToNull; import static google.registry.flows.FlowUtils.validateClientIsLoggedIn; import static google.registry.flows.ResourceFlowUtils.verifyTargetIdCount; -import static google.registry.flows.domain.AllocationTokenFlowUtils.checkDomainsWithToken; import static google.registry.flows.domain.DomainFlowUtils.checkAllowedAccessToTld; import static google.registry.flows.domain.DomainFlowUtils.getReservationTypes; import static google.registry.flows.domain.DomainFlowUtils.handleFeeRequest; @@ -44,6 +44,7 @@ import google.registry.flows.annotations.ReportingSpec; import google.registry.flows.custom.DomainCheckFlowCustomLogic; import google.registry.flows.custom.DomainCheckFlowCustomLogic.BeforeResponseParameters; import google.registry.flows.custom.DomainCheckFlowCustomLogic.BeforeResponseReturnData; +import google.registry.flows.domain.token.AllocationTokenFlowUtils; import google.registry.model.domain.DomainCommand.Check; import google.registry.model.domain.DomainResource; import google.registry.model.domain.fee.FeeCheckCommandExtension; @@ -114,7 +115,8 @@ public final class DomainCheckFlow implements Flow { @Inject @Superuser boolean isSuperuser; @Inject Clock clock; @Inject EppResponse.Builder responseBuilder; - @Inject DomainCheckFlowCustomLogic customLogic; + @Inject AllocationTokenFlowUtils allocationTokenFlowUtils; + @Inject DomainCheckFlowCustomLogic flowCustomLogic; @Inject DomainPricingLogic pricingLogic; @Inject DomainCheckFlow() {} @@ -122,7 +124,7 @@ public final class DomainCheckFlow implements Flow { public EppResponse run() throws EppException { extensionManager.register( FeeCheckCommandExtension.class, LaunchCheckExtension.class, AllocationTokenExtension.class); - customLogic.beforeValidation(); + flowCustomLogic.beforeValidation(); extensionManager.validate(); validateClientIsLoggedIn(clientId); List targetIds = ((Check) resourceCommand).getTargetIds(); @@ -144,7 +146,7 @@ public final class DomainCheckFlow implements Flow { } } ImmutableMap domainNames = domains.build(); - customLogic.afterValidation( + flowCustomLogic.afterValidation( DomainCheckFlowCustomLogic.AfterValidationParameters.newBuilder() .setDomainNames(domainNames) // TODO: Use as of date from fee extension v0.12 instead of now, if specified. @@ -155,7 +157,7 @@ public final class DomainCheckFlow implements Flow { eppInput.getSingleExtension(AllocationTokenExtension.class); ImmutableMap tokenCheckResults = allocationTokenExtension.isPresent() - ? checkDomainsWithToken( + ? allocationTokenFlowUtils.checkDomainsWithToken( targetIds, allocationTokenExtension.get().getAllocationToken(), clientId) : ImmutableMap.of(); ImmutableList.Builder checks = new ImmutableList.Builder<>(); @@ -165,7 +167,7 @@ public final class DomainCheckFlow implements Flow { checks.add(DomainCheck.create(!message.isPresent(), targetId, message.orElse(null))); } BeforeResponseReturnData responseData = - customLogic.beforeResponse( + flowCustomLogic.beforeResponse( BeforeResponseParameters.newBuilder() .setDomainChecks(checks.build()) .setResponseExtensions(getResponseExtensions(domainNames, now)) @@ -202,9 +204,7 @@ public final class DomainCheckFlow implements Flow { if (!reservationTypes.isEmpty()) { return Optional.of(getTypeOfHighestSeverity(reservationTypes).getMessageForCheck()); } - return tokenCheckResults.containsKey(domainName.toString()) - ? Optional.of(tokenCheckResults.get(domainName.toString())) - : Optional.empty(); + return Optional.ofNullable(emptyToNull(tokenCheckResults.get(domainName.toString()))); } /** Handle the fee check extension. */ diff --git a/java/google/registry/flows/domain/DomainCreateFlow.java b/java/google/registry/flows/domain/DomainCreateFlow.java index 90f281702..7ddd3af4c 100644 --- a/java/google/registry/flows/domain/DomainCreateFlow.java +++ b/java/google/registry/flows/domain/DomainCreateFlow.java @@ -17,8 +17,6 @@ package google.registry.flows.domain; 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.AllocationTokenFlowUtils.redeemToken; -import static google.registry.flows.domain.AllocationTokenFlowUtils.verifyToken; import static google.registry.flows.domain.DomainFlowUtils.checkAllowedAccessToTld; import static google.registry.flows.domain.DomainFlowUtils.cloneAndLinkReferences; import static google.registry.flows.domain.DomainFlowUtils.createFeeCreateResponse; @@ -67,6 +65,7 @@ import google.registry.flows.custom.DomainCreateFlowCustomLogic; import google.registry.flows.custom.DomainCreateFlowCustomLogic.BeforeResponseParameters; import google.registry.flows.custom.DomainCreateFlowCustomLogic.BeforeResponseReturnData; import google.registry.flows.custom.EntityChanges; +import google.registry.flows.domain.token.AllocationTokenFlowUtils; import google.registry.model.ImmutableObject; import google.registry.model.billing.BillingEvent; import google.registry.model.billing.BillingEvent.Flag; @@ -112,12 +111,12 @@ import org.joda.time.Duration; /** * An EPP flow that creates a new domain resource. * + * @error {@link google.registry.flows.domain.token.AllocationTokenFlowUtils.AlreadyRedeemedAllocationTokenException} + * @error {@link google.registry.flows.domain.token.AllocationTokenFlowUtils.InvalidAllocationTokenException} * @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.ExtensionManager.UndeclaredServiceExtensionException} - * @error {@link AllocationTokenFlowUtils.AlreadyRedeemedAllocationTokenException} - * @error {@link AllocationTokenFlowUtils.InvalidAllocationTokenException} * @error {@link DomainCreateFlow.DomainHasOpenApplicationsException} * @error {@link DomainCreateFlow.NoGeneralRegistrationsInCurrentPhaseException} * @error {@link DomainFlowUtils.NotAuthorizedForTldException} @@ -185,7 +184,8 @@ public class DomainCreateFlow implements TransactionalFlow { @Inject @Superuser boolean isSuperuser; @Inject HistoryEntry.Builder historyBuilder; @Inject EppResponse.Builder responseBuilder; - @Inject DomainCreateFlowCustomLogic customLogic; + @Inject AllocationTokenFlowUtils allocationTokenFlowUtils; + @Inject DomainCreateFlowCustomLogic flowCustomLogic; @Inject DomainFlowTmchUtils tmchUtils; @Inject DomainPricingLogic pricingLogic; @Inject DnsQueue dnsQueue; @@ -199,7 +199,7 @@ public class DomainCreateFlow implements TransactionalFlow { MetadataExtension.class, LaunchCreateExtension.class, AllocationTokenExtension.class); - customLogic.beforeValidation(); + flowCustomLogic.beforeValidation(); extensionManager.validate(); validateClientIsLoggedIn(clientId); verifyRegistrarIsActive(clientId); @@ -262,7 +262,7 @@ public class DomainCreateFlow implements TransactionalFlow { } Optional allocationToken = verifyAllocationTokenIfPresent(domainName, registry, clientId); - customLogic.afterValidation( + flowCustomLogic.afterValidation( DomainCreateFlowCustomLogic.AfterValidationParameters.newBuilder() .setDomainName(domainName) .setYears(years) @@ -325,7 +325,8 @@ public class DomainCreateFlow implements TransactionalFlow { ForeignKeyIndex.create(newDomain, newDomain.getDeletionTime()), EppResourceIndex.create(Key.create(newDomain))); - allocationToken.ifPresent(t -> entitiesToSave.add(redeemToken(t, Key.create(historyEntry)))); + allocationToken.ifPresent( + t -> entitiesToSave.add(allocationTokenFlowUtils.redeemToken(t, Key.create(historyEntry)))); // 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 (isLrpCreate(registry, isAnchorTenant, now)) { @@ -335,7 +336,7 @@ public class DomainCreateFlow implements TransactionalFlow { enqueueTasks(isSunriseCreate, hasClaimsNotice, newDomain); EntityChanges entityChanges = - customLogic.beforeSave( + flowCustomLogic.beforeSave( DomainCreateFlowCustomLogic.BeforeSaveParameters.newBuilder() .setNewDomain(newDomain) .setHistoryEntry(historyEntry) @@ -346,7 +347,7 @@ public class DomainCreateFlow implements TransactionalFlow { persistEntityChanges(entityChanges); BeforeResponseReturnData responseData = - customLogic.beforeResponse( + flowCustomLogic.beforeResponse( BeforeResponseParameters.newBuilder() .setResData(DomainCreateData.create(targetId, now, registrationExpirationTime)) .setResponseExtensions(createResponseExtensions(feeCreate, feesAndCredits)) @@ -389,7 +390,8 @@ public class DomainCreateFlow implements TransactionalFlow { eppInput.getSingleExtension(AllocationTokenExtension.class); return Optional.ofNullable( extension.isPresent() - ? verifyToken(domainName, extension.get().getAllocationToken(), registry, clientId) + ? allocationTokenFlowUtils.verifyToken( + domainName, extension.get().getAllocationToken(), registry, clientId) : null); } diff --git a/java/google/registry/flows/domain/DomainDeleteFlow.java b/java/google/registry/flows/domain/DomainDeleteFlow.java index 1de464048..d0a33a379 100644 --- a/java/google/registry/flows/domain/DomainDeleteFlow.java +++ b/java/google/registry/flows/domain/DomainDeleteFlow.java @@ -128,14 +128,14 @@ public final class DomainDeleteFlow implements TransactionalFlow { @Inject DnsQueue dnsQueue; @Inject Trid trid; @Inject EppResponse.Builder responseBuilder; - @Inject DomainDeleteFlowCustomLogic customLogic; + @Inject DomainDeleteFlowCustomLogic flowCustomLogic; @Inject DomainDeleteFlow() {} @Override public final EppResponse run() throws EppException { extensionManager.register( MetadataExtension.class, SecDnsCreateExtension.class, DomainDeleteSuperuserExtension.class); - customLogic.beforeValidation(); + flowCustomLogic.beforeValidation(); extensionManager.validate(); validateClientIsLoggedIn(clientId); DateTime now = ofy().getTransactionTime(); @@ -143,7 +143,7 @@ public final class DomainDeleteFlow implements TransactionalFlow { DomainResource existingDomain = loadAndVerifyExistence(DomainResource.class, targetId, now); Registry registry = Registry.get(existingDomain.getTld()); verifyDeleteAllowed(existingDomain, registry, now); - customLogic.afterValidation( + flowCustomLogic.afterValidation( AfterValidationParameters.newBuilder().setExistingDomain(existingDomain).build()); ImmutableSet.Builder entitiesToSave = new ImmutableSet.Builder<>(); Builder builder; @@ -214,7 +214,7 @@ public final class DomainDeleteFlow implements TransactionalFlow { } } entitiesToSave.add(newDomain, historyEntry); - EntityChanges entityChanges = customLogic.beforeSave( + EntityChanges entityChanges = flowCustomLogic.beforeSave( BeforeSaveParameters.newBuilder() .setExistingDomain(existingDomain) .setNewDomain(newDomain) @@ -223,7 +223,7 @@ public final class DomainDeleteFlow implements TransactionalFlow { .build()); persistEntityChanges(entityChanges); BeforeResponseReturnData responseData = - customLogic.beforeResponse( + flowCustomLogic.beforeResponse( BeforeResponseParameters.newBuilder() .setResultCode( newDomain.getDeletionTime().isAfter(now) diff --git a/java/google/registry/flows/domain/DomainInfoFlow.java b/java/google/registry/flows/domain/DomainInfoFlow.java index 03450898d..03357280a 100644 --- a/java/google/registry/flows/domain/DomainInfoFlow.java +++ b/java/google/registry/flows/domain/DomainInfoFlow.java @@ -82,7 +82,7 @@ public final class DomainInfoFlow implements Flow { @Inject @TargetId String targetId; @Inject Clock clock; @Inject EppResponse.Builder responseBuilder; - @Inject DomainInfoFlowCustomLogic customLogic; + @Inject DomainInfoFlowCustomLogic flowCustomLogic; @Inject DomainPricingLogic pricingLogic; @Inject @@ -91,14 +91,15 @@ public final class DomainInfoFlow implements Flow { @Override public final EppResponse run() throws EppException { extensionManager.register(FeeInfoCommandExtensionV06.class); - customLogic.beforeValidation(); + flowCustomLogic.beforeValidation(); extensionManager.validate(); validateClientIsLoggedIn(clientId); DateTime now = clock.nowUtc(); DomainResource domain = verifyExistence( DomainResource.class, targetId, loadByForeignKey(DomainResource.class, targetId, now)); verifyOptionalAuthInfo(authInfo, domain); - customLogic.afterValidation(AfterValidationParameters.newBuilder().setDomain(domain).build()); + flowCustomLogic.afterValidation( + AfterValidationParameters.newBuilder().setDomain(domain).build()); // Prefetch all referenced resources. Calling values() blocks until loading is done. ofy().load() .values(union(domain.getNameservers(), domain.getReferencedContacts())).values(); @@ -131,7 +132,7 @@ public final class DomainInfoFlow implements Flow { .setAuthInfo(domain.getAuthInfo()); } BeforeResponseReturnData responseData = - customLogic.beforeResponse( + flowCustomLogic.beforeResponse( BeforeResponseParameters.newBuilder() .setDomain(domain) .setResData(infoBuilder.build()) diff --git a/java/google/registry/flows/domain/DomainRenewFlow.java b/java/google/registry/flows/domain/DomainRenewFlow.java index d692a94c1..322d100aa 100644 --- a/java/google/registry/flows/domain/DomainRenewFlow.java +++ b/java/google/registry/flows/domain/DomainRenewFlow.java @@ -123,14 +123,14 @@ public final class DomainRenewFlow implements TransactionalFlow { @Inject @Superuser boolean isSuperuser; @Inject HistoryEntry.Builder historyBuilder; @Inject EppResponse.Builder responseBuilder; - @Inject DomainRenewFlowCustomLogic customLogic; + @Inject DomainRenewFlowCustomLogic flowCustomLogic; @Inject DomainPricingLogic pricingLogic; @Inject DomainRenewFlow() {} @Override public final EppResponse run() throws EppException { extensionManager.register(FeeRenewCommandExtension.class, MetadataExtension.class); - customLogic.beforeValidation(); + flowCustomLogic.beforeValidation(); extensionManager.validate(); validateClientIsLoggedIn(clientId); DateTime now = ofy().getTransactionTime(); @@ -147,7 +147,7 @@ public final class DomainRenewFlow implements TransactionalFlow { FeesAndCredits feesAndCredits = pricingLogic.getRenewPrice(Registry.get(existingDomain.getTld()), targetId, now, years); validateFeeChallenge(targetId, existingDomain.getTld(), now, feeRenew, feesAndCredits); - customLogic.afterValidation( + flowCustomLogic.afterValidation( AfterValidationParameters.newBuilder() .setExistingDomain(existingDomain) .setNow(now) @@ -178,7 +178,7 @@ public final class DomainRenewFlow implements TransactionalFlow { .addGracePeriod(GracePeriod.forBillingEvent(GracePeriodStatus.RENEW, explicitRenewEvent)) .build(); EntityChanges entityChanges = - customLogic.beforeSave( + flowCustomLogic.beforeSave( BeforeSaveParameters.newBuilder() .setExistingDomain(existingDomain) .setNewDomain(newDomain) @@ -198,7 +198,7 @@ public final class DomainRenewFlow implements TransactionalFlow { .build()); persistEntityChanges(entityChanges); BeforeResponseReturnData responseData = - customLogic.beforeResponse( + flowCustomLogic.beforeResponse( BeforeResponseParameters.newBuilder() .setDomain(newDomain) .setResData(DomainRenewData.create(targetId, newExpirationTime)) diff --git a/java/google/registry/flows/domain/DomainUpdateFlow.java b/java/google/registry/flows/domain/DomainUpdateFlow.java index 092e3e812..5c5c5f587 100644 --- a/java/google/registry/flows/domain/DomainUpdateFlow.java +++ b/java/google/registry/flows/domain/DomainUpdateFlow.java @@ -155,7 +155,7 @@ public final class DomainUpdateFlow implements TransactionalFlow { @Inject HistoryEntry.Builder historyBuilder; @Inject DnsQueue dnsQueue; @Inject EppResponse.Builder responseBuilder; - @Inject DomainUpdateFlowCustomLogic customLogic; + @Inject DomainUpdateFlowCustomLogic flowCustomLogic; @Inject DomainPricingLogic pricingLogic; @Inject DomainUpdateFlow() {} @@ -165,14 +165,14 @@ public final class DomainUpdateFlow implements TransactionalFlow { FeeUpdateCommandExtension.class, MetadataExtension.class, SecDnsUpdateExtension.class); - customLogic.beforeValidation(); + flowCustomLogic.beforeValidation(); extensionManager.validate(); validateClientIsLoggedIn(clientId); DateTime now = ofy().getTransactionTime(); Update command = cloneAndLinkReferences((Update) resourceCommand, now); DomainResource existingDomain = loadAndVerifyExistence(DomainResource.class, targetId, now); verifyUpdateAllowed(command, existingDomain, now); - customLogic.afterValidation( + flowCustomLogic.afterValidation( AfterValidationParameters.newBuilder().setExistingDomain(existingDomain).build()); HistoryEntry historyEntry = buildHistoryEntry(existingDomain, now); DomainResource newDomain = performUpdate(command, existingDomain, now); @@ -194,7 +194,7 @@ public final class DomainUpdateFlow implements TransactionalFlow { createBillingEventForStatusUpdates(existingDomain, newDomain, historyEntry, now); statusUpdateBillingEvent.ifPresent(entitiesToSave::add); EntityChanges entityChanges = - customLogic.beforeSave( + flowCustomLogic.beforeSave( BeforeSaveParameters.newBuilder() .setHistoryEntry(historyEntry) .setNewDomain(newDomain) diff --git a/java/google/registry/flows/domain/token/AllocationTokenCustomLogic.java b/java/google/registry/flows/domain/token/AllocationTokenCustomLogic.java new file mode 100644 index 000000000..915089e04 --- /dev/null +++ b/java/google/registry/flows/domain/token/AllocationTokenCustomLogic.java @@ -0,0 +1,44 @@ +// 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.token; + +import com.google.common.collect.ImmutableMap; +import com.google.common.net.InternetDomainName; +import google.registry.flows.EppException; +import google.registry.model.domain.AllocationToken; +import google.registry.model.registry.Registry; + +/** + * A no-op base class for allocation token custom logic. + * + *

Extend this class and override the hook(s) to perform custom logic. + */ +public class AllocationTokenCustomLogic { + + /** Performs additional custom logic for verifying a token. */ + public AllocationToken verifyToken( + InternetDomainName domainName, AllocationToken token, Registry registry, String clientId) + throws EppException { + // Do nothing. + return token; + } + + /** Performs additional custom logic for performing domain checks using a token. */ + public ImmutableMap checkDomainsWithToken( + ImmutableMap checkResults, AllocationToken tokenEntity, String clientId) { + // Do nothing. + return checkResults; + } +} diff --git a/java/google/registry/flows/domain/AllocationTokenFlowUtils.java b/java/google/registry/flows/domain/token/AllocationTokenFlowUtils.java similarity index 76% rename from java/google/registry/flows/domain/AllocationTokenFlowUtils.java rename to java/google/registry/flows/domain/token/AllocationTokenFlowUtils.java index e1e0d3c43..5fad9a87f 100644 --- a/java/google/registry/flows/domain/AllocationTokenFlowUtils.java +++ b/java/google/registry/flows/domain/token/AllocationTokenFlowUtils.java @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package google.registry.flows.domain; +package google.registry.flows.domain.token; import static google.registry.model.ofy.ObjectifyService.ofy; @@ -27,17 +27,26 @@ import google.registry.model.registry.Registry; import google.registry.model.reporting.HistoryEntry; import java.util.List; import java.util.function.Function; +import javax.inject.Inject; -/** Static utility functions for dealing with {@link AllocationToken}s in domain flows. */ +/** Utility functions for dealing with {@link AllocationToken}s in domain flows. */ +// TODO: Add a test class. public class AllocationTokenFlowUtils { + final AllocationTokenCustomLogic tokenCustomLogic; + + @Inject + AllocationTokenFlowUtils(AllocationTokenCustomLogic tokenCustomLogic) { + this.tokenCustomLogic = tokenCustomLogic; + } + /** * Verifies that a given allocation token string is valid. * * @return the loaded {@link AllocationToken} for that string. * @throws InvalidAllocationTokenException if the token doesn't exist. */ - static AllocationToken verifyToken( + public AllocationToken verifyToken( InternetDomainName domainName, String token, Registry registry, String clientId) throws EppException { AllocationToken tokenEntity = ofy().load().key(Key.create(AllocationToken.class, token)).now(); @@ -47,7 +56,7 @@ public class AllocationTokenFlowUtils { if (tokenEntity.isRedeemed()) { throw new AlreadyRedeemedAllocationTokenException(); } - return tokenEntity; + return tokenCustomLogic.verifyToken(domainName, tokenEntity, registry, clientId); } /** @@ -55,9 +64,9 @@ public class AllocationTokenFlowUtils { * * @return A map of domain names to domain check error response messages. If a message is present * for a a given domain then it does not validate with this allocation token; domains that do - * validate are not present in the map. + * validate have blank messages (i.e. no error). */ - static ImmutableMap checkDomainsWithToken( + public ImmutableMap checkDomainsWithToken( List domainNames, String token, String clientId) { AllocationToken tokenEntity = ofy().load().key(Key.create(AllocationToken.class, token)).now(); String result; @@ -66,23 +75,23 @@ public class AllocationTokenFlowUtils { } else if (tokenEntity.isRedeemed()) { result = AlreadyRedeemedAllocationTokenException.ERROR_MSG_SHORT; } else { - return ImmutableMap.of(); + result = ""; } - // TODO(b/70628322): For now all checks yield the same result, but custom logic will soon allow - // them to differ, e.g. if tokens can only be used on certain TLDs. - return domainNames - .stream() - .collect(ImmutableMap.toImmutableMap(Function.identity(), domainName -> result)); + ImmutableMap checkResults = + domainNames + .stream() + .collect(ImmutableMap.toImmutableMap(Function.identity(), domainName -> result)); + return tokenCustomLogic.checkDomainsWithToken(checkResults, tokenEntity, clientId); } /** Redeems an {@link AllocationToken}, returning the redeemed copy. */ - static AllocationToken redeemToken( + public AllocationToken redeemToken( AllocationToken token, Key redemptionHistoryEntry) { return token.asBuilder().setRedemptionHistoryEntry(redemptionHistoryEntry).build(); } /** The allocation token was already redeemed. */ - static class AlreadyRedeemedAllocationTokenException + public static class AlreadyRedeemedAllocationTokenException extends AssociationProhibitsOperationException { public static final String ERROR_MSG_LONG = "The allocation token was already redeemed"; @@ -96,7 +105,7 @@ public class AllocationTokenFlowUtils { } /** The allocation token is invalid. */ - static class InvalidAllocationTokenException extends ParameterValueSyntaxErrorException { + public static class InvalidAllocationTokenException extends ParameterValueSyntaxErrorException { public InvalidAllocationTokenException() { super("The allocation token is invalid"); } diff --git a/java/google/registry/flows/domain/token/AllocationTokenModule.java b/java/google/registry/flows/domain/token/AllocationTokenModule.java new file mode 100644 index 000000000..c980aff5c --- /dev/null +++ b/java/google/registry/flows/domain/token/AllocationTokenModule.java @@ -0,0 +1,33 @@ +// 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.token; + +import static google.registry.util.TypeUtils.getClassFromString; +import static google.registry.util.TypeUtils.instantiate; + +import dagger.Module; +import dagger.Provides; +import google.registry.config.RegistryConfig.Config; + +/** Dagger module for allocation token classes. */ +@Module +public class AllocationTokenModule { + + @Provides + static AllocationTokenCustomLogic provideAllocationTokenCustomLogic( + @Config("allocationTokenCustomLogicClass") String customClass) { + return instantiate(getClassFromString(customClass, AllocationTokenCustomLogic.class)); + } +} diff --git a/javatests/google/registry/flows/BUILD b/javatests/google/registry/flows/BUILD index f8585bf64..befe33c03 100644 --- a/javatests/google/registry/flows/BUILD +++ b/javatests/google/registry/flows/BUILD @@ -71,7 +71,7 @@ GenTestRules( shard_count = 4, test_files = glob([ "*Test.java", - "*/*Test.java", + "**/*Test.java", ]), deps = [":flows"], ) diff --git a/javatests/google/registry/flows/domain/DomainCreateFlowTest.java b/javatests/google/registry/flows/domain/DomainCreateFlowTest.java index 00c3f2c5b..7b90451e9 100644 --- a/javatests/google/registry/flows/domain/DomainCreateFlowTest.java +++ b/javatests/google/registry/flows/domain/DomainCreateFlowTest.java @@ -64,8 +64,6 @@ import google.registry.flows.EppException.UnimplementedExtensionException; import google.registry.flows.EppRequestSource; import google.registry.flows.ExtensionManager.UndeclaredServiceExtensionException; import google.registry.flows.ResourceFlowTestCase; -import google.registry.flows.domain.AllocationTokenFlowUtils.AlreadyRedeemedAllocationTokenException; -import google.registry.flows.domain.AllocationTokenFlowUtils.InvalidAllocationTokenException; import google.registry.flows.domain.DomainCreateFlow.DomainHasOpenApplicationsException; import google.registry.flows.domain.DomainCreateFlow.NoGeneralRegistrationsInCurrentPhaseException; import google.registry.flows.domain.DomainFlowUtils.AcceptedTooLongAgoException; @@ -117,6 +115,8 @@ 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.domain.token.AllocationTokenFlowUtils.AlreadyRedeemedAllocationTokenException; +import google.registry.flows.domain.token.AllocationTokenFlowUtils.InvalidAllocationTokenException; import google.registry.flows.exceptions.OnlyToolCanPassMetadataException; import google.registry.flows.exceptions.ResourceAlreadyExistsException; import google.registry.model.billing.BillingEvent; diff --git a/javatests/google/registry/flows/domain/token/AllocationTokenFlowUtilsTest.java b/javatests/google/registry/flows/domain/token/AllocationTokenFlowUtilsTest.java new file mode 100644 index 000000000..e967877fb --- /dev/null +++ b/javatests/google/registry/flows/domain/token/AllocationTokenFlowUtilsTest.java @@ -0,0 +1,157 @@ +// 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.token; + +import static com.google.common.truth.Truth.assertThat; +import static google.registry.testing.DatastoreHelper.createTld; +import static google.registry.testing.DatastoreHelper.persistResource; +import static google.registry.testing.EppExceptionSubject.assertAboutEppExceptions; +import static google.registry.testing.JUnitBackports.expectThrows; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.net.InternetDomainName; +import com.googlecode.objectify.Key; +import google.registry.flows.EppException; +import google.registry.flows.domain.token.AllocationTokenFlowUtils.InvalidAllocationTokenException; +import google.registry.model.domain.AllocationToken; +import google.registry.model.registry.Registry; +import google.registry.model.reporting.HistoryEntry; +import google.registry.testing.AppEngineRule; +import google.registry.testing.ShardableTestCase; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link AllocationTokenFlowUtils}. */ +@RunWith(JUnit4.class) +public class AllocationTokenFlowUtilsTest extends ShardableTestCase { + + @Rule public final AppEngineRule appEngine = AppEngineRule.builder().withDatastore().build(); + + @Before + public void initTest() { + createTld("tld"); + } + + @Test + public void test_verifyToken_successfullyVerifiesValidToken() throws Exception { + AllocationToken token = + persistResource(new AllocationToken.Builder().setToken("tokeN").build()); + AllocationTokenFlowUtils flowUtils = + new AllocationTokenFlowUtils(new AllocationTokenCustomLogic()); + assertThat( + flowUtils.verifyToken( + InternetDomainName.from("blah.tld"), "tokeN", Registry.get("tld"), "TheRegistrar")) + .isEqualTo(token); + } + + @Test + public void test_verifyToken_failsOnNonexistentToken() throws Exception { + AllocationTokenFlowUtils flowUtils = + new AllocationTokenFlowUtils(new AllocationTokenCustomLogic()); + EppException thrown = + expectThrows( + InvalidAllocationTokenException.class, + () -> + flowUtils.verifyToken( + InternetDomainName.from("blah.tld"), + "tokeN", + Registry.get("tld"), + "TheRegistrar")); + assertAboutEppExceptions().that(thrown).marshalsToXml(); + } + + @Test + public void test_verifyToken_callsCustomLogic() throws Exception { + persistResource(new AllocationToken.Builder().setToken("tokeN").build()); + AllocationTokenFlowUtils flowUtils = + new AllocationTokenFlowUtils(new FailingAllocationTokenCustomLogic()); + Exception thrown = + expectThrows( + IllegalStateException.class, + () -> + flowUtils.verifyToken( + InternetDomainName.from("blah.tld"), + "tokeN", + Registry.get("tld"), + "TheRegistrar")); + assertThat(thrown).hasMessageThat().isEqualTo("failed for tests"); + } + + @Test + public void test_checkDomainsWithToken_successfullyVerifiesValidToken() throws Exception { + persistResource(new AllocationToken.Builder().setToken("tokeN").build()); + AllocationTokenFlowUtils flowUtils = + new AllocationTokenFlowUtils(new AllocationTokenCustomLogic()); + assertThat( + flowUtils.checkDomainsWithToken( + ImmutableList.of("blah.tld", "blah2.tld"), "tokeN", "TheRegistrar")) + .containsExactlyEntriesIn(ImmutableMap.of("blah.tld", "", "blah2.tld", "")); + } + + @Test + public void test_checkDomainsWithToken_showsFailureMessageForRedeemedToken() throws Exception { + persistResource( + new AllocationToken.Builder() + .setToken("tokeN") + .setRedemptionHistoryEntry(Key.create(HistoryEntry.class, 101L)) + .build()); + AllocationTokenFlowUtils flowUtils = + new AllocationTokenFlowUtils(new AllocationTokenCustomLogic()); + assertThat( + flowUtils.checkDomainsWithToken( + ImmutableList.of("blah.tld", "blah2.tld"), "tokeN", "TheRegistrar")) + .containsExactlyEntriesIn( + ImmutableMap.of( + "blah.tld", + "Alloc token was already redeemed", + "blah2.tld", + "Alloc token was already redeemed")); + } + + @Test + public void test_checkDomainsWithToken_callsCustomLogic() throws Exception { + persistResource(new AllocationToken.Builder().setToken("tokeN").build()); + AllocationTokenFlowUtils flowUtils = + new AllocationTokenFlowUtils(new FailingAllocationTokenCustomLogic()); + Exception thrown = + expectThrows( + IllegalStateException.class, + () -> + flowUtils.checkDomainsWithToken( + ImmutableList.of("blah.tld", "blah2.tld"), "tokeN", "TheRegistrar")); + assertThat(thrown).hasMessageThat().isEqualTo("failed for tests"); + } + + /** An {@link AllocationTokenCustomLogic} class that throws exceptions on every method. */ + private static class FailingAllocationTokenCustomLogic extends AllocationTokenCustomLogic { + + @Override + public AllocationToken verifyToken( + InternetDomainName domainName, AllocationToken token, Registry registry, String clientId) + throws EppException { + throw new IllegalStateException("failed for tests"); + } + + @Override + public ImmutableMap checkDomainsWithToken( + ImmutableMap checkResults, AllocationToken tokenEntity, String clientId) { + throw new IllegalStateException("failed for tests"); + } + } +}