google-nomulus/java/google/registry/model/OteAccountBuilder.java
mcilwain 8ac8ecf8f6 Rationalize OT&E client ID validation rules
This makes the validation rules much simpler, thus placing less cognitive load on the users of the console and nomulus tool.  The changes are:

1. Don't allow hyphens. No real registrars use hyphens in their client IDs, and it's better to reserve these solely as the delimiter between the base client ID and the number representing the environment.
2. Allow the first character to be a number.  This has affected multiple real registrars, causing their OT&E and production client IDs to be different.  There's no reason for this restriction, as the only reason motivating it was that there are no TLDs that start with a number.  However, the OT&E TLDs are created only in sandbox and never have DNS syncing enabled, so this restriction is purposeless.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=229187198
2019-01-14 16:28:12 -05:00

372 lines
15 KiB
Java

// 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.model.registry.Registry.TldState.GENERAL_AVAILABILITY;
import static google.registry.model.registry.Registry.TldState.START_DATE_SUNRISE;
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.
*
* <p>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&amp;E entities.
* Nothing is created until you call {@link #buildAndPersist}.
*
* Usage example:
*
* <pre> {@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();
* }</pre>
*/
public final class OteAccountBuilder {
/**
* Validation regex for registrar base client IDs (3-14 lowercase alphanumeric characters).
*
* <p>The base client ID is appended with numbers to create four different test registrar accounts
* (e.g. reg-1, reg-3, reg-4, reg-5). Registrar client IDs are of type clIDType in eppcom.xsd
* which is limited to 16 characters, hence the limit of 14 here to account for the dash and
* numbers.
*
* <p>The base client ID is also used to generate the OT&E TLDs, hence the restriction to
* lowercase alphanumeric characters.
*/
private static final Pattern REGISTRAR_PATTERN = Pattern.compile("^[a-z0-9]{3,14}$");
// 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<DateTime, Money> 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<String, String> clientIdToTld;
private final Registry sunriseTld;
private final Registry gaTld;
private final Registry eapTld;
private final ImmutableList.Builder<RegistrarContact> contactsBuilder =
new ImmutableList.Builder<>();
private ImmutableList<Registrar> 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", START_DATE_SUNRISE, false, 0);
gaTld = createTld(baseClientId + "-ga", GENERAL_AVAILABILITY, false, 2);
eapTld = createTld(baseClientId + "-eap", GENERAL_AVAILABILITY, 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.
*
* <p>If true, any existing entity that conflicts with the entities we want to create will be
* replaced with the newly created data.
*
* <p>If false, encountering an existing entity that conflicts with one we want to create will
* throw an exception during {@link #buildAndPersist}.
*
* <p>NOTE that if we fail, no entities are created (the creation is atomic).
*
* <p>Default is false (failing if entities exist)
*/
public OteAccountBuilder setReplaceExisting(boolean replaceExisting) {
this.replaceExisting = replaceExisting;
return this;
}
/**
* Adds a RegistrarContact with Web Console access.
*
* <p>NOTE: can be called more than once, adding multiple contacts. Each contact will have access
* to all OT&amp;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&amp;E Registrars.
*
* <p>Use this to set up registrar fields.
*
* <p>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<Registrar.Builder, Registrar.Builder> 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&amp;E Registrars. */
public OteAccountBuilder setPassword(String password) {
return transformRegistrars(builder -> builder.setPassword(password));
}
/** Sets the client certificate hash to all the OT&amp;E Registrars. */
public OteAccountBuilder setCertificateHash(String certHash) {
return transformRegistrars(builder -> builder.setClientCertificateHash(certHash));
}
/** Sets the client certificate to all the OT&amp;E Registrars. */
public OteAccountBuilder setCertificate(String asciiCert, DateTime now) {
return transformRegistrars(builder -> builder.setClientCertificate(asciiCert, now));
}
/** Sets the IP whitelist to all the OT&amp;E Registrars. */
public OteAccountBuilder setIpWhitelist(Collection<String> ipWhitelist) {
ImmutableList<CidrAddressBlock> ipAddressWhitelist =
ipWhitelist.stream().map(CidrAddressBlock::create).collect(toImmutableList());
return transformRegistrars(builder -> builder.setIpAddressWhitelist(ipAddressWhitelist));
}
/**
* Persists all the OT&amp;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<String, String> buildAndPersist() {
// save all the entitiesl in a single transaction
ofy().transact(this::saveAllEntities);
return clientIdToTld;
}
/**
* Return map from the OT&amp;E clientIds we will create to the new TLDs they will have access to.
*/
public ImmutableMap<String, String> getClientIdToTldMap() {
return clientIdToTld;
}
/** Saves all the OT&amp;E entities we created. */
private void saveAllEntities() {
ofy().assertInTransaction();
ImmutableList<Registry> registries = ImmutableList.of(sunriseTld, gaTld, eapTld);
ImmutableList<RegistrarContact> contacts = contactsBuilder.build();
if (!replaceExisting) {
ImmutableList<Key<ImmutableObject>> keys =
Streams.concat(registries.stream(), registrars.stream(), contacts.stream())
.map(Key::create)
.collect(toImmutableList());
Set<Key<ImmutableObject>> 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,
boolean isEarlyAccess,
int roidSuffix) {
String tldNameAlphaNumerical = tldName.replaceAll("[^a-z0-9]", "");
Optional<PremiumList> 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))
.setAddGracePeriodLength(SHORT_ADD_GRACE_PERIOD)
.setPendingDeleteLength(SHORT_PENDING_DELETE_LENGTH)
.setRedemptionGracePeriodLength(SHORT_REDEMPTION_GRACE_PERIOD);
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&amp;E, with the TLDs each has access to. */
public static ImmutableMap<String, String> createClientIdToTldMap(String baseClientId) {
checkArgument(
REGISTRAR_PATTERN.matcher(baseClientId).matches(),
"Invalid registrar name: %s",
baseClientId);
return new ImmutableMap.Builder<String, String>()
.put(baseClientId + "-1", baseClientId + "-sunrise")
// The -2 registrar no longer exists because landrush no longer exists.
.put(baseClientId + "-3", baseClientId + "-ga")
.put(baseClientId + "-4", baseClientId + "-ga")
.put(baseClientId + "-5", baseClientId + "-eap")
.build();
}
/** Returns the base client ID that correspond to a given OT&amp;E client ID. */
public static String getBaseClientId(String oteClientId) {
int index = oteClientId.lastIndexOf('-');
checkArgument(index > 0, "Invalid OT&E client ID: %s", oteClientId);
String baseClientId = oteClientId.substring(0, index);
checkArgument(
createClientIdToTldMap(baseClientId).containsKey(oteClientId),
"ID %s is not one of the OT&E client IDs for base %s",
oteClientId,
baseClientId);
return baseClientId;
}
}