Flatten the contact flows

There was very little meat in the contact hierarchy and it
flattened quiet easily.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=133080191
This commit is contained in:
cgoldfeder 2016-09-13 19:28:06 -07:00 committed by Ben McIlwain
parent bf9a3a0fb2
commit 99af33328d
22 changed files with 682 additions and 154 deletions

View file

@ -15,34 +15,46 @@
package google.registry.flows.contact; package google.registry.flows.contact;
import static google.registry.model.EppResourceUtils.checkResourcesExist; import static google.registry.model.EppResourceUtils.checkResourcesExist;
import static google.registry.model.eppoutput.Result.Code.Success;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import google.registry.flows.ResourceCheckFlow; import google.registry.config.ConfigModule.Config;
import google.registry.flows.EppException;
import google.registry.flows.LoggedInFlow;
import google.registry.flows.exceptions.TooManyResourceChecksException;
import google.registry.model.contact.ContactCommand.Check; import google.registry.model.contact.ContactCommand.Check;
import google.registry.model.contact.ContactResource; import google.registry.model.contact.ContactResource;
import google.registry.model.eppoutput.CheckData; import google.registry.model.eppinput.ResourceCommand;
import google.registry.model.eppoutput.CheckData.ContactCheck; import google.registry.model.eppoutput.CheckData.ContactCheck;
import google.registry.model.eppoutput.CheckData.ContactCheckData; import google.registry.model.eppoutput.CheckData.ContactCheckData;
import google.registry.model.eppoutput.EppOutput;
import java.util.List;
import java.util.Set; import java.util.Set;
import javax.inject.Inject; import javax.inject.Inject;
/** /**
* An EPP flow that checks whether a contact can be provisioned. * An EPP flow that checks whether a contact can be provisioned.
* *
* @error {@link google.registry.flows.ResourceCheckFlow.TooManyResourceChecksException} * @error {@link google.registry.flows.exceptions.TooManyResourceChecksException}
*/ */
public class ContactCheckFlow extends ResourceCheckFlow<ContactResource, Check> { public class ContactCheckFlow extends LoggedInFlow {
@Inject ResourceCommand resourceCommand;
@Inject @Config("maxChecks") int maxChecks;
@Inject ContactCheckFlow() {} @Inject ContactCheckFlow() {}
@Override @Override
protected CheckData getCheckData() { public final EppOutput run() throws EppException {
Set<String> existingIds = checkResourcesExist(resourceClass, targetIds, now); List<String> targetIds = ((Check) resourceCommand).getTargetIds();
if (targetIds.size() > maxChecks) {
throw new TooManyResourceChecksException(maxChecks);
}
Set<String> existingIds = checkResourcesExist(ContactResource.class, targetIds, now);
ImmutableList.Builder<ContactCheck> checks = new ImmutableList.Builder<>(); ImmutableList.Builder<ContactCheck> checks = new ImmutableList.Builder<>();
for (String id : targetIds) { for (String id : targetIds) {
boolean unused = !existingIds.contains(id); boolean unused = !existingIds.contains(id);
checks.add(ContactCheck.create(unused, id, unused ? null : "In use")); checks.add(ContactCheck.create(unused, id, unused ? null : "In use"));
} }
return ContactCheckData.create(checks.build()); return createOutput(Success, ContactCheckData.create(checks.build()), null);
} }
} }

View file

@ -17,15 +17,24 @@ package google.registry.flows.contact;
import static google.registry.flows.contact.ContactFlowUtils.validateAsciiPostalInfo; import static google.registry.flows.contact.ContactFlowUtils.validateAsciiPostalInfo;
import static google.registry.flows.contact.ContactFlowUtils.validateContactAgainstPolicy; import static google.registry.flows.contact.ContactFlowUtils.validateContactAgainstPolicy;
import static google.registry.model.EppResourceUtils.createContactHostRoid; import static google.registry.model.EppResourceUtils.createContactHostRoid;
import static google.registry.model.EppResourceUtils.loadByUniqueId;
import static google.registry.model.eppoutput.Result.Code.Success; import static google.registry.model.eppoutput.Result.Code.Success;
import static google.registry.model.ofy.ObjectifyService.ofy;
import com.googlecode.objectify.Key;
import google.registry.flows.EppException; import google.registry.flows.EppException;
import google.registry.flows.ResourceCreateFlow; import google.registry.flows.LoggedInFlow;
import google.registry.flows.TransactionalFlow;
import google.registry.flows.exceptions.ResourceAlreadyExistsException;
import google.registry.model.contact.ContactCommand.Create; import google.registry.model.contact.ContactCommand.Create;
import google.registry.model.contact.ContactResource; import google.registry.model.contact.ContactResource;
import google.registry.model.contact.ContactResource.Builder; import google.registry.model.contact.ContactResource.Builder;
import google.registry.model.domain.metadata.MetadataExtension;
import google.registry.model.eppinput.ResourceCommand;
import google.registry.model.eppoutput.CreateData.ContactCreateData; import google.registry.model.eppoutput.CreateData.ContactCreateData;
import google.registry.model.eppoutput.EppOutput; import google.registry.model.eppoutput.EppOutput;
import google.registry.model.index.EppResourceIndex;
import google.registry.model.index.ForeignKeyIndex;
import google.registry.model.ofy.ObjectifyService; import google.registry.model.ofy.ObjectifyService;
import google.registry.model.reporting.HistoryEntry; import google.registry.model.reporting.HistoryEntry;
import javax.inject.Inject; import javax.inject.Inject;
@ -33,37 +42,46 @@ import javax.inject.Inject;
/** /**
* An EPP flow that creates a new contact resource. * An EPP flow that creates a new contact resource.
* *
* @error {@link google.registry.flows.ResourceCreateFlow.ResourceAlreadyExistsException} * @error {@link google.registry.flows.exceptions.ResourceAlreadyExistsException}
* @error {@link ContactFlowUtils.BadInternationalizedPostalInfoException} * @error {@link ContactFlowUtils.BadInternationalizedPostalInfoException}
* @error {@link ContactFlowUtils.DeclineContactDisclosureFieldDisallowedPolicyException} * @error {@link ContactFlowUtils.DeclineContactDisclosureFieldDisallowedPolicyException}
*/ */
public class ContactCreateFlow extends ResourceCreateFlow<ContactResource, Builder, Create> { public class ContactCreateFlow extends LoggedInFlow implements TransactionalFlow {
@Inject ResourceCommand resourceCommand;
@Inject HistoryEntry.Builder historyBuilder;
@Inject ContactCreateFlow() {} @Inject ContactCreateFlow() {}
@Override @Override
protected EppOutput getOutput() { protected final void initLoggedInFlow() throws EppException {
return createOutput(Success, ContactCreateData.create(newResource.getContactId(), now)); registerExtensions(MetadataExtension.class);
} }
@Override @Override
protected String createFlowRepoId() { protected final EppOutput run() throws EppException {
return createContactHostRoid(ObjectifyService.allocateId()); Create command = (Create) resourceCommand;
if (loadByUniqueId(ContactResource.class, command.getTargetId(), now) != null) {
throw new ResourceAlreadyExistsException(command.getTargetId());
} }
Builder builder = new Builder();
@Override command.applyTo(builder);
protected void verifyNewStateIsAllowed() throws EppException { ContactResource newResource = builder
.setCreationClientId(getClientId())
.setCurrentSponsorClientId(getClientId())
.setRepoId(createContactHostRoid(ObjectifyService.allocateId()))
.build();
validateAsciiPostalInfo(newResource.getInternationalizedPostalInfo()); validateAsciiPostalInfo(newResource.getInternationalizedPostalInfo());
validateContactAgainstPolicy(newResource); validateContactAgainstPolicy(newResource);
} historyBuilder
.setType(HistoryEntry.Type.CONTACT_CREATE)
@Override .setModificationTime(now)
protected boolean storeXmlInHistoryEntry() { .setXmlBytes(null) // We don't want to store contact details in the history entry.
return false; .setParent(Key.create(newResource));
} ofy().save().entities(
newResource,
@Override historyBuilder.build(),
protected final HistoryEntry.Type getHistoryEntryType() { ForeignKeyIndex.create(newResource, newResource.getDeletionTime()),
return HistoryEntry.Type.CONTACT_CREATE; EppResourceIndex.create(Key.create(newResource)));
return createOutput(Success, ContactCreateData.create(newResource.getContactId(), now));
} }
} }

