Add domain_lock nomulus command

This command is used by registry operators to apply registry locks to
domain names.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=176549874
This commit is contained in:
mcilwain 2017-11-21 13:05:32 -08:00 committed by jianglai
parent 8cd3979385
commit f041b1bac0
18 changed files with 453 additions and 35 deletions

View file

@ -18,6 +18,7 @@ import static com.google.common.collect.Sets.union;
import static com.google.common.io.BaseEncoding.base16;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.model.eppcommon.StatusValue.SERVER_UPDATE_PROHIBITED;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.testing.DatastoreHelper.assertBillingEvents;
import static google.registry.testing.DatastoreHelper.assertNoBillingEvents;
@ -34,6 +35,7 @@ import static google.registry.testing.DatastoreHelper.persistReservedList;
import static google.registry.testing.DatastoreHelper.persistResource;
import static google.registry.testing.DomainResourceSubject.assertAboutDomains;
import static google.registry.testing.HistoryEntrySubject.assertAboutHistoryEntries;
import static google.registry.testing.JUnitBackports.expectThrows;
import static google.registry.testing.TaskQueueHelper.assertDnsTasksEnqueued;
import static org.joda.money.CurrencyUnit.USD;
@ -892,9 +894,46 @@ public class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow,
persistReferencedEntities();
persistDomain();
runFlowAssertResponse(
CommitMode.LIVE,
UserPrivileges.SUPERUSER,
readFile("domain_update_response.xml"));
CommitMode.LIVE, UserPrivileges.SUPERUSER, readFile("domain_update_response.xml"));
}
@Test
public void testFailure_serverUpdateProhibited_prohibitsNonSuperuserUpdates() throws Exception {
persistReferencedEntities();
persistResource(
newDomainResource(getUniqueIdFromCommand())
.asBuilder()
.addStatusValue(SERVER_UPDATE_PROHIBITED)
.build());
Exception e = expectThrows(ResourceStatusProhibitsOperationException.class, this::runFlow);
assertThat(e).hasMessageThat().containsMatch("serverUpdateProhibited");
}
@Test
public void testSuccess_serverUpdateProhibited_allowsSuperuserUpdates() throws Exception {
persistReferencedEntities();
persistResource(persistDomain().asBuilder().addStatusValue(SERVER_UPDATE_PROHIBITED).build());
clock.advanceOneMilli();
runFlowAssertResponse(
CommitMode.LIVE, UserPrivileges.SUPERUSER, readFile("domain_update_response.xml"));
}
@Test
public void testFailure_serverUpdateProhibited_notSettableWithoutSuperuser() throws Exception {
setEppInput("domain_update_add_registry_lock.xml");
persistReferencedEntities();
persistDomain();
Exception e = expectThrows(StatusNotClientSettableException.class, this::runFlow);
assertThat(e).hasMessageThat().containsMatch("serverUpdateProhibited");
}
@Test
public void testSuccess_serverUpdateProhibited_isSettableWithSuperuser() throws Exception {
setEppInput("domain_update_add_registry_lock.xml");
persistReferencedEntities();
persistDomain();
runFlowAssertResponse(
CommitMode.LIVE, UserPrivileges.SUPERUSER, readFile("domain_update_response.xml"));
}
@Test
@ -915,7 +954,7 @@ public class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow,
persistReferencedEntities();
persistResource(
newDomainResource(getUniqueIdFromCommand()).asBuilder()
.setStatusValues(ImmutableSet.of(StatusValue.SERVER_UPDATE_PROHIBITED))
.setStatusValues(ImmutableSet.of(SERVER_UPDATE_PROHIBITED))
.build());
thrown.expect(ResourceStatusProhibitsOperationException.class, "serverUpdateProhibited");
runFlow();
@ -1398,7 +1437,7 @@ public class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow,
doSuccessfulTest();
assertAboutDomains()
.that(reloadResourceByForeignKey())
.hasStatusValue(StatusValue.SERVER_UPDATE_PROHIBITED)
.hasStatusValue(SERVER_UPDATE_PROHIBITED)
.and()
.hasStatusValue(StatusValue.SERVER_TRANSFER_PROHIBITED);
}

View file

