Fix bug which allowed creation of hosts with superordinate domains in pending delete state.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=154716883
This commit is contained in:
mountford 2017-05-01 08:08:14 -07:00 committed by Ben McIlwain
parent 386d2bc6be
commit 11e7374c0f
7 changed files with 82 additions and 0 deletions

View file

@ -1009,6 +1009,7 @@ are enqueued to update DNS accordingly.
* This resource has clientUpdateProhibited on it, and the update does not * This resource has clientUpdateProhibited on it, and the update does not
clear that status. clear that status.
* Resource status prohibits this operation. * Resource status prohibits this operation.
* Superordinate domain for this hostname is in pending delete.
* Cannot remove all IP addresses from a subordinate host. * Cannot remove all IP addresses from a subordinate host.
* Cannot rename an external host. * Cannot rename an external host.
* 2306 * 2306
@ -1094,6 +1095,8 @@ allows creating a host name, and if necessary enqueues tasks to update DNS.
* Resource with this id already exists. * Resource with this id already exists.
* 2303 * 2303
* Superordinate domain for this hostname does not exist. * Superordinate domain for this hostname does not exist.
* 2304
* Superordinate domain for this hostname is in pending delete.
* 2306 * 2306
* Host names must be at least two levels below the public suffix. * Host names must be at least two levels below the public suffix.

View file

