mirror of
https://github.com/google/nomulus.git
synced 2025-05-14 16:37:13 +02:00
Add support for a domain transfer request superuser EPP extension
Allow superusers to change the transfer period to zero years and allow superusers to change the automatic transfer length. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=167598314
This commit is contained in:
parent
263aea3b2a
commit
2e4b63bb79
28 changed files with 1018 additions and 124 deletions
|
@ -63,7 +63,8 @@ public class EppXmlTransformer {
|
|||
"dsig.xsd",
|
||||
"smd.xsd",
|
||||
"launch.xsd",
|
||||
"allocate.xsd");
|
||||
"allocate.xsd",
|
||||
"superuser.xsd");
|
||||
|
||||
private static final XmlTransformer INPUT_TRANSFORMER =
|
||||
new XmlTransformer(SCHEMAS, EppInput.class);
|
||||
|
|
|
@ -29,8 +29,11 @@ import google.registry.flows.EppException.CommandUseErrorException;
|
|||
import google.registry.flows.EppException.SyntaxErrorException;
|
||||
import google.registry.flows.EppException.UnimplementedExtensionException;
|
||||
import google.registry.flows.FlowModule.ClientId;
|
||||
import google.registry.flows.FlowModule.Superuser;
|
||||
import google.registry.flows.exceptions.OnlyToolCanPassMetadataException;
|
||||
import google.registry.flows.exceptions.UnauthorizedForSuperuserExtensionException;
|
||||
import google.registry.model.domain.metadata.MetadataExtension;
|
||||
import google.registry.model.domain.superuser.SuperuserExtension;
|
||||
import google.registry.model.eppinput.EppInput;
|
||||
import google.registry.model.eppinput.EppInput.CommandExtension;
|
||||
import google.registry.util.FormattingLogger;
|
||||
|
@ -56,6 +59,7 @@ public final class ExtensionManager {
|
|||
@Inject EppInput eppInput;
|
||||
@Inject SessionMetadata sessionMetadata;
|
||||
@Inject @ClientId String clientId;
|
||||
@Inject @Superuser boolean isSuperuser;
|
||||
@Inject Class<? extends Flow> flowClass;
|
||||
@Inject EppRequestSource eppRequestSource;
|
||||
@Inject ExtensionManager() {}
|
||||
|
@ -107,11 +111,18 @@ public final class ExtensionManager {
|
|||
|
||||
private void checkForRestrictedExtensions(
|
||||
ImmutableSet<Class<? extends CommandExtension>> suppliedExtensions)
|
||||
throws OnlyToolCanPassMetadataException {
|
||||
throws OnlyToolCanPassMetadataException, UnauthorizedForSuperuserExtensionException {
|
||||
if (suppliedExtensions.contains(MetadataExtension.class)
|
||||
&& !eppRequestSource.equals(EppRequestSource.TOOL)) {
|
||||
throw new OnlyToolCanPassMetadataException();
|
||||
}
|
||||
// Can't use suppliedExtension.contains() here because the SuperuserExtension has child classes.
|
||||
for (Class<? extends CommandExtension> suppliedExtension : suppliedExtensions) {
|
||||
if (SuperuserExtension.class.isAssignableFrom(suppliedExtension)
|
||||
&& (!eppRequestSource.equals(EppRequestSource.TOOL) || !isSuperuser)) {
|
||||
throw new UnauthorizedForSuperuserExtensionException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkForDuplicateExtensions(
|
||||
|
|
|
@ -115,24 +115,29 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
|
|||
String gainingClientId = transferData.getGainingClientId();
|
||||
Registry registry = Registry.get(existingDomain.getTld());
|
||||
HistoryEntry historyEntry = buildHistoryEntry(existingDomain, registry, now, gainingClientId);
|
||||
// Bill for the transfer.
|
||||
BillingEvent.OneTime billingEvent = new BillingEvent.OneTime.Builder()
|
||||
.setReason(Reason.TRANSFER)
|
||||
.setTargetId(targetId)
|
||||
.setClientId(gainingClientId)
|
||||
.setPeriodYears(1)
|
||||
.setCost(getDomainRenewCost(targetId, transferData.getTransferRequestTime(), 1))
|
||||
.setEventTime(now)
|
||||
.setBillingTime(now.plus(Registry.get(tld).getTransferGracePeriodLength()))
|
||||
.setParent(historyEntry)
|
||||
.build();
|
||||
// Create a transfer billing event for 1 year, unless the superuser extension was used to set
|
||||
// the transfer period to zero. There is not a transfer cost if the transfer period is zero.
|
||||
Optional<BillingEvent.OneTime> billingEvent =
|
||||
(transferData.getTransferPeriod().getValue() == 0)
|
||||
? Optional.absent()
|
||||
: Optional.of(
|
||||
new BillingEvent.OneTime.Builder()
|
||||
.setReason(Reason.TRANSFER)
|
||||
.setTargetId(targetId)
|
||||
.setClientId(gainingClientId)
|
||||
.setPeriodYears(1)
|
||||
.setCost(getDomainRenewCost(targetId, transferData.getTransferRequestTime(), 1))
|
||||
.setEventTime(now)
|
||||
.setBillingTime(now.plus(Registry.get(tld).getTransferGracePeriodLength()))
|
||||
.setParent(historyEntry)
|
||||
.build());
|
||||
// If we are within an autorenew grace period, cancel the autorenew billing event and don't
|
||||
// increase the registration time, since the transfer subsumes the autorenew's extra year.
|
||||
int extraYears = 1; // All transfers are one year.
|
||||
GracePeriod autorenewGrace =
|
||||
getOnlyElement(existingDomain.getGracePeriodsOfType(GracePeriodStatus.AUTO_RENEW), null);
|
||||
int extraYears = transferData.getTransferPeriod().getValue();
|
||||
if (autorenewGrace != null) {
|
||||
extraYears--;
|
||||
extraYears = 0;
|
||||
ofy().save().entity(
|
||||
BillingEvent.Cancellation.forGracePeriod(autorenewGrace, historyEntry, targetId));
|
||||
}
|
||||
|
@ -167,8 +172,11 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
|
|||
.setAutorenewBillingEvent(Key.create(autorenewEvent))
|
||||
.setAutorenewPollMessage(Key.create(gainingClientAutorenewPollMessage))
|
||||
// Remove all the old grace periods and add a new one for the transfer.
|
||||
.setGracePeriods(ImmutableSet.of(
|
||||
GracePeriod.forBillingEvent(GracePeriodStatus.TRANSFER, billingEvent)))
|
||||
.setGracePeriods(
|
||||
(billingEvent.isPresent())
|
||||
? ImmutableSet.of(
|
||||
GracePeriod.forBillingEvent(GracePeriodStatus.TRANSFER, billingEvent.get()))
|
||||
: ImmutableSet.of())
|
||||
.build();
|
||||
// Create a poll message for the gaining client.
|
||||
PollMessage gainingClientPollMessage = createGainingTransferPollMessage(
|
||||
|
@ -176,13 +184,17 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
|
|||
newDomain.getTransferData(),
|
||||
newExpirationTime,
|
||||
historyEntry);
|
||||
ofy().save().<ImmutableObject>entities(
|
||||
ImmutableSet.Builder<ImmutableObject> entitiesToSave = new ImmutableSet.Builder<>();
|
||||
entitiesToSave.add(
|
||||
newDomain,
|
||||
historyEntry,
|
||||
billingEvent,
|
||||
autorenewEvent,
|
||||
gainingClientPollMessage,
|
||||
gainingClientAutorenewPollMessage);
|
||||
if (billingEvent.isPresent()) {
|
||||
entitiesToSave.add(billingEvent.get());
|
||||
}
|
||||
ofy().save().entities(entitiesToSave.build());
|
||||
// Delete the billing event and poll messages that were written in case the transfer would have
|
||||
// been implicitly server approved.
|
||||
ofy().delete().keys(existingDomain.getTransferData().getServerApproveEntities());
|
||||
|
|
|
@ -44,8 +44,11 @@ import google.registry.flows.FlowModule.TargetId;
|
|||
import google.registry.flows.TransactionalFlow;
|
||||
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;
|
||||
import google.registry.model.domain.DomainResource;
|
||||
import google.registry.model.domain.Period;
|
||||
|
@ -53,6 +56,7 @@ import google.registry.model.domain.fee.FeeTransferCommandExtension;
|
|||
import google.registry.model.domain.fee.FeeTransformResponseExtension;
|
||||
import google.registry.model.domain.metadata.MetadataExtension;
|
||||
import google.registry.model.domain.rgp.GracePeriodStatus;
|
||||
import google.registry.model.domain.superuser.DomainTransferRequestSuperuserExtension;
|
||||
import google.registry.model.eppcommon.AuthInfo;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.eppcommon.Trid;
|
||||
|
@ -94,7 +98,10 @@ 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}
|
||||
* @error {@link DomainFlowUtils.BadPeriodUnitException}
|
||||
* @error {@link DomainFlowUtils.CurrencyUnitMismatchException}
|
||||
* @error {@link DomainFlowUtils.CurrencyValueScaleException}
|
||||
|
@ -128,24 +135,43 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
|
|||
@Override
|
||||
public final EppResponse run() throws EppException {
|
||||
extensionManager.register(
|
||||
DomainTransferRequestSuperuserExtension.class,
|
||||
FeeTransferCommandExtension.class,
|
||||
MetadataExtension.class);
|
||||
extensionManager.validate();
|
||||
validateClientIsLoggedIn(gainingClientId);
|
||||
Period period = ((Transfer) resourceCommand).getPeriod();
|
||||
DateTime now = ofy().getTransactionTime();
|
||||
DomainResource existingDomain = loadAndVerifyExistence(DomainResource.class, targetId, now);
|
||||
verifyTransferAllowed(existingDomain, period, now);
|
||||
DomainTransferRequestSuperuserExtension superuserExtension =
|
||||
eppInput.getSingleExtension(DomainTransferRequestSuperuserExtension.class);
|
||||
Period period =
|
||||
(superuserExtension == null)
|
||||
? ((Transfer) resourceCommand).getPeriod()
|
||||
: superuserExtension.getRenewalPeriod();
|
||||
verifyTransferAllowed(existingDomain, period, now, superuserExtension);
|
||||
String tld = existingDomain.getTld();
|
||||
Registry registry = Registry.get(tld);
|
||||
// An optional extension from the client specifying what they think the transfer should cost.
|
||||
FeeTransferCommandExtension feeTransfer =
|
||||
eppInput.getSingleExtension(FeeTransferCommandExtension.class);
|
||||
FeesAndCredits feesAndCredits = pricingLogic.getTransferPrice(registry, targetId, now);
|
||||
validateFeeChallenge(targetId, tld, now, feeTransfer, feesAndCredits);
|
||||
if (period.getValue() == 0 && feeTransfer != null) {
|
||||
// If the period is zero, then there is no transfer billing event, so using the fee transfer
|
||||
// extension does not make sense.
|
||||
throw new TransferPeriodZeroAndFeeTransferExtensionException();
|
||||
}
|
||||
// If the period is zero, then there is no fee for the transfer.
|
||||
Optional<FeesAndCredits> feesAndCredits =
|
||||
(period.getValue() == 0)
|
||||
? Optional.absent()
|
||||
: Optional.of(pricingLogic.getTransferPrice(registry, targetId, now));
|
||||
if (feesAndCredits.isPresent()) {
|
||||
validateFeeChallenge(targetId, tld, now, feeTransfer, feesAndCredits.get());
|
||||
}
|
||||
HistoryEntry historyEntry = buildHistoryEntry(existingDomain, registry, now, period);
|
||||
DateTime automaticTransferTime = now.plus(registry.getAutomaticTransferLength());
|
||||
|
||||
DateTime automaticTransferTime =
|
||||
(superuserExtension == null)
|
||||
? now.plus(registry.getAutomaticTransferLength())
|
||||
: now.plusDays(superuserExtension.getAutomaticTransferLength());
|
||||
// If the domain will be in the auto-renew grace period at the moment of transfer, the transfer
|
||||
// will subsume the autorenew, so we don't add the normal extra year from the transfer.
|
||||
// The gaining registrar is still billed for the extra year; the losing registrar will get a
|
||||
|
@ -153,11 +179,15 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
|
|||
//
|
||||
// See b/19430703#comment17 and https://www.icann.org/news/advisory-2002-06-06-en for the
|
||||
// policy documentation for transfers subsuming autorenews within the autorenew grace period.
|
||||
int extraYears = 1;
|
||||
int extraYears = period.getValue();
|
||||
DomainResource domainAtTransferTime =
|
||||
existingDomain.cloneProjectedAtTime(automaticTransferTime);
|
||||
if (!domainAtTransferTime.getGracePeriodsOfType(GracePeriodStatus.AUTO_RENEW).isEmpty()) {
|
||||
extraYears--;
|
||||
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.
|
||||
DateTime serverApproveNewExpirationTime =
|
||||
|
@ -174,12 +204,16 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
|
|||
existingDomain,
|
||||
trid,
|
||||
gainingClientId,
|
||||
feesAndCredits.getTotalCost(),
|
||||
(feesAndCredits.isPresent())
|
||||
? Optional.of(feesAndCredits.get().getTotalCost())
|
||||
: Optional.absent(),
|
||||
now);
|
||||
// Create the transfer data that represents the pending transfer.
|
||||
TransferData pendingTransferData = createPendingTransferData(
|
||||
createTransferDataBuilder(existingDomain, automaticTransferTime, now),
|
||||
serverApproveEntities);
|
||||
TransferData pendingTransferData =
|
||||
createPendingTransferData(
|
||||
createTransferDataBuilder(existingDomain, automaticTransferTime, now),
|
||||
serverApproveEntities,
|
||||
period);
|
||||
// Create a poll message to notify the losing registrar that a transfer was requested.
|
||||
PollMessage requestPollMessage = createLosingTransferPollMessage(
|
||||
targetId, pendingTransferData, serverApproveNewExpirationTime, historyEntry)
|
||||
|
@ -206,7 +240,11 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
|
|||
.build();
|
||||
}
|
||||
|
||||
private void verifyTransferAllowed(DomainResource existingDomain, Period period, DateTime now)
|
||||
private void verifyTransferAllowed(
|
||||
DomainResource existingDomain,
|
||||
Period period,
|
||||
DateTime now,
|
||||
final DomainTransferRequestSuperuserExtension superuserExtension)
|
||||
throws EppException {
|
||||
verifyNoDisallowedStatuses(existingDomain, DISALLOWED_STATUSES);
|
||||
verifyAuthInfoPresentForResourceTransfer(authInfo);
|
||||
|
@ -219,7 +257,7 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
|
|||
if (gainingClientId.equals(existingDomain.getCurrentSponsorClientId())) {
|
||||
throw new ObjectAlreadySponsoredException();
|
||||
}
|
||||
verifyTransferPeriodIsOneYear(period);
|
||||
verifyTransferPeriod(period, superuserExtension);
|
||||
if (!isSuperuser) {
|
||||
checkAllowedAccessToTld(gainingClientId, existingDomain.getTld());
|
||||
verifyPremiumNameIsNotBlocked(targetId, now, gainingClientId);
|
||||
|
@ -227,7 +265,8 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
|
|||
}
|
||||
|
||||
/**
|
||||
* Verify that the transfer period is one year.
|
||||
* Verify that the transfer period is one year. If the superuser extension is being used, then it
|
||||
* can be zero.
|
||||
*
|
||||
* <p>Restricting transfers to one year is seemingly required by ICANN's <a
|
||||
* href="https://www.icann.org/resources/pages/policy-2012-03-07-en">Policy on Transfer of
|
||||
|
@ -246,10 +285,20 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
|
|||
* <p>Note that clients can omit the period element from the transfer EPP entirely, but then it
|
||||
* will simply default to one year.
|
||||
*/
|
||||
private static void verifyTransferPeriodIsOneYear(Period period) throws EppException {
|
||||
private static void verifyTransferPeriod(
|
||||
Period period, DomainTransferRequestSuperuserExtension superuserExtension)
|
||||
throws EppException {
|
||||
verifyUnitIsYears(period);
|
||||
if (period.getValue() != 1) {
|
||||
throw new TransferPeriodMustBeOneYearException();
|
||||
if (superuserExtension == null) {
|
||||
// If the superuser extension is not being used, then the period can only be one.
|
||||
if (period.getValue() != 1) {
|
||||
throw new TransferPeriodMustBeOneYearException();
|
||||
}
|
||||
} else {
|
||||
// If the superuser extension is being used, then the period can be one or zero.
|
||||
if (period.getValue() != 1 && period.getValue() != 0) {
|
||||
throw new InvalidTransferPeriodValueException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -296,13 +345,13 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
|
|||
}
|
||||
|
||||
private static ImmutableList<FeeTransformResponseExtension> createResponseExtensions(
|
||||
FeesAndCredits feesAndCredits, FeeTransferCommandExtension feeTransfer) {
|
||||
return feeTransfer == null
|
||||
Optional<FeesAndCredits> feesAndCredits, FeeTransferCommandExtension feeTransfer) {
|
||||
return (feeTransfer == null || !feesAndCredits.isPresent())
|
||||
? ImmutableList.<FeeTransformResponseExtension>of()
|
||||
: ImmutableList.of(feeTransfer.createResponseBuilder()
|
||||
.setFees(feesAndCredits.getFees())
|
||||
.setCredits(feesAndCredits.getCredits())
|
||||
.setCurrency(feesAndCredits.getCurrency())
|
||||
.setFees(feesAndCredits.get().getFees())
|
||||
.setCredits(feesAndCredits.get().getCredits())
|
||||
.setCurrency(feesAndCredits.get().getCurrency())
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import google.registry.model.billing.BillingEvent.Flag;
|
|||
import google.registry.model.billing.BillingEvent.Reason;
|
||||
import google.registry.model.domain.DomainResource;
|
||||
import google.registry.model.domain.GracePeriod;
|
||||
import google.registry.model.domain.Period;
|
||||
import google.registry.model.domain.rgp.GracePeriodStatus;
|
||||
import google.registry.model.eppcommon.Trid;
|
||||
import google.registry.model.poll.PendingActionNotificationResponse.DomainPendingActionNotificationResponse;
|
||||
|
@ -47,26 +48,29 @@ import org.joda.time.DateTime;
|
|||
*/
|
||||
public final class DomainTransferUtils {
|
||||
|
||||
/**
|
||||
* Sets up {@link TransferData} for a domain with links to entities for server approval.
|
||||
*/
|
||||
/** Sets up {@link TransferData} for a domain with links to entities for server approval. */
|
||||
public static TransferData createPendingTransferData(
|
||||
TransferData.Builder transferDataBuilder,
|
||||
ImmutableSet<TransferServerApproveEntity> serverApproveEntities) {
|
||||
ImmutableSet<TransferServerApproveEntity> serverApproveEntities,
|
||||
Period transferPeriod) {
|
||||
ImmutableSet.Builder<Key<? extends TransferServerApproveEntity>> serverApproveEntityKeys =
|
||||
new ImmutableSet.Builder<>();
|
||||
for (TransferServerApproveEntity entity : serverApproveEntities) {
|
||||
serverApproveEntityKeys.add(Key.create(entity));
|
||||
}
|
||||
if (transferPeriod.getValue() != 0) {
|
||||
// Unless superuser sets period to 0, add a transfer billing event.
|
||||
transferDataBuilder.setServerApproveBillingEvent(
|
||||
Key.create(getOnlyElement(filter(serverApproveEntities, BillingEvent.OneTime.class))));
|
||||
}
|
||||
return transferDataBuilder
|
||||
.setTransferStatus(TransferStatus.PENDING)
|
||||
.setServerApproveBillingEvent(Key.create(
|
||||
getOnlyElement(filter(serverApproveEntities, BillingEvent.OneTime.class))))
|
||||
.setServerApproveAutorenewEvent(Key.create(
|
||||
getOnlyElement(filter(serverApproveEntities, BillingEvent.Recurring.class))))
|
||||
.setServerApproveAutorenewPollMessage(Key.create(
|
||||
getOnlyElement(filter(serverApproveEntities, PollMessage.Autorenew.class))))
|
||||
.setServerApproveEntities(serverApproveEntityKeys.build())
|
||||
.setTransferPeriod(transferPeriod)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
@ -91,7 +95,7 @@ public final class DomainTransferUtils {
|
|||
DomainResource existingDomain,
|
||||
Trid trid,
|
||||
String gainingClientId,
|
||||
Money transferCost,
|
||||
Optional<Money> transferCost,
|
||||
DateTime now) {
|
||||
String targetId = existingDomain.getFullyQualifiedDomainName();
|
||||
// Create a TransferData for the server-approve case to use for the speculative poll messages.
|
||||
|
@ -101,15 +105,18 @@ public final class DomainTransferUtils {
|
|||
.setTransferStatus(TransferStatus.SERVER_APPROVED)
|
||||
.build();
|
||||
Registry registry = Registry.get(existingDomain.getTld());
|
||||
return new ImmutableSet.Builder<TransferServerApproveEntity>()
|
||||
.add(
|
||||
createTransferBillingEvent(
|
||||
automaticTransferTime,
|
||||
historyEntry,
|
||||
targetId,
|
||||
gainingClientId,
|
||||
registry,
|
||||
transferCost))
|
||||
ImmutableSet.Builder<TransferServerApproveEntity> builder = new ImmutableSet.Builder<>();
|
||||
if (transferCost.isPresent()) {
|
||||
builder.add(
|
||||
createTransferBillingEvent(
|
||||
automaticTransferTime,
|
||||
historyEntry,
|
||||
targetId,
|
||||
gainingClientId,
|
||||
registry,
|
||||
transferCost.get()));
|
||||
}
|
||||
return builder
|
||||
.addAll(
|
||||
createOptionalAutorenewCancellation(
|
||||
automaticTransferTime, historyEntry, targetId, existingDomain)
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
// 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.ParameterValuePolicyErrorException;
|
||||
|
||||
/** Domain transfer period must be zero or one year when using the superuser EPP extension. */
|
||||
public class InvalidTransferPeriodValueException extends ParameterValuePolicyErrorException {
|
||||
public InvalidTransferPeriodValueException() {
|
||||
super(
|
||||
"Domain transfer period must be zero or one year when using the superuser EPP extension.");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
// 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.");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
// 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;
|
||||
|
||||
/** Domain transfer period cannot be zero when using the fee transfer extension. */
|
||||
public class TransferPeriodZeroAndFeeTransferExtensionException
|
||||
extends StatusProhibitsOperationException {
|
||||
public TransferPeriodZeroAndFeeTransferExtensionException() {
|
||||
super("Domain transfer period cannot be zero when using the fee transfer extension.");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
// 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.AuthorizationErrorException;
|
||||
|
||||
/** Superuser extension used by non-superuser or not passed by tool. */
|
||||
public class UnauthorizedForSuperuserExtensionException extends AuthorizationErrorException {
|
||||
public UnauthorizedForSuperuserExtensionException() {
|
||||
super("Superuser extension used by non-superuser or not passed by tool.");
|
||||
}
|
||||
}
|
|
@ -245,9 +245,11 @@ public class DomainResource extends DomainBase
|
|||
// If we are within an autorenew grace period, the transfer will subsume the autorenew. There
|
||||
// will already be a cancellation written in advance by the transfer request flow, so we don't
|
||||
// need to worry about billing, but we do need to cancel out the expiration time increase.
|
||||
int extraYears = 1; // All transfers are one year.
|
||||
// The transfer period saved in the transfer data will be one year, unless the superuser
|
||||
// extension set the transfer period to zero.
|
||||
int extraYears = transferData.getTransferPeriod().getValue();
|
||||
if (domainAtTransferTime.getGracePeriodStatuses().contains(GracePeriodStatus.AUTO_RENEW)) {
|
||||
extraYears--;
|
||||
extraYears = 0;
|
||||
}
|
||||
// Set the expiration, autorenew events, and grace period for the transfer. (Transfer ends
|
||||
// all other graces).
|
||||
|
@ -261,14 +263,22 @@ public class DomainResource extends DomainBase
|
|||
extraYears))
|
||||
// Set the speculatively-written new autorenew events as the domain's autorenew events.
|
||||
.setAutorenewBillingEvent(transferData.getServerApproveAutorenewEvent())
|
||||
.setAutorenewPollMessage(transferData.getServerApproveAutorenewPollMessage())
|
||||
// Set the grace period using a key to the prescheduled transfer billing event. Not using
|
||||
// GracePeriod.forBillingEvent() here in order to avoid the actual Datastore fetch.
|
||||
.setGracePeriods(ImmutableSet.of(GracePeriod.create(
|
||||
GracePeriodStatus.TRANSFER,
|
||||
transferExpirationTime.plus(Registry.get(getTld()).getTransferGracePeriodLength()),
|
||||
transferData.getGainingClientId(),
|
||||
transferData.getServerApproveBillingEvent())));
|
||||
.setAutorenewPollMessage(transferData.getServerApproveAutorenewPollMessage());
|
||||
if (transferData.getTransferPeriod().getValue() == 1) {
|
||||
// Set the grace period using a key to the prescheduled transfer billing event. Not using
|
||||
// GracePeriod.forBillingEvent() here in order to avoid the actual Datastore fetch.
|
||||
builder.setGracePeriods(
|
||||
ImmutableSet.of(
|
||||
GracePeriod.create(
|
||||
GracePeriodStatus.TRANSFER,
|
||||
transferExpirationTime.plus(
|
||||
Registry.get(getTld()).getTransferGracePeriodLength()),
|
||||
transferData.getGainingClientId(),
|
||||
transferData.getServerApproveBillingEvent())));
|
||||
} else {
|
||||
// There won't be a billing event, so we don't need a grace period
|
||||
builder.setGracePeriods(ImmutableSet.of());
|
||||
}
|
||||
// Set all remaining transfer properties.
|
||||
setAutomaticTransferSuccessProperties(builder, transferData);
|
||||
// Finish projecting to now.
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
// 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.model.domain.superuser;
|
||||
|
||||
import google.registry.model.domain.Period;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
|
||||
/** A superuser extension that may be present on domain transfer request commands. */
|
||||
@XmlRootElement(name = "domainTransferRequest")
|
||||
public class DomainTransferRequestSuperuserExtension extends SuperuserExtension {
|
||||
// We need to specify the period here because the transfer object's period cannot be set to zero.
|
||||
@XmlElement(name = "renewalPeriod")
|
||||
Period renewalPeriod;
|
||||
|
||||
// The number of days before the transfer will be automatically approved by the server. A value of
|
||||
// zero means the transfer will happen immediately.
|
||||
@XmlElement(name = "automaticTransferLength")
|
||||
int automaticTransferLength;
|
||||
|
||||
public Period getRenewalPeriod() {
|
||||
return renewalPeriod;
|
||||
}
|
||||
|
||||
public int getAutomaticTransferLength() {
|
||||
return automaticTransferLength;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
// 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.model.domain.superuser;
|
||||
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.eppinput.EppInput.CommandExtension;
|
||||
import javax.xml.bind.annotation.XmlTransient;
|
||||
|
||||
/** Base class for superuser EPP extensions. */
|
||||
@XmlTransient
|
||||
public abstract class SuperuserExtension extends ImmutableObject implements CommandExtension {}
|
|
@ -0,0 +1,26 @@
|
|||
// 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.
|
||||
|
||||
@XmlSchema(
|
||||
namespace = "urn:google:params:xml:ns:superuser-1.0",
|
||||
xmlns = @XmlNs(prefix = "superuser", namespaceURI = "urn:google:params:xml:ns:superuser-1.0"),
|
||||
elementFormDefault = XmlNsForm.QUALIFIED)
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
package google.registry.model.domain.superuser;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlNs;
|
||||
import javax.xml.bind.annotation.XmlNsForm;
|
||||
import javax.xml.bind.annotation.XmlSchema;
|
|
@ -51,6 +51,7 @@ import google.registry.model.domain.metadata.MetadataExtension;
|
|||
import google.registry.model.domain.rgp.RgpUpdateExtension;
|
||||
import google.registry.model.domain.secdns.SecDnsCreateExtension;
|
||||
import google.registry.model.domain.secdns.SecDnsUpdateExtension;
|
||||
import google.registry.model.domain.superuser.DomainTransferRequestSuperuserExtension;
|
||||
import google.registry.model.eppinput.ResourceCommand.ResourceCheck;
|
||||
import google.registry.model.eppinput.ResourceCommand.SingleResourceCommand;
|
||||
import google.registry.model.host.HostCommand;
|
||||
|
@ -345,7 +346,8 @@ public class EppInput extends ImmutableObject {
|
|||
@XmlElementRef(type = MetadataExtension.class),
|
||||
@XmlElementRef(type = RgpUpdateExtension.class),
|
||||
@XmlElementRef(type = SecDnsCreateExtension.class),
|
||||
@XmlElementRef(type = SecDnsUpdateExtension.class) })
|
||||
@XmlElementRef(type = SecDnsUpdateExtension.class),
|
||||
@XmlElementRef(type = DomainTransferRequestSuperuserExtension.class) })
|
||||
@XmlElementWrapper
|
||||
List<CommandExtension> extension;
|
||||
|
||||
|
|
|
@ -25,6 +25,8 @@ import com.googlecode.objectify.condition.IfNull;
|
|||
import google.registry.model.Buildable;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.billing.BillingEvent;
|
||||
import google.registry.model.domain.Period;
|
||||
import google.registry.model.domain.Period.Unit;
|
||||
import google.registry.model.eppcommon.Trid;
|
||||
import google.registry.model.poll.PollMessage;
|
||||
import java.util.Set;
|
||||
|
@ -81,6 +83,14 @@ public class TransferData extends BaseTransferObject implements Buildable {
|
|||
/** The transaction id of the most recent transfer request (or null if there never was one). */
|
||||
Trid transferRequestTrid;
|
||||
|
||||
/**
|
||||
* The period to extend the registration upon completion of the transfer.
|
||||
*
|
||||
* <p>By default, domain transfers are for one year. This can be changed to zero by using the
|
||||
* superuser EPP extension.
|
||||
*/
|
||||
Period transferPeriod = Period.create(1, Unit.YEARS);
|
||||
|
||||
public ImmutableSet<Key<? extends TransferServerApproveEntity>> getServerApproveEntities() {
|
||||
return nullToEmptyImmutableCopy(serverApproveEntities);
|
||||
}
|
||||
|
@ -101,6 +111,10 @@ public class TransferData extends BaseTransferObject implements Buildable {
|
|||
return transferRequestTrid;
|
||||
}
|
||||
|
||||
public Period getTransferPeriod() {
|
||||
return transferPeriod;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder asBuilder() {
|
||||
return new Builder(clone(this));
|
||||
|
@ -145,6 +159,11 @@ public class TransferData extends BaseTransferObject implements Buildable {
|
|||
getInstance().transferRequestTrid = transferRequestTrid;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setTransferPeriod(Period transferPeriod) {
|
||||
getInstance().transferPeriod = transferPeriod;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -42,6 +42,8 @@ import google.registry.gcs.GcsUtils;
|
|||
import google.registry.mapreduce.MapreduceRunner;
|
||||
import google.registry.model.billing.BillingEvent;
|
||||
import google.registry.model.domain.DomainResource;
|
||||
import google.registry.model.domain.Period;
|
||||
import google.registry.model.domain.Period.Unit;
|
||||
import google.registry.model.poll.PollMessage;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.transfer.TransferData;
|
||||
|
@ -203,10 +205,13 @@ public class RdeDomainImportAction implements Runnable {
|
|||
domain,
|
||||
historyEntry.getTrid(),
|
||||
transferData.getGainingClientId(),
|
||||
transferCost,
|
||||
Optional.of(transferCost),
|
||||
transferData.getTransferRequestTime());
|
||||
transferData =
|
||||
createPendingTransferData(transferData.asBuilder(), serverApproveEntities);
|
||||
createPendingTransferData(
|
||||
transferData.asBuilder(),
|
||||
serverApproveEntities,
|
||||
Period.create(1, Unit.YEARS));
|
||||
// Create a poll message to notify the losing registrar that a transfer was requested.
|
||||
PollMessage requestPollMessage = createLosingTransferPollMessage(domain.getRepoId(),
|
||||
transferData, transferData.getPendingTransferExpirationTime(), historyEntry)
|
||||
|
|
41
java/google/registry/xml/xsd/superuser.xsd
Normal file
41
java/google/registry/xml/xsd/superuser.xsd
Normal file
|
@ -0,0 +1,41 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<schema
|
||||
xmlns="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns:superuser="urn:google:params:xml:ns:superuser-1.0"
|
||||
targetNamespace="urn:google:params:xml:ns:superuser-1.0"
|
||||
elementFormDefault="qualified">
|
||||
|
||||
<element name="domainTransferRequest"
|
||||
type="superuser:domainTransferRequest" />
|
||||
|
||||
<complexType name="domainTransferRequest">
|
||||
<all>
|
||||
<element name="renewalPeriod" type="superuser:periodType" />
|
||||
<element name="automaticTransferLength" type="nonNegativeInteger" />
|
||||
</all>
|
||||
</complexType>
|
||||
|
||||
<complexType name="periodType">
|
||||
<simpleContent>
|
||||
<extension base="superuser:pLimitType">
|
||||
<attribute name="unit" type="superuser:pUnitType"
|
||||
use="required"/>
|
||||
</extension>
|
||||
</simpleContent>
|
||||
</complexType>
|
||||
|
||||
<simpleType name="pLimitType">
|
||||
<restriction base="unsignedShort">
|
||||
<minInclusive value="0"/>
|
||||
<maxInclusive value="99"/>
|
||||
</restriction>
|
||||
</simpleType>
|
||||
|
||||
<simpleType name="pUnitType">
|
||||
<restriction base="token">
|
||||
<enumeration value="y"/>
|
||||
<enumeration value="m"/>
|
||||
</restriction>
|
||||
</simpleType>
|
||||
|
||||
</schema>
|
Loading…
Add table
Add a link
Reference in a new issue