Adds the ability to whitelist registrants and nameservers on a TLD

This is needed for ROCC TLDs like .foo
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=118404870
This commit is contained in:
cgoldfeder 2016-03-28 16:16:42 -07:00 committed by Justine Tunney
parent f9e1bab1d2
commit ec2daec412
17 changed files with 576 additions and 85 deletions

View file

@ -22,6 +22,7 @@ import static com.google.domain.registry.flows.domain.DomainFlowUtils.validateDo
import static com.google.domain.registry.flows.domain.DomainFlowUtils.validateDsData;
import static com.google.domain.registry.flows.domain.DomainFlowUtils.validateNameservers;
import static com.google.domain.registry.flows.domain.DomainFlowUtils.validateNoDuplicateContacts;
import static com.google.domain.registry.flows.domain.DomainFlowUtils.validateRegistrantAllowedOnTld;
import static com.google.domain.registry.flows.domain.DomainFlowUtils.validateRequiredContactsPresent;
import static com.google.domain.registry.flows.domain.DomainFlowUtils.verifyLaunchPhase;
import static com.google.domain.registry.flows.domain.DomainFlowUtils.verifyNotInPendingDelete;
@ -207,9 +208,10 @@ public abstract class BaseDomainCreateFlow<R extends DomainBase, B extends Build
command.getRegistrant(),
command.getNameservers());
validateContactsHaveTypes(command.getContacts());
validateRegistrantAllowedOnTld(tld, command.getRegistrant());
validateNoDuplicateContacts(command.getContacts());
validateRequiredContactsPresent(command.getRegistrant(), command.getContacts());
validateNameservers(command.getNameservers());
validateNameservers(tld, command.getNameservers());
validateLaunchCreateExtension();
// If a signed mark was provided, then it must match the desired domain label.
// We do this after validating the launch create extension so that flows which don't allow any

View file

@ -22,6 +22,7 @@ import static com.google.domain.registry.flows.domain.DomainFlowUtils.validateCo
import static com.google.domain.registry.flows.domain.DomainFlowUtils.validateDsData;
import static com.google.domain.registry.flows.domain.DomainFlowUtils.validateNameservers;
import static com.google.domain.registry.flows.domain.DomainFlowUtils.validateNoDuplicateContacts;
import static com.google.domain.registry.flows.domain.DomainFlowUtils.validateRegistrantAllowedOnTld;
import static com.google.domain.registry.flows.domain.DomainFlowUtils.validateRequiredContactsPresent;
import static com.google.domain.registry.flows.domain.DomainFlowUtils.verifyNotInPendingDelete;
@ -121,7 +122,8 @@ public abstract class BaseDomainUpdateFlow<R extends DomainBase, B extends Build
validateNoDuplicateContacts(newResource.getContacts());
validateRequiredContactsPresent(newResource.getRegistrant(), newResource.getContacts());
validateDsData(newResource.getDsData());
validateNameservers(newResource.getNameservers());
validateRegistrantAllowedOnTld(newResource.getTld(), newResource.getRegistrant());
validateNameservers(newResource.getTld(), newResource.getNameservers());
}
/** The secDNS:all element must have value 'true' if present. */

View file

@ -90,8 +90,10 @@ import java.util.List;
* @error {@link DomainFlowUtils.LeadingDashException}
* @error {@link DomainFlowUtils.LinkedResourceDoesNotExistException}
* @error {@link DomainFlowUtils.MissingContactTypeException}
* @error {@link DomainFlowUtils.NameserverNotAllowedException}
* @error {@link DomainFlowUtils.NoMarksFoundMatchingDomainException}
* @error {@link DomainFlowUtils.PremiumNameBlockedException}
* @error {@link DomainFlowUtils.RegistrantNotAllowedException}
* @error {@link DomainFlowUtils.SignedMarksMustBeEncodedException}
* @error {@link DomainFlowUtils.SignedMarkCertificateExpiredException}
* @error {@link DomainFlowUtils.SignedMarkCertificateInvalidException}

