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

@ -135,7 +135,11 @@ import org.joda.time.DateTime;
public final class DomainUpdateFlow implements TransactionalFlow { public final class DomainUpdateFlow implements TransactionalFlow {
/** /**
* Note that CLIENT_UPDATE_PROHIBITED is intentionally not in this list. This is because it * A list of {@link StatusValue}s that prohibit updates.
*
* <p>Superusers can override these statuses and perform updates anyway.
*
* <p>Note that CLIENT_UPDATE_PROHIBITED is intentionally not in this list. This is because it
* requires special checking, since you must be able to clear the status off the object with an * requires special checking, since you must be able to clear the status off the object with an
* update. * update.
*/ */
@ -208,12 +212,12 @@ public final class DomainUpdateFlow implements TransactionalFlow {
/** Fail if the object doesn't exist or was deleted. */ /** Fail if the object doesn't exist or was deleted. */
private void verifyUpdateAllowed(Update command, DomainResource existingDomain, DateTime now) private void verifyUpdateAllowed(Update command, DomainResource existingDomain, DateTime now)
throws EppException { throws EppException {
verifyNoDisallowedStatuses(existingDomain, UPDATE_DISALLOWED_STATUSES);
verifyOptionalAuthInfo(authInfo, existingDomain); verifyOptionalAuthInfo(authInfo, existingDomain);
AddRemove add = command.getInnerAdd(); AddRemove add = command.getInnerAdd();
AddRemove remove = command.getInnerRemove(); AddRemove remove = command.getInnerRemove();
String tld = existingDomain.getTld(); String tld = existingDomain.getTld();
if (!isSuperuser) { if (!isSuperuser) {
verifyNoDisallowedStatuses(existingDomain, UPDATE_DISALLOWED_STATUSES);
verifyResourceOwnership(clientId, existingDomain); verifyResourceOwnership(clientId, existingDomain);
verifyClientUpdateNotProhibited(command, existingDomain); verifyClientUpdateNotProhibited(command, existingDomain);
verifyAllStatusesAreClientSettable(union(add.getStatusValues(), remove.getStatusValues())); verifyAllStatusesAreClientSettable(union(add.getStatusValues(), remove.getStatusValues()));

View file

@ -43,11 +43,9 @@ import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
* @see <a href="https://tools.ietf.org/html/rfc5731#section-2.3">RFC 5731 (Domain) Section 2.3</a> * @see <a href="https://tools.ietf.org/html/rfc5731#section-2.3">RFC 5731 (Domain) Section 2.3</a>
* @see <a href="https://tools.ietf.org/html/rfc5732#section-2.3">RFC 5732 (Host) Section 2.3</a> * @see <a href="https://tools.ietf.org/html/rfc5732#section-2.3">RFC 5732 (Host) Section 2.3</a>
* @see <a href="https://tools.ietf.org/html/rfc5733#section-2.2">RFC 5733 (Contact) Section 2.2</a> * @see <a href="https://tools.ietf.org/html/rfc5733#section-2.2">RFC 5733 (Contact) Section 2.2</a>
*/ */
@XmlJavaTypeAdapter(StatusValueAdapter.class) @XmlJavaTypeAdapter(StatusValueAdapter.class)
public enum StatusValue implements EppEnum { public enum StatusValue implements EppEnum {
CLIENT_DELETE_PROHIBITED(AllowedOn.ALL), CLIENT_DELETE_PROHIBITED(AllowedOn.ALL),
CLIENT_HOLD(AllowedOn.ALL), CLIENT_HOLD(AllowedOn.ALL),
CLIENT_RENEW_PROHIBITED(AllowedOn.ALL), CLIENT_RENEW_PROHIBITED(AllowedOn.ALL),
@ -114,10 +112,17 @@ public enum StatusValue implements EppEnum {
*/ */
PENDING_UPDATE(AllowedOn.NONE), PENDING_UPDATE(AllowedOn.NONE),
/** A non-client-settable status that prevents deletes of EPP resources. */
SERVER_DELETE_PROHIBITED(AllowedOn.ALL), SERVER_DELETE_PROHIBITED(AllowedOn.ALL),
SERVER_HOLD(AllowedOn.ALL), SERVER_HOLD(AllowedOn.ALL),
SERVER_RENEW_PROHIBITED(AllowedOn.ALL), SERVER_RENEW_PROHIBITED(AllowedOn.ALL),
/** A non-client-settable status that prevents transfers of EPP resources. */
SERVER_TRANSFER_PROHIBITED(AllowedOn.ALL), SERVER_TRANSFER_PROHIBITED(AllowedOn.ALL),
/** A non-client-settable status that prevents updates of EPP resources, except by superusers. */
SERVER_UPDATE_PROHIBITED(AllowedOn.ALL); SERVER_UPDATE_PROHIBITED(AllowedOn.ALL);
private final String xmlName = UPPER_UNDERSCORE.to(LOWER_CAMEL, name()); private final String xmlName = UPPER_UNDERSCORE.to(LOWER_CAMEL, name());

View file

@ -0,0 +1,76 @@
// 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.base.Preconditions.checkArgument;
import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.util.FormattingLogger.getLoggerForCallerClass;
import static java.util.stream.Collectors.toList;
import static org.joda.time.DateTimeZone.UTC;
import com.beust.jcommander.Parameters;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.template.soy.data.SoyMapData;
import google.registry.model.domain.DomainResource;
import google.registry.model.eppcommon.StatusValue;
import google.registry.tools.soy.DomainUpdateSoyInfo;
import google.registry.util.FormattingLogger;
import org.joda.time.DateTime;
/**
* A command to registry lock domain names via EPP.
*
* <p>A registry lock consists of server-side statuses preventing deletes, updates, and transfers.
*/
@Parameters(separators = " =", commandDescription = "Registry lock a domain via EPP.")
public class LockDomainCommand extends LockOrUnlockDomainCommand {
private static final FormattingLogger logger = getLoggerForCallerClass();
@Override
protected void initMutatingEppToolCommand() throws Exception {
// Project all domains as of the same time so that argument order doesn't affect behavior.
DateTime now = DateTime.now(UTC);
for (String domain : getDomains()) {
DomainResource domainResource = loadByForeignKey(DomainResource.class, domain, now);
checkArgument(domainResource != null, "Domain '%s' does not exist", domain);
ImmutableSet<StatusValue> statusesToAdd =
Sets.difference(REGISTRY_LOCK_STATUSES, domainResource.getStatusValues()).immutableCopy();
if (statusesToAdd.isEmpty()) {
logger.infofmt("Domain '%s' is already locked and needs no updates.", domain);
continue;
}
setSoyTemplate(DomainUpdateSoyInfo.getInstance(), DomainUpdateSoyInfo.DOMAINUPDATE);
addSoyRecord(
clientId,
new SoyMapData(
"domain", domain,
"add", true,
"addNameservers", ImmutableList.of(),
"addAdmins", ImmutableList.of(),
"addTechs", ImmutableList.of(),
"addStatuses", statusesToAdd.stream().map(StatusValue::getXmlName).collect(toList()),
"remove", false,
"removeNameservers", ImmutableList.of(),
"removeAdmins", ImmutableList.of(),
"removeTechs", ImmutableList.of(),
"removeStatuses", ImmutableList.of(),
"change", false));
}
}
}

View file

@ -0,0 +1,60 @@
// 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.base.Preconditions.checkArgument;
import static google.registry.model.eppcommon.StatusValue.SERVER_DELETE_PROHIBITED;
import static google.registry.model.eppcommon.StatusValue.SERVER_TRANSFER_PROHIBITED;
import static google.registry.model.eppcommon.StatusValue.SERVER_UPDATE_PROHIBITED;
import static google.registry.util.CollectionUtils.findDuplicates;
import com.beust.jcommander.Parameter;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableSet;
import google.registry.model.eppcommon.StatusValue;
import java.util.List;
/** Shared base class for commands to registry lock or unlock a domain via EPP. */
public abstract class LockOrUnlockDomainCommand extends MutatingEppToolCommand {
protected static final ImmutableSet<StatusValue> REGISTRY_LOCK_STATUSES =
ImmutableSet.of(
SERVER_DELETE_PROHIBITED, SERVER_TRANSFER_PROHIBITED, SERVER_UPDATE_PROHIBITED);
@Parameter(
names = {"-c", "--client"},
description = "Client identifier of the registrar to execute the command as",
required = true
)
String clientId;
@Parameter(description = "Names of the domains", required = true)
private List<String> mainParameters;
protected ImmutableSet<String> getDomains() {
return ImmutableSet.copyOf(mainParameters);
}
@Override
protected void initEppToolCommand() throws Exception {
// Superuser status is required to update registry lock statuses.
superuser = true;
String duplicates = Joiner.on(", ").join(findDuplicates(mainParameters));
checkArgument(duplicates.isEmpty(), "Duplicate domain arguments found: '%s'", duplicates);
initMutatingEppToolCommand();
System.out.println(
"== ENSURE THAT YOU HAVE AUTHENTICATED THE REGISTRAR BEFORE RUNNING THIS COMMAND ==");
}
}

View file

@ -95,6 +95,7 @@ public final class RegistryTool {
.put("list_tlds", ListTldsCommand.class) .put("list_tlds", ListTldsCommand.class)
.put("load_snapshot", LoadSnapshotCommand.class) .put("load_snapshot", LoadSnapshotCommand.class)
.put("load_test", LoadTestCommand.class) .put("load_test", LoadTestCommand.class)
.put("lock_domain", LockDomainCommand.class)
.put("login", LoginCommand.class) .put("login", LoginCommand.class)
.put("logout", LogoutCommand.class) .put("logout", LogoutCommand.class)
.put("make_billing_tables", MakeBillingTablesCommand.class) .put("make_billing_tables", MakeBillingTablesCommand.class)

View file

@ -94,9 +94,7 @@ interface RegistryToolComponent {
void inject(SendEscrowReportToIcannCommand command); void inject(SendEscrowReportToIcannCommand command);
void inject(SetupOteCommand command); void inject(SetupOteCommand command);
void inject(UpdateCursorsCommand command); void inject(UpdateCursorsCommand command);
void inject(UpdateDomainCommand command); void inject(UpdateDomainCommand command);
void inject(UpdateKmsKeyringCommand command); void inject(UpdateKmsKeyringCommand command);
void inject(UpdateTldCommand command); void inject(UpdateTldCommand command);
void inject(ValidateEscrowDepositCommand command); void inject(ValidateEscrowDepositCommand command);

View file

@ -17,6 +17,7 @@ package google.registry.tools;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static google.registry.model.EppResourceUtils.loadByForeignKey; 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.model.ofy.ObjectifyService.ofy;
import static org.joda.time.DateTimeZone.UTC; import static org.joda.time.DateTimeZone.UTC;
@ -127,6 +128,11 @@ final class UpdateDomainCommand extends CreateOrUpdateDomainCommand {
DateTime now = DateTime.now(UTC); DateTime now = DateTime.now(UTC);
DomainResource domainResource = loadByForeignKey(DomainResource.class, domain, now); DomainResource domainResource = loadByForeignKey(DomainResource.class, domain, now);
checkArgument(domainResource != null, "Domain '%s' does not exist", domain); checkArgument(domainResource != null, "Domain '%s' does not exist", domain);
checkArgument(
!domainResource.getStatusValues().contains(SERVER_UPDATE_PROHIBITED),
"The domain '%s' has status SERVER_UPDATE_PROHIBITED. Verify that you are allowed "
+ "to make updates, and if so, use the domain_unlock command to enable updates.",
domain);
if (!nameservers.isEmpty()) { if (!nameservers.isEmpty()) {
ImmutableSortedSet<String> existingNameservers = ImmutableSortedSet<String> existingNameservers =
domainResource.loadNameserverFullyQualifiedHostNames(); domainResource.loadNameserverFullyQualifiedHostNames();

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.io.BaseEncoding.base16;
import static com.google.common.truth.Truth.assertThat; 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.model.eppcommon.StatusValue.SERVER_UPDATE_PROHIBITED;
import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.testing.DatastoreHelper.assertBillingEvents; import static google.registry.testing.DatastoreHelper.assertBillingEvents;
import static google.registry.testing.DatastoreHelper.assertNoBillingEvents; 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.DatastoreHelper.persistResource;
import static google.registry.testing.DomainResourceSubject.assertAboutDomains; import static google.registry.testing.DomainResourceSubject.assertAboutDomains;
import static google.registry.testing.HistoryEntrySubject.assertAboutHistoryEntries; import static google.registry.testing.HistoryEntrySubject.assertAboutHistoryEntries;
import static google.registry.testing.JUnitBackports.expectThrows;
import static google.registry.testing.TaskQueueHelper.assertDnsTasksEnqueued; import static google.registry.testing.TaskQueueHelper.assertDnsTasksEnqueued;
import static org.joda.money.CurrencyUnit.USD; import static org.joda.money.CurrencyUnit.USD;
@ -892,9 +894,46 @@ public class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow,
persistReferencedEntities(); persistReferencedEntities();
persistDomain(); persistDomain();
runFlowAssertResponse( runFlowAssertResponse(
CommitMode.LIVE, CommitMode.LIVE, UserPrivileges.SUPERUSER, readFile("domain_update_response.xml"));
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 @Test
@ -915,7 +954,7 @@ public class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow,
persistReferencedEntities(); persistReferencedEntities();
persistResource( persistResource(
newDomainResource(getUniqueIdFromCommand()).asBuilder() newDomainResource(getUniqueIdFromCommand()).asBuilder()
.setStatusValues(ImmutableSet.of(StatusValue.SERVER_UPDATE_PROHIBITED)) .setStatusValues(ImmutableSet.of(SERVER_UPDATE_PROHIBITED))
.build()); .build());
thrown.expect(ResourceStatusProhibitsOperationException.class, "serverUpdateProhibited"); thrown.expect(ResourceStatusProhibitsOperationException.class, "serverUpdateProhibited");
runFlow(); runFlow();
@ -1398,7 +1437,7 @@ public class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow,
doSuccessfulTest(); doSuccessfulTest();
assertAboutDomains() assertAboutDomains()
.that(reloadResourceByForeignKey()) .that(reloadResourceByForeignKey())
.hasStatusValue(StatusValue.SERVER_UPDATE_PROHIBITED) .hasStatusValue(SERVER_UPDATE_PROHIBITED)
.and() .and()
.hasStatusValue(StatusValue.SERVER_TRANSFER_PROHIBITED); .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;
import java.util.Map.Entry; 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 { public final class TestDataHelper {
/** /**
@ -31,7 +29,11 @@ public final class TestDataHelper {
*/ */
public static String loadFileWithSubstitutions( public static String loadFileWithSubstitutions(
Class<?> context, String filename, Map<String, String> substitutions) { 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()) { for (Entry<String, String> entry : nullToEmpty(substitutions).entrySet()) {
fileContents = fileContents.replaceAll("%" + entry.getKey() + "%", entry.getValue()); fileContents = fileContents.replaceAll("%" + entry.getKey() + "%", entry.getValue());
} }

View file

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

View file

@ -14,12 +14,14 @@
package google.registry.tools; package google.registry.tools;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static google.registry.xml.XmlTestUtils.assertXmlEquals; import static google.registry.xml.XmlTestUtils.assertXmlEquals;
import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8;
import static org.mockito.Matchers.eq; import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import com.google.common.base.Splitter; import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableMap; 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.ServerSideCommand.Connection;
import google.registry.tools.server.ToolsTestData; import google.registry.tools.server.ToolsTestData;
import java.net.URLDecoder; import java.net.URLDecoder;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
@ -67,29 +70,34 @@ public class EppToolVerifier {
return new EppToolVerifier(connection, clientId, superuser, true); 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); ArgumentCaptor<byte[]> params = ArgumentCaptor.forClass(byte[].class);
verify(connection, times(xmlToMatch.length)).send( verify(connection, times(expectedXmlContents.size())).send(
eq("/_dr/epptool"), eq("/_dr/epptool"),
eq(ImmutableMap.<String, Object>of()), eq(ImmutableMap.<String, Object>of()),
eq(MediaType.FORM_DATA), eq(MediaType.FORM_DATA),
params.capture()); params.capture());
List<byte[]> capturedParams = params.getAllValues(); List<byte[]> capturedParams = params.getAllValues();
assertThat(capturedParams).hasSize(xmlToMatch.length); assertThat(capturedParams).hasSize(expectedXmlContents.size());
for (int i = 0; i < xmlToMatch.length; i++) { for (int i = 0; i < expectedXmlContents.size(); i++) {
String xml = xmlToMatch[i];
byte[] capturedParam = capturedParams.get(i); byte[] capturedParam = capturedParams.get(i);
Map<String, String> map = Map<String, String> map =
Splitter.on('&') Splitter.on('&').withKeyValueSeparator('=').split(new String(capturedParam, UTF_8));
.withKeyValueSeparator('=')
.split(new String(capturedParam, UTF_8));
assertThat(map).hasSize(4); assertThat(map).hasSize(4);
assertXmlEquals( assertXmlEquals(
ToolsTestData.loadUtf8(xml), expectedXmlContents.get(i), URLDecoder.decode(map.get("xml"), UTF_8.toString()));
URLDecoder.decode(map.get("xml"), UTF_8.toString()));
assertThat(map).containsEntry("dryRun", Boolean.toString(dryRun)); assertThat(map).containsEntry("dryRun", Boolean.toString(dryRun));
assertThat(map).containsEntry("clientId", clientId); assertThat(map).containsEntry("clientId", clientId);
assertThat(map).containsEntry("superuser", Boolean.toString(superuser)); 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; 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.newContactResource;
import static google.registry.testing.DatastoreHelper.newDomainResource; import static google.registry.testing.DatastoreHelper.newDomainResource;
import static google.registry.testing.DatastoreHelper.persistActiveHost; import static google.registry.testing.DatastoreHelper.persistActiveHost;
import static google.registry.testing.DatastoreHelper.persistResource; import static google.registry.testing.DatastoreHelper.persistResource;
import static google.registry.testing.JUnitBackports.expectThrows;
import com.beust.jcommander.ParameterException; import com.beust.jcommander.ParameterException;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
@ -151,7 +154,7 @@ public class UpdateDomainCommandTest extends EppToolCommandTestCase<UpdateDomain
.asBuilder() .asBuilder()
.setStatusValues( .setStatusValues(
ImmutableSet.of( ImmutableSet.of(
StatusValue.CLIENT_RENEW_PROHIBITED, StatusValue.SERVER_UPDATE_PROHIBITED)) StatusValue.CLIENT_RENEW_PROHIBITED, StatusValue.SERVER_TRANSFER_PROHIBITED))
.setNameservers(nameservers) .setNameservers(nameservers)
.build()); .build());
@ -160,6 +163,32 @@ public class UpdateDomainCommandTest extends EppToolCommandTestCase<UpdateDomain
eppVerifier().verifySent("domain_update_set_statuses.xml"); 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 @Test
public void testFailure_duplicateDomains() throws Exception { public void testFailure_duplicateDomains() throws Exception {
thrown.expect(IllegalArgumentException.class, "Duplicate arguments found"); thrown.expect(IllegalArgumentException.class, "Duplicate arguments found");

View file

@ -14,12 +14,12 @@
package google.registry.tools.server; 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.ByteSource;
import com.google.common.io.Resources; import com.google.common.io.Resources;
import java.io.IOException; import google.registry.testing.TestDataHelper;
import java.net.URL; import java.net.URL;
import java.util.Map;
/** Utility class providing easy access to contents of the {@code testdata/} directory. */ /** Utility class providing easy access to contents of the {@code testdata/} directory. */
public final class ToolsTestData { 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). * Loads data from file in {@code tools/server/testdata/} as a UTF-8 String.
*
* @throws IOException if the file couldn't be loaded from the jar.
*/ */
public static String loadUtf8(String filename) throws IOException { public static String loadUtf8(String filename) {
return Resources.asCharSource(getUrl(filename), UTF_8).read(); 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) { 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:status s="serverHold"/>
</domain:add> </domain:add>
<domain:rem> <domain:rem>
<domain:status s="serverUpdateProhibited"/> <domain:status s="serverTransferProhibited"/>
</domain:rem> </domain:rem>
</domain:update> </domain:update>
</update> </update>