diff --git a/core/src/main/java/google/registry/model/tld/Registry.java b/core/src/main/java/google/registry/model/tld/Registry.java index 4d6643d91..b8c3a16d7 100644 --- a/core/src/main/java/google/registry/model/tld/Registry.java +++ b/core/src/main/java/google/registry/model/tld/Registry.java @@ -30,6 +30,7 @@ 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.Joiner; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedMap; @@ -45,11 +46,14 @@ import google.registry.model.UnsafeSerializable; import google.registry.model.common.TimedTransitionProperty; import google.registry.model.domain.fee.BaseFee.FeeType; import google.registry.model.domain.fee.Fee; +import google.registry.model.domain.token.AllocationToken; +import google.registry.model.domain.token.AllocationToken.TokenType; import google.registry.model.tld.label.PremiumList; import google.registry.model.tld.label.ReservedList; import google.registry.persistence.VKey; import google.registry.persistence.converter.JodaMoneyType; import google.registry.util.Idn; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -451,6 +455,18 @@ public class Registry extends ImmutableObject implements Buildable, UnsafeSerial /** An allowlist of hosts allowed to be used on domains on this TLD (ignored if empty). */ @Nullable Set allowedFullyQualifiedHostNames; + /** + * References to allocation tokens that can be used on the TLD if no other token is passed in on a + * domain create. + * + *

Ordering is important for this field as it will determine which token is used if multiple + * tokens in the list are valid for a specific registration. It is crucial that modifications to + * this field only modify the entire list contents. Modifications to a single token in the list + * (ex: add a token to the list or remove a token from the list) should not be allowed without + * resetting the entire list contents. + */ + List> defaultPromoTokens; + public String getTldStr() { return tldStr; } @@ -639,6 +655,10 @@ public class Registry extends ImmutableObject implements Buildable, UnsafeSerial return nullToEmptyImmutableCopy(allowedFullyQualifiedHostNames); } + public ImmutableList> getDefaultPromoTokens() { + return nullToEmptyImmutableCopy(defaultPromoTokens); + } + @Override public Builder asBuilder() { return new Builder(clone(this)); @@ -900,6 +920,28 @@ public class Registry extends ImmutableObject implements Buildable, UnsafeSerial return this; } + public Builder setDefaultPromoTokens(ImmutableList> promoTokens) { + tm().transact( + () -> { + for (VKey tokenKey : promoTokens) { + AllocationToken token = tm().loadByKey(tokenKey); + checkArgument( + token.getTokenType().equals(TokenType.DEFAULT_PROMO), + String.format( + "Token %s has an invalid token type of %s. DefaultPromoTokens must be of" + + " the type DEFAULT_PROMO", + token.getToken(), token.getTokenType())); + checkArgument( + token.getAllowedTlds().contains(getInstance().tldStr), + String.format( + "The token %s is not valid for this TLD. The valid TLDs for it are %s", + token.getToken(), token.getAllowedTlds())); + } + getInstance().defaultPromoTokens = promoTokens; + }); + return this; + } + @Override public Registry build() { final Registry instance = getInstance(); diff --git a/core/src/main/java/google/registry/persistence/converter/AllocationTokenListConverter.java b/core/src/main/java/google/registry/persistence/converter/AllocationTokenListConverter.java new file mode 100644 index 000000000..0ad13bca9 --- /dev/null +++ b/core/src/main/java/google/registry/persistence/converter/AllocationTokenListConverter.java @@ -0,0 +1,33 @@ +// Copyright 2022 The Nomulus Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package google.registry.persistence.converter; + +import google.registry.model.domain.token.AllocationToken; +import google.registry.persistence.VKey; +import javax.persistence.Converter; + +@Converter(autoApply = true) +public class AllocationTokenListConverter extends StringListConverterBase> { + + @Override + String toString(VKey element) { + return element.getKey().toString(); + } + + @Override + VKey fromString(String value) { + return VKey.create(AllocationToken.class, value); + } +} diff --git a/core/src/main/resources/META-INF/persistence.xml b/core/src/main/resources/META-INF/persistence.xml index 0be2ca382..9dbc95e7f 100644 --- a/core/src/main/resources/META-INF/persistence.xml +++ b/core/src/main/resources/META-INF/persistence.xml @@ -78,6 +78,7 @@ google.registry.model.domain.RegistryLock + google.registry.persistence.converter.AllocationTokenListConverter google.registry.persistence.converter.AllocationTokenStatusTransitionConverter google.registry.persistence.converter.BillingCostTransitionConverter google.registry.persistence.converter.BillingEventFlagSetConverter diff --git a/core/src/test/java/google/registry/model/tld/RegistryTest.java b/core/src/test/java/google/registry/model/tld/RegistryTest.java index f02b57443..dd5f37719 100644 --- a/core/src/test/java/google/registry/model/tld/RegistryTest.java +++ b/core/src/test/java/google/registry/model/tld/RegistryTest.java @@ -17,6 +17,8 @@ package google.registry.model.tld; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static com.google.common.truth.Truth8.assertThat; +import static google.registry.model.domain.token.AllocationToken.TokenType.DEFAULT_PROMO; +import static google.registry.model.domain.token.AllocationToken.TokenType.SINGLE_USE; import static google.registry.model.tld.Registry.TldState.GENERAL_AVAILABILITY; import static google.registry.model.tld.Registry.TldState.PREDELEGATION; import static google.registry.model.tld.Registry.TldState.QUIET_PERIOD; @@ -26,6 +28,7 @@ import static google.registry.testing.DatabaseHelper.createTld; import static google.registry.testing.DatabaseHelper.newRegistry; import static google.registry.testing.DatabaseHelper.persistPremiumList; import static google.registry.testing.DatabaseHelper.persistReservedList; +import static google.registry.testing.DatabaseHelper.persistResource; import static google.registry.util.DateTimeUtils.END_OF_TIME; import static google.registry.util.DateTimeUtils.START_OF_TIME; import static java.math.RoundingMode.UNNECESSARY; @@ -38,11 +41,13 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedMap; import google.registry.dns.writer.VoidDnsWriter; import google.registry.model.EntityTestCase; +import google.registry.model.domain.token.AllocationToken; import google.registry.model.tld.Registry.RegistryNotFoundException; import google.registry.model.tld.Registry.TldState; import google.registry.model.tld.label.PremiumList; import google.registry.model.tld.label.PremiumListDao; import google.registry.model.tld.label.ReservedList; +import google.registry.persistence.VKey; import google.registry.util.SerializeUtils; import java.math.BigDecimal; import java.util.Optional; @@ -628,4 +633,81 @@ public final class RegistryTest extends EntityTestCase { IllegalArgumentException.class, () -> Registry.get("tld").asBuilder().setRoidSuffix("ABC-DEF")); } + + @Test + void testSuccess_setDefaultPromoTokens() { + Registry registry = Registry.get("tld"); + assertThat(registry.getDefaultPromoTokens()).isEmpty(); + AllocationToken token1 = + persistResource( + new AllocationToken() + .asBuilder() + .setToken("abc123") + .setTokenType(DEFAULT_PROMO) + .setAllowedTlds(ImmutableSet.of("tld")) + .build()); + AllocationToken token2 = + persistResource( + new AllocationToken() + .asBuilder() + .setToken("token") + .setTokenType(DEFAULT_PROMO) + .setAllowedTlds(ImmutableSet.of("tld")) + .build()); + ImmutableList> tokens = + ImmutableList.of(token1.createVKey(), token2.createVKey()); + registry = registry.asBuilder().setDefaultPromoTokens(tokens).build(); + assertThat(registry.getDefaultPromoTokens()).isEqualTo(tokens); + } + + @Test + void testFailure_setDefaultPromoTokensWrongTokenType() { + Registry registry = Registry.get("tld"); + assertThat(registry.getDefaultPromoTokens()).isEmpty(); + AllocationToken token1 = + persistResource( + new AllocationToken() + .asBuilder() + .setToken("abc123") + .setTokenType(SINGLE_USE) + .setAllowedTlds(ImmutableSet.of("tld")) + .build()); + IllegalArgumentException thrown = + assertThrows( + IllegalArgumentException.class, + () -> + registry + .asBuilder() + .setDefaultPromoTokens(ImmutableList.of(token1.createVKey())) + .build()); + assertThat(thrown.getMessage()) + .isEqualTo( + "Token abc123 has an invalid token type of SINGLE_USE. DefaultPromoTokens must be of" + + " the type DEFAULT_PROMO"); + } + + @Test + void testFailure_setDefaultPromoTokensNotValidForTld() { + Registry registry = Registry.get("tld"); + assertThat(registry.getDefaultPromoTokens()).isEmpty(); + AllocationToken token1 = + persistResource( + new AllocationToken() + .asBuilder() + .setToken("abc123") + .setTokenType(DEFAULT_PROMO) + .setAllowedTlds(ImmutableSet.of("example")) + .build()); + IllegalArgumentException thrown = + assertThrows( + IllegalArgumentException.class, + () -> + registry + .asBuilder() + .setDefaultPromoTokens(ImmutableList.of(token1.createVKey())) + .build()); + assertThat(thrown.getMessage()) + .isEqualTo( + "The token abc123 is not valid for this TLD. The valid TLDs for it are [example]"); + } } diff --git a/core/src/test/java/google/registry/persistence/converter/AllocationTokenListConverterTest.java b/core/src/test/java/google/registry/persistence/converter/AllocationTokenListConverterTest.java new file mode 100644 index 000000000..ce9f130a6 --- /dev/null +++ b/core/src/test/java/google/registry/persistence/converter/AllocationTokenListConverterTest.java @@ -0,0 +1,73 @@ +// Copyright 2022 The Nomulus Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package google.registry.persistence.converter; + +import static com.google.common.truth.Truth.assertThat; +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.jpaTm; +import static google.registry.testing.DatabaseHelper.insertInDb; + +import com.google.common.collect.ImmutableList; +import google.registry.model.ImmutableObject; +import google.registry.model.domain.token.AllocationToken; +import google.registry.persistence.VKey; +import google.registry.persistence.transaction.JpaTestExtensions; +import google.registry.persistence.transaction.JpaTestExtensions.JpaUnitTestExtension; +import java.util.List; +import javax.persistence.Entity; +import javax.persistence.Id; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +/** Unit tests for {@link google.registry.persistence.converter.AllocationTokenListConverter}. */ +public class AllocationTokenListConverterTest { + + @RegisterExtension + public final JpaUnitTestExtension jpaExtension = + new JpaTestExtensions.Builder() + .withEntityClass(TestAllocationTokenVKeyList.class) + .buildUnitTestExtension(); + + @Test + void testRoundTrip() { + AllocationToken token1 = + new AllocationToken().asBuilder().setToken("abc123").setTokenType(SINGLE_USE).build(); + AllocationToken token2 = + new AllocationToken().asBuilder().setToken("token").setTokenType(UNLIMITED_USE).build(); + List> tokens = ImmutableList.of(token1.createVKey(), token2.createVKey()); + TestAllocationTokenVKeyList testAllocationTokenVKeyList = + new TestAllocationTokenVKeyList(tokens); + insertInDb(testAllocationTokenVKeyList); + TestAllocationTokenVKeyList persisted = + jpaTm() + .transact( + () -> jpaTm().getEntityManager().find(TestAllocationTokenVKeyList.class, "id")); + assertThat(persisted.tokenList).isEqualTo(tokens); + } + + @Entity(name = "TestAllocationTokenVKeyList") + static class TestAllocationTokenVKeyList extends ImmutableObject { + @Id String id = "id"; + + List> tokenList; + + TestAllocationTokenVKeyList() {} + + TestAllocationTokenVKeyList(List> tokenList) { + this.tokenList = tokenList; + } + } +} diff --git a/db/src/main/resources/sql/schema/db-schema.sql.generated b/db/src/main/resources/sql/schema/db-schema.sql.generated index 6240063f0..dcfdc5257 100644 --- a/db/src/main/resources/sql/schema/db-schema.sql.generated +++ b/db/src/main/resources/sql/schema/db-schema.sql.generated @@ -704,6 +704,7 @@ create_billing_cost_currency text, creation_time timestamptz not null, currency text not null, + default_promo_tokens text[], dns_paused boolean not null, dns_writers text[] not null, drive_folder_id text,