@ -0,0 +1,29 @@
<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>example.tld</domain:name>
<domain:add>
<domain:ns>
<domain:hostObj>ns2.example.foo</domain:hostObj>
</domain:ns>
<domain:status s="serverUpdateProhibited"
lang="en">Add registry lock for domain.</domain:status>
</domain:add>
<domain:rem>
<domain:ns>
<domain:hostObj>ns1.example.foo</domain:hostObj>
</domain:ns>
</domain:rem>
<domain:chg>
<domain:registrant>sh8013</domain:registrant>
<domain:authInfo>
<domain:pw>2BARfoo</domain:pw>
</domain:authInfo>
</domain:chg>
</domain:update>
</update>
<clTRID>ABC-12345</clTRID>
</command>
</epp>

View file

@ -20,9 +20,7 @@ import static google.registry.util.ResourceUtils.readResourceUtf8;
import java.util.Map;
import java.util.Map.Entry;
/**
* Contains helper methods for dealing with test data.
*/
/** Contains helper methods for dealing with test data. */
public final class TestDataHelper {
/**
@ -31,7 +29,11 @@ public final class TestDataHelper {
*/
public static String loadFileWithSubstitutions(
Class<?> context, String filename, Map<String, String> substitutions) {
String fileContents = readResourceUtf8(context, "testdata/" + filename);
return applySubstitutions(readResourceUtf8(context, "testdata/" + filename), substitutions);
}
/** Applies the given substitutions to the given string and returns the result. */
public static String applySubstitutions(String fileContents, Map<String, String> substitutions) {
for (Entry<String, String> entry : nullToEmpty(substitutions).entrySet()) {
fileContents = fileContents.replaceAll("%" + entry.getKey() + "%", entry.getValue());
}

View file

@ -14,6 +14,7 @@
package google.registry.tools;
import static com.google.common.collect.Iterables.concat;
import static com.google.common.collect.Iterables.toArray;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.ofy.ObjectifyService.ofy;
@ -22,6 +23,8 @@ import static java.nio.charset.StandardCharsets.UTF_8;
import com.beust.jcommander.JCommander;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.ObjectArrays;
import com.google.common.io.Files;
import com.google.common.reflect.TypeToken;
@ -95,11 +98,21 @@ public abstract class CommandTestCase<C extends Command> {
runCommandInEnvironment(RegistryToolEnvironment.UNITTEST, args);
}
protected void runCommand(Iterable<String> args) throws Exception {
runCommandInEnvironment(
RegistryToolEnvironment.UNITTEST, Iterables.toArray(args, String.class));
}
/** Adds "--force" as the first parameter, then runs the command. */
protected void runCommandForced(String... args) throws Exception {
runCommand(ObjectArrays.concat("--force", args));
}
/** Adds "--force" as the first parameter, then runs the command. */
protected void runCommandForced(Iterable<String> args) throws Exception {
runCommand(concat(ImmutableList.of("--force"), args));
}
/** Writes the data to a named temporary file and then returns a path to the file. */
String writeToNamedTmpFile(String filename, byte[] data) throws IOException {
File tmpFile = tmpDir.newFile(filename);

View file

@ -14,12 +14,14 @@
package google.registry.tools;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.xml.XmlTestUtils.assertXmlEquals;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableMap;
@ -27,6 +29,7 @@ import com.google.common.net.MediaType;
import google.registry.tools.ServerSideCommand.Connection;
import google.registry.tools.server.ToolsTestData;
import java.net.URLDecoder;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.mockito.ArgumentCaptor;
@ -67,29 +70,34 @@ public class EppToolVerifier {
return new EppToolVerifier(connection, clientId, superuser, true);
}
void verifySent(String... xmlToMatch) throws Exception {
void verifySent(String... expectedXmlFiles) throws Exception {
verifySentContents(
Arrays.stream(expectedXmlFiles).map(ToolsTestData::loadUtf8).collect(toImmutableList()));
}
void verifySentContents(List<String> expectedXmlContents) throws Exception {
ArgumentCaptor<byte[]> params = ArgumentCaptor.forClass(byte[].class);
verify(connection, times(xmlToMatch.length)).send(
verify(connection, times(expectedXmlContents.size())).send(
eq("/_dr/epptool"),
eq(ImmutableMap.<String, Object>of()),
eq(MediaType.FORM_DATA),
params.capture());
List<byte[]> capturedParams = params.getAllValues();
assertThat(capturedParams).hasSize(xmlToMatch.length);
for (int i = 0; i < xmlToMatch.length; i++) {
String xml = xmlToMatch[i];
assertThat(capturedParams).hasSize(expectedXmlContents.size());
for (int i = 0; i < expectedXmlContents.size(); i++) {
byte[] capturedParam = capturedParams.get(i);
Map<String, String> map =
Splitter.on('&')
.withKeyValueSeparator('=')
.split(new String(capturedParam, UTF_8));
Splitter.on('&').withKeyValueSeparator('=').split(new String(capturedParam, UTF_8));
assertThat(map).hasSize(4);
assertXmlEquals(
ToolsTestData.loadUtf8(xml),
URLDecoder.decode(map.get("xml"), UTF_8.toString()));
expectedXmlContents.get(i), URLDecoder.decode(map.get("xml"), UTF_8.toString()));
assertThat(map).containsEntry("dryRun", Boolean.toString(dryRun));
assertThat(map).containsEntry("clientId", clientId);
assertThat(map).containsEntry("superuser", Boolean.toString(superuser));
}
}
void verifyNothingSent() {
verifyZeroInteractions(connection);
}
}

View file

@ -0,0 +1,112 @@
// 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.tools;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.eppcommon.StatusValue.SERVER_TRANSFER_PROHIBITED;
import static google.registry.testing.DatastoreHelper.newDomainResource;
import static google.registry.testing.DatastoreHelper.persistActiveDomain;
import static google.registry.testing.DatastoreHelper.persistResource;
import static google.registry.testing.JUnitBackports.expectThrows;
import static google.registry.testing.TestDataHelper.applySubstitutions;
import static google.registry.tools.LockOrUnlockDomainCommand.REGISTRY_LOCK_STATUSES;
import static google.registry.tools.server.ToolsTestData.loadUtf8;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
/** Unit tests for {@link LockDomainCommand}. */
public class LockDomainCommandTest extends EppToolCommandTestCase<LockDomainCommand> {
/** Gets an overridden eppVerifier that has superuser set to true on it. */
@Override
EppToolVerifier eppVerifier() {
return new EppToolVerifier()
.withConnection(connection)
.withClientId("NewRegistrar")
.asSuperuser();
}
@Test
public void testSuccess_sendsCorrectEppXml() throws Exception {
persistActiveDomain("example.tld");
runCommandForced("--client=NewRegistrar", "example.tld");
eppVerifier()
.verifySentContents(
ImmutableList.of(
loadUtf8("domain_lock.xml", ImmutableMap.of("DOMAIN", "example.tld"))));
}
@Test
public void testSuccess_partiallyUpdatesStatuses() throws Exception {
persistResource(
newDomainResource("example.tld")
.asBuilder()
.addStatusValue(SERVER_TRANSFER_PROHIBITED)
.build());
runCommandForced("--client=NewRegistrar", "example.tld");
eppVerifier().verifySent("domain_lock_partial_statuses.xml");
}
@Test
public void testSuccess_manyDomains() throws Exception {
List<String> params = new ArrayList<>();
List<String> expectedXmls = new ArrayList<>();
params.add("--client=NewRegistrar");
String updateDomainXml = loadUtf8("domain_lock.xml");
// Create 26 domains -- one more than the number of entity groups allowed in a transaction (in
// case that was going to be the failure point).
for (int n = 0; n < 26; n++) {
String domain = String.format("domain%d.tld", n);
persistActiveDomain(domain);
params.add(domain);
expectedXmls.add(applySubstitutions(updateDomainXml, ImmutableMap.of("DOMAIN", domain)));
}
runCommandForced(params);
eppVerifier().verifySentContents(expectedXmls);
}
@Test
public void testFailure_domainDoesntExist() throws Exception {
IllegalArgumentException e =
expectThrows(
IllegalArgumentException.class,
() -> runCommandForced("--client=NewRegistrar", "missing.tld"));
assertThat(e).hasMessageThat().isEqualTo("Domain 'missing.tld' does not exist");
}
@Test
public void testSuccess_alreadyLockedDomain_performsNoAction() throws Exception {
persistResource(
newDomainResource("example.tld")
.asBuilder()
.addStatusValues(REGISTRY_LOCK_STATUSES)
.build());
runCommandForced("--client=NewRegistrar", "example.tld");
eppVerifier().verifyNothingSent();
}
@Test
public void testFailure_duplicateDomainsAreSpecified() throws Exception {
IllegalArgumentException e =
expectThrows(
IllegalArgumentException.class,
() -> runCommandForced("--client=NewRegistrar", "dupe.tld", "dupe.tld"));
assertThat(e).hasMessageThat().isEqualTo("Duplicate domain arguments found: 'dupe.tld'");
}
}

View file

@ -14,10 +14,13 @@
package google.registry.tools;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.eppcommon.StatusValue.SERVER_UPDATE_PROHIBITED;
import static google.registry.testing.DatastoreHelper.newContactResource;
import static google.registry.testing.DatastoreHelper.newDomainResource;
import static google.registry.testing.DatastoreHelper.persistActiveHost;
import static google.registry.testing.DatastoreHelper.persistResource;
import static google.registry.testing.JUnitBackports.expectThrows;
import com.beust.jcommander.ParameterException;
import com.google.common.collect.ImmutableSet;
@ -151,7 +154,7 @@ public class UpdateDomainCommandTest extends EppToolCommandTestCase<UpdateDomain
.asBuilder()
.setStatusValues(
ImmutableSet.of(
StatusValue.CLIENT_RENEW_PROHIBITED, StatusValue.SERVER_UPDATE_PROHIBITED))
StatusValue.CLIENT_RENEW_PROHIBITED, StatusValue.SERVER_TRANSFER_PROHIBITED))
.setNameservers(nameservers)
.build());
@ -160,6 +163,32 @@ public class UpdateDomainCommandTest extends EppToolCommandTestCase<UpdateDomain
eppVerifier().verifySent("domain_update_set_statuses.xml");
}
@Test
public void testFailure_cantUpdateRegistryLockedDomainEvenAsSuperuser() throws Exception {
HostResource host = persistActiveHost("ns1.zdns.google");
ImmutableSet<Key<HostResource>> nameservers = ImmutableSet.of(Key.create(host));
persistResource(
newDomainResource("example.tld")
.asBuilder()
.setStatusValues(ImmutableSet.of(SERVER_UPDATE_PROHIBITED))
.setNameservers(nameservers)
.build());
Exception e =
expectThrows(
IllegalArgumentException.class,
() ->
runCommandForced(
"--client=NewRegistrar",
"--statuses=clientRenewProhibited,serverHold",
"--superuser",
"example.tld"));
assertThat(e)
.hasMessageThat()
.containsMatch("The domain 'example.tld' has status SERVER_UPDATE_PROHIBITED");
eppVerifier().verifyNothingSent();
}
@Test
public void testFailure_duplicateDomains() throws Exception {
thrown.expect(IllegalArgumentException.class, "Duplicate arguments found");

View file

@ -14,12 +14,12 @@
package google.registry.tools.server;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.ByteSource;
import com.google.common.io.Resources;
import java.io.IOException;
import google.registry.testing.TestDataHelper;
import java.net.URL;
import java.util.Map;
/** Utility class providing easy access to contents of the {@code testdata/} directory. */
public final class ToolsTestData {
@ -30,12 +30,17 @@ public final class ToolsTestData {
}
/**
* Loads data from file in {@code tools/server/testdata/} as a String (assuming file is UTF-8).
*
* @throws IOException if the file couldn't be loaded from the jar.
* Loads data from file in {@code tools/server/testdata/} as a UTF-8 String.
*/
public static String loadUtf8(String filename) throws IOException {
return Resources.asCharSource(getUrl(filename), UTF_8).read();
public static String loadUtf8(String filename) {
return loadUtf8(filename, ImmutableMap.of());
}
/**
* Loads data from file in {@code tools/server/testdata/} as a UTF-8 String, with substitutions.
*/
public static String loadUtf8(String filename, Map<String, String> substitutions) {
return TestDataHelper.loadFileWithSubstitutions(ToolsTestData.class, filename, substitutions);
}
private static URL getUrl(String filename) {

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<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>%DOMAIN%</domain:name>
<domain:add>
<domain:status s="serverDeleteProhibited"/>
<domain:status s="serverTransferProhibited"/>
<domain:status s="serverUpdateProhibited"/>
</domain:add>
</domain:update>
</update>
<clTRID>RegistryTool</clTRID>
</command>
</epp>

View file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<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>example.tld</domain:name>
<domain:add>
<domain:status s="serverDeleteProhibited"/>
<domain:status s="serverUpdateProhibited"/>
</domain:add>
</domain:update>
</update>
<clTRID>RegistryTool</clTRID>
</command>
</epp>

View file

@ -9,7 +9,7 @@
<domain:status s="serverHold"/>
</domain:add>
<domain:rem>
<domain:status s="serverUpdateProhibited"/>
<domain:status s="serverTransferProhibited"/>
</domain:rem>
</domain:update>
</update>