Implement Uniform [] Suspension Tool

The tool needs to:
* Replace all hosts on a domain with a provided set of hosts
* Add 3 server locks to the domain
* Print an undo command that restores the domain to its original state
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=121944934
This commit is contained in:
cgoldfeder 2016-05-10 07:35:49 -07:00 committed by Justine Tunney
parent 758a4ac387
commit 371ffdf0c1
8 changed files with 491 additions and 0 deletions

View file

@ -67,6 +67,7 @@ public final class GtechTool {
.put("registrar_activity_report", RegistrarActivityReportCommand.class) .put("registrar_activity_report", RegistrarActivityReportCommand.class)
.put("registrar_contact", RegistrarContactCommand.class) .put("registrar_contact", RegistrarContactCommand.class)
.put("setup_ote", SetupOteCommand.class) .put("setup_ote", SetupOteCommand.class)
.put("uniform_rapid_suspension", UniformRapidSuspensionCommand.class)
.put("update_registrar", UpdateRegistrarCommand.class) .put("update_registrar", UpdateRegistrarCommand.class)
.put("update_sandbox_tld", UpdateSandboxTldCommand.class) .put("update_sandbox_tld", UpdateSandboxTldCommand.class)
.put("update_server_locks", UpdateServerLocksCommand.class) .put("update_server_locks", UpdateServerLocksCommand.class)

View file

@ -0,0 +1,149 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.Sets.difference;
import static google.registry.model.EppResourceUtils.checkResourcesExist;
import static google.registry.model.EppResourceUtils.loadByUniqueId;
import static org.joda.time.DateTimeZone.UTC;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.template.soy.data.SoyMapData;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import google.registry.model.domain.DomainResource;
import google.registry.model.domain.ReferenceUnion;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.host.HostResource;
import google.registry.tools.Command.GtechCommand;
import google.registry.tools.soy.UniformRapidSuspensionSoyInfo;
import org.joda.time.DateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/** A command to suspend a domain for the Uniform Rapid Suspension process. */
@Parameters(separators = " =",
commandDescription = "Suspend a domain for Uniform Rapid Suspension.")
final class UniformRapidSuspensionCommand extends MutatingEppToolCommand implements GtechCommand {
private static final ImmutableSet<String> URS_LOCKS = ImmutableSet.of(
StatusValue.SERVER_DELETE_PROHIBITED.getXmlName(),
StatusValue.SERVER_TRANSFER_PROHIBITED.getXmlName(),
StatusValue.SERVER_UPDATE_PROHIBITED.getXmlName());
/** Client id that made this change. Only recorded in the history entry. **/
private static final String CLIENT_ID = "CharlestonRoad";
@Parameter(
names = {"-n", "--domain_name"},
description = "Domain to suspend.",
required = true)
private String domainName;
@Parameter(
names = {"-h", "--hosts"},
description = "Comma-delimited set of fully qualified host names to replace the current hosts"
+ " on the domain.")
private List<String> newHosts = new ArrayList<>();
@Parameter(
names = {"-p", "--preserve"},
description = "Comma-delimited set of locks to preserve (only valid with --undo). "
+ "Valid locks: serverDeleteProhibited, serverTransferProhibited, serverUpdateProhibited")
private List<String> locksToPreserve = new ArrayList<>();
@Parameter(
names = {"--undo"},
description = "Flag indicating that is is an undo command, which removes locks.")
private boolean undo;
/** Set of existing locks that need to be preserved during undo, sorted for nicer output. */
ImmutableSortedSet<String> existingLocks;
/** Set of existing nameservers that need to be restored during undo, sorted for nicer output. */
ImmutableSortedSet<String> existingNameservers;
@Override
protected void initMutatingEppToolCommand() {
superuser = true;
DateTime now = DateTime.now(UTC);
ImmutableSet<String> newHostsSet = ImmutableSet.copyOf(newHosts);
DomainResource domain = loadByUniqueId(DomainResource.class, domainName, now);
checkArgument(domain != null, "Domain '%s' does not exist", domainName);
Set<String> missingHosts =
difference(newHostsSet, checkResourcesExist(HostResource.class, newHosts, now));
checkArgument(missingHosts.isEmpty(), "Hosts do not exist: %s", missingHosts);
checkArgument(
locksToPreserve.isEmpty() || undo,
"Locks can only be preserved when running with --undo");
existingNameservers = getExistingNameservers(domain);
existingLocks = getExistingLocks(domain);
setSoyTemplate(
UniformRapidSuspensionSoyInfo.getInstance(),
UniformRapidSuspensionSoyInfo.UNIFORMRAPIDSUSPENSION);
addSoyRecord(CLIENT_ID, new SoyMapData(
"domainName", domainName,
"hostsToAdd", difference(newHostsSet, existingNameservers),
"hostsToRemove", difference(existingNameservers, newHostsSet),
"locksToApply", undo ? ImmutableSet.of() : URS_LOCKS,
"locksToRemove",
undo ? difference(URS_LOCKS, ImmutableSet.copyOf(locksToPreserve)) : ImmutableSet.of(),
"reason", (undo ? "Undo " : "") + "Uniform Rapid Suspension"));
}
private ImmutableSortedSet<String> getExistingNameservers(DomainResource domain) {
ImmutableSortedSet.Builder<String> nameservers = ImmutableSortedSet.naturalOrder();
for (ReferenceUnion<HostResource> nameserverRef : domain.getNameservers()) {
nameservers.add(nameserverRef.getLinked().get().getForeignKey());
}
return nameservers.build();
}
private ImmutableSortedSet<String> getExistingLocks(DomainResource domain) {
ImmutableSortedSet.Builder<String> locks = ImmutableSortedSet.naturalOrder();
for (StatusValue lock : domain.getStatusValues()) {
if (URS_LOCKS.contains(lock.getXmlName())) {
locks.add(lock.getXmlName());
}
}
return locks.build();
}
@Override
protected String postExecute() throws Exception {
if (undo) {
return "";
}
StringBuilder undoBuilder = new StringBuilder("UNDO COMMAND:\n\ngtech_tool -e ")
.append(RegistryToolEnvironment.get())
.append(" uniform_rapid_suspension --undo --domain_name ")
.append(domainName);
if (!existingNameservers.isEmpty()) {
undoBuilder.append(" --hosts ").append(Joiner.on(',').join(existingNameservers));
}
if (!existingLocks.isEmpty()) {
undoBuilder.append(" --preserve ").append(Joiner.on(',').join(existingLocks));
}
return undoBuilder.toString();
}
}

View file

@ -0,0 +1,54 @@
{namespace domain.registry.tools autoescape="strict"}
/**
* Uniform Rapid Suspension
*/
{template .uniformrapidsuspension}
{@param domainName: string}
{@param hostsToAdd: list<string>}
{@param hostsToRemove: list<string>}
{@param locksToApply: list<string>}
{@param locksToRemove: list<string>}
{@param reason: string}
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<update>
<domain:update xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>{$domainName}</domain:name>
<domain:add>
{if length($hostsToAdd) > 0}
<domain:ns>
{foreach $ha in $hostsToAdd}
<domain:hostObj>{$ha}</domain:hostObj>
{/foreach}
</domain:ns>
{/if}
{foreach $la in $locksToApply}
<domain:status s="{$la}" />
{/foreach}
</domain:add>
<domain:rem>
{if length($hostsToRemove) > 0}
<domain:ns>
{foreach $hr in $hostsToRemove}
<domain:hostObj>{$hr}</domain:hostObj>
{/foreach}
</domain:ns>
{/if}
{foreach $lr in $locksToRemove}
<domain:status s="{$lr}" />
{/foreach}
</domain:rem>
</domain:update>
</update>
<extension>
<metadata:metadata xmlns:metadata="urn:google:params:xml:ns:metadata-1.0">
<metadata:reason>{$reason}</metadata:reason>
<metadata:requestedByRegistrar>false</metadata:requestedByRegistrar>
</metadata:metadata>
</extension>
<clTRID>GTechTool</clTRID>
</command>
</epp>
{/template}

View file

@ -0,0 +1,162 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.tools;
import static google.registry.testing.DatastoreHelper.newDomainResource;
import static google.registry.testing.DatastoreHelper.persistActiveDomain;
import static google.registry.testing.DatastoreHelper.persistActiveHost;
import static google.registry.testing.DatastoreHelper.persistResource;
import com.google.common.collect.ImmutableSet;
import com.beust.jcommander.ParameterException;
import com.googlecode.objectify.Ref;
import google.registry.model.domain.ReferenceUnion;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.host.HostResource;
import google.registry.model.registrar.Registrar;
import org.junit.Before;
import org.junit.Test;
/** Unit tests for {@link UniformRapidSuspensionCommand}. */
public class UniformRapidSuspensionCommandTest
extends EppToolCommandTestCase<UniformRapidSuspensionCommand> {
@Before
public void initRegistrar() {
// Since the command's history client ID must be CharlestonRoad, resave TheRegistrar that way.
persistResource(Registrar.loadByClientId("TheRegistrar").asBuilder()
.setClientIdentifier("CharlestonRoad")
.build());
}
private void persistDomainWithHosts(HostResource... hosts) {
ImmutableSet.Builder<ReferenceUnion<HostResource>> hostRefs = new ImmutableSet.Builder<>();
for (HostResource host : hosts) {
hostRefs.add(ReferenceUnion.create(Ref.create(host)));
}
persistResource(newDomainResource("evil.tld").asBuilder()
.setNameservers(hostRefs.build())
.build());
}
@Test
public void testCommand_addsLocksReplacesHostsPrintsUndo() throws Exception {
persistActiveHost("urs1.example.com");
persistActiveHost("urs2.example.com");
persistDomainWithHosts(
persistActiveHost("ns1.example.com"),
persistActiveHost("ns2.example.com"));
runCommandForced("--domain_name=evil.tld", "--hosts=urs1.example.com,urs2.example.com");
eppVerifier()
.setClientIdentifier("CharlestonRoad")
.asSuperuser()
.verifySent("testdata/uniform_rapid_suspension.xml");
assertInStdout("uniform_rapid_suspension "
+ "--undo "
+ "--domain_name evil.tld "
+ "--hosts ns1.example.com,ns2.example.com");
}
@Test
public void testCommand_respectsExistingHost() throws Exception {
persistActiveHost("urs1.example.com");
persistDomainWithHosts(
persistActiveHost("urs2.example.com"),
persistActiveHost("ns1.example.com"));
runCommandForced("--domain_name=evil.tld", "--hosts=urs1.example.com,urs2.example.com");
eppVerifier()
.setClientIdentifier("CharlestonRoad")
.asSuperuser()
.verifySent("testdata/uniform_rapid_suspension_existing_host.xml");
assertInStdout("uniform_rapid_suspension "
+ "--undo "
+ "--domain_name evil.tld "
+ "--hosts ns1.example.com,urs2.example.com");
}
@Test
public void testCommand_generatesUndoForUndelegatedDomain() throws Exception {
persistActiveHost("urs1.example.com");
persistActiveHost("urs2.example.com");
persistActiveDomain("evil.tld");
runCommandForced("--domain_name=evil.tld", "--hosts=urs1.example.com,urs2.example.com");
assertInStdout("uniform_rapid_suspension --undo --domain_name evil.tld");
}
@Test
public void testCommand_generatesUndoWithPreserve() throws Exception {
persistResource(
newDomainResource("evil.tld").asBuilder()
.addStatusValue(StatusValue.SERVER_DELETE_PROHIBITED)
.build());
runCommandForced("--domain_name=evil.tld");
assertInStdout(
"uniform_rapid_suspension --undo --domain_name evil.tld --preserve serverDeleteProhibited");
}
@Test
public void testUndo_removesLocksReplacesHosts() throws Exception {
persistActiveHost("ns1.example.com");
persistActiveHost("ns2.example.com");
persistDomainWithHosts(
persistActiveHost("urs1.example.com"),
persistActiveHost("urs2.example.com"));
runCommandForced(
"--domain_name=evil.tld", "--undo", "--hosts=ns1.example.com,ns2.example.com");
eppVerifier()
.setClientIdentifier("CharlestonRoad")
.asSuperuser()
.verifySent("testdata/uniform_rapid_suspension_undo.xml");
assertNotInStdout("--undo"); // Undo shouldn't print a new undo command.
}
@Test
public void testUndo_respectsPreserveFlag() throws Exception {
persistActiveHost("ns1.example.com");
persistActiveHost("ns2.example.com");
persistDomainWithHosts(
persistActiveHost("urs1.example.com"),
persistActiveHost("urs2.example.com"));
runCommandForced(
"--domain_name=evil.tld",
"--undo",
"--preserve=serverDeleteProhibited",
"--hosts=ns1.example.com,ns2.example.com");
eppVerifier()
.setClientIdentifier("CharlestonRoad")
.asSuperuser()
.verifySent("testdata/uniform_rapid_suspension_undo_preserve.xml");
assertNotInStdout("--undo"); // Undo shouldn't print a new undo command.
}
@Test
public void testFailure_preserveWithoutUndo() throws Exception {
persistActiveDomain("evil.tld");
thrown.expect(IllegalArgumentException.class, "--undo");
runCommandForced("--domain_name=evil.tld", "--preserve=serverDeleteProhibited");
}
@Test
public void testFailure_domainNameRequired() throws Exception {
persistActiveHost("urs1.example.com");
persistActiveHost("urs2.example.com");
persistActiveDomain("evil.tld");
thrown.expect(ParameterException.class, "--domain_name");
runCommandForced("--hosts=urs1.example.com,urs2.example.com");
}
}

View file

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<update>
<domain:update xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>evil.tld</domain:name>
<domain:add>
<domain:ns>
<domain:hostObj>urs1.example.com</domain:hostObj>
<domain:hostObj>urs2.example.com</domain:hostObj>
</domain:ns>
<domain:status s="serverDeleteProhibited" />
<domain:status s="serverTransferProhibited" />
<domain:status s="serverUpdateProhibited" />
</domain:add>
<domain:rem>
<domain:ns>
<domain:hostObj>ns2.example.com</domain:hostObj>
<domain:hostObj>ns1.example.com</domain:hostObj>
</domain:ns>
</domain:rem>
</domain:update>
</update>
<extension>
<metadata:metadata xmlns:metadata="urn:google:params:xml:ns:metadata-1.0">
<metadata:reason>Uniform Rapid Suspension</metadata:reason>
<metadata:requestedByRegistrar>false</metadata:requestedByRegistrar>
</metadata:metadata>
</extension>
<clTRID>GTechTool</clTRID>
</command>
</epp>

View file

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<update>
<domain:update xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>evil.tld</domain:name>
<domain:add>
<domain:ns>
<domain:hostObj>urs1.example.com</domain:hostObj>
</domain:ns>
<domain:status s="serverDeleteProhibited" />
<domain:status s="serverTransferProhibited" />
<domain:status s="serverUpdateProhibited" />
</domain:add>
<domain:rem>
<domain:ns>
<domain:hostObj>ns1.example.com</domain:hostObj>
</domain:ns>
</domain:rem>
</domain:update>
</update>
<extension>
<metadata:metadata xmlns:metadata="urn:google:params:xml:ns:metadata-1.0">
<metadata:reason>Uniform Rapid Suspension</metadata:reason>
<metadata:requestedByRegistrar>false</metadata:requestedByRegistrar>
</metadata:metadata>
</extension>
<clTRID>GTechTool</clTRID>
</command>
</epp>

View file

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<update>
<domain:update xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>evil.tld</domain:name>
<domain:add>
<domain:ns>
<domain:hostObj>ns1.example.com</domain:hostObj>
<domain:hostObj>ns2.example.com</domain:hostObj>
</domain:ns>
</domain:add>
<domain:rem>
<domain:ns>
<domain:hostObj>urs1.example.com</domain:hostObj>
<domain:hostObj>urs2.example.com</domain:hostObj>
</domain:ns>
<domain:status s="serverDeleteProhibited" />
<domain:status s="serverTransferProhibited" />
<domain:status s="serverUpdateProhibited" />
</domain:rem>
</domain:update>
</update>
<extension>
<metadata:metadata xmlns:metadata="urn:google:params:xml:ns:metadata-1.0">
<metadata:reason>Undo Uniform Rapid Suspension</metadata:reason>
<metadata:requestedByRegistrar>false</metadata:requestedByRegistrar>
</metadata:metadata>
</extension>
<clTRID>GTechTool</clTRID>
</command>
</epp>

View file

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<update>
<domain:update xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>evil.tld</domain:name>
<domain:add>
<domain:ns>
<domain:hostObj>ns1.example.com</domain:hostObj>
<domain:hostObj>ns2.example.com</domain:hostObj>
</domain:ns>
</domain:add>
<domain:rem>
<domain:ns>
<domain:hostObj>urs1.example.com</domain:hostObj>
<domain:hostObj>urs2.example.com</domain:hostObj>
</domain:ns>
<domain:status s="serverTransferProhibited" />
<domain:status s="serverUpdateProhibited" />
</domain:rem>
</domain:update>
</update>
<extension>
<metadata:metadata xmlns:metadata="urn:google:params:xml:ns:metadata-1.0">
<metadata:reason>Undo Uniform Rapid Suspension</metadata:reason>
<metadata:requestedByRegistrar>false</metadata:requestedByRegistrar>
</metadata:metadata>
</extension>
<clTRID>GTechTool</clTRID>
</command>
</epp>