diff --git a/java/google/registry/flows/domain/BaseDomainUpdateFlow.java b/java/google/registry/flows/domain/BaseDomainUpdateFlow.java index 7c7716219..1f48b77d8 100644 --- a/java/google/registry/flows/domain/BaseDomainUpdateFlow.java +++ b/java/google/registry/flows/domain/BaseDomainUpdateFlow.java @@ -27,8 +27,8 @@ import static google.registry.flows.domain.DomainFlowUtils.validateRegistrantAll import static google.registry.flows.domain.DomainFlowUtils.validateRequiredContactsPresent; import static google.registry.flows.domain.DomainFlowUtils.verifyNotInPendingDelete; +import com.google.common.base.Optional; import com.google.common.collect.ImmutableSet; - import google.registry.flows.EppException; import google.registry.flows.EppException.ParameterValuePolicyErrorException; import google.registry.flows.EppException.RequiredParameterMissingException; @@ -41,7 +41,6 @@ import google.registry.model.domain.secdns.DelegationSignerData; import google.registry.model.domain.secdns.SecDnsUpdateExtension; import google.registry.model.domain.secdns.SecDnsUpdateExtension.Add; import google.registry.model.domain.secdns.SecDnsUpdateExtension.Remove; - import java.util.Set; /** @@ -53,10 +52,18 @@ import java.util.Set; public abstract class BaseDomainUpdateFlow> extends ResourceUpdateFlow { + protected Optional extraFlowLogic; + @Override public final void initResourceCreateOrMutateFlow() throws EppException { command = cloneAndLinkReferences(command, now); initDomainUpdateFlow(); + // In certain conditions (for instance, errors), there is no existing resource. + if (existingResource == null) { + extraFlowLogic = Optional.absent(); + } else { + extraFlowLogic = RegistryExtraFlowLogicProxy.newInstanceForTld(existingResource.getTld()); + } } @SuppressWarnings("unused") @@ -98,8 +105,13 @@ public abstract class BaseDomainUpdateFlow whitelist = Registry.get(tld).getAllowedRegistrantContactIds(); // Empty whitelist or null registrantContactId are ignored. - if (registrantContactId != null && !whitelist.isEmpty() + if (registrantContactId != null && !whitelist.isEmpty() && !whitelist.contains(registrantContactId)) { throw new RegistrantNotAllowedException(registrantContactId); } @@ -576,7 +576,7 @@ public class DomainFlowUtils { if (feeRequest.getPhase() != null || feeRequest.getSubphase() != null) { throw new FeeChecksDontSupportPhasesException(); } - + CurrencyUnit currency = feeRequest.isCurrencySupported() ? feeRequest.getCurrency() : topLevelCurrency; if ((currency != null) && !currency.equals(registry.getCurrency())) { @@ -620,7 +620,7 @@ public class DomainFlowUtils { } } - static void validateFeeChallenge( + public static void validateFeeChallenge( String domainName, String tld, DateTime priceTime, @@ -664,7 +664,7 @@ public class DomainFlowUtils { throw new CurrencyUnitMismatchException(); } if (!feeTotal.equals(costTotal)) { - throw new FeesMismatchException(); + throw new FeesMismatchException(costTotal); } } @@ -988,10 +988,16 @@ public class DomainFlowUtils { } /** The fees passed in the transform command do not match the fees that will be charged. */ - static class FeesMismatchException extends ParameterValueRangeErrorException { + public static class FeesMismatchException extends ParameterValueRangeErrorException { public FeesMismatchException() { super("The fees passed in the transform command do not match the fees that will be charged"); } + + public FeesMismatchException(Money correctFee) { + super(String.format( + "The fees passed in the transform command do not match the expected total of %s", + correctFee)); + } } /** Registrar is not authorized to access this TLD. */ diff --git a/java/google/registry/flows/domain/DomainInfoFlow.java b/java/google/registry/flows/domain/DomainInfoFlow.java index 0f63d2abb..8af0b758f 100644 --- a/java/google/registry/flows/domain/DomainInfoFlow.java +++ b/java/google/registry/flows/domain/DomainInfoFlow.java @@ -16,6 +16,7 @@ package google.registry.flows.domain; import static google.registry.flows.domain.DomainFlowUtils.handleFeeRequest; +import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import google.registry.flows.EppException; @@ -23,9 +24,11 @@ import google.registry.model.domain.DomainResource; import google.registry.model.domain.DomainResource.Builder; import google.registry.model.domain.fee06.FeeInfoCommandExtensionV06; import google.registry.model.domain.fee06.FeeInfoResponseExtensionV06; +import google.registry.model.domain.flags.FlagsInfoResponseExtension; import google.registry.model.domain.rgp.GracePeriodStatus; import google.registry.model.domain.rgp.RgpInfoExtension; import google.registry.model.eppoutput.EppResponse.ResponseExtension; +import java.util.List; import javax.inject.Inject; /** @@ -100,6 +103,17 @@ public class DomainInfoFlow extends BaseDomainInfoFlow now); extensions.add(builder.build()); } + // If the TLD uses the flags extension, add it to the info response. + Optional extraLogicManager = + RegistryExtraFlowLogicProxy.newInstanceForTld(existingResource.getTld()); + if (extraLogicManager.isPresent()) { + List flags = extraLogicManager.get().getFlags( + existingResource, this.getClientId(), now); // As-of date is always now for info commands. + if (!flags.isEmpty()) { + extensions.add(FlagsInfoResponseExtension.create(flags)); + } + } + return extensions.build(); } } diff --git a/java/google/registry/flows/domain/DomainUpdateFlow.java b/java/google/registry/flows/domain/DomainUpdateFlow.java index d21ca541a..4c3e35eb4 100644 --- a/java/google/registry/flows/domain/DomainUpdateFlow.java +++ b/java/google/registry/flows/domain/DomainUpdateFlow.java @@ -22,11 +22,13 @@ import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; import google.registry.dns.DnsQueue; +import google.registry.flows.EppException; import google.registry.model.billing.BillingEvent; import google.registry.model.billing.BillingEvent.Reason; import google.registry.model.domain.DomainResource; import google.registry.model.domain.DomainResource.Builder; import google.registry.model.domain.GracePeriod; +import google.registry.model.domain.flags.FlagsUpdateCommandExtension; import google.registry.model.domain.rgp.GracePeriodStatus; import google.registry.model.domain.secdns.SecDnsUpdateExtension; import google.registry.model.eppcommon.StatusValue; @@ -70,11 +72,11 @@ public class DomainUpdateFlow extends BaseDomainUpdateFlow sunrushAddGracePeriod = Iterables.tryFind( existingResource.getGracePeriods(), @@ -127,6 +129,11 @@ public class DomainUpdateFlow extends BaseDomainUpdateFlow getFlags( + DomainResource domainResource, String clientIdentifier, DateTime asOfDate); + + /** + * Add and remove flags passed via the EPP flags extension. Any changes should not be persisted to + * Datastore until commitAdditionalDomainUpdates is called. Name suggested by Benjamin McIlwain. + */ + public void performAdditionalDomainUpdateLogic( + DomainResource domainResource, + String clientIdentifier, + DateTime asOfDate, + EppInput eppInput) throws EppException; + + /** Commit any changes made as a result of a call to performAdditionalDomainUpdateLogic(). */ + public void commitAdditionalDomainUpdates(); +} diff --git a/java/google/registry/flows/domain/RegistryExtraFlowLogicProxy.java b/java/google/registry/flows/domain/RegistryExtraFlowLogicProxy.java new file mode 100644 index 000000000..1f5a7f148 --- /dev/null +++ b/java/google/registry/flows/domain/RegistryExtraFlowLogicProxy.java @@ -0,0 +1,49 @@ +// Copyright 2016 The Domain Registry 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.domain; + +import com.google.common.base.Optional; +import google.registry.model.registry.Registry; +import java.util.HashMap; + +/** + * Static class to return the correct {@link RegistryExtraFlowLogic} for a particular TLD. + * Eventually, this will probably be replaced with dependency injection, barring unforeseen + * complications. + */ +public class RegistryExtraFlowLogicProxy { + + private static final HashMap> + extraLogicOverrideMap = new HashMap<>(); + + public static void setOverride( + String tld, Class extraLogicClass) { + if (Registry.get(tld) == null) { + throw new IllegalArgumentException(tld + " does not exist"); + } + extraLogicOverrideMap.put(tld, extraLogicClass); + } + + public static Optional newInstanceForTld(String tld) { + if (extraLogicOverrideMap.containsKey(tld)) { + try { + return Optional.of(extraLogicOverrideMap.get(tld).newInstance()); + } catch (InstantiationException | IllegalAccessException e) { + return Optional.absent(); + } + } + return Optional.absent(); + } +} diff --git a/java/google/registry/model/domain/flags/FlagsInfoResponseExtension.java b/java/google/registry/model/domain/flags/FlagsInfoResponseExtension.java index 82b5b5b42..49dcc914b 100644 --- a/java/google/registry/model/domain/flags/FlagsInfoResponseExtension.java +++ b/java/google/registry/model/domain/flags/FlagsInfoResponseExtension.java @@ -30,4 +30,10 @@ import javax.xml.bind.annotation.XmlRootElement; public class FlagsInfoResponseExtension implements ResponseExtension { @XmlElement(name = "flag") List flags; + + public static FlagsInfoResponseExtension create(List flags) { + FlagsInfoResponseExtension extension = new FlagsInfoResponseExtension(); + extension.flags = flags; + return extension; + } } diff --git a/java/google/registry/model/domain/flags/FlagsList.java b/java/google/registry/model/domain/flags/FlagsList.java index cdb3020e5..f0782fa24 100644 --- a/java/google/registry/model/domain/flags/FlagsList.java +++ b/java/google/registry/model/domain/flags/FlagsList.java @@ -24,4 +24,8 @@ import javax.xml.bind.annotation.XmlElement; public class FlagsList { @XmlElement(name = "flag") List flags; + + public List getFlags() { + return flags; + } } diff --git a/java/google/registry/model/domain/flags/FlagsUpdateCommandExtension.java b/java/google/registry/model/domain/flags/FlagsUpdateCommandExtension.java index a513d0f52..7a8521852 100644 --- a/java/google/registry/model/domain/flags/FlagsUpdateCommandExtension.java +++ b/java/google/registry/model/domain/flags/FlagsUpdateCommandExtension.java @@ -30,4 +30,12 @@ import javax.xml.bind.annotation.XmlType; public class FlagsUpdateCommandExtension implements CommandExtension { FlagsList add; // list of flags to be added (turned on) FlagsList rem; // list of flags to be removed (turned off) + + public FlagsList getAddFlags() { + return add; + } + + public FlagsList getRemoveFlags() { + return rem; + } } diff --git a/javatests/google/registry/flows/FlowTestCase.java b/javatests/google/registry/flows/FlowTestCase.java index d77e75076..8489ff7c5 100644 --- a/javatests/google/registry/flows/FlowTestCase.java +++ b/javatests/google/registry/flows/FlowTestCase.java @@ -39,6 +39,7 @@ import google.registry.model.billing.BillingEvent; import google.registry.model.domain.GracePeriod; import google.registry.model.domain.rgp.GracePeriodStatus; import google.registry.model.eppcommon.ProtocolDefinition; +import google.registry.model.eppinput.EppInput; import google.registry.model.eppoutput.EppOutput; import google.registry.model.ofy.Ofy; import google.registry.model.poll.PollMessage; @@ -112,6 +113,11 @@ public abstract class FlowTestCase extends ShardableTestCase { eppLoader = new EppLoader(this, inputFilename, substitutions); } + /** Returns the EPP data loaded by a previous call to setEppInput. */ + protected EppInput getEppInput() throws Exception { + return eppLoader.getEpp(); + } + protected String readFile(String filename) { return readResourceUtf8(getClass(), "testdata/" + filename); } diff --git a/javatests/google/registry/flows/domain/DomainInfoFlowTest.java b/javatests/google/registry/flows/domain/DomainInfoFlowTest.java index 2eee265d1..975019bcb 100644 --- a/javatests/google/registry/flows/domain/DomainInfoFlowTest.java +++ b/javatests/google/registry/flows/domain/DomainInfoFlowTest.java @@ -17,6 +17,7 @@ package google.registry.flows.domain; import static com.google.common.io.BaseEncoding.base16; import static google.registry.testing.DatastoreHelper.assertNoBillingEvents; import static google.registry.testing.DatastoreHelper.createTld; +import static google.registry.testing.DatastoreHelper.createTlds; import static google.registry.testing.DatastoreHelper.newDomainResource; import static google.registry.testing.DatastoreHelper.persistActiveContact; import static google.registry.testing.DatastoreHelper.persistActiveHost; @@ -42,6 +43,7 @@ import google.registry.model.domain.DesignatedContact.Type; import google.registry.model.domain.DomainAuthInfo; import google.registry.model.domain.DomainResource; import google.registry.model.domain.GracePeriod; +import google.registry.model.domain.TestExtraLogicManager; import google.registry.model.domain.rgp.GracePeriodStatus; import google.registry.model.domain.secdns.DelegationSignerData; import google.registry.model.eppcommon.AuthInfo.PasswordAuth; @@ -67,9 +69,11 @@ public class DomainInfoFlowTest extends ResourceFlowTestCase + + + + domain.flags + + + ABC-12345 + + diff --git a/javatests/google/registry/flows/domain/testdata/domain_info_flags_two.xml b/javatests/google/registry/flows/domain/testdata/domain_info_flags_two.xml new file mode 100644 index 000000000..885a5fb71 --- /dev/null +++ b/javatests/google/registry/flows/domain/testdata/domain_info_flags_two.xml @@ -0,0 +1,11 @@ + + + + + domain-flag1-flag2.flags + + + ABC-12345 + + diff --git a/javatests/google/registry/flows/domain/testdata/domain_info_response_flags_none.xml b/javatests/google/registry/flows/domain/testdata/domain_info_response_flags_none.xml new file mode 100644 index 000000000..6087a6187 --- /dev/null +++ b/javatests/google/registry/flows/domain/testdata/domain_info_response_flags_none.xml @@ -0,0 +1,38 @@ + + + + Command completed successfully + + + + domain.flags + %ROID% + + jd1234 + sh8013 + sh8013 + + ns1.example.tld + ns1.example.net + + ns1.example.tld + ns2.example.tld + NewRegistrar + TheRegistrar + 1999-04-03T22:00:00.0Z + NewRegistrar + 1999-12-03T09:00:00.0Z + 2005-04-03T22:00:00.0Z + 2000-04-08T09:00:00.0Z + + 2fooBAR + + + + + ABC-12345 + server-trid + + + diff --git a/javatests/google/registry/flows/domain/testdata/domain_info_response_flags_two.xml b/javatests/google/registry/flows/domain/testdata/domain_info_response_flags_two.xml new file mode 100644 index 000000000..d5b82d639 --- /dev/null +++ b/javatests/google/registry/flows/domain/testdata/domain_info_response_flags_two.xml @@ -0,0 +1,44 @@ + + + + Command completed successfully + + + + domain-flag1-flag2.flags + %ROID% + + jd1234 + sh8013 + sh8013 + + ns1.example.tld + ns1.example.net + + ns1.example.tld + ns2.example.tld + NewRegistrar + TheRegistrar + 1999-04-03T22:00:00.0Z + NewRegistrar + 1999-12-03T09:00:00.0Z + 2005-04-03T22:00:00.0Z + 2000-04-08T09:00:00.0Z + + 2fooBAR + + + + + + flag1 + flag2 + + + + ABC-12345 + server-trid + + + diff --git a/javatests/google/registry/flows/domain/testdata/domain_update_addremove_flags.xml b/javatests/google/registry/flows/domain/testdata/domain_update_addremove_flags.xml new file mode 100644 index 000000000..206ef1780 --- /dev/null +++ b/javatests/google/registry/flows/domain/testdata/domain_update_addremove_flags.xml @@ -0,0 +1,23 @@ + + + + + example.flags + + + + + + flag1 + flag2 + + + flag3 + flag4 + + + + ABC-12345 + + diff --git a/javatests/google/registry/model/domain/TestExtraLogicManager.java b/javatests/google/registry/model/domain/TestExtraLogicManager.java new file mode 100644 index 000000000..0a5621c2e --- /dev/null +++ b/javatests/google/registry/model/domain/TestExtraLogicManager.java @@ -0,0 +1,63 @@ +// Copyright 2016 The Domain Registry Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package google.registry.model.domain; + +import com.google.common.base.Splitter; +import com.google.common.collect.Iterables; +import google.registry.flows.EppException; +import google.registry.flows.EppException.UnimplementedExtensionException; +import google.registry.flows.domain.RegistryExtraFlowLogic; +import google.registry.model.domain.flags.FlagsUpdateCommandExtension; +import google.registry.model.eppinput.EppInput; +import java.util.List; +import org.joda.time.DateTime; + +/** + * Fake extra logic manager which synthesizes information from the domain name for testing purposes. + */ +public class TestExtraLogicManager implements RegistryExtraFlowLogic { + + @Override + public List getFlags( + DomainResource domainResource, String clientIdentifier, DateTime asOfDate) { + // Take the part before the period, split by dashes, and treat each part after the first as + // a flag. + List components = + Splitter.on('-').splitToList( + Iterables.getFirst( + Splitter.on('.').split(domainResource.getFullyQualifiedDomainName()), "")); + return components.subList(1, components.size()); + } + + @Override + public void performAdditionalDomainUpdateLogic( + DomainResource domainResource, + String clientIdentifier, + DateTime asOfDate, + EppInput eppInput) throws EppException { + FlagsUpdateCommandExtension updateFlags = + eppInput.getSingleExtension(FlagsUpdateCommandExtension.class); + if (updateFlags == null) { + return; + } + // Throw this exception as a signal to the test that we got this far. + throw new UnimplementedExtensionException(); + } + + @Override + public void commitAdditionalDomainUpdates() { + return; + } +}