Implement EPP Allocation Token Extension for domain:renew (#1693)

* Implement EPP Allocation Token Extension for domain:renew

* Add more tests

* Fix AllocationTokenFlowUtilsTest

* Combine loadTokenAndValidateDomain with verifyAllocationTokenIfPresent

* Change to Optional.empty

* Remove unused variable
This commit is contained in:
sarahcaseybot
2022-07-26 10:28:32 -04:00
committed by GitHub
parent 1ca80010c0
commit 40c58bb6f3
8 changed files with 481 additions and 83 deletions
@@ -262,7 +262,12 @@ public final class DomainCreateFlow implements TransactionalFlow {
}
boolean isSunriseCreate = hasSignedMarks && (tldState == START_DATE_SUNRISE);
Optional<AllocationToken> allocationToken =
verifyAllocationTokenIfPresent(command, registry, registrarId, now);
allocationTokenFlowUtils.verifyAllocationTokenCreateIfPresent(
command,
registry,
registrarId,
now,
eppInput.getSingleExtension(AllocationTokenExtension.class));
boolean isAnchorTenant =
isAnchorTenant(
domainName, allocationToken, eppInput.getSingleExtension(MetadataExtension.class));
@@ -485,19 +490,6 @@ public final class DomainCreateFlow implements TransactionalFlow {
throw new NoGeneralRegistrationsInCurrentPhaseException();
}
/** Verifies and returns the allocation token if one is specified, otherwise does nothing. */
private Optional<AllocationToken> verifyAllocationTokenIfPresent(
DomainCommand.Create command, Registry registry, String registrarId, DateTime now)
throws EppException {
Optional<AllocationTokenExtension> extension =
eppInput.getSingleExtension(AllocationTokenExtension.class);
return Optional.ofNullable(
extension.isPresent()
? allocationTokenFlowUtils.loadTokenAndValidateDomainCreate(
command, extension.get().getAllocationToken(), registry, registrarId, now)
: null);
}
private DomainHistory buildDomainHistory(
DomainBase domain, Registry registry, DateTime now, Period period, Duration addGracePeriod) {
// We ignore prober transactions
@@ -51,6 +51,8 @@ import google.registry.flows.custom.DomainRenewFlowCustomLogic.BeforeResponsePar
import google.registry.flows.custom.DomainRenewFlowCustomLogic.BeforeResponseReturnData;
import google.registry.flows.custom.DomainRenewFlowCustomLogic.BeforeSaveParameters;
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.OneTime;
import google.registry.model.billing.BillingEvent.Reason;
@@ -68,6 +70,9 @@ import google.registry.model.domain.fee.FeeRenewCommandExtension;
import google.registry.model.domain.fee.FeeTransformResponseExtension;
import google.registry.model.domain.metadata.MetadataExtension;
import google.registry.model.domain.rgp.GracePeriodStatus;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.domain.token.AllocationToken.TokenType;
import google.registry.model.domain.token.AllocationTokenExtension;
import google.registry.model.eppcommon.AuthInfo;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.eppinput.EppInput;
@@ -113,6 +118,18 @@ import org.joda.time.Duration;
* @error {@link DomainFlowUtils.RegistrarMustBeActiveForThisOperationException}
* @error {@link DomainFlowUtils.UnsupportedFeeAttributeException}
* @error {@link DomainRenewFlow.IncorrectCurrentExpirationDateException}
* @error {@link
* google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotValidForDomainException}
* @error {@link
* google.registry.flows.domain.token.AllocationTokenFlowUtils.InvalidAllocationTokenException}
* @error {@link
* google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotInPromotionException}
* @error {@link
* google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotValidForRegistrarException}
* @error {@link
* google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotValidForTldException}
* @error {@link
* google.registry.flows.domain.token.AllocationTokenFlowUtils.AlreadyRedeemedAllocationTokenException}
*/
@ReportingSpec(ActivityReportField.DOMAIN_RENEW)
public final class DomainRenewFlow implements TransactionalFlow {
@@ -133,13 +150,15 @@ public final class DomainRenewFlow implements TransactionalFlow {
@Inject @Superuser boolean isSuperuser;
@Inject DomainHistory.Builder historyBuilder;
@Inject EppResponse.Builder responseBuilder;
@Inject AllocationTokenFlowUtils allocationTokenFlowUtils;
@Inject DomainRenewFlowCustomLogic flowCustomLogic;
@Inject DomainPricingLogic pricingLogic;
@Inject DomainRenewFlow() {}
@Override
public EppResponse run() throws EppException {
extensionManager.register(FeeRenewCommandExtension.class, MetadataExtension.class);
extensionManager.register(
FeeRenewCommandExtension.class, MetadataExtension.class, AllocationTokenExtension.class);
flowCustomLogic.beforeValidation();
validateRegistrarIsLoggedIn(registrarId);
verifyRegistrarIsActive(registrarId);
@@ -148,6 +167,13 @@ public final class DomainRenewFlow implements TransactionalFlow {
Renew command = (Renew) resourceCommand;
// Loads the target resource if it exists
DomainBase existingDomain = loadAndVerifyExistence(DomainBase.class, targetId, now);
Optional<AllocationToken> allocationToken =
allocationTokenFlowUtils.verifyAllocationTokenIfPresent(
existingDomain,
Registry.get(existingDomain.getTld()),
registrarId,
now,
eppInput.getSingleExtension(AllocationTokenExtension.class));
verifyRenewAllowed(authInfo, existingDomain, command);
int years = command.getPeriod().getValue();
DateTime newExpirationTime =
@@ -176,7 +202,8 @@ public final class DomainRenewFlow implements TransactionalFlow {
String tld = existingDomain.getTld();
// Bill for this explicit renew itself.
BillingEvent.OneTime explicitRenewEvent =
createRenewBillingEvent(tld, feesAndCredits.getTotalCost(), years, domainHistoryKey, now);
createRenewBillingEvent(
tld, feesAndCredits.getTotalCost(), years, domainHistoryKey, allocationToken, now);
// Create a new autorenew billing event and poll message starting at the new expiration time.
BillingEvent.Recurring newAutorenewEvent =
newAutorenewBillingEvent(existingDomain)
@@ -212,6 +239,14 @@ public final class DomainRenewFlow implements TransactionalFlow {
DomainHistory domainHistory =
buildDomainHistory(
newDomain, now, command.getPeriod(), registry.getRenewGracePeriodLength());
ImmutableSet.Builder<ImmutableObject> entitiesToSave = new ImmutableSet.Builder<>();
entitiesToSave.add(
newDomain, domainHistory, explicitRenewEvent, newAutorenewEvent, newAutorenewPollMessage);
if (allocationToken.isPresent()
&& TokenType.SINGLE_USE.equals(allocationToken.get().getTokenType())) {
entitiesToSave.add(
allocationTokenFlowUtils.redeemToken(allocationToken.get(), domainHistory.createVKey()));
}
EntityChanges entityChanges =
flowCustomLogic.beforeSave(
BeforeSaveParameters.newBuilder()
@@ -221,15 +256,7 @@ public final class DomainRenewFlow implements TransactionalFlow {
.setYears(years)
.setHistoryEntry(domainHistory)
.setEntityChanges(
EntityChanges.newBuilder()
.setSaves(
ImmutableSet.of(
newDomain,
domainHistory,
explicitRenewEvent,
newAutorenewEvent,
newAutorenewPollMessage))
.build())
EntityChanges.newBuilder().setSaves(entitiesToSave.build()).build())
.build());
BeforeResponseReturnData responseData =
flowCustomLogic.beforeResponse(
@@ -290,7 +317,12 @@ public final class DomainRenewFlow implements TransactionalFlow {
}
private OneTime createRenewBillingEvent(
String tld, Money renewCost, int years, Key<DomainHistory> domainHistoryKey, DateTime now) {
String tld,
Money renewCost,
int years,
Key<DomainHistory> domainHistoryKey,
Optional<AllocationToken> allocationToken,
DateTime now) {
return new BillingEvent.OneTime.Builder()
.setReason(Reason.RENEW)
.setTargetId(targetId)
@@ -298,6 +330,7 @@ public final class DomainRenewFlow implements TransactionalFlow {
.setPeriodYears(years)
.setCost(renewCost)
.setEventTime(now)
.setAllocationToken(allocationToken.map(AllocationToken::createVKey).orElse(null))
.setBillingTime(now.plus(Registry.get(tld).getRenewGracePeriodLength()))
.setParent(domainHistoryKey)
.build();
@@ -19,6 +19,7 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.net.InternetDomainName;
import google.registry.flows.EppException;
import google.registry.model.domain.DomainBase;
import google.registry.model.domain.DomainCommand;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.tld.Registry;
@@ -31,7 +32,7 @@ import org.joda.time.DateTime;
*/
public class AllocationTokenCustomLogic {
/** Performs additional custom logic for validating a token. */
/** Performs additional custom logic for validating a token on a domain create. */
public AllocationToken validateToken(
DomainCommand.Create command,
AllocationToken token,
@@ -43,6 +44,14 @@ public class AllocationTokenCustomLogic {
return token;
}
/** Performs additional custom logic for validating a token on an existing domain. */
public AllocationToken validateToken(
DomainBase domain, AllocationToken token, Registry registry, String registrarId, DateTime now)
throws EppException {
// Do nothing.
return token;
}
/** Performs additional custom logic for performing domain checks using a token. */
public ImmutableMap<InternetDomainName, String> checkDomainsWithToken(
ImmutableList<InternetDomainName> domainNames,
@@ -26,10 +26,12 @@ import google.registry.flows.EppException;
import google.registry.flows.EppException.AssociationProhibitsOperationException;
import google.registry.flows.EppException.AuthorizationErrorException;
import google.registry.flows.EppException.StatusProhibitsOperationException;
import google.registry.model.domain.DomainBase;
import google.registry.model.domain.DomainCommand;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.domain.token.AllocationToken.TokenStatus;
import google.registry.model.domain.token.AllocationToken.TokenType;
import google.registry.model.domain.token.AllocationTokenExtension;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.tld.Registry;
import google.registry.persistence.VKey;
@@ -48,30 +50,6 @@ public class AllocationTokenFlowUtils {
this.tokenCustomLogic = tokenCustomLogic;
}
/**
* Loads an allocation token given a string and verifies that the token is valid for the domain
* create request.
*
* @return the loaded {@link AllocationToken} for that string.
* @throws EppException if the token doesn't exist, is already redeemed, or is otherwise invalid
* for this request.
*/
public AllocationToken loadTokenAndValidateDomainCreate(
DomainCommand.Create command,
String token,
Registry registry,
String registrarId,
DateTime now)
throws EppException {
AllocationToken tokenEntity = loadToken(token);
validateToken(
InternetDomainName.from(command.getFullyQualifiedDomainName()),
tokenEntity,
registrarId,
now);
return tokenCustomLogic.validateToken(command, tokenEntity, registry, registrarId, now);
}
/**
* Checks if the allocation token applies to the given domain names, used for domain checks.
*
@@ -170,6 +148,45 @@ public class AllocationTokenFlowUtils {
return maybeTokenEntity.get();
}
/** Verifies and returns the allocation token if one is specified, otherwise does nothing. */
public Optional<AllocationToken> verifyAllocationTokenCreateIfPresent(
DomainCommand.Create command,
Registry registry,
String registrarId,
DateTime now,
Optional<AllocationTokenExtension> extension)
throws EppException {
if (!extension.isPresent()) {
return Optional.empty();
}
AllocationToken tokenEntity = loadToken(extension.get().getAllocationToken());
validateToken(
InternetDomainName.from(command.getFullyQualifiedDomainName()),
tokenEntity,
registrarId,
now);
return Optional.of(
tokenCustomLogic.validateToken(command, tokenEntity, registry, registrarId, now));
}
/** Verifies and returns the allocation token if one is specified, otherwise does nothing. */
public Optional<AllocationToken> verifyAllocationTokenIfPresent(
DomainBase existingDomain,
Registry registry,
String registrarId,
DateTime now,
Optional<AllocationTokenExtension> extension)
throws EppException {
if (!extension.isPresent()) {
return Optional.empty();
}
AllocationToken tokenEntity = loadToken(extension.get().getAllocationToken());
validateToken(
InternetDomainName.from(existingDomain.getDomainName()), tokenEntity, registrarId, now);
return Optional.of(
tokenCustomLogic.validateToken(existingDomain, tokenEntity, registry, registrarId, now));
}
// Note: exception messages should be <= 32 characters long for domain check results
/** The allocation token is not currently valid. */