From afb2811704e03a7323bf72bb51dda8692723f644 Mon Sep 17 00:00:00 2001 From: cgoldfeder Date: Fri, 23 Sep 2016 14:47:24 -0700 Subject: [PATCH] Flatten the domain renew flow ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=134120002 --- .../flows/domain/DomainRenewFlow.java | 240 +++++++++--------- .../flows/domain/DomainRenewFlowTest.java | 4 +- 2 files changed, 126 insertions(+), 118 deletions(-) diff --git a/java/google/registry/flows/domain/DomainRenewFlow.java b/java/google/registry/flows/domain/DomainRenewFlow.java index 67bab50aa..656b11d26 100644 --- a/java/google/registry/flows/domain/DomainRenewFlow.java +++ b/java/google/registry/flows/domain/DomainRenewFlow.java @@ -14,7 +14,10 @@ package google.registry.flows.domain; -import static com.google.common.base.Preconditions.checkNotNull; +import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence; +import static google.registry.flows.ResourceFlowUtils.verifyNoDisallowedStatuses; +import static google.registry.flows.ResourceFlowUtils.verifyOptionalAuthInfoForResource; +import static google.registry.flows.ResourceFlowUtils.verifyResourceOwnership; import static google.registry.flows.domain.DomainFlowUtils.checkAllowedAccessToTld; import static google.registry.flows.domain.DomainFlowUtils.newAutorenewBillingEvent; import static google.registry.flows.domain.DomainFlowUtils.newAutorenewPollMessage; @@ -22,6 +25,7 @@ import static google.registry.flows.domain.DomainFlowUtils.updateAutorenewRecurr import static google.registry.flows.domain.DomainFlowUtils.validateFeeChallenge; import static google.registry.flows.domain.DomainFlowUtils.verifyUnitIsYears; import static google.registry.model.domain.DomainResource.MAX_REGISTRATION_YEARS; +import static google.registry.model.domain.DomainResource.extendRegistrationWithCap; import static google.registry.model.domain.fee.Fee.FEE_RENEW_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER; import static google.registry.model.eppoutput.Result.Code.SUCCESS; import static google.registry.model.ofy.ObjectifyService.ofy; @@ -35,180 +39,184 @@ import com.googlecode.objectify.Key; import google.registry.flows.EppException; import google.registry.flows.EppException.ObjectPendingTransferException; import google.registry.flows.EppException.ParameterValueRangeErrorException; -import google.registry.flows.OwnedResourceMutateFlow; +import google.registry.flows.FlowModule.ClientId; +import google.registry.flows.FlowModule.TargetId; +import google.registry.flows.LoggedInFlow; +import google.registry.flows.TransactionalFlow; import google.registry.model.billing.BillingEvent; +import google.registry.model.billing.BillingEvent.OneTime; import google.registry.model.billing.BillingEvent.Reason; import google.registry.model.domain.DomainCommand.Renew; import google.registry.model.domain.DomainRenewData; import google.registry.model.domain.DomainResource; import google.registry.model.domain.GracePeriod; -import google.registry.model.domain.Period; import google.registry.model.domain.fee.BaseFee.FeeType; import google.registry.model.domain.fee.Fee; import google.registry.model.domain.fee.FeeTransformCommandExtension; +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.eppcommon.AuthInfo; import google.registry.model.eppcommon.StatusValue; +import google.registry.model.eppinput.ResourceCommand; import google.registry.model.eppoutput.EppOutput; import google.registry.model.poll.PollMessage; import google.registry.model.registry.Registry; import google.registry.model.reporting.HistoryEntry; import google.registry.model.transfer.TransferStatus; -import java.util.Set; import javax.inject.Inject; import org.joda.money.Money; import org.joda.time.DateTime; /** - * An EPP flow that updates a domain resource. + * An EPP flow that renews a domain. * - * @error {@link google.registry.flows.domain.DomainFlowUtils.NotAuthorizedForTldException} + *

Registrars can use this flow to manually extend the length of a registration, instead of + * relying on domain auto-renewal (where the registry performs an automatic one-year renewal at the + * instant a domain would expire). + * + *

ICANN prohibits any registration from being longer than ten years so if the request would + * result in a registration greater than ten years long it will fail. In practice this means it's + * impossible to request a ten year renewal, since that will always cause the new registration to be + * longer than 10 years unless it comes in at the exact millisecond that the domain would have + * expired. + * + * @error {@link google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException} * @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException} - * @error {@link google.registry.flows.ResourceMutateFlow.ResourceDoesNotExistException} - * @error {@link google.registry.flows.SingleResourceFlow.ResourceStatusProhibitsOperationException} + * @error {@link google.registry.flows.exceptions.ResourceStatusProhibitsOperationException} * @error {@link DomainFlowUtils.BadPeriodUnitException} * @error {@link DomainFlowUtils.CurrencyUnitMismatchException} * @error {@link DomainFlowUtils.CurrencyValueScaleException} * @error {@link DomainFlowUtils.FeesMismatchException} * @error {@link DomainFlowUtils.FeesRequiredForPremiumNameException} + * @error {@link DomainFlowUtils.NotAuthorizedForTldException} * @error {@link DomainFlowUtils.UnsupportedFeeAttributeException} * @error {@link DomainRenewFlow.DomainHasPendingTransferException} * @error {@link DomainRenewFlow.ExceedsMaxRegistrationYearsException} * @error {@link DomainRenewFlow.IncorrectCurrentExpirationDateException} */ -public class DomainRenewFlow extends OwnedResourceMutateFlow { +public final class DomainRenewFlow extends LoggedInFlow implements TransactionalFlow { - private static final Set RENEW_DISALLOWED_STATUSES = ImmutableSet.of( + private static final ImmutableSet RENEW_DISALLOWED_STATUSES = ImmutableSet.of( StatusValue.CLIENT_RENEW_PROHIBITED, StatusValue.PENDING_DELETE, StatusValue.SERVER_RENEW_PROHIBITED); - protected FeeTransformCommandExtension feeRenew; - protected Money renewCost; - - protected Optional extraFlowLogic; - + @Inject ResourceCommand resourceCommand; + @Inject Optional authInfo; + @Inject @ClientId String clientId; + @Inject @TargetId String targetId; + @Inject HistoryEntry.Builder historyBuilder; @Inject DomainRenewFlow() {} @Override - protected Set getDisallowedStatuses() { - return RENEW_DISALLOWED_STATUSES; - } - - @Override - public final void initResourceCreateOrMutateFlow() throws EppException { + protected final void initLoggedInFlow() throws EppException { + registerExtensions(MetadataExtension.class); registerExtensions(FEE_RENEW_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER); - feeRenew = + } + + @Override + public final EppOutput run() throws EppException { + Renew command = (Renew) resourceCommand; + // Loads the target resource if it exists + DomainResource existingDomain = loadAndVerifyExistence(DomainResource.class, targetId, now); + verifyRenewAllowed(authInfo, existingDomain, command); + int years = command.getPeriod().getValue(); + Money renewCost = getDomainRenewCost(targetId, now, years); + FeeTransformCommandExtension feeRenew = eppInput.getFirstExtensionOfClasses(FEE_RENEW_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER); - extraFlowLogic = RegistryExtraFlowLogicProxy.newInstanceForDomain(existingResource); - } - - @Override - protected void verifyMutationOnOwnedResourceAllowed() throws EppException { - checkAllowedAccessToTld(getAllowedTlds(), existingResource.getTld()); - // Verify that the resource does not have a pending transfer on it. - if (existingResource.getTransferData().getTransferStatus() == TransferStatus.PENDING) { - throw new DomainHasPendingTransferException(targetId); + validateFeeChallenge(targetId, existingDomain.getTld(), now, feeRenew, renewCost); + HistoryEntry historyEntry = historyBuilder + .setType(HistoryEntry.Type.DOMAIN_RENEW) + .setPeriod(command.getPeriod()) + .setModificationTime(now) + .setParent(Key.create(existingDomain)) + .build(); + DateTime oldExpirationTime = existingDomain.getRegistrationExpirationTime(); + DateTime newExpirationTime = leapSafeAddYears(oldExpirationTime, years); // Uncapped + if (extendRegistrationWithCap(now, oldExpirationTime, years).isBefore(newExpirationTime)) { + throw new ExceedsMaxRegistrationYearsException(); } - verifyUnitIsYears(command.getPeriod()); - // If the date they specify doesn't match the expiration, fail. (This is an idempotence check). - if (!command.getCurrentExpirationDate().equals( - existingResource.getRegistrationExpirationTime().toLocalDate())) { - throw new IncorrectCurrentExpirationDateException(); - } - renewCost = getDomainRenewCost(targetId, now, command.getPeriod().getValue()); - validateFeeChallenge( - targetId, existingResource.getTld(), now, feeRenew, renewCost); - } - - @Override - protected DomainResource createOrMutateResource() throws EppException { - DateTime newExpirationTime = leapSafeAddYears( - existingResource.getRegistrationExpirationTime(), command.getPeriod().getValue()); + String tld = existingDomain.getTld(); // Bill for this explicit renew itself. - BillingEvent.OneTime explicitRenewEvent = new BillingEvent.OneTime.Builder() - .setReason(Reason.RENEW) - .setTargetId(targetId) - .setClientId(getClientId()) - .setPeriodYears(command.getPeriod().getValue()) - .setCost(checkNotNull(renewCost)) - .setEventTime(now) - .setBillingTime( - now.plus(Registry.get(existingResource.getTld()).getRenewGracePeriodLength())) + BillingEvent.OneTime explicitRenewEvent = + createRenewBillingEvent(tld, renewCost, years, historyEntry); + // Create a new autorenew billing event and poll message starting at the new expiration time. + BillingEvent.Recurring newAutorenewEvent = newAutorenewBillingEvent(existingDomain) + .setEventTime(newExpirationTime) + .setParent(historyEntry) + .build(); + PollMessage.Autorenew newAutorenewPollMessage = newAutorenewPollMessage(existingDomain) + .setEventTime(newExpirationTime) .setParent(historyEntry) .build(); // End the old autorenew billing event and poll message now. This may delete the poll message. - updateAutorenewRecurrenceEndTime(existingResource, now); - // Create a new autorenew billing event and poll message starting at the new expiration time. - BillingEvent.Recurring newAutorenewEvent = newAutorenewBillingEvent(existingResource) - .setEventTime(newExpirationTime) - .setParent(historyEntry) - .build(); - PollMessage.Autorenew newAutorenewPollMessage = newAutorenewPollMessage(existingResource) - .setEventTime(newExpirationTime) - .setParent(historyEntry) - .build(); - + updateAutorenewRecurrenceEndTime(existingDomain, now); // Handle extra flow logic, if any. + Optional extraFlowLogic = + RegistryExtraFlowLogicProxy.newInstanceForDomain(existingDomain); if (extraFlowLogic.isPresent()) { extraFlowLogic.get().performAdditionalDomainRenewLogic( - existingResource, - getClientId(), - now, - command.getPeriod().getValue(), - eppInput, - historyEntry); + existingDomain, clientId, now, years, eppInput, historyEntry); + extraFlowLogic.get().commitAdditionalLogicChanges(); } - - ofy().save().entities(explicitRenewEvent, newAutorenewEvent, newAutorenewPollMessage); - return existingResource.asBuilder() + DomainResource newDomain = existingDomain.asBuilder() .setRegistrationExpirationTime(newExpirationTime) .setAutorenewBillingEvent(Key.create(newAutorenewEvent)) .setAutorenewPollMessage(Key.create(newAutorenewPollMessage)) .addGracePeriod(GracePeriod.forBillingEvent(GracePeriodStatus.RENEW, explicitRenewEvent)) .build(); - } - - @Override - protected void verifyNewStateIsAllowed() throws EppException { - if (leapSafeAddYears(now, MAX_REGISTRATION_YEARS) - .isBefore(newResource.getRegistrationExpirationTime())) { - throw new ExceedsMaxRegistrationYearsException(); - } - } - - /** Commit any extra flow logic. */ - @Override - protected final void modifyRelatedResources() { - if (extraFlowLogic.isPresent()) { - extraFlowLogic.get().commitAdditionalLogicChanges(); - } - } - - @Override - protected final HistoryEntry.Type getHistoryEntryType() { - return HistoryEntry.Type.DOMAIN_RENEW; - } - - @Override - protected final Period getCommandPeriod() { - return command.getPeriod(); - } - - @Override - protected final EppOutput getOutput() { + ofy().save().entities( + newDomain, historyEntry, explicitRenewEvent, newAutorenewEvent, newAutorenewPollMessage); return createOutput( SUCCESS, - DomainRenewData.create( - newResource.getFullyQualifiedDomainName(), newResource.getRegistrationExpirationTime()), - (feeRenew == null) - ? null - : ImmutableList.of( - feeRenew - .createResponseBuilder() - .setCurrency(renewCost.getCurrencyUnit()) - .setFees( - ImmutableList.of(Fee.create(renewCost.getAmount(), FeeType.RENEW))) - .build())); + DomainRenewData.create(targetId, newExpirationTime), + createResponseExtensions(renewCost, feeRenew)); + } + + private void verifyRenewAllowed( + Optional authInfo, + DomainResource existingDomain, + Renew command) throws EppException { + verifyOptionalAuthInfoForResource(authInfo, existingDomain); + verifyNoDisallowedStatuses(existingDomain, RENEW_DISALLOWED_STATUSES); + if (!isSuperuser) { + verifyResourceOwnership(clientId, existingDomain); + } + checkAllowedAccessToTld(getAllowedTlds(), existingDomain.getTld()); + // Verify that the resource does not have a pending transfer on it. + if (existingDomain.getTransferData().getTransferStatus() == TransferStatus.PENDING) { + throw new DomainHasPendingTransferException(targetId); + } + verifyUnitIsYears(command.getPeriod()); + // If the date they specify doesn't match the expiration, fail. (This is an idempotence check). + if (!command.getCurrentExpirationDate().equals( + existingDomain.getRegistrationExpirationTime().toLocalDate())) { + throw new IncorrectCurrentExpirationDateException(); + } + } + + private OneTime createRenewBillingEvent( + String tld, Money renewCost, int years, HistoryEntry historyEntry) { + return new BillingEvent.OneTime.Builder() + .setReason(Reason.RENEW) + .setTargetId(targetId) + .setClientId(clientId) + .setPeriodYears(years) + .setCost(renewCost) + .setEventTime(now) + .setBillingTime(now.plus(Registry.get(tld).getRenewGracePeriodLength())) + .setParent(historyEntry) + .build(); + } + + private ImmutableList createResponseExtensions( + Money renewCost, FeeTransformCommandExtension feeRenew) { + return (feeRenew == null) ? null : ImmutableList.of(feeRenew + .createResponseBuilder() + .setCurrency(renewCost.getCurrencyUnit()) + .setFees(ImmutableList.of(Fee.create(renewCost.getAmount(), FeeType.RENEW))) + .build()); } /** The domain has a pending transfer on it and so can't be explicitly renewed. */ diff --git a/javatests/google/registry/flows/domain/DomainRenewFlowTest.java b/javatests/google/registry/flows/domain/DomainRenewFlowTest.java index f9cfe6b0f..f22bf94f0 100644 --- a/javatests/google/registry/flows/domain/DomainRenewFlowTest.java +++ b/javatests/google/registry/flows/domain/DomainRenewFlowTest.java @@ -36,9 +36,8 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedMap; import com.googlecode.objectify.Key; import google.registry.flows.ResourceFlowTestCase; +import google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException; import google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException; -import google.registry.flows.ResourceMutateFlow.ResourceDoesNotExistException; -import google.registry.flows.SingleResourceFlow.ResourceStatusProhibitsOperationException; import google.registry.flows.domain.DomainFlowUtils.BadPeriodUnitException; import google.registry.flows.domain.DomainFlowUtils.CurrencyUnitMismatchException; import google.registry.flows.domain.DomainFlowUtils.CurrencyValueScaleException; @@ -49,6 +48,7 @@ import google.registry.flows.domain.DomainFlowUtils.UnsupportedFeeAttributeExcep import google.registry.flows.domain.DomainRenewFlow.DomainHasPendingTransferException; import google.registry.flows.domain.DomainRenewFlow.ExceedsMaxRegistrationYearsException; import google.registry.flows.domain.DomainRenewFlow.IncorrectCurrentExpirationDateException; +import google.registry.flows.exceptions.ResourceStatusProhibitsOperationException; import google.registry.model.billing.BillingEvent; import google.registry.model.billing.BillingEvent.Flag; import google.registry.model.billing.BillingEvent.Reason;