View file

@ -14,61 +14,87 @@
package google.registry.flows.contact; package google.registry.flows.contact;
import static google.registry.model.EppResourceUtils.queryDomainsUsingResource; 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.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 static google.registry.model.ofy.ObjectifyService.ofy;
import com.google.common.base.Predicate; import com.google.common.base.Function;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables; import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key; import com.googlecode.objectify.Key;
import google.registry.config.RegistryEnvironment; import google.registry.config.ConfigModule.Config;
import google.registry.flows.EppException; import google.registry.flows.EppException;
import google.registry.flows.ResourceAsyncDeleteFlow; import google.registry.flows.LoggedInFlow;
import google.registry.flows.TransactionalFlow;
import google.registry.flows.async.AsyncFlowUtils; import google.registry.flows.async.AsyncFlowUtils;
import google.registry.flows.async.DeleteContactResourceAction; import google.registry.flows.async.DeleteContactResourceAction;
import google.registry.flows.async.DeleteEppResourceAction; import google.registry.flows.async.DeleteEppResourceAction;
import google.registry.flows.exceptions.ResourceToMutateDoesNotExistException;
import google.registry.model.contact.ContactCommand.Delete; import google.registry.model.contact.ContactCommand.Delete;
import google.registry.model.contact.ContactResource; import google.registry.model.contact.ContactResource;
import google.registry.model.contact.ContactResource.Builder;
import google.registry.model.domain.DomainBase; import google.registry.model.domain.DomainBase;
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.reporting.HistoryEntry; import google.registry.model.reporting.HistoryEntry;
import javax.inject.Inject; import javax.inject.Inject;
import org.joda.time.Duration;
/** /**
* An EPP flow that deletes a contact resource. * An EPP flow that deletes a contact resource.
* *
* @error {@link google.registry.flows.ResourceAsyncDeleteFlow.ResourceToDeleteIsReferencedException}
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException} * @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException}
* @error {@link google.registry.flows.ResourceMutateFlow.ResourceToMutateDoesNotExistException} * @error {@link google.registry.flows.exceptions.ResourceStatusProhibitsOperationException}
* @error {@link google.registry.flows.SingleResourceFlow.ResourceStatusProhibitsOperationException} * @error {@link google.registry.flows.exceptions.ResourceToDeleteIsReferencedException}
* @error {@link google.registry.flows.exceptions.ResourceToMutateDoesNotExistException}
*/ */
public class ContactDeleteFlow extends ResourceAsyncDeleteFlow<ContactResource, Builder, Delete> { public class ContactDeleteFlow extends LoggedInFlow implements TransactionalFlow {
/** In {@link #isLinkedForFailfast}, check this (arbitrary) number of resources from the query. */ private static final ImmutableSet<StatusValue> DISALLOWED_STATUSES = ImmutableSet.of(
private static final int FAILFAST_CHECK_COUNT = 5; StatusValue.LINKED,
StatusValue.CLIENT_DELETE_PROHIBITED,
StatusValue.PENDING_DELETE,
StatusValue.SERVER_DELETE_PROHIBITED);
@Inject ResourceCommand resourceCommand;
@Inject @Config("asyncDeleteFlowMapreduceDelay") Duration mapreduceDelay;
@Inject HistoryEntry.Builder historyBuilder;
@Inject ContactDeleteFlow() {} @Inject ContactDeleteFlow() {}
@Override @Override
protected boolean isLinkedForFailfast(final Key<ContactResource> key) { protected final void initLoggedInFlow() throws EppException {
// Query for the first few linked domains, and if found, actually load them. The query is registerExtensions(MetadataExtension.class);
// eventually consistent and so might be very stale, but the direct load will not be stale,
// just non-transactional. If we find at least one actual reference then we can reliably
// fail. If we don't find any, we can't trust the query and need to do the full mapreduce.
return Iterables.any(
ofy().load().keys(
queryDomainsUsingResource(
ContactResource.class, key, now, FAILFAST_CHECK_COUNT)).values(),
new Predicate<DomainBase>() {
@Override
public boolean apply(DomainBase domain) {
return domain.getReferencedContacts().contains(key);
}});
} }
/** Enqueues a contact resource deletion on the mapreduce queue. */
@Override @Override
protected final void enqueueTasks() throws EppException { public final EppOutput run() throws EppException {
Delete command = (Delete) resourceCommand;
String targetId = command.getTargetId();
failfastForAsyncDelete(
targetId,
now,
ContactResource.class,
new Function<DomainBase, ImmutableSet<?>>() {
@Override
public ImmutableSet<?> apply(DomainBase domain) {
return domain.getReferencedContacts();
}});
ContactResource existingResource = loadByUniqueId(ContactResource.class, targetId, now);
if (existingResource == null) {
throw new ResourceToMutateDoesNotExistException(ContactResource.class, targetId);
}
verifyNoDisallowedStatuses(existingResource, DISALLOWED_STATUSES);
if (command.getAuthInfo() != null) {
verifyAuthInfoForResource(command.getAuthInfo(), existingResource);
}
if (!isSuperuser) {
verifyResourceOwnership(getClientId(), existingResource);
}
AsyncFlowUtils.enqueueMapreduceAction( AsyncFlowUtils.enqueueMapreduceAction(
DeleteContactResourceAction.class, DeleteContactResourceAction.class,
ImmutableMap.of( ImmutableMap.of(
@ -78,11 +104,14 @@ public class ContactDeleteFlow extends ResourceAsyncDeleteFlow<ContactResource,
getClientId(), getClientId(),
DeleteEppResourceAction.PARAM_IS_SUPERUSER, DeleteEppResourceAction.PARAM_IS_SUPERUSER,
Boolean.toString(isSuperuser)), Boolean.toString(isSuperuser)),
RegistryEnvironment.get().config().getAsyncDeleteFlowMapreduceDelay()); mapreduceDelay);
} ContactResource newResource =
existingResource.asBuilder().addStatusValue(StatusValue.PENDING_DELETE).build();
@Override historyBuilder
protected final HistoryEntry.Type getHistoryEntryType() { .setType(HistoryEntry.Type.CONTACT_PENDING_DELETE)
return HistoryEntry.Type.CONTACT_PENDING_DELETE; .setModificationTime(now)
.setParent(Key.create(existingResource));
ofy().save().<Object>entities(newResource, historyBuilder.build());
return createOutput(SuccessWithActionPending, null, null);
} }
} }

