mirror of
https://github.com/google/nomulus.git
synced 2025-04-29 19:47:51 +02:00
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:
parent
badfb29ce1
commit
68f975451e
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. */
|
||||
|
|
|
@ -15,10 +15,13 @@
|
|||
package google.registry.flows.domain;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static com.google.common.truth.Truth8.assertThat;
|
||||
import static google.registry.flows.domain.DomainTransferFlowTestCase.persistWithPendingTransfer;
|
||||
import static google.registry.model.billing.BillingEvent.RenewalPriceBehavior.DEFAULT;
|
||||
import static google.registry.model.billing.BillingEvent.RenewalPriceBehavior.NONPREMIUM;
|
||||
import static google.registry.model.billing.BillingEvent.RenewalPriceBehavior.SPECIFIED;
|
||||
import static google.registry.model.domain.token.AllocationToken.TokenType.SINGLE_USE;
|
||||
import static google.registry.model.domain.token.AllocationToken.TokenType.UNLIMITED_USE;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.testing.DatabaseHelper.assertBillingEvents;
|
||||
import static google.registry.testing.DatabaseHelper.assertPollMessages;
|
||||
|
@ -46,6 +49,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
|
|||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSortedMap;
|
||||
import com.googlecode.objectify.Key;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.EppRequestSource;
|
||||
import google.registry.flows.FlowUtils.NotLoggedInException;
|
||||
|
@ -64,6 +68,12 @@ import google.registry.flows.domain.DomainFlowUtils.NotAuthorizedForTldException
|
|||
import google.registry.flows.domain.DomainFlowUtils.RegistrarMustBeActiveForThisOperationException;
|
||||
import google.registry.flows.domain.DomainFlowUtils.UnsupportedFeeAttributeException;
|
||||
import google.registry.flows.domain.DomainRenewFlow.IncorrectCurrentExpirationDateException;
|
||||
import google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotInPromotionException;
|
||||
import google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotValidForDomainException;
|
||||
import google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotValidForRegistrarException;
|
||||
import google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotValidForTldException;
|
||||
import google.registry.flows.domain.token.AllocationTokenFlowUtils.AlreadyRedeemedAllocationTokenException;
|
||||
import google.registry.flows.domain.token.AllocationTokenFlowUtils.InvalidAllocationTokenException;
|
||||
import google.registry.flows.exceptions.ResourceStatusProhibitsOperationException;
|
||||
import google.registry.model.billing.BillingEvent;
|
||||
import google.registry.model.billing.BillingEvent.Flag;
|
||||
|
@ -73,6 +83,8 @@ import google.registry.model.domain.DomainBase;
|
|||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.domain.GracePeriod;
|
||||
import google.registry.model.domain.rgp.GracePeriodStatus;
|
||||
import google.registry.model.domain.token.AllocationToken;
|
||||
import google.registry.model.domain.token.AllocationToken.TokenStatus;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.poll.PollMessage;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
|
@ -81,6 +93,8 @@ import google.registry.model.reporting.DomainTransactionRecord;
|
|||
import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.tld.Registry;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.testing.DatabaseHelper;
|
||||
import google.registry.testing.SetClockExtension;
|
||||
import java.util.Map;
|
||||
import javax.annotation.Nullable;
|
||||
|
@ -321,6 +335,13 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, DomainBa
|
|||
renewBillingEvent));
|
||||
}
|
||||
|
||||
@Test
|
||||
private void assertAllocationTokenWasNotRedeemed(String token) {
|
||||
AllocationToken reloadedToken =
|
||||
tm().transact(() -> tm().loadByKey(VKey.create(AllocationToken.class, token)));
|
||||
assertThat(reloadedToken.isRedeemed()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNotLoggedIn() {
|
||||
sessionMetadata.setRegistrarId(null);
|
||||
|
@ -577,6 +598,164 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, DomainBa
|
|||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_allocationToken() throws Exception {
|
||||
setEppInput(
|
||||
"domain_renew_allocationtoken.xml", ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2"));
|
||||
persistDomain();
|
||||
AllocationToken allocationToken =
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
.setToken("abc123")
|
||||
.setTokenType(SINGLE_USE)
|
||||
.setDomainName("example.tld")
|
||||
.build());
|
||||
runFlowAssertResponse(
|
||||
loadFile(
|
||||
"domain_renew_response.xml",
|
||||
ImmutableMap.of("DOMAIN", "example.tld", "EXDATE", "2002-04-03T22:00:00.0Z")));
|
||||
assertThat(DatabaseHelper.loadByEntity(allocationToken).getRedemptionHistoryEntry())
|
||||
.isPresent();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_allocationTokenMultiUse() throws Exception {
|
||||
setEppInput(
|
||||
"domain_renew_allocationtoken.xml", ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2"));
|
||||
persistDomain();
|
||||
persistResource(
|
||||
new AllocationToken.Builder().setToken("abc123").setTokenType(UNLIMITED_USE).build());
|
||||
runFlowAssertResponse(
|
||||
loadFile(
|
||||
"domain_renew_response.xml",
|
||||
ImmutableMap.of("DOMAIN", "example.tld", "EXDATE", "2002-04-03T22:00:00.0Z")));
|
||||
clock.advanceOneMilli();
|
||||
setEppInput(
|
||||
"domain_renew_allocationtoken.xml",
|
||||
ImmutableMap.of("DOMAIN", "other-example.tld", "YEARS", "2"));
|
||||
persistDomain();
|
||||
runFlowAssertResponse(
|
||||
loadFile(
|
||||
"domain_renew_response.xml",
|
||||
ImmutableMap.of("DOMAIN", "other-example.tld", "EXDATE", "2002-04-03T22:00:00.0Z")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_invalidAllocationToken() throws Exception {
|
||||
setEppInput(
|
||||
"domain_renew_allocationtoken.xml", ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2"));
|
||||
persistDomain();
|
||||
EppException thrown = assertThrows(InvalidAllocationTokenException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_allocationTokenIsForADifferentDomain() throws Exception {
|
||||
setEppInput(
|
||||
"domain_renew_allocationtoken.xml", ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2"));
|
||||
persistDomain();
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
.setToken("abc123")
|
||||
.setTokenType(SINGLE_USE)
|
||||
.setDomainName("otherdomain.tld")
|
||||
.build());
|
||||
clock.advanceOneMilli();
|
||||
EppException thrown =
|
||||
assertThrows(AllocationTokenNotValidForDomainException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
assertAllocationTokenWasNotRedeemed("abc123");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_promotionNotActive() throws Exception {
|
||||
setEppInput(
|
||||
"domain_renew_allocationtoken.xml", ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2"));
|
||||
persistDomain();
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
.setToken("abc123")
|
||||
.setTokenType(UNLIMITED_USE)
|
||||
.setDiscountFraction(0.5)
|
||||
.setTokenStatusTransitions(
|
||||
ImmutableSortedMap.<DateTime, TokenStatus>naturalOrder()
|
||||
.put(START_OF_TIME, TokenStatus.NOT_STARTED)
|
||||
.put(clock.nowUtc().plusDays(1), TokenStatus.VALID)
|
||||
.put(clock.nowUtc().plusDays(60), TokenStatus.ENDED)
|
||||
.build())
|
||||
.build());
|
||||
assertAboutEppExceptions()
|
||||
.that(assertThrows(AllocationTokenNotInPromotionException.class, this::runFlow))
|
||||
.marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_promoTokenNotValidForRegistrar() throws Exception {
|
||||
setEppInput(
|
||||
"domain_renew_allocationtoken.xml", ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2"));
|
||||
persistDomain();
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
.setToken("abc123")
|
||||
.setTokenType(UNLIMITED_USE)
|
||||
.setAllowedRegistrarIds(ImmutableSet.of("someClientId"))
|
||||
.setDiscountFraction(0.5)
|
||||
.setTokenStatusTransitions(
|
||||
ImmutableSortedMap.<DateTime, TokenStatus>naturalOrder()
|
||||
.put(START_OF_TIME, TokenStatus.NOT_STARTED)
|
||||
.put(clock.nowUtc().minusDays(1), TokenStatus.VALID)
|
||||
.put(clock.nowUtc().plusDays(1), TokenStatus.ENDED)
|
||||
.build())
|
||||
.build());
|
||||
assertAboutEppExceptions()
|
||||
.that(assertThrows(AllocationTokenNotValidForRegistrarException.class, this::runFlow))
|
||||
.marshalsToXml();
|
||||
assertAllocationTokenWasNotRedeemed("abc123");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_promoTokenNotValidForTld() throws Exception {
|
||||
setEppInput(
|
||||
"domain_renew_allocationtoken.xml", ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2"));
|
||||
persistDomain();
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
.setToken("abc123")
|
||||
.setTokenType(UNLIMITED_USE)
|
||||
.setAllowedTlds(ImmutableSet.of("example"))
|
||||
.setDiscountFraction(0.5)
|
||||
.setTokenStatusTransitions(
|
||||
ImmutableSortedMap.<DateTime, TokenStatus>naturalOrder()
|
||||
.put(START_OF_TIME, TokenStatus.NOT_STARTED)
|
||||
.put(clock.nowUtc().minusDays(1), TokenStatus.VALID)
|
||||
.put(clock.nowUtc().plusDays(1), TokenStatus.ENDED)
|
||||
.build())
|
||||
.build());
|
||||
assertAboutEppExceptions()
|
||||
.that(assertThrows(AllocationTokenNotValidForTldException.class, this::runFlow))
|
||||
.marshalsToXml();
|
||||
assertAllocationTokenWasNotRedeemed("abc123");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_alreadyRedemeedAllocationToken() throws Exception {
|
||||
setEppInput(
|
||||
"domain_renew_allocationtoken.xml", ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2"));
|
||||
persistDomain();
|
||||
DomainBase domain = persistActiveDomain("foo.tld");
|
||||
Key<HistoryEntry> historyEntryKey = Key.create(Key.create(domain), HistoryEntry.class, 505L);
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
.setToken("abc123")
|
||||
.setTokenType(SINGLE_USE)
|
||||
.setRedemptionHistoryEntry(HistoryEntry.createVKey(historyEntryKey))
|
||||
.build());
|
||||
clock.advanceOneMilli();
|
||||
EppException thrown =
|
||||
assertThrows(AlreadyRedeemedAllocationTokenException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_suspendedRegistrarCantRenewDomain() {
|
||||
doFailingTest_invalidRegistrarState(State.SUSPENDED);
|
||||
|
|
|
@ -22,6 +22,7 @@ import static google.registry.model.domain.token.AllocationToken.TokenStatus.VAL
|
|||
import static google.registry.model.domain.token.AllocationToken.TokenType.SINGLE_USE;
|
||||
import static google.registry.model.domain.token.AllocationToken.TokenType.UNLIMITED_USE;
|
||||
import static google.registry.testing.DatabaseHelper.createTld;
|
||||
import static google.registry.testing.DatabaseHelper.newDomainBase;
|
||||
import static google.registry.testing.DatabaseHelper.persistActiveDomain;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.testing.EppExceptionSubject.assertAboutEppExceptions;
|
||||
|
@ -47,9 +48,11 @@ 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.AllocationTokenExtension;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.tld.Registry;
|
||||
import google.registry.testing.AppEngineExtension;
|
||||
import java.util.Optional;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -64,98 +67,195 @@ class AllocationTokenFlowUtilsTest {
|
|||
@RegisterExtension
|
||||
final AppEngineExtension appEngine = AppEngineExtension.builder().withCloudSql().build();
|
||||
|
||||
private final AllocationTokenExtension allocationTokenExtension =
|
||||
mock(AllocationTokenExtension.class);
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
createTld("tld");
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_validateToken_successfullyVerifiesValidToken() throws Exception {
|
||||
void test_validateToken_successfullyVerifiesValidTokenOnCreate() throws Exception {
|
||||
AllocationToken token =
|
||||
persistResource(
|
||||
new AllocationToken.Builder().setToken("tokeN").setTokenType(SINGLE_USE).build());
|
||||
when(allocationTokenExtension.getAllocationToken()).thenReturn("tokeN");
|
||||
assertThat(
|
||||
flowUtils.loadTokenAndValidateDomainCreate(
|
||||
createCommand("blah.tld"),
|
||||
"tokeN",
|
||||
Registry.get("tld"),
|
||||
"TheRegistrar",
|
||||
DateTime.now(UTC)))
|
||||
flowUtils
|
||||
.verifyAllocationTokenCreateIfPresent(
|
||||
createCommand("blah.tld"),
|
||||
Registry.get("tld"),
|
||||
"TheRegistrar",
|
||||
DateTime.now(UTC),
|
||||
Optional.of(allocationTokenExtension))
|
||||
.get())
|
||||
.isEqualTo(token);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_validateToken_failsOnNonexistentToken() {
|
||||
assertValidateThrowsEppException(InvalidAllocationTokenException.class);
|
||||
void test_validateToken_successfullyVerifiesValidTokenExistingDomain() throws Exception {
|
||||
AllocationToken token =
|
||||
persistResource(
|
||||
new AllocationToken.Builder().setToken("tokeN").setTokenType(SINGLE_USE).build());
|
||||
when(allocationTokenExtension.getAllocationToken()).thenReturn("tokeN");
|
||||
assertThat(
|
||||
flowUtils
|
||||
.verifyAllocationTokenIfPresent(
|
||||
newDomainBase("blah.tld"),
|
||||
Registry.get("tld"),
|
||||
"TheRegistrar",
|
||||
DateTime.now(UTC),
|
||||
Optional.of(allocationTokenExtension))
|
||||
.get())
|
||||
.isEqualTo(token);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_validateToken_failsOnNullToken() {
|
||||
void test_validateTokenCreate_failsOnNonexistentToken() {
|
||||
assertValidateCreateThrowsEppException(InvalidAllocationTokenException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_validateTokenExistingDomain_failsOnNonexistentToken() {
|
||||
assertValidateExistingDomainThrowsEppException(InvalidAllocationTokenException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_validateTokenCreate_failsOnNullToken() {
|
||||
assertAboutEppExceptions()
|
||||
.that(
|
||||
assertThrows(
|
||||
InvalidAllocationTokenException.class,
|
||||
() ->
|
||||
flowUtils.loadTokenAndValidateDomainCreate(
|
||||
flowUtils.verifyAllocationTokenCreateIfPresent(
|
||||
createCommand("blah.tld"),
|
||||
null,
|
||||
Registry.get("tld"),
|
||||
"TheRegistrar",
|
||||
DateTime.now(UTC))))
|
||||
DateTime.now(UTC),
|
||||
Optional.of(allocationTokenExtension))))
|
||||
.marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_validateToken_callsCustomLogic() {
|
||||
void test_validateTokenExistingDomain_failsOnNullToken() {
|
||||
assertAboutEppExceptions()
|
||||
.that(
|
||||
assertThrows(
|
||||
InvalidAllocationTokenException.class,
|
||||
() ->
|
||||
flowUtils.verifyAllocationTokenIfPresent(
|
||||
newDomainBase("blah.tld"),
|
||||
Registry.get("tld"),
|
||||
"TheRegistrar",
|
||||
DateTime.now(UTC),
|
||||
Optional.of(allocationTokenExtension))))
|
||||
.marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_validateTokenCreate_callsCustomLogic() {
|
||||
AllocationTokenFlowUtils failingFlowUtils =
|
||||
new AllocationTokenFlowUtils(new FailingAllocationTokenCustomLogic());
|
||||
persistResource(
|
||||
new AllocationToken.Builder().setToken("tokeN").setTokenType(SINGLE_USE).build());
|
||||
when(allocationTokenExtension.getAllocationToken()).thenReturn("tokeN");
|
||||
Exception thrown =
|
||||
assertThrows(
|
||||
IllegalStateException.class,
|
||||
() ->
|
||||
failingFlowUtils.loadTokenAndValidateDomainCreate(
|
||||
failingFlowUtils.verifyAllocationTokenCreateIfPresent(
|
||||
createCommand("blah.tld"),
|
||||
"tokeN",
|
||||
Registry.get("tld"),
|
||||
"TheRegistrar",
|
||||
DateTime.now(UTC)));
|
||||
DateTime.now(UTC),
|
||||
Optional.of(allocationTokenExtension)));
|
||||
assertThat(thrown).hasMessageThat().isEqualTo("failed for tests");
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_validateToken_invalidForClientId() {
|
||||
void test_validateTokenExistingDomain_callsCustomLogic() {
|
||||
AllocationTokenFlowUtils failingFlowUtils =
|
||||
new AllocationTokenFlowUtils(new FailingAllocationTokenCustomLogic());
|
||||
persistResource(
|
||||
new AllocationToken.Builder().setToken("tokeN").setTokenType(SINGLE_USE).build());
|
||||
when(allocationTokenExtension.getAllocationToken()).thenReturn("tokeN");
|
||||
Exception thrown =
|
||||
assertThrows(
|
||||
IllegalStateException.class,
|
||||
() ->
|
||||
failingFlowUtils.verifyAllocationTokenIfPresent(
|
||||
newDomainBase("blah.tld"),
|
||||
Registry.get("tld"),
|
||||
"TheRegistrar",
|
||||
DateTime.now(UTC),
|
||||
Optional.of(allocationTokenExtension)));
|
||||
assertThat(thrown).hasMessageThat().isEqualTo("failed for tests");
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_validateTokenCreate_invalidForClientId() {
|
||||
persistResource(
|
||||
createOneMonthPromoTokenBuilder(DateTime.now(UTC).minusDays(1))
|
||||
.setAllowedRegistrarIds(ImmutableSet.of("NewRegistrar"))
|
||||
.build());
|
||||
assertValidateThrowsEppException(AllocationTokenNotValidForRegistrarException.class);
|
||||
assertValidateCreateThrowsEppException(AllocationTokenNotValidForRegistrarException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_validateToken_invalidForTld() {
|
||||
void test_validateTokenExistingDomain_invalidForClientId() {
|
||||
persistResource(
|
||||
createOneMonthPromoTokenBuilder(DateTime.now(UTC).minusDays(1))
|
||||
.setAllowedRegistrarIds(ImmutableSet.of("NewRegistrar"))
|
||||
.build());
|
||||
assertValidateExistingDomainThrowsEppException(
|
||||
AllocationTokenNotValidForRegistrarException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_validateTokenCreate_invalidForTld() {
|
||||
persistResource(
|
||||
createOneMonthPromoTokenBuilder(DateTime.now(UTC).minusDays(1))
|
||||
.setAllowedTlds(ImmutableSet.of("nottld"))
|
||||
.build());
|
||||
assertValidateThrowsEppException(AllocationTokenNotValidForTldException.class);
|
||||
assertValidateCreateThrowsEppException(AllocationTokenNotValidForTldException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_validateToken_beforePromoStart() {
|
||||
void test_validateTokenExistingDomain_invalidForTld() {
|
||||
persistResource(
|
||||
createOneMonthPromoTokenBuilder(DateTime.now(UTC).minusDays(1))
|
||||
.setAllowedTlds(ImmutableSet.of("nottld"))
|
||||
.build());
|
||||
assertValidateExistingDomainThrowsEppException(AllocationTokenNotValidForTldException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_validateTokenCreate_beforePromoStart() {
|
||||
persistResource(createOneMonthPromoTokenBuilder(DateTime.now(UTC).plusDays(1)).build());
|
||||
assertValidateThrowsEppException(AllocationTokenNotInPromotionException.class);
|
||||
assertValidateCreateThrowsEppException(AllocationTokenNotInPromotionException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_validateToken_afterPromoEnd() {
|
||||
void test_validateTokenExistingDomain_beforePromoStart() {
|
||||
persistResource(createOneMonthPromoTokenBuilder(DateTime.now(UTC).plusDays(1)).build());
|
||||
assertValidateExistingDomainThrowsEppException(AllocationTokenNotInPromotionException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_validateTokenCreate_afterPromoEnd() {
|
||||
persistResource(createOneMonthPromoTokenBuilder(DateTime.now(UTC).minusMonths(2)).build());
|
||||
assertValidateThrowsEppException(AllocationTokenNotInPromotionException.class);
|
||||
assertValidateCreateThrowsEppException(AllocationTokenNotInPromotionException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_validateToken_promoCancelled() {
|
||||
void test_validateTokenExistingDomain_afterPromoEnd() {
|
||||
persistResource(createOneMonthPromoTokenBuilder(DateTime.now(UTC).minusMonths(2)).build());
|
||||
assertValidateExistingDomainThrowsEppException(AllocationTokenNotInPromotionException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_validateTokenCreate_promoCancelled() {
|
||||
// the promo would be valid but it was cancelled 12 hours ago
|
||||
persistResource(
|
||||
createOneMonthPromoTokenBuilder(DateTime.now(UTC).minusDays(1))
|
||||
|
@ -166,7 +266,22 @@ class AllocationTokenFlowUtilsTest {
|
|||
.put(DateTime.now(UTC).minusHours(12), CANCELLED)
|
||||
.build())
|
||||
.build());
|
||||
assertValidateThrowsEppException(AllocationTokenNotInPromotionException.class);
|
||||
assertValidateCreateThrowsEppException(AllocationTokenNotInPromotionException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_validateTokenExistingDomain_promoCancelled() {
|
||||
// the promo would be valid but it was cancelled 12 hours ago
|
||||
persistResource(
|
||||
createOneMonthPromoTokenBuilder(DateTime.now(UTC).minusDays(1))
|
||||
.setTokenStatusTransitions(
|
||||
ImmutableSortedMap.<DateTime, TokenStatus>naturalOrder()
|
||||
.put(START_OF_TIME, NOT_STARTED)
|
||||
.put(DateTime.now(UTC).minusMonths(1), VALID)
|
||||
.put(DateTime.now(UTC).minusHours(12), CANCELLED)
|
||||
.build())
|
||||
.build());
|
||||
assertValidateExistingDomainThrowsEppException(AllocationTokenNotInPromotionException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -259,18 +374,33 @@ class AllocationTokenFlowUtilsTest {
|
|||
.inOrder();
|
||||
}
|
||||
|
||||
private void assertValidateThrowsEppException(Class<? extends EppException> clazz) {
|
||||
private void assertValidateCreateThrowsEppException(Class<? extends EppException> clazz) {
|
||||
assertAboutEppExceptions()
|
||||
.that(
|
||||
assertThrows(
|
||||
clazz,
|
||||
() ->
|
||||
flowUtils.loadTokenAndValidateDomainCreate(
|
||||
flowUtils.verifyAllocationTokenCreateIfPresent(
|
||||
createCommand("blah.tld"),
|
||||
"tokeN",
|
||||
Registry.get("tld"),
|
||||
"TheRegistrar",
|
||||
DateTime.now(UTC))))
|
||||
DateTime.now(UTC),
|
||||
Optional.of(allocationTokenExtension))))
|
||||
.marshalsToXml();
|
||||
}
|
||||
|
||||
private void assertValidateExistingDomainThrowsEppException(Class<? extends EppException> clazz) {
|
||||
assertAboutEppExceptions()
|
||||
.that(
|
||||
assertThrows(
|
||||
clazz,
|
||||
() ->
|
||||
flowUtils.verifyAllocationTokenIfPresent(
|
||||
newDomainBase("blah.tld"),
|
||||
Registry.get("tld"),
|
||||
"TheRegistrar",
|
||||
DateTime.now(UTC),
|
||||
Optional.of(allocationTokenExtension))))
|
||||
.marshalsToXml();
|
||||
}
|
||||
|
||||
|
@ -280,7 +410,8 @@ class AllocationTokenFlowUtilsTest {
|
|||
return command;
|
||||
}
|
||||
|
||||
private static AllocationToken.Builder createOneMonthPromoTokenBuilder(DateTime promoStart) {
|
||||
private AllocationToken.Builder createOneMonthPromoTokenBuilder(DateTime promoStart) {
|
||||
when(allocationTokenExtension.getAllocationToken()).thenReturn("tokeN");
|
||||
return new AllocationToken.Builder()
|
||||
.setToken("tokeN")
|
||||
.setTokenType(UNLIMITED_USE)
|
||||
|
@ -305,6 +436,16 @@ class AllocationTokenFlowUtilsTest {
|
|||
throw new IllegalStateException("failed for tests");
|
||||
}
|
||||
|
||||
@Override
|
||||
public AllocationToken validateToken(
|
||||
DomainBase domain,
|
||||
AllocationToken token,
|
||||
Registry registry,
|
||||
String registrarId,
|
||||
DateTime now) {
|
||||
throw new IllegalStateException("failed for tests");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableMap<InternetDomainName, String> checkDomainsWithToken(
|
||||
ImmutableList<InternetDomainName> domainNames,
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
|
||||
<command>
|
||||
<renew>
|
||||
<domain:renew
|
||||
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
|
||||
<domain:name>%DOMAIN%</domain:name>
|
||||
<domain:curExpDate>2000-04-03</domain:curExpDate>
|
||||
<domain:period unit="y">%YEARS%</domain:period>
|
||||
</domain:renew>
|
||||
</renew>
|
||||
<extension>
|
||||
<allocationToken:allocationToken
|
||||
xmlns:allocationToken=
|
||||
"urn:ietf:params:xml:ns:allocationToken-1.0">
|
||||
abc123
|
||||
</allocationToken:allocationToken>
|
||||
</extension>
|
||||
<clTRID>ABC-12345</clTRID>
|
||||
</command>
|
||||
</epp>
|
|
@ -489,10 +489,17 @@ comes in at the exact millisecond that the domain would have expired.
|
|||
* Registrar is missing the billing account map for this currency type.
|
||||
* Registrar is not authorized to access this TLD.
|
||||
* Registrar must be active in order to perform this operation.
|
||||
* The allocation token is invalid.
|
||||
* 2303
|
||||
* Resource with this id does not exist.
|
||||
* 2304
|
||||
* Resource status prohibits this operation.
|
||||
* The allocation token is not currently valid.
|
||||
* 2305
|
||||
* The allocation token is not valid for this domain.
|
||||
* The allocation token is not valid for this registrar.
|
||||
* The allocation token is not valid for this TLD.
|
||||
* The allocation token was already redeemed.
|
||||
* 2306
|
||||
* Periods for domain registrations must be specified in years.
|
||||
* The requested fees cannot be provided in the requested currency.
|
||||
|
|
Loading…
Add table
Reference in a new issue