Ignore TLD state on domain create when using corresponding token (#1709)

This commit is contained in:
gbrodman 2022-07-26 13:54:12 -04:00 committed by GitHub
parent 06ca9266b4
commit 5657089ffc
5 changed files with 233 additions and 31 deletions

View file

@ -97,6 +97,7 @@ import google.registry.model.domain.metadata.MetadataExtension;
import google.registry.model.domain.rgp.GracePeriodStatus; import google.registry.model.domain.rgp.GracePeriodStatus;
import google.registry.model.domain.secdns.SecDnsCreateExtension; import google.registry.model.domain.secdns.SecDnsCreateExtension;
import google.registry.model.domain.token.AllocationToken; import google.registry.model.domain.token.AllocationToken;
import google.registry.model.domain.token.AllocationToken.RegistrationBehavior;
import google.registry.model.domain.token.AllocationToken.TokenType; import google.registry.model.domain.token.AllocationToken.TokenType;
import google.registry.model.domain.token.AllocationTokenExtension; import google.registry.model.domain.token.AllocationTokenExtension;
import google.registry.model.eppcommon.StatusValue; import google.registry.model.eppcommon.StatusValue;
@ -117,6 +118,8 @@ import google.registry.model.tld.Registry;
import google.registry.model.tld.Registry.TldState; import google.registry.model.tld.Registry.TldState;
import google.registry.model.tld.Registry.TldType; import google.registry.model.tld.Registry.TldType;
import google.registry.model.tld.label.ReservationType; import google.registry.model.tld.label.ReservationType;
import google.registry.model.tmch.ClaimsList;
import google.registry.model.tmch.ClaimsListDao;
import google.registry.tmch.LordnTaskUtils; import google.registry.tmch.LordnTaskUtils;
import java.util.Optional; import java.util.Optional;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -279,7 +282,16 @@ public final class DomainCreateFlow implements TransactionalFlow {
checkAllowedAccessToTld(registrarId, registry.getTldStr()); checkAllowedAccessToTld(registrarId, registry.getTldStr());
checkHasBillingAccount(registrarId, registry.getTldStr()); checkHasBillingAccount(registrarId, registry.getTldStr());
boolean isValidReservedCreate = isValidReservedCreate(domainName, allocationToken); boolean isValidReservedCreate = isValidReservedCreate(domainName, allocationToken);
verifyIsGaOrIsSpecialCase(tldState, isAnchorTenant, isValidReservedCreate, hasSignedMarks); ClaimsList claimsList = ClaimsListDao.get();
verifyIsGaOrSpecialCase(
registry,
claimsList,
now,
domainLabel,
allocationToken,
isAnchorTenant,
isValidReservedCreate,
hasSignedMarks);
if (launchCreate.isPresent()) { if (launchCreate.isPresent()) {
verifyLaunchPhaseMatchesRegistryPhase(registry, launchCreate.get(), now); verifyLaunchPhaseMatchesRegistryPhase(registry, launchCreate.get(), now);
} }
@ -290,7 +302,8 @@ public final class DomainCreateFlow implements TransactionalFlow {
verifyClaimsPeriodNotEnded(registry, now); verifyClaimsPeriodNotEnded(registry, now);
} }
if (now.isBefore(registry.getClaimsPeriodEnd())) { if (now.isBefore(registry.getClaimsPeriodEnd())) {
verifyClaimsNoticeIfAndOnlyIfNeeded(domainName, hasSignedMarks, hasClaimsNotice); verifyClaimsNoticeIfAndOnlyIfNeeded(
domainName, claimsList, hasSignedMarks, hasClaimsNotice);
} }
verifyPremiumNameIsNotBlocked(targetId, now, registrarId); verifyPremiumNameIsNotBlocked(targetId, now, registrarId);
verifySignedMarkOnlyInSunrise(hasSignedMarks, tldState); verifySignedMarkOnlyInSunrise(hasSignedMarks, tldState);
@ -447,45 +460,71 @@ public final class DomainCreateFlow implements TransactionalFlow {
} }
/** /**
* Prohibit registrations unless QLP, General Availability or Start Date Sunrise. * Prohibit registrations unless they're in GA or a special case.
* *
* <p>During Start-Date Sunrise, we need a signed mark for registrations. * <p>Non-trademarked names can be registered at any point with a special allocation token
* registration behavior.
*
* <p>Trademarked names require signed marks in sunrise no matter what, and can be registered with
* a special allocation token behavior in any quiet period that is post-sunrise.
* *
* <p>Note that "superuser" status isn't tested here - this should only be called for * <p>Note that "superuser" status isn't tested here - this should only be called for
* non-superusers. * non-superusers.
*/ */
private void verifyIsGaOrIsSpecialCase( private void verifyIsGaOrSpecialCase(
TldState tldState, Registry registry,
ClaimsList claimsList,
DateTime now,
String domainLabel,
Optional<AllocationToken> allocationToken,
boolean isAnchorTenant, boolean isAnchorTenant,
boolean isValidReservedCreate, boolean isValidReservedCreate,
boolean hasSignedMarks) boolean hasSignedMarks)
throws NoGeneralRegistrationsInCurrentPhaseException, throws NoGeneralRegistrationsInCurrentPhaseException,
MustHaveSignedMarksInCurrentPhaseException { MustHaveSignedMarksInCurrentPhaseException {
// Anchor Tenant overrides any other consideration to allow registration.
if (isAnchorTenant) {
return;
}
// We allow general registration during GA. // We allow general registration during GA.
if (GENERAL_AVAILABILITY.equals(tldState)) { TldState currentState = registry.getTldState(now);
if (currentState.equals(GENERAL_AVAILABILITY)) {
return; return;
} }
// During START_DATE_SUNRISE, only allow registration with signed marks. // Determine if there should be any behavior dictated by the allocation token
if (START_DATE_SUNRISE.equals(tldState)) { RegistrationBehavior behavior =
allocationToken
.map(AllocationToken::getRegistrationBehavior)
.orElse(RegistrationBehavior.DEFAULT);
// Bypass most TLD state checks if that behavior is specified by the token
if (behavior.equals(RegistrationBehavior.BYPASS_TLD_STATE)
|| behavior.equals(RegistrationBehavior.ANCHOR_TENANT)) {
// If bypassing TLD state checks, a post-sunrise state is always fine
if (!currentState.equals(START_DATE_SUNRISE)
&& registry.getTldStateTransitions().headMap(now).containsValue(START_DATE_SUNRISE)) {
return;
}
// Non-trademarked names with the state check bypassed are always available
if (!claimsList.getClaimKey(domainLabel).isPresent()) {
return;
}
}
// Otherwise, signed marks are necessary and sufficient in the sunrise period
if (currentState.equals(START_DATE_SUNRISE)) {
if (!hasSignedMarks) { if (!hasSignedMarks) {
throw new MustHaveSignedMarksInCurrentPhaseException(); throw new MustHaveSignedMarksInCurrentPhaseException();
} }
return; return;
} }
// We allow creates of specifically reserved domain names during quiet periods. // Anchor tenant overrides any remaining considerations to allow registration
if (QUIET_PERIOD.equals(tldState)) { if (isAnchorTenant) {
return;
}
// We allow creates of specifically reserved domain names during quiet periods
if (currentState.equals(QUIET_PERIOD)) {
if (isValidReservedCreate) { if (isValidReservedCreate) {
return; return;
} }
} }
// All other phases do not allow registration // All other phases do not allow registration
throw new NoGeneralRegistrationsInCurrentPhaseException(); throw new NoGeneralRegistrationsInCurrentPhaseException();
} }