View file

@ -14,17 +14,41 @@
package google.registry.flows.contact; package google.registry.flows.contact;
import google.registry.flows.ResourceInfoFlow; import static google.registry.flows.ResourceFlowUtils.verifyAuthInfoForResource;
import static google.registry.model.EppResourceUtils.cloneResourceWithLinkedStatus;
import static google.registry.model.EppResourceUtils.loadByUniqueId;
import static google.registry.model.eppoutput.Result.Code.Success;
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.ContactCommand.Info;
import google.registry.model.contact.ContactResource; import google.registry.model.contact.ContactResource;
import google.registry.model.eppinput.ResourceCommand;
import google.registry.model.eppoutput.EppOutput;
import javax.inject.Inject; import javax.inject.Inject;
/** /**
* An EPP flow that reads a contact. * An EPP flow that reads a contact.
* *
* @error {@link google.registry.flows.ResourceQueryFlow.ResourceToQueryDoesNotExistException} * @error {@link google.registry.flows.exceptions.ResourceToQueryDoesNotExistException}
*/ */
public class ContactInfoFlow extends ResourceInfoFlow<ContactResource, Info> { public class ContactInfoFlow extends LoggedInFlow {
@Inject ContactInfoFlow() {}
}
@Inject ResourceCommand resourceCommand;
@Inject ContactInfoFlow() {}
@Override
public final EppOutput run() throws EppException {
Info command = (Info) resourceCommand;
String targetId = command.getTargetId();
ContactResource existingResource = loadByUniqueId(ContactResource.class, targetId, now);
if (existingResource == null) {
throw new ResourceToQueryDoesNotExistException(ContactResource.class, targetId);
}
if (command.getAuthInfo() != null) {
verifyAuthInfoForResource(command.getAuthInfo(), existingResource);
}
return createOutput(Success, cloneResourceWithLinkedStatus(existingResource, now), null);
}
}

View file

@ -14,11 +14,31 @@
package google.registry.flows.contact; package google.registry.flows.contact;
import google.registry.flows.ResourceTransferApproveFlow; 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.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.LoggedInFlow;
import google.registry.flows.TransactionalFlow;
import google.registry.flows.exceptions.NotPendingTransferException;
import google.registry.flows.exceptions.ResourceToMutateDoesNotExistException;
import google.registry.model.contact.ContactCommand.Transfer; import google.registry.model.contact.ContactCommand.Transfer;
import google.registry.model.contact.ContactResource; 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.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.reporting.HistoryEntry;
import google.registry.model.transfer.TransferData;
import google.registry.model.transfer.TransferStatus;
import javax.inject.Inject; import javax.inject.Inject;
/** /**
@ -26,16 +46,71 @@ import javax.inject.Inject;
* *
* @error {@link google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException} * @error {@link google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException}
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException} * @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException}
* @error {@link google.registry.flows.ResourceMutateFlow.ResourceToMutateDoesNotExistException} * @error {@link google.registry.flows.exceptions.NotPendingTransferException}
* @error {@link google.registry.flows.ResourceMutatePendingTransferFlow.NotPendingTransferException} * @error {@link google.registry.flows.exceptions.ResourceToMutateDoesNotExistException}
*/ */
public class ContactTransferApproveFlow public class ContactTransferApproveFlow extends LoggedInFlow implements TransactionalFlow {
extends ResourceTransferApproveFlow<ContactResource, Builder, Transfer> {
@Inject ResourceCommand resourceCommand;
@Inject HistoryEntry.Builder historyBuilder;
@Inject ContactTransferApproveFlow() {} @Inject ContactTransferApproveFlow() {}
@Override @Override
protected final HistoryEntry.Type getHistoryEntryType() { protected final void initLoggedInFlow() throws EppException {
return HistoryEntry.Type.CONTACT_TRANSFER_APPROVE; registerExtensions(MetadataExtension.class);
}
@Override
public final EppOutput run() throws EppException {
Transfer command = (Transfer) resourceCommand;
String targetId = command.getTargetId();
ContactResource existingResource = loadByUniqueId(ContactResource.class, targetId, now);
if (existingResource == null) {
throw new ResourceToMutateDoesNotExistException(ContactResource.class, targetId);
}
if (command.getAuthInfo() != null) {
verifyAuthInfoForResource(command.getAuthInfo(), existingResource);
}
if (existingResource.getTransferData().getTransferStatus() != TransferStatus.PENDING) {
throw new NotPendingTransferException(targetId);
}
verifyResourceOwnership(getClientId(), existingResource);
TransferData oldTransferData = existingResource.getTransferData();
ContactResource newResource = existingResource.asBuilder()
.removeStatusValue(StatusValue.PENDING_TRANSFER)
.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)
.setModificationTime(now)
.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();
ofy().save().<Object>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));
} }
} }