@ -18,6 +18,7 @@ import static google.registry.flows.FlowUtils.validateClientIsLoggedIn;
import static google.registry.flows.ResourceFlowUtils.verifyResourceDoesNotExist; import static google.registry.flows.ResourceFlowUtils.verifyResourceDoesNotExist;
import static google.registry.flows.host.HostFlowUtils.lookupSuperordinateDomain; import static google.registry.flows.host.HostFlowUtils.lookupSuperordinateDomain;
import static google.registry.flows.host.HostFlowUtils.validateHostName; import static google.registry.flows.host.HostFlowUtils.validateHostName;
import static google.registry.flows.host.HostFlowUtils.verifySuperordinateDomainNotInPendingDelete;
import static google.registry.flows.host.HostFlowUtils.verifySuperordinateDomainOwnership; import static google.registry.flows.host.HostFlowUtils.verifySuperordinateDomainOwnership;
import static google.registry.model.EppResourceUtils.createRepoId; import static google.registry.model.EppResourceUtils.createRepoId;
import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.model.ofy.ObjectifyService.ofy;
@ -72,6 +73,7 @@ import org.joda.time.DateTime;
* @error {@link HostFlowUtils.HostNameNotNormalizedException} * @error {@link HostFlowUtils.HostNameNotNormalizedException}
* @error {@link HostFlowUtils.HostNameNotPunyCodedException} * @error {@link HostFlowUtils.HostNameNotPunyCodedException}
* @error {@link HostFlowUtils.SuperordinateDomainDoesNotExistException} * @error {@link HostFlowUtils.SuperordinateDomainDoesNotExistException}
* @error {@link HostFlowUtils.SuperordinateDomainInPendingDeleteException}
* @error {@link SubordinateHostMustHaveIpException} * @error {@link SubordinateHostMustHaveIpException}
* @error {@link UnexpectedExternalHostIpException} * @error {@link UnexpectedExternalHostIpException}
*/ */
@ -101,6 +103,7 @@ public final class HostCreateFlow implements TransactionalFlow {
// we can detect error conditions earlier. // we can detect error conditions earlier.
Optional<DomainResource> superordinateDomain = Optional<DomainResource> superordinateDomain =
lookupSuperordinateDomain(validateHostName(targetId), now); lookupSuperordinateDomain(validateHostName(targetId), now);
verifySuperordinateDomainNotInPendingDelete(superordinateDomain.orNull());
verifySuperordinateDomainOwnership(clientId, superordinateDomain.orNull()); verifySuperordinateDomainOwnership(clientId, superordinateDomain.orNull());
boolean willBeSubordinate = superordinateDomain.isPresent(); boolean willBeSubordinate = superordinateDomain.isPresent();
boolean hasIpAddresses = !isNullOrEmpty(command.getInetAddresses()); boolean hasIpAddresses = !isNullOrEmpty(command.getInetAddresses());

View file

@ -30,7 +30,9 @@ import google.registry.flows.EppException.ObjectDoesNotExistException;
import google.registry.flows.EppException.ParameterValuePolicyErrorException; import google.registry.flows.EppException.ParameterValuePolicyErrorException;
import google.registry.flows.EppException.ParameterValueRangeErrorException; import google.registry.flows.EppException.ParameterValueRangeErrorException;
import google.registry.flows.EppException.ParameterValueSyntaxErrorException; import google.registry.flows.EppException.ParameterValueSyntaxErrorException;
import google.registry.flows.EppException.StatusProhibitsOperationException;
import google.registry.model.domain.DomainResource; import google.registry.model.domain.DomainResource;
import google.registry.model.eppcommon.StatusValue;
import google.registry.util.Idn; import google.registry.util.Idn;
import org.joda.time.DateTime; import org.joda.time.DateTime;
@ -127,6 +129,24 @@ public class HostFlowUtils {
} }
} }
/** Ensure that the superordinate domain is not in pending delete. */
static void verifySuperordinateDomainNotInPendingDelete(
DomainResource superordinateDomain) throws EppException {
if ((superordinateDomain != null)
&& superordinateDomain.getStatusValues().contains(StatusValue.PENDING_DELETE)) {
throw new SuperordinateDomainInPendingDeleteException(
superordinateDomain.getFullyQualifiedDomainName());
}
}
/** Superordinate domain for this hostname is in pending delete. */
static class SuperordinateDomainInPendingDeleteException
extends StatusProhibitsOperationException {
public SuperordinateDomainInPendingDeleteException(String domainName) {
super(domainName);
}
}
/** Host names are limited to 253 characters. */ /** Host names are limited to 253 characters. */
static class HostNameTooLongException extends ParameterValueRangeErrorException { static class HostNameTooLongException extends ParameterValueRangeErrorException {
public HostNameTooLongException() { public HostNameTooLongException() {

View file

@ -24,6 +24,7 @@ import static google.registry.flows.ResourceFlowUtils.verifyNoDisallowedStatuses
import static google.registry.flows.ResourceFlowUtils.verifyResourceOwnership; import static google.registry.flows.ResourceFlowUtils.verifyResourceOwnership;
import static google.registry.flows.host.HostFlowUtils.lookupSuperordinateDomain; import static google.registry.flows.host.HostFlowUtils.lookupSuperordinateDomain;
import static google.registry.flows.host.HostFlowUtils.validateHostName; import static google.registry.flows.host.HostFlowUtils.validateHostName;
import static google.registry.flows.host.HostFlowUtils.verifySuperordinateDomainNotInPendingDelete;
import static google.registry.flows.host.HostFlowUtils.verifySuperordinateDomainOwnership; import static google.registry.flows.host.HostFlowUtils.verifySuperordinateDomainOwnership;
import static google.registry.model.index.ForeignKeyIndex.loadAndGetKey; import static google.registry.model.index.ForeignKeyIndex.loadAndGetKey;
import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.model.ofy.ObjectifyService.ofy;
@ -90,6 +91,7 @@ import org.joda.time.DateTime;
* @error {@link HostFlowUtils.HostNameTooShallowException} * @error {@link HostFlowUtils.HostNameTooShallowException}
* @error {@link HostFlowUtils.InvalidHostNameException} * @error {@link HostFlowUtils.InvalidHostNameException}
* @error {@link HostFlowUtils.SuperordinateDomainDoesNotExistException} * @error {@link HostFlowUtils.SuperordinateDomainDoesNotExistException}
* @error {@link HostFlowUtils.SuperordinateDomainInPendingDeleteException}
* @error {@link CannotAddIpToExternalHostException} * @error {@link CannotAddIpToExternalHostException}
* @error {@link CannotRemoveSubordinateHostLastIpException} * @error {@link CannotRemoveSubordinateHostLastIpException}
* @error {@link CannotRenameExternalHostException} * @error {@link CannotRenameExternalHostException}
@ -139,6 +141,7 @@ public final class HostUpdateFlow implements TransactionalFlow {
// Note that lookupSuperordinateDomain calls cloneProjectedAtTime on the domain for us. // Note that lookupSuperordinateDomain calls cloneProjectedAtTime on the domain for us.
Optional<DomainResource> newSuperordinateDomain = Optional<DomainResource> newSuperordinateDomain =
lookupSuperordinateDomain(validateHostName(newHostName), now); lookupSuperordinateDomain(validateHostName(newHostName), now);
verifySuperordinateDomainNotInPendingDelete(newSuperordinateDomain.orNull());
EppResource owningResource = firstNonNull(oldSuperordinateDomain, existingHost); EppResource owningResource = firstNonNull(oldSuperordinateDomain, existingHost);
verifyUpdateAllowed( verifyUpdateAllowed(
command, existingHost, newSuperordinateDomain.orNull(), owningResource, isHostRename); command, existingHost, newSuperordinateDomain.orNull(), owningResource, isHostRename);

View file

@ -28,6 +28,7 @@ import com.googlecode.objectify.VoidWork;
import google.registry.config.RegistryConfig.Config; import google.registry.config.RegistryConfig.Config;
import google.registry.mapreduce.MapreduceRunner; import google.registry.mapreduce.MapreduceRunner;
import google.registry.model.domain.DomainResource; import google.registry.model.domain.DomainResource;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.host.HostResource; import google.registry.model.host.HostResource;
import google.registry.request.Action; import google.registry.request.Action;
import google.registry.request.Parameter; import google.registry.request.Parameter;
@ -107,6 +108,14 @@ public class RdeHostLinkAction implements Runnable {
logger.infofmt("Host %s is out of zone", xjcHost.getName()); logger.infofmt("Host %s is out of zone", xjcHost.getName());
return; return;
} }
if (superordinateDomain.get().getStatusValues().contains(StatusValue.PENDING_DELETE)) {
getContext()
.incrementCounter(
"post-import hosts with superordinate domains in pending delete");
logger.infofmt(
"Host %s has a superordinate domain in pending delete", xjcHost.getName());
return;
}
// at this point, the host is definitely in zone and should be linked // at this point, the host is definitely in zone and should be linked
getContext().incrementCounter("post-import hosts in zone"); getContext().incrementCounter("post-import hosts in zone");
final Key<DomainResource> superordinateDomainKey = Key.create(superordinateDomain.get()); final Key<DomainResource> superordinateDomainKey = Key.create(superordinateDomain.get());

View file

@ -18,15 +18,18 @@ import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.EppResourceUtils.loadByForeignKey; import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.testing.DatastoreHelper.assertNoBillingEvents; import static google.registry.testing.DatastoreHelper.assertNoBillingEvents;
import static google.registry.testing.DatastoreHelper.createTld; import static google.registry.testing.DatastoreHelper.createTld;
import static google.registry.testing.DatastoreHelper.newDomainResource;
import static google.registry.testing.DatastoreHelper.persistActiveDomain; import static google.registry.testing.DatastoreHelper.persistActiveDomain;
import static google.registry.testing.DatastoreHelper.persistActiveHost; import static google.registry.testing.DatastoreHelper.persistActiveHost;
import static google.registry.testing.DatastoreHelper.persistDeletedHost; import static google.registry.testing.DatastoreHelper.persistDeletedHost;
import static google.registry.testing.DatastoreHelper.persistResource;
import static google.registry.testing.HostResourceSubject.assertAboutHosts; import static google.registry.testing.HostResourceSubject.assertAboutHosts;
import static google.registry.testing.TaskQueueHelper.assertDnsTasksEnqueued; import static google.registry.testing.TaskQueueHelper.assertDnsTasksEnqueued;
import static google.registry.testing.TaskQueueHelper.assertNoDnsTasksEnqueued; import static google.registry.testing.TaskQueueHelper.assertNoDnsTasksEnqueued;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key; import com.googlecode.objectify.Key;
import google.registry.flows.EppXmlTransformer.IpAddressVersionMismatchException; import google.registry.flows.EppXmlTransformer.IpAddressVersionMismatchException;
import google.registry.flows.ResourceFlowTestCase; import google.registry.flows.ResourceFlowTestCase;
@ -40,7 +43,9 @@ import google.registry.flows.host.HostFlowUtils.HostNameTooLongException;
import google.registry.flows.host.HostFlowUtils.HostNameTooShallowException; import google.registry.flows.host.HostFlowUtils.HostNameTooShallowException;
import google.registry.flows.host.HostFlowUtils.InvalidHostNameException; import google.registry.flows.host.HostFlowUtils.InvalidHostNameException;
import google.registry.flows.host.HostFlowUtils.SuperordinateDomainDoesNotExistException; import google.registry.flows.host.HostFlowUtils.SuperordinateDomainDoesNotExistException;
import google.registry.flows.host.HostFlowUtils.SuperordinateDomainInPendingDeleteException;
import google.registry.model.domain.DomainResource; import google.registry.model.domain.DomainResource;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.host.HostResource; import google.registry.model.host.HostResource;
import google.registry.model.reporting.HistoryEntry; import google.registry.model.reporting.HistoryEntry;
import org.joda.time.DateTime; import org.joda.time.DateTime;
@ -167,6 +172,22 @@ public class HostCreateFlowTest extends ResourceFlowTestCase<HostCreateFlow, Hos
runFlow(); runFlow();
} }
@Test
public void testFailure_superordinateInPendingDelete() throws Exception {
setEppHostCreateInputWithIps("ns1.example.tld");
createTld("tld");
persistResource(newDomainResource("example.tld")
.asBuilder()
.setDeletionTime(clock.nowUtc().plusDays(35))
.setStatusValues(ImmutableSet.of(StatusValue.PENDING_DELETE))
.build());
clock.advanceOneMilli();
thrown.expect(
SuperordinateDomainInPendingDeleteException.class,
"example.tld");
runFlow();
}
@Test @Test
public void testFailure_alreadyExists() throws Exception { public void testFailure_alreadyExists() throws Exception {
setEppHostCreateInput("ns1.example.tld", null); setEppHostCreateInput("ns1.example.tld", null);

View file

@ -59,6 +59,7 @@ import google.registry.flows.host.HostFlowUtils.HostNameTooLongException;
import google.registry.flows.host.HostFlowUtils.HostNameTooShallowException; import google.registry.flows.host.HostFlowUtils.HostNameTooShallowException;
import google.registry.flows.host.HostFlowUtils.InvalidHostNameException; import google.registry.flows.host.HostFlowUtils.InvalidHostNameException;
import google.registry.flows.host.HostFlowUtils.SuperordinateDomainDoesNotExistException; import google.registry.flows.host.HostFlowUtils.SuperordinateDomainDoesNotExistException;
import google.registry.flows.host.HostFlowUtils.SuperordinateDomainInPendingDeleteException;
import google.registry.flows.host.HostUpdateFlow.CannotAddIpToExternalHostException; import google.registry.flows.host.HostUpdateFlow.CannotAddIpToExternalHostException;
import google.registry.flows.host.HostUpdateFlow.CannotRemoveSubordinateHostLastIpException; import google.registry.flows.host.HostUpdateFlow.CannotRemoveSubordinateHostLastIpException;
import google.registry.flows.host.HostUpdateFlow.CannotRenameExternalHostException; import google.registry.flows.host.HostUpdateFlow.CannotRenameExternalHostException;
@ -762,6 +763,28 @@ public class HostUpdateFlowTest extends ResourceFlowTestCase<HostUpdateFlow, Hos
runFlow(); runFlow();
} }
@Test
public void testFailure_superordinateInPendingDelete() throws Exception {
setEppHostUpdateInput(
"ns1.example.tld",
"ns2.example.tld",
"<host:addr ip=\"v4\">192.0.2.22</host:addr>",
"<host:addr ip=\"v6\">1080:0:0:0:8:800:200C:417A</host:addr>");
createTld("tld");
DomainResource domain = persistResource(newDomainResource("example.tld")
.asBuilder()
.setSubordinateHosts(ImmutableSet.of(oldHostName()))
.setDeletionTime(clock.nowUtc().plusDays(35))
.setStatusValues(ImmutableSet.of(StatusValue.PENDING_DELETE))
.build());
persistActiveSubordinateHost(oldHostName(), domain);
clock.advanceOneMilli();
thrown.expect(
SuperordinateDomainInPendingDeleteException.class,
"example.tld");
runFlow();
}
@Test @Test
public void testFailure_neverExisted() throws Exception { public void testFailure_neverExisted() throws Exception {
thrown.expect( thrown.expect(