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 badfb29ce1
commit 68f975451e
8 changed files with 481 additions and 83 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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