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
This commit is contained in:
mcilwain 2018-08-16 11:41:34 -07:00 committed by jianglai
parent 782643ce33
commit d2f849ac0f
19 changed files with 184 additions and 346 deletions

View file

@ -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());

View file

@ -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) {

View file

@ -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<LaunchCreateExtension> 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> 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> 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> 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.
*

View file

@ -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<ReservationType> 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<AllocationToken> token,
String authInfoPw,
Optional<MetadataExtension> 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<ReservationType> RESERVED_TYPES =
ImmutableSet.of(RESERVED_FOR_SPECIFIC_USE, RESERVED_FOR_ANCHOR_TENANT, FULLY_BLOCKED);
private static boolean isReserved(InternetDomainName domainName, boolean isSunrise) {
ImmutableSet<ReservationType> 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());
}

View file

@ -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);

View file

@ -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<ReservedList> reservedLists) {
checkArgumentNotNull(reservedLists, "reservedLists must not be null");
checkAuthCodeConflicts(reservedLists);
ImmutableSet.Builder<Key<ReservedList>> 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<ReservedList> reservedLists) {
Multimap<String, String> allAuthCodes = LinkedHashMultimap.create();
for (ReservedList list : reservedLists) {
for (ReservedListEntry entry : list.getReservedListEntries().values()) {
if (entry.getAuthCode() != null) {
allAuthCodes.put(entry.getLabel(), entry.getAuthCode());
}
}
}
ImmutableSet<Entry<String, Collection<String>>> conflicts =
allAuthCodes
.asMap()
.entrySet()
.stream()
.filter((Entry<String, Collection<String>> 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;

View file

@ -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}.
*
* <p>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;

View file

@ -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<String> nameservers) {
@ -159,10 +137,6 @@ public final class ReservedList
return reservationType;
}
public String getAuthCode() {
return authCode;
}
public ImmutableSet<String> 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<String> 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.
*