View file

@ -14,28 +14,101 @@
package google.registry.flows.contact; package google.registry.flows.contact;
import google.registry.flows.ResourceTransferCancelFlow; import static google.registry.flows.ResourceFlowUtils.createTransferResponse;
import static google.registry.flows.ResourceFlowUtils.verifyAuthInfoForResource;
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.LoggedInFlow;
import google.registry.flows.TransactionalFlow;
import google.registry.flows.exceptions.NotPendingTransferException;
import google.registry.flows.exceptions.NotTransferInitiatorException;
import google.registry.flows.exceptions.ResourceToMutateDoesNotExistException;
import google.registry.model.contact.ContactCommand.Transfer; import google.registry.model.contact.ContactCommand.Transfer;
import google.registry.model.contact.ContactResource; 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.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.reporting.HistoryEntry;
import google.registry.model.transfer.TransferStatus;
import javax.inject.Inject; import javax.inject.Inject;
/** /**
* An EPP flow that cancels a pending transfer on a {@link ContactResource}. * An EPP flow that cancels a pending transfer on a {@link ContactResource}.
* *
* @error {@link google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException} * @error {@link google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException}
* @error {@link google.registry.flows.ResourceMutateFlow.ResourceToMutateDoesNotExistException} * @error {@link google.registry.flows.exceptions.NotPendingTransferException}
* @error {@link google.registry.flows.ResourceMutatePendingTransferFlow.NotPendingTransferException} * @error {@link google.registry.flows.exceptions.NotTransferInitiatorException}
* @error {@link google.registry.flows.ResourceTransferCancelFlow.NotTransferInitiatorException} * @error {@link google.registry.flows.exceptions.ResourceToMutateDoesNotExistException}
*/ */
public class ContactTransferCancelFlow public class ContactTransferCancelFlow extends LoggedInFlow implements TransactionalFlow {
extends ResourceTransferCancelFlow<ContactResource, Builder, Transfer> {
@Inject ResourceCommand resourceCommand;
@Inject HistoryEntry.Builder historyBuilder;
@Inject ContactTransferCancelFlow() {} @Inject ContactTransferCancelFlow() {}
@Override @Override
protected final HistoryEntry.Type getHistoryEntryType() { protected final void initLoggedInFlow() throws EppException {
return HistoryEntry.Type.CONTACT_TRANSFER_CANCEL; registerExtensions(MetadataExtension.class);
}
@Override
protected final EppOutput run() throws EppException {
Transfer command = (Transfer) resourceCommand;
String targetId = command.getTargetId();
ContactResource existingResource = loadByUniqueId(ContactResource.class, targetId, now);
// Fail if the object doesn't exist or was deleted.
if (existingResource == null) {
throw new ResourceToMutateDoesNotExistException(ContactResource.class, targetId);
}
if (command.getAuthInfo() != null) {
verifyAuthInfoForResource(command.getAuthInfo(), 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())) {
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())
.build();
HistoryEntry historyEntry = historyBuilder
.setType(HistoryEntry.Type.CONTACT_TRANSFER_CANCEL)
.setModificationTime(now)
.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();
ofy().save().<Object>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));
} }
} }

View file

@ -14,19 +14,59 @@
package google.registry.flows.contact; package google.registry.flows.contact;
import google.registry.flows.ResourceTransferQueryFlow; import static google.registry.flows.ResourceFlowUtils.createTransferResponse;
import static google.registry.flows.ResourceFlowUtils.verifyAuthInfoForResource;
import static google.registry.model.EppResourceUtils.loadByUniqueId;
import static google.registry.model.eppoutput.Result.Code.Success;
import google.registry.flows.EppException;
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.ContactCommand.Transfer;
import google.registry.model.contact.ContactResource; import google.registry.model.contact.ContactResource;
import google.registry.model.eppinput.ResourceCommand;
import google.registry.model.eppoutput.EppOutput;
import javax.inject.Inject; import javax.inject.Inject;
/** /**
* An EPP flow that queries a pending transfer on a {@link ContactResource}. * An EPP flow that queries a pending transfer on a {@link ContactResource}.
* *
* @error {@link google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException} * @error {@link google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException}
* @error {@link google.registry.flows.ResourceQueryFlow.ResourceToQueryDoesNotExistException} * @error {@link google.registry.flows.exceptions.NoTransferHistoryToQueryException}
* @error {@link google.registry.flows.ResourceTransferQueryFlow.NoTransferHistoryToQueryException} * @error {@link google.registry.flows.exceptions.NotAuthorizedToViewTransferException}
* @error {@link google.registry.flows.ResourceTransferQueryFlow.NotAuthorizedToViewTransferException} * @error {@link google.registry.flows.exceptions.ResourceToQueryDoesNotExistException}
*/ */
public class ContactTransferQueryFlow extends ResourceTransferQueryFlow<ContactResource, Transfer> { public class ContactTransferQueryFlow extends LoggedInFlow {
@Inject ResourceCommand resourceCommand;
@Inject ContactTransferQueryFlow() {} @Inject ContactTransferQueryFlow() {}
@Override
public final EppOutput run() throws EppException {
Transfer command = (Transfer) resourceCommand;
String targetId = command.getTargetId();
ContactResource existingResource = loadByUniqueId(ContactResource.class, targetId, now);
if (existingResource == null) {
throw new ResourceToQueryDoesNotExistException(ContactResource.class, targetId);
}
if (command.getAuthInfo() != null) {
verifyAuthInfoForResource(command.getAuthInfo(), 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.
if (command.getAuthInfo() == null
&& !getClientId().equals(existingResource.getTransferData().getGainingClientId())
&& !getClientId().equals(existingResource.getTransferData().getLosingClientId())) {
throw new NotAuthorizedToViewTransferException();
}
return createOutput(
Success, createTransferResponse(existingResource, existingResource.getTransferData(), now));
}
} }

View file

@ -14,11 +14,31 @@
package google.registry.flows.contact; package google.registry.flows.contact;
import google.registry.flows.ResourceTransferRejectFlow; 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.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.LoggedInFlow;
import google.registry.flows.TransactionalFlow;
import google.registry.flows.exceptions.NotPendingTransferException;
import google.registry.flows.exceptions.ResourceToMutateDoesNotExistException;
import google.registry.model.contact.ContactCommand.Transfer; import google.registry.model.contact.ContactCommand.Transfer;
import google.registry.model.contact.ContactResource; 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.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.reporting.HistoryEntry;
import google.registry.model.transfer.TransferData;
import google.registry.model.transfer.TransferStatus;
import javax.inject.Inject; import javax.inject.Inject;
/** /**
@ -26,16 +46,68 @@ import javax.inject.Inject;
* *
* @error {@link google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException} * @error {@link google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException}
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException} * @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException}
* @error {@link google.registry.flows.ResourceMutateFlow.ResourceToMutateDoesNotExistException} * @error {@link google.registry.flows.exceptions.NotPendingTransferException}
* @error {@link google.registry.flows.ResourceMutatePendingTransferFlow.NotPendingTransferException} * @error {@link google.registry.flows.exceptions.ResourceToMutateDoesNotExistException}
*/ */
public class ContactTransferRejectFlow public class ContactTransferRejectFlow extends LoggedInFlow implements TransactionalFlow {
extends ResourceTransferRejectFlow<ContactResource, Builder, Transfer> {
@Inject ResourceCommand resourceCommand;
@Inject HistoryEntry.Builder historyBuilder;
@Inject ContactTransferRejectFlow() {} @Inject ContactTransferRejectFlow() {}
@Override @Override
protected final HistoryEntry.Type getHistoryEntryType() { protected final void initLoggedInFlow() throws EppException {
return HistoryEntry.Type.CONTACT_TRANSFER_REJECT; registerExtensions(MetadataExtension.class);
}
@Override
protected final EppOutput run() throws EppException {
Transfer command = (Transfer) resourceCommand;
String targetId = command.getTargetId();
ContactResource existingResource = loadByUniqueId(ContactResource.class, targetId, now);
if (existingResource == null) {
throw new ResourceToMutateDoesNotExistException(ContactResource.class, targetId);
}
if (command.getAuthInfo() != null) {
verifyAuthInfoForResource(command.getAuthInfo(), existingResource);
}
if (existingResource.getTransferData().getTransferStatus() != TransferStatus.PENDING) {
throw new NotPendingTransferException(targetId);
}
verifyResourceOwnership(getClientId(), existingResource);
TransferData oldTransferData = existingResource.getTransferData();
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())
.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();
ofy().save().<Object>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));
} }
} }

