diff --git a/docs/flows.md b/docs/flows.md index 296a34912..7a1598b45 100644 --- a/docs/flows.md +++ b/docs/flows.md @@ -978,6 +978,7 @@ are enqueued to update DNS accordingly. * Resource status prohibits this operation. * Cannot remove all IP addresses from a subordinate host. * 2306 + * Cannot add and remove the same value. * Host names must be at least two levels below the public suffix. ## HostInfoFlow diff --git a/java/google/registry/flows/ResourceFlowUtils.java b/java/google/registry/flows/ResourceFlowUtils.java index a1b7dfd97..82fc8c294 100644 --- a/java/google/registry/flows/ResourceFlowUtils.java +++ b/java/google/registry/flows/ResourceFlowUtils.java @@ -15,6 +15,7 @@ package google.registry.flows; import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.Sets.intersection; import static google.registry.model.EppResourceUtils.loadByForeignKey; import static google.registry.model.EppResourceUtils.queryDomainsUsingResource; import static google.registry.model.domain.DomainResource.extendRegistrationWithCap; @@ -33,6 +34,8 @@ import com.googlecode.objectify.Work; import google.registry.flows.EppException.AuthorizationErrorException; import google.registry.flows.EppException.InvalidAuthorizationInformationErrorException; import google.registry.flows.EppException.ObjectDoesNotExistException; +import google.registry.flows.EppException.ParameterValuePolicyErrorException; +import google.registry.flows.EppException.ParameterValueRangeErrorException; import google.registry.flows.exceptions.MissingTransferRequestAuthInfoException; import google.registry.flows.exceptions.NotPendingTransferException; import google.registry.flows.exceptions.NotTransferInitiatorException; @@ -359,6 +362,25 @@ public final class ResourceFlowUtils { } } + /** Check that the same values aren't being added and removed in an update command. */ + public static void checkSameValuesNotAddedAndRemoved( + ImmutableSet fieldsToAdd, ImmutableSet fieldsToRemove) + throws AddRemoveSameValueException { + if (!intersection(fieldsToAdd, fieldsToRemove).isEmpty()) { + throw new AddRemoveSameValueException(); + } + } + + /** Check that all {@link StatusValue} objects in a set are client-settable. */ + public static void verifyAllStatusesAreClientSettable(Set statusValues) + throws StatusNotClientSettableException { + for (StatusValue statusValue : statusValues) { + if (!statusValue.isClientSettable()) { + throw new StatusNotClientSettableException(statusValue.getXmlName()); + } + } + } + /** Resource with this id does not exist. */ public static class ResourceDoesNotExistException extends ObjectDoesNotExistException { public ResourceDoesNotExistException(Class type, String targetId) { @@ -380,4 +402,18 @@ public final class ResourceFlowUtils { super("Authorization information for accessing resource is invalid"); } } + + /** Cannot add and remove the same value. */ + public static class AddRemoveSameValueException extends ParameterValuePolicyErrorException { + public AddRemoveSameValueException() { + super("Cannot add and remove the same value"); + } + } + + /** The specified status value cannot be set by clients. */ + public static class StatusNotClientSettableException extends ParameterValueRangeErrorException { + public StatusNotClientSettableException(String statusValue) { + super(String.format("Status value %s cannot be set by clients", statusValue)); + } + } } diff --git a/java/google/registry/flows/contact/ContactCreateFlow.java b/java/google/registry/flows/contact/ContactCreateFlow.java index 14d8c8774..592472e42 100644 --- a/java/google/registry/flows/contact/ContactCreateFlow.java +++ b/java/google/registry/flows/contact/ContactCreateFlow.java @@ -65,12 +65,18 @@ public final class ContactCreateFlow extends Flow implements TransactionalFlow { validateClientIsLoggedIn(clientId); Create command = (Create) resourceCommand; verifyResourceDoesNotExist(ContactResource.class, targetId, now); - Builder builder = new Builder(); - command.applyTo(builder); - ContactResource newContact = builder + ContactResource newContact = new Builder() + .setContactId(targetId) + .setAuthInfo(command.getAuthInfo()) .setCreationClientId(clientId) .setCurrentSponsorClientId(clientId) .setRepoId(createContactHostRoid(ObjectifyService.allocateId())) + .setFaxNumber(command.getFax()) + .setVoiceNumber(command.getVoice()) + .setDisclose(command.getDisclose()) + .setEmailAddress(command.getEmail()) + .setInternationalizedPostalInfo(command.getInternationalizedPostalInfo()) + .setLocalizedPostalInfo(command.getLocalizedPostalInfo()) .build(); validateAsciiPostalInfo(newContact.getInternationalizedPostalInfo()); validateContactAgainstPolicy(newContact); diff --git a/java/google/registry/flows/contact/ContactUpdateFlow.java b/java/google/registry/flows/contact/ContactUpdateFlow.java index 46ac20e41..7a8c032d4 100644 --- a/java/google/registry/flows/contact/ContactUpdateFlow.java +++ b/java/google/registry/flows/contact/ContactUpdateFlow.java @@ -14,8 +14,11 @@ package google.registry.flows.contact; +import static com.google.common.collect.Sets.union; import static google.registry.flows.FlowUtils.validateClientIsLoggedIn; +import static google.registry.flows.ResourceFlowUtils.checkSameValuesNotAddedAndRemoved; import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence; +import static google.registry.flows.ResourceFlowUtils.verifyAllStatusesAreClientSettable; import static google.registry.flows.ResourceFlowUtils.verifyNoDisallowedStatuses; import static google.registry.flows.ResourceFlowUtils.verifyOptionalAuthInfoForResource; import static google.registry.flows.ResourceFlowUtils.verifyResourceOwnership; @@ -26,7 +29,6 @@ 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.ExtensionManager; @@ -34,30 +36,30 @@ import google.registry.flows.Flow; import google.registry.flows.FlowModule.ClientId; import google.registry.flows.FlowModule.TargetId; import google.registry.flows.TransactionalFlow; -import google.registry.flows.exceptions.AddRemoveSameValueEppException; import google.registry.flows.exceptions.ResourceHasClientUpdateProhibitedException; -import google.registry.flows.exceptions.StatusNotClientSettableException; import google.registry.model.contact.ContactCommand.Update; +import google.registry.model.contact.ContactCommand.Update.Change; import google.registry.model.contact.ContactResource; import google.registry.model.contact.ContactResource.Builder; +import google.registry.model.contact.PostalInfo; 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; import google.registry.model.eppoutput.EppOutput; import google.registry.model.reporting.HistoryEntry; +import javax.annotation.Nullable; import javax.inject.Inject; /** * An EPP flow that updates a contact. * + * @error {@link google.registry.flows.ResourceFlowUtils.AddRemoveSameValueException} * @error {@link google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException} * @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException} - * @error {@link google.registry.flows.exceptions.AddRemoveSameValueEppException} + * @error {@link google.registry.flows.ResourceFlowUtils.StatusNotClientSettableException} * @error {@link google.registry.flows.exceptions.ResourceHasClientUpdateProhibitedException} * @error {@link google.registry.flows.exceptions.ResourceStatusProhibitsOperationException} - * @error {@link google.registry.flows.exceptions.StatusNotClientSettableException} * @error {@link ContactFlowUtils.BadInternationalizedPostalInfoException} * @error {@link ContactFlowUtils.DeclineContactDisclosureFieldDisallowedPolicyException} */ @@ -88,15 +90,11 @@ public final class ContactUpdateFlow extends Flow implements TransactionalFlow { Update command = (Update) resourceCommand; ContactResource existingContact = loadAndVerifyExistence(ContactResource.class, targetId, now); verifyOptionalAuthInfoForResource(authInfo, existingContact); - if (!isSuperuser) { + ImmutableSet statusToRemove = command.getInnerRemove().getStatusValues(); + ImmutableSet statusesToAdd = command.getInnerAdd().getStatusValues(); + if (!isSuperuser) { // The superuser can update any contact and set any status. verifyResourceOwnership(clientId, existingContact); - } - 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()); - } + verifyAllStatusesAreClientSettable(union(statusesToAdd, statusToRemove)); } verifyNoDisallowedStatuses(existingContact, DISALLOWED_STATUSES); historyBuilder @@ -104,15 +102,39 @@ public final class ContactUpdateFlow extends Flow implements TransactionalFlow { .setModificationTime(now) .setXmlBytes(null) // We don't want to store contact details in the history entry. .setParent(Key.create(existingContact)); + checkSameValuesNotAddedAndRemoved(statusesToAdd, statusToRemove); Builder builder = existingContact.asBuilder(); - try { - command.applyTo(builder); - } catch (AddRemoveSameValueException e) { - throw new AddRemoveSameValueEppException(); + Change change = command.getInnerChange(); + // The spec requires the following behaviors: + // * If you update part of a postal info, the fields that you didn't update are unchanged. + // * If you update one postal info but not the other, the other is deleted. + // Therefore, if you want to preserve one postal info and update another you need to send the + // update and also something that technically updates the preserved one, even if it only + // "updates" it by setting just one field to the same value. + PostalInfo internationalized = change.getInternationalizedPostalInfo(); + PostalInfo localized = change.getLocalizedPostalInfo(); + if (internationalized != null) { + builder.overlayInternationalizedPostalInfo(internationalized); + if (localized == null) { + builder.setLocalizedPostalInfo(null); + } + } + if (localized != null) { + builder.overlayLocalizedPostalInfo(localized); + if (internationalized == null) { + builder.setInternationalizedPostalInfo(null); + } } ContactResource newContact = builder .setLastEppUpdateTime(now) .setLastEppUpdateClientId(clientId) + .setAuthInfo(preferFirst(change.getAuthInfo(), existingContact.getAuthInfo())) + .setDisclose(preferFirst(change.getDisclose(), existingContact.getDisclose())) + .setEmailAddress(preferFirst(change.getEmail(), existingContact.getEmailAddress())) + .setFaxNumber(preferFirst(change.getFax(), existingContact.getFaxNumber())) + .setVoiceNumber(preferFirst(change.getVoice(), existingContact.getVoiceNumber())) + .addStatusValues(statusesToAdd) + .removeStatusValues(statusToRemove) .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). @@ -126,4 +148,10 @@ public final class ContactUpdateFlow extends Flow implements TransactionalFlow { ofy().save().entities(newContact, historyBuilder.build()); return createOutput(SUCCESS); } + + /** Return the first non-null param, or null if both are null. */ + @Nullable + private static T preferFirst(@Nullable T a, @Nullable T b) { + return a != null ? a : b; + } } diff --git a/java/google/registry/flows/domain/DomainAllocateFlow.java b/java/google/registry/flows/domain/DomainAllocateFlow.java index b7647ab87..6ec78e0c9 100644 --- a/java/google/registry/flows/domain/DomainAllocateFlow.java +++ b/java/google/registry/flows/domain/DomainAllocateFlow.java @@ -146,9 +146,7 @@ public class DomainAllocateFlow extends Flow implements TransactionalFlow { domainName, application, historyEntry, isSunrushAddGracePeriod, registry, years); entitiesToSave.addAll(billsAndPolls); DateTime registrationExpirationTime = leapSafeAddYears(now, years); - DomainResource.Builder domainBuilder = new DomainResource.Builder(); - command.applyTo(domainBuilder); - DomainResource newDomain = domainBuilder + DomainResource newDomain = new DomainResource.Builder() .setCreationClientId(clientId) .setCurrentSponsorClientId(clientId) .setRepoId(repoId) @@ -167,6 +165,11 @@ public class DomainAllocateFlow extends Flow implements TransactionalFlow { .setStatusValues(ReservationType.NAME_COLLISION == getReservationType(domainName) ? ImmutableSet.of(StatusValue.SERVER_HOLD) : ImmutableSet.of()) + .setRegistrant(command.getRegistrant()) + .setAuthInfo(command.getAuthInfo()) + .setFullyQualifiedDomainName(targetId) + .setNameservers(command.getNameservers()) + .setContacts(command.getContacts()) .build(); entitiesToSave.add( newDomain, diff --git a/java/google/registry/flows/domain/DomainApplicationCreateFlow.java b/java/google/registry/flows/domain/DomainApplicationCreateFlow.java index 53ae5d420..aae3207ac 100644 --- a/java/google/registry/flows/domain/DomainApplicationCreateFlow.java +++ b/java/google/registry/flows/domain/DomainApplicationCreateFlow.java @@ -210,9 +210,7 @@ public final class DomainApplicationCreateFlow extends Flow implements Transacti validateFeeChallenge(targetId, tld, now, feeCreate, commandOperations.getTotalCost()); SecDnsCreateExtension secDnsCreate = validateSecDnsExtension(eppInput.getSingleExtension(SecDnsCreateExtension.class)); - DomainApplication.Builder applicationBuilder = new DomainApplication.Builder(); - command.applyTo(applicationBuilder); - applicationBuilder + DomainApplication newApplication = new DomainApplication.Builder() .setCreationTrid(trid) .setCreationClientId(clientId) .setCurrentSponsorClientId(clientId) @@ -224,6 +222,11 @@ public final class DomainApplicationCreateFlow extends Flow implements Transacti .setApplicationStatus(ApplicationStatus.VALIDATED) .addStatusValue(StatusValue.PENDING_CREATE) .setDsData(secDnsCreate == null ? null : secDnsCreate.getDsData()) + .setRegistrant(command.getRegistrant()) + .setAuthInfo(command.getAuthInfo()) + .setFullyQualifiedDomainName(targetId) + .setNameservers(command.getNameservers()) + .setContacts(command.getContacts()) .setEncodedSignedMarks(FluentIterable .from(launchCreate.getSignedMarks()) .transform(new Function() { @@ -231,8 +234,8 @@ public final class DomainApplicationCreateFlow extends Flow implements Transacti public EncodedSignedMark apply(AbstractSignedMark abstractSignedMark) { return (EncodedSignedMark) abstractSignedMark; }}) - .toList()); - DomainApplication newApplication = applicationBuilder.build(); + .toList()) + .build(); HistoryEntry historyEntry = buildHistory(newApplication.getRepoId(), command.getPeriod()); ImmutableSet.Builder entitiesToSave = new ImmutableSet.Builder<>(); handleExtraFlowLogic( diff --git a/java/google/registry/flows/domain/DomainApplicationUpdateFlow.java b/java/google/registry/flows/domain/DomainApplicationUpdateFlow.java index b42061681..2d5d6a05d 100644 --- a/java/google/registry/flows/domain/DomainApplicationUpdateFlow.java +++ b/java/google/registry/flows/domain/DomainApplicationUpdateFlow.java @@ -16,7 +16,11 @@ package google.registry.flows.domain; import static com.google.common.base.CaseFormat.LOWER_CAMEL; import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE; +import static com.google.common.base.MoreObjects.firstNonNull; +import static com.google.common.collect.Sets.union; import static google.registry.flows.FlowUtils.validateClientIsLoggedIn; +import static google.registry.flows.ResourceFlowUtils.checkSameValuesNotAddedAndRemoved; +import static google.registry.flows.ResourceFlowUtils.verifyAllStatusesAreClientSettable; import static google.registry.flows.ResourceFlowUtils.verifyExistence; import static google.registry.flows.ResourceFlowUtils.verifyNoDisallowedStatuses; import static google.registry.flows.ResourceFlowUtils.verifyOptionalAuthInfoForResource; @@ -34,7 +38,6 @@ import static google.registry.flows.domain.DomainFlowUtils.validateRequiredConta import static google.registry.flows.domain.DomainFlowUtils.verifyApplicationDomainMatchesTargetId; import static google.registry.flows.domain.DomainFlowUtils.verifyClientUpdateNotProhibited; import static google.registry.flows.domain.DomainFlowUtils.verifyNotInPendingDelete; -import static google.registry.flows.domain.DomainFlowUtils.verifyStatusChangesAreClientSettable; import static google.registry.model.EppResourceUtils.loadDomainApplication; import static google.registry.model.domain.fee.Fee.FEE_UPDATE_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER; import static google.registry.model.eppoutput.Result.Code.SUCCESS; @@ -52,11 +55,11 @@ import google.registry.flows.FlowModule.ApplicationId; import google.registry.flows.FlowModule.ClientId; import google.registry.flows.FlowModule.TargetId; import google.registry.flows.TransactionalFlow; -import google.registry.flows.exceptions.AddRemoveSameValueEppException; import google.registry.model.ImmutableObject; import google.registry.model.domain.DomainApplication; -import google.registry.model.domain.DomainApplication.Builder; import google.registry.model.domain.DomainCommand.Update; +import google.registry.model.domain.DomainCommand.Update.AddRemove; +import google.registry.model.domain.DomainCommand.Update.Change; import google.registry.model.domain.launch.ApplicationStatus; import google.registry.model.domain.launch.LaunchUpdateExtension; import google.registry.model.domain.metadata.MetadataExtension; @@ -64,7 +67,6 @@ import google.registry.model.domain.secdns.SecDnsUpdateExtension; 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; import google.registry.model.eppoutput.EppOutput; import google.registry.model.reporting.HistoryEntry; import javax.inject.Inject; @@ -76,12 +78,12 @@ import javax.inject.Inject; * cannot change the domain name that is being applied for. * * @error {@link google.registry.flows.EppException.UnimplementedExtensionException} + * @error {@link google.registry.flows.ResourceFlowUtils.AddRemoveSameValueException} * @error {@link google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException} * @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException} - * @error {@link google.registry.flows.exceptions.AddRemoveSameValueEppException} + * @error {@link google.registry.flows.ResourceFlowUtils.StatusNotClientSettableException} * @error {@link google.registry.flows.exceptions.ResourceHasClientUpdateProhibitedException} * @error {@link google.registry.flows.exceptions.ResourceStatusProhibitsOperationException} - * @error {@link google.registry.flows.exceptions.StatusNotClientSettableException} * @error {@link DomainFlowUtils.ApplicationDomainNameMismatchException} * @error {@link DomainFlowUtils.DuplicateContactForRoleException} * @error {@link DomainFlowUtils.EmptySecDnsUpdateException} @@ -151,10 +153,12 @@ public class DomainApplicationUpdateFlow extends Flow implements TransactionalFl protected final void verifyUpdateAllowed( DomainApplication existingApplication, Update command) throws EppException { + AddRemove add = command.getInnerAdd(); + AddRemove remove = command.getInnerRemove(); if (!isSuperuser) { verifyResourceOwnership(clientId, existingApplication); verifyClientUpdateNotProhibited(command, existingApplication); - verifyStatusChangesAreClientSettable(command); + verifyAllStatusesAreClientSettable(union(add.getStatusValues(), remove.getStatusValues())); } String tld = existingApplication.getTld(); checkAllowedAccessToTld(clientId, tld); @@ -164,14 +168,14 @@ public class DomainApplicationUpdateFlow extends Flow implements TransactionalFl existingApplication.getApplicationStatus()); } verifyNotInPendingDelete( - command.getInnerAdd().getContacts(), + add.getContacts(), command.getInnerChange().getRegistrant(), - command.getInnerAdd().getNameservers()); - validateContactsHaveTypes(command.getInnerAdd().getContacts()); - validateContactsHaveTypes(command.getInnerRemove().getContacts()); + add.getNameservers()); + validateContactsHaveTypes(add.getContacts()); + validateContactsHaveTypes(remove.getContacts()); validateRegistrantAllowedOnTld(tld, command.getInnerChange().getRegistrantContactId()); validateNameserversAllowedOnTld( - tld, command.getInnerAdd().getNameserverFullyQualifiedHostNames()); + tld, add.getNameserverFullyQualifiedHostNames()); } private HistoryEntry buildHistory(DomainApplication existingApplication) { @@ -183,20 +187,30 @@ public class DomainApplicationUpdateFlow extends Flow implements TransactionalFl } private DomainApplication updateApplication( - DomainApplication existingApplication, Update command) throws EppException { - Builder builder = existingApplication.asBuilder(); - try { - command.applyTo(builder); - } catch (AddRemoveSameValueException e) { - throw new AddRemoveSameValueEppException(); - } - builder.setLastEppUpdateTime(now).setLastEppUpdateClientId(clientId); - // Handle the secDNS extension. + DomainApplication application, Update command) throws EppException { + AddRemove add = command.getInnerAdd(); + AddRemove remove = command.getInnerRemove(); + checkSameValuesNotAddedAndRemoved(add.getNameservers(), remove.getNameservers()); + checkSameValuesNotAddedAndRemoved(add.getContacts(), remove.getContacts()); + checkSameValuesNotAddedAndRemoved(add.getStatusValues(), remove.getStatusValues()); + Change change = command.getInnerChange(); SecDnsUpdateExtension secDnsUpdate = eppInput.getSingleExtension(SecDnsUpdateExtension.class); - if (secDnsUpdate != null) { - builder.setDsData(updateDsData(existingApplication.getDsData(), secDnsUpdate)); - } - return builder.build(); + return application.asBuilder() + // Handle the secDNS extension. + .setDsData(secDnsUpdate != null + ? updateDsData(application.getDsData(), secDnsUpdate) + : application.getDsData()) + .setLastEppUpdateTime(now) + .setLastEppUpdateClientId(clientId) + .addStatusValues(add.getStatusValues()) + .removeStatusValues(remove.getStatusValues()) + .addNameservers(add.getNameservers()) + .removeNameservers(remove.getNameservers()) + .addContacts(add.getContacts()) + .removeContacts(remove.getContacts()) + .setRegistrant(firstNonNull(change.getRegistrant(), application.getRegistrant())) + .setAuthInfo(firstNonNull(change.getAuthInfo(), application.getAuthInfo())) + .build(); } private void validateNewApplication(DomainApplication newApplication) throws EppException { diff --git a/java/google/registry/flows/domain/DomainCreateFlow.java b/java/google/registry/flows/domain/DomainCreateFlow.java index 54f6cc5ff..e0c13b99e 100644 --- a/java/google/registry/flows/domain/DomainCreateFlow.java +++ b/java/google/registry/flows/domain/DomainCreateFlow.java @@ -245,9 +245,7 @@ public class DomainCreateFlow extends Flow implements TransactionalFlow { if (!commandOperations.getEapCost().isZero()) { entitiesToSave.add(createEapBillingEvent(commandOperations, createBillingEvent)); } - DomainResource.Builder domainBuilder = new DomainResource.Builder(); - command.applyTo(domainBuilder); - DomainResource newDomain = domainBuilder + DomainResource newDomain = new DomainResource.Builder() .setCreationClientId(clientId) .setCurrentSponsorClientId(clientId) .setRepoId(repoId) @@ -261,6 +259,11 @@ public class DomainCreateFlow extends Flow implements TransactionalFlow { ? verifySignedMarks(launchCreate.getSignedMarks(), domainLabel, now).getId() : null) .setDsData(secDnsCreate == null ? null : secDnsCreate.getDsData()) + .setRegistrant(command.getRegistrant()) + .setAuthInfo(command.getAuthInfo()) + .setFullyQualifiedDomainName(targetId) + .setNameservers(command.getNameservers()) + .setContacts(command.getContacts()) .addGracePeriod(GracePeriod.forBillingEvent(GracePeriodStatus.ADD, createBillingEvent)) .build(); handleExtraFlowLogic(registry.getTldStr(), years, historyEntry, newDomain); diff --git a/java/google/registry/flows/domain/DomainFlowUtils.java b/java/google/registry/flows/domain/DomainFlowUtils.java index c1ea80dfa..cf99373fb 100644 --- a/java/google/registry/flows/domain/DomainFlowUtils.java +++ b/java/google/registry/flows/domain/DomainFlowUtils.java @@ -59,7 +59,6 @@ import google.registry.flows.EppException.UnimplementedOptionException; import google.registry.flows.domain.TldSpecificLogicProxy.EppCommandOperations; import google.registry.flows.exceptions.ResourceAlreadyExistsException; import google.registry.flows.exceptions.ResourceHasClientUpdateProhibitedException; -import google.registry.flows.exceptions.StatusNotClientSettableException; import google.registry.model.EppResource; import google.registry.model.billing.BillingEvent; import google.registry.model.billing.BillingEvent.Flag; @@ -829,18 +828,6 @@ public class DomainFlowUtils { return ImmutableSet.copyOf(union(difference(oldDsData, toRemove), toAdd)); } - /** Check that all of the status values added or removed in an update are client-settable. */ - static void verifyStatusChangesAreClientSettable(Update command) - throws StatusNotClientSettableException { - for (StatusValue statusValue : union( - command.getInnerAdd().getStatusValues(), - command.getInnerRemove().getStatusValues())) { - if (!statusValue.isClientSettable()) { - throw new StatusNotClientSettableException(statusValue.getXmlName()); - } - } - } - /** If a domain or application has "clientUpdateProhibited" set, updates must clear it or fail. */ static void verifyClientUpdateNotProhibited(Update command, DomainBase existingResource) throws ResourceHasClientUpdateProhibitedException { diff --git a/java/google/registry/flows/domain/DomainUpdateFlow.java b/java/google/registry/flows/domain/DomainUpdateFlow.java index ae44a027f..a530b3255 100644 --- a/java/google/registry/flows/domain/DomainUpdateFlow.java +++ b/java/google/registry/flows/domain/DomainUpdateFlow.java @@ -14,9 +14,13 @@ package google.registry.flows.domain; +import static com.google.common.base.MoreObjects.firstNonNull; import static com.google.common.collect.Sets.symmetricDifference; +import static com.google.common.collect.Sets.union; import static google.registry.flows.FlowUtils.validateClientIsLoggedIn; +import static google.registry.flows.ResourceFlowUtils.checkSameValuesNotAddedAndRemoved; import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence; +import static google.registry.flows.ResourceFlowUtils.verifyAllStatusesAreClientSettable; import static google.registry.flows.ResourceFlowUtils.verifyNoDisallowedStatuses; import static google.registry.flows.ResourceFlowUtils.verifyOptionalAuthInfoForResource; import static google.registry.flows.ResourceFlowUtils.verifyResourceOwnership; @@ -33,7 +37,6 @@ import static google.registry.flows.domain.DomainFlowUtils.validateRegistrantAll import static google.registry.flows.domain.DomainFlowUtils.validateRequiredContactsPresent; import static google.registry.flows.domain.DomainFlowUtils.verifyClientUpdateNotProhibited; import static google.registry.flows.domain.DomainFlowUtils.verifyNotInPendingDelete; -import static google.registry.flows.domain.DomainFlowUtils.verifyStatusChangesAreClientSettable; import static google.registry.model.domain.fee.Fee.FEE_UPDATE_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER; import static google.registry.model.eppoutput.Result.Code.SUCCESS; import static google.registry.model.ofy.ObjectifyService.ofy; @@ -52,11 +55,12 @@ import google.registry.flows.FlowModule.TargetId; import google.registry.flows.TransactionalFlow; import google.registry.flows.domain.DomainFlowUtils.FeesRequiredForNonFreeUpdateException; import google.registry.flows.domain.TldSpecificLogicProxy.EppCommandOperations; -import google.registry.flows.exceptions.AddRemoveSameValueEppException; import google.registry.model.ImmutableObject; import google.registry.model.billing.BillingEvent; import google.registry.model.billing.BillingEvent.Reason; import google.registry.model.domain.DomainCommand.Update; +import google.registry.model.domain.DomainCommand.Update.AddRemove; +import google.registry.model.domain.DomainCommand.Update.Change; import google.registry.model.domain.DomainResource; import google.registry.model.domain.GracePeriod; import google.registry.model.domain.fee.FeeTransformCommandExtension; @@ -67,7 +71,6 @@ import google.registry.model.domain.secdns.SecDnsUpdateExtension; 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; import google.registry.model.eppoutput.EppOutput; import google.registry.model.registry.Registry; import google.registry.model.reporting.HistoryEntry; @@ -92,13 +95,13 @@ import org.joda.time.DateTime; * accordingly. * * @error {@link google.registry.flows.EppException.UnimplementedExtensionException} + * @error {@link google.registry.flows.ResourceFlowUtils.AddRemoveSameValueException} * @error {@link google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException} * @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException} - * @error {@link google.registry.flows.exceptions.AddRemoveSameValueEppException} + * @error {@link google.registry.flows.ResourceFlowUtils.StatusNotClientSettableException} * @error {@link google.registry.flows.exceptions.OnlyToolCanPassMetadataException} * @error {@link google.registry.flows.exceptions.ResourceHasClientUpdateProhibitedException} * @error {@link google.registry.flows.exceptions.ResourceStatusProhibitsOperationException} - * @error {@link google.registry.flows.exceptions.StatusNotClientSettableException} * @error {@link DomainFlowUtils.DuplicateContactForRoleException} * @error {@link DomainFlowUtils.EmptySecDnsUpdateException} * @error {@link DomainFlowUtils.FeesMismatchException} @@ -181,10 +184,12 @@ public final class DomainUpdateFlow extends Flow implements TransactionalFlow { throws EppException { verifyNoDisallowedStatuses(existingDomain, UPDATE_DISALLOWED_STATUSES); verifyOptionalAuthInfoForResource(authInfo, existingDomain); + AddRemove add = command.getInnerAdd(); + AddRemove remove = command.getInnerRemove(); if (!isSuperuser) { verifyResourceOwnership(clientId, existingDomain); verifyClientUpdateNotProhibited(command, existingDomain); - verifyStatusChangesAreClientSettable(command); + verifyAllStatusesAreClientSettable(union(add.getStatusValues(), remove.getStatusValues())); } String tld = existingDomain.getTld(); checkAllowedAccessToTld(clientId, tld); @@ -204,14 +209,14 @@ public final class DomainUpdateFlow extends Flow implements TransactionalFlow { throw new FeesRequiredForNonFreeUpdateException(); } verifyNotInPendingDelete( - command.getInnerAdd().getContacts(), + add.getContacts(), command.getInnerChange().getRegistrant(), - command.getInnerAdd().getNameservers()); - validateContactsHaveTypes(command.getInnerAdd().getContacts()); - validateContactsHaveTypes(command.getInnerRemove().getContacts()); + add.getNameservers()); + validateContactsHaveTypes(add.getContacts()); + validateContactsHaveTypes(remove.getContacts()); validateRegistrantAllowedOnTld(tld, command.getInnerChange().getRegistrantContactId()); validateNameserversAllowedOnTld( - tld, command.getInnerAdd().getNameserverFullyQualifiedHostNames()); + tld, add.getNameserverFullyQualifiedHostNames()); } private HistoryEntry buildHistoryEntry(DomainResource existingDomain) { @@ -222,22 +227,31 @@ public final class DomainUpdateFlow extends Flow implements TransactionalFlow { .build(); } - private DomainResource performUpdate(Update command, DomainResource existingDomain) + private DomainResource performUpdate(Update command, DomainResource domain) throws EppException { - DomainResource.Builder builder = existingDomain.asBuilder() - .setLastEppUpdateTime(now) - .setLastEppUpdateClientId(clientId); - try { - command.applyTo(builder); - } catch (AddRemoveSameValueException e) { - throw new AddRemoveSameValueEppException(); - } - // Handle the secDNS extension. + AddRemove add = command.getInnerAdd(); + AddRemove remove = command.getInnerRemove(); + checkSameValuesNotAddedAndRemoved(add.getNameservers(), remove.getNameservers()); + checkSameValuesNotAddedAndRemoved(add.getContacts(), remove.getContacts()); + checkSameValuesNotAddedAndRemoved(add.getStatusValues(), remove.getStatusValues()); + Change change = command.getInnerChange(); SecDnsUpdateExtension secDnsUpdate = eppInput.getSingleExtension(SecDnsUpdateExtension.class); - if (secDnsUpdate != null) { - builder.setDsData(updateDsData(existingDomain.getDsData(), secDnsUpdate)); - } - return builder.build(); + return domain.asBuilder() + // Handle the secDNS extension. + .setDsData(secDnsUpdate != null + ? updateDsData(domain.getDsData(), secDnsUpdate) + : domain.getDsData()) + .setLastEppUpdateTime(now) + .setLastEppUpdateClientId(clientId) + .addStatusValues(add.getStatusValues()) + .removeStatusValues(remove.getStatusValues()) + .addNameservers(add.getNameservers()) + .removeNameservers(remove.getNameservers()) + .addContacts(add.getContacts()) + .removeContacts(remove.getContacts()) + .setRegistrant(firstNonNull(change.getRegistrant(), domain.getRegistrant())) + .setAuthInfo(firstNonNull(change.getAuthInfo(), domain.getAuthInfo())) + .build(); } private DomainResource convertSunrushAddToAdd( diff --git a/java/google/registry/flows/exceptions/AddRemoveSameValueEppException.java b/java/google/registry/flows/exceptions/AddRemoveSameValueEppException.java deleted file mode 100644 index 5460b87a4..000000000 --- a/java/google/registry/flows/exceptions/AddRemoveSameValueEppException.java +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2016 The Nomulus Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package google.registry.flows.exceptions; - -import google.registry.flows.EppException.ParameterValuePolicyErrorException; - -/** Cannot add and remove the same value. */ -public class AddRemoveSameValueEppException extends ParameterValuePolicyErrorException { - public AddRemoveSameValueEppException() { - super("Cannot add and remove the same value"); - } -} diff --git a/java/google/registry/flows/exceptions/StatusNotClientSettableException.java b/java/google/registry/flows/exceptions/StatusNotClientSettableException.java deleted file mode 100644 index a743d6f01..000000000 --- a/java/google/registry/flows/exceptions/StatusNotClientSettableException.java +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2016 The Nomulus Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package google.registry.flows.exceptions; - -import google.registry.flows.EppException.ParameterValueRangeErrorException; - -/** The specified status value cannot be set by clients. */ -public class StatusNotClientSettableException extends ParameterValueRangeErrorException { - public StatusNotClientSettableException(String statusValue) { - super(String.format("Status value %s cannot be set by clients", statusValue)); - } -} diff --git a/java/google/registry/flows/host/HostCreateFlow.java b/java/google/registry/flows/host/HostCreateFlow.java index f8889e01e..683e177ca 100644 --- a/java/google/registry/flows/host/HostCreateFlow.java +++ b/java/google/registry/flows/host/HostCreateFlow.java @@ -101,11 +101,11 @@ public final class HostCreateFlow extends Flow implements TransactionalFlow { ? new SubordinateHostMustHaveIpException() : new UnexpectedExternalHostIpException(); } - Builder builder = new Builder(); - command.applyTo(builder); - HostResource newHost = builder + HostResource newHost = new Builder() .setCreationClientId(clientId) .setCurrentSponsorClientId(clientId) + .setFullyQualifiedHostName(targetId) + .setInetAddresses(command.getInetAddresses()) .setRepoId(createContactHostRoid(ObjectifyService.allocateId())) .setSuperordinateDomain( superordinateDomain.isPresent() ? Key.create(superordinateDomain.get()) : null) diff --git a/java/google/registry/flows/host/HostUpdateFlow.java b/java/google/registry/flows/host/HostUpdateFlow.java index 2498f11ca..b54ce71ae 100644 --- a/java/google/registry/flows/host/HostUpdateFlow.java +++ b/java/google/registry/flows/host/HostUpdateFlow.java @@ -15,8 +15,11 @@ package google.registry.flows.host; import static com.google.common.base.MoreObjects.firstNonNull; +import static com.google.common.collect.Sets.union; import static google.registry.flows.FlowUtils.validateClientIsLoggedIn; +import static google.registry.flows.ResourceFlowUtils.checkSameValuesNotAddedAndRemoved; import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence; +import static google.registry.flows.ResourceFlowUtils.verifyAllStatusesAreClientSettable; import static google.registry.flows.ResourceFlowUtils.verifyNoDisallowedStatuses; import static google.registry.flows.ResourceFlowUtils.verifyOptionalAuthInfoForResource; import static google.registry.flows.ResourceFlowUtils.verifyResourceOwnership; @@ -30,7 +33,6 @@ import static google.registry.util.CollectionUtils.isNullOrEmpty; 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.dns.DnsQueue; import google.registry.flows.EppException; @@ -44,20 +46,18 @@ import google.registry.flows.FlowModule.ClientId; import google.registry.flows.FlowModule.TargetId; import google.registry.flows.TransactionalFlow; import google.registry.flows.async.AsyncFlowEnqueuer; -import google.registry.flows.exceptions.AddRemoveSameValueEppException; import google.registry.flows.exceptions.ResourceHasClientUpdateProhibitedException; -import google.registry.flows.exceptions.StatusNotClientSettableException; import google.registry.model.ImmutableObject; import google.registry.model.domain.DomainResource; 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; import google.registry.model.eppoutput.EppOutput; import google.registry.model.host.HostCommand.Update; +import google.registry.model.host.HostCommand.Update.AddRemove; +import google.registry.model.host.HostCommand.Update.Change; import google.registry.model.host.HostResource; -import google.registry.model.host.HostResource.Builder; import google.registry.model.index.ForeignKeyIndex; import google.registry.model.reporting.HistoryEntry; import java.util.Objects; @@ -76,11 +76,12 @@ import javax.inject.Inject; * when it is renamed from external to internal at least one must be added. If the host is renamed * or IP addresses are added, tasks are enqueued to update DNS accordingly. * + * @error {@link google.registry.flows.ResourceFlowUtils.AddRemoveSameValueException} * @error {@link google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException} * @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException} + * @error {@link google.registry.flows.ResourceFlowUtils.StatusNotClientSettableException} * @error {@link google.registry.flows.exceptions.ResourceHasClientUpdateProhibitedException} * @error {@link google.registry.flows.exceptions.ResourceStatusProhibitsOperationException} - * @error {@link google.registry.flows.exceptions.StatusNotClientSettableException} * @error {@link HostFlowUtils.HostNameTooShallowException} * @error {@link HostFlowUtils.InvalidHostNameException} * @error {@link HostFlowUtils.SuperordinateDomainDoesNotExistException} @@ -117,7 +118,8 @@ public final class HostUpdateFlow extends Flow implements TransactionalFlow { extensionManager.validate(); validateClientIsLoggedIn(clientId); Update command = (Update) resourceCommand; - String suppliedNewHostName = command.getInnerChange().getFullyQualifiedHostName(); + Change change = command.getInnerChange(); + String suppliedNewHostName = change.getFullyQualifiedHostName(); HostResource existingHost = loadAndVerifyExistence(HostResource.class, targetId, now); boolean isHostRename = suppliedNewHostName != null; String oldHostName = targetId; @@ -128,13 +130,16 @@ public final class HostUpdateFlow extends Flow implements TransactionalFlow { if (isHostRename && loadAndGetKey(HostResource.class, newHostName, now) != null) { throw new HostAlreadyExistsException(newHostName); } - Builder builder = existingHost.asBuilder(); - try { - command.applyTo(builder); - } catch (AddRemoveSameValueException e) { - throw new AddRemoveSameValueEppException(); - } - builder + AddRemove add = command.getInnerAdd(); + AddRemove remove = command.getInnerRemove(); + checkSameValuesNotAddedAndRemoved(add.getStatusValues(), remove.getStatusValues()); + checkSameValuesNotAddedAndRemoved(add.getInetAddresses(), remove.getInetAddresses()); + HostResource newHost = existingHost.asBuilder() + .setFullyQualifiedHostName(newHostName) + .addStatusValues(add.getStatusValues()) + .removeStatusValues(remove.getStatusValues()) + .addInetAddresses(add.getInetAddresses()) + .removeInetAddresses(remove.getInetAddresses()) .setLastEppUpdateTime(now) .setLastEppUpdateClientId(clientId) // The superordinateDomain can be missing if the new name is external. @@ -142,9 +147,10 @@ public final class HostUpdateFlow extends Flow implements TransactionalFlow { // the lookupSuperordinateDomain(...) call above, so that it will never be stale. .setSuperordinateDomain( superordinateDomain.isPresent() ? Key.create(superordinateDomain.get()) : null) - .setLastSuperordinateChange(superordinateDomain == null ? null : now); - // Rely on the host's cloneProjectedAtTime() method to handle setting of transfer data. - HostResource newHost = builder.build().cloneProjectedAtTime(now); + .setLastSuperordinateChange(superordinateDomain == null ? null : now) + .build() + // Rely on the host's cloneProjectedAtTime() method to handle setting of transfer data. + .cloneProjectedAtTime(now); verifyHasIpsIffIsExternal(command, existingHost, newHost); ImmutableSet.Builder entitiesToSave = new ImmutableSet.Builder<>(); entitiesToSave.add(newHost); @@ -172,21 +178,16 @@ public final class HostUpdateFlow extends Flow implements TransactionalFlow { verifyOptionalAuthInfoForResource(authInfo, existingResource); if (!isSuperuser) { verifyResourceOwnership(clientId, existingResource); + ImmutableSet statusesToAdd = command.getInnerAdd().getStatusValues(); + ImmutableSet statusesToRemove = command.getInnerRemove().getStatusValues(); // If the resource is marked with clientUpdateProhibited, and this update does 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) - && !command.getInnerRemove().getStatusValues() - .contains(StatusValue.CLIENT_UPDATE_PROHIBITED)) { + && !statusesToRemove.contains(StatusValue.CLIENT_UPDATE_PROHIBITED)) { throw new ResourceHasClientUpdateProhibitedException(); } - } - 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()); - } + verifyAllStatusesAreClientSettable(union(statusesToAdd, statusesToRemove)); } verifyDomainIsSameRegistrar(superordinateDomain, clientId); verifyNoDisallowedStatuses(existingResource, DISALLOWED_STATUSES); diff --git a/java/google/registry/model/contact/ContactCommand.java b/java/google/registry/model/contact/ContactCommand.java index c397b30a9..99e665d42 100644 --- a/java/google/registry/model/contact/ContactCommand.java +++ b/java/google/registry/model/contact/ContactCommand.java @@ -20,7 +20,6 @@ import static google.registry.util.CollectionUtils.nullToEmpty; import com.google.common.base.Function; import com.google.common.collect.Maps; import google.registry.model.ImmutableObject; -import google.registry.model.contact.ContactResource.Builder; import google.registry.model.contact.PostalInfo.Type; import google.registry.model.eppinput.ResourceCommand.AbstractSingleResourceCommand; import google.registry.model.eppinput.ResourceCommand.ResourceCheck; @@ -75,23 +74,32 @@ public class ContactCommand { }}); } - @Override - public void applyTo(Builder builder) { - if (authInfo != null) { - builder.setAuthInfo(authInfo); - } - if (disclose != null) { - builder.setDisclose(disclose); - } - if (email != null) { - builder.setEmailAddress(email); - } - if (fax != null) { - builder.setFaxNumber(fax); - } - if (voice != null) { - builder.setVoiceNumber(voice); - } + public ContactPhoneNumber getVoice() { + return voice; + } + + public ContactPhoneNumber getFax() { + return fax; + } + + public String getEmail() { + return email; + } + + public ContactAuthInfo getAuthInfo() { + return authInfo; + } + + public Disclose getDisclose() { + return disclose; + } + + public PostalInfo getInternationalizedPostalInfo() { + return getPostalInfosAsMap().get(Type.INTERNATIONALIZED); + } + + public PostalInfo getLocalizedPostalInfo() { + return getPostalInfosAsMap().get(Type.LOCALIZED); } } @@ -134,21 +142,6 @@ public class ContactCommand { public ContactAuthInfo getAuthInfo() { return authInfo; } - - @Override - public void applyTo(ContactResource.Builder builder) { - super.applyTo(builder); - if (contactId != null) { - builder.setContactId(contactId); - } - Map postalInfosAsMap = getPostalInfosAsMap(); - if (postalInfosAsMap.containsKey(Type.INTERNATIONALIZED)) { - builder.setInternationalizedPostalInfo(postalInfosAsMap.get(Type.INTERNATIONALIZED)); - } - if (postalInfosAsMap.containsKey(Type.LOCALIZED)) { - builder.setLocalizedPostalInfo(postalInfosAsMap.get(Type.LOCALIZED)); - } - } } /** A delete command for a {@link ContactResource}. */ @@ -204,34 +197,6 @@ public class ContactCommand { /** The inner change type on a contact update command. */ @XmlType(propOrder = {"postalInfo", "voice", "fax", "email", "authInfo", "disclose"}) - public static class Change extends ContactCreateOrChange { - /** - * The spec requires the following behaviors: - *
    - *
  • If you update part of a postal info, the fields that you didn't update are unchanged. - *
  • If you update one postal info but not the other, the other is deleted. - *
