diff --git a/docs/flows.md b/docs/flows.md index bf3abf996..8d2bd1add 100644 --- a/docs/flows.md +++ b/docs/flows.md @@ -299,7 +299,8 @@ An EPP flow that creates a new application for a domain resource. * The checksum in the specified TCNID does not validate. * Domain name is under tld which doesn't exist. * 2005 - * Domain name must have exactly one part above the tld. + * Domain name must have exactly one part above the TLD. + * Domain name must not equal an existing multi-part TLD. * The requested fee is expressed in a scale that is invalid for the given currency. * The specified TCNID is invalid. @@ -479,7 +480,8 @@ information. * Domain label is not allowed by IDN table. * Domain name is under tld which doesn't exist. * 2005 - * Domain name must have exactly one part above the tld. + * Domain name must have exactly one part above the TLD. + * Domain name must not equal an existing multi-part TLD. * 2201 * Registrar is not authorized to access this TLD. * 2306 @@ -530,7 +532,8 @@ An EPP flow that creates a new domain resource. * The checksum in the specified TCNID does not validate. * Domain name is under tld which doesn't exist. * 2005 - * Domain name must have exactly one part above the tld. + * Domain name must have exactly one part above the TLD. + * Domain name must not equal an existing multi-part TLD. * The requested fee is expressed in a scale that is invalid for the given currency. * The specified TCNID is invalid. diff --git a/java/google/registry/flows/domain/DomainApplicationCreateFlow.java b/java/google/registry/flows/domain/DomainApplicationCreateFlow.java index 8b7b37c5c..4f2f605f8 100644 --- a/java/google/registry/flows/domain/DomainApplicationCreateFlow.java +++ b/java/google/registry/flows/domain/DomainApplicationCreateFlow.java @@ -112,6 +112,7 @@ import org.joda.time.DateTime; * @error {@link DomainFlowUtils.BadCommandForRegistryPhaseException} * @error {@link DomainFlowUtils.BadDomainNameCharacterException} * @error {@link DomainFlowUtils.BadDomainNamePartsCountException} + * @error {@link DomainFlowUtils.DomainNameExistsAsTldException} * @error {@link DomainFlowUtils.BadPeriodUnitException} * @error {@link DomainFlowTmchUtils.Base64RequiredForEncodedSignedMarksException} * @error {@link DomainFlowUtils.ClaimsPeriodEndedException} diff --git a/java/google/registry/flows/domain/DomainCheckFlow.java b/java/google/registry/flows/domain/DomainCheckFlow.java index 35cc5595f..167c5ff55 100644 --- a/java/google/registry/flows/domain/DomainCheckFlow.java +++ b/java/google/registry/flows/domain/DomainCheckFlow.java @@ -78,6 +78,7 @@ import org.joda.time.DateTime; * @error {@link google.registry.flows.exceptions.TooManyResourceChecksException} * @error {@link DomainFlowUtils.BadDomainNameCharacterException} * @error {@link DomainFlowUtils.BadDomainNamePartsCountException} + * @error {@link DomainFlowUtils.DomainNameExistsAsTldException} * @error {@link DomainFlowUtils.BadPeriodUnitException} * @error {@link DomainFlowUtils.BadCommandForRegistryPhaseException} * @error {@link DomainFlowUtils.CurrencyUnitMismatchException} diff --git a/java/google/registry/flows/domain/DomainCreateFlow.java b/java/google/registry/flows/domain/DomainCreateFlow.java index 7a53463e1..a21b16ba5 100644 --- a/java/google/registry/flows/domain/DomainCreateFlow.java +++ b/java/google/registry/flows/domain/DomainCreateFlow.java @@ -115,6 +115,7 @@ import org.joda.time.DateTime; * @error {@link DomainFlowUtils.AcceptedTooLongAgoException} * @error {@link DomainFlowUtils.BadDomainNameCharacterException} * @error {@link DomainFlowUtils.BadDomainNamePartsCountException} + * @error {@link DomainFlowUtils.DomainNameExistsAsTldException} * @error {@link DomainFlowUtils.BadPeriodUnitException} * @error {@link DomainFlowUtils.ClaimsPeriodEndedException} * @error {@link DomainFlowUtils.CurrencyUnitMismatchException} diff --git a/java/google/registry/flows/domain/DomainFlowUtils.java b/java/google/registry/flows/domain/DomainFlowUtils.java index 522d747b0..ead7026af 100644 --- a/java/google/registry/flows/domain/DomainFlowUtils.java +++ b/java/google/registry/flows/domain/DomainFlowUtils.java @@ -25,6 +25,7 @@ import static google.registry.flows.domain.DomainPricingLogic.getMatchingLrpToke import static google.registry.model.domain.DomainResource.MAX_REGISTRATION_YEARS; import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.model.registry.Registries.findTldForName; +import static google.registry.model.registry.Registries.getTlds; import static google.registry.model.registry.label.ReservationType.FULLY_BLOCKED; import static google.registry.model.registry.label.ReservationType.NAMESERVER_RESTRICTED; import static google.registry.model.registry.label.ReservationType.RESERVED_FOR_ANCHOR_TENANT; @@ -181,6 +182,9 @@ public class DomainFlowUtils { } validateFirstLabel(parts.get(0)); InternetDomainName domainName = InternetDomainName.from(name); + if (getTlds().contains(domainName.toString())) { + throw new DomainNameExistsAsTldException(); + } Optional tldParsed = findTldForName(domainName); if (!tldParsed.isPresent()) { throw new TldDoesNotExistException(domainName.parent().toString()); @@ -1055,10 +1059,17 @@ public class DomainFlowUtils { } } - /** Domain name must have exactly one part above the tld. */ + /** Domain name must have exactly one part above the TLD. */ static class BadDomainNamePartsCountException extends ParameterValueSyntaxErrorException { public BadDomainNamePartsCountException() { - super("Domain name must have exactly one part above the tld"); + super("Domain name must have exactly one part above the TLD"); + } + } + + /** Domain name must not equal an existing multi-part TLD. */ + static class DomainNameExistsAsTldException extends ParameterValueSyntaxErrorException { + public DomainNameExistsAsTldException() { + super("Domain name must not equal an existing multi-part TLD"); } } diff --git a/javatests/google/registry/flows/EppLifecycleDomainTest.java b/javatests/google/registry/flows/EppLifecycleDomainTest.java index c25a17e61..166957599 100644 --- a/javatests/google/registry/flows/EppLifecycleDomainTest.java +++ b/javatests/google/registry/flows/EppLifecycleDomainTest.java @@ -130,7 +130,7 @@ public class EppLifecycleDomainTest extends EppTestCase { "contact_create_response_jd1234.xml", DateTime.parse("2000-06-01T00:01:00Z")); - // Create domain example.tld. + // Create domain example.tld assertCommandAndResponse( "domain_create_no_hosts_or_dsdata.xml", "domain_create_response.xml", @@ -167,7 +167,7 @@ public class EppLifecycleDomainTest extends EppTestCase { "contact_create_response_jd1234.xml", DateTime.parse("2000-06-01T00:01:00Z")); - // Create domain example.tld. + // Create domain example.tld assertCommandAndResponse( "domain_create_no_hosts_or_dsdata.xml", "domain_create_response.xml", @@ -265,7 +265,7 @@ public class EppLifecycleDomainTest extends EppTestCase { assertCommandAndResponse("login_valid.xml", "login_response.xml"); createFakesite(); - // Create domain example.tld. + // Create domain example.tld assertCommandAndResponse( "domain_create_no_hosts_or_dsdata.xml", "domain_create_response_superordinate.xml", @@ -684,4 +684,84 @@ public class EppLifecycleDomainTest extends EppTestCase { DateTime.parse("2001-01-07T00:00:00Z")); assertCommandAndResponse("logout.xml", "logout_response.xml"); } + + @Test + public void testSuccess_multipartTldsWithSharedSuffixes() throws Exception { + createTlds("bar.foo.tld", "foo.tld"); + + assertCommandAndResponse("login_valid.xml", "login_response.xml"); + + assertCommandAndResponse( + "contact_create_sh8013.xml", + null, + "contact_create_response_sh8013.xml", + ImmutableMap.of("CRDATE", "2000-06-01T00:00:00Z"), + DateTime.parse("2000-06-01T00:00:00Z")); + assertCommandAndResponse( + "contact_create_jd1234.xml", + "contact_create_response_jd1234.xml", + DateTime.parse("2000-06-01T00:01:00Z")); + + // Create domain example.bar.foo.tld + assertCommandAndResponse( + "domain_create_wildcard.xml", + ImmutableMap.of("HOSTNAME", "example.bar.foo.tld"), + "domain_create_response_wildcard.xml", + ImmutableMap.of("DOMAIN", "example.bar.foo.tld"), + DateTime.parse("2000-06-01T00:02:00Z")); + + // Create domain example.foo.tld + assertCommandAndResponse( + "domain_create_wildcard.xml", + ImmutableMap.of("HOSTNAME", "example.foo.tld"), + "domain_create_response_wildcard.xml", + ImmutableMap.of("DOMAIN", "example.foo.tld"), + DateTime.parse("2000-06-01T00:02:00Z")); + + // Create domain example.tld + assertCommandAndResponse( + "domain_create_wildcard.xml", + ImmutableMap.of("HOSTNAME", "example.tld"), + "domain_create_response_wildcard.xml", + ImmutableMap.of("DOMAIN", "example.tld"), + DateTime.parse("2000-06-01T00:02:00Z")); + + assertCommandAndResponse("logout.xml", "logout_response.xml"); + } + + @Test + public void testSuccess_multipartTldsWithSharedPrefixes() throws Exception { + createTld("tld.foo"); + + assertCommandAndResponse("login_valid.xml", "login_response.xml"); + + assertCommandAndResponse( + "contact_create_sh8013.xml", + null, + "contact_create_response_sh8013.xml", + ImmutableMap.of("CRDATE", "2000-06-01T00:00:00Z"), + DateTime.parse("2000-06-01T00:00:00Z")); + assertCommandAndResponse( + "contact_create_jd1234.xml", + "contact_create_response_jd1234.xml", + DateTime.parse("2000-06-01T00:01:00Z")); + + // Create domain example.tld.foo + assertCommandAndResponse( + "domain_create_wildcard.xml", + ImmutableMap.of("HOSTNAME", "example.tld.foo"), + "domain_create_response_wildcard.xml", + ImmutableMap.of("DOMAIN", "example.tld.foo"), + DateTime.parse("2000-06-01T00:02:00Z")); + + // Create domain example.tld + assertCommandAndResponse( + "domain_create_wildcard.xml", + ImmutableMap.of("HOSTNAME", "example.tld"), + "domain_create_response_wildcard.xml", + ImmutableMap.of("DOMAIN", "example.tld"), + DateTime.parse("2000-06-01T00:02:00Z")); + + assertCommandAndResponse("logout.xml", "logout_response.xml"); + } } diff --git a/javatests/google/registry/flows/EppLifecycleHostTest.java b/javatests/google/registry/flows/EppLifecycleHostTest.java index cc3d20f12..0a7218ecf 100644 --- a/javatests/google/registry/flows/EppLifecycleHostTest.java +++ b/javatests/google/registry/flows/EppLifecycleHostTest.java @@ -14,12 +14,19 @@ package google.registry.flows; +import static com.google.common.truth.Truth.assertThat; +import static google.registry.model.EppResourceUtils.loadByForeignKey; import static google.registry.model.eppoutput.Result.Code.SUCCESS; import static google.registry.model.eppoutput.Result.Code.SUCCESS_WITH_ACTION_PENDING; import static google.registry.testing.DatastoreHelper.createTld; +import static google.registry.testing.DatastoreHelper.createTlds; import static google.registry.testing.EppMetricSubject.assertThat; +import static google.registry.testing.HostResourceSubject.assertAboutHosts; import com.google.common.collect.ImmutableMap; +import com.googlecode.objectify.Key; +import google.registry.model.domain.DomainResource; +import google.registry.model.host.HostResource; import google.registry.testing.AppEngineRule; import org.joda.time.DateTime; import org.junit.Rule; @@ -148,4 +155,106 @@ public class EppLifecycleHostTest extends EppTestCase { DateTime.parse("2000-06-11T00:08:00Z")); assertCommandAndResponse("logout.xml", "logout_response.xml"); } + + @Test + public void testSuccess_multipartTldsWithSharedSuffixes() throws Exception { + createTlds("bar.foo.tld", "foo.tld", "tld"); + + assertCommandAndResponse("login_valid.xml", "login_response.xml"); + + assertCommandAndResponse( + "contact_create_sh8013.xml", + ImmutableMap.of(), + "contact_create_response_sh8013.xml", + ImmutableMap.of("CRDATE", "2000-06-01T00:00:00Z"), + DateTime.parse("2000-06-01T00:00:00Z")); + assertCommandAndResponse( + "contact_create_jd1234.xml", + "contact_create_response_jd1234.xml", + DateTime.parse("2000-06-01T00:01:00Z")); + + // Create domain example.bar.foo.tld + assertCommandAndResponse( + "domain_create_wildcard.xml", + ImmutableMap.of("HOSTNAME", "example.bar.foo.tld"), + "domain_create_response_wildcard.xml", + ImmutableMap.of("DOMAIN", "example.bar.foo.tld"), + DateTime.parse("2000-06-01T00:02:00Z")); + + // Create domain example.foo.tld + assertCommandAndResponse( + "domain_create_wildcard.xml", + ImmutableMap.of("HOSTNAME", "example.foo.tld"), + "domain_create_response_wildcard.xml", + ImmutableMap.of("DOMAIN", "example.foo.tld"), + DateTime.parse("2000-06-01T00:02:00Z")); + + // Create domain example.tld + assertCommandAndResponse( + "domain_create_wildcard.xml", + ImmutableMap.of("HOSTNAME", "example.tld"), + "domain_create_response_wildcard.xml", + ImmutableMap.of("DOMAIN", "example.tld"), + DateTime.parse("2000-06-01T00:02:00Z")); + + // Create host ns1.example.bar.foo.tld + assertCommandAndResponse( + "host_create_with_ips.xml", + ImmutableMap.of("HOSTNAME", "ns1.example.bar.foo.tld"), + "host_create_response.xml", + ImmutableMap.of("HOSTNAME", "ns1.example.bar.foo.tld", "CRDATE", "2000-06-01T00:03:00Z"), + DateTime.parse("2000-06-01T00:03:00Z")); + + // Create host ns1.example.foo.tld + assertCommandAndResponse( + "host_create_with_ips.xml", + ImmutableMap.of("HOSTNAME", "ns1.example.foo.tld"), + "host_create_response.xml", + ImmutableMap.of("HOSTNAME", "ns1.example.foo.tld", "CRDATE", "2000-06-01T00:04:00Z"), + DateTime.parse("2000-06-01T00:04:00Z")); + + // Create host ns1.example.tld + assertCommandAndResponse( + "host_create_with_ips.xml", + ImmutableMap.of("HOSTNAME", "ns1.example.tld"), + "host_create_response.xml", + ImmutableMap.of("HOSTNAME", "ns1.example.tld", "CRDATE", "2000-06-01T00:04:00Z"), + DateTime.parse("2000-06-01T00:04:00Z")); + + HostResource exampleBarFooTldHost = + loadByForeignKey( + HostResource.class, "ns1.example.bar.foo.tld", DateTime.parse("2000-06-01T00:05:00Z")); + DomainResource exampleBarFooTldDomain = + loadByForeignKey( + DomainResource.class, "example.bar.foo.tld", DateTime.parse("2000-06-01T00:05:00Z")); + assertAboutHosts() + .that(exampleBarFooTldHost) + .hasSuperordinateDomain(Key.create(exampleBarFooTldDomain)); + assertThat(exampleBarFooTldDomain.getSubordinateHosts()) + .containsExactly("ns1.example.bar.foo.tld"); + + HostResource exampleFooTldHost = + loadByForeignKey( + HostResource.class, "ns1.example.foo.tld", DateTime.parse("2000-06-01T00:05:00Z")); + DomainResource exampleFooTldDomain = + loadByForeignKey( + DomainResource.class, "example.foo.tld", DateTime.parse("2000-06-01T00:05:00Z")); + assertAboutHosts() + .that(exampleFooTldHost) + .hasSuperordinateDomain(Key.create(exampleFooTldDomain)); + assertThat(exampleFooTldDomain.getSubordinateHosts()) + .containsExactly("ns1.example.foo.tld"); + + HostResource exampleTldHost = + loadByForeignKey( + HostResource.class, "ns1.example.tld", DateTime.parse("2000-06-01T00:05:00Z")); + DomainResource exampleTldDomain = + loadByForeignKey( + DomainResource.class, "example.tld", DateTime.parse("2000-06-01T00:05:00Z")); + assertAboutHosts().that(exampleTldHost).hasSuperordinateDomain(Key.create(exampleTldDomain)); + assertThat(exampleTldDomain.getSubordinateHosts()) + .containsExactly("ns1.example.tld"); + + assertCommandAndResponse("logout.xml", "logout_response.xml"); + } } diff --git a/javatests/google/registry/flows/domain/DomainApplicationCreateFlowTest.java b/javatests/google/registry/flows/domain/DomainApplicationCreateFlowTest.java index 5f1aea210..d1f2ce6c7 100644 --- a/javatests/google/registry/flows/domain/DomainApplicationCreateFlowTest.java +++ b/javatests/google/registry/flows/domain/DomainApplicationCreateFlowTest.java @@ -22,6 +22,7 @@ import static google.registry.model.index.DomainApplicationIndex.loadActiveAppli import static google.registry.model.ofy.ObjectifyService.ofy; 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.deleteTld; import static google.registry.testing.DatastoreHelper.loadRegistrar; import static google.registry.testing.DatastoreHelper.newDomainApplication; @@ -70,6 +71,7 @@ import google.registry.flows.domain.DomainFlowUtils.CurrencyUnitMismatchExceptio import google.registry.flows.domain.DomainFlowUtils.CurrencyValueScaleException; import google.registry.flows.domain.DomainFlowUtils.DashesInThirdAndFourthException; import google.registry.flows.domain.DomainFlowUtils.DomainLabelTooLongException; +import google.registry.flows.domain.DomainFlowUtils.DomainNameExistsAsTldException; import google.registry.flows.domain.DomainFlowUtils.DomainReservedException; import google.registry.flows.domain.DomainFlowUtils.DuplicateContactForRoleException; import google.registry.flows.domain.DomainFlowUtils.EmptyDomainNamePartException; @@ -1768,6 +1770,18 @@ public class DomainApplicationCreateFlowTest doFailingDomainNameTest("tld", BadDomainNamePartsCountException.class); } + @Test + public void testFailure_domainNameExistsAsTld_lowercase() throws Exception { + createTlds("foo.tld", "tld"); + doFailingDomainNameTest("foo.tld", DomainNameExistsAsTldException.class); + } + + @Test + public void testFailure_domainNameExistsAsTld_uppercase() throws Exception { + createTlds("foo.tld", "tld"); + doFailingDomainNameTest("FOO.TLD", BadDomainNameCharacterException.class); + } + @Test public void testFailure_invalidPunycode() throws Exception { doFailingDomainNameTest("xn--abcdefg.tld", InvalidPunycodeException.class); diff --git a/javatests/google/registry/flows/domain/DomainCheckFlowTest.java b/javatests/google/registry/flows/domain/DomainCheckFlowTest.java index 1ccb9ff8e..e76c19104 100644 --- a/javatests/google/registry/flows/domain/DomainCheckFlowTest.java +++ b/javatests/google/registry/flows/domain/DomainCheckFlowTest.java @@ -16,6 +16,7 @@ package google.registry.flows.domain; import static google.registry.model.eppoutput.CheckData.DomainCheck.create; import static google.registry.testing.DatastoreHelper.createTld; +import static google.registry.testing.DatastoreHelper.createTlds; import static google.registry.testing.DatastoreHelper.loadRegistrar; import static google.registry.testing.DatastoreHelper.newDomainApplication; import static google.registry.testing.DatastoreHelper.persistActiveDomain; @@ -39,6 +40,7 @@ import google.registry.flows.domain.DomainFlowUtils.BadPeriodUnitException; import google.registry.flows.domain.DomainFlowUtils.CurrencyUnitMismatchException; import google.registry.flows.domain.DomainFlowUtils.DashesInThirdAndFourthException; import google.registry.flows.domain.DomainFlowUtils.DomainLabelTooLongException; +import google.registry.flows.domain.DomainFlowUtils.DomainNameExistsAsTldException; import google.registry.flows.domain.DomainFlowUtils.EmptyDomainNamePartException; import google.registry.flows.domain.DomainFlowUtils.FeeChecksDontSupportPhasesException; import google.registry.flows.domain.DomainFlowUtils.InvalidIdnDomainLabelException; @@ -357,6 +359,18 @@ public class DomainCheckFlowTest doFailingBadLabelTest("tld", BadDomainNamePartsCountException.class); } + @Test + public void testFailure_domainNameExistsAsTld_lowercase() throws Exception { + createTlds("foo.tld", "tld"); + doFailingBadLabelTest("foo.tld", DomainNameExistsAsTldException.class); + } + + @Test + public void testFailure_domainNameExistsAsTld_uppercase() throws Exception { + createTlds("foo.tld", "tld"); + doFailingBadLabelTest("FOO.TLD", BadDomainNameCharacterException.class); + } + @Test public void testFailure_invalidPunycode() throws Exception { doFailingBadLabelTest("xn--abcdefg.tld", InvalidPunycodeException.class); diff --git a/javatests/google/registry/flows/domain/DomainCreateFlowTest.java b/javatests/google/registry/flows/domain/DomainCreateFlowTest.java index 6f396a502..73ad6d1dd 100644 --- a/javatests/google/registry/flows/domain/DomainCreateFlowTest.java +++ b/javatests/google/registry/flows/domain/DomainCreateFlowTest.java @@ -26,6 +26,7 @@ import static google.registry.pricing.PricingEngineProxy.isDomainPremium; import static google.registry.testing.DatastoreHelper.assertBillingEvents; import static google.registry.testing.DatastoreHelper.assertPollMessagesForResource; import static google.registry.testing.DatastoreHelper.createTld; +import static google.registry.testing.DatastoreHelper.createTlds; import static google.registry.testing.DatastoreHelper.deleteTld; import static google.registry.testing.DatastoreHelper.getHistoryEntries; import static google.registry.testing.DatastoreHelper.loadRegistrar; @@ -72,6 +73,7 @@ import google.registry.flows.domain.DomainFlowUtils.CurrencyUnitMismatchExceptio import google.registry.flows.domain.DomainFlowUtils.CurrencyValueScaleException; import google.registry.flows.domain.DomainFlowUtils.DashesInThirdAndFourthException; import google.registry.flows.domain.DomainFlowUtils.DomainLabelTooLongException; +import google.registry.flows.domain.DomainFlowUtils.DomainNameExistsAsTldException; import google.registry.flows.domain.DomainFlowUtils.DomainNotAllowedForTldWithCreateRestrictionException; import google.registry.flows.domain.DomainFlowUtils.DomainReservedException; import google.registry.flows.domain.DomainFlowUtils.DuplicateContactForRoleException; @@ -372,6 +374,24 @@ public class DomainCreateFlowTest extends ResourceFlowTestCase + + + Command completed successfully + + + + %DOMAIN% + 2000-06-01T00:02:00.0Z + 2002-06-01T00:02:00.0Z + + + + ABC-12345 + server-trid + + + diff --git a/javatests/google/registry/flows/testdata/domain_create_wildcard.xml b/javatests/google/registry/flows/testdata/domain_create_wildcard.xml new file mode 100644 index 000000000..115b41be8 --- /dev/null +++ b/javatests/google/registry/flows/testdata/domain_create_wildcard.xml @@ -0,0 +1,18 @@ + + + + + %HOSTNAME% + 2 + jd1234 + sh8013 + sh8013 + + 2fooBAR + + + + ABC-12345 + + diff --git a/javatests/google/registry/flows/testdata/host_create_with_ips.xml b/javatests/google/registry/flows/testdata/host_create_with_ips.xml new file mode 100644 index 000000000..aaa273cbf --- /dev/null +++ b/javatests/google/registry/flows/testdata/host_create_with_ips.xml @@ -0,0 +1,13 @@ + + + + + %HOSTNAME% + 192.0.2.2 + 192.0.2.29 + 1080:0:0:0:8:800:200C:417A + + + ABC-12345 + +