View file

@ -14,35 +14,151 @@
package google.registry.flows.contact; package google.registry.flows.contact;
import google.registry.config.RegistryEnvironment; import static google.registry.flows.ResourceFlowUtils.createPendingTransferNotificationResponse;
import google.registry.flows.ResourceTransferRequestFlow; 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.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.collect.ImmutableSet;
import com.googlecode.objectify.Key;
import google.registry.config.ConfigModule.Config;
import google.registry.flows.EppException;
import google.registry.flows.LoggedInFlow;
import google.registry.flows.TransactionalFlow;
import google.registry.flows.exceptions.AlreadyPendingTransferException;
import google.registry.flows.exceptions.MissingTransferRequestAuthInfoException;
import google.registry.flows.exceptions.ObjectAlreadySponsoredException;
import google.registry.flows.exceptions.ResourceToMutateDoesNotExistException;
import google.registry.model.contact.ContactCommand.Transfer; import google.registry.model.contact.ContactCommand.Transfer;
import google.registry.model.contact.ContactResource; 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.reporting.HistoryEntry;
import google.registry.model.transfer.TransferData;
import google.registry.model.transfer.TransferStatus;
import javax.inject.Inject; import javax.inject.Inject;
import org.joda.time.DateTime;
import org.joda.time.Duration; import org.joda.time.Duration;
/** /**
* An EPP flow that requests a transfer on a {@link ContactResource}. * An EPP flow that requests a transfer on a {@link ContactResource}.
* *
* @error {@link google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException} * @error {@link google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException}
* @error {@link google.registry.flows.ResourceMutateFlow.ResourceToMutateDoesNotExistException} * @error {@link google.registry.flows.exceptions.ResourceToMutateDoesNotExistException}
* @error {@link google.registry.flows.ResourceTransferRequestFlow.AlreadyPendingTransferException} * @error {@link google.registry.flows.exceptions.AlreadyPendingTransferException}
* @error {@link google.registry.flows.ResourceTransferRequestFlow.MissingTransferRequestAuthInfoException} * @error {@link google.registry.flows.exceptions.MissingTransferRequestAuthInfoException}
* @error {@link google.registry.flows.ResourceTransferRequestFlow.ObjectAlreadySponsoredException} * @error {@link google.registry.flows.exceptions.ObjectAlreadySponsoredException}
*/ */
public class ContactTransferRequestFlow public class ContactTransferRequestFlow extends LoggedInFlow implements TransactionalFlow {
extends ResourceTransferRequestFlow<ContactResource, Transfer> {
private static final ImmutableSet<StatusValue> DISALLOWED_STATUSES = ImmutableSet.of(
StatusValue.CLIENT_TRANSFER_PROHIBITED,
StatusValue.PENDING_DELETE,
StatusValue.SERVER_TRANSFER_PROHIBITED);
@Inject ResourceCommand resourceCommand;
@Inject @Config("contactAutomaticTransferLength") Duration automaticTransferLength;
@Inject HistoryEntry.Builder historyBuilder;
@Inject ContactTransferRequestFlow() {} @Inject ContactTransferRequestFlow() {}
@Override @Override
protected final HistoryEntry.Type getHistoryEntryType() { protected final void initLoggedInFlow() throws EppException {
return HistoryEntry.Type.CONTACT_TRANSFER_REQUEST; registerExtensions(MetadataExtension.class);
} }
@Override @Override
protected Duration getAutomaticTransferLength() { protected final EppOutput run() throws EppException {
return RegistryEnvironment.get().config().getContactAutomaticTransferLength(); Transfer command = (Transfer) resourceCommand;
String targetId = command.getTargetId();
ContactResource existingResource = loadByUniqueId(ContactResource.class, targetId, now);
if (existingResource == null) {
throw new ResourceToMutateDoesNotExistException(ContactResource.class, targetId);
}
if (command.getAuthInfo() == null) {
throw new MissingTransferRequestAuthInfoException();
}
verifyAuthInfoForResource(command.getAuthInfo(), 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)
.setModificationTime(now)
.setParent(Key.create(existingResource))
.build();
DateTime transferExpirationTime = now.plus(automaticTransferLength);
TransferData serverApproveTransferData = new TransferData.Builder()
.setTransferRequestTime(now)
.setTransferRequestTrid(trid)
.setGainingClientId(gainingClientId)
.setLosingClientId(losingClientId)
.setPendingTransferExpirationTime(transferExpirationTime)
.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();
// 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();
TransferData pendingTransferData = serverApproveTransferData.asBuilder()
.setTransferStatus(TransferStatus.PENDING)
.setServerApproveEntities(
ImmutableSet.<Key<? extends TransferData.TransferServerApproveEntity>>of(
Key.create(serverApproveGainingPollMessage),
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)))
.build();
ContactResource newResource = existingResource.asBuilder()
.setTransferData(pendingTransferData)
.addStatusValue(StatusValue.PENDING_TRANSFER)
.build();
ofy().save().<Object>entities(
newResource,
historyEntry,
requestPollMessage,
serverApproveGainingPollMessage,
serverApproveLosingPollMessage);
return createOutput(
SuccessWithActionPending,
createTransferResponse(newResource, newResource.getTransferData(), now),
null);
} }
} }