View file

@ -49,6 +49,8 @@ import com.google.domain.registry.model.reporting.HistoryEntry;
* @error {@link DomainFlowUtils.MissingAdminContactException}
* @error {@link DomainFlowUtils.MissingContactTypeException}
* @error {@link DomainFlowUtils.MissingTechnicalContactException}
* @error {@link DomainFlowUtils.NameserverNotAllowedException}
* @error {@link DomainFlowUtils.RegistrantNotAllowedException}
* @error {@link DomainFlowUtils.TooManyDsRecordsException}
* @error {@link DomainFlowUtils.TooManyNameserversException}
* @error {@link DomainApplicationUpdateFlow.ApplicationStatusProhibitsUpdateException}

View file

@ -80,7 +80,9 @@ import java.util.Set;
* @error {@link DomainFlowUtils.MissingContactTypeException}
* @error {@link DomainFlowUtils.MissingRegistrantException}
* @error {@link DomainFlowUtils.MissingTechnicalContactException}
* @error {@link DomainFlowUtils.NameserverNotAllowedException}
* @error {@link DomainFlowUtils.PremiumNameBlockedException}
* @error {@link DomainFlowUtils.RegistrantNotAllowedException}
* @error {@link DomainFlowUtils.TldDoesNotExistException}
* @error {@link DomainFlowUtils.TooManyDsRecordsException}
* @error {@link DomainFlowUtils.TooManyNameserversException}

View file

@ -259,6 +259,7 @@ public class DomainFlowUtils {
private static void verifyNotInPendingDelete(
ReferenceUnion<? extends EppResource> resourceRef) throws EppException {
EppResource resource = resourceRef.getLinked().get();
if (resource.getStatusValues().contains(StatusValue.PENDING_DELETE)) {
throw new LinkedResourceInPendingDeleteProhibitsOperationException(resource.getForeignKey());
@ -274,12 +275,26 @@ public class DomainFlowUtils {
}
}
static void validateNameservers(Set<ReferenceUnion<HostResource>> nameservers)
/** Return a foreign key for a {@link ReferenceUnion} from memory or datastore as needed. */
private static String resolveForeignKey(ReferenceUnion<?> ref) {
return Optional.fromNullable(ref.getForeignKey()).or(ref.getLinked().get().getForeignKey());
}
static void validateNameservers(String tld, Set<ReferenceUnion<HostResource>> nameservers)
throws EppException {
if (nameservers != null && nameservers.size() > MAX_NAMESERVERS_PER_DOMAIN) {
throw new TooManyNameserversException(String.format(
"Only %d nameservers are allowed per domain", MAX_NAMESERVERS_PER_DOMAIN));
}
ImmutableSet<String> whitelist = Registry.get(tld).getAllowedFullyQualifiedHostNames();
if (!whitelist.isEmpty()) { // Empty whitelists are ignored.
for (ReferenceUnion<HostResource> nameserver : nameservers) {
String foreignKey = resolveForeignKey(nameserver);
if (!whitelist.contains(foreignKey)) {
throw new NameserverNotAllowedException(foreignKey);
}
}
}
}
static void validateNoDuplicateContacts(Set<DesignatedContact> contacts)
@ -311,6 +326,15 @@ public class DomainFlowUtils {
}
}
static void validateRegistrantAllowedOnTld(String tld, ReferenceUnion<ContactResource> registrant)
throws RegistrantNotAllowedException {
ImmutableSet<String> whitelist = Registry.get(tld).getAllowedRegistrantContactIds();
// Empty whitelists are ignored.
if (!whitelist.isEmpty() && !whitelist.contains(resolveForeignKey(registrant))) {
throw new RegistrantNotAllowedException(registrant.toString());
}
}
static void verifyNotReserved(
InternetDomainName domainName, boolean isSunriseApplication) throws EppException {
if (isReserved(domainName, isSunriseApplication)) {
@ -963,4 +987,18 @@ public class DomainFlowUtils {
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));
}
}
/** Nameserver is not whitelisted for this TLD. */
public static class NameserverNotAllowedException extends StatusProhibitsOperationException {
public NameserverNotAllowedException(String fullyQualifiedHostName) {
super(String.format("Nameserver %s is not whitelisted for this TLD", fullyQualifiedHostName));
}
}
}

