mirror of
https://github.com/google/nomulus.git
synced 2025-04-30 12:07:51 +02:00
It was nullable all along, but wasn't tagged as such, and thus it was possible to misuse the method from its call sites. Also adds an assertion about no NORDN tasks being enqueued in a failing domain create test for a required signed mark. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=187649865
1560 lines
65 KiB
Java
1560 lines
65 KiB
Java
// Copyright 2017 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.flows.domain;
|
|
|
|
import static com.google.common.base.Preconditions.checkNotNull;
|
|
import static com.google.common.base.Preconditions.checkState;
|
|
import static com.google.common.base.Predicates.equalTo;
|
|
import static com.google.common.collect.ImmutableMap.toImmutableMap;
|
|
import static com.google.common.collect.Iterables.any;
|
|
import static com.google.common.collect.Sets.difference;
|
|
import static com.google.common.collect.Sets.intersection;
|
|
import static com.google.common.collect.Sets.union;
|
|
import static google.registry.flows.domain.DomainPricingLogic.getMatchingLrpToken;
|
|
import static google.registry.model.domain.DomainResource.MAX_REGISTRATION_YEARS;
|
|
import static google.registry.model.ofy.ObjectifyService.ofy;
|
|
import static google.registry.model.registry.Registries.findTldForName;
|
|
import static google.registry.model.registry.Registries.getTlds;
|
|
import static google.registry.model.registry.label.ReservationType.FULLY_BLOCKED;
|
|
import static google.registry.model.registry.label.ReservationType.NAMESERVER_RESTRICTED;
|
|
import static google.registry.model.registry.label.ReservationType.RESERVED_FOR_ANCHOR_TENANT;
|
|
import static google.registry.model.registry.label.ReservedList.getAllowedNameservers;
|
|
import static google.registry.pricing.PricingEngineProxy.isDomainPremium;
|
|
import static google.registry.tldconfig.idn.IdnLabelValidator.findValidIdnTableForTld;
|
|
import static google.registry.util.CollectionUtils.nullToEmpty;
|
|
import static google.registry.util.DateTimeUtils.END_OF_TIME;
|
|
import static google.registry.util.DateTimeUtils.isAtOrAfter;
|
|
import static google.registry.util.DateTimeUtils.leapSafeAddYears;
|
|
import static google.registry.util.DomainNameUtils.ACE_PREFIX;
|
|
|
|
import com.google.common.base.CharMatcher;
|
|
import com.google.common.base.Joiner;
|
|
import com.google.common.base.Splitter;
|
|
import com.google.common.collect.ImmutableList;
|
|
import com.google.common.collect.ImmutableMap;
|
|
import com.google.common.collect.ImmutableMultimap;
|
|
import com.google.common.collect.ImmutableSet;
|
|
import com.google.common.collect.Sets;
|
|
import com.google.common.collect.Streams;
|
|
import com.google.common.net.InternetDomainName;
|
|
import com.googlecode.objectify.Key;
|
|
import google.registry.flows.EppException;
|
|
import google.registry.flows.EppException.AuthorizationErrorException;
|
|
import google.registry.flows.EppException.CommandUseErrorException;
|
|
import google.registry.flows.EppException.InvalidAuthorizationInformationErrorException;
|
|
import google.registry.flows.EppException.ObjectDoesNotExistException;
|
|
import google.registry.flows.EppException.ParameterValuePolicyErrorException;
|
|
import google.registry.flows.EppException.ParameterValueRangeErrorException;
|
|
import google.registry.flows.EppException.ParameterValueSyntaxErrorException;
|
|
import google.registry.flows.EppException.RequiredParameterMissingException;
|
|
import google.registry.flows.EppException.StatusProhibitsOperationException;
|
|
import google.registry.flows.EppException.UnimplementedOptionException;
|
|
import google.registry.flows.exceptions.ResourceHasClientUpdateProhibitedException;
|
|
import google.registry.model.EppResource;
|
|
import google.registry.model.billing.BillingEvent;
|
|
import google.registry.model.billing.BillingEvent.Flag;
|
|
import google.registry.model.billing.BillingEvent.Reason;
|
|
import google.registry.model.billing.BillingEvent.Recurring;
|
|
import google.registry.model.contact.ContactResource;
|
|
import google.registry.model.domain.DesignatedContact;
|
|
import google.registry.model.domain.DesignatedContact.Type;
|
|
import google.registry.model.domain.DomainApplication;
|
|
import google.registry.model.domain.DomainBase;
|
|
import google.registry.model.domain.DomainCommand.Create;
|
|
import google.registry.model.domain.DomainCommand.CreateOrUpdate;
|
|
import google.registry.model.domain.DomainCommand.InvalidReferencesException;
|
|
import google.registry.model.domain.DomainCommand.Update;
|
|
import google.registry.model.domain.DomainResource;
|
|
import google.registry.model.domain.ForeignKeyedDesignatedContact;
|
|
import google.registry.model.domain.LrpTokenEntity;
|
|
import google.registry.model.domain.Period;
|
|
import google.registry.model.domain.fee.BaseFee.FeeType;
|
|
import google.registry.model.domain.fee.Credit;
|
|
import google.registry.model.domain.fee.Fee;
|
|
import google.registry.model.domain.fee.FeeQueryCommandExtensionItem;
|
|
import google.registry.model.domain.fee.FeeQueryResponseExtensionItem;
|
|
import google.registry.model.domain.fee.FeeTransformCommandExtension;
|
|
import google.registry.model.domain.fee.FeeTransformResponseExtension;
|
|
import google.registry.model.domain.launch.LaunchCreateExtension;
|
|
import google.registry.model.domain.launch.LaunchExtension;
|
|
import google.registry.model.domain.launch.LaunchNotice;
|
|
import google.registry.model.domain.launch.LaunchNotice.InvalidChecksumException;
|
|
import google.registry.model.domain.launch.LaunchPhase;
|
|
import google.registry.model.domain.secdns.DelegationSignerData;
|
|
import google.registry.model.domain.secdns.SecDnsCreateExtension;
|
|
import google.registry.model.domain.secdns.SecDnsInfoExtension;
|
|
import google.registry.model.domain.secdns.SecDnsUpdateExtension;
|
|
import google.registry.model.domain.secdns.SecDnsUpdateExtension.Add;
|
|
import google.registry.model.domain.secdns.SecDnsUpdateExtension.Remove;
|
|
import google.registry.model.eppcommon.StatusValue;
|
|
import google.registry.model.eppoutput.EppResponse.ResponseExtension;
|
|
import google.registry.model.host.HostResource;
|
|
import google.registry.model.poll.PollMessage;
|
|
import google.registry.model.registrar.Registrar;
|
|
import google.registry.model.registrar.Registrar.State;
|
|
import google.registry.model.registry.Registry;
|
|
import google.registry.model.registry.Registry.TldState;
|
|
import google.registry.model.registry.label.ReservationType;
|
|
import google.registry.model.registry.label.ReservedList;
|
|
import google.registry.model.reporting.DomainTransactionRecord;
|
|
import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField;
|
|
import google.registry.model.reporting.HistoryEntry;
|
|
import google.registry.model.tmch.ClaimsListShard;
|
|
import google.registry.util.Idn;
|
|
import java.math.BigDecimal;
|
|
import java.util.Comparator;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Map.Entry;
|
|
import java.util.Optional;
|
|
import java.util.Set;
|
|
import javax.annotation.Nullable;
|
|
import org.joda.money.CurrencyUnit;
|
|
import org.joda.money.Money;
|
|
import org.joda.time.DateTime;
|
|
import org.joda.time.Duration;
|
|
|
|
/** Static utility functions for domain flows. */
|
|
public class DomainFlowUtils {
|
|
|
|
/** Map from launch phases to the allowed tld states. */
|
|
private static final ImmutableMultimap<LaunchPhase, TldState> LAUNCH_PHASE_TO_TLD_STATES =
|
|
new ImmutableMultimap.Builder<LaunchPhase, TldState>()
|
|
.put(LaunchPhase.SUNRISE, TldState.SUNRISE)
|
|
.put(LaunchPhase.SUNRUSH, TldState.SUNRUSH)
|
|
.put(LaunchPhase.LANDRUSH, TldState.LANDRUSH)
|
|
.put(LaunchPhase.CLAIMS, TldState.GENERAL_AVAILABILITY)
|
|
.put(LaunchPhase.SUNRISE, TldState.START_DATE_SUNRISE)
|
|
.put(LaunchPhase.OPEN, TldState.GENERAL_AVAILABILITY)
|
|
.build();
|
|
|
|
/** Reservation types that are allowed in sunrise by policy. */
|
|
public static final ImmutableSet<ReservationType> TYPES_ALLOWED_FOR_CREATE_ONLY_IN_SUNRISE =
|
|
Sets.immutableEnumSet(
|
|
ReservationType.ALLOWED_IN_SUNRISE,
|
|
ReservationType.NAME_COLLISION,
|
|
ReservationType.MISTAKEN_PREMIUM);
|
|
|
|
/** Non-sunrise tld states. */
|
|
private static final ImmutableSet<TldState> DISALLOWED_TLD_STATES_FOR_APPLICATION_FLOWS =
|
|
Sets.immutableEnumSet(
|
|
TldState.PREDELEGATION,
|
|
TldState.QUIET_PERIOD,
|
|
TldState.START_DATE_SUNRISE,
|
|
TldState.GENERAL_AVAILABILITY);
|
|
|
|
/** Strict validator for ascii lowercase letters, digits, and "-", allowing "." as a separator */
|
|
private static final CharMatcher ALLOWED_CHARS =
|
|
CharMatcher.inRange('a', 'z').or(CharMatcher.inRange('0', '9').or(CharMatcher.anyOf("-.")));
|
|
|
|
/** The maximum number of DS records allowed on a domain. */
|
|
private static final int MAX_DS_RECORDS_PER_DOMAIN = 8;
|
|
|
|
/** Maximum number of nameservers allowed per domain. */
|
|
private static final int MAX_NAMESERVERS_PER_DOMAIN = 13;
|
|
|
|
/** Maximum number of characters in a domain label, from RFC 2181. */
|
|
private static final int MAX_LABEL_SIZE = 63;
|
|
|
|
/**
|
|
* Returns parsed version of {@code name} if domain name label follows our naming rules and is
|
|
* under one of the given allowed TLDs.
|
|
*
|
|
* <p><b>Note:</b> This method does not perform language validation with IDN tables.
|
|
*
|
|
* @see #validateDomainNameWithIdnTables(InternetDomainName)
|
|
*/
|
|
public static InternetDomainName validateDomainName(String name) throws EppException {
|
|
if (!ALLOWED_CHARS.matchesAllOf(name)) {
|
|
throw new BadDomainNameCharacterException();
|
|
}
|
|
List<String> parts = Splitter.on('.').splitToList(name);
|
|
if (parts.size() <= 1) {
|
|
throw new BadDomainNamePartsCountException();
|
|
}
|
|
if (any(parts, equalTo(""))) {
|
|
throw new EmptyDomainNamePartException();
|
|
}
|
|
validateFirstLabel(parts.get(0));
|
|
InternetDomainName domainName = InternetDomainName.from(name);
|
|
if (getTlds().contains(domainName.toString())) {
|
|
throw new DomainNameExistsAsTldException();
|
|
}
|
|
Optional<InternetDomainName> tldParsed = findTldForName(domainName);
|
|
if (!tldParsed.isPresent()) {
|
|
throw new TldDoesNotExistException(domainName.parent().toString());
|
|
}
|
|
if (domainName.parts().size() != tldParsed.get().parts().size() + 1) {
|
|
throw new BadDomainNamePartsCountException();
|
|
}
|
|
return domainName;
|
|
}
|
|
|
|
private static void validateFirstLabel(String firstLabel) throws EppException {
|
|
if (firstLabel.length() > MAX_LABEL_SIZE) {
|
|
throw new DomainLabelTooLongException();
|
|
}
|
|
if (firstLabel.startsWith("-")) {
|
|
throw new LeadingDashException();
|
|
}
|
|
if (firstLabel.endsWith("-")) {
|
|
throw new TrailingDashException();
|
|
}
|
|
String unicode = Idn.toUnicode(firstLabel);
|
|
if (firstLabel.startsWith(ACE_PREFIX) && firstLabel.equals(unicode)) {
|
|
throw new InvalidPunycodeException();
|
|
}
|
|
if (!firstLabel.startsWith(ACE_PREFIX)
|
|
&& firstLabel.length() >= 4
|
|
&& firstLabel.substring(2).startsWith("--")) {
|
|
throw new DashesInThirdAndFourthException();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns name of first matching IDN table for domain label.
|
|
*
|
|
* @throws InvalidIdnDomainLabelException if IDN table or language validation failed
|
|
* @see #validateDomainName(String)
|
|
*/
|
|
static String validateDomainNameWithIdnTables(InternetDomainName domainName)
|
|
throws InvalidIdnDomainLabelException {
|
|
Optional<String> idnTableName =
|
|
findValidIdnTableForTld(domainName.parts().get(0), domainName.parent().toString());
|
|
if (!idnTableName.isPresent()) {
|
|
throw new InvalidIdnDomainLabelException();
|
|
}
|
|
return idnTableName.get();
|
|
}
|
|
|
|
/** Check if the registrar running the flow has access to the TLD in question. */
|
|
public static void checkAllowedAccessToTld(String clientId, String tld) throws EppException {
|
|
if (!Registrar.loadByClientIdCached(clientId).get().getAllowedTlds().contains(tld)) {
|
|
throw new DomainFlowUtils.NotAuthorizedForTldException(tld);
|
|
}
|
|
}
|
|
|
|
/** Check that the DS data that will be set on a domain is valid. */
|
|
static void validateDsData(Set<DelegationSignerData> dsData) throws EppException {
|
|
if (dsData != null && dsData.size() > MAX_DS_RECORDS_PER_DOMAIN) {
|
|
throw new TooManyDsRecordsException(
|
|
String.format(
|
|
"A maximum of %s DS records are allowed per domain.", MAX_DS_RECORDS_PER_DOMAIN));
|
|
}
|
|
}
|
|
|
|
/** We only allow specifying years in a period. */
|
|
static Period verifyUnitIsYears(Period period) throws EppException {
|
|
if (!checkNotNull(period).getUnit().equals(Period.Unit.YEARS)) {
|
|
throw new BadPeriodUnitException();
|
|
}
|
|
return period;
|
|
}
|
|
|
|
/** Verify that no linked resources have disallowed statuses. */
|
|
static void verifyNotInPendingDelete(
|
|
Set<DesignatedContact> contacts,
|
|
Key<ContactResource> registrant,
|
|
Set<Key<HostResource>> nameservers)
|
|
throws EppException {
|
|
ImmutableList.Builder<Key<? extends EppResource>> keysToLoad = new ImmutableList.Builder<>();
|
|
nullToEmpty(contacts).stream().map(DesignatedContact::getContactKey).forEach(keysToLoad::add);
|
|
Optional.ofNullable(registrant).ifPresent(keysToLoad::add);
|
|
keysToLoad.addAll(nullToEmpty(nameservers));
|
|
verifyNotInPendingDelete(EppResource.loadCached(keysToLoad.build()).values());
|
|
}
|
|
|
|
private static void verifyNotInPendingDelete(Iterable<EppResource> resources)
|
|
throws EppException {
|
|
for (EppResource resource : resources) {
|
|
if (resource.getStatusValues().contains(StatusValue.PENDING_DELETE)) {
|
|
throw new LinkedResourceInPendingDeleteProhibitsOperationException(
|
|
resource.getForeignKey());
|
|
}
|
|
}
|
|
}
|
|
|
|
static void validateContactsHaveTypes(Set<DesignatedContact> contacts)
|
|
throws ParameterValuePolicyErrorException {
|
|
for (DesignatedContact contact : nullToEmpty(contacts)) {
|
|
if (contact.getType() == null) {
|
|
throw new MissingContactTypeException();
|
|
}
|
|
}
|
|
}
|
|
|
|
static void validateNameserversCountForTld(String tld, InternetDomainName domainName, int count)
|
|
throws EppException {
|
|
// For TLDs with a nameserver whitelist, all domains must have at least 1 nameserver.
|
|
ImmutableSet<String> tldNameserversWhitelist =
|
|
Registry.get(tld).getAllowedFullyQualifiedHostNames();
|
|
if (!tldNameserversWhitelist.isEmpty() && count == 0) {
|
|
throw new NameserversNotSpecifiedForTldWithNameserverWhitelistException(
|
|
domainName.toString());
|
|
}
|
|
// For domains with a nameserver restricted reservation, they must have at least 1 nameserver.
|
|
ImmutableSet<String> domainNameserversWhitelist = getAllowedNameservers(domainName);
|
|
if (!domainNameserversWhitelist.isEmpty() && count == 0) {
|
|
throw new NameserversNotSpecifiedForNameserverRestrictedDomainException(
|
|
domainName.toString());
|
|
}
|
|
if (count > MAX_NAMESERVERS_PER_DOMAIN) {
|
|
throw new TooManyNameserversException(
|
|
String.format("Only %d nameservers are allowed per domain", MAX_NAMESERVERS_PER_DOMAIN));
|
|
}
|
|
}
|
|
|
|
static void validateNoDuplicateContacts(Set<DesignatedContact> contacts)
|
|
throws ParameterValuePolicyErrorException {
|
|
Set<Type> roles = new HashSet<>();
|
|
for (DesignatedContact contact : nullToEmpty(contacts)) {
|
|
if (!roles.add(contact.getType())) {
|
|
throw new DuplicateContactForRoleException();
|
|
}
|
|
}
|
|
}
|
|
|
|
static void validateRequiredContactsPresent(
|
|
Key<ContactResource> registrant, Set<DesignatedContact> contacts)
|
|
throws RequiredParameterMissingException {
|
|
if (registrant == null) {
|
|
throw new MissingRegistrantException();
|
|
}
|
|
|
|
Set<Type> roles = new HashSet<>();
|
|
for (DesignatedContact contact : nullToEmpty(contacts)) {
|
|
roles.add(contact.getType());
|
|
}
|
|
if (!roles.contains(Type.ADMIN)) {
|
|
throw new MissingAdminContactException();
|
|
}
|
|
if (!roles.contains(Type.TECH)) {
|
|
throw new MissingTechnicalContactException();
|
|
}
|
|
}
|
|
|
|
static void validateRegistrantAllowedOnTld(String tld, String registrantContactId)
|
|
throws RegistrantNotAllowedException {
|
|
ImmutableSet<String> whitelist = Registry.get(tld).getAllowedRegistrantContactIds();
|
|
// Empty whitelist or null registrantContactId are ignored.
|
|
if (registrantContactId != null
|
|
&& !whitelist.isEmpty()
|
|
&& !whitelist.contains(registrantContactId)) {
|
|
throw new RegistrantNotAllowedException(registrantContactId);
|
|
}
|
|
}
|
|
|
|
static void validateNameserversAllowedOnTld(String tld, Set<String> fullyQualifiedHostNames)
|
|
throws EppException {
|
|
ImmutableSet<String> whitelist = Registry.get(tld).getAllowedFullyQualifiedHostNames();
|
|
Set<String> hostnames = nullToEmpty(fullyQualifiedHostNames);
|
|
if (!whitelist.isEmpty()) { // Empty whitelist is ignored.
|
|
Set<String> disallowedNameservers = difference(hostnames, whitelist);
|
|
if (!disallowedNameservers.isEmpty()) {
|
|
throw new NameserversNotAllowedForTldException(disallowedNameservers);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validates if the requested nameservers can be set on the requested domain.
|
|
*
|
|
* @param domainName the domain to be created.
|
|
* @param fullyQualifiedHostNames the set of nameservers to be set on the domain.
|
|
* @throws EppException
|
|
*/
|
|
static void validateNameserversAllowedOnDomain(
|
|
InternetDomainName domainName, Set<String> fullyQualifiedHostNames) throws EppException {
|
|
ImmutableSet<ReservationType> reservationTypes = getReservationTypes(domainName);
|
|
if (reservationTypes.contains(NAMESERVER_RESTRICTED)) {
|
|
ImmutableSet<String> allowedNameservers = getAllowedNameservers(domainName);
|
|
Set<String> disallowedNameservers = difference(fullyQualifiedHostNames, allowedNameservers);
|
|
if (!disallowedNameservers.isEmpty()) {
|
|
throw new NameserversNotAllowedForDomainException(disallowedNameservers);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Validates if the requested domain can be reated on a domain create restricted TLD. */
|
|
static void validateDomainAllowedOnCreateRestrictedTld(InternetDomainName domainName)
|
|
throws EppException {
|
|
ImmutableSet<ReservationType> reservationTypes = getReservationTypes(domainName);
|
|
if (!reservationTypes.contains(NAMESERVER_RESTRICTED)) {
|
|
throw new DomainNotAllowedForTldWithCreateRestrictionException(domainName.toString());
|
|
}
|
|
}
|
|
|
|
static void verifyNotReserved(InternetDomainName domainName, boolean isSunrise)
|
|
throws EppException {
|
|
if (isReserved(domainName, isSunrise)) {
|
|
throw new DomainReservedException(domainName.toString());
|
|
}
|
|
}
|
|
|
|
private static boolean isReserved(InternetDomainName domainName, boolean isSunrise) {
|
|
ImmutableSet<ReservationType> types = getReservationTypes(domainName);
|
|
return types.contains(FULLY_BLOCKED)
|
|
|| types.contains(RESERVED_FOR_ANCHOR_TENANT)
|
|
|| !(isSunrise || intersection(TYPES_ALLOWED_FOR_CREATE_ONLY_IN_SUNRISE, types).isEmpty());
|
|
}
|
|
|
|
/** Returns a set of {@link ReservationType}s for the given domain name. */
|
|
static ImmutableSet<ReservationType> getReservationTypes(InternetDomainName domainName) {
|
|
// The TLD should always be the parent of the requested domain name.
|
|
return ReservedList.getReservationTypes(
|
|
domainName.parts().get(0), domainName.parent().toString());
|
|
}
|
|
|
|
/** Verifies that a launch extension's specified phase matches the specified registry's phase. */
|
|
static void verifyLaunchPhaseMatchesRegistryPhase(
|
|
Registry registry, LaunchExtension launchExtension, DateTime now) throws EppException {
|
|
if (!LAUNCH_PHASE_TO_TLD_STATES.containsEntry(
|
|
launchExtension.getPhase(), registry.getTldState(now))) {
|
|
// No launch operations are allowed during the quiet period or predelegation.
|
|
throw new LaunchPhaseMismatchException();
|
|
}
|
|
}
|
|
|
|
/** Verifies that an application's domain name matches the target id (from a command). */
|
|
static void verifyApplicationDomainMatchesTargetId(DomainApplication application, String targetId)
|
|
throws EppException {
|
|
if (!application.getFullyQualifiedDomainName().equals(targetId)) {
|
|
throw new ApplicationDomainNameMismatchException();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Verifies that a domain name is allowed to be delegated to the given client id. The only case
|
|
* where it would not be allowed is if domain name is premium, and premium names are blocked by
|
|
* this registrar.
|
|
*/
|
|
static void verifyPremiumNameIsNotBlocked(String domainName, DateTime priceTime, String clientId)
|
|
throws EppException {
|
|
if (isDomainPremium(domainName, priceTime)) {
|
|
if (Registrar.loadByClientIdCached(clientId).get().getBlockPremiumNames()) {
|
|
throw new PremiumNameBlockedException();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper to call {@link CreateOrUpdate#cloneAndLinkReferences} and convert exceptions to
|
|
* EppExceptions, since this is needed in several places.
|
|
*/
|
|
static <T extends CreateOrUpdate<T>> T cloneAndLinkReferences(T command, DateTime now)
|
|
throws EppException {
|
|
try {
|
|
return command.cloneAndLinkReferences(now);
|
|
} catch (InvalidReferencesException e) {
|
|
throw new LinkedResourcesDoNotExistException(e.getType(), e.getForeignKeys());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fills in a builder with the data needed for an autorenew billing event for this domain. This
|
|
* does not copy over the id of the current autorenew billing event.
|
|
*/
|
|
static BillingEvent.Recurring.Builder newAutorenewBillingEvent(DomainResource domain) {
|
|
return new BillingEvent.Recurring.Builder()
|
|
.setReason(Reason.RENEW)
|
|
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
|
|
.setTargetId(domain.getFullyQualifiedDomainName())
|
|
.setClientId(domain.getCurrentSponsorClientId())
|
|
.setEventTime(domain.getRegistrationExpirationTime());
|
|
}
|
|
|
|
/**
|
|
* Fills in a builder with the data needed for an autorenew poll message for this domain. This
|
|
* does not copy over the id of the current autorenew poll message.
|
|
*/
|
|
static PollMessage.Autorenew.Builder newAutorenewPollMessage(DomainResource domain) {
|
|
return new PollMessage.Autorenew.Builder()
|
|
.setTargetId(domain.getFullyQualifiedDomainName())
|
|
.setClientId(domain.getCurrentSponsorClientId())
|
|
.setEventTime(domain.getRegistrationExpirationTime())
|
|
.setMsg("Domain was auto-renewed.");
|
|
}
|
|
|
|
/**
|
|
* Re-saves the current autorenew billing event and poll message with a new end time. This may end
|
|
* up deleting the poll message (if closing the message interval) or recreating it (if opening the
|
|
* message interval).
|
|
*/
|
|
@SuppressWarnings("unchecked")
|
|
static void updateAutorenewRecurrenceEndTime(DomainResource domain, DateTime newEndTime) {
|
|
Optional<PollMessage.Autorenew> autorenewPollMessage =
|
|
Optional.ofNullable(ofy().load().key(domain.getAutorenewPollMessage()).now());
|
|
|
|
// Construct an updated autorenew poll message. If the autorenew poll message no longer exists,
|
|
// create a new one at the same id. This can happen if a transfer was requested on a domain
|
|
// where all autorenew poll messages had already been delivered (this would cause the poll
|
|
// message to be deleted), and then subsequently the transfer was canceled, rejected, or deleted
|
|
// (which would cause the poll message to be recreated here).
|
|
Key<PollMessage.Autorenew> existingAutorenewKey = domain.getAutorenewPollMessage();
|
|
PollMessage.Autorenew updatedAutorenewPollMessage =
|
|
autorenewPollMessage.isPresent()
|
|
? autorenewPollMessage.get().asBuilder().setAutorenewEndTime(newEndTime).build()
|
|
: newAutorenewPollMessage(domain)
|
|
.setId(existingAutorenewKey.getId())
|
|
.setAutorenewEndTime(newEndTime)
|
|
.setParentKey(existingAutorenewKey.getParent())
|
|
.build();
|
|
|
|
// If the resultant autorenew poll message would have no poll messages to deliver, then just
|
|
// delete it. Otherwise save it with the new end time.
|
|
if (isAtOrAfter(updatedAutorenewPollMessage.getEventTime(), newEndTime)) {
|
|
autorenewPollMessage.ifPresent(autorenew -> ofy().delete().entity(autorenew));
|
|
} else {
|
|
ofy().save().entity(updatedAutorenewPollMessage);
|
|
}
|
|
|
|
Recurring recurring = ofy().load().key(domain.getAutorenewBillingEvent()).now();
|
|
ofy().save().entity(recurring.asBuilder().setRecurrenceEndTime(newEndTime).build());
|
|
}
|
|
|
|
/**
|
|
* Validates a {@link FeeQueryCommandExtensionItem} and sets the appropriate fields on a {@link
|
|
* FeeQueryResponseExtensionItem} builder.
|
|
*/
|
|
static void handleFeeRequest(
|
|
FeeQueryCommandExtensionItem feeRequest,
|
|
FeeQueryResponseExtensionItem.Builder<?, ?> builder,
|
|
InternetDomainName domain,
|
|
@Nullable CurrencyUnit topLevelCurrency,
|
|
DateTime currentDate,
|
|
DomainPricingLogic pricingLogic)
|
|
throws EppException {
|
|
DateTime now = currentDate;
|
|
// Use the custom effective date specified in the fee check request, if there is one.
|
|
if (feeRequest.getEffectiveDate().isPresent()) {
|
|
now = feeRequest.getEffectiveDate().get();
|
|
builder.setEffectiveDateIfSupported(now);
|
|
}
|
|
String domainNameString = domain.toString();
|
|
Registry registry = Registry.get(domain.parent().toString());
|
|
int years = verifyUnitIsYears(feeRequest.getPeriod()).getValue();
|
|
boolean isSunrise = registry.getTldState(now).equals(TldState.SUNRISE);
|
|
|
|
if (feeRequest.getPhase() != null || feeRequest.getSubphase() != null) {
|
|
throw new FeeChecksDontSupportPhasesException();
|
|
}
|
|
|
|
CurrencyUnit currency =
|
|
feeRequest.getCurrency() != null ? feeRequest.getCurrency() : topLevelCurrency;
|
|
if ((currency != null) && !currency.equals(registry.getCurrency())) {
|
|
throw new CurrencyUnitMismatchException();
|
|
}
|
|
|
|
builder
|
|
.setCommand(feeRequest.getCommandName(), feeRequest.getPhase(), feeRequest.getSubphase())
|
|
.setCurrencyIfSupported(registry.getCurrency())
|
|
.setPeriod(feeRequest.getPeriod())
|
|
.setClass(pricingLogic.getFeeClass(domainNameString, now).orElse(null));
|
|
|
|
ImmutableList<Fee> fees = ImmutableList.of();
|
|
switch (feeRequest.getCommandName()) {
|
|
case CREATE:
|
|
if (isReserved(domain, isSunrise)) { // Don't return a create price for reserved names.
|
|
builder.setClass("reserved"); // Override whatever class we've set above.
|
|
builder.setAvailIfSupported(false);
|
|
builder.setReasonIfSupported("reserved");
|
|
} else {
|
|
builder.setAvailIfSupported(true);
|
|
fees = pricingLogic.getCreatePrice(registry, domainNameString, now, years).getFees();
|
|
}
|
|
break;
|
|
case RENEW:
|
|
builder.setAvailIfSupported(true);
|
|
fees = pricingLogic.getRenewPrice(registry, domainNameString, now, years).getFees();
|
|
break;
|
|
case RESTORE:
|
|
if (years != 1) {
|
|
throw new RestoresAreAlwaysForOneYearException();
|
|
}
|
|
builder.setAvailIfSupported(true);
|
|
fees = pricingLogic.getRestorePrice(registry, domainNameString, now).getFees();
|
|
break;
|
|
case TRANSFER:
|
|
if (years != 1) {
|
|
throw new TransfersAreAlwaysForOneYearException();
|
|
}
|
|
builder.setAvailIfSupported(true);
|
|
fees = pricingLogic.getTransferPrice(registry, domainNameString, now).getFees();
|
|
break;
|
|
case UPDATE:
|
|
builder.setAvailIfSupported(true);
|
|
fees = pricingLogic.getUpdatePrice(registry, domainNameString, now).getFees();
|
|
break;
|
|
default:
|
|
throw new UnknownFeeCommandException(feeRequest.getUnparsedCommandName());
|
|
}
|
|
|
|
// Set the fees, and based on the validDateRange of the fees, set the notAfterDate.
|
|
if (!fees.isEmpty()) {
|
|
builder.setFees(fees);
|
|
DateTime notAfterDate = null;
|
|
for (Fee fee : fees) {
|
|
if (fee.hasValidDateRange()) {
|
|
DateTime endDate = fee.getValidDateRange().upperEndpoint();
|
|
if (notAfterDate == null || notAfterDate.isAfter(endDate)) {
|
|
notAfterDate = endDate;
|
|
}
|
|
}
|
|
}
|
|
if (notAfterDate != null && !notAfterDate.equals(END_OF_TIME)) {
|
|
builder.setNotAfterDateIfSupported(notAfterDate);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validates that fees are acked and match if they are required (typically for premium domains).
|
|
*
|
|
* <p>This is used by domain operations that have an implicit cost, e.g. domain create or renew
|
|
* (both of which add one or more years' worth of registration). Depending on registry and/or
|
|
* registrar settings, explicit price acking using the fee extension may be required for premium
|
|
* domain names.
|
|
*/
|
|
public static void validateFeeChallenge(
|
|
String domainName,
|
|
String tld,
|
|
String clientId,
|
|
DateTime priceTime,
|
|
final Optional<? extends FeeTransformCommandExtension> feeCommand,
|
|
FeesAndCredits feesAndCredits)
|
|
throws EppException {
|
|
|
|
Registry registry = Registry.get(tld);
|
|
Registrar registrar = Registrar.loadByClientIdCached(clientId).get();
|
|
boolean premiumAckRequired =
|
|
registry.getPremiumPriceAckRequired() || registrar.getPremiumPriceAckRequired();
|
|
if (premiumAckRequired && isDomainPremium(domainName, priceTime) && !feeCommand.isPresent()) {
|
|
throw new FeesRequiredForPremiumNameException();
|
|
}
|
|
validateFeesAckedIfPresent(feeCommand, feesAndCredits);
|
|
}
|
|
|
|
/**
|
|
* Validates that non-zero fees are acked (i.e. they are specified and the amount matches).
|
|
*
|
|
* <p>This is used directly by update operations, i.e. those that otherwise don't have implicit
|
|
* costs, and is also used as a helper method to validate if fees are required for operations that
|
|
* do have implicit costs, e.g. creates and renews.
|
|
*/
|
|
public static void validateFeesAckedIfPresent(
|
|
final Optional<? extends FeeTransformCommandExtension> feeCommand,
|
|
FeesAndCredits feesAndCredits)
|
|
throws EppException {
|
|
// Check for the case where a fee command extension was required but not provided.
|
|
// This only happens when the total fees are non-zero and include custom fees requiring the
|
|
// extension.
|
|
if (!feeCommand.isPresent()) {
|
|
if (!feesAndCredits.getEapCost().isZero()) {
|
|
throw new FeesRequiredDuringEarlyAccessProgramException(feesAndCredits.getEapCost());
|
|
}
|
|
if (feesAndCredits.getTotalCost().isZero() || !feesAndCredits.isFeeExtensionRequired()) {
|
|
return;
|
|
}
|
|
throw new FeesRequiredForNonFreeOperationException(feesAndCredits.getTotalCost());
|
|
}
|
|
|
|
List<Fee> fees = feeCommand.get().getFees();
|
|
// The schema guarantees that at least one fee will be present.
|
|
checkState(!fees.isEmpty());
|
|
BigDecimal total = BigDecimal.ZERO;
|
|
for (Fee fee : fees) {
|
|
if (!fee.hasDefaultAttributes()) {
|
|
throw new UnsupportedFeeAttributeException();
|
|
}
|
|
total = total.add(fee.getCost());
|
|
}
|
|
for (Credit credit : feeCommand.get().getCredits()) {
|
|
if (!credit.hasDefaultAttributes()) {
|
|
throw new UnsupportedFeeAttributeException();
|
|
}
|
|
total = total.add(credit.getCost());
|
|
}
|
|
|
|
Money feeTotal;
|
|
try {
|
|
feeTotal = Money.of(feeCommand.get().getCurrency(), total);
|
|
} catch (ArithmeticException e) {
|
|
throw new CurrencyValueScaleException();
|
|
}
|
|
|
|
if (!feeTotal.getCurrencyUnit().equals(feesAndCredits.getCurrency())) {
|
|
throw new CurrencyUnitMismatchException();
|
|
}
|
|
if (!feeTotal.equals(feesAndCredits.getTotalCost())) {
|
|
throw new FeesMismatchException(feesAndCredits.getTotalCost());
|
|
}
|
|
// If more than one fees are required, always validate individual fees.
|
|
ImmutableMap<FeeType, Money> expectedFeeMap =
|
|
buildFeeMap(feesAndCredits.getFees(), feesAndCredits.getCurrency());
|
|
if (expectedFeeMap.size() > 1) {
|
|
ImmutableMap<FeeType, Money> providedFeeMap =
|
|
buildFeeMap(feeCommand.get().getFees(), feeCommand.get().getCurrency());
|
|
for (FeeType type : expectedFeeMap.keySet()) {
|
|
Money providedCost = providedFeeMap.get(type);
|
|
Money expectedCost = expectedFeeMap.get(type);
|
|
if (!providedCost.isEqual(expectedCost)) {
|
|
throw new FeesMismatchException(type, expectedCost);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static FeeType getOrParseType(Fee fee) throws ParameterValuePolicyErrorException {
|
|
if (fee.getType() != null) {
|
|
return fee.getType();
|
|
}
|
|
ImmutableList<FeeType> types = fee.parseDescriptionForTypes();
|
|
if (types.size() == 0) {
|
|
throw new FeeDescriptionParseException(fee.getDescription());
|
|
} else if (types.size() > 1) {
|
|
throw new FeeDescriptionMultipleMatchesException(fee.getDescription());
|
|
} else {
|
|
return types.get(0);
|
|
}
|
|
}
|
|
|
|
private static ImmutableMap<FeeType, Money> buildFeeMap(List<Fee> fees, CurrencyUnit currency)
|
|
throws ParameterValuePolicyErrorException {
|
|
ImmutableMultimap.Builder<FeeType, Money> mapBuilder =
|
|
new ImmutableMultimap.Builder<FeeType, Money>().orderKeysBy(Comparator.naturalOrder());
|
|
for (Fee fee : fees) {
|
|
mapBuilder.put(getOrParseType(fee), Money.of(currency, fee.getCost()));
|
|
}
|
|
return mapBuilder
|
|
.build()
|
|
.asMap()
|
|
.entrySet()
|
|
.stream()
|
|
.collect(toImmutableMap(Entry::getKey, entry -> Money.total(entry.getValue())));
|
|
}
|
|
|
|
/**
|
|
* Check whether a new expiration time (via a renew) does not extend beyond a maximum number of
|
|
* years (e.g. {@link DomainResource#MAX_REGISTRATION_YEARS}) from "now".
|
|
*
|
|
* @throws ExceedsMaxRegistrationYearsException if the new registration period is too long
|
|
*/
|
|
public static void validateRegistrationPeriod(DateTime now, DateTime newExpirationTime)
|
|
throws EppException {
|
|
if (leapSafeAddYears(now, MAX_REGISTRATION_YEARS).isBefore(newExpirationTime)) {
|
|
throw new ExceedsMaxRegistrationYearsException();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check whether a new registration period (via a create, allocate, or application create) does
|
|
* not extend beyond a maximum number of years (e.g. {@link
|
|
* DomainResource#MAX_REGISTRATION_YEARS}).
|
|
*
|
|
* @throws ExceedsMaxRegistrationYearsException if the new registration period is too long
|
|
*/
|
|
public static void validateRegistrationPeriod(int years) throws EppException {
|
|
if (years > MAX_REGISTRATION_YEARS) {
|
|
throw new ExceedsMaxRegistrationYearsException();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds a secDns extension to a list if the given set of dsData is non-empty.
|
|
*
|
|
* <p>According to RFC 5910 section 2, we should only return this if the client specified the
|
|
* "urn:ietf:params:xml:ns:secDNS-1.1" when logging in. However, this is a "SHOULD" not a "MUST"
|
|
* and we are going to ignore it; clients who don't care about secDNS can just ignore it.
|
|
*/
|
|
static void addSecDnsExtensionIfPresent(
|
|
ImmutableList.Builder<ResponseExtension> extensions,
|
|
ImmutableSet<DelegationSignerData> dsData) {
|
|
if (!dsData.isEmpty()) {
|
|
extensions.add(SecDnsInfoExtension.create(dsData));
|
|
}
|
|
}
|
|
|
|
/** Update {@link DelegationSignerData} based on an update extension command. */
|
|
static ImmutableSet<DelegationSignerData> updateDsData(
|
|
ImmutableSet<DelegationSignerData> oldDsData, SecDnsUpdateExtension secDnsUpdate)
|
|
throws EppException {
|
|
// We don't support 'urgent' because we do everything as fast as we can anyways.
|
|
if (Boolean.TRUE.equals(secDnsUpdate.getUrgent())) { // We allow both false and null.
|
|
throw new UrgentAttributeNotSupportedException();
|
|
}
|
|
// There must be at least one of add/rem/chg, and chg isn't actually supported.
|
|
if (secDnsUpdate.getChange() != null) {
|
|
// The only thing you can change is maxSigLife, and we don't support that at all.
|
|
throw new MaxSigLifeChangeNotSupportedException();
|
|
}
|
|
Add add = secDnsUpdate.getAdd();
|
|
Remove remove = secDnsUpdate.getRemove();
|
|
if (add == null && remove == null) {
|
|
throw new EmptySecDnsUpdateException();
|
|
}
|
|
if (remove != null && Boolean.FALSE.equals(remove.getAll())) {
|
|
throw new SecDnsAllUsageException(); // Explicit all=false is meaningless.
|
|
}
|
|
Set<DelegationSignerData> toAdd = (add == null) ? ImmutableSet.of() : add.getDsData();
|
|
Set<DelegationSignerData> toRemove =
|
|
(remove == null)
|
|
? ImmutableSet.of()
|
|
: (remove.getAll() == null) ? remove.getDsData() : oldDsData;
|
|
// RFC 5910 specifies that removes are processed before adds.
|
|
return ImmutableSet.copyOf(union(difference(oldDsData, toRemove), toAdd));
|
|
}
|
|
|
|
/** If a domain or application has "clientUpdateProhibited" set, updates must clear it or fail. */
|
|
static void verifyClientUpdateNotProhibited(Update command, DomainBase existingResource)
|
|
throws ResourceHasClientUpdateProhibitedException {
|
|
if (existingResource.getStatusValues().contains(StatusValue.CLIENT_UPDATE_PROHIBITED)
|
|
&& !command
|
|
.getInnerRemove()
|
|
.getStatusValues()
|
|
.contains(StatusValue.CLIENT_UPDATE_PROHIBITED)) {
|
|
throw new ResourceHasClientUpdateProhibitedException();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check that the registrar with the given client ID is active.
|
|
*
|
|
* <p>Non-active registrars are not allowed to create domain applications or domain resources.
|
|
*/
|
|
static void verifyRegistrarIsActive(String clientId)
|
|
throws RegistrarMustBeActiveToCreateDomainsException {
|
|
Registrar registrar = Registrar.loadByClientIdCached(clientId).get();
|
|
if (registrar.getState() != State.ACTIVE) {
|
|
throw new RegistrarMustBeActiveToCreateDomainsException();
|
|
}
|
|
}
|
|
|
|
/** Check that the registry phase is not incompatible with launch extension flows. */
|
|
static void verifyRegistryStateAllowsApplicationFlows(Registry registry, DateTime now)
|
|
throws BadCommandForRegistryPhaseException {
|
|
if (DISALLOWED_TLD_STATES_FOR_APPLICATION_FLOWS.contains(registry.getTldState(now))) {
|
|
throw new BadCommandForRegistryPhaseException();
|
|
}
|
|
}
|
|
|
|
/** Check that the registry phase is not predelegation, during which some flows are forbidden. */
|
|
static void verifyNotInPredelegation(Registry registry, DateTime now)
|
|
throws BadCommandForRegistryPhaseException {
|
|
if (registry.getTldState(now) == TldState.PREDELEGATION) {
|
|
throw new BadCommandForRegistryPhaseException();
|
|
}
|
|
}
|
|
|
|
/** Validate the contacts and nameservers specified in a domain or application create command. */
|
|
static void validateCreateCommandContactsAndNameservers(
|
|
Create command, Registry registry, InternetDomainName domainName) throws EppException {
|
|
verifyNotInPendingDelete(
|
|
command.getContacts(), command.getRegistrant(), command.getNameservers());
|
|
validateContactsHaveTypes(command.getContacts());
|
|
String tld = registry.getTldStr();
|
|
validateRegistrantAllowedOnTld(tld, command.getRegistrantContactId());
|
|
validateNoDuplicateContacts(command.getContacts());
|
|
validateRequiredContactsPresent(command.getRegistrant(), command.getContacts());
|
|
Set<String> fullyQualifiedHostNames =
|
|
nullToEmpty(command.getNameserverFullyQualifiedHostNames());
|
|
validateNameserversCountForTld(tld, domainName, fullyQualifiedHostNames.size());
|
|
validateNameserversAllowedOnTld(tld, fullyQualifiedHostNames);
|
|
validateNameserversAllowedOnDomain(domainName, fullyQualifiedHostNames);
|
|
}
|
|
|
|
/** Validate the secDNS extension, if present. */
|
|
static Optional<SecDnsCreateExtension> validateSecDnsExtension(
|
|
Optional<SecDnsCreateExtension> secDnsCreate) throws EppException {
|
|
if (!secDnsCreate.isPresent()) {
|
|
return Optional.empty();
|
|
}
|
|
if (secDnsCreate.get().getDsData() == null) {
|
|
throw new DsDataRequiredException();
|
|
}
|
|
if (secDnsCreate.get().getMaxSigLife() != null) {
|
|
throw new MaxSigLifeNotSupportedException();
|
|
}
|
|
validateDsData(secDnsCreate.get().getDsData());
|
|
return secDnsCreate;
|
|
}
|
|
|
|
/** Validate the notice from a launch create extension, allowing null as a valid notice. */
|
|
static void validateLaunchCreateNotice(
|
|
@Nullable LaunchNotice notice, String domainLabel, boolean isSuperuser, DateTime now)
|
|
throws EppException {
|
|
if (notice == null) {
|
|
return;
|
|
}
|
|
if (!notice.getNoticeId().getValidatorId().equals("tmch")) {
|
|
throw new InvalidTrademarkValidatorException();
|
|
}
|
|
// Superuser can force domain creations regardless of the current date.
|
|
if (!isSuperuser) {
|
|
if (notice.getExpirationTime().isBefore(now)) {
|
|
throw new ExpiredClaimException();
|
|
}
|
|
// An acceptance within the past 48 hours is mandated by the TMCH Functional Spec.
|
|
if (notice.getAcceptedTime().isBefore(now.minusHours(48))) {
|
|
throw new AcceptedTooLongAgoException();
|
|
}
|
|
}
|
|
try {
|
|
notice.validate(domainLabel);
|
|
} catch (IllegalArgumentException e) {
|
|
throw new MalformedTcnIdException();
|
|
} catch (InvalidChecksumException e) {
|
|
throw new InvalidTcnIdChecksumException();
|
|
}
|
|
}
|
|
|
|
/** Check that the claims period hasn't ended. */
|
|
static void verifyClaimsPeriodNotEnded(Registry registry, DateTime now)
|
|
throws ClaimsPeriodEndedException {
|
|
if (isAtOrAfter(now, registry.getClaimsPeriodEnd())) {
|
|
throw new ClaimsPeriodEndedException(registry.getTldStr());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check that if there's a claims notice it's on the claims list, and that if there's not one it's
|
|
* not on the claims list and is a sunrise application.
|
|
*/
|
|
static void verifyClaimsNoticeIfAndOnlyIfNeeded(
|
|
InternetDomainName domainName, boolean hasSignedMarks, boolean hasClaimsNotice)
|
|
throws EppException {
|
|
boolean isInClaimsList =
|
|
ClaimsListShard.get().getClaimKey(domainName.parts().get(0)).isPresent();
|
|
if (hasClaimsNotice && !isInClaimsList) {
|
|
throw new UnexpectedClaimsNoticeException(domainName.toString());
|
|
}
|
|
if (!hasClaimsNotice && isInClaimsList && !hasSignedMarks) {
|
|
throw new MissingClaimsNoticeException(domainName.toString());
|
|
}
|
|
}
|
|
|
|
/** Create a {@link LrpTokenEntity} object that records this LRP registration. */
|
|
static LrpTokenEntity prepareMarkedLrpTokenEntity(
|
|
String lrpTokenString, InternetDomainName domainName, HistoryEntry historyEntry)
|
|
throws InvalidLrpTokenException {
|
|
Optional<LrpTokenEntity> lrpToken = getMatchingLrpToken(lrpTokenString, domainName);
|
|
if (!lrpToken.isPresent()) {
|
|
throw new InvalidLrpTokenException();
|
|
}
|
|
return lrpToken.get().asBuilder().setRedemptionHistoryEntry(Key.create(historyEntry)).build();
|
|
}
|
|
|
|
/** Check that there are no code marks, which is a type of mark we don't support. */
|
|
static void verifyNoCodeMarks(LaunchCreateExtension launchCreate)
|
|
throws UnsupportedMarkTypeException {
|
|
if (launchCreate.hasCodeMarks()) {
|
|
throw new UnsupportedMarkTypeException();
|
|
}
|
|
}
|
|
|
|
/** Create a response extension listing the fees on a domain or application create. */
|
|
static FeeTransformResponseExtension createFeeCreateResponse(
|
|
FeeTransformCommandExtension feeCreate, FeesAndCredits feesAndCredits) {
|
|
return feeCreate
|
|
.createResponseBuilder()
|
|
.setCurrency(feesAndCredits.getCurrency())
|
|
.setFees(feesAndCredits.getFees())
|
|
.setCredits(feesAndCredits.getCredits())
|
|
.build();
|
|
}
|
|
|
|
static ImmutableSet<ForeignKeyedDesignatedContact> loadForeignKeyedDesignatedContacts(
|
|
ImmutableSet<DesignatedContact> contacts) {
|
|
ImmutableSet.Builder<ForeignKeyedDesignatedContact> builder = new ImmutableSet.Builder<>();
|
|
for (DesignatedContact contact : contacts) {
|
|
builder.add(
|
|
ForeignKeyedDesignatedContact.create(
|
|
contact.getType(), ofy().load().key(contact.getContactKey()).now().getContactId()));
|
|
}
|
|
return builder.build();
|
|
}
|
|
|
|
/**
|
|
* Returns a set of DomainTransactionRecords which negate the most recent HistoryEntry's records.
|
|
*
|
|
* <p>Domain deletes and transfers use this function to account for previous records negated by
|
|
* their flow. For example, if a grace period delete occurs, we must add -1 counters for the
|
|
* associated NET_ADDS_#_YRS field, if it exists.
|
|
*
|
|
* <p>The steps are as follows: 1. Find all HistoryEntries under the domain modified in the past,
|
|
* up to the maxSearchPeriod. 2. Only keep HistoryEntries with a DomainTransactionRecord that a)
|
|
* hasn't been reported yet and b) matches the predicate 3. Return the transactionRecords under
|
|
* the most recent HistoryEntry that fits the above criteria, with negated reportAmounts.
|
|
*/
|
|
static ImmutableSet<DomainTransactionRecord> createCancelingRecords(
|
|
DomainResource domainResource,
|
|
final DateTime now,
|
|
Duration maxSearchPeriod,
|
|
final ImmutableSet<TransactionReportField> cancelableFields) {
|
|
|
|
List<HistoryEntry> recentHistoryEntries =
|
|
ofy()
|
|
.load()
|
|
.type(HistoryEntry.class)
|
|
.ancestor(domainResource)
|
|
.filter("modificationTime >=", now.minus(maxSearchPeriod))
|
|
.order("modificationTime")
|
|
.list();
|
|
Optional<HistoryEntry> entryToCancel =
|
|
Streams.findLast(
|
|
recentHistoryEntries
|
|
.stream()
|
|
.filter(
|
|
historyEntry -> {
|
|
// Look for add and renew transaction records that have yet to be reported
|
|
for (DomainTransactionRecord record :
|
|
historyEntry.getDomainTransactionRecords()) {
|
|
if (cancelableFields.contains(record.getReportField())
|
|
&& record.getReportingTime().isAfter(now)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}));
|
|
ImmutableSet.Builder<DomainTransactionRecord> recordsBuilder = new ImmutableSet.Builder<>();
|
|
if (entryToCancel.isPresent()) {
|
|
for (DomainTransactionRecord record : entryToCancel.get().getDomainTransactionRecords()) {
|
|
// Only cancel fields which are cancelable
|
|
if (cancelableFields.contains(record.getReportField())) {
|
|
int cancelledAmount = -1 * record.getReportAmount();
|
|
recordsBuilder.add(record.asBuilder().setReportAmount(cancelledAmount).build());
|
|
}
|
|
}
|
|
}
|
|
return recordsBuilder.build();
|
|
}
|
|
|
|
/** Resource linked to this domain does not exist. */
|
|
static class LinkedResourcesDoNotExistException extends ObjectDoesNotExistException {
|
|
public LinkedResourcesDoNotExistException(Class<?> type, ImmutableSet<String> resourceIds) {
|
|
super(type, resourceIds);
|
|
}
|
|
}
|
|
|
|
/** Linked resource in pending delete prohibits operation. */
|
|
static class LinkedResourceInPendingDeleteProhibitsOperationException
|
|
extends StatusProhibitsOperationException {
|
|
public LinkedResourceInPendingDeleteProhibitsOperationException(String resourceId) {
|
|
super(String.format("Linked resource in pending delete prohibits operation: %s", resourceId));
|
|
}
|
|
}
|
|
|
|
/** Domain names can only contain a-z, 0-9, '.' and '-'. */
|
|
static class BadDomainNameCharacterException extends ParameterValuePolicyErrorException {
|
|
public BadDomainNameCharacterException() {
|
|
super("Domain names can only contain a-z, 0-9, '.' and '-'");
|
|
}
|
|
}
|
|
|
|
/** Non-IDN domain names cannot contain hyphens in the third or fourth position. */
|
|
static class DashesInThirdAndFourthException extends ParameterValuePolicyErrorException {
|
|
public DashesInThirdAndFourthException() {
|
|
super("Non-IDN domain names cannot contain dashes in the third or fourth position");
|
|
}
|
|
}
|
|
|
|
/** Domain labels cannot begin with a dash. */
|
|
static class LeadingDashException extends ParameterValuePolicyErrorException {
|
|
public LeadingDashException() {
|
|
super("Domain labels cannot begin with a dash");
|
|
}
|
|
}
|
|
|
|
/** Domain labels cannot end with a dash. */
|
|
static class TrailingDashException extends ParameterValuePolicyErrorException {
|
|
public TrailingDashException() {
|
|
super("Domain labels cannot end with a dash");
|
|
}
|
|
}
|
|
|
|
/** Domain labels cannot be longer than 63 characters. */
|
|
static class DomainLabelTooLongException extends ParameterValuePolicyErrorException {
|
|
public DomainLabelTooLongException() {
|
|
super("Domain labels cannot be longer than 63 characters");
|
|
}
|
|
}
|
|
|
|
/** No part of a domain name can be empty. */
|
|
static class EmptyDomainNamePartException extends ParameterValuePolicyErrorException {
|
|
public EmptyDomainNamePartException() {
|
|
super("No part of a domain name can be empty");
|
|
}
|
|
}
|
|
|
|
/** Domain name starts with xn-- but is not a valid IDN. */
|
|
static class InvalidPunycodeException extends ParameterValuePolicyErrorException {
|
|
public InvalidPunycodeException() {
|
|
super("Domain name starts with xn-- but is not a valid IDN");
|
|
}
|
|
}
|
|
|
|
/** Periods for domain registrations must be specified in years. */
|
|
static class BadPeriodUnitException extends ParameterValuePolicyErrorException {
|
|
public BadPeriodUnitException() {
|
|
super("Periods for domain registrations must be specified in years");
|
|
}
|
|
}
|
|
|
|
/** Missing type attribute for contact. */
|
|
static class MissingContactTypeException extends ParameterValuePolicyErrorException {
|
|
public MissingContactTypeException() {
|
|
super("Missing type attribute for contact");
|
|
}
|
|
}
|
|
|
|
/** More than one contact for a given role is not allowed. */
|
|
static class DuplicateContactForRoleException extends ParameterValuePolicyErrorException {
|
|
public DuplicateContactForRoleException() {
|
|
super("More than one contact for a given role is not allowed");
|
|
}
|
|
}
|
|
|
|
/** Declared launch extension phase does not match the current registry phase. */
|
|
static class LaunchPhaseMismatchException extends ParameterValuePolicyErrorException {
|
|
public LaunchPhaseMismatchException() {
|
|
super("Declared launch extension phase does not match the current registry phase");
|
|
}
|
|
}
|
|
|
|
/** Application referenced does not match specified domain name. */
|
|
static class ApplicationDomainNameMismatchException extends ParameterValuePolicyErrorException {
|
|
public ApplicationDomainNameMismatchException() {
|
|
super("Application referenced does not match specified domain name");
|
|
}
|
|
}
|
|
|
|
/** Too many DS records set on a domain. */
|
|
static class TooManyDsRecordsException extends ParameterValuePolicyErrorException {
|
|
public TooManyDsRecordsException(String message) {
|
|
super(message);
|
|
}
|
|
}
|
|
|
|
/** Domain name is under tld which doesn't exist. */
|
|
static class TldDoesNotExistException extends ParameterValueRangeErrorException {
|
|
public TldDoesNotExistException(String tld) {
|
|
super(String.format("Domain name is under tld %s which doesn't exist", tld));
|
|
}
|
|
}
|
|
|
|
/** Domain label is not allowed by IDN table. */
|
|
static class InvalidIdnDomainLabelException extends ParameterValueRangeErrorException {
|
|
public InvalidIdnDomainLabelException() {
|
|
super("Domain label is not allowed by IDN table");
|
|
}
|
|
}
|
|
|
|
/** Registrant is required. */
|
|
static class MissingRegistrantException extends RequiredParameterMissingException {
|
|
public MissingRegistrantException() {
|
|
super("Registrant is required");
|
|
}
|
|
}
|
|
|
|
/** Admin contact is required. */
|
|
static class MissingAdminContactException extends RequiredParameterMissingException {
|
|
public MissingAdminContactException() {
|
|
super("Admin contact is required");
|
|
}
|
|
}
|
|
|
|
/** Technical contact is required. */
|
|
static class MissingTechnicalContactException extends RequiredParameterMissingException {
|
|
public MissingTechnicalContactException() {
|
|
super("Technical contact is required");
|
|
}
|
|
}
|
|
|
|
/** Too many nameservers set on this domain. */
|
|
static class TooManyNameserversException extends ParameterValuePolicyErrorException {
|
|
public TooManyNameserversException(String message) {
|
|
super(message);
|
|
}
|
|
}
|
|
|
|
/** Domain name must have exactly one part above the TLD. */
|
|
static class BadDomainNamePartsCountException extends ParameterValueSyntaxErrorException {
|
|
public BadDomainNamePartsCountException() {
|
|
super("Domain name must have exactly one part above the TLD");
|
|
}
|
|
}
|
|
|
|
/** Domain name must not equal an existing multi-part TLD. */
|
|
static class DomainNameExistsAsTldException extends ParameterValueSyntaxErrorException {
|
|
public DomainNameExistsAsTldException() {
|
|
super("Domain name must not equal an existing multi-part TLD");
|
|
}
|
|
}
|
|
|
|
/** Unknown fee command name. */
|
|
static class UnknownFeeCommandException extends ParameterValuePolicyErrorException {
|
|
UnknownFeeCommandException(String commandName) {
|
|
super("Unknown fee command: " + commandName);
|
|
}
|
|
}
|
|
|
|
/** Fee checks for command phases and subphases are not supported. */
|
|
static class FeeChecksDontSupportPhasesException extends ParameterValuePolicyErrorException {
|
|
FeeChecksDontSupportPhasesException() {
|
|
super("Fee checks for command phases and subphases are not supported");
|
|
}
|
|
}
|
|
|
|
/** The requested fees cannot be provided in the requested currency. */
|
|
static class CurrencyUnitMismatchException extends ParameterValuePolicyErrorException {
|
|
CurrencyUnitMismatchException() {
|
|
super("The requested fees cannot be provided in the requested currency");
|
|
}
|
|
}
|
|
|
|
/** The requested fee is expressed in a scale that is invalid for the given currency. */
|
|
static class CurrencyValueScaleException extends ParameterValueSyntaxErrorException {
|
|
CurrencyValueScaleException() {
|
|
super("The requested fee is expressed in a scale that is invalid for the given currency");
|
|
}
|
|
}
|
|
|
|
/** Fees must be explicitly acknowledged when performing any operations on a premium name. */
|
|
static class FeesRequiredForPremiumNameException extends RequiredParameterMissingException {
|
|
FeesRequiredForPremiumNameException() {
|
|
super(
|
|
"Fees must be explicitly acknowledged when performing any operations on a premium"
|
|
+ " name");
|
|
}
|
|
}
|
|
|
|
/** Fees must be explicitly acknowledged when performing an operation which is not free. */
|
|
static class FeesRequiredForNonFreeOperationException extends RequiredParameterMissingException {
|
|
|
|
public FeesRequiredForNonFreeOperationException(Money expectedFee) {
|
|
super(
|
|
"Fees must be explicitly acknowledged when performing an operation which is not free."
|
|
+ " The total fee is: "
|
|
+ expectedFee);
|
|
}
|
|
}
|
|
|
|
/** Fees must be explicitly acknowledged when creating domains during the Early Access Program. */
|
|
static class FeesRequiredDuringEarlyAccessProgramException
|
|
extends RequiredParameterMissingException {
|
|
|
|
public FeesRequiredDuringEarlyAccessProgramException(Money expectedFee) {
|
|
super(
|
|
"Fees must be explicitly acknowledged when creating domains "
|
|
+ "during the Early Access Program. The EAP fee is: "
|
|
+ expectedFee);
|
|
}
|
|
}
|
|
|
|
/** The 'grace-period', 'applied' and 'refundable' fields are disallowed by server policy. */
|
|
static class UnsupportedFeeAttributeException extends UnimplementedOptionException {
|
|
UnsupportedFeeAttributeException() {
|
|
super(
|
|
"The 'grace-period', 'refundable' and 'applied' attributes are disallowed by server "
|
|
+ "policy");
|
|
}
|
|
}
|
|
|
|
/** Restores always renew a domain for one year. */
|
|
static class RestoresAreAlwaysForOneYearException extends ParameterValuePolicyErrorException {
|
|
RestoresAreAlwaysForOneYearException() {
|
|
super("Restores always renew a domain for one year");
|
|
}
|
|
}
|
|
|
|
/** Transfers always renew a domain for one year. */
|
|
static class TransfersAreAlwaysForOneYearException extends ParameterValuePolicyErrorException {
|
|
TransfersAreAlwaysForOneYearException() {
|
|
super("Transfers always renew a domain for one year");
|
|
}
|
|
}
|
|
|
|
/** Requested domain is reserved. */
|
|
static class DomainReservedException extends StatusProhibitsOperationException {
|
|
public DomainReservedException(String domainName) {
|
|
super(String.format("%s is a reserved domain", domainName));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Requested domain does not have nameserver-restricted reservation for a TLD that requires such a
|
|
* reservation to create domains.
|
|
*/
|
|
static class DomainNotAllowedForTldWithCreateRestrictionException
|
|
extends StatusProhibitsOperationException {
|
|
public DomainNotAllowedForTldWithCreateRestrictionException(String domainName) {
|
|
super(
|
|
String.format(
|
|
"%s is not allowed without a nameserver-restricted reservation"
|
|
+ " for a TLD that requires such reservation",
|
|
domainName));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The requested domain name is on the premium price list, and this registrar has blocked premium
|
|
* registrations.
|
|
*/
|
|
static class PremiumNameBlockedException extends StatusProhibitsOperationException {
|
|
public PremiumNameBlockedException() {
|
|
super(
|
|
"The requested domain name is on the premium price list, "
|
|
+ "and this registrar has blocked premium registrations");
|
|
}
|
|
}
|
|
|
|
/** The fees passed in the transform command do not match the fees that will be charged. */
|
|
public static class FeesMismatchException extends ParameterValueRangeErrorException {
|
|
public FeesMismatchException() {
|
|
super("The fees passed in the transform command do not match the fees that will be charged");
|
|
}
|
|
|
|
public FeesMismatchException(Money correctFee) {
|
|
super(
|
|
String.format(
|
|
"The fees passed in the transform command do not match the expected total of %s",
|
|
correctFee));
|
|
}
|
|
|
|
public FeesMismatchException(FeeType type, Money correctFee) {
|
|
super(
|
|
String.format(
|
|
"The fees passed in the transform command for type \"%s\" do not match the expected "
|
|
+ "fee of %s",
|
|
type, correctFee));
|
|
}
|
|
}
|
|
|
|
/** The fee description passed in the transform command cannot be parsed. */
|
|
public static class FeeDescriptionParseException extends ParameterValuePolicyErrorException {
|
|
public FeeDescriptionParseException(String description) {
|
|
super(
|
|
String.format(
|
|
"The fee description \"%s\" passed in the transform command cannot be parsed",
|
|
description == null ? "" : description));
|
|
}
|
|
}
|
|
|
|
/** The fee description passed in the transform command matches multiple fee types. */
|
|
public static class FeeDescriptionMultipleMatchesException
|
|
extends ParameterValuePolicyErrorException {
|
|
public FeeDescriptionMultipleMatchesException(String description) {
|
|
super(
|
|
String.format(
|
|
"The fee description \"%s\" passed in the transform matches multiple fee types",
|
|
description));
|
|
}
|
|
}
|
|
|
|
/** Registrar is not authorized to access this TLD. */
|
|
public static class NotAuthorizedForTldException extends AuthorizationErrorException {
|
|
public NotAuthorizedForTldException(String tld) {
|
|
super("Registrar is not authorized to access the TLD " + tld);
|
|
}
|
|
}
|
|
|
|
/** Registrant is not whitelisted for this TLD. */
|
|
public static class RegistrantNotAllowedException extends StatusProhibitsOperationException {
|
|
public RegistrantNotAllowedException(String contactId) {
|
|
super(String.format("Registrant with id %s is not whitelisted for this TLD", contactId));
|
|
}
|
|
}
|
|
|
|
/** Nameservers are not whitelisted for this TLD. */
|
|
public static class NameserversNotAllowedForTldException
|
|
extends StatusProhibitsOperationException {
|
|
public NameserversNotAllowedForTldException(Set<String> fullyQualifiedHostNames) {
|
|
super(
|
|
String.format(
|
|
"Nameservers '%s' are not whitelisted for this TLD",
|
|
Joiner.on(',').join(fullyQualifiedHostNames)));
|
|
}
|
|
}
|
|
|
|
/** Nameservers are not whitelisted for this domain. */
|
|
public static class NameserversNotAllowedForDomainException
|
|
extends StatusProhibitsOperationException {
|
|
public NameserversNotAllowedForDomainException(Set<String> fullyQualifiedHostNames) {
|
|
super(
|
|
String.format(
|
|
"Nameservers '%s' are not whitelisted for this domain",
|
|
Joiner.on(',').join(fullyQualifiedHostNames)));
|
|
}
|
|
}
|
|
|
|
/** Nameservers not specified for domain on TLD with nameserver whitelist. */
|
|
public static class NameserversNotSpecifiedForTldWithNameserverWhitelistException
|
|
extends StatusProhibitsOperationException {
|
|
public NameserversNotSpecifiedForTldWithNameserverWhitelistException(String domain) {
|
|
super(
|
|
String.format(
|
|
"At least one nameserver must be specified for domain %s"
|
|
+ " on a TLD with nameserver whitelist",
|
|
domain));
|
|
}
|
|
}
|
|
|
|
/** Nameservers not specified for domain with nameserver-restricted reservation. */
|
|
public static class NameserversNotSpecifiedForNameserverRestrictedDomainException
|
|
extends StatusProhibitsOperationException {
|
|
public NameserversNotSpecifiedForNameserverRestrictedDomainException(String domain) {
|
|
super(
|
|
String.format(
|
|
"At least one nameserver must be specified for domain %s"
|
|
+ " on a TLD with nameserver restriction",
|
|
domain));
|
|
}
|
|
}
|
|
|
|
/** Command is not allowed in the current registry phase. */
|
|
public static class BadCommandForRegistryPhaseException extends CommandUseErrorException {
|
|
public BadCommandForRegistryPhaseException() {
|
|
super("Command is not allowed in the current registry phase");
|
|
}
|
|
}
|
|
|
|
/** The secDNS:all element must have value 'true' if present. */
|
|
static class SecDnsAllUsageException extends ParameterValuePolicyErrorException {
|
|
public SecDnsAllUsageException() {
|
|
super("The secDNS:all element must have value 'true' if present");
|
|
}
|
|
}
|
|
|
|
/** At least one of 'add' or 'rem' is required on a secDNS update. */
|
|
static class EmptySecDnsUpdateException extends RequiredParameterMissingException {
|
|
public EmptySecDnsUpdateException() {
|
|
super("At least one of 'add' or 'rem' is required on a secDNS update");
|
|
}
|
|
}
|
|
|
|
/** At least one dsData is required when using the secDNS extension. */
|
|
static class DsDataRequiredException extends ParameterValuePolicyErrorException {
|
|
public DsDataRequiredException() {
|
|
super("At least one dsData is required when using the secDNS extension");
|
|
}
|
|
}
|
|
|
|
/** The 'urgent' attribute is not supported. */
|
|
static class UrgentAttributeNotSupportedException extends UnimplementedOptionException {
|
|
public UrgentAttributeNotSupportedException() {
|
|
super("The 'urgent' attribute is not supported");
|
|
}
|
|
}
|
|
|
|
/** The 'maxSigLife' setting is not supported. */
|
|
static class MaxSigLifeNotSupportedException extends UnimplementedOptionException {
|
|
public MaxSigLifeNotSupportedException() {
|
|
super("The 'maxSigLife' setting is not supported");
|
|
}
|
|
}
|
|
|
|
/** Changing 'maxSigLife' is not supported. */
|
|
static class MaxSigLifeChangeNotSupportedException extends UnimplementedOptionException {
|
|
public MaxSigLifeChangeNotSupportedException() {
|
|
super("Changing 'maxSigLife' is not supported");
|
|
}
|
|
}
|
|
|
|
/** The specified trademark validator is not supported. */
|
|
static class InvalidTrademarkValidatorException extends ParameterValuePolicyErrorException {
|
|
public InvalidTrademarkValidatorException() {
|
|
super("The only supported validationID is 'tmch' for the ICANN Trademark Clearinghouse.");
|
|
}
|
|
}
|
|
|
|
/** The expiration time specified in the claim notice has elapsed. */
|
|
static class ExpiredClaimException extends ParameterValueRangeErrorException {
|
|
public ExpiredClaimException() {
|
|
super("The expiration time specified in the claim notice has elapsed");
|
|
}
|
|
}
|
|
|
|
/** The acceptance time specified in the claim notice is more than 48 hours in the past. */
|
|
static class AcceptedTooLongAgoException extends ParameterValueRangeErrorException {
|
|
public AcceptedTooLongAgoException() {
|
|
super("The acceptance time specified in the claim notice is more than 48 hours in the past");
|
|
}
|
|
}
|
|
|
|
/** The specified TCNID is invalid. */
|
|
static class MalformedTcnIdException extends ParameterValueSyntaxErrorException {
|
|
public MalformedTcnIdException() {
|
|
super("The specified TCNID is malformed");
|
|
}
|
|
}
|
|
|
|
/** The checksum in the specified TCNID does not validate. */
|
|
static class InvalidTcnIdChecksumException extends ParameterValueRangeErrorException {
|
|
public InvalidTcnIdChecksumException() {
|
|
super("The checksum in the specified TCNID does not validate");
|
|
}
|
|
}
|
|
|
|
/** The claims period for this TLD has ended. */
|
|
static class ClaimsPeriodEndedException extends StatusProhibitsOperationException {
|
|
public ClaimsPeriodEndedException(String tld) {
|
|
super(String.format("The claims period for %s has ended", tld));
|
|
}
|
|
}
|
|
|
|
/** Requested domain requires a claims notice. */
|
|
static class MissingClaimsNoticeException extends StatusProhibitsOperationException {
|
|
public MissingClaimsNoticeException(String domainName) {
|
|
super(String.format("%s requires a claims notice", domainName));
|
|
}
|
|
}
|
|
|
|
/** Requested domain does not require a claims notice. */
|
|
static class UnexpectedClaimsNoticeException extends StatusProhibitsOperationException {
|
|
public UnexpectedClaimsNoticeException(String domainName) {
|
|
super(String.format("%s does not require a claims notice", domainName));
|
|
}
|
|
}
|
|
|
|
/** Invalid limited registration period token. */
|
|
static class InvalidLrpTokenException extends InvalidAuthorizationInformationErrorException {
|
|
public InvalidLrpTokenException() {
|
|
super("Invalid limited registration period token");
|
|
}
|
|
}
|
|
|
|
/** Only encoded signed marks are supported. */
|
|
static class UnsupportedMarkTypeException extends ParameterValuePolicyErrorException {
|
|
public UnsupportedMarkTypeException() {
|
|
super("Only encoded signed marks are supported");
|
|
}
|
|
}
|
|
|
|
/** New registration period exceeds maximum number of years. */
|
|
static class ExceedsMaxRegistrationYearsException extends ParameterValueRangeErrorException {
|
|
public ExceedsMaxRegistrationYearsException() {
|
|
super(
|
|
String.format(
|
|
"New registration period exceeds maximum number of years (%d)",
|
|
MAX_REGISTRATION_YEARS));
|
|
}
|
|
}
|
|
|
|
/** Registrar must be active in order to create domains or applications. */
|
|
static class RegistrarMustBeActiveToCreateDomainsException extends AuthorizationErrorException {
|
|
public RegistrarMustBeActiveToCreateDomainsException() {
|
|
super("Registrar must be active in order to create domains or applications");
|
|
}
|
|
}
|
|
}
|