- * Therefore, if you want to preserve one postal info and update another you need to send the - * update and also something that technically updates the preserved one, even if it only - * "updates" it by setting just one field to the same value. - */ - @Override - public void applyTo(ContactResource.Builder builder) { - super.applyTo(builder); - Map postalInfosAsMap = getPostalInfosAsMap(); - if (postalInfosAsMap.containsKey(Type.INTERNATIONALIZED)) { - builder.overlayInternationalizedPostalInfo(postalInfosAsMap.get(Type.INTERNATIONALIZED)); - if (postalInfosAsMap.size() == 1) { - builder.setLocalizedPostalInfo(null); - } - } - if (postalInfosAsMap.containsKey(Type.LOCALIZED)) { - builder.overlayLocalizedPostalInfo(postalInfosAsMap.get(Type.LOCALIZED)); - if (postalInfosAsMap.size() == 1) { - builder.setInternationalizedPostalInfo(null); - } - } - } - } + public static class Change extends ContactCreateOrChange {} } } diff --git a/java/google/registry/model/contact/ContactResource.java b/java/google/registry/model/contact/ContactResource.java index 385d49560..f0bcaf53a 100644 --- a/java/google/registry/model/contact/ContactResource.java +++ b/java/google/registry/model/contact/ContactResource.java @@ -30,7 +30,6 @@ import google.registry.model.EppResource; import google.registry.model.EppResource.ForeignKeyedEppResource; import google.registry.model.annotations.ExternalMessagingName; import google.registry.model.contact.PostalInfo.Type; -import google.registry.model.eppcommon.AuthInfo; import java.util.List; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; @@ -147,7 +146,7 @@ public class ContactResource extends EppResource implements ForeignKeyedEppResou return email; } - public AuthInfo getAuthInfo() { + public ContactAuthInfo getAuthInfo() { return authInfo; } diff --git a/java/google/registry/model/domain/DomainBase.java b/java/google/registry/model/domain/DomainBase.java index 9c37eebda..102dd7360 100644 --- a/java/google/registry/model/domain/DomainBase.java +++ b/java/google/registry/model/domain/DomainBase.java @@ -47,7 +47,6 @@ import google.registry.model.contact.ContactResource; import google.registry.model.domain.DesignatedContact.Type; import google.registry.model.domain.launch.LaunchNotice; import google.registry.model.domain.secdns.DelegationSignerData; -import google.registry.model.eppcommon.AuthInfo; import google.registry.model.host.HostResource; import java.util.Set; import javax.xml.bind.annotation.XmlElement; @@ -221,7 +220,7 @@ public abstract class DomainBase extends EppResource { .toSet(); } - public AuthInfo getAuthInfo() { + public DomainAuthInfo getAuthInfo() { return authInfo; } diff --git a/java/google/registry/model/domain/DomainCommand.java b/java/google/registry/model/domain/DomainCommand.java index e00cac1e4..47d733c0e 100644 --- a/java/google/registry/model/domain/DomainCommand.java +++ b/java/google/registry/model/domain/DomainCommand.java @@ -19,7 +19,6 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.collect.Maps.transformValues; import static com.google.common.collect.Sets.difference; -import static com.google.common.collect.Sets.intersection; import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.util.CollectionUtils.difference; import static google.registry.util.CollectionUtils.forceEmptyToNull; @@ -37,7 +36,6 @@ import com.googlecode.objectify.Work; import google.registry.model.EppResource; import google.registry.model.ImmutableObject; import google.registry.model.contact.ContactResource; -import google.registry.model.eppcommon.AuthInfo; import google.registry.model.eppinput.ResourceCommand.AbstractSingleResourceCommand; import google.registry.model.eppinput.ResourceCommand.ResourceCheck; import google.registry.model.eppinput.ResourceCommand.ResourceCreateOrChange; @@ -98,14 +96,8 @@ public class DomainCommand { return registrant; } - @Override - public void applyTo(B builder) { - if (registrant != null) { - builder.setRegistrant(registrant); - } - if (authInfo != null) { - builder.setAuthInfo(authInfo); - } + public DomainAuthInfo getAuthInfo() { + return authInfo; } } @@ -175,24 +167,10 @@ public class DomainCommand { } @Override - public AuthInfo getAuthInfo() { + public DomainAuthInfo getAuthInfo() { return authInfo; } - @Override - public void applyTo(DomainBase.Builder builder) { - super.applyTo(builder); - if (fullyQualifiedDomainName != null) { - builder.setFullyQualifiedDomainName(fullyQualifiedDomainName); - } - if (nameservers != null) { - builder.setNameservers(getNameservers()); - } - if (contacts != null) { - builder.setContacts(getContacts()); - } - } - /** Creates a copy of this {@link Create} with hard links to hosts and contacts. */ @Override public Create cloneAndLinkReferences(DateTime now) throws InvalidReferencesException { @@ -422,24 +400,6 @@ public class DomainCommand { } } - @Override - public void applyTo(DomainBase.Builder builder) throws AddRemoveSameValueException { - super.applyTo(builder); - getInnerChange().applyTo(builder); - AddRemove add = getInnerAdd(); - AddRemove remove = getInnerRemove(); - if (!intersection(add.getNameservers(), remove.getNameservers()).isEmpty()) { - throw new AddRemoveSameValueException(); - } - builder.addNameservers(add.getNameservers()); - builder.removeNameservers(remove.getNameservers()); - if (!intersection(add.getContacts(), remove.getContacts()).isEmpty()) { - throw new AddRemoveSameValueException(); - } - builder.addContacts(add.getContacts()); - builder.removeContacts(remove.getContacts()); - } - /** * Creates a copy of this {@link Update} with hard links to hosts and contacts. * diff --git a/java/google/registry/model/eppinput/ResourceCommand.java b/java/google/registry/model/eppinput/ResourceCommand.java index 1b3805beb..1ca29661e 100644 --- a/java/google/registry/model/eppinput/ResourceCommand.java +++ b/java/google/registry/model/eppinput/ResourceCommand.java @@ -14,7 +14,6 @@ package google.registry.model.eppinput; -import static com.google.common.collect.Sets.intersection; import static google.registry.util.CollectionUtils.nullSafeImmutableCopy; import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy; @@ -83,9 +82,7 @@ public interface ResourceCommand { } /** A create command, or the inner change (as opposed to add or remove) part of an update. */ - public interface ResourceCreateOrChange> { - public abstract void applyTo(B builder); - } + public interface ResourceCreateOrChange> {} /** * An update command for an {@link EppResource}. @@ -133,18 +130,5 @@ public interface ResourceCommand { A remove = getNullableInnerRemove(); return remove == null ? new TypeInstantiator(getClass()){}.instantiate() : remove; } - - public void applyTo(B builder) throws AddRemoveSameValueException { - getInnerChange().applyTo(builder); - if (!intersection(getInnerAdd().getStatusValues(), getInnerRemove().getStatusValues()) - .isEmpty()) { - throw new AddRemoveSameValueException(); - } - builder.addStatusValues(getInnerAdd().getStatusValues()); - builder.removeStatusValues(getInnerRemove().getStatusValues()); - } } - - /** Exception for adding and removing the same value in {@link ResourceUpdate#applyTo}. */ - public static class AddRemoveSameValueException extends Exception {} } diff --git a/java/google/registry/model/host/HostCommand.java b/java/google/registry/model/host/HostCommand.java index 001c6b835..746a4740f 100644 --- a/java/google/registry/model/host/HostCommand.java +++ b/java/google/registry/model/host/HostCommand.java @@ -14,7 +14,6 @@ package google.registry.model.host; -import static com.google.common.collect.Sets.intersection; import static google.registry.util.CollectionUtils.nullSafeImmutableCopy; import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy; @@ -37,27 +36,9 @@ public class HostCommand { @XmlTransient abstract static class HostCreateOrChange extends AbstractSingleResourceCommand implements ResourceCreateOrChange { - /** IP Addresses for this host. Can be null if this is an external host. */ - @XmlElement(name = "addr") - Set inetAddresses; - - public ImmutableSet getInetAddresses() { - return nullSafeImmutableCopy(inetAddresses); - } - public String getFullyQualifiedHostName() { return getTargetId(); } - - @Override - public void applyTo(HostResource.Builder builder) { - if (getFullyQualifiedHostName() != null) { - builder.setFullyQualifiedHostName(getFullyQualifiedHostName()); - } - if (getInetAddresses() != null) { - builder.setInetAddresses(getInetAddresses()); - } - } } /** @@ -67,7 +48,15 @@ public class HostCommand { @XmlType(propOrder = {"targetId", "inetAddresses" }) @XmlRootElement public static class Create - extends HostCreateOrChange implements ResourceCreateOrChange {} + extends HostCreateOrChange implements ResourceCreateOrChange { + /** IP Addresses for this host. Can be null if this is an external host. */ + @XmlElement(name = "addr") + Set inetAddresses; + + public ImmutableSet getInetAddresses() { + return nullSafeImmutableCopy(inetAddresses); + } + } /** A delete command for a {@link HostResource}. */ @XmlRootElement @@ -124,18 +113,6 @@ public class HostCommand { } /** The inner change type on a host update command. */ - @XmlType(propOrder = {"targetId", "inetAddresses" }) public static class Change extends HostCreateOrChange {} - - @Override - public void applyTo(HostResource.Builder builder) throws AddRemoveSameValueException { - super.applyTo(builder); - if (!intersection(getInnerAdd().getInetAddresses(), getInnerRemove().getInetAddresses()) - .isEmpty()) { - throw new AddRemoveSameValueException(); - } - builder.addInetAddresses(getInnerAdd().getInetAddresses()); - builder.removeInetAddresses(getInnerRemove().getInetAddresses()); - } } } diff --git a/javatests/google/registry/flows/contact/ContactUpdateFlowTest.java b/javatests/google/registry/flows/contact/ContactUpdateFlowTest.java index a7659b4ac..b2fbef2da 100644 --- a/javatests/google/registry/flows/contact/ContactUpdateFlowTest.java +++ b/javatests/google/registry/flows/contact/ContactUpdateFlowTest.java @@ -24,14 +24,14 @@ import static google.registry.testing.DatastoreHelper.persistResource; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import google.registry.flows.ResourceFlowTestCase; +import google.registry.flows.ResourceFlowUtils.AddRemoveSameValueException; import google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException; import google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException; +import google.registry.flows.ResourceFlowUtils.StatusNotClientSettableException; import google.registry.flows.contact.ContactFlowUtils.BadInternationalizedPostalInfoException; 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.StatusNotClientSettableException; import google.registry.model.contact.ContactAddress; import google.registry.model.contact.ContactResource; import google.registry.model.contact.PostalInfo; @@ -84,7 +84,7 @@ public class ContactUpdateFlowTest } @Test - public void testSuccess_updatingOnePostalInfoDeletesTheOther() throws Exception { + public void testSuccess_updatingInternationalizedPostalInfoDeletesLocalized() throws Exception { ContactResource contact = persistResource( newContactResource(getUniqueIdFromCommand()).asBuilder() @@ -104,12 +104,60 @@ public class ContactUpdateFlowTest // the localized one since they are treated as a pair for update purposes. assertAboutContacts().that(contact) .hasNonNullLocalizedPostalInfo().and() - .hasInternationalizedPostalInfo(null); + .hasNullInternationalizedPostalInfo(); runFlowAssertResponse(readFile("contact_update_response.xml")); assertAboutContacts().that(reloadResourceByForeignKey()) - .hasLocalizedPostalInfo(null).and() - .hasNonNullInternationalizedPostalInfo(); + .hasNullLocalizedPostalInfo().and() + .hasInternationalizedPostalInfo(new PostalInfo.Builder() + .setType(Type.INTERNATIONALIZED) + .setAddress(new ContactAddress.Builder() + .setStreet(ImmutableList.of("124 Example Dr.", "Suite 200")) + .setCity("Dulles") + .setState("VA") + .setZip("20166-6503") + .setCountryCode("US") + .build()) + .build()); + } + + @Test + public void testSuccess_updatingLocalizedPostalInfoDeletesInternationalized() throws Exception { + setEppInput("contact_update_localized.xml"); + ContactResource contact = + persistResource( + newContactResource(getUniqueIdFromCommand()).asBuilder() + .setInternationalizedPostalInfo(new PostalInfo.Builder() + .setType(Type.INTERNATIONALIZED) + .setAddress(new ContactAddress.Builder() + .setStreet(ImmutableList.of("111 8th Ave", "4th Floor")) + .setCity("New York") + .setState("NY") + .setZip("10011") + .setCountryCode("US") + .build()) + .build()) + .build()); + clock.advanceOneMilli(); + // The test xml updates the localized postal info and should therefore implicitly delete + // the internationalized one since they are treated as a pair for update purposes. + assertAboutContacts().that(contact) + .hasNonNullInternationalizedPostalInfo().and() + .hasNullLocalizedPostalInfo(); + + runFlowAssertResponse(readFile("contact_update_response.xml")); + assertAboutContacts().that(reloadResourceByForeignKey()) + .hasNullInternationalizedPostalInfo().and() + .hasLocalizedPostalInfo(new PostalInfo.Builder() + .setType(Type.LOCALIZED) + .setAddress(new ContactAddress.Builder() + .setStreet(ImmutableList.of("124 Example Dr.", "Suite 200")) + .setCity("Dulles") + .setState("VA") + .setZip("20166-6503") + .setCountryCode("US") + .build()) + .build()); } @Test @@ -148,6 +196,73 @@ public class ContactUpdateFlowTest .build()); } + + @Test + public void testSuccess_updateOnePostalInfo_touchOtherPostalInfoPreservesIt() throws Exception { + setEppInput("contact_update_partial_postalinfo_preserve_int.xml"); + persistResource( + newContactResource(getUniqueIdFromCommand()).asBuilder() + .setLocalizedPostalInfo(new PostalInfo.Builder() + .setType(Type.LOCALIZED) + .setName("A. Person") + .setOrg("Company Inc.") + .setAddress(new ContactAddress.Builder() + .setStreet(ImmutableList.of("123 4th st", "5th Floor")) + .setCity("City") + .setState("AB") + .setZip("12345") + .setCountryCode("US") + .build()) + .build()) + .setInternationalizedPostalInfo(new PostalInfo.Builder() + .setType(Type.INTERNATIONALIZED) + .setName("B. Person") + .setOrg("Company Co.") + .setAddress(new ContactAddress.Builder() + .setStreet(ImmutableList.of("100 200th Dr.", "6th Floor")) + .setCity("Town") + .setState("CD") + .setZip("67890") + .setCountryCode("US") + .build()) + .build()) + .build()); + clock.advanceOneMilli(); + // The test xml updates the address of the localized postal info. It also sets the name of the + // internationalized postal info to the same value it previously had, which causes it to be + // preserved. If the xml had not mentioned the internationalized one at all it would have been + // deleted. + runFlowAssertResponse(readFile("contact_update_response.xml")); + assertAboutContacts().that(reloadResourceByForeignKey()) + .hasLocalizedPostalInfo( + new PostalInfo.Builder() + .setType(Type.LOCALIZED) + .setName("A. Person") + .setOrg("Company Inc.") + .setAddress(new ContactAddress.Builder() + .setStreet(ImmutableList.of("456 5th st")) + .setCity("Place") + .setState("CD") + .setZip("54321") + .setCountryCode("US") + .build()) + .build()) + .and() + .hasInternationalizedPostalInfo( + new PostalInfo.Builder() + .setType(Type.INTERNATIONALIZED) + .setName("B. Person") + .setOrg("Company Co.") + .setAddress(new ContactAddress.Builder() + .setStreet(ImmutableList.of("100 200th Dr.", "6th Floor")) + .setCity("Town") + .setState("CD") + .setZip("67890") + .setCountryCode("US") + .build()) + .build()); + } + @Test public void testFailure_neverExisted() throws Exception { thrown.expect( @@ -264,7 +379,7 @@ public class ContactUpdateFlowTest public void testFailure_addRemoveSameValue() throws Exception { setEppInput("contact_update_add_remove_same.xml"); persistActiveContact(getUniqueIdFromCommand()); - thrown.expect(AddRemoveSameValueEppException.class); + thrown.expect(AddRemoveSameValueException.class); runFlow(); } } diff --git a/javatests/google/registry/flows/contact/testdata/contact_update_localized.xml b/javatests/google/registry/flows/contact/testdata/contact_update_localized.xml new file mode 100644 index 000000000..8c922f8c7 --- /dev/null +++ b/javatests/google/registry/flows/contact/testdata/contact_update_localized.xml @@ -0,0 +1,36 @@ + + + + + sh8013 + + + + + + + + 124 Example Dr. + Suite 200 + Dulles + VA + 20166-6503 + US + + + +1.7034444444 + + + 2fooBAR + + + + + + + + + ABC-12345 + + diff --git a/javatests/google/registry/flows/contact/testdata/contact_update_partial_postalinfo_preserve_int.xml b/javatests/google/registry/flows/contact/testdata/contact_update_partial_postalinfo_preserve_int.xml new file mode 100644 index 000000000..28b6b062d --- /dev/null +++ b/javatests/google/registry/flows/contact/testdata/contact_update_partial_postalinfo_preserve_int.xml @@ -0,0 +1,25 @@ + + + + + sh8013 + + + + 456 5th st + Place + CD + 54321 + US + + + + Company Co. + + + + + ABC-12345 + + diff --git a/javatests/google/registry/flows/domain/DomainApplicationUpdateFlowTest.java b/javatests/google/registry/flows/domain/DomainApplicationUpdateFlowTest.java index 7c8aecf06..cf2d606c9 100644 --- a/javatests/google/registry/flows/domain/DomainApplicationUpdateFlowTest.java +++ b/javatests/google/registry/flows/domain/DomainApplicationUpdateFlowTest.java @@ -32,8 +32,10 @@ import com.google.common.collect.ImmutableSet; import com.googlecode.objectify.Key; import google.registry.flows.EppException.UnimplementedExtensionException; import google.registry.flows.ResourceFlowTestCase; +import google.registry.flows.ResourceFlowUtils.AddRemoveSameValueException; import google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException; import google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException; +import google.registry.flows.ResourceFlowUtils.StatusNotClientSettableException; import google.registry.flows.domain.DomainApplicationUpdateFlow.ApplicationStatusProhibitsUpdateException; import google.registry.flows.domain.DomainFlowUtils.ApplicationDomainNameMismatchException; import google.registry.flows.domain.DomainFlowUtils.DuplicateContactForRoleException; @@ -50,10 +52,8 @@ import google.registry.flows.domain.DomainFlowUtils.SecDnsAllUsageException; import google.registry.flows.domain.DomainFlowUtils.TooManyDsRecordsException; import google.registry.flows.domain.DomainFlowUtils.TooManyNameserversException; import google.registry.flows.domain.DomainFlowUtils.UrgentAttributeNotSupportedException; -import google.registry.flows.exceptions.AddRemoveSameValueEppException; import google.registry.flows.exceptions.ResourceHasClientUpdateProhibitedException; import google.registry.flows.exceptions.ResourceStatusProhibitsOperationException; -import google.registry.flows.exceptions.StatusNotClientSettableException; import google.registry.model.contact.ContactResource; import google.registry.model.domain.DesignatedContact; import google.registry.model.domain.DesignatedContact.Type; @@ -587,7 +587,7 @@ public class DomainApplicationUpdateFlowTest .setNameservers(ImmutableSet.of(Key.create( loadByForeignKey(HostResource.class, "ns1.example.tld", clock.nowUtc())))) .build()); - thrown.expect(AddRemoveSameValueEppException.class); + thrown.expect(AddRemoveSameValueException.class); runFlow(); } @@ -601,7 +601,7 @@ public class DomainApplicationUpdateFlowTest Key.create( loadByForeignKey(ContactResource.class, "sh8013", clock.nowUtc()))))) .build()); - thrown.expect(AddRemoveSameValueEppException.class); + thrown.expect(AddRemoveSameValueException.class); runFlow(); } diff --git a/javatests/google/registry/flows/domain/DomainUpdateFlowTest.java b/javatests/google/registry/flows/domain/DomainUpdateFlowTest.java index 5a3af9d64..36bf13daa 100644 --- a/javatests/google/registry/flows/domain/DomainUpdateFlowTest.java +++ b/javatests/google/registry/flows/domain/DomainUpdateFlowTest.java @@ -44,8 +44,10 @@ import com.googlecode.objectify.Key; import google.registry.flows.EppException.UnimplementedExtensionException; import google.registry.flows.EppRequestSource; import google.registry.flows.ResourceFlowTestCase; +import google.registry.flows.ResourceFlowUtils.AddRemoveSameValueException; import google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException; import google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException; +import google.registry.flows.ResourceFlowUtils.StatusNotClientSettableException; import google.registry.flows.domain.DomainFlowUtils.DuplicateContactForRoleException; import google.registry.flows.domain.DomainFlowUtils.EmptySecDnsUpdateException; import google.registry.flows.domain.DomainFlowUtils.FeesMismatchException; @@ -64,11 +66,9 @@ import google.registry.flows.domain.DomainFlowUtils.SecDnsAllUsageException; import google.registry.flows.domain.DomainFlowUtils.TooManyDsRecordsException; import google.registry.flows.domain.DomainFlowUtils.TooManyNameserversException; import google.registry.flows.domain.DomainFlowUtils.UrgentAttributeNotSupportedException; -import google.registry.flows.exceptions.AddRemoveSameValueEppException; import google.registry.flows.exceptions.OnlyToolCanPassMetadataException; import google.registry.flows.exceptions.ResourceHasClientUpdateProhibitedException; import google.registry.flows.exceptions.ResourceStatusProhibitsOperationException; -import google.registry.flows.exceptions.StatusNotClientSettableException; import google.registry.model.billing.BillingEvent; import google.registry.model.billing.BillingEvent.Reason; import google.registry.model.contact.ContactResource; @@ -1003,7 +1003,7 @@ public class DomainUpdateFlowTest extends ResourceFlowTestCase { private void setEppHostUpdateInput( - String oldHostName, String newHostName, String addHostAddrs, String remHostAddrs) { + String oldHostName, String newHostName, String ipOrStatusToAdd, String ipOrStatusToRem) { setEppInput( "host_update.xml", ImmutableMap.of( "OLD-HOSTNAME", oldHostName, "NEW-HOSTNAME", newHostName, - "ADD-HOSTADDRS", (addHostAddrs == null) ? "" : addHostAddrs, - "REM-HOSTADDRS", (remHostAddrs == null) ? "" : remHostAddrs)); + "ADD-HOSTADDRSORSTATUS", nullToEmpty(ipOrStatusToAdd), + "REM-HOSTADDRSORSTATUS", nullToEmpty(ipOrStatusToRem))); } public HostUpdateFlowTest() { @@ -793,6 +795,34 @@ public class HostUpdateFlowTest extends ResourceFlowTestCase", + ""); + persistActiveHost(oldHostName()); + thrown.expect(AddRemoveSameValueException.class); + runFlow(); + } + + @Test + public void testFailure_addRemoveSameInetAddresses() throws Exception { + createTld("tld"); + persistActiveDomain("example.tld"); + setEppHostUpdateInput( + "ns1.example.tld", + "ns2.example.tld", + "192.0.2.22", + "192.0.2.22"); + persistActiveHost(oldHostName()); + thrown.expect(AddRemoveSameValueException.class); + runFlow(); + } + @Test public void testFailure_clientProhibitedStatusValue() throws Exception { createTld("tld"); diff --git a/javatests/google/registry/flows/host/testdata/host_update.xml b/javatests/google/registry/flows/host/testdata/host_update.xml index 45d2d98cb..f49b05637 100644 --- a/javatests/google/registry/flows/host/testdata/host_update.xml +++ b/javatests/google/registry/flows/host/testdata/host_update.xml @@ -5,11 +5,11 @@ xmlns:host="urn:ietf:params:xml:ns:host-1.0"> %OLD-HOSTNAME% - %ADD-HOSTADDRS% + %ADD-HOSTADDRSORSTATUS% - %REM-HOSTADDRS% + %REM-HOSTADDRSORSTATUS% %NEW-HOSTNAME% diff --git a/javatests/google/registry/model/contact/ContactCommandTest.java b/javatests/google/registry/model/contact/ContactCommandTest.java index cff306065..fc0f9f275 100644 --- a/javatests/google/registry/model/contact/ContactCommandTest.java +++ b/javatests/google/registry/model/contact/ContactCommandTest.java @@ -16,14 +16,10 @@ package google.registry.model.contact; import static google.registry.flows.EppXmlTransformer.marshalInput; import static google.registry.flows.EppXmlTransformer.validateInput; -import static google.registry.testing.ContactResourceSubject.assertAboutContacts; -import static google.registry.testing.DatastoreHelper.createTld; import static google.registry.xml.ValidationMode.LENIENT; import static google.registry.xml.XmlTestUtils.assertXmlEquals; import static java.nio.charset.StandardCharsets.UTF_8; -import com.google.common.collect.ImmutableList; -import google.registry.model.contact.PostalInfo.Type; import google.registry.testing.AppEngineRule; import google.registry.testing.EppLoader; import org.junit.Rule; @@ -103,75 +99,4 @@ public class ContactCommandTest { public void testTransferRequest() throws Exception { doXmlRoundtripTest("contact_transfer_request.xml"); } - - @Test - public void testPostalInfoOverlay() { - createTld("foo"); - - ContactResource contact = new ContactResource.Builder() - .setLocalizedPostalInfo(new PostalInfo.Builder() - .setType(Type.LOCALIZED) - .setName("loc name") - .build()) - .setInternationalizedPostalInfo(new PostalInfo.Builder() - .setType(Type.INTERNATIONALIZED) - .setName("int name") - .build()) - .build(); - ContactCommand.Update.Change change = new ContactCommand.Update.Change(); - - // Updating one field of the loc should delete the int and leave the loc otherwise untouched. - change.postalInfo = ImmutableList.of(new PostalInfo.Builder() - .setType(Type.LOCALIZED) - .setOrg("org") - .build()); - ContactResource.Builder builder = contact.asBuilder(); - change.applyTo(builder); - ContactResource changed = builder.build(); - assertAboutContacts().that(changed) - .hasNullInternationalizedPostalInfo().and() - .hasLocalizedPostalInfo(new PostalInfo.Builder() - .setType(Type.LOCALIZED) - .setName("loc name") - .setOrg("org") - .build()); - - // Updating one field of the int should delete the loc and leave the int otherwise untouched. - change.postalInfo = ImmutableList.of(new PostalInfo.Builder() - .setType(Type.INTERNATIONALIZED) - .setOrg("org") - .build()); - builder = contact.asBuilder(); - change.applyTo(builder); - changed = builder.build(); - assertAboutContacts().that(changed) - .hasNullLocalizedPostalInfo().and() - .hasInternationalizedPostalInfo(new PostalInfo.Builder() - .setType(Type.INTERNATIONALIZED) - .setName("int name") - .setOrg("org") - .build()); - - // Updating one field of the int and touching the loc with no changes should preserve both. - change.postalInfo = ImmutableList.of( - new PostalInfo.Builder() - .setType(Type.INTERNATIONALIZED) - .setName("new int name") - .build(), - new PostalInfo.Builder() - .setType(Type.LOCALIZED) - .build()); - builder = contact.asBuilder(); - change.applyTo(builder); - changed = builder.build(); - assertAboutContacts().that(changed) - .hasLocalizedPostalInfo(new PostalInfo.Builder() - .setType(Type.LOCALIZED) - .setName("loc name") - .build()).and() - .hasInternationalizedPostalInfo(new PostalInfo.Builder() - .setType(Type.INTERNATIONALIZED) - .setName("new int name") - .build()); - } }