diff --git a/java/google/registry/model/OteAccountBuilder.java b/java/google/registry/model/OteAccountBuilder.java new file mode 100644 index 000000000..03506889b --- /dev/null +++ b/java/google/registry/model/OteAccountBuilder.java @@ -0,0 +1,377 @@ +// Copyright 2018 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.model; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.ImmutableList.toImmutableList; +import static google.registry.model.ofy.ObjectifyService.ofy; +import static google.registry.util.DateTimeUtils.START_OF_TIME; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSortedMap; +import com.google.common.collect.Sets; +import com.google.common.collect.Streams; +import com.googlecode.objectify.Key; +import google.registry.config.RegistryEnvironment; +import google.registry.model.common.GaeUserIdConverter; +import google.registry.model.pricing.StaticPremiumListPricingEngine; +import google.registry.model.registrar.Registrar; +import google.registry.model.registrar.RegistrarAddress; +import google.registry.model.registrar.RegistrarContact; +import google.registry.model.registry.Registry; +import google.registry.model.registry.Registry.TldState; +import google.registry.model.registry.label.PremiumList; +import google.registry.util.CidrAddressBlock; +import java.util.Collection; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.regex.Pattern; +import org.joda.money.CurrencyUnit; +import org.joda.money.Money; +import org.joda.time.DateTime; +import org.joda.time.Duration; + +/** + * Class to help build and persist all the OT&E entities in Datastore. + * + *

