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
+
+
+