Add REMOVEPACKAGE token functionality to domain transfer flow (#1792)

This commit is contained in:
Pavlo Tkach 2022-09-19 15:11:36 -04:00 committed by GitHub
parent 527bf82370
commit 5ec4ec3af5
5 changed files with 119 additions and 19 deletions

View file

@ -32,6 +32,8 @@ import static google.registry.flows.domain.DomainTransferUtils.createLosingTrans
import static google.registry.flows.domain.DomainTransferUtils.createPendingTransferData; import static google.registry.flows.domain.DomainTransferUtils.createPendingTransferData;
import static google.registry.flows.domain.DomainTransferUtils.createTransferResponse; import static google.registry.flows.domain.DomainTransferUtils.createTransferResponse;
import static google.registry.flows.domain.DomainTransferUtils.createTransferServerApproveEntities; import static google.registry.flows.domain.DomainTransferUtils.createTransferServerApproveEntities;
import static google.registry.flows.domain.token.AllocationTokenFlowUtils.maybeApplyPackageRemovalToken;
import static google.registry.flows.domain.token.AllocationTokenFlowUtils.verifyTokenAllowedOnDomain;
import static google.registry.model.eppoutput.Result.Code.SUCCESS_WITH_ACTION_PENDING; import static google.registry.model.eppoutput.Result.Code.SUCCESS_WITH_ACTION_PENDING;
import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_TRANSFER_REQUEST; import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_TRANSFER_REQUEST;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
@ -63,6 +65,7 @@ import google.registry.model.domain.fee.FeeTransferCommandExtension;
import google.registry.model.domain.fee.FeeTransformResponseExtension; import google.registry.model.domain.fee.FeeTransformResponseExtension;
import google.registry.model.domain.metadata.MetadataExtension; import google.registry.model.domain.metadata.MetadataExtension;
import google.registry.model.domain.superuser.DomainTransferRequestSuperuserExtension; import google.registry.model.domain.superuser.DomainTransferRequestSuperuserExtension;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.domain.token.AllocationTokenExtension; import google.registry.model.domain.token.AllocationTokenExtension;
import google.registry.model.eppcommon.AuthInfo; import google.registry.model.eppcommon.AuthInfo;
import google.registry.model.eppcommon.StatusValue; import google.registry.model.eppcommon.StatusValue;
@ -132,6 +135,8 @@ import org.joda.time.DateTime;
* google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotValidForTldException} * google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotValidForTldException}
* @error {@link * @error {@link
* google.registry.flows.domain.token.AllocationTokenFlowUtils.AlreadyRedeemedAllocationTokenException} * google.registry.flows.domain.token.AllocationTokenFlowUtils.AlreadyRedeemedAllocationTokenException}
* @error {@link
* google.registry.flows.domain.token.AllocationTokenFlowUtils.MissingRemovePackageTokenOnPackageDomainException}
*/ */
@ReportingSpec(ActivityReportField.DOMAIN_TRANSFER_REQUEST) @ReportingSpec(ActivityReportField.DOMAIN_TRANSFER_REQUEST)
public final class DomainTransferRequestFlow implements TransactionalFlow { public final class DomainTransferRequestFlow implements TransactionalFlow {
@ -169,19 +174,24 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
extensionManager.validate(); extensionManager.validate();
DateTime now = tm().getTransactionTime(); DateTime now = tm().getTransactionTime();
Domain existingDomain = loadAndVerifyExistence(Domain.class, targetId, now); Domain existingDomain = loadAndVerifyExistence(Domain.class, targetId, now);
allocationTokenFlowUtils.verifyAllocationTokenIfPresent( Optional<AllocationToken> allocationToken =
existingDomain, allocationTokenFlowUtils.verifyAllocationTokenIfPresent(
Registry.get(existingDomain.getTld()), existingDomain,
gainingClientId, Registry.get(existingDomain.getTld()),
now, gainingClientId,
eppInput.getSingleExtension(AllocationTokenExtension.class)); now,
eppInput.getSingleExtension(AllocationTokenExtension.class));
Optional<DomainTransferRequestSuperuserExtension> superuserExtension = Optional<DomainTransferRequestSuperuserExtension> superuserExtension =
eppInput.getSingleExtension(DomainTransferRequestSuperuserExtension.class); eppInput.getSingleExtension(DomainTransferRequestSuperuserExtension.class);
Period period = Period period =
superuserExtension.isPresent() superuserExtension.isPresent()
? superuserExtension.get().getRenewalPeriod() ? superuserExtension.get().getRenewalPeriod()
: ((Transfer) resourceCommand).getPeriod(); : ((Transfer) resourceCommand).getPeriod();
verifyTransferAllowed(existingDomain, period, now, superuserExtension); verifyTransferAllowed(existingDomain, period, now, superuserExtension, allocationToken);
// If client passed an applicable static token this updates the domain
existingDomain = maybeApplyPackageRemovalToken(existingDomain, allocationToken);
String tld = existingDomain.getTld(); String tld = existingDomain.getTld();
Registry registry = Registry.get(tld); Registry registry = Registry.get(tld);
// An optional extension from the client specifying what they think the transfer should cost. // An optional extension from the client specifying what they think the transfer should cost.
@ -293,9 +303,11 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
Domain existingDomain, Domain existingDomain,
Period period, Period period,
DateTime now, DateTime now,
Optional<DomainTransferRequestSuperuserExtension> superuserExtension) Optional<DomainTransferRequestSuperuserExtension> superuserExtension,
Optional<AllocationToken> allocationToken)
throws EppException { throws EppException {
verifyNoDisallowedStatuses(existingDomain, DISALLOWED_STATUSES); verifyNoDisallowedStatuses(existingDomain, DISALLOWED_STATUSES);
verifyTokenAllowedOnDomain(existingDomain, allocationToken);
if (!isSuperuser) { if (!isSuperuser) {
verifyAuthInfoPresentForResourceTransfer(authInfo); verifyAuthInfoPresentForResourceTransfer(authInfo);
verifyAuthInfo(authInfo.get(), existingDomain); verifyAuthInfo(authInfo.get(), existingDomain);

View file

@ -293,11 +293,11 @@ public class AllocationTokenFlowUtils {
} }
} }
/** The __REMOVEPACKAGE__ token is missing on a renew package domain command */ /** The __REMOVEPACKAGE__ token is missing on a package domain command */
public static class MissingRemovePackageTokenOnPackageDomainException public static class MissingRemovePackageTokenOnPackageDomainException
extends AssociationProhibitsOperationException { extends AssociationProhibitsOperationException {
MissingRemovePackageTokenOnPackageDomainException() { MissingRemovePackageTokenOnPackageDomainException() {
super("Domains that are inside packages cannot be explicitly renewed"); super("Domains that are inside packages cannot be explicitly renewed or transferred");
} }
} }

View file

@ -21,6 +21,9 @@ import static com.google.common.truth.Truth8.assertThat;
import static google.registry.batch.AsyncTaskEnqueuer.PARAM_REQUESTED_TIME; import static google.registry.batch.AsyncTaskEnqueuer.PARAM_REQUESTED_TIME;
import static google.registry.batch.AsyncTaskEnqueuer.PARAM_RESOURCE_KEY; import static google.registry.batch.AsyncTaskEnqueuer.PARAM_RESOURCE_KEY;
import static google.registry.batch.AsyncTaskEnqueuer.QUEUE_ASYNC_ACTIONS; import static google.registry.batch.AsyncTaskEnqueuer.QUEUE_ASYNC_ACTIONS;
import static google.registry.model.billing.BillingEvent.RenewalPriceBehavior.DEFAULT;
import static google.registry.model.billing.BillingEvent.RenewalPriceBehavior.SPECIFIED;
import static google.registry.model.domain.token.AllocationToken.TokenType.PACKAGE;
import static google.registry.model.domain.token.AllocationToken.TokenType.SINGLE_USE; 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.model.domain.token.AllocationToken.TokenType.UNLIMITED_USE;
import static google.registry.model.reporting.DomainTransactionRecord.TransactionReportField.TRANSFER_SUCCESSFUL; import static google.registry.model.reporting.DomainTransactionRecord.TransactionReportField.TRANSFER_SUCCESSFUL;
@ -61,6 +64,7 @@ import com.google.common.collect.Iterables;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import com.google.common.collect.Streams; import com.google.common.collect.Streams;
import com.google.common.truth.Truth8;
import com.googlecode.objectify.Key; import com.googlecode.objectify.Key;
import google.registry.batch.ResaveEntityAction; import google.registry.batch.ResaveEntityAction;
import google.registry.flows.EppException; import google.registry.flows.EppException;
@ -85,6 +89,7 @@ import google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTok
import google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotValidForTldException; import google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotValidForTldException;
import google.registry.flows.domain.token.AllocationTokenFlowUtils.AlreadyRedeemedAllocationTokenException; import google.registry.flows.domain.token.AllocationTokenFlowUtils.AlreadyRedeemedAllocationTokenException;
import google.registry.flows.domain.token.AllocationTokenFlowUtils.InvalidAllocationTokenException; import google.registry.flows.domain.token.AllocationTokenFlowUtils.InvalidAllocationTokenException;
import google.registry.flows.domain.token.AllocationTokenFlowUtils.MissingRemovePackageTokenOnPackageDomainException;
import google.registry.flows.exceptions.AlreadyPendingTransferException; import google.registry.flows.exceptions.AlreadyPendingTransferException;
import google.registry.flows.exceptions.InvalidTransferPeriodValueException; import google.registry.flows.exceptions.InvalidTransferPeriodValueException;
import google.registry.flows.exceptions.MissingTransferRequestAuthInfoException; import google.registry.flows.exceptions.MissingTransferRequestAuthInfoException;
@ -1700,13 +1705,15 @@ class DomainTransferRequestFlowTest
.setDomainName("example.tld") .setDomainName("example.tld")
.build()); .build());
doSuccessfulTest( doSuccessfulTest(
"domain_transfer_request_allocation_token.xml", "domain_transfer_request_response.xml"); "domain_transfer_request_allocation_token.xml",
"domain_transfer_request_response.xml",
ImmutableMap.of("TOKEN", "abc123"));
} }
@Test @Test
void testFailure_invalidAllocationToken() throws Exception { void testFailure_invalidAllocationToken() throws Exception {
setupDomain("example", "tld"); setupDomain("example", "tld");
setEppInput("domain_transfer_request_allocation_token.xml"); setEppInput("domain_transfer_request_allocation_token.xml", ImmutableMap.of("TOKEN", "abc123"));
EppException thrown = assertThrows(InvalidAllocationTokenException.class, this::runFlow); EppException thrown = assertThrows(InvalidAllocationTokenException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml(); assertAboutEppExceptions().that(thrown).marshalsToXml();
} }
@ -1720,7 +1727,7 @@ class DomainTransferRequestFlowTest
.setTokenType(SINGLE_USE) .setTokenType(SINGLE_USE)
.setDomainName("otherdomain.tld") .setDomainName("otherdomain.tld")
.build()); .build());
setEppInput("domain_transfer_request_allocation_token.xml"); setEppInput("domain_transfer_request_allocation_token.xml", ImmutableMap.of("TOKEN", "abc123"));
EppException thrown = EppException thrown =
assertThrows(AllocationTokenNotValidForDomainException.class, this::runFlow); assertThrows(AllocationTokenNotValidForDomainException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml(); assertAboutEppExceptions().that(thrown).marshalsToXml();
@ -1741,7 +1748,7 @@ class DomainTransferRequestFlowTest
.put(clock.nowUtc().plusDays(60), TokenStatus.ENDED) .put(clock.nowUtc().plusDays(60), TokenStatus.ENDED)
.build()) .build())
.build()); .build());
setEppInput("domain_transfer_request_allocation_token.xml"); setEppInput("domain_transfer_request_allocation_token.xml", ImmutableMap.of("TOKEN", "abc123"));
EppException thrown = assertThrows(AllocationTokenNotInPromotionException.class, this::runFlow); EppException thrown = assertThrows(AllocationTokenNotInPromotionException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml(); assertAboutEppExceptions().that(thrown).marshalsToXml();
} }
@ -1762,7 +1769,7 @@ class DomainTransferRequestFlowTest
.put(clock.nowUtc().plusDays(1), TokenStatus.ENDED) .put(clock.nowUtc().plusDays(1), TokenStatus.ENDED)
.build()) .build())
.build()); .build());
setEppInput("domain_transfer_request_allocation_token.xml"); setEppInput("domain_transfer_request_allocation_token.xml", ImmutableMap.of("TOKEN", "abc123"));
EppException thrown = EppException thrown =
assertThrows(AllocationTokenNotValidForRegistrarException.class, this::runFlow); assertThrows(AllocationTokenNotValidForRegistrarException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml(); assertAboutEppExceptions().that(thrown).marshalsToXml();
@ -1784,7 +1791,7 @@ class DomainTransferRequestFlowTest
.put(clock.nowUtc().plusDays(1), TokenStatus.ENDED) .put(clock.nowUtc().plusDays(1), TokenStatus.ENDED)
.build()) .build())
.build()); .build());
setEppInput("domain_transfer_request_allocation_token.xml"); setEppInput("domain_transfer_request_allocation_token.xml", ImmutableMap.of("TOKEN", "abc123"));
EppException thrown = assertThrows(AllocationTokenNotValidForTldException.class, this::runFlow); EppException thrown = assertThrows(AllocationTokenNotValidForTldException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml(); assertAboutEppExceptions().that(thrown).marshalsToXml();
} }
@ -1800,9 +1807,89 @@ class DomainTransferRequestFlowTest
.setTokenType(SINGLE_USE) .setTokenType(SINGLE_USE)
.setRedemptionHistoryEntry(HistoryEntry.createVKey(historyEntryKey)) .setRedemptionHistoryEntry(HistoryEntry.createVKey(historyEntryKey))
.build()); .build());
setEppInput("domain_transfer_request_allocation_token.xml"); setEppInput("domain_transfer_request_allocation_token.xml", ImmutableMap.of("TOKEN", "abc123"));
EppException thrown = EppException thrown =
assertThrows(AlreadyRedeemedAllocationTokenException.class, this::runFlow); assertThrows(AlreadyRedeemedAllocationTokenException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml(); assertAboutEppExceptions().that(thrown).marshalsToXml();
} }
@Test
void testFailsPackageDomainInvalidAllocationToken() throws Exception {
AllocationToken token =
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(PACKAGE)
.setAllowedRegistrarIds(ImmutableSet.of("NewRegistrar"))
.setAllowedTlds(ImmutableSet.of("example", "tld"))
.setRenewalPriceBehavior(SPECIFIED)
.build());
setupDomain("example", "tld");
persistResource(
reloadResourceByForeignKey()
.asBuilder()
.setCurrentPackageToken(token.createVKey())
.build());
setEppInput("domain_transfer_request_allocation_token.xml", ImmutableMap.of("TOKEN", "abc123"));
assertThrows(MissingRemovePackageTokenOnPackageDomainException.class, this::runFlow);
}
@Test
void testFailsToTransferPackageDomainNoRemovePackageToken() throws Exception {
AllocationToken token =
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(PACKAGE)
.setAllowedRegistrarIds(ImmutableSet.of("NewRegistrar"))
.setAllowedTlds(ImmutableSet.of("example", "tld"))
.setRenewalPriceBehavior(SPECIFIED)
.build());
setupDomain("example", "tld");
persistResource(
reloadResourceByForeignKey()
.asBuilder()
.setCurrentPackageToken(token.createVKey())
.build());
setEppInput("domain_transfer_request.xml");
assertThrows(MissingRemovePackageTokenOnPackageDomainException.class, this::runFlow);
}
@Test
void testSuccesfullyAppliesRemovePackageToken() throws Exception {
setupDomain("example", "tld");
AllocationToken token =
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(PACKAGE)
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setAllowedTlds(ImmutableSet.of("tld"))
.setRenewalPriceBehavior(SPECIFIED)
.build());
domain = loadByEntity(domain);
persistResource(
loadByKey(domain.getAutorenewBillingEvent())
.asBuilder()
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
.setRenewalPrice(Money.of(USD, new BigDecimal("10.00")))
.build());
persistResource(
reloadResourceByForeignKey()
.asBuilder()
.setCurrentPackageToken(token.createVKey())
.build());
doSuccessfulTest(
"domain_transfer_request_allocation_token.xml",
"domain_transfer_request_response.xml",
ImmutableMap.of("TOKEN", "__REMOVEPACKAGE__"));
Domain domain = reloadResourceByForeignKey();
Truth8.assertThat(domain.getCurrentPackageToken()).isEmpty();
RenewalPriceBehavior priceBehavior =
loadByKey(domain.getAutorenewBillingEvent()).getRenewalPriceBehavior();
assertThat(priceBehavior).isEqualTo(DEFAULT);
}
} }