View file

@ -14,14 +14,33 @@
package google.registry.flows.contact; 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.verifyResourceOwnership;
import static google.registry.flows.contact.ContactFlowUtils.validateAsciiPostalInfo; import static google.registry.flows.contact.ContactFlowUtils.validateAsciiPostalInfo;
import static google.registry.flows.contact.ContactFlowUtils.validateContactAgainstPolicy; import static google.registry.flows.contact.ContactFlowUtils.validateContactAgainstPolicy;
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.ImmutableSet;
import com.google.common.collect.Sets;
import com.googlecode.objectify.Key;
import google.registry.flows.EppException; import google.registry.flows.EppException;
import google.registry.flows.ResourceUpdateFlow; import google.registry.flows.LoggedInFlow;
import google.registry.flows.TransactionalFlow;
import google.registry.flows.exceptions.AddRemoveSameValueEppException;
import google.registry.flows.exceptions.ResourceHasClientUpdateProhibitedException;
import google.registry.flows.exceptions.ResourceToMutateDoesNotExistException;
import google.registry.flows.exceptions.StatusNotClientSettableException;
import google.registry.model.contact.ContactCommand.Update; import google.registry.model.contact.ContactCommand.Update;
import google.registry.model.contact.ContactResource; import google.registry.model.contact.ContactResource;
import google.registry.model.contact.ContactResource.Builder; import google.registry.model.contact.ContactResource.Builder;
import google.registry.model.domain.metadata.MetadataExtension;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.eppinput.ResourceCommand;
import google.registry.model.eppinput.ResourceCommand.AddRemoveSameValueException;
import google.registry.model.eppoutput.EppOutput;
import google.registry.model.reporting.HistoryEntry; import google.registry.model.reporting.HistoryEntry;
import javax.inject.Inject; import javax.inject.Inject;
@ -29,31 +48,81 @@ import javax.inject.Inject;
* An EPP flow that updates a contact resource. * An EPP flow that updates a contact resource.
* *
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException} * @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException}
* @error {@link google.registry.flows.ResourceUpdateFlow.AddRemoveSameValueEppException} * @error {@link google.registry.flows.exceptions.AddRemoveSameValueEppException}
* @error {@link google.registry.flows.ResourceMutateFlow.ResourceToMutateDoesNotExistException} * @error {@link google.registry.flows.exceptions.ResourceHasClientUpdateProhibitedException}
* @error {@link google.registry.flows.ResourceUpdateFlow.ResourceHasClientUpdateProhibitedException} * @error {@link google.registry.flows.exceptions.ResourceStatusProhibitsOperationException}
* @error {@link google.registry.flows.ResourceUpdateFlow.StatusNotClientSettableException} * @error {@link google.registry.flows.exceptions.ResourceToMutateDoesNotExistException}
* @error {@link google.registry.flows.SingleResourceFlow.ResourceStatusProhibitsOperationException} * @error {@link google.registry.flows.exceptions.StatusNotClientSettableException}
* @error {@link ContactFlowUtils.BadInternationalizedPostalInfoException} * @error {@link ContactFlowUtils.BadInternationalizedPostalInfoException}
* @error {@link ContactFlowUtils.DeclineContactDisclosureFieldDisallowedPolicyException} * @error {@link ContactFlowUtils.DeclineContactDisclosureFieldDisallowedPolicyException}
*/ */
public class ContactUpdateFlow extends ResourceUpdateFlow<ContactResource, Builder, Update> { public class ContactUpdateFlow extends LoggedInFlow implements TransactionalFlow {
/**
* Note that CLIENT_UPDATE_PROHIBITED is intentionally not in this list. This is because it
* requires special checking, since you must be able to clear the status off the object with an
* update.
*/
private static final ImmutableSet<StatusValue> DISALLOWED_STATUSES = ImmutableSet.of(
StatusValue.PENDING_DELETE,
StatusValue.SERVER_UPDATE_PROHIBITED);
@Inject ResourceCommand resourceCommand;
@Inject HistoryEntry.Builder historyBuilder;
@Inject ContactUpdateFlow() {} @Inject ContactUpdateFlow() {}
@Override @Override
protected void verifyNewUpdatedStateIsAllowed() throws EppException { protected final void initLoggedInFlow() throws EppException {
registerExtensions(MetadataExtension.class);
}
@Override
public final EppOutput run() throws EppException {
Update command = (Update) resourceCommand;
String targetId = command.getTargetId();
ContactResource existingResource = loadByUniqueId(ContactResource.class, targetId, now);
if (existingResource == null) {
throw new ResourceToMutateDoesNotExistException(ContactResource.class, targetId);
}
if (command.getAuthInfo() != null) {
verifyAuthInfoForResource(command.getAuthInfo(), existingResource);
}
if (!isSuperuser) {
verifyResourceOwnership(getClientId(), existingResource);
}
for (StatusValue statusValue : Sets.union(
command.getInnerAdd().getStatusValues(),
command.getInnerRemove().getStatusValues())) {
if (!isSuperuser && !statusValue.isClientSettable()) { // The superuser can set any status.
throw new StatusNotClientSettableException(statusValue.getXmlName());
}
}
verifyNoDisallowedStatuses(existingResource, DISALLOWED_STATUSES);
historyBuilder
.setType(HistoryEntry.Type.CONTACT_UPDATE)
.setModificationTime(now)
.setXmlBytes(null) // We don't want to store contact details in the history entry.
.setParent(Key.create(existingResource));
Builder builder = existingResource.asBuilder();
try {
command.applyTo(builder);
} catch (AddRemoveSameValueException e) {
throw new AddRemoveSameValueEppException();
}
ContactResource newResource = builder
.setLastEppUpdateTime(now)
.setLastEppUpdateClientId(getClientId())
.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).
if (!isSuperuser
&& existingResource.getStatusValues().contains(StatusValue.CLIENT_UPDATE_PROHIBITED)
&& newResource.getStatusValues().contains(StatusValue.CLIENT_UPDATE_PROHIBITED)) {
throw new ResourceHasClientUpdateProhibitedException();
}
validateAsciiPostalInfo(newResource.getInternationalizedPostalInfo()); validateAsciiPostalInfo(newResource.getInternationalizedPostalInfo());
validateContactAgainstPolicy(newResource); validateContactAgainstPolicy(newResource);
} ofy().save().<Object>entities(newResource, historyBuilder.build());
return createOutput(Success);
@Override
protected boolean storeXmlInHistoryEntry() {
return false;
}
@Override
protected final HistoryEntry.Type getHistoryEntryType() {
return HistoryEntry.Type.CONTACT_UPDATE;
} }
} }

