From 5726f1dc4e87a309ec188d6cb87d3745bdafe263 Mon Sep 17 00:00:00 2001 From: mcilwain Date: Thu, 11 Jan 2018 14:16:25 -0800 Subject: [PATCH] Add base AllocationToken validation logic for domain checks Next up is adding custom logic so that the results of these checks can be more meaningful. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=181660956 --- docs/flows.md | 1 + .../domain/AllocationTokenFlowUtils.java | 36 +++++++++- .../flows/domain/DomainCheckFlow.java | 28 ++++++-- .../flows/domain/DomainClaimsCheckFlow.java | 16 ++++- .../flows/ResourceCheckFlowTestCase.java | 4 +- .../flows/domain/DomainCheckFlowTest.java | 53 ++++++++++++++- .../domain/DomainClaimsCheckFlowTest.java | 19 ++++-- .../domain_check_50_allocationtoken.xml | 66 +++++++++++++++++++ .../testdata/domain_check_allocationtoken.xml | 4 +- .../domain/testdata/domain_check_claims.xml | 30 ++++----- .../domain_check_claims_allocationtoken.xml | 25 +++++++ .../domain_check_reserved_allocationtoken.xml | 22 +++++++ 12 files changed, 266 insertions(+), 38 deletions(-) create mode 100644 javatests/google/registry/flows/domain/testdata/domain_check_50_allocationtoken.xml create mode 100644 javatests/google/registry/flows/domain/testdata/domain_check_claims_allocationtoken.xml create mode 100644 javatests/google/registry/flows/domain/testdata/domain_check_reserved_allocationtoken.xml diff --git a/docs/flows.md b/docs/flows.md index 2d0057d62..e58157bef 100644 --- a/docs/flows.md +++ b/docs/flows.md @@ -495,6 +495,7 @@ An EPP flow that checks whether domain labels are trademarked. * 2002 * Command is not allowed in the current registry phase. * Claims checks are not allowed during sunrise. + * Claims checks are not allowed with allocation tokens. * 2004 * Domain name is under tld which doesn't exist. * 2201 diff --git a/java/google/registry/flows/domain/AllocationTokenFlowUtils.java b/java/google/registry/flows/domain/AllocationTokenFlowUtils.java index bdd4a067a..e1e0d3c43 100644 --- a/java/google/registry/flows/domain/AllocationTokenFlowUtils.java +++ b/java/google/registry/flows/domain/AllocationTokenFlowUtils.java @@ -16,6 +16,7 @@ package google.registry.flows.domain; import static google.registry.model.ofy.ObjectifyService.ofy; +import com.google.common.collect.ImmutableMap; import com.google.common.net.InternetDomainName; import com.googlecode.objectify.Key; import google.registry.flows.EppException; @@ -24,6 +25,8 @@ import google.registry.flows.EppException.ParameterValueSyntaxErrorException; import google.registry.model.domain.AllocationToken; import google.registry.model.registry.Registry; import google.registry.model.reporting.HistoryEntry; +import java.util.List; +import java.util.function.Function; /** Static utility functions for dealing with {@link AllocationToken}s in domain flows. */ public class AllocationTokenFlowUtils { @@ -47,6 +50,31 @@ public class AllocationTokenFlowUtils { return tokenEntity; } + /** + * Checks if the allocation token applies to the given domain names, used for domain checks. + * + * @return A map of domain names to domain check error response messages. If a message is present + * for a a given domain then it does not validate with this allocation token; domains that do + * validate are not present in the map. + */ + static ImmutableMap checkDomainsWithToken( + List domainNames, String token, String clientId) { + AllocationToken tokenEntity = ofy().load().key(Key.create(AllocationToken.class, token)).now(); + String result; + if (tokenEntity == null) { + result = new InvalidAllocationTokenException().getMessage(); + } else if (tokenEntity.isRedeemed()) { + result = AlreadyRedeemedAllocationTokenException.ERROR_MSG_SHORT; + } else { + return ImmutableMap.of(); + } + // TODO(b/70628322): For now all checks yield the same result, but custom logic will soon allow + // them to differ, e.g. if tokens can only be used on certain TLDs. + return domainNames + .stream() + .collect(ImmutableMap.toImmutableMap(Function.identity(), domainName -> result)); + } + /** Redeems an {@link AllocationToken}, returning the redeemed copy. */ static AllocationToken redeemToken( AllocationToken token, Key redemptionHistoryEntry) { @@ -56,8 +84,14 @@ public class AllocationTokenFlowUtils { /** The allocation token was already redeemed. */ static class AlreadyRedeemedAllocationTokenException extends AssociationProhibitsOperationException { + + public static final String ERROR_MSG_LONG = "The allocation token was already redeemed"; + + /** A short error message fitting within 32 characters for use in domain check responses. */ + public static final String ERROR_MSG_SHORT = "Alloc token was already redeemed"; + public AlreadyRedeemedAllocationTokenException() { - super("The allocation token was already redeemed"); + super(ERROR_MSG_LONG); } } diff --git a/java/google/registry/flows/domain/DomainCheckFlow.java b/java/google/registry/flows/domain/DomainCheckFlow.java index 975e9d95a..f0e18881a 100644 --- a/java/google/registry/flows/domain/DomainCheckFlow.java +++ b/java/google/registry/flows/domain/DomainCheckFlow.java @@ -16,6 +16,7 @@ package google.registry.flows.domain; import static google.registry.flows.FlowUtils.validateClientIsLoggedIn; import static google.registry.flows.ResourceFlowUtils.verifyTargetIdCount; +import static google.registry.flows.domain.AllocationTokenFlowUtils.checkDomainsWithToken; import static google.registry.flows.domain.DomainFlowUtils.checkAllowedAccessToTld; import static google.registry.flows.domain.DomainFlowUtils.getReservationTypes; import static google.registry.flows.domain.DomainFlowUtils.handleFeeRequest; @@ -146,13 +147,21 @@ public final class DomainCheckFlow implements Flow { customLogic.afterValidation( DomainCheckFlowCustomLogic.AfterValidationParameters.newBuilder() .setDomainNames(domainNames) - // TODO: Use as of date from fee extension v0.12 instead of now, if specificed. + // TODO: Use as of date from fee extension v0.12 instead of now, if specified. .setAsOfDate(now) .build()); Set existingIds = checkResourcesExist(DomainResource.class, targetIds, now); + AllocationTokenExtension allocationTokenExtension = + eppInput.getSingleExtension(AllocationTokenExtension.class); + ImmutableMap tokenCheckResults = + (allocationTokenExtension == null) + ? ImmutableMap.of() + : checkDomainsWithToken( + targetIds, allocationTokenExtension.getAllocationToken(), clientId); ImmutableList.Builder checks = new ImmutableList.Builder<>(); for (String targetId : targetIds) { - Optional message = getMessageForCheck(domainNames.get(targetId), existingIds, now); + Optional message = + getMessageForCheck(domainNames.get(targetId), existingIds, tokenCheckResults, now); checks.add(DomainCheck.create(!message.isPresent(), targetId, message.orElse(null))); } BeforeResponseReturnData responseData = @@ -169,7 +178,10 @@ public final class DomainCheckFlow implements Flow { } private Optional getMessageForCheck( - InternetDomainName domainName, Set existingIds, DateTime now) { + InternetDomainName domainName, + Set existingIds, + ImmutableMap tokenCheckResults, + DateTime now) { if (existingIds.contains(domainName.toString())) { return Optional.of("In use"); } @@ -187,10 +199,12 @@ public final class DomainCheckFlow implements Flow { && eppInput.getSingleExtension(FeeCheckCommandExtension.class) == null) { return Optional.of("Premium names require EPP ext."); } - - return reservationTypes.isEmpty() - ? Optional.empty() - : Optional.of(getTypeOfHighestSeverity(reservationTypes).getMessageForCheck()); + if (!reservationTypes.isEmpty()) { + return Optional.of(getTypeOfHighestSeverity(reservationTypes).getMessageForCheck()); + } + return tokenCheckResults.containsKey(domainName.toString()) + ? Optional.of(tokenCheckResults.get(domainName.toString())) + : Optional.empty(); } /** Handle the fee check extension. */ diff --git a/java/google/registry/flows/domain/DomainClaimsCheckFlow.java b/java/google/registry/flows/domain/DomainClaimsCheckFlow.java index 098d88869..171dc07d2 100644 --- a/java/google/registry/flows/domain/DomainClaimsCheckFlow.java +++ b/java/google/registry/flows/domain/DomainClaimsCheckFlow.java @@ -39,6 +39,8 @@ import google.registry.model.domain.launch.LaunchCheckExtension; import google.registry.model.domain.launch.LaunchCheckResponseExtension; import google.registry.model.domain.launch.LaunchCheckResponseExtension.LaunchCheck; import google.registry.model.domain.launch.LaunchCheckResponseExtension.LaunchCheckName; +import google.registry.model.domain.token.AllocationTokenExtension; +import google.registry.model.eppinput.EppInput; import google.registry.model.eppinput.ResourceCommand; import google.registry.model.eppoutput.EppResponse; import google.registry.model.registry.Registry; @@ -61,11 +63,13 @@ import org.joda.time.DateTime; * @error {@link DomainFlowUtils.NotAuthorizedForTldException} * @error {@link DomainFlowUtils.TldDoesNotExistException} * @error {@link DomainClaimsCheckNotAllowedInSunrise} + * @error {@link DomainClaimsCheckNotAllowedWithAllocationTokens} */ @ReportingSpec(ActivityReportField.DOMAIN_CHECK) // Claims check is a special domain check. public final class DomainClaimsCheckFlow implements Flow { @Inject ExtensionManager extensionManager; + @Inject EppInput eppInput; @Inject ResourceCommand resourceCommand; @Inject @ClientId String clientId; @Inject @Superuser boolean isSuperuser; @@ -78,9 +82,12 @@ public final class DomainClaimsCheckFlow implements Flow { @Override public EppResponse run() throws EppException { - extensionManager.register(LaunchCheckExtension.class); + extensionManager.register(LaunchCheckExtension.class, AllocationTokenExtension.class); extensionManager.validate(); validateClientIsLoggedIn(clientId); + if (eppInput.getSingleExtension(AllocationTokenExtension.class) != null) { + throw new DomainClaimsCheckNotAllowedWithAllocationTokens(); + } List targetIds = ((Check) resourceCommand).getTargetIds(); verifyTargetIdCount(targetIds, maxChecks); Set seenTlds = new HashSet<>(); @@ -118,4 +125,11 @@ public final class DomainClaimsCheckFlow implements Flow { super("Claims checks are not allowed during sunrise"); } } + + /** Claims checks are not allowed with allocation tokens. */ + static class DomainClaimsCheckNotAllowedWithAllocationTokens extends CommandUseErrorException { + public DomainClaimsCheckNotAllowedWithAllocationTokens() { + super("Claims checks are not allowed with allocation tokens"); + } + } } diff --git a/javatests/google/registry/flows/ResourceCheckFlowTestCase.java b/javatests/google/registry/flows/ResourceCheckFlowTestCase.java index 309fc4b33..a40341b7a 100644 --- a/javatests/google/registry/flows/ResourceCheckFlowTestCase.java +++ b/javatests/google/registry/flows/ResourceCheckFlowTestCase.java @@ -31,8 +31,8 @@ public class ResourceCheckFlowTestCase protected void doCheckTest(CheckData.Check... expected) throws Exception { assertTransactionalFlow(false); - assertThat(expected).asList().containsExactlyElementsIn( - ((CheckData) runFlow().getResponse().getResponseData().get(0)).getChecks()); + assertThat(((CheckData) runFlow().getResponse().getResponseData().get(0)).getChecks()) + .containsExactlyElementsIn(expected); assertNoHistory(); // Checks don't create a history event. assertNoBillingEvents(); // Checks are always free. } diff --git a/javatests/google/registry/flows/domain/DomainCheckFlowTest.java b/javatests/google/registry/flows/domain/DomainCheckFlowTest.java index 0b9deb378..143b10eee 100644 --- a/javatests/google/registry/flows/domain/DomainCheckFlowTest.java +++ b/javatests/google/registry/flows/domain/DomainCheckFlowTest.java @@ -34,6 +34,7 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedMap; import com.google.common.collect.Ordering; +import com.googlecode.objectify.Key; import google.registry.flows.EppException; import google.registry.flows.ResourceCheckFlowTestCase; import google.registry.flows.domain.DomainCheckFlow.OnlyCheckedNamesCanBeFeeCheckedException; @@ -57,12 +58,14 @@ import google.registry.flows.domain.DomainFlowUtils.TrailingDashException; import google.registry.flows.domain.DomainFlowUtils.TransfersAreAlwaysForOneYearException; import google.registry.flows.domain.DomainFlowUtils.UnknownFeeCommandException; import google.registry.flows.exceptions.TooManyResourceChecksException; +import google.registry.model.domain.AllocationToken; import google.registry.model.domain.DomainResource; import google.registry.model.domain.launch.ApplicationStatus; import google.registry.model.domain.launch.LaunchPhase; import google.registry.model.registry.Registry; import google.registry.model.registry.Registry.TldState; import google.registry.model.registry.label.ReservedList; +import google.registry.model.reporting.HistoryEntry; import org.joda.money.CurrencyUnit; import org.joda.money.Money; import org.joda.time.DateTime; @@ -112,14 +115,52 @@ public class DomainCheckFlowTest } @Test - public void testSuccess_oneExists_allocationTokenDoesNothing() throws Exception { - // TODO(b/70628322): Change this test to fail on invalid allocationToken. + public void testSuccess_oneExists_allocationTokenIsInvalid() throws Exception { setEppInput("domain_check_allocationtoken.xml"); persistActiveDomain("example1.tld"); + doCheckTest( + create(false, "example1.tld", "In use"), + create(false, "example2.tld", "The allocation token is invalid"), + create(false, "reserved.tld", "Reserved")); + } + + @Test + public void testSuccess_oneExists_allocationTokenIsValid() throws Exception { + setEppInput("domain_check_allocationtoken.xml"); + persistActiveDomain("example1.tld"); + persistResource(new AllocationToken.Builder().setToken("abc123").build()); doCheckTest( create(false, "example1.tld", "In use"), create(true, "example2.tld", null), - create(true, "example3.tld", null)); + create(false, "reserved.tld", "Reserved")); + } + + @Test + public void testSuccess_oneExists_allocationTokenIsRedeemed() throws Exception { + setEppInput("domain_check_allocationtoken.xml"); + persistActiveDomain("example1.tld"); + persistResource( + new AllocationToken.Builder() + .setToken("abc123") + .setRedemptionHistoryEntry(Key.create(HistoryEntry.class, 1L)) + .build()); + doCheckTest( + create(false, "example1.tld", "In use"), + create(false, "example2.tld", "Alloc token was already redeemed"), + create(false, "reserved.tld", "Reserved")); + } + + @Test + public void testSuccess_nothingExists_reservationsOverrideInvalidAllocationTokens() + throws Exception { + setEppInput("domain_check_reserved_allocationtoken.xml"); + // Fill out these reasons + doCheckTest( + create(false, "collision.tld", "Cannot be delegated"), + create(false, "reserved.tld", "Reserved"), + create(false, "anchor.tld", "Reserved"), + create(false, "allowedinsunrise.tld", "Reserved for non-sunrise"), + create(false, "premiumcollision.tld", "Cannot be delegated")); } @Test @@ -202,6 +243,12 @@ public class DomainCheckFlowTest runFlow(); } + @Test + public void testSuccess_50IdsAllowed_withAllocationToken() throws Exception { + setEppInput("domain_check_50_allocationtoken.xml"); + runFlow(); + } + @Test public void testSuccess_pendingSunriseApplicationInGeneralAvailability() throws Exception { createTld("tld", TldState.GENERAL_AVAILABILITY); diff --git a/javatests/google/registry/flows/domain/DomainClaimsCheckFlowTest.java b/javatests/google/registry/flows/domain/DomainClaimsCheckFlowTest.java index 577c0ba12..43d484540 100644 --- a/javatests/google/registry/flows/domain/DomainClaimsCheckFlowTest.java +++ b/javatests/google/registry/flows/domain/DomainClaimsCheckFlowTest.java @@ -16,6 +16,7 @@ package google.registry.flows.domain; import static google.registry.testing.DatastoreHelper.assertNoBillingEvents; import static google.registry.testing.DatastoreHelper.createTld; +import static google.registry.testing.DatastoreHelper.createTlds; import static google.registry.testing.DatastoreHelper.loadRegistrar; import static google.registry.testing.DatastoreHelper.persistResource; import static google.registry.testing.EppExceptionSubject.assertAboutEppExceptions; @@ -26,6 +27,7 @@ import com.google.common.collect.ImmutableSet; import google.registry.flows.EppException; import google.registry.flows.ResourceFlowTestCase; import google.registry.flows.domain.DomainClaimsCheckFlow.DomainClaimsCheckNotAllowedInSunrise; +import google.registry.flows.domain.DomainClaimsCheckFlow.DomainClaimsCheckNotAllowedWithAllocationTokens; import google.registry.flows.domain.DomainFlowUtils.BadCommandForRegistryPhaseException; import google.registry.flows.domain.DomainFlowUtils.ClaimsPeriodEndedException; import google.registry.flows.domain.DomainFlowUtils.NotAuthorizedForTldException; @@ -48,7 +50,6 @@ public class DomainClaimsCheckFlowTest @Before public void initCheckTest() { createTld("tld"); - persistResource(Registry.get("tld").asBuilder().build()); } protected void doSuccessfulTest(String expectedXmlFilename) throws Exception { @@ -66,14 +67,12 @@ public class DomainClaimsCheckFlowTest @Test public void testSuccess_sunrush() throws Exception { createTld("tld", TldState.SUNRUSH); - persistResource(Registry.get("tld").asBuilder().build()); doSuccessfulTest("domain_check_claims_response_none.xml"); } @Test public void testSuccess_quietPeriod() throws Exception { createTld("tld", TldState.QUIET_PERIOD); - persistResource(Registry.get("tld").asBuilder().build()); doSuccessfulTest("domain_check_claims_response_none.xml"); } @@ -139,7 +138,6 @@ public class DomainClaimsCheckFlowTest @Test public void testFailure_predelgation() throws Exception { createTld("tld", TldState.PREDELEGATION); - persistResource(Registry.get("tld").asBuilder().build()); setEppInput("domain_check_claims.xml"); EppException thrown = expectThrows(BadCommandForRegistryPhaseException.class, this::runFlow); assertAboutEppExceptions().that(thrown).marshalsToXml(); @@ -148,16 +146,23 @@ public class DomainClaimsCheckFlowTest @Test public void testFailure_sunrise() throws Exception { createTld("tld", TldState.SUNRISE); - persistResource(Registry.get("tld").asBuilder().build()); setEppInput("domain_check_claims.xml"); EppException thrown = expectThrows(DomainClaimsCheckNotAllowedInSunrise.class, this::runFlow); assertAboutEppExceptions().that(thrown).marshalsToXml(); } + @Test + public void testFailure_allocationToken() throws Exception { + createTld("tld", TldState.SUNRISE); + setEppInput("domain_check_claims_allocationtoken.xml"); + EppException thrown = + expectThrows(DomainClaimsCheckNotAllowedWithAllocationTokens.class, this::runFlow); + assertAboutEppExceptions().that(thrown).marshalsToXml(); + } + @Test public void testFailure_multipleTlds_oneHasEndedClaims() throws Exception { - createTld("tld1"); - createTld("tld2"); + createTlds("tld1", "tld2"); persistResource( Registry.get("tld2").asBuilder().setClaimsPeriodEnd(clock.nowUtc().minusMillis(1)).build()); setEppInput("domain_check_claims_multiple_tlds.xml"); diff --git a/javatests/google/registry/flows/domain/testdata/domain_check_50_allocationtoken.xml b/javatests/google/registry/flows/domain/testdata/domain_check_50_allocationtoken.xml new file mode 100644 index 000000000..40d51f5d5 --- /dev/null +++ b/javatests/google/registry/flows/domain/testdata/domain_check_50_allocationtoken.xml @@ -0,0 +1,66 @@ + + + + + www1.tld + www2.tld + www3.tld + www4.tld + www5.tld + www6.tld + www7.tld + www8.tld + www9.tld + www10.tld + www11.tld + www12.tld + www13.tld + www14.tld + www15.tld + www16.tld + www17.tld + www18.tld + www19.tld + www20.tld + www21.tld + www22.tld + www23.tld + www24.tld + www25.tld + www26.tld + www27.tld + www28.tld + www29.tld + www30.tld + www31.tld + www32.tld + www33.tld + www34.tld + www35.tld + www36.tld + www37.tld + www38.tld + www39.tld + www40.tld + www41.tld + www42.tld + www43.tld + www44.tld + www45.tld + www46.tld + www47.tld + www48.tld + www49.tld + www50.tld + + + + + abc123 + + + ABC-12345 + + diff --git a/javatests/google/registry/flows/domain/testdata/domain_check_allocationtoken.xml b/javatests/google/registry/flows/domain/testdata/domain_check_allocationtoken.xml index 644cac864..0ef949ce7 100644 --- a/javatests/google/registry/flows/domain/testdata/domain_check_allocationtoken.xml +++ b/javatests/google/registry/flows/domain/testdata/domain_check_allocationtoken.xml @@ -2,10 +2,10 @@ + xmlns:domain="urn:ietf:params:xml:ns:domain-1.0"> example1.tld example2.tld - example3.tld + reserved.tld diff --git a/javatests/google/registry/flows/domain/testdata/domain_check_claims.xml b/javatests/google/registry/flows/domain/testdata/domain_check_claims.xml index 8dace8c1e..ebcda5bb3 100644 --- a/javatests/google/registry/flows/domain/testdata/domain_check_claims.xml +++ b/javatests/google/registry/flows/domain/testdata/domain_check_claims.xml @@ -1,20 +1,20 @@ - - - example1.tld - example2.tld - - - - - claims - - - ABC-12345 + + + example1.tld + example2.tld + + + + + claims + + + ABC-12345 diff --git a/javatests/google/registry/flows/domain/testdata/domain_check_claims_allocationtoken.xml b/javatests/google/registry/flows/domain/testdata/domain_check_claims_allocationtoken.xml new file mode 100644 index 000000000..b047dba06 --- /dev/null +++ b/javatests/google/registry/flows/domain/testdata/domain_check_claims_allocationtoken.xml @@ -0,0 +1,25 @@ + + + + + + example1.tld + example2.tld + + + + + claims + + + abc123 + + + ABC-12345 + + diff --git a/javatests/google/registry/flows/domain/testdata/domain_check_reserved_allocationtoken.xml b/javatests/google/registry/flows/domain/testdata/domain_check_reserved_allocationtoken.xml new file mode 100644 index 000000000..f06bfa989 --- /dev/null +++ b/javatests/google/registry/flows/domain/testdata/domain_check_reserved_allocationtoken.xml @@ -0,0 +1,22 @@ + + + + + collision.tld + reserved.tld + anchor.tld + allowedinsunrise.tld + premiumcollision.tld + + + + + abc123 + + + ABC-12345 + +