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 {