View file

@ -126,7 +126,7 @@ import google.registry.model.tld.Registry.TldState;
import google.registry.model.tld.Registry.TldType; import google.registry.model.tld.Registry.TldType;
import google.registry.model.tld.label.ReservationType; import google.registry.model.tld.label.ReservationType;
import google.registry.model.tld.label.ReservedList; import google.registry.model.tld.label.ReservedList;
import google.registry.model.tmch.ClaimsListDao; import google.registry.model.tmch.ClaimsList;
import google.registry.persistence.VKey; import google.registry.persistence.VKey;
import google.registry.tldconfig.idn.IdnLabelValidator; import google.registry.tldconfig.idn.IdnLabelValidator;
import google.registry.tools.DigestType; import google.registry.tools.DigestType;
@ -1063,9 +1063,12 @@ public class DomainFlowUtils {
* not on the claims list. * not on the claims list.
*/ */
static void verifyClaimsNoticeIfAndOnlyIfNeeded( static void verifyClaimsNoticeIfAndOnlyIfNeeded(
InternetDomainName domainName, boolean hasSignedMarks, boolean hasClaimsNotice) InternetDomainName domainName,
ClaimsList claimsList,
boolean hasSignedMarks,
boolean hasClaimsNotice)
throws EppException { throws EppException {
boolean isInClaimsList = ClaimsListDao.get().getClaimKey(domainName.parts().get(0)).isPresent(); boolean isInClaimsList = claimsList.getClaimKey(domainName.parts().get(0)).isPresent();
if (hasClaimsNotice && !isInClaimsList) { if (hasClaimsNotice && !isInClaimsList) {
throw new UnexpectedClaimsNoticeException(domainName.toString()); throw new UnexpectedClaimsNoticeException(domainName.toString());
} }

View file

@ -23,7 +23,6 @@ import static google.registry.persistence.transaction.TransactionManagerFactory.
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import google.registry.model.CreateAutoTimestamp; import google.registry.model.CreateAutoTimestamp;
import google.registry.model.ImmutableObject; import google.registry.model.ImmutableObject;
import google.registry.model.tld.label.ReservedList.ReservedListEntry;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import javax.persistence.AttributeOverride; import javax.persistence.AttributeOverride;
@ -94,8 +93,8 @@ public class ClaimsList extends ImmutableObject {
} }
/** /**
* Hibernate hook called on the insert of a new ReservedList. Stores the associated {@link * Hibernate hook called on the insert of a new ClaimsList. Stores the associated {@link
* ReservedListEntry}'s. * ClaimsEntry}'s.
* *
* <p>We need to persist the list entries, but only on the initial insert (not on update) since * <p>We need to persist the list entries, but only on the initial insert (not on update) since
* the entries themselves never get changed, so we only annotate it with {@link PostPersist}, not * the entries themselves never get changed, so we only annotate it with {@link PostPersist}, not
@ -104,10 +103,9 @@ public class ClaimsList extends ImmutableObject {
@PostPersist @PostPersist
void postPersist() { void postPersist() {
if (labelsToKeys != null) { if (labelsToKeys != null) {
labelsToKeys.entrySet().stream() labelsToKeys.forEach(
.forEach( (domainLabel, claimKey) ->
entry -> jpaTm().insert(new ClaimsEntry(revisionId, domainLabel, claimKey)));
jpaTm().insert(new ClaimsEntry(revisionId, entry.getKey(), entry.getValue())));
} }
} }

View file

@ -160,6 +160,7 @@ import google.registry.model.domain.launch.LaunchNotice;
import google.registry.model.domain.rgp.GracePeriodStatus; import google.registry.model.domain.rgp.GracePeriodStatus;
import google.registry.model.domain.secdns.DelegationSignerData; import google.registry.model.domain.secdns.DelegationSignerData;
import google.registry.model.domain.token.AllocationToken; import google.registry.model.domain.token.AllocationToken;
import google.registry.model.domain.token.AllocationToken.RegistrationBehavior;
import google.registry.model.domain.token.AllocationToken.TokenStatus; import google.registry.model.domain.token.AllocationToken.TokenStatus;
import google.registry.model.poll.PendingActionNotificationResponse.DomainPendingActionNotificationResponse; import google.registry.model.poll.PendingActionNotificationResponse.DomainPendingActionNotificationResponse;
import google.registry.model.poll.PollMessage; import google.registry.model.poll.PollMessage;
@ -219,7 +220,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
"badcrash,NAME_COLLISION"), "badcrash,NAME_COLLISION"),
persistReservedList("global-list", "resdom,FULLY_BLOCKED")) persistReservedList("global-list", "resdom,FULLY_BLOCKED"))
.build()); .build());
persistClaimsList(ImmutableMap.of("example-one", CLAIMS_KEY)); persistClaimsList(ImmutableMap.of("example-one", CLAIMS_KEY, "test-validate", CLAIMS_KEY));
} }
/** /**
@ -1230,10 +1231,24 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
setEppInput("domain_create_anchor_tenant_sunrise_metadata_extension.xml"); setEppInput("domain_create_anchor_tenant_sunrise_metadata_extension.xml");
eppRequestSource = EppRequestSource.TOOL; // Only tools can pass in metadata. eppRequestSource = EppRequestSource.TOOL; // Only tools can pass in metadata.
persistContactsAndHosts(); persistContactsAndHosts();
// Even for anchor tenants, require signed marks in sunrise
EppException exception =
assertThrows(MustHaveSignedMarksInCurrentPhaseException.class, this::runFlow);
assertAboutEppExceptions().that(exception).marshalsToXml();
}
@Test
void testSuccess_anchorTenantInSunrise_withMetadataExtension_andSignedMark() throws Exception {
createTld("tld", START_DATE_SUNRISE);
setEppInput("domain_create_anchor_tenant_sunrise_metadata_extension_signed_mark.xml");
eppRequestSource = EppRequestSource.TOOL; // Only tools can pass in metadata.
persistContactsAndHosts();
clock.setTo(DateTime.parse("2014-09-09T09:09:09Z"));
runFlowAssertResponse( runFlowAssertResponse(
loadFile("domain_create_response.xml", ImmutableMap.of("DOMAIN", "example.tld"))); loadFile(
assertSuccessfulCreate("tld", ImmutableSet.of(ANCHOR_TENANT)); "domain_create_response_encoded_signed_mark_name.xml",
assertNoLordn(); ImmutableMap.of("DOMAIN", "test-validate.tld")));
assertSuccessfulCreate("tld", ImmutableSet.of(SUNRISE, ANCHOR_TENANT));
} }
@Test @Test
@ -2718,4 +2733,116 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
"No enum constant" "No enum constant"
+ " google.registry.model.billing.BillingEvent.RenewalPriceBehavior.INVALID"); + " google.registry.model.billing.BillingEvent.RenewalPriceBehavior.INVALID");
} }
@Test
void testSuccess_quietPeriod_skipTldCheckWithToken() throws Exception {
AllocationToken token =
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(SINGLE_USE)
.setRegistrationBehavior(RegistrationBehavior.BYPASS_TLD_STATE)
.build());
persistContactsAndHosts();
setEppInput(
"domain_create_allocationtoken.xml",
ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2"));
persistResource(
Registry.get("tld")
.asBuilder()
.setTldStateTransitions(ImmutableSortedMap.of(START_OF_TIME, QUIET_PERIOD))
.build());
runFlow();
assertSuccessfulCreate("tld", ImmutableSet.of(), token);
}
@Test
void testSuccess_sunrise_skipTldCheckWithToken() throws Exception {
AllocationToken token =
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(SINGLE_USE)
.setRegistrationBehavior(RegistrationBehavior.BYPASS_TLD_STATE)
.build());
persistContactsAndHosts();
setEppInput(
"domain_create_allocationtoken.xml",
ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2"));
persistResource(
Registry.get("tld")
.asBuilder()
.setTldStateTransitions(ImmutableSortedMap.of(START_OF_TIME, QUIET_PERIOD))
.build());
runFlow();
assertSuccessfulCreate("tld", ImmutableSet.of(), token);
}
@Test
void testFailure_quietPeriod_defaultTokenPresent() throws Exception {
persistResource(
new AllocationToken.Builder().setToken("abc123").setTokenType(SINGLE_USE).build());
persistContactsAndHosts();
setEppInput(
"domain_create_allocationtoken.xml",
ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2"));
persistResource(
Registry.get("tld")
.asBuilder()
.setTldStateTransitions(ImmutableSortedMap.of(START_OF_TIME, QUIET_PERIOD))
.build());
assertThrows(NoGeneralRegistrationsInCurrentPhaseException.class, this::runFlow);
}
@Test
void testFailure_quietPeriodBeforeSunrise_trademarkedDomain() throws Exception {
allocationToken =
persistResource(
allocationToken
.asBuilder()
.setRegistrationBehavior(RegistrationBehavior.BYPASS_TLD_STATE)
.setDomainName(null)
.build());
// Trademarked domains using a bypass-tld-state token should fail if we're in a quiet period
// before the sunrise period
persistResource(
Registry.get("tld")
.asBuilder()
.setTldStateTransitions(
ImmutableSortedMap.of(
START_OF_TIME, QUIET_PERIOD, clock.nowUtc().plusYears(1), START_DATE_SUNRISE))
.build());
persistContactsAndHosts();
setEppInput("domain_create_allocationtoken_claims.xml");
assertThrows(NoGeneralRegistrationsInCurrentPhaseException.class, this::runFlow);
}
@Test
void testSuccess_quietPeriodAfterSunrise_trademarkedDomain() throws Exception {
allocationToken =
persistResource(
allocationToken
.asBuilder()
.setRegistrationBehavior(RegistrationBehavior.BYPASS_TLD_STATE)
.setDomainName(null)
.build());
// Trademarked domains using a bypass-tld-state token should succeed if we're in a quiet period
// after the sunrise period
persistResource(
Registry.get("tld")
.asBuilder()
.setTldStateTransitions(
ImmutableSortedMap.of(
START_OF_TIME,
QUIET_PERIOD,
clock.nowUtc().minusYears(1),
START_DATE_SUNRISE,
clock.nowUtc().minusMonths(1),
QUIET_PERIOD))
.build());
persistContactsAndHosts();
setEppInput("domain_create_allocationtoken_claims.xml");
runFlow();
assertSuccessfulCreate("tld", ImmutableSet.of(), allocationToken);
}
} }