Don't apply non-premium default tokens to premium names (#2007)

* Don't apply non-premium default tokens to premium names

* Add test for renew

* Remove premium check from try/catch block

* Add check in validateToken

* Update docs

* Add validateForPremiums

* Better method name

* Shorten error message to fit as reason

* Add missing extension catch

* Remove extra javadoc

* Fix merge conflicts and change error message

* Update flow docs
This commit is contained in:
sarahcaseybot 2023-04-28 17:56:15 -04:00 committed by GitHub
parent 53549afd0d
commit 2d49aed306
8 changed files with 264 additions and 36 deletions

View file

@ -50,6 +50,7 @@ import google.registry.flows.annotations.ReportingSpec;
import google.registry.flows.custom.DomainCheckFlowCustomLogic; import google.registry.flows.custom.DomainCheckFlowCustomLogic;
import google.registry.flows.custom.DomainCheckFlowCustomLogic.BeforeResponseParameters; import google.registry.flows.custom.DomainCheckFlowCustomLogic.BeforeResponseParameters;
import google.registry.flows.custom.DomainCheckFlowCustomLogic.BeforeResponseReturnData; import google.registry.flows.custom.DomainCheckFlowCustomLogic.BeforeResponseReturnData;
import google.registry.flows.domain.DomainPricingLogic.AllocationTokenInvalidForPremiumNameException;
import google.registry.flows.domain.token.AllocationTokenDomainCheckResults; import google.registry.flows.domain.token.AllocationTokenDomainCheckResults;
import google.registry.flows.domain.token.AllocationTokenFlowUtils; import google.registry.flows.domain.token.AllocationTokenFlowUtils;
import google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotInPromotionException; import google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotInPromotionException;
@ -81,6 +82,7 @@ import google.registry.model.tld.Tld;
import google.registry.model.tld.Tld.TldState; import google.registry.model.tld.Tld.TldState;
import google.registry.model.tld.label.ReservationType; import google.registry.model.tld.label.ReservationType;
import google.registry.persistence.VKey; import google.registry.persistence.VKey;
import google.registry.pricing.PricingEngineProxy;
import google.registry.util.Clock; import google.registry.util.Clock;
import java.util.HashSet; import java.util.HashSet;
import java.util.Optional; import java.util.Optional;
@ -291,6 +293,7 @@ public final class DomainCheckFlow implements Flow {
allocationToken.get(), allocationToken.get(),
feeCheckItem.getCommandName(), feeCheckItem.getCommandName(),
registrarId, registrarId,
PricingEngineProxy.isDomainPremium(domainName, now),
now); now);
} }
handleFeeRequest( handleFeeRequest(
@ -305,7 +308,8 @@ public final class DomainCheckFlow implements Flow {
availableDomains.contains(domainName), availableDomains.contains(domainName),
recurrences.getOrDefault(domainName, null)); recurrences.getOrDefault(domainName, null));
responseItems.add(builder.setDomainNameIfSupported(domainName).build()); responseItems.add(builder.setDomainNameIfSupported(domainName).build());
} catch (AllocationTokenNotValidForCommandException } catch (AllocationTokenInvalidForPremiumNameException
| AllocationTokenNotValidForCommandException
| AllocationTokenNotValidForDomainException | AllocationTokenNotValidForDomainException
| AllocationTokenNotValidForRegistrarException | AllocationTokenNotValidForRegistrarException
| AllocationTokenNotValidForTldException | AllocationTokenNotValidForTldException

View file

@ -74,6 +74,7 @@ import google.registry.flows.EppException.ParameterValueSyntaxErrorException;
import google.registry.flows.EppException.RequiredParameterMissingException; import google.registry.flows.EppException.RequiredParameterMissingException;
import google.registry.flows.EppException.StatusProhibitsOperationException; import google.registry.flows.EppException.StatusProhibitsOperationException;
import google.registry.flows.EppException.UnimplementedOptionException; import google.registry.flows.EppException.UnimplementedOptionException;
import google.registry.flows.domain.DomainPricingLogic.AllocationTokenInvalidForPremiumNameException;
import google.registry.flows.domain.token.AllocationTokenFlowUtils; import google.registry.flows.domain.token.AllocationTokenFlowUtils;
import google.registry.flows.exceptions.ResourceHasClientUpdateProhibitedException; import google.registry.flows.exceptions.ResourceHasClientUpdateProhibitedException;
import google.registry.model.EppResource; import google.registry.model.EppResource;
@ -1217,8 +1218,15 @@ public class DomainFlowUtils {
for (Optional<AllocationToken> token : tokenList) { for (Optional<AllocationToken> token : tokenList) {
try { try {
AllocationTokenFlowUtils.validateToken( AllocationTokenFlowUtils.validateToken(
InternetDomainName.from(domainName), token.get(), commandName, registrarId, now); InternetDomainName.from(domainName),
} catch (AssociationProhibitsOperationException | StatusProhibitsOperationException e) { token.get(),
commandName,
registrarId,
isDomainPremium(domainName, now),
now);
} catch (AssociationProhibitsOperationException
| StatusProhibitsOperationException
| AllocationTokenInvalidForPremiumNameException e) {
// Allocation token was not valid for this registration, continue to check the next token in // Allocation token was not valid for this registration, continue to check the next token in
// the list // the list
continue; continue;

View file

@ -16,6 +16,7 @@ package google.registry.flows.domain;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.flows.domain.DomainFlowUtils.zeroInCurrency; import static google.registry.flows.domain.DomainFlowUtils.zeroInCurrency;
import static google.registry.flows.domain.token.AllocationTokenFlowUtils.validateTokenForPossiblePremiumName;
import static google.registry.pricing.PricingEngineProxy.getPricesForDomainName; import static google.registry.pricing.PricingEngineProxy.getPricesForDomainName;
import static google.registry.util.DomainNameUtils.getTldFromDomainName; import static google.registry.util.DomainNameUtils.getTldFromDomainName;
import static google.registry.util.PreconditionsUtils.checkArgumentPresent; import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
@ -257,12 +258,7 @@ public final class DomainPricingLogic {
private Money getDomainCostWithDiscount( private Money getDomainCostWithDiscount(
boolean isPremium, int years, Optional<AllocationToken> allocationToken, Money oneYearCost) boolean isPremium, int years, Optional<AllocationToken> allocationToken, Money oneYearCost)
throws AllocationTokenInvalidForPremiumNameException { throws AllocationTokenInvalidForPremiumNameException {
if (allocationToken.isPresent() validateTokenForPossiblePremiumName(allocationToken, isPremium);
&& allocationToken.get().getDiscountFraction() != 0.0
&& isPremium
&& !allocationToken.get().shouldDiscountPremiums()) {
throw new AllocationTokenInvalidForPremiumNameException();
}
Money totalDomainFlowCost = oneYearCost.multipliedBy(years); Money totalDomainFlowCost = oneYearCost.multipliedBy(years);
// Apply the allocation token discount, if applicable. // Apply the allocation token discount, if applicable.
@ -281,8 +277,8 @@ public final class DomainPricingLogic {
/** An allocation token was provided that is invalid for premium domains. */ /** An allocation token was provided that is invalid for premium domains. */
public static class AllocationTokenInvalidForPremiumNameException public static class AllocationTokenInvalidForPremiumNameException
extends CommandUseErrorException { extends CommandUseErrorException {
AllocationTokenInvalidForPremiumNameException() { public AllocationTokenInvalidForPremiumNameException() {
super("A nonzero discount code cannot be applied to premium domains"); super("Token not valid for premium name");
} }
} }
} }

View file

@ -16,6 +16,7 @@ package google.registry.flows.domain.token;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.pricing.PricingEngineProxy.isDomainPremium;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
@ -26,6 +27,7 @@ import google.registry.flows.EppException;
import google.registry.flows.EppException.AssociationProhibitsOperationException; import google.registry.flows.EppException.AssociationProhibitsOperationException;
import google.registry.flows.EppException.AuthorizationErrorException; import google.registry.flows.EppException.AuthorizationErrorException;
import google.registry.flows.EppException.StatusProhibitsOperationException; import google.registry.flows.EppException.StatusProhibitsOperationException;
import google.registry.flows.domain.DomainPricingLogic.AllocationTokenInvalidForPremiumNameException;
import google.registry.model.billing.BillingBase.RenewalPriceBehavior; import google.registry.model.billing.BillingBase.RenewalPriceBehavior;
import google.registry.model.billing.BillingRecurrence; import google.registry.model.billing.BillingRecurrence;
import google.registry.model.domain.Domain; import google.registry.model.domain.Domain;
@ -79,7 +81,13 @@ public class AllocationTokenFlowUtils {
ImmutableMap.Builder<InternetDomainName, String> resultsBuilder = new ImmutableMap.Builder<>(); ImmutableMap.Builder<InternetDomainName, String> resultsBuilder = new ImmutableMap.Builder<>();
for (InternetDomainName domainName : domainNames) { for (InternetDomainName domainName : domainNames) {
try { try {
validateToken(domainName, tokenEntity, CommandName.CREATE, registrarId, now); validateToken(
domainName,
tokenEntity,
CommandName.CREATE,
registrarId,
isDomainPremium(domainName.toString(), now),
now);
validDomainNames.add(domainName); validDomainNames.add(domainName);
} catch (EppException e) { } catch (EppException e) {
resultsBuilder.put(domainName, e.getMessage()); resultsBuilder.put(domainName, e.getMessage());
@ -105,7 +113,7 @@ public class AllocationTokenFlowUtils {
/** /**
* Validates a given token. The token could be invalid if it has allowed client IDs or TLDs that * Validates a given token. The token could be invalid if it has allowed client IDs or TLDs that
* do not include this client ID / TLD, or if the token has a promotion that is not currently * do not include this client ID / TLD, or if the token has a promotion that is not currently
* running. * running, or the token is not valid for a premium name when necessary.
* *
* @throws EppException if the token is invalid in any way * @throws EppException if the token is invalid in any way
*/ */
@ -114,33 +122,48 @@ public class AllocationTokenFlowUtils {
AllocationToken token, AllocationToken token,
CommandName commandName, CommandName commandName,
String registrarId, String registrarId,
boolean isPremium,
DateTime now) DateTime now)
throws EppException { throws EppException {
// Only tokens with default behavior require validation // Only tokens with default behavior require validation
if (TokenBehavior.DEFAULT.equals(token.getTokenBehavior())) { if (!TokenBehavior.DEFAULT.equals(token.getTokenBehavior())) {
if (!token.getAllowedEppActions().isEmpty() return;
&& !token.getAllowedEppActions().contains(commandName)) { }
throw new AllocationTokenNotValidForCommandException(); validateTokenForPossiblePremiumName(Optional.of(token), isPremium);
} if (!token.getAllowedEppActions().isEmpty()
if (!token.getAllowedRegistrarIds().isEmpty() && !token.getAllowedEppActions().contains(commandName)) {
&& !token.getAllowedRegistrarIds().contains(registrarId)) { throw new AllocationTokenNotValidForCommandException();
throw new AllocationTokenNotValidForRegistrarException(); }
} if (!token.getAllowedRegistrarIds().isEmpty()
if (!token.getAllowedTlds().isEmpty() && !token.getAllowedRegistrarIds().contains(registrarId)) {
&& !token.getAllowedTlds().contains(domainName.parent().toString())) { throw new AllocationTokenNotValidForRegistrarException();
throw new AllocationTokenNotValidForTldException(); }
} if (!token.getAllowedTlds().isEmpty()
if (token.getDomainName().isPresent() && !token.getAllowedTlds().contains(domainName.parent().toString())) {
&& !token.getDomainName().get().equals(domainName.toString())) { throw new AllocationTokenNotValidForTldException();
throw new AllocationTokenNotValidForDomainException(); }
} if (token.getDomainName().isPresent()
// Tokens without status transitions will just have a single-entry NOT_STARTED map, so only && !token.getDomainName().get().equals(domainName.toString())) {
// check the status transitions map if it's non-trivial. throw new AllocationTokenNotValidForDomainException();
if (token.getTokenStatusTransitions().size() > 1 }
&& !TokenStatus.VALID.equals(token.getTokenStatusTransitions().getValueAtTime(now))) { // Tokens without status transitions will just have a single-entry NOT_STARTED map, so only
throw new AllocationTokenNotInPromotionException(); // check the status transitions map if it's non-trivial.
} if (token.getTokenStatusTransitions().size() > 1
&& !TokenStatus.VALID.equals(token.getTokenStatusTransitions().getValueAtTime(now))) {
throw new AllocationTokenNotInPromotionException();
}
}
/** Validates that the given token is valid for a premium name if the name is premium. */
public static void validateTokenForPossiblePremiumName(
Optional<AllocationToken> token, boolean isPremium)
throws AllocationTokenInvalidForPremiumNameException {
if (token.isPresent()
&& token.get().getDiscountFraction() != 0.0
&& isPremium
&& !token.get().shouldDiscountPremiums()) {
throw new AllocationTokenInvalidForPremiumNameException();
} }
} }
@ -187,6 +210,7 @@ public class AllocationTokenFlowUtils {
tokenEntity, tokenEntity,
CommandName.CREATE, CommandName.CREATE,
registrarId, registrarId,
isDomainPremium(command.getDomainName(), now),
now); now);
return Optional.of(tokenCustomLogic.validateToken(command, tokenEntity, tld, registrarId, now)); return Optional.of(tokenCustomLogic.validateToken(command, tokenEntity, tld, registrarId, now));
} }
@ -209,6 +233,7 @@ public class AllocationTokenFlowUtils {
tokenEntity, tokenEntity,
commandName, commandName,
registrarId, registrarId,
isDomainPremium(existingDomain.getDomainName(), now),
now); now);
return Optional.of( return Optional.of(
tokenCustomLogic.validateToken(existingDomain, tokenEntity, tld, registrarId, now)); tokenCustomLogic.validateToken(existingDomain, tokenEntity, tld, registrarId, now));

View file

@ -407,6 +407,26 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase<DomainCheckFlow, Dom
.build())); .build()));
} }
@Test
void testFailure_allocationTokenPromotion_PremiumsNotSet() throws Exception {
createTld("example");
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(SINGLE_USE)
.setDiscountFraction(0.9)
.setDiscountYears(3)
.setDiscountPremiums(false)
.build());
setEppInput(
"domain_check_allocationtoken_multiname_promotion.xml",
ImmutableMap.of("DOMAIN", "rich.example"));
doCheckTest(
create(true, "example1.example", null),
create(false, "rich.example", "Token not valid for premium name"),
create(true, "example3.example", null));
}
@Test @Test
void testSuccess_allocationTokenPromotion_multiYear() throws Exception { void testSuccess_allocationTokenPromotion_multiYear() throws Exception {
createTld("tld"); createTld("tld");
@ -563,6 +583,23 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase<DomainCheckFlow, Dom
doCheckTest(create(true, "anchor.tld", null)); doCheckTest(create(true, "anchor.tld", null));
} }
@Test
void testSuccess_premiumAnchorTenantWithToken() throws Exception {
setEppInput("domain_check_anchor_allocationtoken.xml");
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(SINGLE_USE)
.setDomainName("anchor.tld")
.build());
persistResource(
Tld.get("tld")
.asBuilder()
.setPremiumList(persistPremiumList("tld", USD, "anchor,USD 70"))
.build());
doCheckTest(create(true, "anchor.tld", null));
}
@Test @Test
void testSuccess_multipartTld_oneReserved() throws Exception { void testSuccess_multipartTld_oneReserved() throws Exception {
createTld("tld.foo"); createTld("tld.foo");
@ -1001,6 +1038,29 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase<DomainCheckFlow, Dom
runFlowAssertResponse(loadFile("domain_check_fee_premium_response_v06.xml")); runFlowAssertResponse(loadFile("domain_check_fee_premium_response_v06.xml"));
} }
/** Test the same as {@link #testFeeExtension_multipleCommands_v06} with premium labels. */
@Test
void testFeeExtension_premiumLabels_doesNotApplyDefaultToken_v06() throws Exception {
createTld("example");
AllocationToken defaultToken =
persistResource(
new AllocationToken.Builder()
.setToken("bbbbb")
.setTokenType(DEFAULT_PROMO)
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setAllowedTlds(ImmutableSet.of("example"))
.setDiscountPremiums(false)
.setDiscountFraction(0.5)
.build());
persistResource(
Tld.get("example")
.asBuilder()
.setDefaultPromoTokens(ImmutableList.of(defaultToken.createVKey()))
.build());
setEppInput("domain_check_fee_premium_v06.xml");
runFlowAssertResponse(loadFile("domain_check_fee_premium_response_v06.xml"));
}
@Test @Test
void testFeeExtension_existingPremiumDomain_withNonPremiumRenewalBehavior() throws Exception { void testFeeExtension_existingPremiumDomain_withNonPremiumRenewalBehavior() throws Exception {
createTld("example"); createTld("example");
@ -1079,6 +1139,28 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase<DomainCheckFlow, Dom
runFlowAssertResponse(loadFile("domain_check_fee_premium_response_v11_create.xml")); runFlowAssertResponse(loadFile("domain_check_fee_premium_response_v11_create.xml"));
} }
@Test
void testFeeExtension_premiumLabels_doesNotApplyDefaultToken_v11() throws Exception {
createTld("example");
AllocationToken defaultToken =
persistResource(
new AllocationToken.Builder()
.setToken("bbbbb")
.setTokenType(DEFAULT_PROMO)
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setAllowedTlds(ImmutableSet.of("example"))
.setDiscountPremiums(false)
.setDiscountFraction(0.5)
.build());
persistResource(
Tld.get("example")
.asBuilder()
.setDefaultPromoTokens(ImmutableList.of(defaultToken.createVKey()))
.build());
setEppInput("domain_check_fee_premium_v11_create.xml");
runFlowAssertResponse(loadFile("domain_check_fee_premium_response_v11_create.xml"));
}
@Test @Test
void testFeeExtension_premiumLabels_v11_renew() throws Exception { void testFeeExtension_premiumLabels_v11_renew() throws Exception {
createTld("example"); createTld("example");
@ -1123,6 +1205,28 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase<DomainCheckFlow, Dom
runFlowAssertResponse(loadFile("domain_check_fee_premium_response_v12.xml")); runFlowAssertResponse(loadFile("domain_check_fee_premium_response_v12.xml"));
} }
@Test
void testFeeExtension_premiumLabels_doesNotApplyDefaultToken_v12() throws Exception {
createTld("example");
AllocationToken defaultToken =
persistResource(
new AllocationToken.Builder()
.setToken("bbbbb")
.setTokenType(DEFAULT_PROMO)
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setAllowedTlds(ImmutableSet.of("example"))
.setDiscountPremiums(false)
.setDiscountFraction(0.5)
.build());
persistResource(
Tld.get("example")
.asBuilder()
.setDefaultPromoTokens(ImmutableList.of(defaultToken.createVKey()))
.build());
setEppInput("domain_check_fee_premium_v12.xml");
runFlowAssertResponse(loadFile("domain_check_fee_premium_response_v12.xml"));
}
@Test @Test
void testFeeExtension_premiumLabels_v12_withRenewalOnRestore() throws Exception { void testFeeExtension_premiumLabels_v12_withRenewalOnRestore() throws Exception {
createTld("example"); createTld("example");

View file

@ -2043,6 +2043,33 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
doSuccessfulTest(); doSuccessfulTest();
} }
@Test
void testSuccess_doesNotApplyNonPremiumDefaultTokenToPremiumName() throws Exception {
persistContactsAndHosts();
createTld("example");
AllocationToken defaultToken1 =
persistResource(
new AllocationToken.Builder()
.setToken("aaaaa")
.setTokenType(DEFAULT_PROMO)
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setAllowedTlds(ImmutableSet.of("example"))
.setDiscountFraction(0.5)
.setDiscountPremiums(false)
.build());
persistResource(
Tld.get("example")
.asBuilder()
.setDefaultPromoTokens(ImmutableList.of(defaultToken1.createVKey()))
.build());
setEppInput("domain_create_premium.xml");
runFlowAssertResponse(
loadFile(
"domain_create_response_premium.xml",
ImmutableMap.of("EXDATE", "2001-04-03T22:00:00.0Z", "FEE", "200.00")));
assertSuccessfulCreate("example", ImmutableSet.of());
}
BillingEvent runTest_defaultToken(String token) throws Exception { BillingEvent runTest_defaultToken(String token) throws Exception {
setEppInput("domain_create.xml", ImmutableMap.of("DOMAIN", "example.tld")); setEppInput("domain_create.xml", ImmutableMap.of("DOMAIN", "example.tld"));
runFlowAssertResponse( runFlowAssertResponse(

View file

@ -1506,6 +1506,50 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, Domain>
assertThat(billingEvent.getCost()).isEqualTo(Money.of(USD, 22)); assertThat(billingEvent.getCost()).isEqualTo(Money.of(USD, 22));
} }
@Test
void testSuccess_doesNotApplyNonPremiumDefaultTokenToPremiumName() throws Exception {
ImmutableMap<String, String> customFeeMap = updateSubstitutions(FEE_06_MAP, "FEE", "500");
setEppInput("domain_renew_fee.xml", customFeeMap);
persistDomain();
AllocationToken defaultToken1 =
persistResource(
new AllocationToken.Builder()
.setToken("aaaaa")
.setTokenType(DEFAULT_PROMO)
.setDiscountFraction(0.5)
.setDiscountYears(1)
.setAllowedTlds(ImmutableSet.of("tld"))
.build());
persistResource(
Tld.get("tld")
.asBuilder()
.setDefaultPromoTokens(ImmutableList.of(defaultToken1.createVKey()))
.setPremiumList(persistPremiumList("tld", USD, "example,USD 100"))
.build());
runFlowAssertResponse(
loadFile(
"domain_renew_response_fee.xml",
ImmutableMap.of(
"NAME",
"example.tld",
"PERIOD",
"5",
"EX_DATE",
"2005-04-03T22:00:00.0Z",
"FEE",
"500.0",
"CURRENCY",
"USD",
"FEE_VERSION",
"0.6",
"FEE_NS",
"fee")));
BillingEvent billingEvent =
Iterables.getOnlyElement(DatabaseHelper.loadAllOf(BillingEvent.class));
assertThat(billingEvent.getTargetId()).isEqualTo("example.tld");
assertThat(billingEvent.getAllocationToken()).isEmpty();
}
@Test @Test
void testSuccess_onlyUsesFirstValidToken() throws Exception { void testSuccess_onlyUsesFirstValidToken() throws Exception {
setEppInput("domain_renew.xml", ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2")); setEppInput("domain_renew.xml", ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2"));

View file

@ -0,0 +1,20 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<check>
<domain:check
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>example1.example</domain:name>
<domain:name>rich.example</domain:name>
<domain:name>example3.example</domain:name>
</domain:check>
</check>
<extension>
<allocationToken:allocationToken
xmlns:allocationToken=
"urn:ietf:params:xml:ns:allocationToken-1.0">
abc123
</allocationToken:allocationToken>
</extension>
<clTRID>ABC-12345</clTRID>
</command>
</epp>