View file

@ -14,7 +14,7 @@
<allocationToken:allocationToken <allocationToken:allocationToken
xmlns:allocationToken= xmlns:allocationToken=
"urn:ietf:params:xml:ns:allocationToken-1.0"> "urn:ietf:params:xml:ns:allocationToken-1.0">
abc123 %TOKEN%
</allocationToken:allocationToken> </allocationToken:allocationToken>
</extension> </extension>
<clTRID>ABC-12345</clTRID> <clTRID>ABC-12345</clTRID>

View file

@ -498,7 +498,7 @@ comes in at the exact millisecond that the domain would have expired.
* Resource status prohibits this operation. * Resource status prohibits this operation.
* The allocation token is not currently valid. * The allocation token is not currently valid.
* 2305 * 2305
* The __REMOVEPACKAGE__ token is missing on a renew package domain command * The __REMOVEPACKAGE__ token is missing on a package domain command
* The __REMOVEPACKAGE__ token is not allowed on non package domains * The __REMOVEPACKAGE__ token is not allowed on non package domains
* The allocation token is not valid for this domain. * 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 registrar.
@ -754,6 +754,7 @@ new ones with the correct approval time).
* The allocation token is not valid for this registrar. * The allocation token is not valid for this registrar.
* The allocation token is not valid for this TLD. * The allocation token is not valid for this TLD.
* The allocation token was already redeemed. * The allocation token was already redeemed.
* The __REMOVEPACKAGE__ token is missing on a package domain command
* 2306 * 2306
* Domain transfer period must be one year. * Domain transfer period must be one year.
* Domain transfer period must be zero or one year when using the superuser * Domain transfer period must be zero or one year when using the superuser