View file

@ -76,7 +76,7 @@ public class EppLifecycleContactTest extends EppTestCase {
DateTime.parse("2000-06-08T22:01:00Z")); DateTime.parse("2000-06-08T22:01:00Z"));
assertCommandAndResponse( assertCommandAndResponse(
"poll_ack.xml", "poll_ack.xml",
ImmutableMap.of("ID", "2-1-ROID-3-4"), ImmutableMap.of("ID", "2-1-ROID-3-6"),
"poll_ack_response_empty.xml", "poll_ack_response_empty.xml",
null, null,
DateTime.parse("2000-06-08T22:02:00Z")); DateTime.parse("2000-06-08T22:02:00Z"));

View file

@ -18,8 +18,8 @@ import static google.registry.model.eppoutput.CheckData.ContactCheck.create;
import static google.registry.testing.DatastoreHelper.persistActiveContact; import static google.registry.testing.DatastoreHelper.persistActiveContact;
import static google.registry.testing.DatastoreHelper.persistDeletedContact; import static google.registry.testing.DatastoreHelper.persistDeletedContact;
import google.registry.flows.ResourceCheckFlow.TooManyResourceChecksException;
import google.registry.flows.ResourceCheckFlowTestCase; import google.registry.flows.ResourceCheckFlowTestCase;
import google.registry.flows.exceptions.TooManyResourceChecksException;
import google.registry.model.contact.ContactResource; import google.registry.model.contact.ContactResource;
import org.junit.Test; import org.junit.Test;

View file

@ -19,10 +19,10 @@ import static google.registry.testing.DatastoreHelper.assertNoBillingEvents;
import static google.registry.testing.DatastoreHelper.persistActiveContact; import static google.registry.testing.DatastoreHelper.persistActiveContact;
import static google.registry.testing.DatastoreHelper.persistDeletedContact; import static google.registry.testing.DatastoreHelper.persistDeletedContact;
import google.registry.flows.ResourceCreateFlow.ResourceAlreadyExistsException;
import google.registry.flows.ResourceFlowTestCase; import google.registry.flows.ResourceFlowTestCase;
import google.registry.flows.contact.ContactFlowUtils.BadInternationalizedPostalInfoException; import google.registry.flows.contact.ContactFlowUtils.BadInternationalizedPostalInfoException;
import google.registry.flows.contact.ContactFlowUtils.DeclineContactDisclosureFieldDisallowedPolicyException; import google.registry.flows.contact.ContactFlowUtils.DeclineContactDisclosureFieldDisallowedPolicyException;
import google.registry.flows.exceptions.ResourceAlreadyExistsException;
import google.registry.model.contact.ContactResource; import google.registry.model.contact.ContactResource;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.junit.Test; import org.junit.Test;

View file

@ -28,13 +28,13 @@ import static google.registry.testing.TaskQueueHelper.assertTasksEnqueued;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key; import com.googlecode.objectify.Key;
import google.registry.flows.ResourceAsyncDeleteFlow.ResourceToDeleteIsReferencedException;
import google.registry.flows.ResourceFlowTestCase; import google.registry.flows.ResourceFlowTestCase;
import google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException; import google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException;
import google.registry.flows.ResourceMutateFlow.ResourceToMutateDoesNotExistException;
import google.registry.flows.SingleResourceFlow.ResourceStatusProhibitsOperationException;
import google.registry.flows.async.DeleteContactResourceAction; import google.registry.flows.async.DeleteContactResourceAction;
import google.registry.flows.async.DeleteEppResourceAction; import google.registry.flows.async.DeleteEppResourceAction;
import google.registry.flows.exceptions.ResourceStatusProhibitsOperationException;
import google.registry.flows.exceptions.ResourceToDeleteIsReferencedException;
import google.registry.flows.exceptions.ResourceToMutateDoesNotExistException;
import google.registry.model.contact.ContactResource; import google.registry.model.contact.ContactResource;
import google.registry.model.eppcommon.StatusValue; import google.registry.model.eppcommon.StatusValue;
import google.registry.model.reporting.HistoryEntry; import google.registry.model.reporting.HistoryEntry;

View file

@ -24,7 +24,7 @@ import static google.registry.testing.DatastoreHelper.persistResource;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import google.registry.flows.ResourceFlowTestCase; import google.registry.flows.ResourceFlowTestCase;
import google.registry.flows.ResourceQueryFlow.ResourceToQueryDoesNotExistException; import google.registry.flows.exceptions.ResourceToQueryDoesNotExistException;
import google.registry.model.contact.ContactAddress; import google.registry.model.contact.ContactAddress;
import google.registry.model.contact.ContactAuthInfo; import google.registry.model.contact.ContactAuthInfo;
import google.registry.model.contact.ContactPhoneNumber; import google.registry.model.contact.ContactPhoneNumber;

View file

@ -27,8 +27,8 @@ import com.google.common.collect.FluentIterable;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException; import google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException;
import google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException; import google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException;
import google.registry.flows.ResourceMutateFlow.ResourceToMutateDoesNotExistException; import google.registry.flows.exceptions.NotPendingTransferException;
import google.registry.flows.ResourceMutatePendingTransferFlow.NotPendingTransferException; import google.registry.flows.exceptions.ResourceToMutateDoesNotExistException;
import google.registry.model.contact.ContactAuthInfo; import google.registry.model.contact.ContactAuthInfo;
import google.registry.model.contact.ContactResource; import google.registry.model.contact.ContactResource;
import google.registry.model.eppcommon.AuthInfo.PasswordAuth; import google.registry.model.eppcommon.AuthInfo.PasswordAuth;

View file