This includes the TLDs (Registries), Registrars, and the RegistrarContacts that can access the + * web console. + * + * This class is basically a "builder" for the parameters needed to generate the OT&E entities. + * Nothing is created until you call {@link #buildAndPersist}. + * + * Usage example: + * + *

   {@code
+ * OteAccountBuilder.forClientId("example")
+ *     .addContact("contact@email.com") // OPTIONAL
+ *     .setPassword("password") // OPTIONAL
+ *     .setCertificateHash(certificateHash) // OPTIONAL
+ *     .setIpWhitelist(ImmutableList.of("1.1.1.1", "2.2.2.0/24")) // OPTIONAL
+ *     .buildAndPersist();
+ * }
+ */ +public final class OteAccountBuilder { + + // Regex: 3-14 lower-case alphanumeric characters or hyphens, the first of which must be a letter. + private static final Pattern REGISTRAR_PATTERN = Pattern.compile("^[a-z][-a-z0-9]{2,13}$"); + + // Durations are short so that registrars can test with quick transfer (etc.) turnaround. + private static final Duration SHORT_ADD_GRACE_PERIOD = Duration.standardMinutes(60); + private static final Duration SHORT_REDEMPTION_GRACE_PERIOD = Duration.standardMinutes(10); + private static final Duration SHORT_PENDING_DELETE_LENGTH = Duration.standardMinutes(5); + + private static final String DEFAULT_PREMIUM_LIST = "default_sandbox_list"; + + private static final RegistrarAddress DEFAULT_ADDRESS = + new RegistrarAddress.Builder() + .setStreet(ImmutableList.of("e-street")) + .setCity("Neverland") + .setState("NY") + .setCountryCode("US") + .setZip("55555") + .build(); + + private static final ImmutableSortedMap EAP_FEE_SCHEDULE = + ImmutableSortedMap.of( + new DateTime(0), + Money.of(CurrencyUnit.USD, 0), + DateTime.parse("2018-03-01T00:00:00Z"), + Money.of(CurrencyUnit.USD, 100), + DateTime.parse("2030-03-01T00:00:00Z"), + Money.of(CurrencyUnit.USD, 0)); + + private final ImmutableMap clientIdToTld; + private final Registry sunriseTld; + private final Registry landrushTld; + private final Registry gaTld; + private final Registry eapTld; + private final ImmutableList.Builder contactsBuilder = + new ImmutableList.Builder<>(); + + private ImmutableList registrars; + private boolean replaceExisting = false; + + private OteAccountBuilder(String baseClientId) { + checkState( + RegistryEnvironment.get() != RegistryEnvironment.PRODUCTION, + "Can't setup OT&E in production"); + clientIdToTld = createClientIdToTldMap(baseClientId); + sunriseTld = + createTld( + baseClientId + "-sunrise", TldState.START_DATE_SUNRISE, null, null, null, false, 0); + landrushTld = + createTld(baseClientId + "-landrush", TldState.LANDRUSH, null, null, null, false, 1); + gaTld = + createTld( + baseClientId + "-ga", + TldState.GENERAL_AVAILABILITY, + SHORT_ADD_GRACE_PERIOD, + SHORT_REDEMPTION_GRACE_PERIOD, + SHORT_PENDING_DELETE_LENGTH, + false, + 2); + eapTld = + createTld( + baseClientId + "-eap", + TldState.GENERAL_AVAILABILITY, + SHORT_ADD_GRACE_PERIOD, + SHORT_REDEMPTION_GRACE_PERIOD, + SHORT_PENDING_DELETE_LENGTH, + true, + 3); + registrars = + clientIdToTld.keySet().stream() + .map(OteAccountBuilder::createRegistrar) + .collect(toImmutableList()); + } + + /** + * Creates an OteAccountBuilder for the given base client ID. + * + * @param baseClientId the base clientId which will help name all the entities we create. Normally + * is the same as the "prod" clientId designated for this registrar. + */ + public static OteAccountBuilder forClientId(String baseClientId) { + return new OteAccountBuilder(baseClientId); + } + + /** + * Set whether to replace any conflicting existing entities. + * + *

If true, any existing entity that conflicts with the entities we want to create will be + * replaced with the newly created data. + * + *

If false, encountering an existing entity that conflicts with one we want to create will + * throw an exception during {@link #buildAndPersist}. + * + *

NOTE that if we fail, no entities are created (the creation is atomic). + * + *

Default is false (failing if entities exist) + */ + public OteAccountBuilder setReplaceExisting(boolean replaceExisting) { + this.replaceExisting = replaceExisting; + return this; + } + + /** + * Adds a RegistrarContact with Web Console access. + * + *

NOTE: can be called more than once, adding multiple contacts. Each contact will have access + * to all OT&E Registrars. + * + * @param email the contact email that will have web-console access to all the Registrars. Must be + * from "our G Suite domain" (we have to be able to get its GaeUserId) + */ + public OteAccountBuilder addContact(String email) { + String gaeUserId = + checkNotNull( + GaeUserIdConverter.convertEmailAddressToGaeUserId(email), + "Email address %s is not associated with any GAE ID", + email); + registrars.forEach( + registrar -> contactsBuilder.add(createRegistrarContact(email, gaeUserId, registrar))); + return this; + } + + /** + * Apply a function on all the OT&E Registrars. + * + *

Use this to set up registrar fields. + * + *

NOTE: DO NOT change anything that would affect the {@link Key#create} result on Registrars. + * If you want to make this function public, add a check that the Key.create on the registrars + * hasn't changed. + * + * @param func a function setting the requested fields on Registrar Builders. Will be applied to + * all the Registrars. + */ + private OteAccountBuilder transformRegistrars( + Function func) { + registrars = + registrars.stream() + .map(Registrar::asBuilder) + .map(func) + .map(Registrar.Builder::build) + .collect(toImmutableList()); + return this; + } + + /** Sets the EPP login password for all the OT&E Registrars. */ + public OteAccountBuilder setPassword(String password) { + return transformRegistrars(builder -> builder.setPassword(password)); + } + + /** Sets the client certificate hash to all the OT&E Registrars. */ + public OteAccountBuilder setCertificateHash(String certHash) { + return transformRegistrars(builder -> builder.setClientCertificateHash(certHash)); + } + + /** Sets the client certificate to all the OT&E Registrars. */ + public OteAccountBuilder setCertificate(String asciiCert, DateTime now) { + return transformRegistrars(builder -> builder.setClientCertificate(asciiCert, now)); + } + + /** Sets the IP whitelist to all the OT&E Registrars. */ + public OteAccountBuilder setIpWhitelist(Collection ipWhitelist) { + ImmutableList ipAddressWhitelist = + ipWhitelist.stream().map(CidrAddressBlock::create).collect(toImmutableList()); + return transformRegistrars(builder -> builder.setIpAddressWhitelist(ipAddressWhitelist)); + } + + /** + * Persists all the OT&E entities to datastore. + * + * @return map from the new clientIds created to the new TLDs they have access to. Can be used to + * go over all the newly created Registrars / Registries / RegistrarContacts if any + * post-creation work is needed. + */ + public ImmutableMap buildAndPersist() { + // save all the entitiesl in a single transaction + ofy().transact(this::saveAllEntities); + return clientIdToTld; + } + + /** + * Return map from the OT&E clientIds we will create to the new TLDs they will have access to. + */ + public ImmutableMap getClientIdToTldMap() { + return clientIdToTld; + } + + /** Saves all the OT&E entities we created. */ + private void saveAllEntities() { + ofy().assertInTransaction(); + + ImmutableList registries = ImmutableList.of(sunriseTld, landrushTld, gaTld, eapTld); + ImmutableList contacts = contactsBuilder.build(); + + if (!replaceExisting) { + ImmutableList> keys = + Streams.concat(registries.stream(), registrars.stream(), contacts.stream()) + .map(Key::create) + .collect(toImmutableList()); + Set> existingKeys = ofy().load().keys(keys).keySet(); + checkState( + existingKeys.isEmpty(), + "Found existing object(s) conflicting with OT&E objects: %s", + existingKeys); + } + // Save the Registries (TLDs) first + ofy().save().entities(registries).now(); + // Now we can set the allowedTlds for the registrars + registrars = registrars.stream().map(this::addAllowedTld).collect(toImmutableList()); + // and we can save the registrars and contacts! + ofy().save().entities(registrars); + ofy().save().entities(contacts); + } + + private Registrar addAllowedTld(Registrar registrar) { + String tld = clientIdToTld.get(registrar.getClientId()); + if (registrar.getAllowedTlds().contains(tld)) { + return registrar; + } + return registrar + .asBuilder() + .setAllowedTldsUncached(Sets.union(registrar.getAllowedTlds(), ImmutableSet.of(tld))) + .build(); + } + + private static Registry createTld( + String tldName, + TldState initialTldState, + Duration addGracePeriod, + Duration redemptionGracePeriod, + Duration pendingDeleteLength, + boolean isEarlyAccess, + int roidSuffix) { + String tldNameAlphaNumerical = tldName.replaceAll("[^a-z0-9]", ""); + Optional premiumList = PremiumList.getUncached(DEFAULT_PREMIUM_LIST); + checkState(premiumList.isPresent(), "Couldn't find premium list %s.", DEFAULT_PREMIUM_LIST); + Registry.Builder builder = + new Registry.Builder() + .setTldStr(tldName) + .setPremiumPricingEngine(StaticPremiumListPricingEngine.NAME) + .setTldStateTransitions(ImmutableSortedMap.of(START_OF_TIME, initialTldState)) + .setDnsWriters(ImmutableSet.of("VoidDnsWriter")) + .setPremiumList(premiumList.get()) + .setRoidSuffix( + String.format( + "%S%X", + tldNameAlphaNumerical.substring(0, Math.min(tldNameAlphaNumerical.length(), 7)), + roidSuffix)); + if (addGracePeriod != null) { + builder.setAddGracePeriodLength(addGracePeriod); + } + if (pendingDeleteLength != null) { + builder.setPendingDeleteLength(pendingDeleteLength); + } + if (redemptionGracePeriod != null) { + builder.setRedemptionGracePeriodLength(redemptionGracePeriod); + } + if (isEarlyAccess) { + builder.setEapFeeSchedule(EAP_FEE_SCHEDULE); + } + return builder.build(); + } + + /** + * Creates the Registrar without the allowedTlds set - because we can't set allowedTlds before the + * TLD is saved. + */ + private static Registrar createRegistrar(String registrarName) { + return new Registrar.Builder() + .setClientId(registrarName) + .setRegistrarName(registrarName) + .setType(Registrar.Type.OTE) + .setLocalizedAddress(DEFAULT_ADDRESS) + .setEmailAddress("foo@neverland.com") + .setFaxNumber("+1.2125550100") + .setPhoneNumber("+1.2125550100") + .setIcannReferralEmail("nightmare@registrar.test") + .setState(Registrar.State.ACTIVE) + .build(); + } + + private static RegistrarContact createRegistrarContact( + String email, String gaeUserId, Registrar registrar) { + return new RegistrarContact.Builder() + .setParent(registrar) + .setName(email) + .setEmailAddress(email) + .setGaeUserId(gaeUserId) + .build(); + } + + /** Returns the ClientIds of the OT&E, with the TLDs each has access to. */ + private static ImmutableMap createClientIdToTldMap(String baseClientId) { + checkArgument( + REGISTRAR_PATTERN.matcher(baseClientId).matches(), + "Invalid registrar name: %s", + baseClientId); + return new ImmutableMap.Builder() + .put(baseClientId + "-1", baseClientId + "-sunrise") + .put(baseClientId + "-2", baseClientId + "-landrush") + .put(baseClientId + "-3", baseClientId + "-ga") + .put(baseClientId + "-4", baseClientId + "-ga") + .put(baseClientId + "-5", baseClientId + "-eap") + .build(); + } +} diff --git a/java/google/registry/model/registrar/Registrar.java b/java/google/registry/model/registrar/Registrar.java index b67910cbe..23f026159 100644 --- a/java/google/registry/model/registrar/Registrar.java +++ b/java/google/registry/model/registrar/Registrar.java @@ -19,6 +19,7 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Strings.emptyToNull; import static com.google.common.base.Strings.nullToEmpty; import static com.google.common.collect.ImmutableMap.toImmutableMap; +import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.google.common.collect.ImmutableSortedMap.toImmutableSortedMap; import static com.google.common.collect.ImmutableSortedSet.toImmutableSortedSet; import static com.google.common.collect.Ordering.natural; @@ -68,6 +69,7 @@ import google.registry.model.UpdateAutoTimestamp; import google.registry.model.annotations.ReportedOn; import google.registry.model.common.EntityGroupRoot; import google.registry.model.registrar.Registrar.BillingAccountEntry.CurrencyMapper; +import google.registry.model.registry.Registry; import google.registry.util.CidrAddressBlock; import google.registry.util.NonFinalForTesting; import java.security.MessageDigest; @@ -704,6 +706,28 @@ public class Registrar extends ImmutableObject implements Buildable, Jsonifiable return this; } + /** + * Same as {@link #setAllowedTlds}, but doesn't use the cache to check if the TLDs exist. + * + *

This should be used if the TLD we want to set is persisted in the same transaction - + * meaning its existence can't be cached before we need to save the Registrar. + * + *

We can still only set the allowedTld AFTER we saved the Registry entity. Make sure to call + * {@code .now()} when saving the Registry entity to make sure it's actually saved before trying + * to set the allowed TLDs. + */ + public Builder setAllowedTldsUncached(Set allowedTlds) { + ImmutableSet> newTldKeys = + Sets.difference(allowedTlds, getInstance().getAllowedTlds()).stream() + .map(tld -> Key.create(getCrossTldKey(), Registry.class, tld)) + .collect(toImmutableSet()); + Set> missingTldKeys = + Sets.difference(newTldKeys, ofy().load().keys(newTldKeys).keySet()); + checkArgument(missingTldKeys.isEmpty(), "Trying to set nonexisting TLDs: %s", missingTldKeys); + getInstance().allowedTlds = ImmutableSortedSet.copyOf(allowedTlds); + return this; + } + public Builder setClientCertificate(String clientCertificate, DateTime now) { clientCertificate = emptyToNull(clientCertificate); String clientCertificateHash = calculateHash(clientCertificate); diff --git a/java/google/registry/tools/SetupOteCommand.java b/java/google/registry/tools/SetupOteCommand.java index 8764103bb..b53bfcc7c 100644 --- a/java/google/registry/tools/SetupOteCommand.java +++ b/java/google/registry/tools/SetupOteCommand.java @@ -15,69 +15,30 @@ package google.registry.tools; import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; -import static google.registry.model.ofy.ObjectifyService.ofy; -import static google.registry.tools.CommandUtilities.promptForYes; import static google.registry.util.X509Utils.loadCertificate; +import static java.nio.charset.StandardCharsets.US_ASCII; import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameters; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Throwables; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSortedMap; -import com.google.re2j.Pattern; +import com.google.common.collect.ImmutableMap; +import com.google.common.io.MoreFiles; import google.registry.config.RegistryConfig.Config; import google.registry.config.RegistryEnvironment; -import google.registry.model.common.GaeUserIdConverter; -import google.registry.model.registrar.Registrar; -import google.registry.model.registry.Registry.TldState; +import google.registry.model.OteAccountBuilder; import google.registry.tools.params.PathParameter; +import google.registry.util.Clock; import google.registry.util.StringGenerator; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; -import java.util.Optional; -import java.util.Set; import javax.inject.Inject; -import javax.inject.Named; -import org.joda.money.CurrencyUnit; -import org.joda.money.Money; -import org.joda.time.DateTime; -import org.joda.time.Duration; /** Composite command to set up OT&E TLDs and accounts. */ @Parameters(separators = " =", commandDescription = "Set up OT&E TLDs and registrars") final class SetupOteCommand extends ConfirmingCommand implements CommandWithRemoteApi { - // Regex: 3-14 alphanumeric characters or hyphens, the first of which must be a letter. - private static final Pattern REGISTRAR_PATTERN = Pattern.compile("^[a-z][-a-z0-9]{2,13}$"); private static final int PASSWORD_LENGTH = 16; - // Durations are short so that registrars can test with quick transfer (etc.) turnaround. - private static final Duration SHORT_ADD_GRACE_PERIOD = Duration.standardMinutes(60); - private static final Duration SHORT_REDEMPTION_GRACE_PERIOD = Duration.standardMinutes(10); - private static final Duration SHORT_PENDING_DELETE_LENGTH = Duration.standardMinutes(5); - - // Whether to prompt the user on command failures. Set to false for testing of these failures. - @VisibleForTesting - static boolean interactive = true; - - private static final ImmutableSortedMap EAP_FEE_SCHEDULE = - ImmutableSortedMap.of( - new DateTime(0), - Money.of(CurrencyUnit.USD, 0), - DateTime.parse("2018-03-01T00:00:00Z"), - Money.of(CurrencyUnit.USD, 100), - DateTime.parse("2022-03-01T00:00:00Z"), - Money.of(CurrencyUnit.USD, 0)); - - private static final String DEFAULT_PREMIUM_LIST = "default_sandbox_list"; - - @Inject - @Named("dnsWriterNames") - Set validDnsWriterNames; - @Parameter( names = {"-r", "--registrar"}, description = @@ -101,7 +62,7 @@ final class SetupOteCommand extends ConfirmingCommand implements CommandWithRemo names = {"--email"}, description = "the registrar's account to use for console access. " - + "Must be on the registry's G-Suite domain.", + + "Must be on the registry's G Suite domain.", required = true) private String email; @@ -121,252 +82,83 @@ final class SetupOteCommand extends ConfirmingCommand implements CommandWithRemo private String certHash; @Parameter( - names = {"--dns_writers"}, - description = "comma separated list of DNS writers to use on all TLDs", - required = true + names = {"--overwrite"}, + description = "whether to replace existing entities if we encounter any, instead of failing" ) - private List dnsWriters; - - @Parameter( - names = {"--premium_list"}, - description = "premium list to apply to all TLDs" - ) - private String premiumList = DEFAULT_PREMIUM_LIST; - - // TODO: (b/74079782) remove this flag once OT&E for .app is complete. - @Parameter( - names = {"--eap_only"}, - description = "whether to only create EAP TLD and registrar" - ) - private boolean eapOnly = false; + private boolean overwrite = false; @Inject @Config("base64StringGenerator") StringGenerator passwordGenerator; - /** - * Long registrar names are truncated and then have an incrementing digit appended at the end so - * that unique ROID suffixes can be generated for all TLDs for the registrar. - */ - private int roidSuffixCounter = 0; + @Inject Clock clock; - /** Runs a command, clearing the cache before and prompting the user on failures. */ - private void runCommand(Command command) { - ofy().clearSessionCache(); - try { - command.run(); - } catch (Exception e) { - System.err.format("Command failed with error %s\n", e); - if (interactive && promptForYes("Continue to next command?")) { - return; - } - Throwables.throwIfUnchecked(e); - throw new RuntimeException(e); - } - } - - /** Constructs and runs a CreateTldCommand. */ - private void createTld( - String tldName, - TldState initialTldState, - Duration addGracePeriod, - Duration redemptionGracePeriod, - Duration pendingDeleteLength, - boolean isEarlyAccess) { - CreateTldCommand command = new CreateTldCommand(); - command.addGracePeriod = addGracePeriod; - command.dnsWriters = dnsWriters; - command.validDnsWriterNames = validDnsWriterNames; - command.force = force; - command.initialTldState = initialTldState; - command.mainParameters = ImmutableList.of(tldName); - command.pendingDeleteLength = pendingDeleteLength; - command.premiumListName = Optional.of(premiumList); - String tldNameAlphaNumerical = tldName.replaceAll("[^a-z0-9]", ""); - command.roidSuffix = - String.format( - "%S%X", - tldNameAlphaNumerical.substring(0, Math.min(tldNameAlphaNumerical.length(), 7)), - roidSuffixCounter++); - command.redemptionGracePeriod = redemptionGracePeriod; - if (isEarlyAccess) { - command.eapFeeSchedule = EAP_FEE_SCHEDULE; - } - runCommand(command); - } - - /** Constructs and runs a CreateRegistrarCommand */ - private void createRegistrar(String registrarName, String password, String tld) { - CreateRegistrarCommand command = new CreateRegistrarCommand(); - command.mainParameters = ImmutableList.of(registrarName); - command.createGoogleGroups = false; // Don't create Google Groups for OT&E registrars. - command.allowedTlds = ImmutableList.of(tld); - command.registrarName = registrarName; - command.registrarType = Registrar.Type.OTE; - command.password = password; - command.clientCertificateFilename = certFile; - command.clientCertificateHash = certHash; - command.ipWhitelist = ipWhitelist; - command.street = ImmutableList.of("e-street"); - command.city = "Neverland"; - command.state = "NY"; - command.countryCode = "US"; - command.zip = "55555"; - command.email = Optional.of("foo@neverland.com"); - command.fax = Optional.of("+1.2125550100"); - command.phone = Optional.of("+1.2125550100"); - command.icannReferralEmail = "nightmare@registrar.test"; - command.force = force; - runCommand(command); - } - - /** Constructs and runs a RegistrarContactCommand */ - private void createRegistrarContact(String registrarName) { - RegistrarContactCommand command = new RegistrarContactCommand(); - command.mainParameters = ImmutableList.of(registrarName); - command.mode = RegistrarContactCommand.Mode.CREATE; - command.name = email; - command.email = email; - command.allowConsoleAccess = true; - command.force = force; - runCommand(command); - } + OteAccountBuilder oteAccountBuilder; + String password; /** Run any pre-execute command checks */ @Override - protected boolean checkExecutionState() throws Exception { - checkArgument( - REGISTRAR_PATTERN.matcher(registrar).matches(), - "Registrar name is invalid (see usage text for requirements)."); - - // Make sure the email is "correct" - as in it's a valid email we can convert to gaeId - // There's no need to look at the result - it'll be converted again inside - // RegistrarContactCommand. - checkNotNull( - GaeUserIdConverter.convertEmailAddressToGaeUserId(email), - "Email address %s is not associated with any GAE ID", - email); - - boolean warned = false; - if (RegistryEnvironment.get() != RegistryEnvironment.SANDBOX - && RegistryEnvironment.get() != RegistryEnvironment.UNITTEST) { - System.err.printf( - "WARNING: Running against %s environment. Are " - + "you sure you didn\'t mean to run this against sandbox (e.g. \"-e SANDBOX\")?%n", - RegistryEnvironment.get()); - warned = true; - } - - if (warned && !promptForYes("Proceed despite warnings?")) { - System.out.println("Command aborted."); - return false; - } - + protected void init() throws Exception { checkArgument( certFile == null ^ certHash == null, "Must specify exactly one of client certificate file or client certificate hash."); - // Don't wait for create_registrar to fail if it's a bad certificate file. + password = passwordGenerator.createString(PASSWORD_LENGTH); + oteAccountBuilder = + OteAccountBuilder.forClientId(registrar) + .addContact(email) + .setPassword(password) + .setIpWhitelist(ipWhitelist) + .setReplaceExisting(overwrite); + if (certFile != null) { - loadCertificate(certFile.toAbsolutePath()); + String asciiCert = MoreFiles.asCharSource(certFile, US_ASCII).read(); + // Don't wait for create_registrar to fail if it's a bad certificate file. + loadCertificate(asciiCert); + oteAccountBuilder.setCertificate(asciiCert, clock.nowUtc()); + } + + if (certHash != null) { + oteAccountBuilder.setCertificateHash(certHash); } - return true; } @Override protected String prompt() { - // Each underlying command will confirm its own operation as well, so just provide - // a summary of the steps in this command. - if (eapOnly) { - return "Creating TLD:\n" - + " " + registrar + "-eap\n" - + "Creating registrar:\n" - + " " + registrar + "-5 (access to TLD " + registrar + "-eap)\n" - + "Giving contact access to this registrar:\n" - + " " + email; - } else { - return "Creating TLDs:\n" - + " " + registrar + "-sunrise\n" - + " " + registrar + "-landrush\n" - + " " + registrar + "-ga\n" - + " " + registrar + "-eap\n" - + "Creating registrars:\n" - + " " + registrar + "-1 (access to TLD " + registrar + "-sunrise)\n" - + " " + registrar + "-2 (access to TLD " + registrar + "-landrush)\n" - + " " + registrar + "-3 (access to TLD " + registrar + "-ga)\n" - + " " + registrar + "-4 (access to TLD " + registrar + "-ga)\n" - + " " + registrar + "-5 (access to TLD " + registrar + "-eap)\n" - + "Giving contact access to these registrars:\n" - + " " + email; + ImmutableMap registrarToTldMap = oteAccountBuilder.getClientIdToTldMap(); + StringBuilder builder = new StringBuilder(); + builder.append("Creating TLDs:"); + registrarToTldMap.values().forEach(tld -> builder.append("\n ").append(tld)); + builder.append("\nCreating registrars:"); + registrarToTldMap.forEach( + (clientId, tld) -> + builder.append(String.format("\n %s (with access to %s)", clientId, tld))); + builder.append("\nGiving contact access to these registrars:").append("\n ").append(email); + + if (RegistryEnvironment.get() != RegistryEnvironment.SANDBOX + && RegistryEnvironment.get() != RegistryEnvironment.UNITTEST) { + builder.append( + String.format( + "\n\nWARNING: Running against %s environment. Are " + + "you sure you didn\'t mean to run this against sandbox (e.g. \"-e SANDBOX\")?", + RegistryEnvironment.get())); } + + return builder.toString(); } @Override public String execute() throws Exception { - if (!eapOnly) { - createTld(registrar + "-sunrise", TldState.START_DATE_SUNRISE, null, null, null, false); - createTld(registrar + "-landrush", TldState.LANDRUSH, null, null, null, false); - createTld( - registrar + "-ga", - TldState.GENERAL_AVAILABILITY, - SHORT_ADD_GRACE_PERIOD, - SHORT_REDEMPTION_GRACE_PERIOD, - SHORT_PENDING_DELETE_LENGTH, - false); - } else { - // Increase ROID suffix counter to not collide with existing TLDs. - roidSuffixCounter = roidSuffixCounter + 3; - } - createTld( - registrar + "-eap", - TldState.GENERAL_AVAILABILITY, - SHORT_ADD_GRACE_PERIOD, - SHORT_REDEMPTION_GRACE_PERIOD, - SHORT_PENDING_DELETE_LENGTH, - true); - - // Storing names and credentials in a list of tuples for later play-back. - List> registrars = new ArrayList<>(); - if (!eapOnly) { - registrars.add( - ImmutableList.of( - registrar + "-1", - passwordGenerator.createString(PASSWORD_LENGTH), - registrar + "-sunrise")); - registrars.add( - ImmutableList.of( - registrar + "-2", - passwordGenerator.createString(PASSWORD_LENGTH), - registrar + "-landrush")); - registrars.add( - ImmutableList.of( - registrar + "-3", - passwordGenerator.createString(PASSWORD_LENGTH), - registrar + "-ga")); - registrars.add( - ImmutableList.of( - registrar + "-4", - passwordGenerator.createString(PASSWORD_LENGTH), - registrar + "-ga")); - } - registrars.add( - ImmutableList.of( - registrar + "-5", passwordGenerator.createString(PASSWORD_LENGTH), registrar + "-eap")); - - for (List r : registrars) { - createRegistrar(r.get(0), r.get(1), r.get(2)); - createRegistrarContact(r.get(0)); - } + ImmutableMap clientIdToTld = oteAccountBuilder.buildAndPersist(); StringBuilder output = new StringBuilder(); output.append("Copy these usernames/passwords back into the onboarding bug:\n\n"); - - for (List r : registrars) { - output.append("Login: " + r.get(0) + "\n"); - output.append("Password: " + r.get(1) + "\n"); - output.append("TLD: " + r.get(2) + "\n\n"); - } + clientIdToTld.forEach( + (clientId, tld) -> { + output.append( + String.format("Login: %s\nPassword: %s\nTLD: %s\n\n", clientId, password, tld)); + }); return output.toString(); } diff --git a/javatests/google/registry/model/OteAccountBuilderTest.java b/javatests/google/registry/model/OteAccountBuilderTest.java new file mode 100644 index 000000000..792f8cd84 --- /dev/null +++ b/javatests/google/registry/model/OteAccountBuilderTest.java @@ -0,0 +1,302 @@ +// Copyright 2018 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.model; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth8.assertThat; +import static google.registry.testing.CertificateSamples.SAMPLE_CERT; +import static google.registry.testing.CertificateSamples.SAMPLE_CERT_HASH; +import static google.registry.testing.DatastoreHelper.persistPremiumList; +import static google.registry.testing.JUnitBackports.assertThrows; +import static google.registry.util.DateTimeUtils.START_OF_TIME; +import static org.joda.money.CurrencyUnit.USD; + +import com.google.common.collect.ImmutableList; +import google.registry.model.registrar.Registrar; +import google.registry.model.registrar.RegistrarContact; +import google.registry.model.registry.Registry; +import google.registry.model.registry.Registry.TldState; +import google.registry.testing.AppEngineRule; +import google.registry.testing.DatastoreHelper; +import google.registry.util.CidrAddressBlock; +import google.registry.util.SystemClock; +import org.joda.money.Money; +import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; +import org.joda.time.Duration; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class OteAccountBuilderTest { + + @Rule public final AppEngineRule appEngine = AppEngineRule.builder().withDatastore().build(); + + @Test + public void testGetRegistrarToTldMap() { + assertThat(OteAccountBuilder.forClientId("myclientid").getClientIdToTldMap()) + .containsExactly( + "myclientid-1", "myclientid-sunrise", + "myclientid-2", "myclientid-landrush", + "myclientid-3", "myclientid-ga", + "myclientid-4", "myclientid-ga", + "myclientid-5", "myclientid-eap"); + } + + @Before + public void setUp() { + persistPremiumList("default_sandbox_list", "sandbox,USD 1000"); + } + + private void assertTldExists(String tld, Registry.TldState tldState) { + Registry registry = Registry.get(tld); + assertThat(registry).isNotNull(); + assertThat(registry.getPremiumList().getName()).isEqualTo("default_sandbox_list"); + assertThat(registry.getTldStateTransitions()).containsExactly(START_OF_TIME, tldState); + assertThat(registry.getDnsWriters()).containsExactly("VoidDnsWriter"); + assertThat(registry.getAddGracePeriodLength()).isEqualTo(Duration.standardDays(5)); + assertThat(registry.getPendingDeleteLength()).isEqualTo(Duration.standardDays(5)); + assertThat(registry.getRedemptionGracePeriodLength()).isEqualTo(Duration.standardDays(30)); + assertThat(registry.getEapFeeScheduleAsMap()).containsExactly(START_OF_TIME, Money.of(USD, 0)); + } + + private void assertTldExistsGa(String tld, Money eapFee) { + Registry registry = Registry.get(tld); + assertThat(registry).isNotNull(); + assertThat(registry.getPremiumList().getName()).isEqualTo("default_sandbox_list"); + assertThat(registry.getTldStateTransitions()) + .containsExactly(START_OF_TIME, TldState.GENERAL_AVAILABILITY); + assertThat(registry.getDnsWriters()).containsExactly("VoidDnsWriter"); + assertThat(registry.getAddGracePeriodLength()).isEqualTo(Duration.standardHours(1)); + assertThat(registry.getPendingDeleteLength()).isEqualTo(Duration.standardMinutes(5)); + assertThat(registry.getRedemptionGracePeriodLength()).isEqualTo(Duration.standardMinutes(10)); + assertThat(registry.getCurrency()).isEqualTo(eapFee.getCurrencyUnit()); + // This uses "now" on purpose - so the test will break at 2022 when the current EapFee in OTE + // goes back to 0 + assertThat(registry.getEapFeeFor(DateTime.now(DateTimeZone.UTC)).getCost()) + .isEqualTo(eapFee.getAmount()); + } + + private void assertRegistrarExists(String clientId, String tld) { + Registrar registrar = Registrar.loadByClientId(clientId).orElse(null); + assertThat(registrar).isNotNull(); + assertThat(registrar.getType()).isEqualTo(Registrar.Type.OTE); + assertThat(registrar.getState()).isEqualTo(Registrar.State.ACTIVE); + assertThat(registrar.getAllowedTlds()).containsExactly(tld); + } + + private void assertContactExists(String clientId, String email) { + Registrar registrar = Registrar.loadByClientId(clientId).get(); + assertThat(registrar.getContacts().stream().map(RegistrarContact::getEmailAddress)) + .contains(email); + RegistrarContact contact = + registrar.getContacts().stream() + .filter(c -> email.equals(c.getEmailAddress())) + .findAny() + .get(); + assertThat(contact.getEmailAddress()).isEqualTo(email); + assertThat(contact.getGaeUserId()).isNotEmpty(); + } + + @Test + public void testCreateOteEntities_success() { + OteAccountBuilder.forClientId("myclientid").addContact("email@example.com").buildAndPersist(); + + assertTldExists("myclientid-sunrise", TldState.START_DATE_SUNRISE); + assertTldExists("myclientid-landrush", TldState.LANDRUSH); + assertTldExistsGa("myclientid-ga", Money.of(USD, 0)); + assertTldExistsGa("myclientid-eap", Money.of(USD, 100)); + assertRegistrarExists("myclientid-1", "myclientid-sunrise"); + assertRegistrarExists("myclientid-2", "myclientid-landrush"); + assertRegistrarExists("myclientid-3", "myclientid-ga"); + assertRegistrarExists("myclientid-4", "myclientid-ga"); + assertRegistrarExists("myclientid-5", "myclientid-eap"); + assertContactExists("myclientid-1", "email@example.com"); + assertContactExists("myclientid-2", "email@example.com"); + assertContactExists("myclientid-3", "email@example.com"); + assertContactExists("myclientid-4", "email@example.com"); + assertContactExists("myclientid-5", "email@example.com"); + } + + @Test + public void testCreateOteEntities_multipleContacts_success() { + OteAccountBuilder.forClientId("myclientid") + .addContact("email@example.com") + .addContact("other@example.com") + .addContact("someone@example.com") + .buildAndPersist(); + + assertTldExists("myclientid-sunrise", TldState.START_DATE_SUNRISE); + assertTldExists("myclientid-landrush", TldState.LANDRUSH); + assertTldExistsGa("myclientid-ga", Money.of(USD, 0)); + assertTldExistsGa("myclientid-eap", Money.of(USD, 100)); + assertRegistrarExists("myclientid-1", "myclientid-sunrise"); + assertRegistrarExists("myclientid-2", "myclientid-landrush"); + assertRegistrarExists("myclientid-3", "myclientid-ga"); + assertRegistrarExists("myclientid-4", "myclientid-ga"); + assertRegistrarExists("myclientid-5", "myclientid-eap"); + assertContactExists("myclientid-1", "email@example.com"); + assertContactExists("myclientid-2", "email@example.com"); + assertContactExists("myclientid-3", "email@example.com"); + assertContactExists("myclientid-4", "email@example.com"); + assertContactExists("myclientid-5", "email@example.com"); + assertContactExists("myclientid-1", "other@example.com"); + assertContactExists("myclientid-2", "other@example.com"); + assertContactExists("myclientid-3", "other@example.com"); + assertContactExists("myclientid-4", "other@example.com"); + assertContactExists("myclientid-5", "other@example.com"); + assertContactExists("myclientid-1", "someone@example.com"); + assertContactExists("myclientid-2", "someone@example.com"); + assertContactExists("myclientid-3", "someone@example.com"); + assertContactExists("myclientid-4", "someone@example.com"); + assertContactExists("myclientid-5", "someone@example.com"); + } + + @Test + public void testCreateOteEntities_setPassword() { + OteAccountBuilder.forClientId("myclientid").setPassword("myPassword").buildAndPersist(); + + assertThat(Registrar.loadByClientId("myclientid-3").get().testPassword("myPassword")).isTrue(); + } + + @Test + public void testCreateOteEntities_setCertificateHash() { + OteAccountBuilder.forClientId("myclientid") + .setCertificateHash(SAMPLE_CERT_HASH) + .buildAndPersist(); + + assertThat(Registrar.loadByClientId("myclientid-3").get().getClientCertificateHash()) + .isEqualTo(SAMPLE_CERT_HASH); + } + + @Test + public void testCreateOteEntities_setCertificate() { + OteAccountBuilder.forClientId("myclientid") + .setCertificate(SAMPLE_CERT, new SystemClock().nowUtc()) + .buildAndPersist(); + + assertThat(Registrar.loadByClientId("myclientid-3").get().getClientCertificateHash()) + .isEqualTo(SAMPLE_CERT_HASH); + assertThat(Registrar.loadByClientId("myclientid-3").get().getClientCertificate()) + .isEqualTo(SAMPLE_CERT); + } + + @Test + public void testCreateOteEntities_setIpWhitelist() { + OteAccountBuilder.forClientId("myclientid") + .setIpWhitelist(ImmutableList.of("1.1.1.0/24")) + .buildAndPersist(); + + assertThat(Registrar.loadByClientId("myclientid-3").get().getIpAddressWhitelist()) + .containsExactly(CidrAddressBlock.create("1.1.1.0/24")); + } + + @Test + public void testCreateOteEntities_invalidClientId_fails() { + assertThat( + assertThrows( + IllegalArgumentException.class, () -> OteAccountBuilder.forClientId("3blobio"))) + .hasMessageThat() + .isEqualTo("Invalid registrar name: 3blobio"); + } + + @Test + public void testCreateOteEntities_clientIdTooShort_fails() { + assertThat( + assertThrows(IllegalArgumentException.class, () -> OteAccountBuilder.forClientId("bl"))) + .hasMessageThat() + .isEqualTo("Invalid registrar name: bl"); + } + + @Test + public void testCreateOteEntities_clientIdTooLong_fails() { + assertThat( + assertThrows( + IllegalArgumentException.class, + () -> OteAccountBuilder.forClientId("blobiotoooolong"))) + .hasMessageThat() + .isEqualTo("Invalid registrar name: blobiotoooolong"); + } + + @Test + public void testCreateOteEntities_clientIdBadCharacter_fails() { + assertThat( + assertThrows( + IllegalArgumentException.class, () -> OteAccountBuilder.forClientId("blo#bio"))) + .hasMessageThat() + .isEqualTo("Invalid registrar name: blo#bio"); + } + + @Test + public void testCreateOteEntities_entityExists_failsWhenNotReplaceExisting() { + DatastoreHelper.persistSimpleResource( + AppEngineRule.makeRegistrar1().asBuilder().setClientId("myclientid-1").build()); + OteAccountBuilder oteSetupHelper = OteAccountBuilder.forClientId("myclientid"); + + assertThat(assertThrows(IllegalStateException.class, () -> oteSetupHelper.buildAndPersist())) + .hasMessageThat() + .contains("Found existing object(s) conflicting with OT&E objects"); + } + + @Test + public void testCreateOteEntities_entityExists_succeedsWhenReplaceExisting() { + DatastoreHelper.persistSimpleResource( + AppEngineRule.makeRegistrar1().asBuilder().setClientId("myclientid-1").build()); + DatastoreHelper.createTld("myclientid-landrush", Registry.TldState.SUNRUSH); + + OteAccountBuilder.forClientId("myclientid").setReplaceExisting(true).buildAndPersist(); + + assertTldExists("myclientid-landrush", TldState.LANDRUSH); + assertRegistrarExists("myclientid-3", "myclientid-ga"); + } + + @Test + public void testCreateOteEntities_doubleCreation_actuallyReplaces() { + OteAccountBuilder.forClientId("myclientid") + .setPassword("oldPassword") + .addContact("email@example.com") + .buildAndPersist(); + + assertThat(Registrar.loadByClientId("myclientid-3").get().testPassword("oldPassword")).isTrue(); + + OteAccountBuilder.forClientId("myclientid") + .setPassword("newPassword") + .addContact("email@example.com") + .setReplaceExisting(true) + .buildAndPersist(); + + assertThat(Registrar.loadByClientId("myclientid-3").get().testPassword("oldPassword")) + .isFalse(); + assertThat(Registrar.loadByClientId("myclientid-3").get().testPassword("newPassword")).isTrue(); + } + + @Test + public void testCreateOteEntities_doubleCreation_keepsOldContacts() { + OteAccountBuilder.forClientId("myclientid").addContact("email@example.com").buildAndPersist(); + + assertContactExists("myclientid-3", "email@example.com"); + + OteAccountBuilder.forClientId("myclientid") + .addContact("other@example.com") + .setReplaceExisting(true) + .buildAndPersist(); + + assertContactExists("myclientid-3", "other@example.com"); + assertContactExists("myclientid-3", "email@example.com"); + } +} diff --git a/javatests/google/registry/model/registrar/RegistrarTest.java b/javatests/google/registry/model/registrar/RegistrarTest.java index 066660683..ed095087f 100644 --- a/javatests/google/registry/model/registrar/RegistrarTest.java +++ b/javatests/google/registry/model/registrar/RegistrarTest.java @@ -23,6 +23,7 @@ import static google.registry.testing.CertificateSamples.SAMPLE_CERT2_HASH; import static google.registry.testing.CertificateSamples.SAMPLE_CERT_HASH; import static google.registry.testing.DatastoreHelper.cloneAndSetAutoTimestamps; import static google.registry.testing.DatastoreHelper.createTld; +import static google.registry.testing.DatastoreHelper.newRegistry; import static google.registry.testing.DatastoreHelper.persistResource; import static google.registry.testing.DatastoreHelper.persistSimpleResource; import static google.registry.testing.DatastoreHelper.persistSimpleResources; @@ -33,10 +34,12 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; import com.googlecode.objectify.Key; +import google.registry.config.RegistryConfig; import google.registry.model.EntityTestCase; import google.registry.model.common.EntityGroupRoot; import google.registry.model.registrar.Registrar.State; import google.registry.model.registrar.Registrar.Type; +import google.registry.model.registry.Registries; import google.registry.util.CidrAddressBlock; import org.joda.money.CurrencyUnit; import org.junit.Before; @@ -415,6 +418,77 @@ public class RegistrarTest extends EntityTestCase { IllegalArgumentException.class, () -> new Registrar.Builder().setPhonePasscode("code1")); } + @Test + public void testSuccess_setAllowedTlds() { + assertThat( + registrar.asBuilder() + .setAllowedTlds(ImmutableSet.of("xn--q9jyb4c")) + .build() + .getAllowedTlds()) + .containsExactly("xn--q9jyb4c"); + } + + @Test + public void testSuccess_setAllowedTldsUncached() { + assertThat( + registrar.asBuilder() + .setAllowedTldsUncached(ImmutableSet.of("xn--q9jyb4c")) + .build() + .getAllowedTlds()) + .containsExactly("xn--q9jyb4c"); + } + + @Test + public void testFailure_setAllowedTlds_nonexistentTld() { + assertThrows( + IllegalArgumentException.class, + () -> registrar.asBuilder().setAllowedTlds(ImmutableSet.of("bad"))); + } + + @Test + public void testFailure_setAllowedTldsUncached_nonexistentTld() { + assertThrows( + IllegalArgumentException.class, + () -> registrar.asBuilder().setAllowedTldsUncached(ImmutableSet.of("bad"))); + } + + @Test + public void testSuccess_setAllowedTldsUncached_newTldNotInCache() { + // Cache duration in tests is 0. To make sure the data isn't in the cache we have to set it to a + // higher value and reset the cache. + RegistryConfig.CONFIG_SETTINGS.get().caching.singletonCacheRefreshSeconds = 600; + Registries.resetCache(); + // Make sure the TLD we want to create doesn't exist yet. + // This is also important because getTlds fills out the cache when used. + assertThat(Registries.getTlds()).doesNotContain("newtld"); + // We can't use createTld here because it failes when the cache is used. + persistResource(newRegistry("newtld", "NEWTLD")); + // Make sure we set up the cache correctly, so the newly created TLD isn't in the cache + assertThat(Registries.getTlds()).doesNotContain("newtld"); + + // Test that the uncached version works + assertThat( + registrar.asBuilder() + .setAllowedTldsUncached(ImmutableSet.of("newtld")) + .build() + .getAllowedTlds()) + .containsExactly("newtld"); + + // Test that the "regular" cached version fails. If this doesn't throw - then we changed how the + // cached version works: + // - either we switched to a different cache type/duration, and we haven't actually set up that + // cache in the test + // - or we stopped using the cache entirely and we should rethink if the Uncached version is + // still needed + assertThrows( + IllegalArgumentException.class, + () -> registrar.asBuilder().setAllowedTlds(ImmutableSet.of("newtld"))); + + // Make sure the cache hasn't expired during the test and "newtld" is still not in the cached + // TLDs + assertThat(Registries.getTlds()).doesNotContain("newtld"); + } + @Test public void testLoadByClientIdCached_isTransactionless() { ofy() diff --git a/javatests/google/registry/tools/SetupOteCommandTest.java b/javatests/google/registry/tools/SetupOteCommandTest.java index dc403172b..0b6b92bc3 100644 --- a/javatests/google/registry/tools/SetupOteCommandTest.java +++ b/javatests/google/registry/tools/SetupOteCommandTest.java @@ -34,6 +34,7 @@ import google.registry.model.registrar.RegistrarContact; import google.registry.model.registry.Registry; import google.registry.model.registry.Registry.TldState; import google.registry.testing.DeterministicStringGenerator; +import google.registry.testing.FakeClock; import google.registry.util.CidrAddressBlock; import java.security.cert.CertificateParsingException; import org.joda.money.CurrencyUnit; @@ -46,23 +47,16 @@ import org.junit.Test; /** Unit tests for {@link SetupOteCommand}. */ public class SetupOteCommandTest extends CommandTestCase { - ImmutableList passwords = - ImmutableList.of( - "abcdefghijklmnop", - "qrstuvwxyzabcdef", - "ghijklmnopqrstuv", - "wxyzabcdefghijkl", - "mnopqrstuvwxyzab"); + static final String PASSWORD = "abcdefghijklmnop"; + DeterministicStringGenerator passwordGenerator = new DeterministicStringGenerator("abcdefghijklmnopqrstuvwxyz"); @Before public void init() { - SetupOteCommand.interactive = false; - command.validDnsWriterNames = ImmutableSet.of("FooDnsWriter", "BarDnsWriter", "VoidDnsWriter"); command.passwordGenerator = passwordGenerator; + command.clock = new FakeClock(DateTime.parse("2018-07-07TZ")); persistPremiumList("default_sandbox_list", "sandbox,USD 1000"); - persistPremiumList("alternate_list", "rich,USD 3000"); } /** Verify TLD creation. */ @@ -70,8 +64,6 @@ public class SetupOteCommandTest extends CommandTestCase { String tldName, String roidSuffix, TldState tldState, - String dnsWriter, - String premiumList, Duration addGracePeriodLength, Duration redemptionGracePeriodLength, Duration pendingDeleteLength, @@ -80,9 +72,9 @@ public class SetupOteCommandTest extends CommandTestCase { assertThat(registry).isNotNull(); assertThat(registry.getRoidSuffix()).isEqualTo(roidSuffix); assertThat(registry.getTldState(DateTime.now(UTC))).isEqualTo(tldState); - assertThat(registry.getDnsWriters()).containsExactly(dnsWriter); + assertThat(registry.getDnsWriters()).containsExactly("VoidDnsWriter"); assertThat(registry.getPremiumList()).isNotNull(); - assertThat(registry.getPremiumList().getName()).isEqualTo(premiumList); + assertThat(registry.getPremiumList().getName()).isEqualTo("default_sandbox_list"); assertThat(registry.getAddGracePeriodLength()).isEqualTo(addGracePeriodLength); assertThat(registry.getRedemptionGracePeriodLength()).isEqualTo(redemptionGracePeriodLength); assertThat(registry.getPendingDeleteLength()).isEqualTo(pendingDeleteLength); @@ -98,20 +90,18 @@ public class SetupOteCommandTest extends CommandTestCase { Money.of(CurrencyUnit.USD, 0), DateTime.parse("2018-03-01T00:00:00Z"), Money.of(CurrencyUnit.USD, 100), - DateTime.parse("2022-03-01T00:00:00Z"), + DateTime.parse("2030-03-01T00:00:00Z"), Money.of(CurrencyUnit.USD, 0))); } } /** Verify TLD creation with registry default durations. */ private void verifyTldCreation( - String tldName, String roidSuffix, TldState tldState, String dnsWriter, String premiumList) { + String tldName, String roidSuffix, TldState tldState) { verifyTldCreation( tldName, roidSuffix, tldState, - dnsWriter, - premiumList, Registry.DEFAULT_ADD_GRACE_PERIOD, Registry.DEFAULT_REDEMPTION_GRACE_PERIOD, Registry.DEFAULT_PENDING_DELETE_LENGTH, @@ -162,23 +152,14 @@ public class SetupOteCommandTest extends CommandTestCase { "--ip_whitelist=1.1.1.1", "--registrar=blobio", "--email=contact@email.com", - "--dns_writers=VoidDnsWriter", "--certfile=" + getCertFilename()); - verifyTldCreation( - "blobio-sunrise", - "BLOBIOS0", - TldState.START_DATE_SUNRISE, - "VoidDnsWriter", - "default_sandbox_list"); - verifyTldCreation( - "blobio-landrush", "BLOBIOL1", TldState.LANDRUSH, "VoidDnsWriter", "default_sandbox_list"); + verifyTldCreation("blobio-sunrise", "BLOBIOS0", TldState.START_DATE_SUNRISE); + verifyTldCreation("blobio-landrush", "BLOBIOL1", TldState.LANDRUSH); verifyTldCreation( "blobio-ga", "BLOBIOG2", TldState.GENERAL_AVAILABILITY, - "VoidDnsWriter", - "default_sandbox_list", Duration.standardMinutes(60), Duration.standardMinutes(10), Duration.standardMinutes(5), @@ -187,8 +168,6 @@ public class SetupOteCommandTest extends CommandTestCase { "blobio-eap", "BLOBIOE3", TldState.GENERAL_AVAILABILITY, - "VoidDnsWriter", - "default_sandbox_list", Duration.standardMinutes(60), Duration.standardMinutes(10), Duration.standardMinutes(5), @@ -197,11 +176,11 @@ public class SetupOteCommandTest extends CommandTestCase { ImmutableList ipAddress = ImmutableList.of( CidrAddressBlock.create("1.1.1.1")); - verifyRegistrarCreation("blobio-1", "blobio-sunrise", passwords.get(0), ipAddress); - verifyRegistrarCreation("blobio-2", "blobio-landrush", passwords.get(1), ipAddress); - verifyRegistrarCreation("blobio-3", "blobio-ga", passwords.get(2), ipAddress); - verifyRegistrarCreation("blobio-4", "blobio-ga", passwords.get(3), ipAddress); - verifyRegistrarCreation("blobio-5", "blobio-eap", passwords.get(4), ipAddress); + verifyRegistrarCreation("blobio-1", "blobio-sunrise", PASSWORD, ipAddress); + verifyRegistrarCreation("blobio-2", "blobio-landrush", PASSWORD, ipAddress); + verifyRegistrarCreation("blobio-3", "blobio-ga", PASSWORD, ipAddress); + verifyRegistrarCreation("blobio-4", "blobio-ga", PASSWORD, ipAddress); + verifyRegistrarCreation("blobio-5", "blobio-eap", PASSWORD, ipAddress); verifyRegistrarContactCreation("blobio-1", "contact@email.com"); verifyRegistrarContactCreation("blobio-2", "contact@email.com"); @@ -216,23 +195,14 @@ public class SetupOteCommandTest extends CommandTestCase { "--ip_whitelist=1.1.1.1", "--registrar=abc", "--email=abc@email.com", - "--dns_writers=VoidDnsWriter", "--certfile=" + getCertFilename()); - verifyTldCreation( - "abc-sunrise", - "ABCSUNR0", - TldState.START_DATE_SUNRISE, - "VoidDnsWriter", - "default_sandbox_list"); - verifyTldCreation( - "abc-landrush", "ABCLAND1", TldState.LANDRUSH, "VoidDnsWriter", "default_sandbox_list"); + verifyTldCreation("abc-sunrise", "ABCSUNR0", TldState.START_DATE_SUNRISE); + verifyTldCreation("abc-landrush", "ABCLAND1", TldState.LANDRUSH); verifyTldCreation( "abc-ga", "ABCGA2", TldState.GENERAL_AVAILABILITY, - "VoidDnsWriter", - "default_sandbox_list", Duration.standardMinutes(60), Duration.standardMinutes(10), Duration.standardMinutes(5), @@ -241,8 +211,6 @@ public class SetupOteCommandTest extends CommandTestCase { "abc-eap", "ABCEAP3", TldState.GENERAL_AVAILABILITY, - "VoidDnsWriter", - "default_sandbox_list", Duration.standardMinutes(60), Duration.standardMinutes(10), Duration.standardMinutes(5), @@ -251,11 +219,11 @@ public class SetupOteCommandTest extends CommandTestCase { ImmutableList ipAddress = ImmutableList.of(CidrAddressBlock.create("1.1.1.1")); - verifyRegistrarCreation("abc-1", "abc-sunrise", passwords.get(0), ipAddress); - verifyRegistrarCreation("abc-2", "abc-landrush", passwords.get(1), ipAddress); - verifyRegistrarCreation("abc-3", "abc-ga", passwords.get(2), ipAddress); - verifyRegistrarCreation("abc-4", "abc-ga", passwords.get(3), ipAddress); - verifyRegistrarCreation("abc-5", "abc-eap", passwords.get(4), ipAddress); + verifyRegistrarCreation("abc-1", "abc-sunrise", PASSWORD, ipAddress); + verifyRegistrarCreation("abc-2", "abc-landrush", PASSWORD, ipAddress); + verifyRegistrarCreation("abc-3", "abc-ga", PASSWORD, ipAddress); + verifyRegistrarCreation("abc-4", "abc-ga", PASSWORD, ipAddress); + verifyRegistrarCreation("abc-5", "abc-eap", PASSWORD, ipAddress); verifyRegistrarContactCreation("abc-1", "abc@email.com"); verifyRegistrarContactCreation("abc-2", "abc@email.com"); @@ -267,19 +235,15 @@ public class SetupOteCommandTest extends CommandTestCase { @Test public void testSuccess_certificateHash() throws Exception { runCommandForced( - "--eap_only", "--ip_whitelist=1.1.1.1", "--registrar=blobio", "--email=contact@email.com", - "--dns_writers=VoidDnsWriter", "--certhash=" + SAMPLE_CERT_HASH); verifyTldCreation( "blobio-eap", "BLOBIOE3", TldState.GENERAL_AVAILABILITY, - "VoidDnsWriter", - "default_sandbox_list", Duration.standardMinutes(60), Duration.standardMinutes(10), Duration.standardMinutes(5), @@ -288,36 +252,7 @@ public class SetupOteCommandTest extends CommandTestCase { ImmutableList ipAddress = ImmutableList.of(CidrAddressBlock.create("1.1.1.1")); - verifyRegistrarCreation("blobio-5", "blobio-eap", passwords.get(0), ipAddress, true); - - verifyRegistrarContactCreation("blobio-5", "contact@email.com"); - } - - @Test - public void testSuccess_eapOnly() throws Exception { - runCommandForced( - "--eap_only", - "--ip_whitelist=1.1.1.1", - "--registrar=blobio", - "--email=contact@email.com", - "--dns_writers=VoidDnsWriter", - "--certfile=" + getCertFilename()); - - verifyTldCreation( - "blobio-eap", - "BLOBIOE3", - TldState.GENERAL_AVAILABILITY, - "VoidDnsWriter", - "default_sandbox_list", - Duration.standardMinutes(60), - Duration.standardMinutes(10), - Duration.standardMinutes(5), - true); - - ImmutableList ipAddress = ImmutableList.of( - CidrAddressBlock.create("1.1.1.1")); - - verifyRegistrarCreation("blobio-5", "blobio-eap", passwords.get(0), ipAddress); + verifyRegistrarCreation("blobio-5", "blobio-eap", PASSWORD, ipAddress, true); verifyRegistrarContactCreation("blobio-5", "contact@email.com"); } @@ -328,23 +263,14 @@ public class SetupOteCommandTest extends CommandTestCase { "--ip_whitelist=1.1.1.1,2.2.2.2", "--registrar=blobio", "--email=contact@email.com", - "--dns_writers=FooDnsWriter", "--certfile=" + getCertFilename()); - verifyTldCreation( - "blobio-sunrise", - "BLOBIOS0", - TldState.START_DATE_SUNRISE, - "FooDnsWriter", - "default_sandbox_list"); - verifyTldCreation( - "blobio-landrush", "BLOBIOL1", TldState.LANDRUSH, "FooDnsWriter", "default_sandbox_list"); + verifyTldCreation("blobio-sunrise", "BLOBIOS0", TldState.START_DATE_SUNRISE); + verifyTldCreation("blobio-landrush", "BLOBIOL1", TldState.LANDRUSH); verifyTldCreation( "blobio-ga", "BLOBIOG2", TldState.GENERAL_AVAILABILITY, - "FooDnsWriter", - "default_sandbox_list", Duration.standardMinutes(60), Duration.standardMinutes(10), Duration.standardMinutes(5), @@ -353,8 +279,6 @@ public class SetupOteCommandTest extends CommandTestCase { "blobio-eap", "BLOBIOE3", TldState.GENERAL_AVAILABILITY, - "FooDnsWriter", - "default_sandbox_list", Duration.standardMinutes(60), Duration.standardMinutes(10), Duration.standardMinutes(5), @@ -364,66 +288,11 @@ public class SetupOteCommandTest extends CommandTestCase { CidrAddressBlock.create("1.1.1.1"), CidrAddressBlock.create("2.2.2.2")); - verifyRegistrarCreation("blobio-1", "blobio-sunrise", passwords.get(0), ipAddresses); - verifyRegistrarCreation("blobio-2", "blobio-landrush", passwords.get(1), ipAddresses); - verifyRegistrarCreation("blobio-3", "blobio-ga", passwords.get(2), ipAddresses); - verifyRegistrarCreation("blobio-4", "blobio-ga", passwords.get(3), ipAddresses); - verifyRegistrarCreation("blobio-5", "blobio-eap", passwords.get(4), ipAddresses); - - verifyRegistrarContactCreation("blobio-1", "contact@email.com"); - verifyRegistrarContactCreation("blobio-2", "contact@email.com"); - verifyRegistrarContactCreation("blobio-3", "contact@email.com"); - verifyRegistrarContactCreation("blobio-4", "contact@email.com"); - verifyRegistrarContactCreation("blobio-5", "contact@email.com"); - } - - @Test - public void testSuccess_alternatePremiumList() throws Exception { - runCommandForced( - "--ip_whitelist=1.1.1.1", - "--registrar=blobio", - "--email=contact@email.com", - "--certfile=" + getCertFilename(), - "--dns_writers=BarDnsWriter", - "--premium_list=alternate_list"); - - verifyTldCreation( - "blobio-sunrise", - "BLOBIOS0", - TldState.START_DATE_SUNRISE, - "BarDnsWriter", - "alternate_list"); - verifyTldCreation( - "blobio-landrush", "BLOBIOL1", TldState.LANDRUSH, "BarDnsWriter", "alternate_list"); - verifyTldCreation( - "blobio-ga", - "BLOBIOG2", - TldState.GENERAL_AVAILABILITY, - "BarDnsWriter", - "alternate_list", - Duration.standardMinutes(60), - Duration.standardMinutes(10), - Duration.standardMinutes(5), - false); - verifyTldCreation( - "blobio-eap", - "BLOBIOE3", - TldState.GENERAL_AVAILABILITY, - "BarDnsWriter", - "alternate_list", - Duration.standardMinutes(60), - Duration.standardMinutes(10), - Duration.standardMinutes(5), - true); - - ImmutableList ipAddress = ImmutableList.of( - CidrAddressBlock.create("1.1.1.1")); - - verifyRegistrarCreation("blobio-1", "blobio-sunrise", passwords.get(0), ipAddress); - verifyRegistrarCreation("blobio-2", "blobio-landrush", passwords.get(1), ipAddress); - verifyRegistrarCreation("blobio-3", "blobio-ga", passwords.get(2), ipAddress); - verifyRegistrarCreation("blobio-4", "blobio-ga", passwords.get(3), ipAddress); - verifyRegistrarCreation("blobio-5", "blobio-eap", passwords.get(4), ipAddress); + verifyRegistrarCreation("blobio-1", "blobio-sunrise", PASSWORD, ipAddresses); + verifyRegistrarCreation("blobio-2", "blobio-landrush", PASSWORD, ipAddresses); + verifyRegistrarCreation("blobio-3", "blobio-ga", PASSWORD, ipAddresses); + verifyRegistrarCreation("blobio-4", "blobio-ga", PASSWORD, ipAddresses); + verifyRegistrarCreation("blobio-5", "blobio-eap", PASSWORD, ipAddresses); verifyRegistrarContactCreation("blobio-1", "contact@email.com"); verifyRegistrarContactCreation("blobio-2", "contact@email.com"); @@ -441,7 +310,6 @@ public class SetupOteCommandTest extends CommandTestCase { runCommandForced( "--registrar=blobio", "--email=contact@email.com", - "--dns_writers=VoidDnsWriter", "--certfile=" + getCertFilename())); assertThat(thrown).hasMessageThat().contains("option is required: -w, --ip_whitelist"); } @@ -455,7 +323,6 @@ public class SetupOteCommandTest extends CommandTestCase { runCommandForced( "--ip_whitelist=1.1.1.1", "--email=contact@email.com", - "--dns_writers=VoidDnsWriter", "--certfile=" + getCertFilename())); assertThat(thrown).hasMessageThat().contains("option is required: -r, --registrar"); } @@ -469,7 +336,6 @@ public class SetupOteCommandTest extends CommandTestCase { runCommandForced( "--ip_whitelist=1.1.1.1", "--email=contact@email.com", - "--dns_writers=VoidDnsWriter", "--registrar=blobio")); assertThat(thrown) .hasMessageThat() @@ -486,7 +352,6 @@ public class SetupOteCommandTest extends CommandTestCase { runCommandForced( "--ip_whitelist=1.1.1.1", "--email=contact@email.com", - "--dns_writers=VoidDnsWriter", "--registrar=blobio", "--certfile=" + getCertFilename(), "--certhash=" + SAMPLE_CERT_HASH)); @@ -496,20 +361,6 @@ public class SetupOteCommandTest extends CommandTestCase { "Must specify exactly one of client certificate file or client certificate hash."); } - @Test - public void testFailure_missingDnsWriter() { - ParameterException thrown = - assertThrows( - ParameterException.class, - () -> - runCommandForced( - "--ip_whitelist=1.1.1.1", - "--email=contact@email.com", - "--certfile=" + getCertFilename(), - "--registrar=blobio")); - assertThat(thrown).hasMessageThat().contains("option is required: --dns_writers"); - } - @Test public void testFailure_missingEmail() { ParameterException thrown = @@ -518,7 +369,6 @@ public class SetupOteCommandTest extends CommandTestCase { () -> runCommandForced( "--ip_whitelist=1.1.1.1", - "--dns_writers=VoidDnsWriter", "--certfile=" + getCertFilename(), "--registrar=blobio")); assertThat(thrown).hasMessageThat().contains("option is required: --email"); @@ -534,7 +384,6 @@ public class SetupOteCommandTest extends CommandTestCase { "--ip_whitelist=1.1.1.1", "--registrar=blobio", "--email=contact@email.com", - "--dns_writers=VoidDnsWriter", "--certfile=/dev/null")); assertThat(thrown).hasMessageThat().contains("No X509Certificate found"); } @@ -549,26 +398,8 @@ public class SetupOteCommandTest extends CommandTestCase { "--ip_whitelist=1.1.1.1", "--registrar=3blobio", "--email=contact@email.com", - "--dns_writers=VoidDnsWriter", "--certfile=" + getCertFilename())); - assertThat(thrown).hasMessageThat().contains("Registrar name is invalid"); - } - - @Test - public void testFailure_invalidDnsWriter() { - IllegalArgumentException thrown = - assertThrows( - IllegalArgumentException.class, - () -> - runCommandForced( - "--ip_whitelist=1.1.1.1", - "--registrar=blobio", - "--email=contact@email.com", - "--dns_writers=InvalidDnsWriter", - "--certfile=" + getCertFilename())); - assertThat(thrown) - .hasMessageThat() - .contains("Invalid DNS writer name(s) specified: [InvalidDnsWriter]"); + assertThat(thrown).hasMessageThat().contains("Invalid registrar name: 3blobio"); } @Test @@ -581,9 +412,8 @@ public class SetupOteCommandTest extends CommandTestCase { "--ip_whitelist=1.1.1.1", "--registrar=bl", "--email=contact@email.com", - "--dns_writers=VoidDnsWriter", "--certfile=" + getCertFilename())); - assertThat(thrown).hasMessageThat().contains("Registrar name is invalid"); + assertThat(thrown).hasMessageThat().contains("Invalid registrar name: bl"); } @Test @@ -596,9 +426,8 @@ public class SetupOteCommandTest extends CommandTestCase { "--ip_whitelist=1.1.1.1", "--registrar=blobiotoooolong", "--email=contact@email.com", - "--dns_writers=VoidDnsWriter", "--certfile=" + getCertFilename())); - assertThat(thrown).hasMessageThat().contains("Registrar name is invalid"); + assertThat(thrown).hasMessageThat().contains("Invalid registrar name: blobiotoooolong"); } @Test @@ -611,25 +440,8 @@ public class SetupOteCommandTest extends CommandTestCase { "--ip_whitelist=1.1.1.1", "--registrar=blo#bio", "--email=contact@email.com", - "--dns_writers=VoidDnsWriter", "--certfile=" + getCertFilename())); - assertThat(thrown).hasMessageThat().contains("Registrar name is invalid"); - } - - @Test - public void testFailure_invalidPremiumList() { - IllegalArgumentException thrown = - assertThrows( - IllegalArgumentException.class, - () -> - runCommandForced( - "--ip_whitelist=1.1.1.1", - "--registrar=blobio", - "--email=contact@email.com", - "--dns_writers=VoidDnsWriter", - "--certfile=" + getCertFilename(), - "--premium_list=foo")); - assertThat(thrown).hasMessageThat().contains("The premium list 'foo' doesn't exist"); + assertThat(thrown).hasMessageThat().contains("Invalid registrar name: blo#bio"); } @Test @@ -643,9 +455,23 @@ public class SetupOteCommandTest extends CommandTestCase { "--ip_whitelist=1.1.1.1", "--registrar=blobio", "--email=contact@email.com", - "--dns_writers=VoidDnsWriter", "--certfile=" + getCertFilename())); - assertThat(thrown).hasMessageThat().contains("TLD 'blobio-sunrise' already exists"); + assertThat(thrown).hasMessageThat().contains("Registry(\"blobio-sunrise\")"); + } + + @Test + public void testSuccess_tldExists_replaceExisting() throws Exception { + createTld("blobio-sunrise"); + + runCommandForced( + "--overwrite", + "--ip_whitelist=1.1.1.1", + "--registrar=blobio", + "--email=contact@email.com", + "--certfile=" + getCertFilename()); + + verifyTldCreation("blobio-sunrise", "BLOBIOS0", TldState.START_DATE_SUNRISE); + verifyTldCreation("blobio-landrush", "BLOBIOL1", TldState.LANDRUSH); } @Test @@ -663,8 +489,29 @@ public class SetupOteCommandTest extends CommandTestCase { "--ip_whitelist=1.1.1.1", "--registrar=blobio", "--email=contact@email.com", - "--dns_writers=VoidDnsWriter", "--certfile=" + getCertFilename())); - assertThat(thrown).hasMessageThat().contains("Registrar blobio-1 already exists"); + assertThat(thrown).hasMessageThat().contains("Registrar(\"blobio-1\")"); + } + + @Test + public void testSuccess_registrarExists_replaceExisting() throws Exception { + Registrar registrar = loadRegistrar("TheRegistrar").asBuilder() + .setClientId("blobio-1") + .setRegistrarName("blobio-1") + .build(); + persistResource(registrar); + + runCommandForced( + "--overwrite", + "--ip_whitelist=1.1.1.1", + "--registrar=blobio", + "--email=contact@email.com", + "--certfile=" + getCertFilename()); + + ImmutableList ipAddress = ImmutableList.of( + CidrAddressBlock.create("1.1.1.1")); + + verifyRegistrarCreation("blobio-1", "blobio-sunrise", PASSWORD, ipAddress); + verifyRegistrarCreation("blobio-2", "blobio-landrush", PASSWORD, ipAddress); } }