diff --git a/docs/flows.md b/docs/flows.md index d9a5b94ef..20cce1b6b 100644 --- a/docs/flows.md +++ b/docs/flows.md @@ -898,7 +898,6 @@ new ones with the correct approval time). * Resource with this id does not exist. * 2304 * Resource status prohibits this operation. - * Superuser extensions cannot be used during autorenew grace periods. * Domain transfer period cannot be zero when using the fee transfer extension. * The requested domain name is on the premium price list, and this diff --git a/java/google/registry/flows/domain/DomainTransferApproveFlow.java b/java/google/registry/flows/domain/DomainTransferApproveFlow.java index 1faa64560..69e78b2d4 100644 --- a/java/google/registry/flows/domain/DomainTransferApproveFlow.java +++ b/java/google/registry/flows/domain/DomainTransferApproveFlow.java @@ -138,8 +138,15 @@ public final class DomainTransferApproveFlow implements TransactionalFlow { int extraYears = transferData.getTransferPeriod().getValue(); if (autorenewGrace != null) { extraYears = 0; - ofy().save().entity( - BillingEvent.Cancellation.forGracePeriod(autorenewGrace, historyEntry, targetId)); + // During a normal transfer, if the domain is in the auto-renew grace period, the auto-renew + // billing event is cancelled and the gaining registrar is charged for the one year renewal. + // But, if the superuser extension is used to request a transfer without an additional year + // then the gaining registrar is not charged for the one year renewal and the losing registrar + // still needs to be charged for the auto-renew. + if (billingEvent.isPresent()) { + ofy().save().entity( + BillingEvent.Cancellation.forGracePeriod(autorenewGrace, historyEntry, targetId)); + } } // Close the old autorenew event and poll message at the transfer time (aka now). This may end // up deleting the poll message. diff --git a/java/google/registry/flows/domain/DomainTransferRequestFlow.java b/java/google/registry/flows/domain/DomainTransferRequestFlow.java index 9ca47075e..588dd6ceb 100644 --- a/java/google/registry/flows/domain/DomainTransferRequestFlow.java +++ b/java/google/registry/flows/domain/DomainTransferRequestFlow.java @@ -46,7 +46,6 @@ import google.registry.flows.annotations.ReportingSpec; import google.registry.flows.exceptions.AlreadyPendingTransferException; import google.registry.flows.exceptions.InvalidTransferPeriodValueException; import google.registry.flows.exceptions.ObjectAlreadySponsoredException; -import google.registry.flows.exceptions.SuperuserExtensionAndAutorenewGracePeriodException; import google.registry.flows.exceptions.TransferPeriodMustBeOneYearException; import google.registry.flows.exceptions.TransferPeriodZeroAndFeeTransferExtensionException; import google.registry.model.domain.DomainCommand.Transfer; @@ -99,7 +98,6 @@ import org.joda.time.DateTime; * @error {@link google.registry.flows.exceptions.MissingTransferRequestAuthInfoException} * @error {@link google.registry.flows.exceptions.ObjectAlreadySponsoredException} * @error {@link google.registry.flows.exceptions.ResourceStatusProhibitsOperationException} - * @error {@link google.registry.flows.exceptions.SuperuserExtensionAndAutorenewGracePeriodException} * @error {@link google.registry.flows.exceptions.TransferPeriodMustBeOneYearException} * @error {@link InvalidTransferPeriodValueException} * @error {@link google.registry.flows.exceptions.TransferPeriodZeroAndFeeTransferExtensionException} @@ -184,10 +182,6 @@ public final class DomainTransferRequestFlow implements TransactionalFlow { DomainResource domainAtTransferTime = existingDomain.cloneProjectedAtTime(automaticTransferTime); if (!domainAtTransferTime.getGracePeriodsOfType(GracePeriodStatus.AUTO_RENEW).isEmpty()) { - if (superuserExtension != null) { - // We don't allow the superuser extension for domains in the auto renew grace period - throw new SuperuserExtensionAndAutorenewGracePeriodException(); - } extraYears = 0; } // The new expiration time if there is a server approval. diff --git a/java/google/registry/flows/domain/DomainTransferUtils.java b/java/google/registry/flows/domain/DomainTransferUtils.java index 61543e52a..98c2ad430 100644 --- a/java/google/registry/flows/domain/DomainTransferUtils.java +++ b/java/google/registry/flows/domain/DomainTransferUtils.java @@ -119,7 +119,7 @@ public final class DomainTransferUtils { return builder .addAll( createOptionalAutorenewCancellation( - automaticTransferTime, historyEntry, targetId, existingDomain) + automaticTransferTime, historyEntry, targetId, existingDomain, transferCost) .asSet()) .add( createGainingClientAutorenewEvent( @@ -229,7 +229,9 @@ public final class DomainTransferUtils { * expiration time. Since the gaining registrar will still be billed for the transfer's 1-year * renewal, we must issue a cancellation for the autorenew, so that the losing registrar will not * be charged (essentially, the gaining registrar takes on the cost of the year of registration - * that the autorenew just added). + * that the autorenew just added). But, if the superuser extension is used to request a transfer + * without an additional year then the gaining registrar is not charged for the one year renewal + * and the losing registrar still needs to be charged for the auto-renew. * *

For details on the policy justification, see b/19430703#comment17 and this ICANN advisory. @@ -238,13 +240,14 @@ public final class DomainTransferUtils { DateTime automaticTransferTime, HistoryEntry historyEntry, String targetId, - DomainResource existingDomain) { + DomainResource existingDomain, + Optional transferCost) { DomainResource domainAtTransferTime = existingDomain.cloneProjectedAtTime(automaticTransferTime); GracePeriod autorenewGracePeriod = getOnlyElement( domainAtTransferTime.getGracePeriodsOfType(GracePeriodStatus.AUTO_RENEW), null); - if (autorenewGracePeriod != null) { + if (autorenewGracePeriod != null && transferCost.isPresent()) { return Optional.of( BillingEvent.Cancellation.forGracePeriod(autorenewGracePeriod, historyEntry, targetId) .asBuilder() diff --git a/java/google/registry/flows/exceptions/SuperuserExtensionAndAutorenewGracePeriodException.java b/java/google/registry/flows/exceptions/SuperuserExtensionAndAutorenewGracePeriodException.java deleted file mode 100644 index 6309d350b..000000000 --- a/java/google/registry/flows/exceptions/SuperuserExtensionAndAutorenewGracePeriodException.java +++ /dev/null @@ -1,25 +0,0 @@ -// 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.flows.exceptions; - -import google.registry.flows.EppException.StatusProhibitsOperationException; - -/** Superuser extensions cannot be used during autorenew grace periods. */ -public class SuperuserExtensionAndAutorenewGracePeriodException extends - StatusProhibitsOperationException { - public SuperuserExtensionAndAutorenewGracePeriodException() { - super("Superuser extensions cannot be used during autorenew grace periods."); - } -} diff --git a/javatests/google/registry/flows/domain/DomainTransferApproveFlowTest.java b/javatests/google/registry/flows/domain/DomainTransferApproveFlowTest.java index ac0351c53..89b5112c2 100644 --- a/javatests/google/registry/flows/domain/DomainTransferApproveFlowTest.java +++ b/javatests/google/registry/flows/domain/DomainTransferApproveFlowTest.java @@ -42,6 +42,7 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedMap; import com.google.common.collect.Iterables; import com.google.common.collect.Ordering; +import com.googlecode.objectify.Key; import google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException; import google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException; import google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException; @@ -52,6 +53,7 @@ import google.registry.model.billing.BillingEvent.Cancellation; import google.registry.model.billing.BillingEvent.Cancellation.Builder; import google.registry.model.billing.BillingEvent.OneTime; import google.registry.model.billing.BillingEvent.Reason; +import google.registry.model.billing.BillingEvent.Recurring; import google.registry.model.contact.ContactAuthInfo; import google.registry.model.domain.DomainAuthInfo; import google.registry.model.domain.DomainResource; @@ -568,7 +570,6 @@ public class DomainTransferApproveFlowTest previousSuccessRecord.asBuilder().setReportAmount(-1).build(), DomainTransactionRecord.create( "tld", clock.nowUtc().plusDays(3), TRANSFER_SUCCESSFUL, 1)); - } @Test @@ -589,4 +590,37 @@ public class DomainTransferApproveFlowTest domain.getRegistrationExpirationTime().plusYears(0)); assertHistoryEntriesDoNotContainTransferBillingEventsOrGracePeriods(); } + + @Test + public void testSuccess_superuserExtension_transferPeriodZero_autorenewGraceActive() + throws Exception { + DomainResource domain = reloadResourceByForeignKey(); + Key existingAutorenewEvent = domain.getAutorenewBillingEvent(); + // Set domain to have auto-renewed just before the transfer request, so that it will have an + // active autorenew grace period spanning the entire transfer window. + DateTime autorenewTime = clock.nowUtc().minusDays(1); + DateTime expirationTime = autorenewTime.plusYears(1); + TransferData.Builder transferDataBuilder = domain.getTransferData().asBuilder(); + domain = + persistResource( + domain + .asBuilder() + .setTransferData( + transferDataBuilder.setTransferPeriod(Period.create(0, Unit.YEARS)).build()) + .setRegistrationExpirationTime(expirationTime) + .addGracePeriod( + GracePeriod.createForRecurring( + GracePeriodStatus.AUTO_RENEW, + autorenewTime.plus(Registry.get("tld").getAutoRenewGracePeriodLength()), + "TheRegistrar", + existingAutorenewEvent)) + .build()); + clock.advanceOneMilli(); + runSuccessfulFlowWithAssertions( + "tld", + "domain_transfer_approve.xml", + "domain_transfer_approve_response_zero_period_autorenew_grace.xml", + domain.getRegistrationExpirationTime()); + assertHistoryEntriesDoNotContainTransferBillingEventsOrGracePeriods(); + } } diff --git a/javatests/google/registry/flows/domain/DomainTransferRequestFlowTest.java b/javatests/google/registry/flows/domain/DomainTransferRequestFlowTest.java index e4be91eba..1572176ef 100644 --- a/javatests/google/registry/flows/domain/DomainTransferRequestFlowTest.java +++ b/javatests/google/registry/flows/domain/DomainTransferRequestFlowTest.java @@ -60,7 +60,6 @@ import google.registry.flows.exceptions.InvalidTransferPeriodValueException; import google.registry.flows.exceptions.MissingTransferRequestAuthInfoException; import google.registry.flows.exceptions.ObjectAlreadySponsoredException; import google.registry.flows.exceptions.ResourceStatusProhibitsOperationException; -import google.registry.flows.exceptions.SuperuserExtensionAndAutorenewGracePeriodException; import google.registry.flows.exceptions.TransferPeriodMustBeOneYearException; import google.registry.flows.exceptions.TransferPeriodZeroAndFeeTransferExtensionException; import google.registry.model.billing.BillingEvent; @@ -713,20 +712,33 @@ public class DomainTransferRequestFlowTest } @Test - public void testFailure_superuserExtension_duringAutorenewGracePeriod() throws Exception { - setupDomain("example", "tld"); + public void testSuccess_superuserExtension_zeroPeriod_autorenewGraceActive() + throws Exception { eppRequestSource = EppRequestSource.TOOL; - DomainResource domain = reloadResourceByForeignKey(); - DateTime oldExpirationTime = clock.nowUtc().minusDays(1); - persistResource(domain.asBuilder() - .setRegistrationExpirationTime(oldExpirationTime) + setupDomain("example", "tld"); + Key existingAutorenewEvent = + domain.getAutorenewBillingEvent(); + // Set domain to have auto-renewed just before the transfer request, so that it will have an + // active autorenew grace period spanning the entire transfer window. + DateTime autorenewTime = clock.nowUtc().minusDays(1); + DateTime expirationTime = autorenewTime.plusYears(1); + domain = persistResource(domain.asBuilder() + .setRegistrationExpirationTime(expirationTime) + .addGracePeriod(GracePeriod.createForRecurring( + GracePeriodStatus.AUTO_RENEW, + autorenewTime.plus(Registry.get("tld").getAutoRenewGracePeriodLength()), + "TheRegistrar", + existingAutorenewEvent)) .build()); clock.advanceOneMilli(); - thrown.expect(SuperuserExtensionAndAutorenewGracePeriodException.class); - runTest( + doSuccessfulSuperuserExtensionTest( "domain_transfer_request_superuser_extension.xml", - UserPrivileges.SUPERUSER, - ImmutableMap.of("PERIOD", "1", "AUTOMATIC_TRANSFER_LENGTH", "5")); + "domain_transfer_request_response_su_ext_zero_period_autorenew_grace.xml", + domain.getRegistrationExpirationTime(), + ImmutableMap.of("PERIOD", "0", "AUTOMATIC_TRANSFER_LENGTH", "0"), + Optional.absent(), + Period.create(0, Unit.YEARS), + Duration.standardDays(0)); } @Test diff --git a/javatests/google/registry/flows/domain/testdata/domain_transfer_approve_response_zero_period_autorenew_grace.xml b/javatests/google/registry/flows/domain/testdata/domain_transfer_approve_response_zero_period_autorenew_grace.xml new file mode 100644 index 000000000..9884e88e5 --- /dev/null +++ b/javatests/google/registry/flows/domain/testdata/domain_transfer_approve_response_zero_period_autorenew_grace.xml @@ -0,0 +1,23 @@ + + + + Command completed successfully + + + + example.tld + clientApproved + NewRegistrar + 2000-06-06T22:00:00.0Z + TheRegistrar + 2000-06-09T22:00:00.0Z + 2001-06-08T22:00:00.0Z + + + + ABC-12345 + server-trid + + + diff --git a/javatests/google/registry/flows/domain/testdata/domain_transfer_request_response_su_ext_zero_period_autorenew_grace.xml b/javatests/google/registry/flows/domain/testdata/domain_transfer_request_response_su_ext_zero_period_autorenew_grace.xml new file mode 100644 index 000000000..6368fdea4 --- /dev/null +++ b/javatests/google/registry/flows/domain/testdata/domain_transfer_request_response_su_ext_zero_period_autorenew_grace.xml @@ -0,0 +1,23 @@ + + + + Command completed successfully; action pending + + + + example.tld + pending + NewRegistrar + 2000-06-09T22:00:00.0Z + TheRegistrar + 2000-06-09T22:00:00.0Z + 2001-06-08T22:00:00.0Z + + + + ABC-12345 + server-trid + + +