@ -25,9 +25,9 @@ import static google.registry.testing.DatastoreHelper.persistResource;
import com.google.common.collect.FluentIterable; import com.google.common.collect.FluentIterable;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException; import google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException;
import google.registry.flows.ResourceMutateFlow.ResourceToMutateDoesNotExistException; import google.registry.flows.exceptions.NotPendingTransferException;
import google.registry.flows.ResourceMutatePendingTransferFlow.NotPendingTransferException; import google.registry.flows.exceptions.NotTransferInitiatorException;
import google.registry.flows.ResourceTransferCancelFlow.NotTransferInitiatorException; import google.registry.flows.exceptions.ResourceToMutateDoesNotExistException;
import google.registry.model.contact.ContactAuthInfo; import google.registry.model.contact.ContactAuthInfo;
import google.registry.model.contact.ContactResource; import google.registry.model.contact.ContactResource;
import google.registry.model.eppcommon.AuthInfo.PasswordAuth; import google.registry.model.eppcommon.AuthInfo.PasswordAuth;

View file

@ -20,9 +20,9 @@ import static google.registry.testing.DatastoreHelper.deleteResource;
import static google.registry.testing.DatastoreHelper.persistResource; import static google.registry.testing.DatastoreHelper.persistResource;
import google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException; import google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException;
import google.registry.flows.ResourceQueryFlow.ResourceToQueryDoesNotExistException; import google.registry.flows.exceptions.NoTransferHistoryToQueryException;
import google.registry.flows.ResourceTransferQueryFlow.NoTransferHistoryToQueryException; import google.registry.flows.exceptions.NotAuthorizedToViewTransferException;
import google.registry.flows.ResourceTransferQueryFlow.NotAuthorizedToViewTransferException; import google.registry.flows.exceptions.ResourceToQueryDoesNotExistException;
import google.registry.model.contact.ContactAuthInfo; import google.registry.model.contact.ContactAuthInfo;
import google.registry.model.contact.ContactResource; import google.registry.model.contact.ContactResource;
import google.registry.model.eppcommon.AuthInfo.PasswordAuth; import google.registry.model.eppcommon.AuthInfo.PasswordAuth;

View file

@ -26,8 +26,8 @@ import com.google.common.collect.FluentIterable;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException; import google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException;
import google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException; import google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException;
import google.registry.flows.ResourceMutateFlow.ResourceToMutateDoesNotExistException; import google.registry.flows.exceptions.NotPendingTransferException;
import google.registry.flows.ResourceMutatePendingTransferFlow.NotPendingTransferException; import google.registry.flows.exceptions.ResourceToMutateDoesNotExistException;
import google.registry.model.contact.ContactAuthInfo; import google.registry.model.contact.ContactAuthInfo;
import google.registry.model.contact.ContactResource; import google.registry.model.contact.ContactResource;
import google.registry.model.eppcommon.AuthInfo.PasswordAuth; import google.registry.model.eppcommon.AuthInfo.PasswordAuth;

View file

@ -24,10 +24,10 @@ import static google.registry.testing.DatastoreHelper.persistResource;
import google.registry.config.RegistryEnvironment; import google.registry.config.RegistryEnvironment;
import google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException; import google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException;
import google.registry.flows.ResourceMutateFlow.ResourceToMutateDoesNotExistException; import google.registry.flows.exceptions.AlreadyPendingTransferException;
import google.registry.flows.ResourceTransferRequestFlow.AlreadyPendingTransferException; import google.registry.flows.exceptions.MissingTransferRequestAuthInfoException;
import google.registry.flows.ResourceTransferRequestFlow.MissingTransferRequestAuthInfoException; import google.registry.flows.exceptions.ObjectAlreadySponsoredException;
import google.registry.flows.ResourceTransferRequestFlow.ObjectAlreadySponsoredException; import google.registry.flows.exceptions.ResourceToMutateDoesNotExistException;
import google.registry.model.contact.ContactAuthInfo; import google.registry.model.contact.ContactAuthInfo;
import google.registry.model.contact.ContactResource; import google.registry.model.contact.ContactResource;
import google.registry.model.eppcommon.AuthInfo.PasswordAuth; import google.registry.model.eppcommon.AuthInfo.PasswordAuth;

View file

@ -25,13 +25,13 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import google.registry.flows.ResourceFlowTestCase; import google.registry.flows.ResourceFlowTestCase;
import google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException; import google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException;
import google.registry.flows.ResourceMutateFlow.ResourceToMutateDoesNotExistException;
import google.registry.flows.ResourceUpdateFlow.AddRemoveSameValueEppException;
import google.registry.flows.ResourceUpdateFlow.ResourceHasClientUpdateProhibitedException;
import google.registry.flows.ResourceUpdateFlow.StatusNotClientSettableException;
import google.registry.flows.SingleResourceFlow.ResourceStatusProhibitsOperationException;
import google.registry.flows.contact.ContactFlowUtils.BadInternationalizedPostalInfoException; import google.registry.flows.contact.ContactFlowUtils.BadInternationalizedPostalInfoException;
import google.registry.flows.contact.ContactFlowUtils.DeclineContactDisclosureFieldDisallowedPolicyException; import google.registry.flows.contact.ContactFlowUtils.DeclineContactDisclosureFieldDisallowedPolicyException;
import google.registry.flows.exceptions.AddRemoveSameValueEppException;
import google.registry.flows.exceptions.ResourceHasClientUpdateProhibitedException;
import google.registry.flows.exceptions.ResourceStatusProhibitsOperationException;
import google.registry.flows.exceptions.ResourceToMutateDoesNotExistException;
import google.registry.flows.exceptions.StatusNotClientSettableException;
import google.registry.model.contact.ContactAddress; import google.registry.model.contact.ContactAddress;
import google.registry.model.contact.ContactResource; import google.registry.model.contact.ContactResource;
import google.registry.model.contact.PostalInfo; import google.registry.model.contact.PostalInfo;

View file

@ -3,7 +3,7 @@
<result code="1301"> <result code="1301">
<msg>Command completed successfully; ack to dequeue</msg> <msg>Command completed successfully; ack to dequeue</msg>
</result> </result>
<msgQ count="1" id="2-1-ROID-3-4"> <msgQ count="1" id="2-1-ROID-3-6">
<qDate>2000-06-08T22:00:00Z</qDate> <qDate>2000-06-08T22:00:00Z</qDate>
<msg>Transfer requested.</msg> <msg>Transfer requested.</msg>
</msgQ> </msgQ>