View file

@ -60,6 +60,8 @@ import java.util.Set;
* @error {@link DomainFlowUtils.MissingAdminContactException}
* @error {@link DomainFlowUtils.MissingContactTypeException}
* @error {@link DomainFlowUtils.MissingTechnicalContactException}
* @error {@link DomainFlowUtils.NameserverNotAllowedException}
* @error {@link DomainFlowUtils.RegistrantNotAllowedException}
* @error {@link DomainFlowUtils.TooManyDsRecordsException}
* @error {@link DomainFlowUtils.TooManyNameserversException}
*/

View file

@ -343,6 +343,12 @@ public class Registry extends BackupGroupRoot implements Buildable {
/** The end of the claims period (at or after this time, claims no longer applies). */
DateTime claimsPeriodEnd = END_OF_TIME;
/** A whitelist of clients allowed to be used on domains on this TLD (ignored if empty). */
Set<String> allowedRegistrantContactIds;
/** A whitelist of hosts allowed to be used on domains on this TLD (ignored if empty). */
Set<String> allowedFullyQualifiedHostNames;
public String getTldStr() {
return tldStr;
}
@ -521,6 +527,14 @@ public class Registry extends BackupGroupRoot implements Buildable {
return claimsPeriodEnd;
}
public ImmutableSet<String> getAllowedRegistrantContactIds() {
return nullToEmptyImmutableCopy(allowedRegistrantContactIds);
}
public ImmutableSet<String> getAllowedFullyQualifiedHostNames() {
return nullToEmptyImmutableCopy(allowedFullyQualifiedHostNames);
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));
@ -734,6 +748,18 @@ public class Registry extends BackupGroupRoot implements Buildable {
return this;
}
public Builder setAllowedRegistrantContactIds(
ImmutableSet<String> allowedRegistrantContactIds) {
getInstance().allowedRegistrantContactIds = allowedRegistrantContactIds;
return this;
}
public Builder setAllowedFullyQualifiedHostNames(
ImmutableSet<String> allowedFullyQualifiedHostNames) {
getInstance().allowedFullyQualifiedHostNames = allowedFullyQualifiedHostNames;
return this;
}
@Override
public Registry build() {
final Registry instance = getInstance();

View file

@ -15,18 +15,22 @@
package com.google.domain.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
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 com.google.domain.registry.model.RoidSuffixes.isRoidSuffixUsed;
import static com.google.domain.registry.util.CollectionUtils.findDuplicates;
import static com.google.domain.registry.util.CollectionUtils.nullToEmpty;
import static com.google.domain.registry.util.DomainNameUtils.canonicalizeDomainName;
import com.google.common.base.CharMatcher;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Sets;
import com.google.domain.registry.model.registry.Registries;
import com.google.domain.registry.model.registry.Registry;
import com.google.domain.registry.model.registry.Registry.TldState;
@ -44,7 +48,6 @@ import org.joda.money.Money;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@ -180,6 +183,18 @@ abstract class CreateOrUpdateTldCommand extends MutatingCommand {
description = "A comma-separated list of reserved list names to be applied to the TLD")
List<String> reservedListNames;
@Nullable
@Parameter(
names = "--allowed_registrants",
description = "A comma-separated list of allowed registrants for the TLD")
List<String> allowedRegistrants;
@Nullable
@Parameter(
names = "--allowed_nameservers",
description = "A comma-separated list of allowed nameservers for the TLD")
List<String> allowedNameservers;
@Parameter(
names = {"-o", "--override_reserved_list_rules"},
description = "Override restrictions on reserved list naming")
@ -197,6 +212,18 @@ abstract class CreateOrUpdateTldCommand extends MutatingCommand {
@Nullable
Set<String> reservedListNamesToRemove;
@Nullable
Set<String> allowedRegistrantsToAdd;
@Nullable
Set<String> allowedRegistrantsToRemove;
@Nullable
Set<String> allowedNameserversToAdd;
@Nullable
Set<String> allowedNameserversToRemove;
/** Returns the existing registry (for update) or null (for creates). */
@Nullable
abstract Registry getOldRegistry(String tld);
@ -309,54 +336,6 @@ abstract class CreateOrUpdateTldCommand extends MutatingCommand {
builder.setClaimsPeriodEnd(claimsPeriodEnd);
}
if (reservedListNames != null
|| reservedListNamesToAdd != null
|| reservedListNamesToRemove != null) {
Set<String> listsToApply = new HashSet<>();
if (reservedListNames != null) {
listsToApply = ImmutableSet.copyOf(reservedListNames);
checkReservedListValidityForTld(tld, listsToApply);
} else {
checkArgument(
Sets
.intersection(
nullToEmpty(reservedListNamesToAdd),
nullToEmpty(reservedListNamesToRemove))
.isEmpty(),
"Adding and removing the same reserved list simultaneously doesn't make sense");
for (Key<ReservedList> key : oldRegistry.getReservedLists()) {
listsToApply.add(key.getName());
}
Set<String> duplicateNames =
Sets.intersection(listsToApply, nullToEmpty(reservedListNamesToAdd));
checkArgument(
duplicateNames.isEmpty(),
"Cannot add reserved list(s) %s to TLD %s because they're already on it",
Joiner.on(", ").join(duplicateNames),
tld);
if (reservedListNamesToAdd != null) {
checkReservedListValidityForTld(tld, reservedListNamesToAdd);
listsToApply.addAll(reservedListNamesToAdd);
}
if (reservedListNamesToRemove != null) {
for (String name : reservedListNamesToRemove) {
checkArgument(
listsToApply.contains(name),
"Cannot remove reserved list %s from TLD %s because it isn't on it",
name,
tld);
listsToApply.remove(name);
}
}
}
builder.setReservedListsByName(listsToApply);
}
if (premiumListName != null) {
if (premiumListName.isPresent()) {
Optional<PremiumList> premiumList = PremiumList.get(premiumListName.get());
@ -368,12 +347,78 @@ abstract class CreateOrUpdateTldCommand extends MutatingCommand {
}
}
ImmutableSet<String> newReservedListNames =
formUpdatedList(
"reserved lists",
oldRegistry == null ? ImmutableSet.<String>of() : FluentIterable
.from(oldRegistry.getReservedLists())
.transform(
new Function<Key<ReservedList>, String>() {
@Override
public String apply(Key<ReservedList> key) {
return key.getName();
}})
.toSet(),
reservedListNames,
reservedListNamesToAdd,
reservedListNamesToRemove);
checkReservedListValidityForTld(tld, newReservedListNames);
builder.setReservedListsByName(newReservedListNames);
builder.setAllowedRegistrantContactIds(
formUpdatedList(
"allowed registrants",
oldRegistry == null
? ImmutableSet.<String>of()
: oldRegistry.getAllowedRegistrantContactIds(),
allowedRegistrants,
allowedRegistrantsToAdd,
allowedRegistrantsToRemove));
builder.setAllowedFullyQualifiedHostNames(
formUpdatedList(
"allowed nameservers",
oldRegistry == null
? ImmutableSet.<String>of()
: oldRegistry.getAllowedFullyQualifiedHostNames(),
allowedNameservers,
allowedNameserversToAdd,
allowedNameserversToRemove));
// Update the Registry object.
setCommandSpecificProperties(builder);
stageEntityChange(oldRegistry, builder.build());
}
}
private ImmutableSet<String> formUpdatedList(
String description,
ImmutableSet<String> originals,
List<String> toReplace,
Set<String> toAdd,
Set<String> toRemove) {
if (toReplace != null) {
return ImmutableSet.copyOf(toReplace);
}
toAdd = nullToEmpty(toAdd);
toRemove = nullToEmpty(toRemove);
checkIsEmpty(
intersection(toAdd, toRemove),
String.format(
"Adding and removing the same %s simultaneously doesn't make sense", description));
checkIsEmpty(
intersection(originals, toAdd),
String.format("Cannot add %s that were previously present", description));
checkIsEmpty(
difference(toRemove, originals),
String.format("Cannot remove %s that were not previously present", description));
return ImmutableSet.copyOf(difference(union(originals, toAdd), toRemove));
}
private void checkIsEmpty(Set<String> set, String errorString) {
checkArgument(set.isEmpty(), String.format("%s: %s", errorString, set));
}
@Override
public String execute() throws Exception {
try {

View file

@ -43,6 +43,30 @@ class UpdateTldCommand extends CreateOrUpdateTldCommand {
description = "A comma-separated list of reserved list names to be removed from the TLD")
List<String> reservedListsRemove;
@Nullable
@Parameter(
names = "--add_allowed_registrants",
description = "A comma-separated list of allowed registrants to be added to the TLD")
List<String> allowedRegistrantsAdd;
@Nullable
@Parameter(
names = "--remove_allowed_registrants",
description = "A comma-separated list of allowed registrants to be removed from the TLD")
List<String> allowedRegistrantsRemove;
@Nullable
@Parameter(
names = "--add_allowed_nameservers",
description = "A comma-separated list of allowed nameservers to be added to the TLD")
List<String> allowedNameserversAdd;
@Nullable
@Parameter(
names = "--remove_allowed_nameservers",
description = "A comma-separated list of allowed nameservers to be removed from the TLD")
List<String> allowedNameserversRemove;
@Override
Registry getOldRegistry(String tld) {
return Registry.get(assertTldExists(tld));
@ -50,11 +74,26 @@ class UpdateTldCommand extends CreateOrUpdateTldCommand {
@Override
protected void initTldCommand() throws Exception {
checkArgument(reservedListsAdd == null || reservedListNames == null,
"Don't pass both --reserved_lists and --add_reserved_lists");
checkConflicts("reserved_lists", reservedListNames, reservedListsAdd, reservedListsRemove);
checkConflicts(
"allowed_registrants", allowedRegistrants, allowedRegistrantsAdd, allowedRegistrantsRemove);
checkConflicts(
"allowed_nameservers", allowedNameservers, allowedNameserversAdd, allowedNameserversRemove);
reservedListNamesToAdd = ImmutableSet.copyOf(nullToEmpty(reservedListsAdd));
checkArgument(reservedListsRemove == null || reservedListNames == null,
"Don't pass both --reserved_lists and --remove_reserved_lists");
reservedListNamesToRemove = ImmutableSet.copyOf(nullToEmpty(reservedListsRemove));
allowedRegistrantsToAdd = ImmutableSet.copyOf(nullToEmpty(allowedRegistrantsAdd));
allowedRegistrantsToRemove = ImmutableSet.copyOf(nullToEmpty(allowedRegistrantsRemove));
allowedNameserversToAdd = ImmutableSet.copyOf(nullToEmpty(allowedNameserversAdd));
allowedNameserversToRemove = ImmutableSet.copyOf(nullToEmpty(allowedNameserversRemove));
}
private void checkConflicts(
String baseFlagName, Object overwriteValue, Object addValue, Object removeValue) {
checkNotBoth(baseFlagName, overwriteValue, "add_" + baseFlagName, addValue);
checkNotBoth(baseFlagName, overwriteValue, "remove_" + baseFlagName, removeValue);
}
private void checkNotBoth(String nameA, Object valueA, String nameB, Object valueB) {
checkArgument(valueA == null || valueB == null, "Don't pass both --%s and --%s", nameA, nameB);
}
}