Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions core/src/main/java/google/registry/config/RegistryConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import static com.google.common.base.Suppliers.memoize;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static com.google.common.collect.ImmutableSortedMap.toImmutableSortedMap;
import static google.registry.config.ConfigUtils.makeUrl;
import static google.registry.util.DateTimeUtils.START_INSTANT;
Expand Down Expand Up @@ -49,6 +50,7 @@
import jakarta.mail.internet.InternetAddress;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.math.BigDecimal;
import java.net.URI;
import java.net.URL;
import java.time.DayOfWeek;
Expand All @@ -59,6 +61,7 @@
import java.util.function.Supplier;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.joda.money.CurrencyUnit;

/**
* Central clearing-house for all configuration.
Expand Down Expand Up @@ -1110,6 +1113,40 @@ public static String provideRegistryAdminClientId(RegistryConfigSettings config)
return config.registryPolicy.registryAdminClientId;
}

/** Returns the total length of the Expiry Access Period. */
@Provides
@Config("domainExpiryAccessPeriodTotalLength")
public static Duration provideDomainExpiryAccessPeriodTotalLength(
RegistryConfigSettings config) {
return Duration.ofSeconds(config.registryPolicy.domainExpiryAccessPeriod.totalLengthSeconds);
}

/** Returns the length of each tier of the Expiry Access Period. */
@Provides
@Config("domainExpiryAccessPeriodTierLength")
public static Duration provideDomainExpiryAccessPeriodTierLength(
RegistryConfigSettings config) {
return Duration.ofSeconds(config.registryPolicy.domainExpiryAccessPeriod.tierLengthSeconds);
}

/** Returns a map of the fee for the first tier of the Expiry Access Period, by currency. */
@Provides
@Config("domainExpiryAccessPeriodInitialFee")
public static ImmutableMap<CurrencyUnit, BigDecimal> provideDomainExpiryAccessPeriodInitialFee(
RegistryConfigSettings config) {
return config.registryPolicy.domainExpiryAccessPeriod.initialFee.entrySet().stream()
.collect(toImmutableMap(entry -> CurrencyUnit.of(entry.getKey()), Entry::getValue));
}

/** Returns a map of the fee for the last tier of the Expiry Access Period, by currency. */
@Provides
@Config("domainExpiryAccessPeriodFinalFee")
public static ImmutableMap<CurrencyUnit, BigDecimal> provideDomainExpiryAccessPeriodFinalFee(
RegistryConfigSettings config) {
return config.registryPolicy.domainExpiryAccessPeriod.finalFee.entrySet().stream()
.collect(toImmutableMap(entry -> CurrencyUnit.of(entry.getKey()), Entry::getValue));
}

@Singleton
@Provides
static RegistryConfigSettings provideRegistryConfigSettings() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

package google.registry.config;

