Add defaultPromoTokens to Registry (#1850)

* Add defaultPromoTokens to Registry

* Remove flyway files from this PR

* Fix merge conflicts

* Add back flyway file

* Add more info to error messages

* Change to a list

* Fix javadoc

* Change error message

* Add note to field declaration
This commit is contained in:
sarahcaseybot 2022-12-06 12:22:43 -05:00 committed by GitHub
parent c13962554e
commit fa3f2a2f21
6 changed files with 232 additions and 0 deletions

View file

@ -30,6 +30,7 @@ import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.LoadingCache; import com.github.benmanes.caffeine.cache.LoadingCache;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner; import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap; 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.common.TimedTransitionProperty;
import google.registry.model.domain.fee.BaseFee.FeeType; import google.registry.model.domain.fee.BaseFee.FeeType;
import google.registry.model.domain.fee.Fee; 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.PremiumList;
import google.registry.model.tld.label.ReservedList; import google.registry.model.tld.label.ReservedList;
import google.registry.persistence.VKey; import google.registry.persistence.VKey;
import google.registry.persistence.converter.JodaMoneyType; import google.registry.persistence.converter.JodaMoneyType;
import google.registry.util.Idn; import google.registry.util.Idn;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set; 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). */ /** An allowlist of hosts allowed to be used on domains on this TLD (ignored if empty). */
@Nullable Set<String> allowedFullyQualifiedHostNames; @Nullable Set<String> allowedFullyQualifiedHostNames;
/**
* References to allocation tokens that can be used on the TLD if no other token is passed in on a
* domain create.
*
* <p>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<VKey<AllocationToken>> defaultPromoTokens;
public String getTldStr() { public String getTldStr() {
return tldStr; return tldStr;
} }
@ -639,6 +655,10 @@ public class Registry extends ImmutableObject implements Buildable, UnsafeSerial
return nullToEmptyImmutableCopy(allowedFullyQualifiedHostNames); return nullToEmptyImmutableCopy(allowedFullyQualifiedHostNames);
} }
public ImmutableList<VKey<AllocationToken>> getDefaultPromoTokens() {
return nullToEmptyImmutableCopy(defaultPromoTokens);
}
@Override @Override
public Builder asBuilder() { public Builder asBuilder() {
return new Builder(clone(this)); return new Builder(clone(this));
@ -900,6 +920,28 @@ public class Registry extends ImmutableObject implements Buildable, UnsafeSerial
return this; return this;
} }
public Builder setDefaultPromoTokens(ImmutableList<VKey<AllocationToken>> promoTokens) {
tm().transact(
() -> {
for (VKey<AllocationToken> 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 @Override
public Registry build() { public Registry build() {
final Registry instance = getInstance(); final Registry instance = getInstance();

View file

@ -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<VKey<AllocationToken>> {
@Override
String toString(VKey<AllocationToken> element) {
return element.getKey().toString();
}
@Override
VKey<AllocationToken> fromString(String value) {
return VKey.create(AllocationToken.class, value);
}
}

View file

@ -78,6 +78,7 @@
<class>google.registry.model.domain.RegistryLock</class> <class>google.registry.model.domain.RegistryLock</class>
<!-- Customized type converters --> <!-- Customized type converters -->
<class>google.registry.persistence.converter.AllocationTokenListConverter</class>
<class>google.registry.persistence.converter.AllocationTokenStatusTransitionConverter</class> <class>google.registry.persistence.converter.AllocationTokenStatusTransitionConverter</class>
<class>google.registry.persistence.converter.BillingCostTransitionConverter</class> <class>google.registry.persistence.converter.BillingCostTransitionConverter</class>
<class>google.registry.persistence.converter.BillingEventFlagSetConverter</class> <class>google.registry.persistence.converter.BillingEventFlagSetConverter</class>

View file

@ -17,6 +17,8 @@ package google.registry.model.tld;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage; import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.common.truth.Truth8.assertThat; 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.GENERAL_AVAILABILITY;
import static google.registry.model.tld.Registry.TldState.PREDELEGATION; import static google.registry.model.tld.Registry.TldState.PREDELEGATION;
import static google.registry.model.tld.Registry.TldState.QUIET_PERIOD; 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.newRegistry;
import static google.registry.testing.DatabaseHelper.persistPremiumList; import static google.registry.testing.DatabaseHelper.persistPremiumList;
import static google.registry.testing.DatabaseHelper.persistReservedList; 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.END_OF_TIME;
import static google.registry.util.DateTimeUtils.START_OF_TIME; import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static java.math.RoundingMode.UNNECESSARY; import static java.math.RoundingMode.UNNECESSARY;
@ -38,11 +41,13 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap; import com.google.common.collect.ImmutableSortedMap;
import google.registry.dns.writer.VoidDnsWriter; import google.registry.dns.writer.VoidDnsWriter;
import google.registry.model.EntityTestCase; 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.RegistryNotFoundException;
import google.registry.model.tld.Registry.TldState; import google.registry.model.tld.Registry.TldState;
import google.registry.model.tld.label.PremiumList; import google.registry.model.tld.label.PremiumList;
import google.registry.model.tld.label.PremiumListDao; import google.registry.model.tld.label.PremiumListDao;
import google.registry.model.tld.label.ReservedList; import google.registry.model.tld.label.ReservedList;
import google.registry.persistence.VKey;
import google.registry.util.SerializeUtils; import google.registry.util.SerializeUtils;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Optional; import java.util.Optional;
@ -628,4 +633,81 @@ public final class RegistryTest extends EntityTestCase {
IllegalArgumentException.class, IllegalArgumentException.class,
() -> Registry.get("tld").asBuilder().setRoidSuffix("ABC-DEF")); () -> 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<VKey<AllocationToken>> 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]");
}
} }

View file

@ -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<VKey<AllocationToken>> 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<VKey<AllocationToken>> tokenList;
TestAllocationTokenVKeyList() {}
TestAllocationTokenVKeyList(List<VKey<AllocationToken>> tokenList) {
this.tokenList = tokenList;
}
}
}

View file

@ -704,6 +704,7 @@
create_billing_cost_currency text, create_billing_cost_currency text,
creation_time timestamptz not null, creation_time timestamptz not null,
currency text not null, currency text not null,
default_promo_tokens text[],
dns_paused boolean not null, dns_paused boolean not null,
dns_writers text[] not null, dns_writers text[] not null,
drive_folder_id text, drive_folder_id text,