From d2f849ac0f7255d256528bfde09ba436a9c5580e Mon Sep 17 00:00:00 2001 From: mcilwain Date: Thu, 16 Aug 2018 11:41:34 -0700 Subject: [PATCH] Add new reserved domain creation from allocation tokens mechanism Note that this gets rid of anchor tenant codes in reserved lists (yay!), which are no longer valid. They have to come from allocation tokens now. This removes support for LRP from domain application create flow (that's fine, we never used it and I'm going to delete all of LRP later). It also uses allocation tokens from EPP authcodes as a fallback, for now, but that will be removed later once we switch fully to the allocation token mechanism. This doesn't yet allow registration of RESERVED_FOR_SPECIFIC_USE domains using the allocation token extension; that will come in the next CL. Ditto for showing these reserved domains as available on domain checks when the allocation token is specified. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=209019617 --- .../reserved-list-management.md | 26 +-- .../flows/domain/DomainAllocateFlow.java | 13 -- .../domain/DomainApplicationCreateFlow.java | 4 +- .../flows/domain/DomainCreateFlow.java | 22 ++- .../flows/domain/DomainFlowUtils.java | 42 ++++- .../model/domain/token/AllocationToken.java | 4 +- .../registry/model/registry/Registry.java | 32 ---- .../model/registry/label/ReservationType.java | 30 +++- .../model/registry/label/ReservedList.java | 70 ++------- .../flows/domain/DomainCheckFlowTest.java | 4 +- .../flows/domain/DomainCreateFlowTest.java | 45 ++++-- .../domain_create_allocationtoken.xml | 2 +- .../domain_create_anchor_allocationtoken.xml | 29 ++++ .../domain_create_anchor_authcode.xml | 2 +- ...n_create_anchor_authcode_invalid_years.xml | 7 + .../domain/token/AllocationTokenTest.java | 8 +- .../registry/model/registry/RegistryTest.java | 40 ----- .../registry/label/ReservedListTest.java | 148 +----------------- .../google/registry/model/testdata/schema.txt | 2 +- 19 files changed, 184 insertions(+), 346 deletions(-) create mode 100644 javatests/google/registry/flows/domain/testdata/domain_create_anchor_allocationtoken.xml diff --git a/docs/operational-procedures/reserved-list-management.md b/docs/operational-procedures/reserved-list-management.md index 53a2b11d1..e78cddcf7 100644 --- a/docs/operational-procedures/reserved-list-management.md +++ b/docs/operational-procedures/reserved-list-management.md @@ -23,12 +23,18 @@ a price, it has a reservation type. The valid values for reservation types are: period by a registrant with a valid claim but it is reserved thereafter. * **`MISTAKEN_PREMIUM`** - The label is reserved because it was mistakenly put on a premium list. It may be registered during sunrise by a registrant with - a valid claim but is reserved thereafter. -* **`RESERVED_FOR_ANCHOR_TENANT`** - The label is reserved for the use of an - anchor tenant, and can only be registered by someone sending along the EPP - passcode specified here at time of registration. If a label has different - passcodes in different lists that are applied to the same TLD, an error will - occur. + a valid claim but is reserved thereafter. This is deprecated. +* **RESERVED_FOR_SPECIFIC_USE** - The label is reserved for the use of a + specific registrant, and can only be registered by someone sending along the + allocation token at time of registration. This token is configured on an + `AllocationToken` entity with a matching `domainName`, and is sent by the + registrar using the [allocation token EPP + extension](https://tools.ietf.org/id/draft-ietf-regext-allocation-token-07.html). +* **`RESERVED_FOR_ANCHOR_TENANT`** - Like `RESERVED_FOR_SPECIFIC_USE`, except + for an anchor tenant (i.e. a registrant participating in a [Qualified Launch + Program](https://newgtlds.icann.org/en/announcements-and-media/announcement-10apr14-en)), + meaning that registrations can occur during sunrise ahead of GA, and must be + for a two year term. * **`NAME_COLLISION`** - The label is reserved because it is on an [ICANN collision list](https://www.icann.org/resources/pages/name-collision-2013-12-06-en). @@ -48,17 +54,15 @@ label is reserved due to name collision (with message "Cannot be delegated"). In general `FULLY_BLOCKED` is by far the most widely used reservation type for typical TLD use cases. -Here's an example of a small reserved list. Note that -`RESERVED_FOR_ANCHOR_TENANT` has a third entry on the line, being the EPP -passcode required to register the domain (`hunter2` in this case); and that -`NAMESERVER_RESERVED` also has a third entry, a colon separated list of +Here's an example of a small reserved list. Note that the +`NAMESERVER_RESTRICTED` label has a third entry, a colon separated list of nameservers that the label can be delegated to: ``` reserveddomain,FULLY_BLOCKED availableinga,ALLOWED_IN_SUNRISE fourletterword,FULLY_BLOCKED -acmecorp,RESERVED_FOR_ANCHOR_TENANT,hunter2 +acmecorp,RESERVED_FOR_ANCHOR_TENANT internaldomain,NAMESERVER_RESTRICTED,ns1.internal.tld:ns1.internal.tld ``` diff --git a/java/google/registry/flows/domain/DomainAllocateFlow.java b/java/google/registry/flows/domain/DomainAllocateFlow.java index 050b24203..c0e5a9b6e 100644 --- a/java/google/registry/flows/domain/DomainAllocateFlow.java +++ b/java/google/registry/flows/domain/DomainAllocateFlow.java @@ -21,7 +21,6 @@ import static google.registry.flows.domain.DomainFlowUtils.COLLISION_MESSAGE; import static google.registry.flows.domain.DomainFlowUtils.cloneAndLinkReferences; import static google.registry.flows.domain.DomainFlowUtils.createFeeCreateResponse; import static google.registry.flows.domain.DomainFlowUtils.getReservationTypes; -import static google.registry.flows.domain.DomainFlowUtils.prepareMarkedLrpTokenEntity; import static google.registry.flows.domain.DomainFlowUtils.validateCreateCommandContactsAndNameservers; import static google.registry.flows.domain.DomainFlowUtils.validateDomainName; import static google.registry.flows.domain.DomainFlowUtils.validateDomainNameWithIdnTables; @@ -31,7 +30,6 @@ import static google.registry.flows.domain.DomainFlowUtils.verifyUnitIsYears; import static google.registry.model.EppResourceUtils.createDomainRepoId; import static google.registry.model.EppResourceUtils.loadDomainApplication; import static google.registry.model.ofy.ObjectifyService.ofy; -import static google.registry.model.registry.label.ReservedList.matchesAnchorTenantReservation; import static google.registry.pricing.PricingEngineProxy.getDomainCreateCost; import static google.registry.util.CollectionUtils.isNullOrEmpty; import static google.registry.util.DateTimeUtils.END_OF_TIME; @@ -200,11 +198,6 @@ public class DomainAllocateFlow implements TransactionalFlow { updateApplication(application), ForeignKeyIndex.create(newDomain, newDomain.getDeletionTime()), EppResourceIndex.create(Key.create(newDomain))); - // Anchor tenant registrations override LRP. - String authInfoToken = authInfo.getPw().getValue(); - if (hasLrpToken(domainName, registry, authInfoToken, now)) { - entitiesToSave.add(prepareMarkedLrpTokenEntity(authInfoToken, domainName, historyEntry)); - } ofy().save().entities(entitiesToSave.build()); enqueueTasks(allocateCreate, newDomain); return responseBuilder @@ -387,12 +380,6 @@ public class DomainAllocateFlow implements TransactionalFlow { .build(); } - private boolean hasLrpToken( - InternetDomainName domainName, Registry registry, String authInfoToken, DateTime now) { - return registry.getLrpPeriod().contains(now) - && !matchesAnchorTenantReservation(domainName, authInfoToken); - } - private void enqueueTasks(AllocateCreateExtension allocateCreate, DomainResource newDomain) { if (newDomain.shouldPublishToDns()) { dnsQueue.get().addDomainRefreshTask(newDomain.getFullyQualifiedDomainName()); diff --git a/java/google/registry/flows/domain/DomainApplicationCreateFlow.java b/java/google/registry/flows/domain/DomainApplicationCreateFlow.java index 3139d0325..c2eddc5ca 100644 --- a/java/google/registry/flows/domain/DomainApplicationCreateFlow.java +++ b/java/google/registry/flows/domain/DomainApplicationCreateFlow.java @@ -22,6 +22,7 @@ import static google.registry.flows.ResourceFlowUtils.verifyResourceDoesNotExist import static google.registry.flows.domain.DomainFlowUtils.checkAllowedAccessToTld; import static google.registry.flows.domain.DomainFlowUtils.cloneAndLinkReferences; import static google.registry.flows.domain.DomainFlowUtils.createFeeCreateResponse; +import static google.registry.flows.domain.DomainFlowUtils.isAnchorTenant; import static google.registry.flows.domain.DomainFlowUtils.prepareMarkedLrpTokenEntity; import static google.registry.flows.domain.DomainFlowUtils.validateCreateCommandContactsAndNameservers; import static google.registry.flows.domain.DomainFlowUtils.validateDomainName; @@ -42,7 +43,6 @@ import static google.registry.flows.domain.DomainFlowUtils.verifyUnitIsYears; import static google.registry.model.EppResourceUtils.createDomainRepoId; import static google.registry.model.index.DomainApplicationIndex.loadActiveApplicationsByDomainName; import static google.registry.model.ofy.ObjectifyService.ofy; -import static google.registry.model.registry.label.ReservedList.matchesAnchorTenantReservation; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -223,7 +223,7 @@ public final class DomainApplicationCreateFlow implements TransactionalFlow { eppInput.getSingleExtension(LaunchCreateExtension.class).get(); validateLaunchCreateExtension(launchCreate, registry, domainName, now); boolean isAnchorTenant = - matchesAnchorTenantReservation(domainName, authInfo.getPw().getValue()); + isAnchorTenant(domainName, Optional.empty(), authInfo.getPw().getValue(), Optional.empty()); // Superusers can create reserved domains, force creations on domains that require a claims // notice without specifying a claims key, and override blocks on registering premium domains. if (!isSuperuser) { diff --git a/java/google/registry/flows/domain/DomainCreateFlow.java b/java/google/registry/flows/domain/DomainCreateFlow.java index 58b927ade..93d251dbe 100644 --- a/java/google/registry/flows/domain/DomainCreateFlow.java +++ b/java/google/registry/flows/domain/DomainCreateFlow.java @@ -22,6 +22,7 @@ import static google.registry.flows.domain.DomainFlowUtils.checkAllowedAccessToT import static google.registry.flows.domain.DomainFlowUtils.cloneAndLinkReferences; import static google.registry.flows.domain.DomainFlowUtils.createFeeCreateResponse; import static google.registry.flows.domain.DomainFlowUtils.getReservationTypes; +import static google.registry.flows.domain.DomainFlowUtils.isAnchorTenant; import static google.registry.flows.domain.DomainFlowUtils.prepareMarkedLrpTokenEntity; import static google.registry.flows.domain.DomainFlowUtils.validateCreateCommandContactsAndNameservers; import static google.registry.flows.domain.DomainFlowUtils.validateDomainAllowedOnCreateRestrictedTld; @@ -50,7 +51,6 @@ import static google.registry.model.registry.Registry.TldState.START_DATE_SUNRIS import static google.registry.model.registry.Registry.TldState.SUNRISE; import static google.registry.model.registry.Registry.TldState.SUNRUSH; import static google.registry.model.registry.label.ReservationType.NAME_COLLISION; -import static google.registry.model.registry.label.ReservedList.matchesAnchorTenantReservation; import static google.registry.util.DateTimeUtils.END_OF_TIME; import static google.registry.util.DateTimeUtils.leapSafeAddYears; @@ -251,8 +251,6 @@ public class DomainCreateFlow implements TransactionalFlow { validateDomainAllowedOnCreateRestrictedTld(domainName); } TldState tldState = registry.getTldState(now); - boolean isAnchorTenant = isAnchorTenant(domainName); - verifyAnchorTenantValidPeriod(isAnchorTenant, years); Optional launchCreate = eppInput.getSingleExtension(LaunchCreateExtension.class); boolean hasSignedMarks = @@ -263,6 +261,15 @@ public class DomainCreateFlow implements TransactionalFlow { validateLaunchCreateNotice(launchCreate.get().getNotice(), domainLabel, isSuperuser, now); } boolean isSunriseCreate = hasSignedMarks && SUNRISE_STATES.contains(tldState); + Optional allocationToken = + verifyAllocationTokenIfPresent(command, registry, clientId, now); + boolean isAnchorTenant = + isAnchorTenant( + domainName, + allocationToken, + authInfo.getPw().getValue(), + eppInput.getSingleExtension(MetadataExtension.class)); + verifyAnchorTenantValidPeriod(isAnchorTenant, years); // Superusers can create reserved domains, force creations on domains that require a claims // notice without specifying a claims key, ignore the registry phase, and override blocks on // registering premium domains. @@ -294,8 +301,6 @@ public class DomainCreateFlow implements TransactionalFlow { .verifySignedMarks(launchCreate.get().getSignedMarks(), domainLabel, now) .getId(); } - Optional allocationToken = - verifyAllocationTokenIfPresent(command, registry, clientId, now); flowCustomLogic.afterValidation( DomainCreateFlowCustomLogic.AfterValidationParameters.newBuilder() .setDomainName(domainName) @@ -400,13 +405,6 @@ public class DomainCreateFlow implements TransactionalFlow { .build(); } - private boolean isAnchorTenant(InternetDomainName domainName) { - Optional metadataExtension = - eppInput.getSingleExtension(MetadataExtension.class); - return matchesAnchorTenantReservation(domainName, authInfo.getPw().getValue()) - || (metadataExtension.isPresent() && metadataExtension.get().getIsAnchorTenant()); - } - /** * Verifies that signed marks are only sent during sunrise. * diff --git a/java/google/registry/flows/domain/DomainFlowUtils.java b/java/google/registry/flows/domain/DomainFlowUtils.java index 2c57be8dd..5db8b9ec8 100644 --- a/java/google/registry/flows/domain/DomainFlowUtils.java +++ b/java/google/registry/flows/domain/DomainFlowUtils.java @@ -30,6 +30,7 @@ import static google.registry.model.registry.Registries.getTlds; import static google.registry.model.registry.label.ReservationType.FULLY_BLOCKED; import static google.registry.model.registry.label.ReservationType.NAMESERVER_RESTRICTED; import static google.registry.model.registry.label.ReservationType.RESERVED_FOR_ANCHOR_TENANT; +import static google.registry.model.registry.label.ReservationType.RESERVED_FOR_SPECIFIC_USE; import static google.registry.model.registry.label.ReservedList.getAllowedNameservers; import static google.registry.pricing.PricingEngineProxy.isDomainPremium; import static google.registry.tldconfig.idn.IdnLabelValidator.findValidIdnTableForTld; @@ -93,12 +94,14 @@ import google.registry.model.domain.launch.LaunchExtension; import google.registry.model.domain.launch.LaunchNotice; import google.registry.model.domain.launch.LaunchNotice.InvalidChecksumException; import google.registry.model.domain.launch.LaunchPhase; +import google.registry.model.domain.metadata.MetadataExtension; import google.registry.model.domain.secdns.DelegationSignerData; import google.registry.model.domain.secdns.SecDnsCreateExtension; import google.registry.model.domain.secdns.SecDnsInfoExtension; import google.registry.model.domain.secdns.SecDnsUpdateExtension; import google.registry.model.domain.secdns.SecDnsUpdateExtension.Add; import google.registry.model.domain.secdns.SecDnsUpdateExtension.Remove; +import google.registry.model.domain.token.AllocationToken; import google.registry.model.eppcommon.StatusValue; import google.registry.model.eppoutput.EppResponse.ResponseExtension; import google.registry.model.host.HostResource; @@ -142,7 +145,7 @@ public class DomainFlowUtils { .put(LaunchPhase.OPEN, TldState.GENERAL_AVAILABILITY) .build(); - /** Reservation types that are allowed in sunrise by policy. */ + /** Reservation types that are only allowed in sunrise by policy. */ public static final ImmutableSet TYPES_ALLOWED_FOR_CREATE_ONLY_IN_SUNRISE = Sets.immutableEnumSet( ReservationType.ALLOWED_IN_SUNRISE, @@ -247,6 +250,37 @@ public class DomainFlowUtils { return idnTableName.get(); } + /** + * Returns whether the information for a given domain create request is for a valid anchor tenant. + */ + public static boolean isAnchorTenant( + InternetDomainName domainName, + Optional token, + String authInfoPw, + Optional metadataExtension) { + // If the domain is reserved for anchor tenants, then check if the allocation token exists and + // is for this domain. + if (getReservationTypes(domainName).contains(RESERVED_FOR_ANCHOR_TENANT)) { + // If there wasn't an allocation token specified, then use the fallback of attempting to load + // the token with the specified EPP authcode. + // TODO(b/111827374): Remove the authInfoPw fallback and only accept an allocation token. + if (!token.isPresent()) { + token = + Optional.ofNullable( + ofy().load().key(Key.create(AllocationToken.class, authInfoPw)).now()); + } + // If the token exists, check if it's valid for this domain. + if (token.isPresent() + && token.get().getDomainName().isPresent() + && token.get().getDomainName().get().equals(domainName.toString())) { + return true; + } + } + // Otherwise check whether the metadata extension is being used by a superuser to specify that + // it's an anchor tenant creation. + return metadataExtension.isPresent() && metadataExtension.get().getIsAnchorTenant(); + } + /** Check if the registrar running the flow has access to the TLD in question. */ public static void checkAllowedAccessToTld(String clientId, String tld) throws EppException { if (!Registrar.loadByClientIdCached(clientId).get().getAllowedTlds().contains(tld)) { @@ -411,10 +445,12 @@ public class DomainFlowUtils { } } + private static final ImmutableSet RESERVED_TYPES = + ImmutableSet.of(RESERVED_FOR_SPECIFIC_USE, RESERVED_FOR_ANCHOR_TENANT, FULLY_BLOCKED); + private static boolean isReserved(InternetDomainName domainName, boolean isSunrise) { ImmutableSet types = getReservationTypes(domainName); - return types.contains(FULLY_BLOCKED) - || types.contains(RESERVED_FOR_ANCHOR_TENANT) + return !Sets.intersection(types, RESERVED_TYPES).isEmpty() || !(isSunrise || intersection(TYPES_ALLOWED_FOR_CREATE_ONLY_IN_SUNRISE, types).isEmpty()); } diff --git a/java/google/registry/model/domain/token/AllocationToken.java b/java/google/registry/model/domain/token/AllocationToken.java index 806b9cf02..aa783b918 100644 --- a/java/google/registry/model/domain/token/AllocationToken.java +++ b/java/google/registry/model/domain/token/AllocationToken.java @@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import static google.registry.util.PreconditionsUtils.checkArgumentNotNull; +import com.google.common.annotations.VisibleForTesting; import com.googlecode.objectify.Key; import com.googlecode.objectify.annotation.Entity; import com.googlecode.objectify.annotation.Id; @@ -100,7 +101,8 @@ public class AllocationToken extends BackupGroupRoot implements Buildable { return this; } - public Builder setCreationTime(DateTime creationTime) { + @VisibleForTesting + public Builder setCreationTimeForTest(DateTime creationTime) { checkState( getInstance().creationTime.getTimestamp() == null, "creationTime can only be set once"); getInstance().creationTime = CreateAutoTimestamp.create(creationTime); diff --git a/java/google/registry/model/registry/Registry.java b/java/google/registry/model/registry/Registry.java index 0a4bd7073..297bb4fd8 100644 --- a/java/google/registry/model/registry/Registry.java +++ b/java/google/registry/model/registry/Registry.java @@ -18,7 +18,6 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Predicates.equalTo; import static com.google.common.base.Predicates.not; -import static com.google.common.collect.ImmutableSet.toImmutableSet; import static google.registry.config.RegistryConfig.getSingletonCacheRefreshDuration; import static google.registry.model.common.EntityGroupRoot.getCrossTldKey; import static google.registry.model.ofy.ObjectifyService.ofy; @@ -36,8 +35,6 @@ import com.google.common.cache.LoadingCache; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedMap; import com.google.common.collect.Iterables; -import com.google.common.collect.LinkedHashMultimap; -import com.google.common.collect.Multimap; import com.google.common.collect.Ordering; import com.google.common.collect.Range; import com.google.common.net.InternetDomainName; @@ -61,10 +58,7 @@ import google.registry.model.domain.fee.Fee; import google.registry.model.registry.label.PremiumList; import google.registry.model.registry.label.ReservationType; import google.registry.model.registry.label.ReservedList; -import google.registry.model.registry.label.ReservedList.ReservedListEntry; import google.registry.util.Idn; -import java.util.Collection; -import java.util.Map.Entry; import java.util.Optional; import java.util.Set; import java.util.function.Predicate; @@ -837,7 +831,6 @@ public class Registry extends ImmutableObject implements Buildable { public Builder setReservedLists(Set reservedLists) { checkArgumentNotNull(reservedLists, "reservedLists must not be null"); - checkAuthCodeConflicts(reservedLists); ImmutableSet.Builder> builder = new ImmutableSet.Builder<>(); for (ReservedList reservedList : reservedLists) { builder.add(Key.create(reservedList)); @@ -846,31 +839,6 @@ public class Registry extends ImmutableObject implements Buildable { return this; } - /** - * Checks that domain names don't have conflicting auth codes across different reserved lists. - */ - private static void checkAuthCodeConflicts(Set reservedLists) { - Multimap allAuthCodes = LinkedHashMultimap.create(); - for (ReservedList list : reservedLists) { - for (ReservedListEntry entry : list.getReservedListEntries().values()) { - if (entry.getAuthCode() != null) { - allAuthCodes.put(entry.getLabel(), entry.getAuthCode()); - } - } - } - ImmutableSet>> conflicts = - allAuthCodes - .asMap() - .entrySet() - .stream() - .filter((Entry> entry) -> entry.getValue().size() > 1) - .collect(toImmutableSet()); - checkArgument( - conflicts.isEmpty(), - "Cannot set reserved lists because of auth code conflicts for labels: %s", - conflicts); - } - public Builder setPremiumList(PremiumList premiumList) { getInstance().premiumList = (premiumList == null) ? null : Key.create(premiumList); return this; diff --git a/java/google/registry/model/registry/label/ReservationType.java b/java/google/registry/model/registry/label/ReservationType.java index 870394be0..f8597de89 100644 --- a/java/google/registry/model/registry/label/ReservationType.java +++ b/java/google/registry/model/registry/label/ReservationType.java @@ -22,7 +22,11 @@ import com.google.common.collect.Ordering; import java.util.Set; import javax.annotation.Nullable; -/** Enum describing reservation on a label in a {@link ReservedList} */ +/** + * Enum describing reservation on a label in a {@link ReservedList}. + * + *

