Add extensibility framework for allocation tokens

This uses an extensibility mechanism similar to that of WhoisCommandFactory
and CustomLogicFactory, namely, that a fully qualified Java class is
specified in the YAML file for each environment with the allocation token
custom logic to be used.  By default, this points to a no-op base class
that does nothing.  Users that wish to add their own allocation token
custom logic can simply create a new class that extends
AllocationTokenCustomLogic and then configure it in their .yaml config
files.

This also renames the existing *FlowCustomLogic *Flow instance variables
from customLogic to flowCustomLogic, to avoid the potential confusion with
the new AllocationTokenCustomLogic class that also now exists.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=183003112
This commit is contained in:
mcilwain 2018-01-23 15:31:15 -08:00 committed by Ben McIlwain
parent e6a097a590
commit 9d532cb507
17 changed files with 320 additions and 61 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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<String> targetIds = ((Check) resourceCommand).getTargetIds();
@ -144,7 +146,7 @@ public final class DomainCheckFlow implements Flow {
}
}
ImmutableMap<String, InternetDomainName> 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<String, String> tokenCheckResults =
allocationTokenExtension.isPresent()
? checkDomainsWithToken(
? allocationTokenFlowUtils.checkDomainsWithToken(
targetIds, allocationTokenExtension.get().getAllocationToken(), clientId)
: ImmutableMap.of();
ImmutableList.Builder<DomainCheck> 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. */

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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.
*
* <p>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<String, String> checkDomainsWithToken(
ImmutableMap<String, String> checkResults, AllocationToken tokenEntity, String clientId) {
// Do nothing.
return checkResults;
}
}

View file

@ -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<String, String> checkDomainsWithToken(
public ImmutableMap<String, String> checkDomainsWithToken(
List<String> 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
ImmutableMap<String, String> 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<HistoryEntry> 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");
}

View file

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

View file

@ -71,7 +71,7 @@ GenTestRules(
shard_count = 4,
test_files = glob([
"*Test.java",
"*/*Test.java",
"**/*Test.java",
]),
deps = [":flows"],
)

View file

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

View file

@ -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<String, String> checkDomainsWithToken(
ImmutableMap<String, String> checkResults, AllocationToken tokenEntity, String clientId) {
throw new IllegalStateException("failed for tests");
}
}
}