1
0
mirror of https://github.com/google/nomulus synced 2026-05-21 23:31:51 +00:00

Compare commits

...

7 Commits

Author SHA1 Message Date
dependabot[bot]
654b165dff Bump http-cache-semantics from 4.1.0 to 4.1.1 in /console-webapp (#1929)
Bumps [http-cache-semantics](https://github.com/kornelski/http-cache-semantics) from 4.1.0 to 4.1.1.
- [Release notes](https://github.com/kornelski/http-cache-semantics/releases)
- [Commits](https://github.com/kornelski/http-cache-semantics/compare/v4.1.0...v4.1.1)

---
updated-dependencies:
- dependency-name: http-cache-semantics
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-06 13:22:50 -05:00
Lai Jiang
14d68d4cb2 Change base image for schema-verifier and schema-deployer (#1930)
Ubuntu 18.04 is entering EOL and the Cloud Build jobs are failing,
seemingly due to connection error to 18.04 repos:

https://pantheon.corp.google.com/cloud-build/builds;region=global/126a7c90-4322-41f1-ba1c-a10e38a32dab;step=5?project=domain-registry-dev

We use Debian 10 for the main builder, so it's better to keep everything
on the same schedule:

https://cs.opensource.google/nomulus/nomulus/+/master:release/builder/Dockerfile

Debian 10 is supported till June 2024:

https://wiki.debian.org/LTS
2023-02-06 13:09:37 -05:00
Lai Jiang
bbf405d566 Fix expand recurring billing event pipeline (#1928) 2023-02-06 11:33:57 -05:00
sarahcaseybot
356f7d0099 Modify DomainCreateFlow to check for an applicable defaultPromoToken (#1904)
* Modify DomainCreateFlow to check for an applicable defaultPromoToken

* Add handling for deleted tokens

* Change cache to allocation token cache

* Abstract away cache methods

* Use AllocationToken.getAll in create flow

* Filter out empty tokens
2023-02-01 14:53:51 -05:00
dependabot[bot]
70509cfe46 Bump ua-parser-js from 0.7.31 to 0.7.33 in /console-webapp (#1924)
Bumps [ua-parser-js](https://github.com/faisalman/ua-parser-js) from 0.7.31 to 0.7.33.
- [Release notes](https://github.com/faisalman/ua-parser-js/releases)
- [Changelog](https://github.com/faisalman/ua-parser-js/blob/master/changelog.md)
- [Commits](https://github.com/faisalman/ua-parser-js/compare/0.7.31...0.7.33)

---
updated-dependencies:
- dependency-name: ua-parser-js
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Lai Jiang <jianglai@google.com>
2023-01-31 14:52:26 -05:00
sarahcaseybot
5e081f4692 Prevent ending package tokens with active domains (#1919)
* Prevent ending package tokens with active domains

* Fix bad formatting in comments

* Fix lots of nits
2023-01-30 16:13:23 -05:00
Lai Jiang
07b87bbb4d Remove @IdAllocation annotation from repoId (#1923)
This annotation only works for Long or long field.
2023-01-30 15:40:40 -05:00
13 changed files with 497 additions and 30 deletions

View File

@@ -6824,9 +6824,9 @@
"dev": true
},
"node_modules/http-cache-semantics": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz",
"integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==",
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz",
"integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==",
"dev": true
},
"node_modules/http-deceiver": {
@@ -11362,9 +11362,9 @@
}
},
"node_modules/ua-parser-js": {
"version": "0.7.31",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz",
"integrity": "sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==",
"version": "0.7.33",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.33.tgz",
"integrity": "sha512-s8ax/CeZdK9R/56Sui0WM6y9OFREJarMRHqLB2EwkovemBxNQ+Bqu8GAsUnVcXKgphb++ghr/B2BZx4mahujPw==",
"dev": true,
"funding": [
{
@@ -17202,9 +17202,9 @@
"dev": true
},
"http-cache-semantics": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz",
"integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==",
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz",
"integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==",
"dev": true
},
"http-deceiver": {
@@ -20623,9 +20623,9 @@
"dev": true
},
"ua-parser-js": {
"version": "0.7.31",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz",
"integrity": "sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==",
"version": "0.7.33",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.33.tgz",
"integrity": "sha512-s8ax/CeZdK9R/56Sui0WM6y9OFREJarMRHqLB2EwkovemBxNQ+Bqu8GAsUnVcXKgphb++ghr/B2BZx4mahujPw==",
"dev": true
},
"unicode-canonical-property-names-ecmascript": {

View File

@@ -182,7 +182,7 @@ public class ExpandRecurringBillingEventsPipeline implements Serializable {
+ "AND event_Time < :endTime "
// Recurrence should not close before start time.
+ "AND :startTime < recurrence_end_time "
// Last expansion should happen at least one year before start time.
// Last expansion should happen at least one year before end time.
+ "AND recurrence_last_expansion < :oneYearAgo "
// The recurrence should not close before next expansion time.
+ "AND recurrence_last_expansion + INTERVAL '1 YEAR' < recurrence_end_time",
@@ -239,20 +239,44 @@ public class ExpandRecurringBillingEventsPipeline implements Serializable {
private void expandOneRecurring(Long recurringId, ImmutableSet.Builder<ImmutableObject> results) {
Recurring recurring = tm().loadByKey(Recurring.createVKey(recurringId));
// Determine the complete set of EventTimes this recurring event should expand to within
// [max(recurrenceLastExpansion + 1 yr, startTime), min(recurrenceEndTime, endTime)).
//
// This range should always be legal for recurrings that are returned from the query. However,
// it is possible that the recurring has changed between when the read transformation occurred
// and now. This could be caused by some out-of-process mutations (such as a domain deletion
// closing out a previously open-ended recurrence), or more subtly, Beam could execute the same
// work multiple times due to transient communication issues between workers and the scheduler.
// Such opportunistic retries are OK for pure functional transformations, but can cause
// unexpected behavior when side effects are executed more than once. For example, the
// recurrence_last_expansion field could be updated by a worker after a success expansion, which
// failed to report the status to the scheduler in time, which in turn scheduled another worker
// to work on the same batch. The second worker would see a new recurrence_last_expansion that
// causes the range to be illegal.
//
// The best way to handle any unexpected behavior is to simply drop the recurring from
// expansion, if its new state still calls for an expansion, it would be picked up the next time
// the pipeline runs.
ImmutableSet<DateTime> eventTimes;
try {
eventTimes =
ImmutableSet.copyOf(
recurring
.getRecurrenceTimeOfYear()
.getInstancesInRange(
Range.closedOpen(
latestOf(recurring.getRecurrenceLastExpansion().plusYears(1), startTime),
earliestOf(recurring.getRecurrenceEndTime(), endTime))));
} catch (IllegalArgumentException e) {
return;
}
recurringsInScopeCounter.inc();
Domain domain = tm().loadByKey(Domain.createVKey(recurring.getDomainRepoId()));
Registry tld = Registry.get(domain.getTld());
// Determine the complete set of EventTimes this recurring event should expand to within
// [max(recurrenceLastExpansion + 1 yr, startTime), min(recurrenceEndTime, endTime)).
ImmutableSet<DateTime> eventTimes =
ImmutableSet.copyOf(
recurring
.getRecurrenceTimeOfYear()
.getInstancesInRange(
Range.closedOpen(
latestOf(recurring.getRecurrenceLastExpansion().plusYears(1), startTime),
earliestOf(recurring.getRecurrenceEndTime(), endTime))));
// Find the times for which the OneTime billing event are already created, making this expansion
// idempotent. There is no need to match to the domain repo ID as the cancellation matching

View File

@@ -15,6 +15,8 @@
package google.registry.flows.domain;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static google.registry.flows.FlowUtils.persistEntityChanges;
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
@@ -52,6 +54,7 @@ import static google.registry.model.tld.Registry.TldState.QUIET_PERIOD;
import static google.registry.model.tld.Registry.TldState.START_DATE_SUNRISE;
import static google.registry.model.tld.label.ReservationType.NAME_COLLISION;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.CollectionUtils.isNullOrEmpty;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
import static google.registry.util.DateTimeUtils.leapSafeAddYears;
@@ -61,6 +64,7 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.net.InternetDomainName;
import google.registry.dns.DnsQueue;
import google.registry.flows.EppException;
import google.registry.flows.EppException.AssociationProhibitsOperationException;
import google.registry.flows.EppException.CommandUseErrorException;
import google.registry.flows.EppException.ParameterValuePolicyErrorException;
import google.registry.flows.ExtensionManager;
@@ -117,7 +121,9 @@ import google.registry.model.tld.Registry.TldType;
import google.registry.model.tld.label.ReservationType;
import google.registry.model.tmch.ClaimsList;
import google.registry.model.tmch.ClaimsListDao;
import google.registry.persistence.VKey;
import google.registry.tmch.LordnTaskUtils;
import java.util.Map;
import java.util.Optional;
import javax.annotation.Nullable;
import javax.inject.Inject;
@@ -270,6 +276,9 @@ public final class DomainCreateFlow implements TransactionalFlow {
registrarId,
now,
eppInput.getSingleExtension(AllocationTokenExtension.class));
if (!allocationToken.isPresent() && !registry.getDefaultPromoTokens().isEmpty()) {
allocationToken = checkForDefaultToken(registry, command);
}
boolean isAnchorTenant =
isAnchorTenant(
domainName, allocationToken, eppInput.getSingleExtension(MetadataExtension.class));
@@ -434,6 +443,36 @@ public final class DomainCreateFlow implements TransactionalFlow {
.build();
}
private Optional<AllocationToken> checkForDefaultToken(
Registry registry, DomainCommand.Create command) throws EppException {
Map<VKey<AllocationToken>, Optional<AllocationToken>> tokens =
AllocationToken.getAll(registry.getDefaultPromoTokens());
ImmutableList<Optional<AllocationToken>> tokenList =
registry.getDefaultPromoTokens().stream()
.map(tokens::get)
.filter(Optional::isPresent)
.collect(toImmutableList());
checkState(
!isNullOrEmpty(tokenList),
"Failure while loading default TLD promotions from the database");
// Check if any of the tokens are valid for this domain registration
for (Optional<AllocationToken> token : tokenList) {
try {
AllocationTokenFlowUtils.validateToken(
InternetDomainName.from(command.getDomainName()),
token.get(),
registrarId,
tm().getTransactionTime());
} catch (AssociationProhibitsOperationException e) {
// Allocation token was not valid for this registration, continue to check the next token in
// the list
continue;
}
// Only use the first valid token in the list
return token;
}
return Optional.empty();
}
/**
* Verifies that signed marks are only sent during sunrise.
*

View File

@@ -108,7 +108,7 @@ public class AllocationTokenFlowUtils {
*
* @throws EppException if the token is invalid in any way
*/
private static void validateToken(
public static void validateToken(
InternetDomainName domainName, AllocationToken token, String registrarId, DateTime now)
throws EppException {

View File

@@ -33,7 +33,6 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import google.registry.config.RegistryConfig;
import google.registry.dns.RefreshDnsAction;
import google.registry.model.annotations.IdAllocation;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.transfer.TransferData;
import google.registry.persistence.VKey;
@@ -68,7 +67,7 @@ public abstract class EppResource extends UpdateAutoTimestampEntity implements B
*
* @see <a href="https://tools.ietf.org/html/rfc5730">RFC 5730</a>
*/
@IdAllocation @Transient String repoId;
@Transient String repoId;
/**
* The ID of the registrar that is currently sponsoring this resource.

View File

@@ -16,16 +16,22 @@ package google.registry.model.domain.token;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static google.registry.config.RegistryConfig.getSingletonCacheRefreshDuration;
import static google.registry.model.domain.token.AllocationToken.TokenStatus.CANCELLED;
import static google.registry.model.domain.token.AllocationToken.TokenStatus.ENDED;
import static google.registry.model.domain.token.AllocationToken.TokenStatus.NOT_STARTED;
import static google.registry.model.domain.token.AllocationToken.TokenStatus.VALID;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.CollectionUtils.forceEmptyToNull;
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
@@ -34,6 +40,7 @@ import com.google.common.collect.Range;
import google.registry.flows.EppException;
import google.registry.flows.domain.DomainFlowUtils;
import google.registry.model.Buildable;
import google.registry.model.CacheUtils;
import google.registry.model.CreateAutoTimestamp;
import google.registry.model.UpdateAutoTimestampEntity;
import google.registry.model.billing.BillingEvent.RenewalPriceBehavior;
@@ -41,6 +48,7 @@ import google.registry.model.common.TimedTransitionProperty;
import google.registry.model.reporting.HistoryEntry.HistoryEntryId;
import google.registry.persistence.VKey;
import google.registry.persistence.WithVKey;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import javax.annotation.Nullable;
@@ -267,6 +275,39 @@ public class AllocationToken extends UpdateAutoTimestampEntity implements Builda
return STATIC_TOKEN_BEHAVIORS.getOrDefault(token, TokenBehavior.DEFAULT);
}
public static Optional<AllocationToken> get(VKey<AllocationToken> key) {
return ALLOCATION_TOKENS_CACHE.get(key);
}
public static Map<VKey<AllocationToken>, Optional<AllocationToken>> getAll(
ImmutableList<VKey<AllocationToken>> keys) {
return ALLOCATION_TOKENS_CACHE.getAll(keys);
}
/** A cache that loads the {@link AllocationToken} object for a given AllocationToken VKey. */
private static final LoadingCache<VKey<AllocationToken>, Optional<AllocationToken>>
ALLOCATION_TOKENS_CACHE =
CacheUtils.newCacheBuilder(getSingletonCacheRefreshDuration())
.build(
new CacheLoader<VKey<AllocationToken>, Optional<AllocationToken>>() {
@Override
public Optional<AllocationToken> load(VKey<AllocationToken> key) {
return tm().transact(() -> tm().loadByKeyIfPresent(key));
}
@Override
public Map<VKey<AllocationToken>, Optional<AllocationToken>> loadAll(
Iterable<? extends VKey<AllocationToken>> keys) {
ImmutableSet<VKey<AllocationToken>> keySet = ImmutableSet.copyOf(keys);
return tm().transact(
() ->
keySet.stream()
.collect(
toImmutableMap(
key -> key, key -> tm().loadByKeyIfPresent(key))));
}
});
@Override
public VKey<AllocationToken> createVKey() {
if (!AllocationToken.TokenBehavior.DEFAULT.equals(getTokenBehavior())) {

View File

@@ -18,9 +18,11 @@ import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.collect.Sets.difference;
import static google.registry.model.billing.BillingEvent.RenewalPriceBehavior.DEFAULT;
import static google.registry.model.domain.token.AllocationToken.TokenType.PACKAGE;
import static google.registry.model.domain.token.AllocationToken.TokenType.SINGLE_USE;
import static google.registry.model.domain.token.AllocationToken.TokenType.UNLIMITED_USE;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.CollectionUtils.isNullOrEmpty;
import static google.registry.util.CollectionUtils.nullToEmpty;
import static google.registry.util.StringGenerator.DEFAULT_PASSWORD_LENGTH;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -253,6 +255,22 @@ class GenerateAllocationTokensCommand implements Command {
!ImmutableList.of("").equals(allowedTlds),
"Either omit --allowed_tlds if all TLDs are allowed, or include a comma-separated list");
if (!isNullOrEmpty(tokenStatusTransitions)) {
// Don't allow package tokens to be created with a scheduled end time since this could allow
// future domains to be attributed to the package and never be billed. Package promotion
// tokens should only be scheduled to end with a brief time period before the status
// transition occurs so that no new domains are registered using that token between when the
// status is scheduled and when the transition occurs.
// TODO(@sarahbot): Create a cleaner way to handle ending packages once we actually have
// customers using them
boolean hasEnding =
tokenStatusTransitions.containsValue(TokenStatus.ENDED)
|| tokenStatusTransitions.containsValue(TokenStatus.CANCELLED);
checkArgument(
!(PACKAGE.equals(tokenType) && hasEnding),
"PACKAGE tokens should not be generated with ENDED or CANCELLED in their transition map");
}
if (tokenStrings != null) {
verifyTokenStringsDoNotExist();
}

View File

@@ -14,6 +14,7 @@
package google.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.collect.Iterables.partition;
@@ -31,6 +32,7 @@ import google.registry.model.billing.BillingEvent.RenewalPriceBehavior;
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.TokenType;
import google.registry.tools.params.TransitionListParameter.TokenStatusTransitions;
import java.util.List;
import java.util.Map;
@@ -114,6 +116,7 @@ final class UpdateAllocationTokensCommand extends UpdateOrDeleteAllocationTokens
private static final Joiner JOINER = Joiner.on(", ");
private ImmutableSet<AllocationToken> tokensToSave;
private boolean endToken = false;
@Override
public void init() {
@@ -126,6 +129,12 @@ final class UpdateAllocationTokensCommand extends UpdateOrDeleteAllocationTokens
allowedTlds = ImmutableList.of();
}
if (tokenStatusTransitions != null
&& (tokenStatusTransitions.containsValue(TokenStatus.ENDED)
|| tokenStatusTransitions.containsValue(TokenStatus.CANCELLED))) {
endToken = true;
}
tokensToSave =
tm().transact(
() ->
@@ -157,6 +166,19 @@ final class UpdateAllocationTokensCommand extends UpdateOrDeleteAllocationTokens
}
private AllocationToken updateToken(AllocationToken original) {
if (endToken && original.getTokenType().equals(TokenType.PACKAGE)) {
Long domainsInPackage =
tm().query("SELECT COUNT(*) FROM Domain WHERE currentPackageToken = :token", Long.class)
.setParameter("token", original.createVKey())
.getSingleResult();
checkArgument(
domainsInPackage == 0,
"Package token %s can not end its promotion because it still has %s domains in the"
+ " package",
original.getToken(),
domainsInPackage);
}
AllocationToken.Builder builder = original.asBuilder();
Optional.ofNullable(allowedClientIds)
.ifPresent(clientIds -> builder.setAllowedRegistrarIds(ImmutableSet.copyOf(clientIds)));

View File

@@ -25,6 +25,7 @@ import static google.registry.model.billing.BillingEvent.RenewalPriceBehavior.DE
import static google.registry.model.billing.BillingEvent.RenewalPriceBehavior.NONPREMIUM;
import static google.registry.model.billing.BillingEvent.RenewalPriceBehavior.SPECIFIED;
import static google.registry.model.domain.fee.Fee.FEE_EXTENSION_URIS;
import static google.registry.model.domain.token.AllocationToken.TokenType.DEFAULT_PROMO;
import static google.registry.model.domain.token.AllocationToken.TokenType.PACKAGE;
import static google.registry.model.domain.token.AllocationToken.TokenType.SINGLE_USE;
import static google.registry.model.domain.token.AllocationToken.TokenType.UNLIMITED_USE;
@@ -1612,6 +1613,239 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
.marshalsToXml();
}
@Test
void testSuccess_usesDefaultToken() throws Exception {
persistContactsAndHosts();
AllocationToken defaultToken1 =
persistResource(
new AllocationToken.Builder()
.setToken("aaaaa")
.setTokenType(DEFAULT_PROMO)
.setAllowedRegistrarIds(ImmutableSet.of("NewRegistrar"))
.setAllowedTlds(ImmutableSet.of("tld"))
.build());
AllocationToken defaultToken2 =
persistResource(
new AllocationToken.Builder()
.setToken("bbbbb")
.setTokenType(DEFAULT_PROMO)
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setAllowedTlds(ImmutableSet.of("tld"))
.build());
persistResource(
Registry.get("tld")
.asBuilder()
.setDefaultPromoTokens(
ImmutableList.of(defaultToken1.createVKey(), defaultToken2.createVKey()))
.build());
runTest_defaultToken("bbbbb");
}
@Test
void testSuccess_doesNotUseDefaultTokenWhenTokenPassedIn() throws Exception {
persistContactsAndHosts();
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(UNLIMITED_USE)
.setDiscountFraction(0.5)
.build());
AllocationToken defaultToken1 =
persistResource(
new AllocationToken.Builder()
.setToken("aaaaa")
.setTokenType(DEFAULT_PROMO)
.setAllowedRegistrarIds(ImmutableSet.of("NewRegistrar"))
.setAllowedTlds(ImmutableSet.of("tld"))
.build());
AllocationToken defaultToken2 =
persistResource(
new AllocationToken.Builder()
.setToken("bbbbb")
.setTokenType(DEFAULT_PROMO)
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setAllowedTlds(ImmutableSet.of("tld"))
.build());
persistResource(
Registry.get("tld")
.asBuilder()
.setDefaultPromoTokens(
ImmutableList.of(defaultToken1.createVKey(), defaultToken2.createVKey()))
.build());
clock.advanceOneMilli();
setEppInput(
"domain_create_allocationtoken.xml",
ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2"));
runFlowAssertResponse(
loadFile("domain_create_response.xml", ImmutableMap.of("DOMAIN", "example.tld")));
BillingEvent.OneTime billingEvent =
Iterables.getOnlyElement(tm().transact(() -> tm().loadAllOf(BillingEvent.OneTime.class)));
assertThat(billingEvent.getTargetId()).isEqualTo("example.tld");
assertThat(billingEvent.getCost()).isEqualTo(Money.of(USD, BigDecimal.valueOf(19.5)));
assertThat(billingEvent.getAllocationToken().get().getKey()).isEqualTo("abc123");
}
@Test
void testSuccess_noValidDefaultToken() throws Exception {
persistContactsAndHosts();
AllocationToken defaultToken1 =
persistResource(
new AllocationToken.Builder()
.setToken("aaaaa")
.setTokenType(DEFAULT_PROMO)
.setAllowedRegistrarIds(ImmutableSet.of("NewRegistrar"))
.setAllowedTlds(ImmutableSet.of("tld"))
.build());
AllocationToken defaultToken2 =
persistResource(
new AllocationToken.Builder()
.setToken("bbbbb")
.setTokenType(DEFAULT_PROMO)
.setAllowedRegistrarIds(ImmutableSet.of("OtherRegistrar"))
.setAllowedTlds(ImmutableSet.of("tld"))
.build());
persistResource(
Registry.get("tld")
.asBuilder()
.setDefaultPromoTokens(
ImmutableList.of(defaultToken1.createVKey(), defaultToken2.createVKey()))
.build());
doSuccessfulTest();
}
void testSuccess_onlyUseFirstValidDefaultToken() throws Exception {
persistContactsAndHosts();
AllocationToken defaultToken1 =
persistResource(
new AllocationToken.Builder()
.setToken("aaaaa")
.setTokenType(DEFAULT_PROMO)
.setAllowedRegistrarIds(ImmutableSet.of("NewRegistrar"))
.setAllowedTlds(ImmutableSet.of("tld"))
.build());
AllocationToken defaultToken2 =
persistResource(
new AllocationToken.Builder()
.setToken("bbbbb")
.setTokenType(DEFAULT_PROMO)
.setAllowedRegistrarIds(ImmutableSet.of("NewRegistrar"))
.setAllowedTlds(ImmutableSet.of("tld"))
.build());
persistResource(
Registry.get("tld")
.asBuilder()
.setDefaultPromoTokens(
ImmutableList.of(defaultToken1.createVKey(), defaultToken2.createVKey()))
.build());
runTest_defaultToken("aaaaa");
}
void testSuccess_registryHasDeletedDefaultToken() throws Exception {
persistContactsAndHosts();
AllocationToken defaultToken1 =
persistResource(
new AllocationToken.Builder()
.setToken("aaaaa")
.setTokenType(DEFAULT_PROMO)
.setAllowedRegistrarIds(ImmutableSet.of("NewRegistrar"))
.setAllowedTlds(ImmutableSet.of("tld"))
.build());
AllocationToken defaultToken2 =
persistResource(
new AllocationToken.Builder()
.setToken("bbbbb")
.setTokenType(DEFAULT_PROMO)
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setAllowedTlds(ImmutableSet.of("tld"))
.build());
persistResource(
Registry.get("tld")
.asBuilder()
.setDefaultPromoTokens(
ImmutableList.of(defaultToken1.createVKey(), defaultToken2.createVKey()))
.build());
DatabaseHelper.deleteResource(defaultToken1);
runTest_defaultToken("bbbbb");
}
@Test
void testSuccess_defaultTokenAppliesCorrectPrice() throws Exception {
persistContactsAndHosts();
AllocationToken defaultToken1 =
persistResource(
new AllocationToken.Builder()
.setToken("aaaaa")
.setTokenType(DEFAULT_PROMO)
.setAllowedRegistrarIds(ImmutableSet.of("NewRegistrar"))
.setAllowedTlds(ImmutableSet.of("tld"))
.build());
AllocationToken defaultToken2 =
persistResource(
new AllocationToken.Builder()
.setToken("bbbbb")
.setTokenType(DEFAULT_PROMO)
.setDiscountFraction(0.5)
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setAllowedTlds(ImmutableSet.of("tld"))
.build());
persistResource(
Registry.get("tld")
.asBuilder()
.setDefaultPromoTokens(
ImmutableList.of(defaultToken1.createVKey(), defaultToken2.createVKey()))
.build());
BillingEvent.OneTime billingEvent = runTest_defaultToken("bbbbb");
assertThat(billingEvent.getCost()).isEqualTo(Money.of(USD, BigDecimal.valueOf(19.5)));
}
@Test
void testSuccess_skipsOverMissingDefaultToken() throws Exception {
persistContactsAndHosts();
AllocationToken defaultToken1 =
persistResource(
new AllocationToken.Builder()
.setToken("aaaaa")
.setTokenType(DEFAULT_PROMO)
.setAllowedRegistrarIds(ImmutableSet.of("NewRegistrar"))
.setAllowedTlds(ImmutableSet.of("tld"))
.build());
AllocationToken defaultToken2 =
persistResource(
new AllocationToken.Builder()
.setToken("bbbbb")
.setTokenType(DEFAULT_PROMO)
.setDiscountFraction(0.5)
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setAllowedTlds(ImmutableSet.of("tld"))
.build());
persistResource(
Registry.get("tld")
.asBuilder()
.setDefaultPromoTokens(
ImmutableList.of(defaultToken1.createVKey(), defaultToken2.createVKey()))
.build());
DatabaseHelper.deleteResource(defaultToken1);
BillingEvent.OneTime billingEvent = runTest_defaultToken("bbbbb");
assertThat(billingEvent.getCost()).isEqualTo(Money.of(USD, BigDecimal.valueOf(19.5)));
}
BillingEvent.OneTime runTest_defaultToken(String token) throws Exception {
setEppInput("domain_create.xml", ImmutableMap.of("DOMAIN", "example.tld"));
runFlowAssertResponse(
loadFile(
"domain_create_response_wildcard.xml",
new ImmutableMap.Builder<String, String>()
.put("DOMAIN", "example.tld")
.put("CRDATE", "1999-04-03T22:00:00.0Z")
.put("EXDATE", "2001-04-03T22:00:00.0Z")
.build()));
BillingEvent.OneTime billingEvent =
Iterables.getOnlyElement(tm().transact(() -> tm().loadAllOf(BillingEvent.OneTime.class)));
assertThat(billingEvent.getTargetId()).isEqualTo("example.tld");
assertThat(billingEvent.getAllocationToken().get().getKey()).isEqualTo(token);
return billingEvent;
}
@Test
void testSuccess_superuserReserved() throws Exception {
setEppInput("domain_create_reserved.xml");

View File

@@ -395,6 +395,26 @@ class GenerateAllocationTokensCommandTest extends CommandTestCase<GenerateAlloca
.isInstanceOf(IllegalArgumentException.class);
}
@Test
void testFailure_invalidPackageTokenStatusTransition() {
assertThat(
assertThrows(
IllegalArgumentException.class,
() ->
runCommand(
"--number",
"999",
"--type",
"PACKAGE",
String.format(
"--token_status_transitions=\"%s=NOT_STARTED,%s=VALID,%s=ENDED\"",
START_OF_TIME, fakeClock.nowUtc(), fakeClock.nowUtc().plusDays(1)))))
.hasMessageThat()
.isEqualTo(
"PACKAGE tokens should not be generated with ENDED or CANCELLED in their transition"
+ " map");
}
@Test
void testFailure_lengthOfZero() {
IllegalArgumentException thrown =

View File

@@ -22,9 +22,12 @@ import static google.registry.model.domain.token.AllocationToken.TokenStatus.CAN
import static google.registry.model.domain.token.AllocationToken.TokenStatus.ENDED;
import static google.registry.model.domain.token.AllocationToken.TokenStatus.NOT_STARTED;
import static google.registry.model.domain.token.AllocationToken.TokenStatus.VALID;
import static google.registry.model.domain.token.AllocationToken.TokenType.PACKAGE;
import static google.registry.model.domain.token.AllocationToken.TokenType.SINGLE_USE;
import static google.registry.model.domain.token.AllocationToken.TokenType.UNLIMITED_USE;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.loadByEntity;
import static google.registry.testing.DatabaseHelper.persistActiveDomain;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static org.joda.time.DateTimeZone.UTC;
@@ -255,7 +258,7 @@ class UpdateAllocationTokensCommandTest extends CommandTestCase<UpdateAllocation
@Test
void testUpdateStatusTransitions() throws Exception {
DateTime now = DateTime.now(UTC);
DateTime now = fakeClock.nowUtc();
AllocationToken token = persistResource(builderWithPromo().build());
runCommandForced(
"--prefix",
@@ -270,7 +273,7 @@ class UpdateAllocationTokensCommandTest extends CommandTestCase<UpdateAllocation
@Test
void testUpdateStatusTransitions_badTransitions() {
DateTime now = DateTime.now(UTC);
DateTime now = fakeClock.nowUtc();
persistResource(builderWithPromo().build());
IllegalArgumentException thrown =
assertThrows(
@@ -288,6 +291,73 @@ class UpdateAllocationTokensCommandTest extends CommandTestCase<UpdateAllocation
.isEqualTo("tokenStatusTransitions map cannot transition from NOT_STARTED to ENDED.");
}
@Test
void testUpdateStatusTransitions_endPackageTokenNoDomains() throws Exception {
DateTime now = fakeClock.nowUtc();
AllocationToken token =
persistResource(
new AllocationToken.Builder()
.setToken("token")
.setTokenType(PACKAGE)
.setRenewalPriceBehavior(SPECIFIED)
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setTokenStatusTransitions(
ImmutableSortedMap.<DateTime, TokenStatus>naturalOrder()
.put(START_OF_TIME, NOT_STARTED)
.put(now.minusDays(1), VALID)
.build())
.build());
runCommandForced(
"--prefix",
"token",
"--token_status_transitions",
String.format(
"\"%s=NOT_STARTED,%s=VALID,%s=ENDED\"", START_OF_TIME, now.minusDays(1), now));
token = reloadResource(token);
assertThat(token.getTokenStatusTransitions().toValueMap())
.containsExactly(START_OF_TIME, NOT_STARTED, now.minusDays(1), VALID, now, ENDED);
}
@Test
void testUpdateStatusTransitions_endPackageTokenWithActiveDomainsFails() throws Exception {
DateTime now = fakeClock.nowUtc();
AllocationToken token =
persistResource(
new AllocationToken.Builder()
.setToken("token")
.setTokenType(PACKAGE)
.setRenewalPriceBehavior(SPECIFIED)
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setTokenStatusTransitions(
ImmutableSortedMap.<DateTime, TokenStatus>naturalOrder()
.put(START_OF_TIME, NOT_STARTED)
.put(now.minusDays(1), VALID)
.build())
.build());
createTld("tld");
persistResource(
persistActiveDomain("example.tld")
.asBuilder()
.setCurrentPackageToken(token.createVKey())
.build());
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() ->
runCommandForced(
"--prefix",
"token",
"--token_status_transitions",
String.format(
"\"%s=NOT_STARTED,%s=VALID,%s=ENDED\"",
START_OF_TIME, now.minusDays(1), now)));
assertThat(thrown)
.hasMessageThat()
.isEqualTo(
"Package token token can not end its promotion because it still has 1 domains in the"
+ " package");
}
@Test
void testUpdate_onlyWithPrefix() throws Exception {
AllocationToken token =

View File

@@ -23,7 +23,7 @@
# Although any Linux-based Java image with bash would work (e.g., openjdk:11),
# as a GCP application we prefer to start with a GCP-approved base image.
FROM marketplace.gcr.io/google/ubuntu1804
FROM marketplace.gcr.io/google/debian10
ENV DEBIAN_FRONTEND=noninteractive LANG=en_US.UTF-8
# Install openjdk-11
RUN apt-get update -y \

View File

@@ -22,7 +22,7 @@
# Please refer to verify_deployed_sql_schema.sh for expected volumes and
# arguments.
FROM marketplace.gcr.io/google/ubuntu1804
FROM marketplace.gcr.io/google/debian10
ENV DEBIAN_FRONTEND=noninteractive LANG=en_US.UTF-8
# Install pg_dump v11 (same as current server version). This needs to be
# downloaded from postgresql's own repo, because ubuntu1804 is too old. With a