mirror of
https://github.com/google/nomulus.git
synced 2025-04-30 12:07:51 +02:00
404 lines
14 KiB
Java
404 lines
14 KiB
Java
// Copyright 2016 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.tools;
|
|
|
|
import static com.google.common.base.Preconditions.checkArgument;
|
|
import static com.google.common.base.Preconditions.checkState;
|
|
import static com.google.common.base.Predicates.isNull;
|
|
import static com.google.common.base.Strings.isNullOrEmpty;
|
|
import static google.registry.util.DomainNameUtils.canonicalizeDomainName;
|
|
import static google.registry.util.RegistrarUtils.normalizeRegistrarName;
|
|
import static java.nio.charset.StandardCharsets.US_ASCII;
|
|
import static org.joda.time.DateTimeZone.UTC;
|
|
|
|
import com.beust.jcommander.Parameter;
|
|
import com.google.common.base.Optional;
|
|
import com.google.common.collect.ImmutableList;
|
|
import com.google.common.collect.ImmutableSet;
|
|
import com.google.common.collect.Iterables;
|
|
import google.registry.model.billing.RegistrarBillingUtils;
|
|
import google.registry.model.registrar.Registrar;
|
|
import google.registry.model.registrar.Registrar.BillingMethod;
|
|
import google.registry.model.registrar.RegistrarAddress;
|
|
import google.registry.tools.params.OptionalLongParameter;
|
|
import google.registry.tools.params.OptionalPhoneNumberParameter;
|
|
import google.registry.tools.params.OptionalStringParameter;
|
|
import google.registry.tools.params.PathParameter;
|
|
import google.registry.util.CidrAddressBlock;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.Path;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import javax.annotation.Nullable;
|
|
import org.joda.money.CurrencyUnit;
|
|
import org.joda.money.Money;
|
|
import org.joda.time.DateTime;
|
|
|
|
/** Shared base class for commands to create or update a {@link Registrar}. */
|
|
abstract class CreateOrUpdateRegistrarCommand extends MutatingCommand {
|
|
|
|
@Parameter(
|
|
description = "Client identifier of the registrar account",
|
|
required = true)
|
|
List<String> mainParameters;
|
|
|
|
@Parameter(
|
|
names = "--registrar_type",
|
|
description = "Type of the registrar")
|
|
Registrar.Type registrarType;
|
|
|
|
@Nullable
|
|
@Parameter(
|
|
names = "--registrar_state",
|
|
description = "Initial state of the registrar")
|
|
Registrar.State registrarState;
|
|
|
|
@Parameter(
|
|
names = "--allowed_tlds",
|
|
description = "Comma-delimited list of TLDs which the registrar is allowed to use")
|
|
List<String> allowedTlds = new ArrayList<>();
|
|
|
|
@Parameter(
|
|
names = "--add_allowed_tlds",
|
|
description = "Comma-delimited list of TLDs to add to TLDs a registrar is allowed to use")
|
|
List<String> addAllowedTlds = new ArrayList<>();
|
|
|
|
@Nullable
|
|
@Parameter(
|
|
names = "--password",
|
|
description = "Password for the registrar account")
|
|
String password;
|
|
|
|
@Nullable
|
|
@Parameter(
|
|
names = "--name",
|
|
description = "Name of the registrar")
|
|
String registrarName;
|
|
|
|
@Nullable
|
|
@Parameter(
|
|
names = "--email",
|
|
description = "Email address of registrar",
|
|
converter = OptionalStringParameter.class,
|
|
validateWith = OptionalStringParameter.class)
|
|
Optional<String> email;
|
|
|
|
@Nullable
|
|
@Parameter(
|
|
names = "--icann_referral_email",
|
|
description = "ICANN referral email, as specified in registrar contract")
|
|
String icannReferralEmail;
|
|
|
|
@Nullable
|
|
@Parameter(
|
|
names = "--url",
|
|
description = "URL of registrar's website",
|
|
converter = OptionalStringParameter.class,
|
|
validateWith = OptionalStringParameter.class)
|
|
private Optional<String> url;
|
|
|
|
@Nullable
|
|
@Parameter(
|
|
names = "--phone",
|
|
description = "E.164 phone number, e.g. +1.2125650666",
|
|
converter = OptionalPhoneNumberParameter.class,
|
|
validateWith = OptionalPhoneNumberParameter.class)
|
|
Optional<String> phone;
|
|
|
|
@Nullable
|
|
@Parameter(
|
|
names = "--fax",
|
|
description = "E.164 fax number, e.g. +1.2125650666",
|
|
converter = OptionalPhoneNumberParameter.class,
|
|
validateWith = OptionalPhoneNumberParameter.class)
|
|
Optional<String> fax;
|
|
|
|
@Nullable
|
|
@Parameter(
|
|
names = "--cert_file",
|
|
description = "File containing client certificate (X.509 PEM)",
|
|
validateWith = PathParameter.InputFile.class)
|
|
Path clientCertificateFilename;
|
|
|
|
@Nullable
|
|
@Parameter(
|
|
names = "--cert_hash",
|
|
description = "Hash of client certificate (SHA256 base64 no padding). Do not use this unless "
|
|
+ "you want to store ONLY the hash and not the full certificate")
|
|
private String clientCertificateHash;
|
|
|
|
@Nullable
|
|
@Parameter(
|
|
names = "--failover_cert_file",
|
|
description = "File containing failover client certificate (X.509 PEM)",
|
|
validateWith = PathParameter.InputFile.class)
|
|
Path failoverClientCertificateFilename;
|
|
|
|
@Parameter(
|
|
names = "--ip_whitelist",
|
|
description = "Comma-delimited list of IP ranges")
|
|
List<String> ipWhitelist = new ArrayList<>();
|
|
|
|
@Nullable
|
|
@Parameter(
|
|
names = "--iana_id",
|
|
description = "Registrar IANA ID",
|
|
converter = OptionalLongParameter.class,
|
|
validateWith = OptionalLongParameter.class)
|
|
Optional<Long> ianaId;
|
|
|
|
@Nullable
|
|
@Parameter(
|
|
names = "--billing_id",
|
|
description = "Registrar Billing ID (i.e. Oracle #)",
|
|
converter = OptionalLongParameter.class,
|
|
validateWith = OptionalLongParameter.class)
|
|
private Optional<Long> billingId;
|
|
|
|
@Nullable
|
|
@Parameter(
|
|
names = "--billing_method",
|
|
description = "Method by which registry bills this registrar customer")
|
|
private BillingMethod billingMethod;
|
|
|
|
@Nullable
|
|
@Parameter(
|
|
names = "--street",
|
|
variableArity = true,
|
|
description = "Street lines of address. Can take up to 3 lines.")
|
|
List<String> street;
|
|
|
|
@Nullable
|
|
@Parameter(
|
|
names = "--city",
|
|
description = "City of address")
|
|
String city;
|
|
|
|
@Nullable
|
|
@Parameter(
|
|
names = "--state",
|
|
description = "State/Province of address. The value \"null\" clears this field.")
|
|
String state;
|
|
|
|
@Nullable
|
|
@Parameter(
|
|
names = "--zip",
|
|
description = "Postal code of address. The value \"null\" clears this field.")
|
|
String zip;
|
|
|
|
@Nullable
|
|
@Parameter(
|
|
names = "--cc",
|
|
description = "Country code of address")
|
|
String countryCode;
|
|
|
|
@Nullable
|
|
@Parameter(
|
|
names = "--block_premium",
|
|
description = "Whether premium name registration should be blocked on this registrar",
|
|
arity = 1)
|
|
private Boolean blockPremiumNames;
|
|
|
|
@Nullable
|
|
@Parameter(
|
|
names = "--sync_groups",
|
|
description = "Whether this registrar's groups should be updated at the next scheduled sync",
|
|
arity = 1)
|
|
private Boolean contactsRequireSyncing;
|
|
|
|
@Nullable
|
|
@Parameter(
|
|
names = "--drive_id",
|
|
description = "Id of this registrar's folder in Drive",
|
|
converter = OptionalStringParameter.class,
|
|
validateWith = OptionalStringParameter.class)
|
|
Optional<String> driveFolderId;
|
|
|
|
@Nullable
|
|
@Parameter(
|
|
names = "--passcode",
|
|
description = "Telephone support passcode")
|
|
String phonePasscode;
|
|
|
|
@Nullable
|
|
@Parameter(
|
|
names = "--whois",
|
|
description = "Hostname of registrar WHOIS server. (Default: whois.nic.google)")
|
|
String whoisServer;
|
|
|
|
/** Returns the existing registrar (for update) or null (for creates). */
|
|
@Nullable
|
|
abstract Registrar getOldRegistrar(String clientId);
|
|
|
|
protected void initRegistrarCommand() throws Exception {}
|
|
|
|
@Override
|
|
protected final void init() throws Exception {
|
|
initRegistrarCommand();
|
|
DateTime now = DateTime.now(UTC);
|
|
for (String clientId : mainParameters) {
|
|
Registrar oldRegistrar = getOldRegistrar(clientId);
|
|
Registrar.Builder builder = (oldRegistrar == null)
|
|
? new Registrar.Builder().setClientId(clientId)
|
|
: oldRegistrar.asBuilder();
|
|
|
|
if (!isNullOrEmpty(password)) {
|
|
builder.setPassword(password);
|
|
}
|
|
if (!isNullOrEmpty(registrarName)) {
|
|
builder.setRegistrarName(registrarName);
|
|
}
|
|
if (email != null) {
|
|
builder.setEmailAddress(email.orNull());
|
|
}
|
|
if (url != null) {
|
|
builder.setUrl(url.orNull());
|
|
}
|
|
if (phone != null) {
|
|
builder.setPhoneNumber(phone.orNull());
|
|
}
|
|
if (fax != null) {
|
|
builder.setFaxNumber(fax.orNull());
|
|
}
|
|
if (registrarType != null) {
|
|
builder.setType(registrarType);
|
|
}
|
|
if (registrarState != null) {
|
|
builder.setState(registrarState);
|
|
}
|
|
if (driveFolderId != null) {
|
|
builder.setDriveFolderId(driveFolderId.orNull());
|
|
}
|
|
if (!allowedTlds.isEmpty()) {
|
|
checkArgument(addAllowedTlds.isEmpty(),
|
|
"Can't specify both --allowedTlds and --addAllowedTlds");
|
|
ImmutableSet.Builder<String> allowedTldsBuilder = new ImmutableSet.Builder<>();
|
|
for (String allowedTld : allowedTlds) {
|
|
allowedTldsBuilder.add(canonicalizeDomainName(allowedTld));
|
|
}
|
|
builder.setAllowedTlds(allowedTldsBuilder.build());
|
|
}
|
|
if (!addAllowedTlds.isEmpty()) {
|
|
ImmutableSet.Builder<String> allowedTldsBuilder = new ImmutableSet.Builder<>();
|
|
if (oldRegistrar != null) {
|
|
allowedTldsBuilder.addAll(oldRegistrar.getAllowedTlds());
|
|
}
|
|
for (String allowedTld : addAllowedTlds) {
|
|
allowedTldsBuilder.add(canonicalizeDomainName(allowedTld));
|
|
}
|
|
builder.setAllowedTlds(allowedTldsBuilder.build());
|
|
}
|
|
if (!ipWhitelist.isEmpty()) {
|
|
ImmutableList.Builder<CidrAddressBlock> ipWhitelistBuilder = new ImmutableList.Builder<>();
|
|
if (!(ipWhitelist.size() == 1 && ipWhitelist.get(0).contains("null"))) {
|
|
for (String ipRange : ipWhitelist) {
|
|
ipWhitelistBuilder.add(CidrAddressBlock.create(ipRange));
|
|
}
|
|
}
|
|
builder.setIpAddressWhitelist(ipWhitelistBuilder.build());
|
|
}
|
|
if (clientCertificateFilename != null) {
|
|
String asciiCert = new String(Files.readAllBytes(clientCertificateFilename), US_ASCII);
|
|
builder.setClientCertificate(asciiCert, now);
|
|
}
|
|
if (failoverClientCertificateFilename != null) {
|
|
String asciiCert =
|
|
new String(Files.readAllBytes(failoverClientCertificateFilename), US_ASCII);
|
|
builder.setFailoverClientCertificate(asciiCert, now);
|
|
}
|
|
if (!isNullOrEmpty(clientCertificateHash)) {
|
|
checkArgument(clientCertificateFilename == null,
|
|
"Can't specify both --cert_hash and --cert_file");
|
|
if ("null".equals(clientCertificateHash)) {
|
|
clientCertificateHash = null;
|
|
}
|
|
builder.setClientCertificateHash(clientCertificateHash);
|
|
}
|
|
if (ianaId != null) {
|
|
builder.setIanaIdentifier(ianaId.orNull());
|
|
}
|
|
if (billingId != null) {
|
|
builder.setBillingIdentifier(billingId.orNull());
|
|
}
|
|
if (billingMethod != null) {
|
|
if (oldRegistrar != null && !billingMethod.equals(oldRegistrar.getBillingMethod())) {
|
|
Map<CurrencyUnit, Money> balances = RegistrarBillingUtils.loadBalance(oldRegistrar);
|
|
for (Money balance : balances.values()) {
|
|
checkState(balance.isZero(),
|
|
"Refusing to change billing method on Registrar '%s' from %s to %s"
|
|
+ " because current balance is non-zero: %s",
|
|
clientId, oldRegistrar.getBillingMethod(), billingMethod, balances);
|
|
}
|
|
}
|
|
builder.setBillingMethod(billingMethod);
|
|
}
|
|
List<Object> streetAddressFields = Arrays.asList(street, city, state, zip, countryCode);
|
|
checkArgument(Iterables.any(streetAddressFields, isNull())
|
|
== Iterables.all(streetAddressFields, isNull()),
|
|
"Must specify all fields of address");
|
|
if (street != null) {
|
|
// We always set the localized address for now. That should be safe to do since it supports
|
|
// unrestricted UTF-8.
|
|
builder.setLocalizedAddress(new RegistrarAddress.Builder()
|
|
.setStreet(ImmutableList.copyOf(street))
|
|
.setCity(city)
|
|
.setState("null".equals(state) ? null : state)
|
|
.setZip("null".equals(zip) ? null : zip)
|
|
.setCountryCode(countryCode)
|
|
.build());
|
|
}
|
|
if (blockPremiumNames != null) {
|
|
builder.setBlockPremiumNames(blockPremiumNames);
|
|
}
|
|
if (contactsRequireSyncing != null) {
|
|
builder.setContactsRequireSyncing(contactsRequireSyncing);
|
|
}
|
|
// When creating a new REAL registrar or changing the type to REAL, a passcode is required.
|
|
// Leave existing REAL registrars alone.
|
|
if (Registrar.Type.REAL.equals(registrarType)
|
|
&& (oldRegistrar == null || oldRegistrar.getPhonePasscode() == null)) {
|
|
checkArgument(phonePasscode != null, "--passcode is required for REAL registrars.");
|
|
}
|
|
if (phonePasscode != null) {
|
|
builder.setPhonePasscode(phonePasscode);
|
|
}
|
|
if (icannReferralEmail != null) {
|
|
builder.setIcannReferralEmail(icannReferralEmail);
|
|
}
|
|
if (whoisServer != null) {
|
|
builder.setWhoisServer(whoisServer);
|
|
}
|
|
|
|
// If the registrarName is being set, verify that it is either null or it normalizes uniquely.
|
|
String oldRegistrarName = (oldRegistrar == null) ? null : oldRegistrar.getRegistrarName();
|
|
if (registrarName != null && !registrarName.equals(oldRegistrarName)) {
|
|
String normalizedName = normalizeRegistrarName(registrarName);
|
|
for (Registrar registrar : Registrar.loadAll()) {
|
|
if (registrar.getRegistrarName() != null) {
|
|
checkArgument(
|
|
!normalizedName.equals(normalizeRegistrarName(registrar.getRegistrarName())),
|
|
"The registrar name %s normalizes identically to existing registrar name %s",
|
|
registrarName,
|
|
registrar.getRegistrarName());
|
|
}
|
|
}
|
|
}
|
|
|
|
stageEntityChange(oldRegistrar, builder.build());
|
|
}
|
|
}
|
|
}
|