Add new parameter renew_one_year to URS (#1364)

* Add autorenews to URS (#1343)

* Add autorenews to URS

* Add autorenews to existing xml files for test cases

* Harmonize domain.get() in existing code

* Fix typo in test case name

* Modify existing test helper method to allow testing with different domain bases
This commit is contained in:
Rachel Guan 2021-10-06 20:40:43 -04:00 committed by GitHub
parent 0f4156c563
commit 34ecc6fbe7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 161 additions and 42 deletions

View file

@ -36,6 +36,7 @@ import google.registry.model.domain.DomainBase;
import google.registry.model.domain.secdns.DelegationSignerData; import google.registry.model.domain.secdns.DelegationSignerData;
import google.registry.model.eppcommon.StatusValue; import google.registry.model.eppcommon.StatusValue;
import google.registry.model.host.HostResource; import google.registry.model.host.HostResource;
import google.registry.tools.soy.DomainRenewSoyInfo;
import google.registry.tools.soy.UniformRapidSuspensionSoyInfo; import google.registry.tools.soy.UniformRapidSuspensionSoyInfo;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -43,6 +44,7 @@ import java.util.Optional;
import java.util.Set; import java.util.Set;
import javax.xml.bind.annotation.adapters.HexBinaryAdapter; import javax.xml.bind.annotation.adapters.HexBinaryAdapter;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
/** A command to suspend a domain for the Uniform Rapid Suspension process. */ /** A command to suspend a domain for the Uniform Rapid Suspension process. */
@Parameters(separators = " =", @Parameters(separators = " =",
@ -97,6 +99,13 @@ final class UniformRapidSuspensionCommand extends MutatingEppToolCommand {
description = "Flag indicating that is is an undo command, which removes locks.") description = "Flag indicating that is is an undo command, which removes locks.")
private boolean undo; private boolean undo;
@Parameter(
names = {"--renew_one_year"},
required = true,
description = "Flag indicating whether or not the domain will be renewed for a year.",
arity = 1)
private boolean renewOneYear;
/** Set of existing locks that need to be preserved during undo, sorted for nicer output. */ /** Set of existing locks that need to be preserved during undo, sorted for nicer output. */
ImmutableSortedSet<String> existingLocks; ImmutableSortedSet<String> existingLocks;
@ -114,19 +123,20 @@ final class UniformRapidSuspensionCommand extends MutatingEppToolCommand {
superuser = true; superuser = true;
DateTime now = DateTime.now(UTC); DateTime now = DateTime.now(UTC);
ImmutableSet<String> newHostsSet = ImmutableSet.copyOf(newHosts); ImmutableSet<String> newHostsSet = ImmutableSet.copyOf(newHosts);
Optional<DomainBase> domain = loadByForeignKey(DomainBase.class, domainName, now); Optional<DomainBase> domainOpt = loadByForeignKey(DomainBase.class, domainName, now);
checkArgumentPresent(domain, "Domain '%s' does not exist or is deleted", domainName); checkArgumentPresent(domainOpt, "Domain '%s' does not exist or is deleted", domainName);
DomainBase domain = domainOpt.get();
Set<String> missingHosts = Set<String> missingHosts =
difference(newHostsSet, checkResourcesExist(HostResource.class, newHosts, now)); difference(newHostsSet, checkResourcesExist(HostResource.class, newHosts, now));
checkArgument(missingHosts.isEmpty(), "Hosts do not exist: %s", missingHosts); checkArgument(missingHosts.isEmpty(), "Hosts do not exist: %s", missingHosts);
checkArgument( checkArgument(
locksToPreserve.isEmpty() || undo, locksToPreserve.isEmpty() || undo,
"Locks can only be preserved when running with --undo"); "Locks can only be preserved when running with --undo");
existingNameservers = getExistingNameservers(domain.get()); existingNameservers = getExistingNameservers(domain);
existingLocks = getExistingLocks(domain.get()); existingLocks = getExistingLocks(domain);
existingDsData = getExistingDsData(domain.get()); existingDsData = getExistingDsData(domain);
removeStatuses = removeStatuses =
(hasClientHold(domain.get()) && !undo) (hasClientHold(domain) && !undo)
? ImmutableSet.of(StatusValue.CLIENT_HOLD.getXmlName()) ? ImmutableSet.of(StatusValue.CLIENT_HOLD.getXmlName())
: ImmutableSet.of(); : ImmutableSet.of();
ImmutableSet<String> statusesToApply; ImmutableSet<String> statusesToApply;
@ -138,6 +148,25 @@ final class UniformRapidSuspensionCommand extends MutatingEppToolCommand {
} else { } else {
statusesToApply = URS_LOCKS; statusesToApply = URS_LOCKS;
} }
// trigger renew flow
if (renewOneYear) {
setSoyTemplate(DomainRenewSoyInfo.getInstance(), DomainRenewSoyInfo.RENEWDOMAIN);
addSoyRecord(
CLIENT_ID,
new SoyMapData(
"domainName",
domain.getDomainName(),
"expirationDate",
domain
.getRegistrationExpirationTime()
.toString(DateTimeFormat.forPattern("YYYY-MM-dd")),
// period is the number of years to renew the registration for
"period",
String.valueOf(1)));
}
// trigger update flow
setSoyTemplate( setSoyTemplate(
UniformRapidSuspensionSoyInfo.getInstance(), UniformRapidSuspensionSoyInfo.getInstance(),
UniformRapidSuspensionSoyInfo.UNIFORMRAPIDSUSPENSION); UniformRapidSuspensionSoyInfo.UNIFORMRAPIDSUSPENSION);

View file

@ -23,12 +23,15 @@ import static google.registry.testing.DatabaseHelper.persistResource;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
import com.beust.jcommander.ParameterException; import com.beust.jcommander.ParameterException;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import google.registry.model.domain.DomainBase;
import google.registry.model.domain.secdns.DelegationSignerData; import google.registry.model.domain.secdns.DelegationSignerData;
import google.registry.model.eppcommon.StatusValue; import google.registry.model.eppcommon.StatusValue;
import google.registry.model.host.HostResource; import google.registry.model.host.HostResource;
import google.registry.persistence.VKey; import google.registry.persistence.VKey;
import javax.xml.bind.annotation.adapters.HexBinaryAdapter; import javax.xml.bind.annotation.adapters.HexBinaryAdapter;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -40,6 +43,8 @@ class UniformRapidSuspensionCommandTest
private HostResource ns2; private HostResource ns2;
private HostResource urs1; private HostResource urs1;
private HostResource urs2; private HostResource urs2;
private DomainBase defaultDomainBase;
private ImmutableSet<DelegationSignerData> defaultDsData;
@BeforeEach @BeforeEach
void beforeEach() { void beforeEach() {
@ -50,32 +55,36 @@ class UniformRapidSuspensionCommandTest
ns2 = persistActiveHost("ns2.example.com"); ns2 = persistActiveHost("ns2.example.com");
urs1 = persistActiveHost("urs1.example.com"); urs1 = persistActiveHost("urs1.example.com");
urs2 = persistActiveHost("urs2.example.com"); urs2 = persistActiveHost("urs2.example.com");
defaultDomainBase = newDomainBase("evil.tld");
defaultDsData =
ImmutableSet.of(
DelegationSignerData.create(1, 2, 3, new HexBinaryAdapter().unmarshal("dead")),
DelegationSignerData.create(4, 5, 6, new HexBinaryAdapter().unmarshal("beef")));
} }
private void persistDomainWithHosts(HostResource... hosts) { private void persistDomainWithHosts(
DomainBase domainBase, ImmutableSet<DelegationSignerData> dsData, HostResource... hosts) {
ImmutableSet.Builder<VKey<HostResource>> hostRefs = new ImmutableSet.Builder<>(); ImmutableSet.Builder<VKey<HostResource>> hostRefs = new ImmutableSet.Builder<>();
for (HostResource host : hosts) { for (HostResource host : hosts) {
hostRefs.add(host.createVKey()); hostRefs.add(host.createVKey());
} }
persistResource(newDomainBase("evil.tld").asBuilder() persistResource(
.setNameservers(hostRefs.build()) domainBase.asBuilder().setNameservers(hostRefs.build()).setDsData(dsData).build());
.setDsData(ImmutableSet.of(
DelegationSignerData.create(1, 2, 3, new HexBinaryAdapter().unmarshal("dead")),
DelegationSignerData.create(4, 5, 6, new HexBinaryAdapter().unmarshal("beef"))))
.build());
} }
@Test @Test
void testCommand_addsLocksReplacesHostsAndDsDataPrintsUndo() throws Exception { void testCommand_addsLocksReplacesHostsAndDsDataPrintsUndo() throws Exception {
persistDomainWithHosts(ns1, ns2); persistDomainWithHosts(defaultDomainBase, defaultDsData, ns1, ns2);
runCommandForced( runCommandForced(
"--domain_name=evil.tld", "--domain_name=evil.tld",
"--hosts=urs1.example.com,urs2.example.com", "--hosts=urs1.example.com,urs2.example.com",
"--dsdata=1 1 1 abcd"); "--dsdata=1 1 1 abcd",
"--renew_one_year=false");
eppVerifier eppVerifier
.expectRegistrarId("CharlestonRoad") .expectRegistrarId("CharlestonRoad")
.expectSuperuser() .expectSuperuser()
.verifySent("uniform_rapid_suspension.xml"); .verifySent("uniform_rapid_suspension.xml")
.verifyNoMoreSent();
assertInStdout("uniform_rapid_suspension --undo"); assertInStdout("uniform_rapid_suspension --undo");
assertInStdout("--domain_name evil.tld"); assertInStdout("--domain_name evil.tld");
assertInStdout("--hosts ns1.example.com,ns2.example.com"); assertInStdout("--hosts ns1.example.com,ns2.example.com");
@ -86,12 +95,16 @@ class UniformRapidSuspensionCommandTest
@Test @Test
void testCommand_respectsExistingHost() throws Exception { void testCommand_respectsExistingHost() throws Exception {
persistDomainWithHosts(urs2, ns1); persistDomainWithHosts(defaultDomainBase, defaultDsData, urs2, ns1);
runCommandForced("--domain_name=evil.tld", "--hosts=urs1.example.com,urs2.example.com"); runCommandForced(
"--domain_name=evil.tld",
"--hosts=urs1.example.com,urs2.example.com",
"--renew_one_year=false");
eppVerifier eppVerifier
.expectRegistrarId("CharlestonRoad") .expectRegistrarId("CharlestonRoad")
.expectSuperuser() .expectSuperuser()
.verifySent("uniform_rapid_suspension_existing_host.xml"); .verifySent("uniform_rapid_suspension_existing_host.xml")
.verifyNoMoreSent();
assertInStdout("uniform_rapid_suspension --undo "); assertInStdout("uniform_rapid_suspension --undo ");
assertInStdout("--domain_name evil.tld"); assertInStdout("--domain_name evil.tld");
assertInStdout("--hosts ns1.example.com,urs2.example.com"); assertInStdout("--hosts ns1.example.com,urs2.example.com");
@ -101,8 +114,11 @@ class UniformRapidSuspensionCommandTest
@Test @Test
void testCommand_generatesUndoForUndelegatedDomain() throws Exception { void testCommand_generatesUndoForUndelegatedDomain() throws Exception {
persistActiveDomain("evil.tld"); persistActiveDomain("evil.tld");
runCommandForced("--domain_name=evil.tld", "--hosts=urs1.example.com,urs2.example.com"); runCommandForced(
eppVerifier.verifySentAny(); "--domain_name=evil.tld",
"--hosts=urs1.example.com,urs2.example.com",
"--renew_one_year=false");
eppVerifier.verifySentAny().verifyNoMoreSent();
assertInStdout("uniform_rapid_suspension --undo"); assertInStdout("uniform_rapid_suspension --undo");
assertInStdout("--domain_name evil.tld"); assertInStdout("--domain_name evil.tld");
assertNotInStdout("--locks_to_preserve"); assertNotInStdout("--locks_to_preserve");
@ -114,8 +130,8 @@ class UniformRapidSuspensionCommandTest
newDomainBase("evil.tld").asBuilder() newDomainBase("evil.tld").asBuilder()
.addStatusValue(StatusValue.SERVER_DELETE_PROHIBITED) .addStatusValue(StatusValue.SERVER_DELETE_PROHIBITED)
.build()); .build());
runCommandForced("--domain_name=evil.tld"); runCommandForced("--domain_name=evil.tld", "--renew_one_year=false");
eppVerifier.verifySentAny(); eppVerifier.verifySentAny().verifyNoMoreSent();
assertInStdout("uniform_rapid_suspension --undo"); assertInStdout("uniform_rapid_suspension --undo");
assertInStdout("--domain_name evil.tld"); assertInStdout("--domain_name evil.tld");
assertInStdout("--locks_to_preserve serverDeleteProhibited"); assertInStdout("--locks_to_preserve serverDeleteProhibited");
@ -133,11 +149,13 @@ class UniformRapidSuspensionCommandTest
runCommandForced( runCommandForced(
"--domain_name=evil.tld", "--domain_name=evil.tld",
"--hosts=urs1.example.com,urs2.example.com", "--hosts=urs1.example.com,urs2.example.com",
"--dsdata=1 1 1 abcd"); "--dsdata=1 1 1 abcd",
"--renew_one_year=false");
eppVerifier eppVerifier
.expectRegistrarId("CharlestonRoad") .expectRegistrarId("CharlestonRoad")
.expectSuperuser() .expectSuperuser()
.verifySent("uniform_rapid_suspension_with_client_hold.xml"); .verifySent("uniform_rapid_suspension_with_client_hold.xml")
.verifyNoMoreSent();
assertInStdout("uniform_rapid_suspension --undo"); assertInStdout("uniform_rapid_suspension --undo");
assertInStdout("--domain_name evil.tld"); assertInStdout("--domain_name evil.tld");
assertInStdout("--hosts ns1.example.com,ns2.example.com"); assertInStdout("--hosts ns1.example.com,ns2.example.com");
@ -146,54 +164,62 @@ class UniformRapidSuspensionCommandTest
@Test @Test
void testUndo_removesLocksReplacesHostsAndDsData() throws Exception { void testUndo_removesLocksReplacesHostsAndDsData() throws Exception {
persistDomainWithHosts(urs1, urs2); persistDomainWithHosts(defaultDomainBase, defaultDsData, urs1, urs2);
runCommandForced( runCommandForced(
"--domain_name=evil.tld", "--undo", "--hosts=ns1.example.com,ns2.example.com"); "--domain_name=evil.tld",
"--undo",
"--hosts=ns1.example.com,ns2.example.com",
"--renew_one_year=false");
eppVerifier eppVerifier
.expectRegistrarId("CharlestonRoad") .expectRegistrarId("CharlestonRoad")
.expectSuperuser() .expectSuperuser()
.verifySent("uniform_rapid_suspension_undo.xml"); .verifySent("uniform_rapid_suspension_undo.xml")
.verifyNoMoreSent();
assertNotInStdout("--undo"); // Undo shouldn't print a new undo command. assertNotInStdout("--undo"); // Undo shouldn't print a new undo command.
} }
@Test @Test
void testUndo_respectsLocksToPreserveFlag() throws Exception { void testUndo_respectsLocksToPreserveFlag() throws Exception {
persistDomainWithHosts(urs1, urs2); persistDomainWithHosts(defaultDomainBase, defaultDsData, urs1, urs2);
runCommandForced( runCommandForced(
"--domain_name=evil.tld", "--domain_name=evil.tld",
"--undo", "--undo",
"--locks_to_preserve=serverDeleteProhibited", "--locks_to_preserve=serverDeleteProhibited",
"--hosts=ns1.example.com,ns2.example.com"); "--hosts=ns1.example.com,ns2.example.com",
"--renew_one_year=false");
eppVerifier eppVerifier
.expectRegistrarId("CharlestonRoad") .expectRegistrarId("CharlestonRoad")
.expectSuperuser() .expectSuperuser()
.verifySent("uniform_rapid_suspension_undo_preserve.xml"); .verifySent("uniform_rapid_suspension_undo_preserve.xml")
.verifyNoMoreSent();
assertNotInStdout("--undo"); // Undo shouldn't print a new undo command. assertNotInStdout("--undo"); // Undo shouldn't print a new undo command.
} }
@Test @Test
void testUndo_restoresClientHolds() throws Exception { void testUndo_restoresClientHolds() throws Exception {
persistDomainWithHosts(urs1, urs2); persistDomainWithHosts(defaultDomainBase, defaultDsData, urs1, urs2);
runCommandForced( runCommandForced(
"--domain_name=evil.tld", "--domain_name=evil.tld",
"--undo", "--undo",
"--hosts=ns1.example.com,ns2.example.com", "--hosts=ns1.example.com,ns2.example.com",
"--restore_client_hold"); "--restore_client_hold",
"--renew_one_year=false");
eppVerifier eppVerifier
.expectRegistrarId("CharlestonRoad") .expectRegistrarId("CharlestonRoad")
.expectSuperuser() .expectSuperuser()
.verifySent("uniform_rapid_suspension_undo_client_hold.xml"); .verifySent("uniform_rapid_suspension_undo_client_hold.xml")
.verifyNoMoreSent();
assertNotInStdout("--undo"); // Undo shouldn't print a new undo command. assertNotInStdout("--undo"); // Undo shouldn't print a new undo command.
} }
@Test @Test
void testAutorenews_setToFalsebyDefault() throws Exception { void testAutorenews_setToFalseByDefault() throws Exception {
persistResource( persistResource(
newDomainBase("evil.tld") newDomainBase("evil.tld")
.asBuilder() .asBuilder()
.addStatusValue(StatusValue.SERVER_DELETE_PROHIBITED) .addStatusValue(StatusValue.SERVER_DELETE_PROHIBITED)
.build()); .build());
runCommandForced("--domain_name=evil.tld"); runCommandForced("--domain_name=evil.tld", "--renew_one_year=false");
eppVerifier.verifySentAny(); eppVerifier.verifySentAny();
assertInStdout("<superuser:autorenews>false</superuser:autorenews>"); assertInStdout("<superuser:autorenews>false</superuser:autorenews>");
} }
@ -209,11 +235,57 @@ class UniformRapidSuspensionCommandTest
"--domain_name=evil.tld", "--domain_name=evil.tld",
"--undo", "--undo",
"--hosts=ns1.example.com,ns2.example.com", "--hosts=ns1.example.com,ns2.example.com",
"--restore_client_hold"); "--restore_client_hold",
"--renew_one_year=false");
eppVerifier.verifySentAny(); eppVerifier.verifySentAny();
assertInStdout("<superuser:autorenews>true</superuser:autorenews>"); assertInStdout("<superuser:autorenews>true</superuser:autorenews>");
} }
@Test
void testRenewOneYear_renewFlowIsTriggered() throws Exception {
// this test case was written based on an existing test case,
// testUndo_removesLocksReplacesHostsAndDsData() but two things were modified to test the
// renew workflow, which were:
// 1) a different domain that contains creation time and expiration time
// 2) renew_one_year is set to true
persistDomainWithHosts(
newDomainBase("evil.tld")
.asBuilder()
.setCreationTimeForTest(DateTime.parse("2021-10-01T05:01:11Z"))
.setRegistrationExpirationTime(DateTime.parse("2022-10-01T05:01:11Z"))
.setPersistedCurrentSponsorRegistrarId("CharlestonRoad")
.build(),
defaultDsData,
urs1,
urs2);
runCommandForced(
"--domain_name=evil.tld",
"--undo",
"--hosts=ns1.example.com,ns2.example.com",
"--renew_one_year=true");
// verify if renew flow is triggered
eppVerifier
.expectRegistrarId("CharlestonRoad")
.expectSuperuser()
.verifySent(
"domain_renew.xml",
ImmutableMap.of("DOMAIN", "evil.tld", "EXPDATE", "2022-10-01", "YEARS", "1"));
// verify if update flow is triggered
eppVerifier
.expectRegistrarId("CharlestonRoad")
.expectSuperuser()
.verifySent("uniform_rapid_suspension_undo.xml")
.verifyNoMoreSent();
assertNotInStdout("--undo"); // Undo shouldn't print a new undo command.
// verify that no other flows are triggered after the renew and update flows
eppVerifier.verifyNoMoreSent();
}
@Test @Test
void testFailure_locksToPreserveWithoutUndo() { void testFailure_locksToPreserveWithoutUndo() {
persistActiveDomain("evil.tld"); persistActiveDomain("evil.tld");
@ -222,7 +294,9 @@ class UniformRapidSuspensionCommandTest
IllegalArgumentException.class, IllegalArgumentException.class,
() -> () ->
runCommandForced( runCommandForced(
"--domain_name=evil.tld", "--locks_to_preserve=serverDeleteProhibited")); "--domain_name=evil.tld",
"--locks_to_preserve=serverDeleteProhibited",
"--renew_one_year=false"));
assertThat(thrown).hasMessageThat().contains("--undo"); assertThat(thrown).hasMessageThat().contains("--undo");
} }
@ -232,17 +306,29 @@ class UniformRapidSuspensionCommandTest
ParameterException thrown = ParameterException thrown =
assertThrows( assertThrows(
ParameterException.class, ParameterException.class,
() -> runCommandForced("--hosts=urs1.example.com,urs2.example.com")); () ->
runCommandForced(
"--hosts=urs1.example.com,urs2.example.com", "--renew_one_year=false"));
assertThat(thrown).hasMessageThat().contains("--domain_name"); assertThat(thrown).hasMessageThat().contains("--domain_name");
} }
@Test
void testFailure_renewOneYearRequired() {
persistActiveDomain("evil.tld");
ParameterException thrown =
assertThrows(ParameterException.class, () -> runCommandForced("--domain_name=evil.tld"));
assertThat(thrown).hasMessageThat().contains("--renew_one_year");
}
@Test @Test
void testFailure_extraFieldInDsData() { void testFailure_extraFieldInDsData() {
persistActiveDomain("evil.tld"); persistActiveDomain("evil.tld");
IllegalArgumentException thrown = IllegalArgumentException thrown =
assertThrows( assertThrows(
IllegalArgumentException.class, IllegalArgumentException.class,
() -> runCommandForced("--domain_name=evil.tld", "--dsdata=1 1 1 abc 1")); () ->
runCommandForced(
"--domain_name=evil.tld", "--dsdata=1 1 1 abc 1", "--renew_one_year=false"));
assertThat(thrown) assertThat(thrown)
.hasMessageThat() .hasMessageThat()
.contains("dsRecord 1 1 1 abc 1 should have 4 parts, but has 5"); .contains("dsRecord 1 1 1 abc 1 should have 4 parts, but has 5");
@ -254,7 +340,9 @@ class UniformRapidSuspensionCommandTest
IllegalArgumentException thrown = IllegalArgumentException thrown =
assertThrows( assertThrows(
IllegalArgumentException.class, IllegalArgumentException.class,
() -> runCommandForced("--domain_name=evil.tld", "--dsdata=1 1 1")); () ->
runCommandForced(
"--domain_name=evil.tld", "--dsdata=1 1 1", "--renew_one_year=false"));
assertThat(thrown).hasMessageThat().contains("dsRecord 1 1 1 should have 4 parts, but has 3"); assertThat(thrown).hasMessageThat().contains("dsRecord 1 1 1 should have 4 parts, but has 3");
} }
@ -264,7 +352,9 @@ class UniformRapidSuspensionCommandTest
IllegalArgumentException thrown = IllegalArgumentException thrown =
assertThrows( assertThrows(
IllegalArgumentException.class, IllegalArgumentException.class,
() -> runCommandForced("--domain_name=evil.tld", "--dsdata=1,2,3")); () ->
runCommandForced(
"--domain_name=evil.tld", "--dsdata=1,2,3", "--renew_one_year=false"));
assertThat(thrown).hasMessageThat().contains("dsRecord 1 should have 4 parts, but has 1"); assertThat(thrown).hasMessageThat().contains("dsRecord 1 should have 4 parts, but has 1");
} }
} }