import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.Set;
Expand Down Expand Up @@ -94,6 +95,7 @@ public static class RegistryPolicy {
public String tmchCrlUrl;
public String tmchMarksDbUrl;
public String registryAdminClientId;
public DomainExpiryAccessPeriod domainExpiryAccessPeriod;
public String premiumTermsExportDisclaimer;
public String reservedTermsExportDisclaimer;
public String rdapTos;
Expand All @@ -106,6 +108,13 @@ public static class RegistryPolicy {
public Set<String> noPollMessageOnDeletionRegistrarIds;
}

public static class DomainExpiryAccessPeriod {
public long totalLengthSeconds;
public long tierLengthSeconds;
public Map<String, BigDecimal> initialFee;
public Map<String, BigDecimal> finalFee;
}

/** Configuration for Hibernate. */
public static class Hibernate {
public boolean allowNestedTransactions;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,28 @@ registryPolicy:
# registrar
registryAdminClientId: TheRegistrar

# Configuration for the Expiry Access Period (XAP) that occurs immediately
# following completion of a domain's deletion. This needs to be enabled on a
# per-TLD basis as well.
domainExpiryAccessPeriod:
# The length of the total expiry access period; defaults to 10 days.
totalLengthSeconds: 864000
# The length of each tier during the expiry access period; defaults to 1
# hour. The XAP fee remains constant for the duration of each tier. The
# total length must be evenly divisible by the tier length, i.e. there
# should be a whole number of tiers all the same length.
tierLengthSeconds: 3600
# The initial fee for the first tier when the expiry access period starts,
# specified for each currency this registry platform uses.
initialFee:
USD: 100000
JPY: 10000000
# The final fee for the last tier when the expiry access period ends,
# specified for each currency this registry platform uses.
finalFee:
USD: 10
JPY: 1000

# Disclaimer at the top of the exported premium terms list.
premiumTermsExportDisclaimer: |
This list contains domains for the TLD offered at a premium price. This
Expand Down
26 changes: 25 additions & 1 deletion core/src/main/java/google/registry/flows/ResourceFlowUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import google.registry.persistence.VKey;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
import java.util.Objects;
Expand Down Expand Up @@ -100,15 +101,38 @@ public static <R extends EppResource> R verifyExistence(

public static <R extends EppResource> void verifyResourceDoesNotExist(
Class<R> clazz, String targetId, Instant now, String registrarId) throws EppException {
Optional<R> resource = ForeignKeyUtils.loadResource(clazz, targetId, now);
verifyResourceDoesNotExist(clazz, targetId, now, Duration.ZERO, registrarId);
}

/**
* Verifies that a resource with the specified type and id did not exist at the specified time.
*
* <p>This will throw an exception if the resource exists with a deletionTime greater than the
* specified time (i.e. a registrar is attempting to create a duplicate domain, host, or contact).
* If the resource had existed within the specified lookback window, but is not active now, i.e.
* it is recently deleted, then don't throw an exception, but do return the recently deleted
* resource for further processing (this is used by the Expiry Access Period).
*
* <p>If there is no need to specially handle the situation of recently deleted resources, then
* simply pass a lookback duration of {@link Duration#ZERO}.
*/
public static <R extends EppResource> Optional<R> verifyResourceDoesNotExist(
Class<R> clazz, String targetId, Instant now, Duration lookback, String registrarId)
throws EppException {
Instant startOfWindow = now.minus(lookback);
Optional<R> resource = ForeignKeyUtils.loadResource(clazz, targetId, startOfWindow);
if (resource.isPresent()) {
if (resource.get().getDeletionTime().isBefore(now)) {
return resource;
}
// These are similar exceptions, but we can track them internally as log-based metrics.
if (Objects.equals(registrarId, resource.get().getPersistedCurrentSponsorRegistrarId())) {
throw new ResourceAlreadyExistsForThisClientException(targetId);
} else {
throw new ResourceCreateContentionException(targetId);
}
}
return Optional.empty();
}

/** Check that the given AuthInfo is present for a resource being transferred. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import static google.registry.flows.domain.DomainFlowUtils.getReservationTypes;
import static google.registry.flows.domain.DomainFlowUtils.handleFeeRequest;
import static google.registry.flows.domain.DomainFlowUtils.isAnchorTenant;
import static google.registry.flows.domain.DomainFlowUtils.isDomainEligibleForXap;
import static google.registry.flows.domain.DomainFlowUtils.isRegisterBsaCreate;
import static google.registry.flows.domain.DomainFlowUtils.isReserved;
import static google.registry.flows.domain.DomainFlowUtils.isValidReservedCreate;
Expand Down Expand Up @@ -75,13 +76,15 @@
import google.registry.model.eppoutput.CheckData.DomainCheckData;
import google.registry.model.eppoutput.EppResponse;
import google.registry.model.eppoutput.EppResponse.ResponseExtension;
import google.registry.model.registrar.Registrar;
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
import google.registry.model.tld.Tld;
import google.registry.model.tld.Tld.TldState;
import google.registry.model.tld.label.ReservationType;
import google.registry.persistence.VKey;
import google.registry.util.Clock;
import jakarta.inject.Inject;
import java.time.Duration;
import java.time.Instant;
import java.util.Collection;
import java.util.HashSet;
Expand Down Expand Up @@ -138,6 +141,10 @@ public final class DomainCheckFlow implements TransactionalFlow {
@Config("maxChecks")
int maxChecks;

@Inject
@Config("domainExpiryAccessPeriodTotalLength")
Duration domainExpiryAccessPeriodTotalLength;

@Inject @Superuser boolean isSuperuser;
@Inject Clock clock;
@Inject EppResponse.Builder responseBuilder;
Expand Down Expand Up @@ -249,18 +256,33 @@ private Optional<String> getMessageForCheck(
return Optional.of(e.getMessage());
}
return getMessageForCheckWithToken(
idn, existingDomains, bsaBlockedDomainNames, tldStates, token);
idn, existingDomains, bsaBlockedDomainNames, tldStates, token, now);
}

private static Optional<Domain> loadDomainIfInXap(
String domainName, Instant now, Duration domainExpiryAccessPeriodTotalLength) {
return ForeignKeyUtils.loadResource(
Domain.class, domainName, now.minus(domainExpiryAccessPeriodTotalLength))
.filter(domain -> isDomainEligibleForXap(domain, Tld.get(domain.getTld()), now));
}

private Optional<String> getMessageForCheckWithToken(
InternetDomainName domainName,
ImmutableMap<String, VKey<Domain>> existingDomains,
ImmutableSet<InternetDomainName> bsaBlockedDomains,
ImmutableMap<String, TldState> tldStates,
Optional<AllocationToken> allocationToken) {
Optional<AllocationToken> allocationToken,
Instant now) {
if (existingDomains.containsKey(domainName.toString())) {
return Optional.of("In use");
}
Tld tld = Tld.get(domainName.parent().toString());
if (tld.getExpiryAccessPeriodModeAt(now) == Tld.ExpiryAccessPeriodMode.ENABLED
&& !Registrar.loadByRegistrarIdCached(registrarId).get().getExpiryAccessPeriodEnabled()
&& loadDomainIfInXap(domainName.toString(), now, domainExpiryAccessPeriodTotalLength)
.isPresent()) {
return Optional.of("Reserved");
}
TldState tldState = tldStates.get(domainName.parent().toString());
if (isReserved(domainName, START_DATE_SUNRISE.equals(tldState))) {
if (!isValidReservedCreate(domainName, allocationToken)
Expand Down Expand Up @@ -339,16 +361,27 @@ private ImmutableList<? extends ResponseExtension> getResponseExtensions(
.build());
continue;
}
Optional<Domain> domainForFee = domain;
boolean isAvailable = availableDomains.contains(domainName);
if (isAvailable
&& feeCheckItem.getCommandName().equals(FeeQueryCommandExtensionItem.CommandName.CREATE)
&& tld.getExpiryAccessPeriodModeAt(now) == Tld.ExpiryAccessPeriodMode.ENABLED) {
Optional<Domain> recentlyDeletedDomain =
loadDomainIfInXap(domainName, now, domainExpiryAccessPeriodTotalLength);
if (recentlyDeletedDomain.isPresent()) {
domainForFee = recentlyDeletedDomain;
}
}
handleFeeRequest(
feeCheckItem,
builder,
domainNames.get(domainName),
domain,
domainForFee,
feeCheck.getCurrency(),
now,
pricingLogic,
token,
availableDomains.contains(domainName),
isAvailable,
recurrences.getOrDefault(domainName, null));
// In the case of a registrar that is running a tiered pricing promotion, we issue two
// responses for the CREATE fee check command: one (the default response) with the
Expand Down
Loading
Loading