diff --git a/java/google/registry/flows/FlowModule.java b/java/google/registry/flows/FlowModule.java index aab0f04ac..669ca333f 100644 --- a/java/google/registry/flows/FlowModule.java +++ b/java/google/registry/flows/FlowModule.java @@ -16,16 +16,19 @@ package google.registry.flows; import static com.google.common.base.Preconditions.checkState; +import com.google.common.base.Optional; import com.google.common.base.Strings; import dagger.Module; import dagger.Provides; import google.registry.flows.exceptions.OnlyToolCanPassMetadataException; import google.registry.flows.picker.FlowPicker; import google.registry.model.domain.metadata.MetadataExtension; +import google.registry.model.eppcommon.AuthInfo; import google.registry.model.eppcommon.Trid; import google.registry.model.eppinput.EppInput; import google.registry.model.eppinput.EppInput.ResourceCommandWrapper; import google.registry.model.eppinput.ResourceCommand; +import google.registry.model.eppinput.ResourceCommand.SingleResourceCommand; import google.registry.model.reporting.HistoryEntry; import java.lang.annotation.Documented; import javax.inject.Qualifier; @@ -177,6 +180,12 @@ public class FlowModule { .getResourceCommand(); } + @Provides + @FlowScope + static Optional provideAuthInfo(ResourceCommand resourceCommand) { + return Optional.fromNullable(((SingleResourceCommand) resourceCommand).getAuthInfo()); + } + /** * Provides a partially filled in {@link HistoryEntry} builder. * diff --git a/java/google/registry/flows/ResourceFlowUtils.java b/java/google/registry/flows/ResourceFlowUtils.java index 88065d649..6450779d5 100644 --- a/java/google/registry/flows/ResourceFlowUtils.java +++ b/java/google/registry/flows/ResourceFlowUtils.java @@ -20,6 +20,7 @@ import static google.registry.model.domain.DomainResource.extendRegistrationWith import static google.registry.model.ofy.ObjectifyService.ofy; import com.google.common.base.Function; +import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -223,6 +224,14 @@ public class ResourceFlowUtils { } } + /** Check that the given AuthInfo is either missing or else is valid for the given resource. */ + public static void verifyOptionalAuthInfoForResource( + Optional authInfo, EppResource resource) throws EppException { + if (authInfo.isPresent()) { + verifyAuthInfoForResource(authInfo.get(), resource); + } + } + /** Check that the given AuthInfo is valid for the given resource. */ public static void verifyAuthInfoForResource(AuthInfo authInfo, EppResource resource) throws EppException { diff --git a/java/google/registry/flows/contact/ContactCheckFlow.java b/java/google/registry/flows/contact/ContactCheckFlow.java index 1e1346dd3..e8b2b0237 100644 --- a/java/google/registry/flows/contact/ContactCheckFlow.java +++ b/java/google/registry/flows/contact/ContactCheckFlow.java @@ -55,6 +55,6 @@ public class ContactCheckFlow extends LoggedInFlow { boolean unused = !existingIds.contains(id); checks.add(ContactCheck.create(unused, id, unused ? null : "In use")); } - return createOutput(Success, ContactCheckData.create(checks.build()), null); + return createOutput(Success, ContactCheckData.create(checks.build())); } } diff --git a/java/google/registry/flows/contact/ContactCreateFlow.java b/java/google/registry/flows/contact/ContactCreateFlow.java index f034347ae..53d26fcf0 100644 --- a/java/google/registry/flows/contact/ContactCreateFlow.java +++ b/java/google/registry/flows/contact/ContactCreateFlow.java @@ -23,6 +23,7 @@ import static google.registry.model.ofy.ObjectifyService.ofy; import com.googlecode.objectify.Key; import google.registry.flows.EppException; +import google.registry.flows.FlowModule.ClientId; import google.registry.flows.LoggedInFlow; import google.registry.flows.TransactionalFlow; import google.registry.flows.exceptions.ResourceAlreadyExistsException; @@ -49,6 +50,7 @@ import javax.inject.Inject; public class ContactCreateFlow extends LoggedInFlow implements TransactionalFlow { @Inject ResourceCommand resourceCommand; + @Inject @ClientId String clientId; @Inject HistoryEntry.Builder historyBuilder; @Inject ContactCreateFlow() {} @@ -66,8 +68,8 @@ public class ContactCreateFlow extends LoggedInFlow implements TransactionalFlow Builder builder = new Builder(); command.applyTo(builder); ContactResource newResource = builder - .setCreationClientId(getClientId()) - .setCurrentSponsorClientId(getClientId()) + .setCreationClientId(clientId) + .setCurrentSponsorClientId(clientId) .setRepoId(createContactHostRoid(ObjectifyService.allocateId())) .build(); validateAsciiPostalInfo(newResource.getInternationalizedPostalInfo()); diff --git a/java/google/registry/flows/contact/ContactDeleteFlow.java b/java/google/registry/flows/contact/ContactDeleteFlow.java index 09d2affe3..fa300f68b 100644 --- a/java/google/registry/flows/contact/ContactDeleteFlow.java +++ b/java/google/registry/flows/contact/ContactDeleteFlow.java @@ -15,19 +15,21 @@ package google.registry.flows.contact; import static google.registry.flows.ResourceFlowUtils.failfastForAsyncDelete; -import static google.registry.flows.ResourceFlowUtils.verifyAuthInfoForResource; 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.model.EppResourceUtils.loadByUniqueId; import static google.registry.model.eppoutput.Result.Code.SuccessWithActionPending; import static google.registry.model.ofy.ObjectifyService.ofy; import com.google.common.base.Function; +import com.google.common.base.Optional; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.googlecode.objectify.Key; import google.registry.config.ConfigModule.Config; import google.registry.flows.EppException; +import google.registry.flows.FlowModule.ClientId; import google.registry.flows.LoggedInFlow; import google.registry.flows.TransactionalFlow; import google.registry.flows.async.AsyncFlowUtils; @@ -38,6 +40,7 @@ import google.registry.model.contact.ContactCommand.Delete; import google.registry.model.contact.ContactResource; import google.registry.model.domain.DomainBase; import google.registry.model.domain.metadata.MetadataExtension; +import google.registry.model.eppcommon.AuthInfo; import google.registry.model.eppcommon.StatusValue; import google.registry.model.eppinput.ResourceCommand; import google.registry.model.eppoutput.EppOutput; @@ -62,6 +65,8 @@ public class ContactDeleteFlow extends LoggedInFlow implements TransactionalFlow StatusValue.SERVER_DELETE_PROHIBITED); @Inject ResourceCommand resourceCommand; + @Inject @ClientId String clientId; + @Inject Optional authInfo; @Inject @Config("asyncDeleteFlowMapreduceDelay") Duration mapreduceDelay; @Inject HistoryEntry.Builder historyBuilder; @Inject ContactDeleteFlow() {} @@ -89,11 +94,9 @@ public class ContactDeleteFlow extends LoggedInFlow implements TransactionalFlow throw new ResourceToMutateDoesNotExistException(ContactResource.class, targetId); } verifyNoDisallowedStatuses(existingResource, DISALLOWED_STATUSES); - if (command.getAuthInfo() != null) { - verifyAuthInfoForResource(command.getAuthInfo(), existingResource); - } + verifyOptionalAuthInfoForResource(authInfo, existingResource); if (!isSuperuser) { - verifyResourceOwnership(getClientId(), existingResource); + verifyResourceOwnership(clientId, existingResource); } AsyncFlowUtils.enqueueMapreduceAction( DeleteContactResourceAction.class, @@ -101,7 +104,7 @@ public class ContactDeleteFlow extends LoggedInFlow implements TransactionalFlow DeleteEppResourceAction.PARAM_RESOURCE_KEY, Key.create(existingResource).getString(), DeleteEppResourceAction.PARAM_REQUESTING_CLIENT_ID, - getClientId(), + clientId, DeleteEppResourceAction.PARAM_IS_SUPERUSER, Boolean.toString(isSuperuser)), mapreduceDelay); @@ -112,6 +115,6 @@ public class ContactDeleteFlow extends LoggedInFlow implements TransactionalFlow .setModificationTime(now) .setParent(Key.create(existingResource)); ofy().save().entities(newResource, historyBuilder.build()); - return createOutput(SuccessWithActionPending, null, null); + return createOutput(SuccessWithActionPending); } } diff --git a/java/google/registry/flows/contact/ContactFlowUtils.java b/java/google/registry/flows/contact/ContactFlowUtils.java index a812fe46e..661f4d4ba 100644 --- a/java/google/registry/flows/contact/ContactFlowUtils.java +++ b/java/google/registry/flows/contact/ContactFlowUtils.java @@ -18,6 +18,7 @@ import static google.registry.model.contact.PostalInfo.Type.INTERNATIONALIZED; import com.google.common.base.CharMatcher; import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; import google.registry.flows.EppException; import google.registry.flows.EppException.ParameterValuePolicyErrorException; @@ -25,6 +26,11 @@ import google.registry.flows.EppException.ParameterValueSyntaxErrorException; import google.registry.model.contact.ContactAddress; import google.registry.model.contact.ContactResource; import google.registry.model.contact.PostalInfo; +import google.registry.model.poll.PendingActionNotificationResponse.ContactPendingActionNotificationResponse; +import google.registry.model.poll.PollMessage; +import google.registry.model.reporting.HistoryEntry; +import google.registry.model.transfer.TransferData; +import google.registry.model.transfer.TransferResponse.ContactTransferResponse; import java.util.Set; import javax.annotation.Nullable; @@ -50,7 +56,7 @@ public class ContactFlowUtils { } } } - + /** Check contact's state against server policy. */ static void validateContactAgainstPolicy(ContactResource contact) throws EppException { if (contact.getDisclose() != null && !contact.getDisclose().getFlag()) { @@ -58,6 +64,49 @@ public class ContactFlowUtils { } } + /** Create a poll message for the gaining client in a transfer. */ + static PollMessage createGainingTransferPollMessage( + String targetId, TransferData transferData, HistoryEntry historyEntry) { + return new PollMessage.OneTime.Builder() + .setClientId(transferData.getGainingClientId()) + .setEventTime(transferData.getPendingTransferExpirationTime()) + .setMsg(transferData.getTransferStatus().getMessage()) + .setResponseData(ImmutableList.of( + createTransferResponse(targetId, transferData), + ContactPendingActionNotificationResponse.create( + targetId, + transferData.getTransferStatus().isApproved(), + transferData.getTransferRequestTrid(), + historyEntry.getModificationTime()))) + .setParent(historyEntry) + .build(); + } + + /** Create a poll message for the losing client in a transfer. */ + static PollMessage createLosingTransferPollMessage( + String targetId, TransferData transferData, HistoryEntry historyEntry) { + return new PollMessage.OneTime.Builder() + .setClientId(transferData.getLosingClientId()) + .setEventTime(transferData.getPendingTransferExpirationTime()) + .setMsg(transferData.getTransferStatus().getMessage()) + .setResponseData(ImmutableList.of(createTransferResponse(targetId, transferData))) + .setParent(historyEntry) + .build(); + } + + /** Create a {@link ContactTransferResponse} off of the info in a {@link TransferData}. */ + static ContactTransferResponse createTransferResponse( + String targetId, TransferData transferData) { + return new ContactTransferResponse.Builder() + .setContactId(targetId) + .setGainingClientId(transferData.getGainingClientId()) + .setLosingClientId(transferData.getLosingClientId()) + .setPendingTransferExpirationTime(transferData.getPendingTransferExpirationTime()) + .setTransferRequestTime(transferData.getTransferRequestTime()) + .setTransferStatus(transferData.getTransferStatus()) + .build(); + } + /** Declining contact disclosure is disallowed by server policy. */ static class DeclineContactDisclosureFieldDisallowedPolicyException extends ParameterValuePolicyErrorException { @@ -65,7 +114,7 @@ public class ContactFlowUtils { super("Declining contact disclosure is disallowed by server policy."); } } - + /** Internationalized postal infos can only contain ASCII characters. */ static class BadInternationalizedPostalInfoException extends ParameterValueSyntaxErrorException { public BadInternationalizedPostalInfoException() { diff --git a/java/google/registry/flows/contact/ContactInfoFlow.java b/java/google/registry/flows/contact/ContactInfoFlow.java index 350da8f24..9adcad333 100644 --- a/java/google/registry/flows/contact/ContactInfoFlow.java +++ b/java/google/registry/flows/contact/ContactInfoFlow.java @@ -14,16 +14,18 @@ package google.registry.flows.contact; -import static google.registry.flows.ResourceFlowUtils.verifyAuthInfoForResource; +import static google.registry.flows.ResourceFlowUtils.verifyOptionalAuthInfoForResource; import static google.registry.model.EppResourceUtils.cloneResourceWithLinkedStatus; import static google.registry.model.EppResourceUtils.loadByUniqueId; import static google.registry.model.eppoutput.Result.Code.Success; +import com.google.common.base.Optional; import google.registry.flows.EppException; import google.registry.flows.LoggedInFlow; import google.registry.flows.exceptions.ResourceToQueryDoesNotExistException; import google.registry.model.contact.ContactCommand.Info; import google.registry.model.contact.ContactResource; +import google.registry.model.eppcommon.AuthInfo; import google.registry.model.eppinput.ResourceCommand; import google.registry.model.eppoutput.EppOutput; import javax.inject.Inject; @@ -36,6 +38,7 @@ import javax.inject.Inject; public class ContactInfoFlow extends LoggedInFlow { @Inject ResourceCommand resourceCommand; + @Inject Optional authInfo; @Inject ContactInfoFlow() {} @Override @@ -46,9 +49,7 @@ public class ContactInfoFlow extends LoggedInFlow { if (existingResource == null) { throw new ResourceToQueryDoesNotExistException(ContactResource.class, targetId); } - if (command.getAuthInfo() != null) { - verifyAuthInfoForResource(command.getAuthInfo(), existingResource); - } - return createOutput(Success, cloneResourceWithLinkedStatus(existingResource, now), null); + verifyOptionalAuthInfoForResource(authInfo, existingResource); + return createOutput(Success, cloneResourceWithLinkedStatus(existingResource, now)); } } diff --git a/java/google/registry/flows/contact/ContactTransferApproveFlow.java b/java/google/registry/flows/contact/ContactTransferApproveFlow.java index f68f6e27b..b3e4dbc90 100644 --- a/java/google/registry/flows/contact/ContactTransferApproveFlow.java +++ b/java/google/registry/flows/contact/ContactTransferApproveFlow.java @@ -14,17 +14,18 @@ package google.registry.flows.contact; -import static google.registry.flows.ResourceFlowUtils.createPendingTransferNotificationResponse; -import static google.registry.flows.ResourceFlowUtils.createTransferResponse; -import static google.registry.flows.ResourceFlowUtils.verifyAuthInfoForResource; +import static google.registry.flows.ResourceFlowUtils.verifyOptionalAuthInfoForResource; import static google.registry.flows.ResourceFlowUtils.verifyResourceOwnership; +import static google.registry.flows.contact.ContactFlowUtils.createGainingTransferPollMessage; +import static google.registry.flows.contact.ContactFlowUtils.createTransferResponse; import static google.registry.model.EppResourceUtils.loadByUniqueId; import static google.registry.model.eppoutput.Result.Code.Success; import static google.registry.model.ofy.ObjectifyService.ofy; -import com.google.common.collect.ImmutableList; +import com.google.common.base.Optional; import com.googlecode.objectify.Key; import google.registry.flows.EppException; +import google.registry.flows.FlowModule.ClientId; import google.registry.flows.LoggedInFlow; import google.registry.flows.TransactionalFlow; import google.registry.flows.exceptions.NotPendingTransferException; @@ -32,12 +33,11 @@ import google.registry.flows.exceptions.ResourceToMutateDoesNotExistException; import google.registry.model.contact.ContactCommand.Transfer; import google.registry.model.contact.ContactResource; import google.registry.model.domain.metadata.MetadataExtension; -import google.registry.model.eppcommon.StatusValue; +import google.registry.model.eppcommon.AuthInfo; import google.registry.model.eppinput.ResourceCommand; import google.registry.model.eppoutput.EppOutput; import google.registry.model.poll.PollMessage; import google.registry.model.reporting.HistoryEntry; -import google.registry.model.transfer.TransferData; import google.registry.model.transfer.TransferStatus; import javax.inject.Inject; @@ -52,6 +52,8 @@ import javax.inject.Inject; public class ContactTransferApproveFlow extends LoggedInFlow implements TransactionalFlow { @Inject ResourceCommand resourceCommand; + @Inject @ClientId String clientId; + @Inject Optional authInfo; @Inject HistoryEntry.Builder historyBuilder; @Inject ContactTransferApproveFlow() {} @@ -68,27 +70,15 @@ public class ContactTransferApproveFlow extends LoggedInFlow implements Transact if (existingResource == null) { throw new ResourceToMutateDoesNotExistException(ContactResource.class, targetId); } - if (command.getAuthInfo() != null) { - verifyAuthInfoForResource(command.getAuthInfo(), existingResource); - } + verifyOptionalAuthInfoForResource(authInfo, existingResource); if (existingResource.getTransferData().getTransferStatus() != TransferStatus.PENDING) { throw new NotPendingTransferException(targetId); } - verifyResourceOwnership(getClientId(), existingResource); - TransferData oldTransferData = existingResource.getTransferData(); + verifyResourceOwnership(clientId, existingResource); ContactResource newResource = existingResource.asBuilder() - .removeStatusValue(StatusValue.PENDING_TRANSFER) + .clearPendingTransfer(TransferStatus.CLIENT_APPROVED, now) .setLastTransferTime(now) .setCurrentSponsorClientId(existingResource.getTransferData().getGainingClientId()) - .setTransferData(oldTransferData.asBuilder() - .setTransferStatus(TransferStatus.CLIENT_APPROVED) - .setPendingTransferExpirationTime(now) - .setExtendedRegistrationYears(null) - .setServerApproveEntities(null) - .setServerApproveBillingEvent(null) - .setServerApproveAutorenewEvent(null) - .setServerApproveAutorenewPollMessage(null) - .build()) .build(); HistoryEntry historyEntry = historyBuilder .setType(HistoryEntry.Type.CONTACT_TRANSFER_APPROVE) @@ -96,21 +86,12 @@ public class ContactTransferApproveFlow extends LoggedInFlow implements Transact .setParent(Key.create(existingResource)) .build(); // Create a poll message for the gaining client. - PollMessage gainingPollMessage = new PollMessage.OneTime.Builder() - .setClientId(oldTransferData.getGainingClientId()) - .setEventTime(now) - .setMsg(TransferStatus.CLIENT_APPROVED.getMessage()) - .setResponseData(ImmutableList.of( - createTransferResponse(newResource, newResource.getTransferData(), now), - createPendingTransferNotificationResponse( - existingResource, oldTransferData.getTransferRequestTrid(), true, now))) - .setParent(historyEntry) - .build(); + PollMessage gainingPollMessage = + createGainingTransferPollMessage(targetId, newResource.getTransferData(), historyEntry); ofy().save().entities(newResource, historyEntry, gainingPollMessage); // Delete the billing event and poll messages that were written in case the transfer would have // been implicitly server approved. ofy().delete().keys(existingResource.getTransferData().getServerApproveEntities()); - return createOutput( - Success, createTransferResponse(newResource, newResource.getTransferData(), now)); + return createOutput(Success, createTransferResponse(targetId, newResource.getTransferData())); } } diff --git a/java/google/registry/flows/contact/ContactTransferCancelFlow.java b/java/google/registry/flows/contact/ContactTransferCancelFlow.java index 689ec4093..665064e27 100644 --- a/java/google/registry/flows/contact/ContactTransferCancelFlow.java +++ b/java/google/registry/flows/contact/ContactTransferCancelFlow.java @@ -14,15 +14,17 @@ package google.registry.flows.contact; -import static google.registry.flows.ResourceFlowUtils.createTransferResponse; -import static google.registry.flows.ResourceFlowUtils.verifyAuthInfoForResource; +import static google.registry.flows.ResourceFlowUtils.verifyOptionalAuthInfoForResource; +import static google.registry.flows.contact.ContactFlowUtils.createLosingTransferPollMessage; +import static google.registry.flows.contact.ContactFlowUtils.createTransferResponse; import static google.registry.model.EppResourceUtils.loadByUniqueId; import static google.registry.model.eppoutput.Result.Code.Success; import static google.registry.model.ofy.ObjectifyService.ofy; -import com.google.common.collect.ImmutableList; +import com.google.common.base.Optional; import com.googlecode.objectify.Key; import google.registry.flows.EppException; +import google.registry.flows.FlowModule.ClientId; import google.registry.flows.LoggedInFlow; import google.registry.flows.TransactionalFlow; import google.registry.flows.exceptions.NotPendingTransferException; @@ -31,7 +33,7 @@ import google.registry.flows.exceptions.ResourceToMutateDoesNotExistException; import google.registry.model.contact.ContactCommand.Transfer; import google.registry.model.contact.ContactResource; import google.registry.model.domain.metadata.MetadataExtension; -import google.registry.model.eppcommon.StatusValue; +import google.registry.model.eppcommon.AuthInfo; import google.registry.model.eppinput.ResourceCommand; import google.registry.model.eppoutput.EppOutput; import google.registry.model.poll.PollMessage; @@ -50,6 +52,8 @@ import javax.inject.Inject; public class ContactTransferCancelFlow extends LoggedInFlow implements TransactionalFlow { @Inject ResourceCommand resourceCommand; + @Inject Optional authInfo; + @Inject @ClientId String clientId; @Inject HistoryEntry.Builder historyBuilder; @Inject ContactTransferCancelFlow() {} @@ -67,28 +71,17 @@ public class ContactTransferCancelFlow extends LoggedInFlow implements Transacti if (existingResource == null) { throw new ResourceToMutateDoesNotExistException(ContactResource.class, targetId); } - if (command.getAuthInfo() != null) { - verifyAuthInfoForResource(command.getAuthInfo(), existingResource); - } + verifyOptionalAuthInfoForResource(authInfo, existingResource); // Fail if object doesn't have a pending transfer, or if authinfo doesn't match. */ if (existingResource.getTransferData().getTransferStatus() != TransferStatus.PENDING) { throw new NotPendingTransferException(targetId); } // TODO(b/18997997): Determine if authInfo is necessary to cancel a transfer. - if (!getClientId().equals(existingResource.getTransferData().getGainingClientId())) { + if (!clientId.equals(existingResource.getTransferData().getGainingClientId())) { throw new NotTransferInitiatorException(); } ContactResource newResource = existingResource.asBuilder() - .removeStatusValue(StatusValue.PENDING_TRANSFER) - .setTransferData(existingResource.getTransferData().asBuilder() - .setTransferStatus(TransferStatus.CLIENT_CANCELLED) - .setPendingTransferExpirationTime(now) - .setExtendedRegistrationYears(null) - .setServerApproveEntities(null) - .setServerApproveBillingEvent(null) - .setServerApproveAutorenewEvent(null) - .setServerApproveAutorenewPollMessage(null) - .build()) + .clearPendingTransfer(TransferStatus.CLIENT_CANCELLED, now) .build(); HistoryEntry historyEntry = historyBuilder .setType(HistoryEntry.Type.CONTACT_TRANSFER_CANCEL) @@ -96,19 +89,12 @@ public class ContactTransferCancelFlow extends LoggedInFlow implements Transacti .setParent(Key.create(existingResource)) .build(); // Create a poll message for the losing client. - PollMessage losingPollMessage = new PollMessage.OneTime.Builder() - .setClientId(existingResource.getTransferData().getLosingClientId()) - .setEventTime(now) - .setMsg(TransferStatus.CLIENT_CANCELLED.getMessage()) - .setResponseData(ImmutableList.of( - createTransferResponse(newResource, newResource.getTransferData(), now))) - .setParent(historyEntry) - .build(); + PollMessage losingPollMessage = + createLosingTransferPollMessage(targetId, newResource.getTransferData(), historyEntry); ofy().save().entities(newResource, historyEntry, losingPollMessage); // Delete the billing event and poll messages that were written in case the transfer would have // been implicitly server approved. ofy().delete().keys(existingResource.getTransferData().getServerApproveEntities()); - return createOutput( - Success, createTransferResponse(newResource, newResource.getTransferData(), now)); + return createOutput(Success, createTransferResponse(targetId, newResource.getTransferData())); } } diff --git a/java/google/registry/flows/contact/ContactTransferQueryFlow.java b/java/google/registry/flows/contact/ContactTransferQueryFlow.java index 5d7786dbb..56c4052d2 100644 --- a/java/google/registry/flows/contact/ContactTransferQueryFlow.java +++ b/java/google/registry/flows/contact/ContactTransferQueryFlow.java @@ -14,18 +14,21 @@ package google.registry.flows.contact; -import static google.registry.flows.ResourceFlowUtils.createTransferResponse; -import static google.registry.flows.ResourceFlowUtils.verifyAuthInfoForResource; +import static google.registry.flows.ResourceFlowUtils.verifyOptionalAuthInfoForResource; +import static google.registry.flows.contact.ContactFlowUtils.createTransferResponse; import static google.registry.model.EppResourceUtils.loadByUniqueId; import static google.registry.model.eppoutput.Result.Code.Success; +import com.google.common.base.Optional; import google.registry.flows.EppException; +import google.registry.flows.FlowModule.ClientId; import google.registry.flows.LoggedInFlow; import google.registry.flows.exceptions.NoTransferHistoryToQueryException; import google.registry.flows.exceptions.NotAuthorizedToViewTransferException; import google.registry.flows.exceptions.ResourceToQueryDoesNotExistException; import google.registry.model.contact.ContactCommand.Transfer; import google.registry.model.contact.ContactResource; +import google.registry.model.eppcommon.AuthInfo; import google.registry.model.eppinput.ResourceCommand; import google.registry.model.eppoutput.EppOutput; import javax.inject.Inject; @@ -41,6 +44,8 @@ import javax.inject.Inject; public class ContactTransferQueryFlow extends LoggedInFlow { @Inject ResourceCommand resourceCommand; + @Inject Optional authInfo; + @Inject @ClientId String clientId; @Inject ContactTransferQueryFlow() {} @Override @@ -51,22 +56,20 @@ public class ContactTransferQueryFlow extends LoggedInFlow { if (existingResource == null) { throw new ResourceToQueryDoesNotExistException(ContactResource.class, targetId); } - if (command.getAuthInfo() != null) { - verifyAuthInfoForResource(command.getAuthInfo(), existingResource); - } + verifyOptionalAuthInfoForResource(authInfo, existingResource); // Most of the fields on the transfer response are required, so there's no way to return valid // XML if the object has never been transferred (and hence the fields aren't populated). if (existingResource.getTransferData().getTransferStatus() == null) { throw new NoTransferHistoryToQueryException(); } - // Note that the authorization info on the command (if present) has already been verified by the - // parent class. If it's present, then the other checks are unnecessary. + // Note that the authorization info on the command (if present) has already been verified. If + // it's present, then the other checks are unnecessary. if (command.getAuthInfo() == null - && !getClientId().equals(existingResource.getTransferData().getGainingClientId()) - && !getClientId().equals(existingResource.getTransferData().getLosingClientId())) { + && !clientId.equals(existingResource.getTransferData().getGainingClientId()) + && !clientId.equals(existingResource.getTransferData().getLosingClientId())) { throw new NotAuthorizedToViewTransferException(); } return createOutput( - Success, createTransferResponse(existingResource, existingResource.getTransferData(), now)); + Success, createTransferResponse(targetId, existingResource.getTransferData())); } } diff --git a/java/google/registry/flows/contact/ContactTransferRejectFlow.java b/java/google/registry/flows/contact/ContactTransferRejectFlow.java index b564399e4..3fd7308b9 100644 --- a/java/google/registry/flows/contact/ContactTransferRejectFlow.java +++ b/java/google/registry/flows/contact/ContactTransferRejectFlow.java @@ -14,17 +14,17 @@ package google.registry.flows.contact; -import static google.registry.flows.ResourceFlowUtils.createPendingTransferNotificationResponse; -import static google.registry.flows.ResourceFlowUtils.createTransferResponse; import static google.registry.flows.ResourceFlowUtils.verifyAuthInfoForResource; import static google.registry.flows.ResourceFlowUtils.verifyResourceOwnership; +import static google.registry.flows.contact.ContactFlowUtils.createGainingTransferPollMessage; +import static google.registry.flows.contact.ContactFlowUtils.createTransferResponse; import static google.registry.model.EppResourceUtils.loadByUniqueId; import static google.registry.model.eppoutput.Result.Code.Success; import static google.registry.model.ofy.ObjectifyService.ofy; -import com.google.common.collect.ImmutableList; import com.googlecode.objectify.Key; import google.registry.flows.EppException; +import google.registry.flows.FlowModule.ClientId; import google.registry.flows.LoggedInFlow; import google.registry.flows.TransactionalFlow; import google.registry.flows.exceptions.NotPendingTransferException; @@ -32,12 +32,10 @@ import google.registry.flows.exceptions.ResourceToMutateDoesNotExistException; import google.registry.model.contact.ContactCommand.Transfer; import google.registry.model.contact.ContactResource; import google.registry.model.domain.metadata.MetadataExtension; -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.reporting.HistoryEntry; -import google.registry.model.transfer.TransferData; import google.registry.model.transfer.TransferStatus; import javax.inject.Inject; @@ -52,6 +50,7 @@ import javax.inject.Inject; public class ContactTransferRejectFlow extends LoggedInFlow implements TransactionalFlow { @Inject ResourceCommand resourceCommand; + @Inject @ClientId String clientId; @Inject HistoryEntry.Builder historyBuilder; @Inject ContactTransferRejectFlow() {} @@ -74,40 +73,21 @@ public class ContactTransferRejectFlow extends LoggedInFlow implements Transacti if (existingResource.getTransferData().getTransferStatus() != TransferStatus.PENDING) { throw new NotPendingTransferException(targetId); } - verifyResourceOwnership(getClientId(), existingResource); - TransferData oldTransferData = existingResource.getTransferData(); + verifyResourceOwnership(clientId, existingResource); ContactResource newResource = existingResource.asBuilder() - .removeStatusValue(StatusValue.PENDING_TRANSFER) - .setTransferData(oldTransferData.asBuilder() - .setTransferStatus(TransferStatus.CLIENT_REJECTED) - .setPendingTransferExpirationTime(now) - .setExtendedRegistrationYears(null) - .setServerApproveEntities(null) - .setServerApproveBillingEvent(null) - .setServerApproveAutorenewEvent(null) - .setServerApproveAutorenewPollMessage(null) - .build()) + .clearPendingTransfer(TransferStatus.CLIENT_REJECTED, now) .build(); HistoryEntry historyEntry = historyBuilder .setType(HistoryEntry.Type.CONTACT_TRANSFER_REJECT) .setModificationTime(now) .setParent(Key.create(existingResource)) .build(); - PollMessage.OneTime gainingPollMessage = new PollMessage.OneTime.Builder() - .setClientId(oldTransferData.getGainingClientId()) - .setEventTime(now) - .setMsg(TransferStatus.CLIENT_REJECTED.getMessage()) - .setResponseData(ImmutableList.of( - createTransferResponse(newResource, newResource.getTransferData(), now), - createPendingTransferNotificationResponse( - existingResource, oldTransferData.getTransferRequestTrid(), false, now))) - .setParent(historyEntry) - .build(); + PollMessage gainingPollMessage = + createGainingTransferPollMessage(targetId, newResource.getTransferData(), historyEntry); ofy().save().entities(newResource, historyEntry, gainingPollMessage); // Delete the billing event and poll messages that were written in case the transfer would have // been implicitly server approved. ofy().delete().keys(existingResource.getTransferData().getServerApproveEntities()); - return createOutput( - Success, createTransferResponse(newResource, newResource.getTransferData(), now)); + return createOutput(Success, createTransferResponse(targetId, newResource.getTransferData())); } } diff --git a/java/google/registry/flows/contact/ContactTransferRequestFlow.java b/java/google/registry/flows/contact/ContactTransferRequestFlow.java index 357b81ccc..c40201d3b 100644 --- a/java/google/registry/flows/contact/ContactTransferRequestFlow.java +++ b/java/google/registry/flows/contact/ContactTransferRequestFlow.java @@ -14,19 +14,21 @@ package google.registry.flows.contact; -import static google.registry.flows.ResourceFlowUtils.createPendingTransferNotificationResponse; -import static google.registry.flows.ResourceFlowUtils.createTransferResponse; import static google.registry.flows.ResourceFlowUtils.verifyAuthInfoForResource; import static google.registry.flows.ResourceFlowUtils.verifyNoDisallowedStatuses; +import static google.registry.flows.contact.ContactFlowUtils.createGainingTransferPollMessage; +import static google.registry.flows.contact.ContactFlowUtils.createLosingTransferPollMessage; +import static google.registry.flows.contact.ContactFlowUtils.createTransferResponse; import static google.registry.model.EppResourceUtils.loadByUniqueId; import static google.registry.model.eppoutput.Result.Code.SuccessWithActionPending; import static google.registry.model.ofy.ObjectifyService.ofy; -import com.google.common.collect.ImmutableList; +import com.google.common.base.Optional; import com.google.common.collect.ImmutableSet; import com.googlecode.objectify.Key; import google.registry.config.ConfigModule.Config; import google.registry.flows.EppException; +import google.registry.flows.FlowModule.ClientId; import google.registry.flows.LoggedInFlow; import google.registry.flows.TransactionalFlow; import google.registry.flows.exceptions.AlreadyPendingTransferException; @@ -36,6 +38,7 @@ import google.registry.flows.exceptions.ResourceToMutateDoesNotExistException; import google.registry.model.contact.ContactCommand.Transfer; import google.registry.model.contact.ContactResource; import google.registry.model.domain.metadata.MetadataExtension; +import google.registry.model.eppcommon.AuthInfo; import google.registry.model.eppcommon.StatusValue; import google.registry.model.eppinput.ResourceCommand; import google.registry.model.eppoutput.EppOutput; @@ -64,6 +67,8 @@ public class ContactTransferRequestFlow extends LoggedInFlow implements Transact StatusValue.SERVER_TRANSFER_PROHIBITED); @Inject ResourceCommand resourceCommand; + @Inject Optional authInfo; + @Inject @ClientId String gainingClientId; @Inject @Config("contactAutomaticTransferLength") Duration automaticTransferLength; @Inject HistoryEntry.Builder historyBuilder; @Inject ContactTransferRequestFlow() {} @@ -81,21 +86,19 @@ public class ContactTransferRequestFlow extends LoggedInFlow implements Transact if (existingResource == null) { throw new ResourceToMutateDoesNotExistException(ContactResource.class, targetId); } - if (command.getAuthInfo() == null) { + if (!authInfo.isPresent()) { throw new MissingTransferRequestAuthInfoException(); } - verifyAuthInfoForResource(command.getAuthInfo(), existingResource); + verifyAuthInfoForResource(authInfo.get(), existingResource); // Verify that the resource does not already have a pending transfer. if (TransferStatus.PENDING.equals(existingResource.getTransferData().getTransferStatus())) { throw new AlreadyPendingTransferException(targetId); } - String gainingClientId = getClientId(); String losingClientId = existingResource.getCurrentSponsorClientId(); // Verify that this client doesn't already sponsor this resource. if (gainingClientId.equals(losingClientId)) { throw new ObjectAlreadySponsoredException(); } - verifyAuthInfoForResource(command.getAuthInfo(), existingResource); verifyNoDisallowedStatuses(existingResource, DISALLOWED_STATUSES); HistoryEntry historyEntry = historyBuilder .setType(HistoryEntry.Type.CONTACT_TRANSFER_REQUEST) @@ -112,24 +115,11 @@ public class ContactTransferRequestFlow extends LoggedInFlow implements Transact .setTransferStatus(TransferStatus.SERVER_APPROVED) .build(); // If the transfer is server approved, this message will be sent to the losing registrar. */ - PollMessage.OneTime serverApproveLosingPollMessage = new PollMessage.OneTime.Builder() - .setClientId(losingClientId) - .setMsg(TransferStatus.SERVER_APPROVED.getMessage()) - .setParent(historyEntry) - .setEventTime(transferExpirationTime) - .setResponseData(ImmutableList.of( - createTransferResponse(existingResource, serverApproveTransferData, now))) - .build(); + PollMessage serverApproveLosingPollMessage = + createLosingTransferPollMessage(targetId, serverApproveTransferData, historyEntry); // If the transfer is server approved, this message will be sent to the gaining registrar. */ - PollMessage.OneTime serverApproveGainingPollMessage = new PollMessage.OneTime.Builder() - .setClientId(gainingClientId) - .setMsg(TransferStatus.SERVER_APPROVED.getMessage()) - .setParent(historyEntry) - .setEventTime(transferExpirationTime) - .setResponseData(ImmutableList.of( - createTransferResponse(existingResource, serverApproveTransferData, now), - createPendingTransferNotificationResponse(existingResource, trid, true, now))) - .build(); + PollMessage serverApproveGainingPollMessage = + createGainingTransferPollMessage(targetId, serverApproveTransferData, historyEntry); TransferData pendingTransferData = serverApproveTransferData.asBuilder() .setTransferStatus(TransferStatus.PENDING) .setServerApproveEntities( @@ -138,13 +128,9 @@ public class ContactTransferRequestFlow extends LoggedInFlow implements Transact Key.create(serverApproveLosingPollMessage))) .build(); // When a transfer is requested, a poll message is created to notify the losing registrar. - PollMessage.OneTime requestPollMessage = new PollMessage.OneTime.Builder() - .setClientId(losingClientId) - .setMsg(TransferStatus.PENDING.getMessage()) - .setParent(historyEntry) - .setEventTime(now) - .setResponseData(ImmutableList.of( - createTransferResponse(existingResource, pendingTransferData, now))) + PollMessage requestPollMessage = + createLosingTransferPollMessage(targetId, pendingTransferData, historyEntry).asBuilder() + .setEventTime(now) // Unlike the serverApprove messages, this applies immediately. .build(); ContactResource newResource = existingResource.asBuilder() .setTransferData(pendingTransferData) @@ -157,8 +143,7 @@ public class ContactTransferRequestFlow extends LoggedInFlow implements Transact serverApproveGainingPollMessage, serverApproveLosingPollMessage); return createOutput( - SuccessWithActionPending, - createTransferResponse(newResource, newResource.getTransferData(), now), - null); + SuccessWithActionPending, createTransferResponse(targetId, newResource.getTransferData())); } } + diff --git a/java/google/registry/flows/contact/ContactUpdateFlow.java b/java/google/registry/flows/contact/ContactUpdateFlow.java index 0f0b19fda..9052a00c6 100644 --- a/java/google/registry/flows/contact/ContactUpdateFlow.java +++ b/java/google/registry/flows/contact/ContactUpdateFlow.java @@ -14,8 +14,8 @@ package google.registry.flows.contact; -import static google.registry.flows.ResourceFlowUtils.verifyAuthInfoForResource; 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.contact.ContactFlowUtils.validateAsciiPostalInfo; import static google.registry.flows.contact.ContactFlowUtils.validateContactAgainstPolicy; @@ -23,10 +23,12 @@ import static google.registry.model.EppResourceUtils.loadByUniqueId; import static google.registry.model.eppoutput.Result.Code.Success; import static google.registry.model.ofy.ObjectifyService.ofy; +import com.google.common.base.Optional; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import com.googlecode.objectify.Key; import google.registry.flows.EppException; +import google.registry.flows.FlowModule.ClientId; import google.registry.flows.LoggedInFlow; import google.registry.flows.TransactionalFlow; import google.registry.flows.exceptions.AddRemoveSameValueEppException; @@ -37,6 +39,7 @@ import google.registry.model.contact.ContactCommand.Update; import google.registry.model.contact.ContactResource; import google.registry.model.contact.ContactResource.Builder; import google.registry.model.domain.metadata.MetadataExtension; +import google.registry.model.eppcommon.AuthInfo; import google.registry.model.eppcommon.StatusValue; import google.registry.model.eppinput.ResourceCommand; import google.registry.model.eppinput.ResourceCommand.AddRemoveSameValueException; @@ -68,6 +71,8 @@ public class ContactUpdateFlow extends LoggedInFlow implements TransactionalFlow StatusValue.SERVER_UPDATE_PROHIBITED); @Inject ResourceCommand resourceCommand; + @Inject Optional authInfo; + @Inject @ClientId String clientId; @Inject HistoryEntry.Builder historyBuilder; @Inject ContactUpdateFlow() {} @@ -84,11 +89,9 @@ public class ContactUpdateFlow extends LoggedInFlow implements TransactionalFlow if (existingResource == null) { throw new ResourceToMutateDoesNotExistException(ContactResource.class, targetId); } - if (command.getAuthInfo() != null) { - verifyAuthInfoForResource(command.getAuthInfo(), existingResource); - } + verifyOptionalAuthInfoForResource(authInfo, existingResource); if (!isSuperuser) { - verifyResourceOwnership(getClientId(), existingResource); + verifyResourceOwnership(clientId, existingResource); } for (StatusValue statusValue : Sets.union( command.getInnerAdd().getStatusValues(), @@ -111,7 +114,7 @@ public class ContactUpdateFlow extends LoggedInFlow implements TransactionalFlow } ContactResource newResource = builder .setLastEppUpdateTime(now) - .setLastEppUpdateClientId(getClientId()) + .setLastEppUpdateClientId(clientId) .build(); // If the resource is marked with clientUpdateProhibited, and this update did not clear that // status, then the update must be disallowed (unless a superuser is requesting the change). diff --git a/java/google/registry/model/EppResource.java b/java/google/registry/model/EppResource.java index 741653ae6..2d09f2cb9 100644 --- a/java/google/registry/model/EppResource.java +++ b/java/google/registry/model/EppResource.java @@ -32,6 +32,7 @@ import google.registry.model.eppcommon.StatusValue; import google.registry.model.eppoutput.EppResponse.ResponseData; import google.registry.model.ofy.CommitLogManifest; import google.registry.model.transfer.TransferData; +import google.registry.model.transfer.TransferStatus; import java.util.Set; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlTransient; @@ -303,6 +304,27 @@ public abstract class EppResource extends BackupGroupRoot implements Buildable, return thisCastToDerived(); } + /** + * Remove a pending transfer. + * + *

This removes the {@link StatusValue#PENDING_TRANSFER} status, clears all the + * server-approve fields on the {@link TransferData} including the extended registration years + * field, and sets the expiration time of the last pending transfer (i.e. the one being cleared) + * to now. + */ + public B clearPendingTransfer(TransferStatus transferStatus, DateTime now) { + removeStatusValue(StatusValue.PENDING_TRANSFER); + return setTransferData(getInstance().getTransferData().asBuilder() + .setExtendedRegistrationYears(null) + .setServerApproveEntities(null) + .setServerApproveBillingEvent(null) + .setServerApproveAutorenewEvent(null) + .setServerApproveAutorenewPollMessage(null) + .setTransferStatus(transferStatus) + .setPendingTransferExpirationTime(now) + .build()); + } + /** Wipe out any personal information in the resource. */ public B wipeOut() { return thisCastToDerived(); diff --git a/java/google/registry/model/transfer/TransferStatus.java b/java/google/registry/model/transfer/TransferStatus.java index 0b9d985f0..4768c216f 100644 --- a/java/google/registry/model/transfer/TransferStatus.java +++ b/java/google/registry/model/transfer/TransferStatus.java @@ -50,4 +50,8 @@ public enum TransferStatus { public String getXmlName() { return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, toString()); } + + public boolean isApproved() { + return this.equals(CLIENT_APPROVED) || this.equals(SERVER_APPROVED); + } }