Note that superusers can override reservations and register a domain no matter what. + */ public enum ReservationType { // We explicitly set the severity, even though we have a checkState that makes it equal to the @@ -30,12 +34,30 @@ public enum ReservationType { // label has multiple reservation types, its message is the that of the one with the highest // severity. + /** Nameservers on the domain are restricted to a given set. */ NAMESERVER_RESTRICTED("Nameserver restricted", 0), + + /** The domain can only be registered during sunrise, and is reserved thereafter. */ ALLOWED_IN_SUNRISE("Reserved for non-sunrise", 1), + + /** The domain can only be registered during sunrise, and is reserved thereafter. */ + @Deprecated MISTAKEN_PREMIUM("Reserved", 2), - RESERVED_FOR_ANCHOR_TENANT("Reserved", 3), - NAME_COLLISION("Cannot be delegated", 4), - FULLY_BLOCKED("Reserved", 5); + + /** The domain can only be registered by providing a specific token. */ + RESERVED_FOR_SPECIFIC_USE("Reserved", 3), + + /** The domain is for an anchor tenant and can only be registered using a specific token. */ + RESERVED_FOR_ANCHOR_TENANT("Reserved", 4), + + /** + * The domain can only be registered during sunrise for defensive purposes, and will never + * resolve. + */ + NAME_COLLISION("Cannot be delegated", 5), + + /** The domain can never be registered. */ + FULLY_BLOCKED("Reserved", 6); @Nullable private final String messageForCheck; diff --git a/java/google/registry/model/registry/label/ReservedList.java b/java/google/registry/model/registry/label/ReservedList.java index c08b0aa96..6fe746023 100644 --- a/java/google/registry/model/registry/label/ReservedList.java +++ b/java/google/registry/model/registry/label/ReservedList.java @@ -16,15 +16,12 @@ package google.registry.model.registry.label; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.ImmutableSet.toImmutableSet; -import static com.google.common.collect.Iterables.getOnlyElement; import static google.registry.config.RegistryConfig.getDomainLabelListCacheDuration; import static google.registry.model.common.EntityGroupRoot.getCrossTldKey; import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.model.registry.label.ReservationType.FULLY_BLOCKED; import static google.registry.model.registry.label.ReservationType.NAMESERVER_RESTRICTED; -import static google.registry.model.registry.label.ReservationType.RESERVED_FOR_ANCHOR_TENANT; import static google.registry.util.CollectionUtils.nullToEmpty; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.joda.time.DateTimeZone.UTC; @@ -77,12 +74,6 @@ public final class ReservedList ReservationType reservationType; - /** - * Contains the auth code necessary to register a domain with this label. Note that this field - * will only ever be populated for entries with type RESERVED_FOR_ANCHOR_TENANT. - */ - String authCode; - /** * Contains a comma-delimited list of the fully qualified hostnames of the nameservers that can * be set on a domain with this label (only applicable to NAMESERVER_RESTRICTED). @@ -114,34 +105,21 @@ public final class ReservedList public static ReservedListEntry create( String label, ReservationType reservationType, - @Nullable String restrictions, + @Nullable String allowedNameservers, @Nullable String comment) { - ReservedListEntry.Builder builder = + ReservedListEntry.Builder entry = new ReservedListEntry.Builder() .setLabel(label) .setComment(comment) .setReservationType(reservationType); - if (restrictions != null) { - checkArgument( - reservationType == RESERVED_FOR_ANCHOR_TENANT - || reservationType == NAMESERVER_RESTRICTED, - "Only anchor tenant and nameserver restricted reservations " - + "should have restrictions imposed"); - if (reservationType == RESERVED_FOR_ANCHOR_TENANT) { - builder.setAuthCode(restrictions); - } else if (reservationType == NAMESERVER_RESTRICTED) { - builder.setAllowedNameservers( - ImmutableSet.copyOf(Splitter.on(':').trimResults().split(restrictions))); - } - } else { - checkArgument( - reservationType != RESERVED_FOR_ANCHOR_TENANT, - "Anchor tenant reservations must have an auth code configured"); - checkArgument( - reservationType != NAMESERVER_RESTRICTED, - "Nameserver restricted reservations must have at least one nameserver configured"); + checkArgument( + (reservationType == NAMESERVER_RESTRICTED) ^ (allowedNameservers == null), + "Allowed nameservers must be specified for NAMESERVER_RESTRICTED reservations only"); + if (allowedNameservers != null) { + entry.setAllowedNameservers( + ImmutableSet.copyOf(Splitter.on(':').trimResults().split(allowedNameservers))); } - return builder.build(); + return entry.build(); } private static void checkNameserversAreValid(Set nameservers) { @@ -159,10 +137,6 @@ public final class ReservedList return reservationType; } - public String getAuthCode() { - return authCode; - } - public ImmutableSet getAllowedNameservers() { return ImmutableSet.copyOf(Splitter.on(',').splitToList(allowedNameservers)); } @@ -188,11 +162,6 @@ public final class ReservedList return this; } - ReservedListEntry.Builder setAuthCode(String authCode) { - getInstance().authCode = authCode; - return this; - } - ReservedListEntry.Builder setReservationType(ReservationType reservationType) { getInstance().reservationType = reservationType; return this; @@ -256,27 +225,6 @@ public final class ReservedList .collect(toImmutableSet()); } - /** - * Returns true if the given label and TLD is reserved for an anchor tenant, and the given auth - * code matches the one set on the reservation. If there are multiple anchor tenant entries for - * this label, all the auth codes need to be the same and match the given one, otherwise an - * exception is thrown. - */ - public static boolean matchesAnchorTenantReservation( - InternetDomainName domainName, String authCode) { - - ImmutableSet domainAuthCodes = - getReservedListEntries(domainName.parts().get(0), domainName.parent().toString()) - .stream() - .filter((entry) -> entry.reservationType == RESERVED_FOR_ANCHOR_TENANT) - .map(ReservedListEntry::getAuthCode) - .collect(toImmutableSet()); - checkState( - domainAuthCodes.size() <= 1, "There are conflicting auth codes for domain: %s", domainName); - - return !domainAuthCodes.isEmpty() && getOnlyElement(domainAuthCodes).equals(authCode); - } - /** * Returns the set of nameservers that can be set on the given domain. * diff --git a/javatests/google/registry/flows/domain/DomainCheckFlowTest.java b/javatests/google/registry/flows/domain/DomainCheckFlowTest.java index a9883d49d..1246c373e 100644 --- a/javatests/google/registry/flows/domain/DomainCheckFlowTest.java +++ b/javatests/google/registry/flows/domain/DomainCheckFlowTest.java @@ -82,10 +82,12 @@ public class DomainCheckFlowTest } private ReservedList createReservedList() { + persistResource( + new AllocationToken.Builder().setDomainName("anchor.tld").setToken("2fooBAR").build()); return persistReservedList( "tld-reserved", "reserved,FULLY_BLOCKED", - "anchor,RESERVED_FOR_ANCHOR_TENANT,foo2BAR", + "anchor,RESERVED_FOR_ANCHOR_TENANT", "allowedinsunrise,ALLOWED_IN_SUNRISE", "collision,NAME_COLLISION", "premiumcollision,NAME_COLLISION"); diff --git a/javatests/google/registry/flows/domain/DomainCreateFlowTest.java b/javatests/google/registry/flows/domain/DomainCreateFlowTest.java index 64e139284..e07b7e9c9 100644 --- a/javatests/google/registry/flows/domain/DomainCreateFlowTest.java +++ b/javatests/google/registry/flows/domain/DomainCreateFlowTest.java @@ -180,6 +180,8 @@ public class DomainCreateFlowTest extends ResourceFlowTestCase - example.tld + %DOMAIN% 2 ns1.example.net diff --git a/javatests/google/registry/flows/domain/testdata/domain_create_anchor_allocationtoken.xml b/javatests/google/registry/flows/domain/testdata/domain_create_anchor_allocationtoken.xml new file mode 100644 index 000000000..9c59eaffb --- /dev/null +++ b/javatests/google/registry/flows/domain/testdata/domain_create_anchor_allocationtoken.xml @@ -0,0 +1,29 @@ + + + + + anchor.tld + 2 + + ns1.example.net + ns2.example.net + + jd1234 + sh8013 + sh8013 + + 2fooBAR + + + + + + abcDEF23456 + + + ABC-12345 + + diff --git a/javatests/google/registry/flows/domain/testdata/domain_create_anchor_authcode.xml b/javatests/google/registry/flows/domain/testdata/domain_create_anchor_authcode.xml index 000a3a8df..e9ab63691 100644 --- a/javatests/google/registry/flows/domain/testdata/domain_create_anchor_authcode.xml +++ b/javatests/google/registry/flows/domain/testdata/domain_create_anchor_authcode.xml @@ -13,7 +13,7 @@ sh8013 sh8013 - 2fooBAR + abcDEF23456 diff --git a/javatests/google/registry/flows/domain/testdata/domain_create_anchor_authcode_invalid_years.xml b/javatests/google/registry/flows/domain/testdata/domain_create_anchor_authcode_invalid_years.xml index 2f0757631..5667a2c6d 100644 --- a/javatests/google/registry/flows/domain/testdata/domain_create_anchor_authcode_invalid_years.xml +++ b/javatests/google/registry/flows/domain/testdata/domain_create_anchor_authcode_invalid_years.xml @@ -17,6 +17,13 @@ + + + abcDEF23456 + + ABC-12345 diff --git a/javatests/google/registry/model/domain/token/AllocationTokenTest.java b/javatests/google/registry/model/domain/token/AllocationTokenTest.java index ae3ff14be..4395dcc68 100644 --- a/javatests/google/registry/model/domain/token/AllocationTokenTest.java +++ b/javatests/google/registry/model/domain/token/AllocationTokenTest.java @@ -37,7 +37,7 @@ public class AllocationTokenTest extends EntityTestCase { .setToken("abc123") .setRedemptionHistoryEntry(Key.create(HistoryEntry.class, 1L)) .setDomainName("foo.example") - .setCreationTime(DateTime.parse("2010-11-12T05:00:00Z")) + .setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z")) .build()); assertThat(ofy().load().entity(token).now()).isEqualTo(token); } @@ -49,7 +49,7 @@ public class AllocationTokenTest extends EntityTestCase { new AllocationToken.Builder() .setToken("abc123") .setDomainName("blahdomain.fake") - .setCreationTime(DateTime.parse("2010-11-12T05:00:00Z")) + .setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z")) .build()), "token", "domainName"); @@ -69,11 +69,11 @@ public class AllocationTokenTest extends EntityTestCase { AllocationToken.Builder builder = new AllocationToken.Builder() .setToken("foobar") - .setCreationTime(DateTime.parse("2010-11-12T05:00:00Z")); + .setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z")); IllegalStateException thrown = assertThrows( IllegalStateException.class, - () -> builder.setCreationTime(DateTime.parse("2010-11-13T05:00:00Z"))); + () -> builder.setCreationTimeForTest(DateTime.parse("2010-11-13T05:00:00Z"))); assertThat(thrown).hasMessageThat().isEqualTo("creationTime can only be set once"); } diff --git a/javatests/google/registry/model/registry/RegistryTest.java b/javatests/google/registry/model/registry/RegistryTest.java index 7145b3717..b9abede29 100644 --- a/javatests/google/registry/model/registry/RegistryTest.java +++ b/javatests/google/registry/model/registry/RegistryTest.java @@ -180,46 +180,6 @@ public class RegistryTest extends EntityTestCase { assertThat(r.getReservedLists()).isEmpty(); } - @Test - public void testSetReservedLists_succeedsWithDuplicateIdenticalAuthCodes() { - ReservedList rl1 = persistReservedList( - "tld-reserved007", - "lol,RESERVED_FOR_ANCHOR_TENANT,identical", - "cat,FULLY_BLOCKED"); - ReservedList rl2 = persistReservedList( - "tld-reserved008", - "lol,RESERVED_FOR_ANCHOR_TENANT,identical", - "tim,FULLY_BLOCKED"); - Registry registry = Registry.get("tld").asBuilder().setReservedLists(rl1, rl2).build(); - assertThat(registry.getReservedLists()).containsExactly(Key.create(rl1), Key.create(rl2)); - } - - @Test - public void testSetReservedLists_failsForConflictingAuthCodes() { - ReservedList rl1 = persistReservedList( - "tld-reserved055", - "lol,RESERVED_FOR_ANCHOR_TENANT,conflict1", - "cat,FULLY_BLOCKED"); - ReservedList rl2 = persistReservedList( - "tld-reserved056", - "lol,RESERVED_FOR_ANCHOR_TENANT,conflict2", - "tim,FULLY_BLOCKED"); - ReservedList rl3 = persistReservedList( - "tld-reserved057", - "lol,RESERVED_FOR_ANCHOR_TENANT,another_conflict"); - IllegalArgumentException thrown = - assertThrows( - IllegalArgumentException.class, - () -> { - @SuppressWarnings("unused") - Registry unused = - Registry.get("tld").asBuilder().setReservedLists(rl1, rl2, rl3).build(); - }); - assertThat(thrown) - .hasMessageThat() - .contains("auth code conflicts for labels: [lol=[conflict1, conflict2, another_conflict]]"); - } - @Test public void testSetPremiumList() { PremiumList pl2 = persistPremiumList("tld2", "lol,USD 50", "cat,USD 700"); diff --git a/javatests/google/registry/model/registry/label/ReservedListTest.java b/javatests/google/registry/model/registry/label/ReservedListTest.java index 7effb87d6..c7529949a 100644 --- a/javatests/google/registry/model/registry/label/ReservedListTest.java +++ b/javatests/google/registry/model/registry/label/ReservedListTest.java @@ -23,13 +23,10 @@ import static google.registry.model.registry.label.DomainLabelMetrics.reservedLi import static google.registry.model.registry.label.DomainLabelMetrics.reservedListProcessingTime; import static google.registry.model.registry.label.ReservationType.ALLOWED_IN_SUNRISE; import static google.registry.model.registry.label.ReservationType.FULLY_BLOCKED; -import static google.registry.model.registry.label.ReservationType.MISTAKEN_PREMIUM; import static google.registry.model.registry.label.ReservationType.NAMESERVER_RESTRICTED; import static google.registry.model.registry.label.ReservationType.NAME_COLLISION; -import static google.registry.model.registry.label.ReservationType.RESERVED_FOR_ANCHOR_TENANT; import static google.registry.model.registry.label.ReservedList.getAllowedNameservers; import static google.registry.model.registry.label.ReservedList.getReservationTypes; -import static google.registry.model.registry.label.ReservedList.matchesAnchorTenantReservation; import static google.registry.testing.DatastoreHelper.createTld; import static google.registry.testing.DatastoreHelper.persistReservedList; import static google.registry.testing.DatastoreHelper.persistResource; @@ -118,47 +115,6 @@ public class ReservedListTest { verifyUnreservedCheckCount(26); } - @Test - public void testMatchesAnchorTenantReservation() { - persistResource( - Registry.get("tld") - .asBuilder() - .setReservedLists(ImmutableSet.of( - persistReservedList( - "reserved1", - "lol,RESERVED_FOR_ANCHOR_TENANT,foobar1", - "lol2,RESERVED_FOR_ANCHOR_TENANT,abcdefg # This is a comment"))) - .build()); - assertThat(getReservationTypes("lol", "tld")).containsExactly(RESERVED_FOR_ANCHOR_TENANT); - assertThat(getReservationTypes("lol2", "tld")).containsExactly(RESERVED_FOR_ANCHOR_TENANT); - assertThat(matchesAnchorTenantReservation(InternetDomainName.from("lol.tld"), "foobar1")) - .isTrue(); - assertThat(matchesAnchorTenantReservation(InternetDomainName.from("lol.tld"), "foobar")) - .isFalse(); - assertThat(matchesAnchorTenantReservation(InternetDomainName.from("lol2.tld"), "abcdefg")) - .isTrue(); - assertThat(matchesAnchorTenantReservation(InternetDomainName.from("lol2.tld"), "abcdefg ")) - .isFalse(); - assertThat(matchesAnchorTenantReservation(InternetDomainName.from("random.tld"), "abcdefg")) - .isFalse(); - assertThat(reservedListChecks) - .hasValueForLabels(1, "tld", "0", "(none)", "(none)") - .and() - .hasValueForLabels(6, "tld", "1", "reserved1", RESERVED_FOR_ANCHOR_TENANT.toString()) - .and() - .hasNoOtherValues(); - assertThat(reservedListProcessingTime) - .hasAnyValueForLabels("tld", "0", "(none)", "(none)") - .and() - .hasAnyValueForLabels("tld", "1", "reserved1", RESERVED_FOR_ANCHOR_TENANT.toString()) - .and() - .hasNoOtherValues(); - assertThat(reservedListHits) - .hasValueForLabels(6, "tld", "reserved1", RESERVED_FOR_ANCHOR_TENANT.toString()) - .and() - .hasNoOtherValues(); - } - @Test public void testGetAllowedNameservers() { ReservedList rl1 = @@ -191,87 +147,6 @@ public class ReservedListTest { assertThat(getAllowedNameservers(InternetDomainName.from("lol4.tld"))).isEmpty(); } - @Test - public void testMatchesAnchorTenantReservation_falseOnOtherReservationTypes() { - persistResource( - Registry.get("tld") - .asBuilder() - .setReservedLists( - ImmutableSet.of( - persistReservedList( - "reserved2", - "lol,FULLY_BLOCKED", - "lol2,NAME_COLLISION", - "lol3,MISTAKEN_PREMIUM", - "lol4,ALLOWED_IN_SUNRISE", - "lol5,NAMESERVER_RESTRICTED,na1.domain.tld"))) - .build()); - assertThat(matchesAnchorTenantReservation(InternetDomainName.from("lol.tld"), "")).isFalse(); - assertThat(matchesAnchorTenantReservation(InternetDomainName.from("lol2.tld"), "")).isFalse(); - assertThat(matchesAnchorTenantReservation(InternetDomainName.from("lol3.tld"), "")).isFalse(); - assertThat(matchesAnchorTenantReservation(InternetDomainName.from("lol4.tld"), "")).isFalse(); - assertThat(matchesAnchorTenantReservation(InternetDomainName.from("lol5.tld"), "")).isFalse(); - assertThat(matchesAnchorTenantReservation(InternetDomainName.from("lol6.tld"), "")).isFalse(); - assertThat(reservedListChecks) - .hasValueForLabels(1, "tld", "1", "reserved2", FULLY_BLOCKED.toString()) - .and() - .hasValueForLabels(1, "tld", "1", "reserved2", NAME_COLLISION.toString()) - .and() - .hasValueForLabels(1, "tld", "1", "reserved2", MISTAKEN_PREMIUM.toString()) - .and() - .hasValueForLabels(1, "tld", "1", "reserved2", ALLOWED_IN_SUNRISE.toString()) - .and() - .hasValueForLabels(1, "tld", "1", "reserved2", NAMESERVER_RESTRICTED.toString()) - .and() - .hasValueForLabels(1, "tld", "0", "(none)", "(none)") - .and() - .hasNoOtherValues(); - assertThat(reservedListProcessingTime) - .hasAnyValueForLabels("tld", "1", "reserved2", FULLY_BLOCKED.toString()) - .and() - .hasAnyValueForLabels("tld", "1", "reserved2", NAME_COLLISION.toString()) - .and() - .hasAnyValueForLabels("tld", "1", "reserved2", MISTAKEN_PREMIUM.toString()) - .and() - .hasAnyValueForLabels("tld", "1", "reserved2", ALLOWED_IN_SUNRISE.toString()) - .and() - .hasAnyValueForLabels("tld", "1", "reserved2", NAMESERVER_RESTRICTED.toString()) - .and() - .hasAnyValueForLabels("tld", "0", "(none)", "(none)") - .and() - .hasNoOtherValues(); - assertThat(reservedListHits) - .hasValueForLabels(1, "tld", "reserved2", FULLY_BLOCKED.toString()) - .and() - .hasValueForLabels(1, "tld", "reserved2", NAME_COLLISION.toString()) - .and() - .hasValueForLabels(1, "tld", "reserved2", MISTAKEN_PREMIUM.toString()) - .and() - .hasValueForLabels(1, "tld", "reserved2", ALLOWED_IN_SUNRISE.toString()) - .and() - .hasValueForLabels(1, "tld", "reserved2", NAMESERVER_RESTRICTED.toString()) - .and() - .hasNoOtherValues(); - } - - @Test - public void testMatchesAnchorTenantReservation_duplicatingAuthCodes() { - ReservedList rl1 = persistReservedList("reserved1", "lol,RESERVED_FOR_ANCHOR_TENANT,foo"); - ReservedList rl2 = persistReservedList("reserved2", "lol,RESERVED_FOR_ANCHOR_TENANT,foo"); - createTld("tld"); - persistResource(Registry.get("tld").asBuilder().setReservedLists(rl1, rl2).build()); - assertThat(matchesAnchorTenantReservation(InternetDomainName.from("lol.tld"), "foo")).isTrue(); - assertThat(matchesAnchorTenantReservation(InternetDomainName.from("lol.tld"), "bar")).isFalse(); - persistReservedList("reserved2", "lol,RESERVED_FOR_ANCHOR_TENANT,bar"); - IllegalStateException thrown = - assertThrows( - IllegalStateException.class, - () -> matchesAnchorTenantReservation(InternetDomainName.from("lol.tld"), "bar")); - assertThat(thrown) - .hasMessageThat() - .contains("There are conflicting auth codes for domain: lol.tld"); - } - @Test public void testGetReservationTypes_concatsMultipleListsCorrectly() { ReservedList rl1 = persistReservedList( @@ -494,8 +369,7 @@ public class ReservedListTest { assertThat(thrown) .hasMessageThat() .contains( - "Only anchor tenant and nameserver restricted reservations " - + "should have restrictions imposed"); + "Allowed nameservers must be specified for NAMESERVER_RESTRICTED reservations only"); } @Test @@ -524,24 +398,6 @@ public class ReservedListTest { assertThat(thrown).hasMessageThat().contains("domain.tld is not a valid nameserver hostname"); } - @Test - public void testSave_noPasswordWithAnchorTenantReservation() { - IllegalArgumentException thrown = - assertThrows( - IllegalArgumentException.class, - () -> - persistResource( - Registry.get("tld") - .asBuilder() - .setReservedLists( - ImmutableSet.of( - persistReservedList("reserved1", "lol,RESERVED_FOR_ANCHOR_TENANT"))) - .build())); - assertThat(thrown) - .hasMessageThat() - .contains("Anchor tenant reservations must have an auth code configured"); - } - @Test public void testSave_noNameserversWithNameserverRestrictedReservation() { IllegalArgumentException thrown = @@ -558,7 +414,7 @@ public class ReservedListTest { assertThat(thrown) .hasMessageThat() .contains( - "Nameserver restricted reservations must have at least one nameserver configured"); + "Allowed nameservers must be specified for NAMESERVER_RESTRICTED reservations only"); } @Test diff --git a/javatests/google/registry/model/testdata/schema.txt b/javatests/google/registry/model/testdata/schema.txt index daa644697..670a48bdc 100644 --- a/javatests/google/registry/model/testdata/schema.txt +++ b/javatests/google/registry/model/testdata/schema.txt @@ -723,6 +723,7 @@ enum google.registry.model.registry.label.ReservationType { NAMESERVER_RESTRICTED; NAME_COLLISION; RESERVED_FOR_ANCHOR_TENANT; + RESERVED_FOR_SPECIFIC_USE; } class google.registry.model.registry.label.ReservedList { @Id java.lang.String name; @@ -737,7 +738,6 @@ class google.registry.model.registry.label.ReservedList$ReservedListEntry { @Id java.lang.String label; google.registry.model.registry.label.ReservationType reservationType; java.lang.String allowedNameservers; - java.lang.String authCode; java.lang.String comment; } class google.registry.model.reporting.DomainTransactionRecord {