// 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.flows.domain; 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.loadByUniqueId; import static google.registry.testing.DatastoreHelper.assertBillingEvents; import static google.registry.testing.DatastoreHelper.assertNoBillingEvents; import static google.registry.testing.DatastoreHelper.createTld; import static google.registry.testing.DatastoreHelper.getOnlyHistoryEntryOfType; import static google.registry.testing.DatastoreHelper.newDomainResource; import static google.registry.testing.DatastoreHelper.persistActiveContact; import static google.registry.testing.DatastoreHelper.persistActiveDomain; import static google.registry.testing.DatastoreHelper.persistActiveHost; import static google.registry.testing.DatastoreHelper.persistActiveSubordinateHost; import static google.registry.testing.DatastoreHelper.persistDeletedDomain; 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.TaskQueueHelper.assertDnsTasksEnqueued; import static org.joda.money.CurrencyUnit.USD; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.googlecode.objectify.Key; import com.googlecode.objectify.Ref; import google.registry.flows.EppException.UnimplementedExtensionException; import google.registry.flows.EppRequestSource; import google.registry.flows.ResourceCreateOrMutateFlow.OnlyToolCanPassMetadataException; import google.registry.flows.ResourceFlowTestCase; import google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException; import google.registry.flows.ResourceMutateFlow.ResourceToMutateDoesNotExistException; import google.registry.flows.ResourceUpdateFlow.AddRemoveSameValueEppException; import google.registry.flows.ResourceUpdateFlow.ResourceHasClientUpdateProhibitedException; import google.registry.flows.ResourceUpdateFlow.StatusNotClientSettableException; import google.registry.flows.SingleResourceFlow.ResourceStatusProhibitsOperationException; import google.registry.flows.domain.BaseDomainUpdateFlow.EmptySecDnsUpdateException; import google.registry.flows.domain.BaseDomainUpdateFlow.MaxSigLifeChangeNotSupportedException; import google.registry.flows.domain.BaseDomainUpdateFlow.SecDnsAllUsageException; import google.registry.flows.domain.BaseDomainUpdateFlow.UrgentAttributeNotSupportedException; import google.registry.flows.domain.DomainFlowUtils.DuplicateContactForRoleException; import google.registry.flows.domain.DomainFlowUtils.LinkedResourceInPendingDeleteProhibitsOperationException; import google.registry.flows.domain.DomainFlowUtils.LinkedResourcesDoNotExistException; import google.registry.flows.domain.DomainFlowUtils.MissingAdminContactException; import google.registry.flows.domain.DomainFlowUtils.MissingContactTypeException; import google.registry.flows.domain.DomainFlowUtils.MissingTechnicalContactException; import google.registry.flows.domain.DomainFlowUtils.NameserversNotAllowedException; import google.registry.flows.domain.DomainFlowUtils.NotAuthorizedForTldException; import google.registry.flows.domain.DomainFlowUtils.RegistrantNotAllowedException; import google.registry.flows.domain.DomainFlowUtils.TooManyDsRecordsException; import google.registry.flows.domain.DomainFlowUtils.TooManyNameserversException; import google.registry.model.billing.BillingEvent; import google.registry.model.billing.BillingEvent.Reason; import google.registry.model.contact.ContactResource; import google.registry.model.domain.DesignatedContact; import google.registry.model.domain.DesignatedContact.Type; import google.registry.model.domain.DomainResource; import google.registry.model.domain.GracePeriod; import google.registry.model.domain.rgp.GracePeriodStatus; import google.registry.model.domain.secdns.DelegationSignerData; import google.registry.model.eppcommon.StatusValue; import google.registry.model.host.HostResource; import google.registry.model.registrar.Registrar; import google.registry.model.registry.Registry; import google.registry.model.reporting.HistoryEntry; import org.joda.money.Money; import org.joda.time.DateTime; import org.junit.Before; import org.junit.Test; /** Unit tests for {@link DomainUpdateFlow}. */ public class DomainUpdateFlowTest extends ResourceFlowTestCase { private static final DelegationSignerData SOME_DSDATA = DelegationSignerData.create(1, 2, 3, new byte[]{0, 1, 2}); ContactResource sh8013Contact; ContactResource mak21Contact; ContactResource unusedContact; HistoryEntry historyEntryDomainCreate; public DomainUpdateFlowTest() { // Note that "domain_update.xml" tests adding and removing the same contact type. setEppInput("domain_update.xml"); } @Before public void initDomainTest() { createTld("tld"); } private void persistReferencedEntities() { for (int i = 1; i <= 14; ++i) { persistActiveHost(String.format("ns%d.example.foo", i)); } sh8013Contact = persistActiveContact("sh8013"); mak21Contact = persistActiveContact("mak21"); unusedContact = persistActiveContact("unused"); } private DomainResource persistDomain() throws Exception { HostResource host = loadByUniqueId(HostResource.class, "ns1.example.foo", clock.nowUtc()); DomainResource domain = persistResource( newDomainResource(getUniqueIdFromCommand()).asBuilder() .setContacts(ImmutableSet.of( DesignatedContact.create(Type.TECH, Ref.create(sh8013Contact)), DesignatedContact.create(Type.ADMIN, Ref.create(unusedContact)))) .setNameservers(ImmutableSet.of(Ref.create(host))) .build()); historyEntryDomainCreate = persistResource( new HistoryEntry.Builder() .setType(HistoryEntry.Type.DOMAIN_CREATE) .setParent(domain) .build()); clock.advanceOneMilli(); return domain; } private void doSuccessfulTest() throws Exception { assertTransactionalFlow(true); runFlowAssertResponse(readFile("domain_update_response.xml")); // Check that the domain was updated. These values came from the xml. assertAboutDomains().that(reloadResourceByUniqueId()) .hasStatusValue(StatusValue.CLIENT_HOLD).and() .hasAuthInfoPwd("2BARfoo").and() .hasOneHistoryEntryEachOfTypes( HistoryEntry.Type.DOMAIN_CREATE, HistoryEntry.Type.DOMAIN_UPDATE); assertNoBillingEvents(); assertDnsTasksEnqueued("example.tld"); } @Test public void testDryRun() throws Exception { persistReferencedEntities(); persistDomain(); dryRunFlowAssertResponse(readFile("domain_update_response.xml")); } @Test public void testSuccess() throws Exception { persistReferencedEntities(); persistDomain(); doSuccessfulTest(); } private void doSunrushAddTest( BillingEvent.OneTime sunrushAddBillingEvent, UserPrivileges userPrivileges, DateTime addExpirationTime) throws Exception { // The billing event for the original sunrush add should already exist; check for it now to // avoid a confusing assertBillingEvents() later if it doesn't exist. assertBillingEvents(sunrushAddBillingEvent); runFlowAssertResponse( CommitMode.LIVE, userPrivileges, readFile("domain_update_response.xml")); // Verify that the domain now has the new nameserver and is in the add grace period. DomainResource resource = reloadResourceByUniqueId(); HistoryEntry historyEntryDomainUpdate = getOnlyHistoryEntryOfType(resource, HistoryEntry.Type.DOMAIN_UPDATE); assertThat(resource.getNameservers()).containsExactly( Ref.create( loadByUniqueId(HostResource.class, "ns2.example.foo", clock.nowUtc()))); BillingEvent.OneTime regularAddBillingEvent = new BillingEvent.OneTime.Builder() .setReason(Reason.CREATE) .setTargetId("example.tld") .setClientId("TheRegistrar") .setCost(Money.of(USD, 50)) .setPeriodYears(4) .setEventTime(clock.nowUtc()) .setBillingTime(addExpirationTime) .setParent(historyEntryDomainUpdate) .build(); assertBillingEvents( sunrushAddBillingEvent, // There should be a cancellation for the original sunrush add billing event. new BillingEvent.Cancellation.Builder() .setReason(Reason.CREATE) .setTargetId("example.tld") .setClientId("TheRegistrar") .setEventTime(clock.nowUtc()) .setBillingTime(sunrushAddBillingEvent.getBillingTime()) .setOneTimeEventRef(Ref.create(sunrushAddBillingEvent)) .setParent(historyEntryDomainUpdate) .build(), regularAddBillingEvent); assertGracePeriods( resource.getGracePeriods(), ImmutableMap.of( GracePeriod.create(GracePeriodStatus.ADD, addExpirationTime, "TheRegistrar", null), regularAddBillingEvent)); } private BillingEvent.OneTime getSunrushAddBillingEvent(DateTime billingTime) { return new BillingEvent.OneTime.Builder() .setReason(Reason.CREATE) .setTargetId("example.tld") .setClientId("TheRegistrar") .setCost(Money.of(USD, 50)) .setPeriodYears(4) .setEventTime(billingTime.minusDays(30)) .setBillingTime(billingTime) .setParent(historyEntryDomainCreate) .build(); } @Test public void testSuccess_sunrushAddGracePeriod() throws Exception { setEppInput("domain_update_add_nameserver.xml"); persistReferencedEntities(); persistDomain(); BillingEvent.OneTime sunrushAddBillingEvent = persistResource( getSunrushAddBillingEvent(clock.nowUtc().plusDays(20))); // Modify domain so it has no nameservers and is in the sunrush add grace period. persistResource( reloadResourceByUniqueId().asBuilder() .setNameservers(null) .addGracePeriod(GracePeriod.forBillingEvent( GracePeriodStatus.SUNRUSH_ADD, sunrushAddBillingEvent)) .build()); clock.advanceOneMilli(); doSunrushAddTest( sunrushAddBillingEvent, UserPrivileges.NORMAL, clock.nowUtc().plus(Registry.get("tld").getAddGracePeriodLength())); } @Test public void testSuccess_truncatedSunrushAddGracePeriod() throws Exception { setEppInput("domain_update_add_nameserver.xml"); persistReferencedEntities(); persistDomain(); DateTime billingTime = clock.nowUtc().plusDays(2); BillingEvent.OneTime sunrushAddBillingEvent = persistResource(getSunrushAddBillingEvent(billingTime)); // Modify domain so it has no nameservers and is in the sunrush add grace period, but make sure // that grace period only has a couple days left so that the resultant add grace period will get // truncated. persistResource( reloadResourceByUniqueId().asBuilder() .setNameservers(null) .addGracePeriod(GracePeriod.forBillingEvent( GracePeriodStatus.SUNRUSH_ADD, sunrushAddBillingEvent)) .build()); clock.advanceOneMilli(); doSunrushAddTest(sunrushAddBillingEvent, UserPrivileges.NORMAL, billingTime); } @Test public void testSuccess_sunrushAddGracePeriodRemoveServerHold() throws Exception { setEppInput("domain_update_remove_server_hold.xml"); persistReferencedEntities(); persistDomain(); BillingEvent.OneTime sunrushAddBillingEvent = persistResource(getSunrushAddBillingEvent(clock.nowUtc().plusDays(20))); // Modify domain so that it is in the sunrush add grace period, has nameservers, but has a // serverHold on it. persistResource( reloadResourceByUniqueId().asBuilder() .setNameservers(ImmutableSet.of(Ref.create( loadByUniqueId(HostResource.class, "ns2.example.foo", clock.nowUtc())))) .addGracePeriod(GracePeriod.forBillingEvent( GracePeriodStatus.SUNRUSH_ADD, sunrushAddBillingEvent)) .addStatusValue(StatusValue.SERVER_HOLD) .build()); clock.advanceOneMilli(); doSunrushAddTest( sunrushAddBillingEvent, UserPrivileges.SUPERUSER, clock.nowUtc().plus(Registry.get("tld").getAddGracePeriodLength())); } @Test public void testSuccess_sunrushAddGracePeriodRemoveClientHold() throws Exception { setEppInput("domain_update_remove_client_hold.xml"); persistReferencedEntities(); persistDomain(); BillingEvent.OneTime sunrushAddBillingEvent = persistResource(getSunrushAddBillingEvent(clock.nowUtc().plusDays(20))); // Modify domain so that it is in the sunrush add grace period, has nameservers, but has a // serverHold on it. persistResource( reloadResourceByUniqueId().asBuilder() .setNameservers(ImmutableSet.of(Ref.create( loadByUniqueId(HostResource.class, "ns2.example.foo", clock.nowUtc())))) .addGracePeriod(GracePeriod.forBillingEvent( GracePeriodStatus.SUNRUSH_ADD, sunrushAddBillingEvent)) .addStatusValue(StatusValue.CLIENT_HOLD) .build()); clock.advanceOneMilli(); doSunrushAddTest( sunrushAddBillingEvent, UserPrivileges.NORMAL, clock.nowUtc().plus(Registry.get("tld").getAddGracePeriodLength())); } @Test public void testSuccess_sunrushAddGracePeriodRemainsBecauseOfServerHold() throws Exception { setEppInput("domain_update_add_nameserver.xml"); persistReferencedEntities(); persistDomain(); DateTime endOfGracePeriod = clock.nowUtc().plusDays(20); BillingEvent.OneTime sunrushAddBillingEvent = persistResource(getSunrushAddBillingEvent(endOfGracePeriod)); // Modify domain so it has no nameservers and is in the sunrush add grace period, but also has a // server hold on it that will prevent the sunrush add grace period from being removed. persistResource( reloadResourceByUniqueId().asBuilder() .setNameservers(null) .addGracePeriod(GracePeriod.forBillingEvent( GracePeriodStatus.SUNRUSH_ADD, sunrushAddBillingEvent)) .addStatusValue(StatusValue.SERVER_HOLD) .build()); clock.advanceOneMilli(); runFlowAssertResponse( CommitMode.LIVE, UserPrivileges.NORMAL, readFile("domain_update_response.xml")); // Verify that the domain is still in the sunrush add grace period. assertGracePeriods( reloadResourceByUniqueId().getGracePeriods(), ImmutableMap.of( GracePeriod.create( GracePeriodStatus.SUNRUSH_ADD, endOfGracePeriod, "TheRegistrar", null), sunrushAddBillingEvent)); } private void modifyDomainToHave13Nameservers() throws Exception { ImmutableSet.Builder> nameservers = new ImmutableSet.Builder<>(); for (int i = 1; i < 15; i++) { if (i != 2) { // Skip 2 since that's the one that the tests will add. nameservers.add(Ref.create(loadByUniqueId( HostResource.class, String.format("ns%d.example.foo", i), clock.nowUtc()))); } } persistResource( reloadResourceByUniqueId().asBuilder() .setNameservers(nameservers.build()) .build()); clock.advanceOneMilli(); } @Test public void testSuccess_maxNumberOfNameservers() throws Exception { persistReferencedEntities(); persistDomain(); // Modify domain to have 13 nameservers. We will then remove one and add one in the test. modifyDomainToHave13Nameservers(); doSuccessfulTest(); } @Test public void testSuccess_addAndRemoveLargeNumberOfNameserversAndContacts() throws Exception { persistReferencedEntities(); persistDomain(); setEppInput("domain_update_max_everything.xml"); // Create 26 hosts and 8 contacts. Start the domain with half of them. ImmutableSet.Builder> nameservers = new ImmutableSet.Builder<>(); for (int i = 0; i < 26; i++) { HostResource host = persistActiveHost(String.format("max_test_%d.example.tld", i)); if (i < 13) { nameservers.add(Ref.create(host)); } } ImmutableList.Builder contactsBuilder = new ImmutableList.Builder<>(); for (int i = 0; i < 8; i++) { contactsBuilder.add( DesignatedContact.create( DesignatedContact.Type.values()[i % 4], Ref.create(persistActiveContact(String.format("max_test_%d", i))))); } ImmutableList contacts = contactsBuilder.build(); persistResource( reloadResourceByUniqueId().asBuilder() .setNameservers(nameservers.build()) .setContacts(ImmutableSet.copyOf(contacts.subList(0, 3))) .setRegistrant(contacts.get(3).getContactRef()) .build()); clock.advanceOneMilli(); assertTransactionalFlow(true); runFlowAssertResponse(readFile("domain_update_response.xml")); DomainResource domain = reloadResourceByUniqueId(); assertAboutDomains().that(domain) .hasOneHistoryEntryEachOfTypes( HistoryEntry.Type.DOMAIN_CREATE, HistoryEntry.Type.DOMAIN_UPDATE); assertThat(domain.getNameservers()).hasSize(13); // getContacts does not return contacts of type REGISTRANT, so check these separately. assertThat(domain.getContacts()).hasSize(3); assertThat(domain.getRegistrant().get().getContactId()).isEqualTo("max_test_7"); assertNoBillingEvents(); assertDnsTasksEnqueued("example.tld"); } @Test public void testSuccess_metadata() throws Exception { eppRequestSource = EppRequestSource.TOOL; setEppInput("domain_update_metadata.xml"); persistReferencedEntities(); persistDomain(); runFlow(); DomainResource domain = reloadResourceByUniqueId(); assertAboutDomains().that(domain) .hasOneHistoryEntryEachOfTypes( HistoryEntry.Type.DOMAIN_CREATE, HistoryEntry.Type.DOMAIN_UPDATE); assertAboutHistoryEntries() .that(getOnlyHistoryEntryOfType(domain, HistoryEntry.Type.DOMAIN_UPDATE)) .hasMetadataReason("domain-update-test").and() .hasMetadataRequestedByRegistrar(true); } @Test public void testSuccess_metadataNotFromTool() throws Exception { thrown.expect(OnlyToolCanPassMetadataException.class); setEppInput("domain_update_metadata.xml"); persistReferencedEntities(); persistDomain(); runFlow(); } @Test public void testSuccess_removeContact() throws Exception { setEppInput("domain_update_remove_contact.xml"); persistReferencedEntities(); persistDomain(); doSuccessfulTest(); } @Test public void testSuccess_addAndRemoveSubordinateHostNameservers() throws Exception { // Test that operations involving subordinate hosts as nameservers do not change the subordinate // host relationship itself. setEppInput("domain_update_subordinate_hosts.xml"); persistReferencedEntities(); DomainResource domain = persistDomain(); HostResource existingHost = persistActiveSubordinateHost("ns1.example.tld", domain); HostResource addedHost = persistActiveSubordinateHost("ns2.example.tld", domain); domain = persistResource(domain.asBuilder() .addSubordinateHost("ns1.example.tld") .addSubordinateHost("ns2.example.tld") .setNameservers(ImmutableSet.of(Ref.create( loadByUniqueId(HostResource.class, "ns1.example.tld", clock.nowUtc())))) .build()); clock.advanceOneMilli(); assertTransactionalFlow(true); runFlowAssertResponse(readFile("domain_update_response.xml")); domain = reloadResourceByUniqueId(); assertThat(domain.getNameservers()).containsExactly(Ref.create(addedHost)); assertThat(domain.getSubordinateHosts()).containsExactly("ns1.example.tld", "ns2.example.tld"); existingHost = loadByUniqueId(HostResource.class, "ns1.example.tld", clock.nowUtc()); addedHost = loadByUniqueId(HostResource.class, "ns2.example.tld", clock.nowUtc()); assertThat(existingHost.getSuperordinateDomain()).isEqualTo(Ref.create(Key.create(domain))); assertThat(addedHost.getSuperordinateDomain()).isEqualTo(Ref.create(Key.create(domain))); } @Test public void testSuccess_registrantMovedToTechContact() throws Exception { setEppInput("domain_update_registrant_to_tech.xml"); persistReferencedEntities(); ContactResource sh8013 = loadByUniqueId(ContactResource.class, "sh8013", clock.nowUtc()); persistResource( newDomainResource(getUniqueIdFromCommand()).asBuilder() .setRegistrant(Ref.create(sh8013)) .build()); clock.advanceOneMilli(); runFlowAssertResponse(readFile("domain_update_response.xml")); } @Test public void testSuccess_multipleReferencesToSameContactRemoved() throws Exception { setEppInput("domain_update_remove_multiple_contacts.xml"); persistReferencedEntities(); ContactResource sh8013 = loadByUniqueId(ContactResource.class, "sh8013", clock.nowUtc()); Ref sh8013Ref = Ref.create(sh8013); persistResource( newDomainResource(getUniqueIdFromCommand()).asBuilder() .setRegistrant(sh8013Ref) .setContacts(ImmutableSet.of( DesignatedContact.create(Type.ADMIN, sh8013Ref), DesignatedContact.create(Type.BILLING, sh8013Ref), DesignatedContact.create(Type.TECH, sh8013Ref))) .build()); clock.advanceOneMilli(); runFlowAssertResponse(readFile("domain_update_response.xml")); } @Test public void testSuccess_removeClientUpdateProhibited() throws Exception { persistReferencedEntities(); persistResource( persistDomain().asBuilder() .setStatusValues(ImmutableSet.of(StatusValue.CLIENT_UPDATE_PROHIBITED)) .build()); clock.advanceOneMilli(); runFlow(); assertAboutDomains().that(reloadResourceByUniqueId()) .doesNotHaveStatusValue(StatusValue.CLIENT_UPDATE_PROHIBITED); } private void doSecDnsSuccessfulTest( String xmlFilename, ImmutableSet originalDsData, ImmutableSet expectedDsData) throws Exception { setEppInput(xmlFilename); persistResource( newDomainResource(getUniqueIdFromCommand()).asBuilder() .setDsData(originalDsData) .build()); assertTransactionalFlow(true); clock.advanceOneMilli(); runFlowAssertResponse(readFile("domain_update_response.xml")); DomainResource resource = reloadResourceByUniqueId(); assertAboutDomains().that(resource) .hasOnlyOneHistoryEntryWhich() .hasType(HistoryEntry.Type.DOMAIN_UPDATE); assertThat(resource.getDsData()).isEqualTo(expectedDsData); assertDnsTasksEnqueued("example.tld"); } @Test public void testSuccess_secDnsAdd() throws Exception { doSecDnsSuccessfulTest( "domain_update_dsdata_add.xml", null, ImmutableSet.of(DelegationSignerData.create( 12346, 3, 1, base16().decode("38EC35D5B3A34B44C39B")))); } @Test public void testSuccess_secDnsAddPreservesExisting() throws Exception { doSecDnsSuccessfulTest( "domain_update_dsdata_add.xml", ImmutableSet.of(SOME_DSDATA), ImmutableSet.of(SOME_DSDATA, DelegationSignerData.create( 12346, 3, 1, base16().decode("38EC35D5B3A34B44C39B")))); } @Test public void testSuccess_secDnsAddToMaxRecords() throws Exception { ImmutableSet.Builder builder = new ImmutableSet.Builder<>(); for (int i = 0; i < 7; ++i) { builder.add(DelegationSignerData.create(i, 2, 3, new byte[]{0, 1, 2})); } ImmutableSet commonDsData = builder.build(); doSecDnsSuccessfulTest( "domain_update_dsdata_add.xml", commonDsData, ImmutableSet.copyOf( union(commonDsData, ImmutableSet.of(DelegationSignerData.create( 12346, 3, 1, base16().decode("38EC35D5B3A34B44C39B")))))); } @Test public void testSuccess_secDnsRemove() throws Exception { doSecDnsSuccessfulTest( "domain_update_dsdata_rem.xml", ImmutableSet.of(SOME_DSDATA, DelegationSignerData.create( 12346, 3, 1, base16().decode("38EC35D5B3A34B44C39B"))), ImmutableSet.of(SOME_DSDATA)); } @Test public void testSuccess_secDnsRemoveAll() throws Exception { // As an aside, this test also validates that it's ok to set the 'urgent' attribute to false. doSecDnsSuccessfulTest( "domain_update_dsdata_rem_all.xml", ImmutableSet.of(SOME_DSDATA, DelegationSignerData.create( 12346, 3, 1, base16().decode("38EC35D5B3A34B44C39B"))), ImmutableSet.of()); } @Test public void testSuccess_secDnsAddRemove() throws Exception { doSecDnsSuccessfulTest( "domain_update_dsdata_add_rem.xml", ImmutableSet.of(SOME_DSDATA, DelegationSignerData.create( 12345, 3, 1, base16().decode("38EC35D5B3A34B33C99B"))), ImmutableSet.of(SOME_DSDATA, DelegationSignerData.create( 12346, 3, 1, base16().decode("38EC35D5B3A34B44C39B")))); } @Test public void testSuccess_secDnsAddRemoveToMaxRecords() throws Exception { ImmutableSet.Builder builder = new ImmutableSet.Builder<>(); for (int i = 0; i < 7; ++i) { builder.add(DelegationSignerData.create(i, 2, 3, new byte[]{0, 1, 2})); } ImmutableSet commonDsData = builder.build(); doSecDnsSuccessfulTest( "domain_update_dsdata_add_rem.xml", ImmutableSet.copyOf( union(commonDsData, ImmutableSet.of(DelegationSignerData.create( 12345, 3, 1, base16().decode("38EC35D5B3A34B33C99B"))))), ImmutableSet.copyOf( union(commonDsData, ImmutableSet.of(DelegationSignerData.create( 12346, 3, 1, base16().decode("38EC35D5B3A34B44C39B")))))); } @Test public void testSuccess_secDnsAddRemoveSame() throws Exception { // Adding and removing the same dsData is a no-op because removes are processed first. doSecDnsSuccessfulTest( "domain_update_dsdata_add_rem_same.xml", ImmutableSet.of(SOME_DSDATA, DelegationSignerData.create( 12345, 3, 1, base16().decode("38EC35D5B3A34B33C99B"))), ImmutableSet.of(SOME_DSDATA, DelegationSignerData.create( 12345, 3, 1, base16().decode("38EC35D5B3A34B33C99B")))); } @Test public void testSuccess_secDnsRemoveAlreadyNotThere() throws Exception { // Removing a dsData that isn't there is a no-op. doSecDnsSuccessfulTest( "domain_update_dsdata_rem.xml", ImmutableSet.of(SOME_DSDATA), ImmutableSet.of(SOME_DSDATA)); } public void doServerStatusBillingTest(String xmlFilename, boolean isBillable) throws Exception { setEppInput(xmlFilename); clock.advanceOneMilli(); runFlowAssertResponse( CommitMode.LIVE, UserPrivileges.SUPERUSER, readFile("domain_update_response.xml")); if (isBillable) { assertBillingEvents( new BillingEvent.OneTime.Builder() .setReason(Reason.SERVER_STATUS) .setTargetId("example.tld") .setClientId("TheRegistrar") .setCost(Money.of(USD, 19)) .setEventTime(clock.nowUtc()) .setBillingTime(clock.nowUtc()) .setParent(getOnlyHistoryEntryOfType( reloadResourceByUniqueId(), HistoryEntry.Type.DOMAIN_UPDATE)) .build()); } else { assertNoBillingEvents(); } } @Test public void testSuccess_addServerStatusBillingEvent() throws Exception { eppRequestSource = EppRequestSource.TOOL; persistReferencedEntities(); persistDomain(); doServerStatusBillingTest("domain_update_add_server_status.xml", true); } @Test public void testSuccess_noBillingOnPreExistingServerStatus() throws Exception { eppRequestSource = EppRequestSource.TOOL; DomainResource addStatusDomain = persistActiveDomain(getUniqueIdFromCommand()); persistResource( addStatusDomain.asBuilder() .addStatusValue(StatusValue.SERVER_RENEW_PROHIBITED) .build()); doServerStatusBillingTest("domain_update_add_server_status.xml", false); } @Test public void testSuccess_removeServerStatusBillingEvent() throws Exception { eppRequestSource = EppRequestSource.TOOL; persistReferencedEntities(); DomainResource removeStatusDomain = persistDomain(); persistResource( removeStatusDomain.asBuilder() .addStatusValue(StatusValue.SERVER_RENEW_PROHIBITED) .build()); doServerStatusBillingTest("domain_update_remove_server_status.xml", true); } @Test public void testSuccess_changeServerStatusBillingEvent() throws Exception { eppRequestSource = EppRequestSource.TOOL; persistReferencedEntities(); DomainResource changeStatusDomain = persistDomain(); persistResource( changeStatusDomain.asBuilder() .addStatusValue(StatusValue.SERVER_RENEW_PROHIBITED) .build()); doServerStatusBillingTest("domain_update_change_server_status.xml", true); } @Test public void testSuccess_noBillingEventOnNonServerStatusChange() throws Exception { persistActiveDomain(getUniqueIdFromCommand()); doServerStatusBillingTest("domain_update_add_non_server_status.xml", false); } @Test public void testSuccess_noBillingEventOnServerHoldStatusChange() throws Exception { persistActiveDomain(getUniqueIdFromCommand()); doServerStatusBillingTest("domain_update_add_server_hold_status.xml", false); } @Test public void testSuccess_noBillingEventOnServerStatusChangeNotFromRegistrar() throws Exception { eppRequestSource = EppRequestSource.TOOL; persistActiveDomain(getUniqueIdFromCommand()); doServerStatusBillingTest("domain_update_add_server_status_non_registrar.xml", false); } @Test public void testSuccess_superuserClientUpdateProhibited() throws Exception { setEppInput("domain_update_add_server_hold_status.xml"); persistReferencedEntities(); persistResource( persistActiveDomain(getUniqueIdFromCommand()).asBuilder() .setStatusValues(ImmutableSet.of(StatusValue.CLIENT_UPDATE_PROHIBITED)) .build()); clock.advanceOneMilli(); runFlowAssertResponse( CommitMode.LIVE, UserPrivileges.SUPERUSER, readFile("domain_update_response.xml")); assertAboutDomains().that(reloadResourceByUniqueId()) .hasStatusValue(StatusValue.CLIENT_UPDATE_PROHIBITED).and() .hasStatusValue(StatusValue.SERVER_HOLD); } private void doSecDnsFailingTest(Class expectedException, String xmlFilename) throws Exception { thrown.expect(expectedException); setEppInput(xmlFilename); persistReferencedEntities(); persistActiveDomain(getUniqueIdFromCommand()); runFlow(); } @Test public void testFailure_secDnsAllCannotBeFalse() throws Exception { doSecDnsFailingTest( SecDnsAllUsageException.class, "domain_update_dsdata_rem_all_false.xml"); } @Test public void testFailure_secDnsEmptyNotAllowed() throws Exception { doSecDnsFailingTest(EmptySecDnsUpdateException.class, "domain_update_dsdata_empty.xml"); } @Test public void testFailure_secDnsUrgentNotSupported() throws Exception { doSecDnsFailingTest( UrgentAttributeNotSupportedException.class, "domain_update_dsdata_urgent.xml"); } @Test public void testFailure_secDnsChangeNotSupported() throws Exception { doSecDnsFailingTest( MaxSigLifeChangeNotSupportedException.class, "domain_update_maxsiglife.xml"); } @Test public void testFailure_secDnsTooManyDsRecords() throws Exception { thrown.expect(TooManyDsRecordsException.class); ImmutableSet.Builder builder = new ImmutableSet.Builder<>(); for (int i = 0; i < 8; ++i) { builder.add(DelegationSignerData.create(i, 2, 3, new byte[]{0, 1, 2})); } setEppInput("domain_update_dsdata_add.xml"); persistResource( newDomainResource(getUniqueIdFromCommand()).asBuilder() .setDsData(builder.build()) .build()); runFlow(); } @Test public void testFailure_tooManyNameservers() throws Exception { thrown.expect(TooManyNameserversException.class); setEppInput("domain_update_add_nameserver.xml"); persistReferencedEntities(); persistDomain(); // Modify domain so it has 13 nameservers. We will then try to add one in the test. modifyDomainToHave13Nameservers(); runFlow(); } @Test public void testFailure_wrongExtension() throws Exception { thrown.expect(UnimplementedExtensionException.class); setEppInput("domain_update_wrong_extension.xml"); runFlow(); } @Test public void testFailure_neverExisted() throws Exception { thrown.expect( ResourceToMutateDoesNotExistException.class, String.format("(%s)", getUniqueIdFromCommand())); persistReferencedEntities(); runFlow(); } @Test public void testFailure_existedButWasDeleted() throws Exception { thrown.expect( ResourceToMutateDoesNotExistException.class, String.format("(%s)", getUniqueIdFromCommand())); persistReferencedEntities(); persistDeletedDomain(getUniqueIdFromCommand(), clock.nowUtc()); runFlow(); } @Test public void testFailure_clientUpdateProhibited() throws Exception { createTld("com"); thrown.expect(ResourceHasClientUpdateProhibitedException.class); setEppInput("domain_update_authinfo.xml"); persistReferencedEntities(); persistResource( newDomainResource(getUniqueIdFromCommand()).asBuilder() .setStatusValues(ImmutableSet.of(StatusValue.CLIENT_UPDATE_PROHIBITED)) .build()); runFlow(); } @Test public void testFailure_serverUpdateProhibited() throws Exception { thrown.expect(ResourceStatusProhibitsOperationException.class); persistReferencedEntities(); persistResource( newDomainResource(getUniqueIdFromCommand()).asBuilder() .setStatusValues(ImmutableSet.of(StatusValue.SERVER_UPDATE_PROHIBITED)) .build()); runFlow(); } @Test public void testFailure_missingHost() throws Exception { thrown.expect( LinkedResourcesDoNotExistException.class, "(ns2.example.foo)"); persistActiveHost("ns1.example.foo"); persistActiveContact("sh8013"); persistActiveContact("mak21"); persistActiveDomain(getUniqueIdFromCommand()); runFlow(); } @Test public void testFailure_missingContact() throws Exception { thrown.expect( LinkedResourcesDoNotExistException.class, "(sh8013)"); persistActiveHost("ns1.example.foo"); persistActiveHost("ns2.example.foo"); persistActiveContact("mak21"); persistActiveDomain(getUniqueIdFromCommand()); runFlow(); } @Test public void testFailure_addingDuplicateContact() throws Exception { thrown.expect(DuplicateContactForRoleException.class); persistReferencedEntities(); persistActiveContact("foo"); persistDomain(); // Add a tech contact to the persisted entity, which should cause the flow to fail when it tries // to add "mak21" as a second tech contact. persistResource( reloadResourceByUniqueId().asBuilder() .setContacts(ImmutableSet.of( DesignatedContact.create(Type.TECH, Ref.create( loadByUniqueId(ContactResource.class, "foo", clock.nowUtc()))))) .build()); runFlow(); } @Test public void testFailure_clientProhibitedStatusValue() throws Exception { thrown.expect(StatusNotClientSettableException.class); setEppInput("domain_update_prohibited_status.xml"); persistReferencedEntities(); persistDomain(); runFlow(); } @Test public void testSuccess_superuserClientProhibitedStatusValue() throws Exception { setEppInput("domain_update_prohibited_status.xml"); persistReferencedEntities(); persistDomain(); runFlowAssertResponse( CommitMode.LIVE, UserPrivileges.SUPERUSER, readFile("domain_update_response.xml")); } @Test public void testFailure_pendingDelete() throws Exception { thrown.expect(ResourceStatusProhibitsOperationException.class); persistReferencedEntities(); persistResource( newDomainResource(getUniqueIdFromCommand()).asBuilder() .setDeletionTime(clock.nowUtc().plusDays(1)) .addStatusValue(StatusValue.PENDING_DELETE) .build()); runFlow(); } @Test public void testFailure_duplicateContactInCommand() throws Exception { thrown.expect(DuplicateContactForRoleException.class); setEppInput("domain_update_duplicate_contact.xml"); persistReferencedEntities(); persistDomain(); runFlow(); } @Test public void testFailure_missingContactType() throws Exception { // We need to test for missing type, but not for invalid - the schema enforces that for us. thrown.expect(MissingContactTypeException.class); setEppInput("domain_update_missing_contact_type.xml"); persistReferencedEntities(); persistDomain(); runFlow(); } @Test public void testFailure_unauthorizedClient() throws Exception { thrown.expect(ResourceNotOwnedException.class); sessionMetadata.setClientId("NewRegistrar"); persistReferencedEntities(); persistDomain(); runFlow(); } @Test public void testFailure_notAuthorizedForTld() throws Exception { thrown.expect(NotAuthorizedForTldException.class); persistResource( Registrar.loadByClientId("TheRegistrar") .asBuilder() .setAllowedTlds(ImmutableSet.of()) .build()); persistReferencedEntities(); persistDomain(); runFlow(); } @Test public void testSuccess_superuserUnauthorizedClient() throws Exception { sessionMetadata.setClientId("NewRegistrar"); persistReferencedEntities(); persistDomain(); clock.advanceOneMilli(); runFlowAssertResponse( CommitMode.LIVE, UserPrivileges.SUPERUSER, readFile("domain_update_response.xml")); } @Test public void testFailure_sameNameserverAddedAndRemoved() throws Exception { thrown.expect(AddRemoveSameValueEppException.class); setEppInput("domain_update_add_remove_same_host.xml"); persistReferencedEntities(); persistResource( newDomainResource(getUniqueIdFromCommand()).asBuilder() .setNameservers(ImmutableSet.of(Ref.create( loadByUniqueId(HostResource.class, "ns1.example.foo", clock.nowUtc())))) .build()); runFlow(); } @Test public void testFailure_sameContactAddedAndRemoved() throws Exception { thrown.expect(AddRemoveSameValueEppException.class); setEppInput("domain_update_add_remove_same_contact.xml"); persistReferencedEntities(); persistResource( newDomainResource(getUniqueIdFromCommand()).asBuilder() .setContacts(ImmutableSet.of(DesignatedContact.create( Type.TECH, Ref.create( loadByUniqueId(ContactResource.class, "sh8013", clock.nowUtc()))))) .build()); runFlow(); } @Test public void testFailure_removeAdmin() throws Exception { thrown.expect(MissingAdminContactException.class); setEppInput("domain_update_remove_admin.xml"); persistReferencedEntities(); persistResource( newDomainResource(getUniqueIdFromCommand()).asBuilder() .setContacts(ImmutableSet.of( DesignatedContact.create(Type.ADMIN, Ref.create(sh8013Contact)), DesignatedContact.create(Type.TECH, Ref.create(sh8013Contact)))) .build()); runFlow(); } @Test public void testFailure_removeTech() throws Exception { thrown.expect(MissingTechnicalContactException.class); setEppInput("domain_update_remove_tech.xml"); persistReferencedEntities(); persistResource( newDomainResource(getUniqueIdFromCommand()).asBuilder() .setContacts(ImmutableSet.of( DesignatedContact.create(Type.ADMIN, Ref.create(sh8013Contact)), DesignatedContact.create(Type.TECH, Ref.create(sh8013Contact)))) .build()); runFlow(); } @Test public void testFailure_addPendingDeleteContact() throws Exception { thrown.expect( LinkedResourceInPendingDeleteProhibitsOperationException.class, "mak21"); persistReferencedEntities(); persistDomain(); persistActiveHost("ns1.example.foo"); persistActiveHost("ns2.example.foo"); persistActiveContact("sh8013"); persistResource(loadByUniqueId(ContactResource.class, "mak21", clock.nowUtc()).asBuilder() .addStatusValue(StatusValue.PENDING_DELETE) .build()); clock.advanceOneMilli(); runFlow(); } @Test public void testFailure_addPendingDeleteHost() throws Exception { thrown.expect( LinkedResourceInPendingDeleteProhibitsOperationException.class, "ns2.example.foo"); persistReferencedEntities(); persistDomain(); persistActiveHost("ns1.example.foo"); persistActiveContact("mak21"); persistActiveContact("sh8013"); persistResource( loadByUniqueId(HostResource.class, "ns2.example.foo", clock.nowUtc()).asBuilder() .addStatusValue(StatusValue.PENDING_DELETE) .build()); clock.advanceOneMilli(); runFlow(); } @Test public void testFailure_newRegistrantNotWhitelisted() throws Exception { persistReferencedEntities(); persistDomain(); persistResource( Registry.get("tld").asBuilder() .setAllowedRegistrantContactIds(ImmutableSet.of("contact1234")) .build()); clock.advanceOneMilli(); thrown.expect(RegistrantNotAllowedException.class); runFlow(); } @Test public void testFailure_newNameserverNotWhitelisted() throws Exception { persistReferencedEntities(); persistDomain(); persistResource( Registry.get("tld").asBuilder() .setAllowedFullyQualifiedHostNames(ImmutableSet.of("ns1.example.foo")) .build()); clock.advanceOneMilli(); thrown.expect(NameserversNotAllowedException.class); runFlow(); } @Test public void testSuccess_newNameserverWhitelisted() throws Exception { setEppInput("domain_update_add_nameserver.xml"); persistReferencedEntities(); persistDomain(); // No registrant is given but both nameserver and registrant whitelist exist. persistResource( Registry.get("tld").asBuilder() .setAllowedRegistrantContactIds(ImmutableSet.of("sh8013")) .setAllowedFullyQualifiedHostNames(ImmutableSet.of("ns2.example.foo")) .build()); assertThat(reloadResourceByUniqueId().getNameservers()).doesNotContain( Ref.create(loadByUniqueId(HostResource.class, "ns2.example.foo", clock.nowUtc()))); runFlow(); assertThat(reloadResourceByUniqueId().getNameservers()).contains( Ref.create(loadByUniqueId(HostResource.class, "ns2.example.foo", clock.nowUtc()))); } @Test public void testSuccess_nameserverAndRegistrantWhitelisted() throws Exception { persistReferencedEntities(); persistDomain(); persistResource( Registry.get("tld").asBuilder() .setAllowedRegistrantContactIds(ImmutableSet.of("sh8013")) .setAllowedFullyQualifiedHostNames(ImmutableSet.of("ns2.example.foo")) .build()); doSuccessfulTest(); } @Test public void testSuccess_regTypeExtensionValidates() throws Exception { setEppInput("domain_update_regtype.xml"); persistReferencedEntities(); persistDomain(); doSuccessfulTest(); } }