diff --git a/core/src/main/java/google/registry/model/domain/token/AllocationToken.java b/core/src/main/java/google/registry/model/domain/token/AllocationToken.java index c315fb23e..b4d4be37e 100644 --- a/core/src/main/java/google/registry/model/domain/token/AllocationToken.java +++ b/core/src/main/java/google/registry/model/domain/token/AllocationToken.java @@ -35,6 +35,8 @@ import com.googlecode.objectify.annotation.Entity; import com.googlecode.objectify.annotation.Id; import com.googlecode.objectify.annotation.Index; import com.googlecode.objectify.annotation.Mapify; +import google.registry.flows.EppException; +import google.registry.flows.domain.DomainFlowUtils; import google.registry.model.BackupGroupRoot; import google.registry.model.Buildable; import google.registry.model.CreateAutoTimestamp; @@ -201,6 +203,13 @@ public class AllocationToken extends BackupGroupRoot implements Buildable { getInstance().redemptionHistoryEntry == null || TokenType.SINGLE_USE.equals(getInstance().tokenType), "Redemption history entry can only be specified for SINGLE_USE tokens"); + if (getInstance().domainName != null) { + try { + DomainFlowUtils.validateDomainName(getInstance().domainName); + } catch (EppException e) { + throw new IllegalArgumentException("Invalid domain name: " + getInstance().domainName, e); + } + } return super.build(); } diff --git a/core/src/test/java/google/registry/flows/domain/DomainFlowUtilsTest.java b/core/src/test/java/google/registry/flows/domain/DomainFlowUtilsTest.java new file mode 100644 index 000000000..de2896a7f --- /dev/null +++ b/core/src/test/java/google/registry/flows/domain/DomainFlowUtilsTest.java @@ -0,0 +1,154 @@ +// Copyright 2017 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.domain; + +import static com.google.common.truth.Truth.assertThat; +import static google.registry.testing.DatastoreHelper.createTld; +import static google.registry.testing.DatastoreHelper.persistResource; +import static google.registry.testing.EppExceptionSubject.assertAboutEppExceptions; +import static google.registry.testing.JUnitBackports.assertThrows; + +import google.registry.flows.EppException; +import google.registry.flows.ResourceFlowTestCase; +import google.registry.flows.domain.DomainFlowUtils.BadDomainNameCharacterException; +import google.registry.flows.domain.DomainFlowUtils.BadDomainNamePartsCountException; +import google.registry.flows.domain.DomainFlowUtils.DashesInThirdAndFourthException; +import google.registry.flows.domain.DomainFlowUtils.DomainLabelTooLongException; +import google.registry.flows.domain.DomainFlowUtils.EmptyDomainNamePartException; +import google.registry.flows.domain.DomainFlowUtils.InvalidPunycodeException; +import google.registry.flows.domain.DomainFlowUtils.LeadingDashException; +import google.registry.flows.domain.DomainFlowUtils.TldDoesNotExistException; +import google.registry.flows.domain.DomainFlowUtils.TrailingDashException; +import google.registry.model.domain.DomainBase; +import google.registry.testing.AppEngineRule; +import org.junit.Before; +import org.junit.Test; + +public class DomainFlowUtilsTest extends ResourceFlowTestCase { + + @Before + public void setup() { + setEppInput("domain_info.xml"); + createTld("tld"); + persistResource(AppEngineRule.makeRegistrar1().asBuilder().build()); + } + + @Test + public void testValidateDomainNameAcceptsValidName() throws EppException { + assertThat(DomainFlowUtils.validateDomainName("example.tld")).isNotNull(); + } + + @Test + public void testValidateDomainName_IllegalCharacters() { + BadDomainNameCharacterException thrown = + assertThrows( + BadDomainNameCharacterException.class, + () -> DomainFlowUtils.validateDomainName("$.foo")); + assertThat(thrown) + .hasMessageThat() + .isEqualTo("Domain names can only contain a-z, 0-9, '.' and '-'"); + assertAboutEppExceptions().that(thrown).marshalsToXml(); + } + + @Test + public void testValidateDomainName_DomainNameWithEmptyParts() { + EmptyDomainNamePartException thrown = + assertThrows( + EmptyDomainNamePartException.class, + () -> DomainFlowUtils.validateDomainName("example.")); + assertThat(thrown).hasMessageThat().isEqualTo("No part of a domain name can be empty"); + assertAboutEppExceptions().that(thrown).marshalsToXml(); + } + + @Test + public void testValidateDomainName_DomainNameWithLessThanTwoParts() { + BadDomainNamePartsCountException thrown = + assertThrows( + BadDomainNamePartsCountException.class, + () -> DomainFlowUtils.validateDomainName("example")); + assertThat(thrown) + .hasMessageThat() + .isEqualTo("Domain name must have exactly one part above the TLD"); + assertAboutEppExceptions().that(thrown).marshalsToXml(); + } + + @Test + public void testValidateDomainName_invalidTLD() { + TldDoesNotExistException thrown = + assertThrows( + TldDoesNotExistException.class, + () -> DomainFlowUtils.validateDomainName("example.nosuchtld")); + assertThat(thrown) + .hasMessageThat() + .isEqualTo("Domain name is under tld nosuchtld which doesn't exist"); + assertAboutEppExceptions().that(thrown).marshalsToXml(); + } + + @Test + public void testValidateDomainName_DomainNameIsTooLong() { + DomainLabelTooLongException thrown = + assertThrows( + DomainLabelTooLongException.class, + () -> + DomainFlowUtils.validateDomainName( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.foo")); + assertThat(thrown) + .hasMessageThat() + .isEqualTo("Domain labels cannot be longer than 63 characters"); + assertAboutEppExceptions().that(thrown).marshalsToXml(); + } + + @Test + public void testValidateDomainName_leadingDash() { + LeadingDashException thrown = + assertThrows( + LeadingDashException.class, () -> DomainFlowUtils.validateDomainName("-example.foo")); + assertThat(thrown).hasMessageThat().isEqualTo("Domain labels cannot begin with a dash"); + assertAboutEppExceptions().that(thrown).marshalsToXml(); + } + + @Test + public void testValidateDomainName_trailingDash() { + TrailingDashException thrown = + assertThrows( + TrailingDashException.class, () -> DomainFlowUtils.validateDomainName("example-.foo")); + assertThat(thrown).hasMessageThat().isEqualTo("Domain labels cannot end with a dash"); + assertAboutEppExceptions().that(thrown).marshalsToXml(); + } + + @Test + public void testValidateDomainName_invalidIDN() { + InvalidPunycodeException thrown = + assertThrows( + InvalidPunycodeException.class, + () -> DomainFlowUtils.validateDomainName("xn--abcd.foo")); + assertThat(thrown) + .hasMessageThat() + .isEqualTo("Domain name starts with xn-- but is not a valid IDN"); + assertAboutEppExceptions().that(thrown).marshalsToXml(); + } + + @Test + public void testValidateDomainName_containsInvalidDashes() { + DashesInThirdAndFourthException thrown = + assertThrows( + DashesInThirdAndFourthException.class, + () -> DomainFlowUtils.validateDomainName("ab--cd.foo")); + assertThat(thrown) + .hasMessageThat() + .isEqualTo("Non-IDN domain names cannot contain dashes in the third or fourth position"); + assertAboutEppExceptions().that(thrown).marshalsToXml(); + } +} diff --git a/core/src/test/java/google/registry/model/domain/token/AllocationTokenTest.java b/core/src/test/java/google/registry/model/domain/token/AllocationTokenTest.java index da209dc54..c58ee677d 100644 --- a/core/src/test/java/google/registry/model/domain/token/AllocationTokenTest.java +++ b/core/src/test/java/google/registry/model/domain/token/AllocationTokenTest.java @@ -23,6 +23,7 @@ import static google.registry.model.domain.token.AllocationToken.TokenStatus.VAL import static google.registry.model.domain.token.AllocationToken.TokenType.SINGLE_USE; import static google.registry.model.domain.token.AllocationToken.TokenType.UNLIMITED_USE; import static google.registry.model.ofy.ObjectifyService.ofy; +import static google.registry.testing.DatastoreHelper.createTld; import static google.registry.testing.DatastoreHelper.persistResource; import static google.registry.testing.JUnitBackports.assertThrows; import static google.registry.util.DateTimeUtils.START_OF_TIME; @@ -36,11 +37,17 @@ import google.registry.model.domain.token.AllocationToken.TokenStatus; import google.registry.model.domain.token.AllocationToken.TokenType; import google.registry.model.reporting.HistoryEntry; import org.joda.time.DateTime; +import org.junit.Before; import org.junit.Test; /** Unit tests for {@link AllocationToken}. */ public class AllocationTokenTest extends EntityTestCase { + @Before + public void setup() { + createTld("foo"); + } + @Test public void testPersistence() { AllocationToken unlimitedUseToken = @@ -66,7 +73,7 @@ public class AllocationTokenTest extends EntityTestCase { new AllocationToken.Builder() .setToken("abc123") .setRedemptionHistoryEntry(Key.create(HistoryEntry.class, 1L)) - .setDomainName("foo.example") + .setDomainName("example.foo") .setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z")) .setTokenType(SINGLE_USE) .build()); @@ -81,7 +88,7 @@ public class AllocationTokenTest extends EntityTestCase { .setToken("abc123") .setTokenType(SINGLE_USE) .setRedemptionHistoryEntry(Key.create(HistoryEntry.class, 1L)) - .setDomainName("blahdomain.fake") + .setDomainName("blahdomain.foo") .setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z")) .build()), "token", @@ -129,13 +136,49 @@ public class AllocationTokenTest extends EntityTestCase { assertThat(thrown).hasMessageThat().isEqualTo("Token type can only be set once"); } + @Test + public void testBuild_DomainNameWithLessThanTwoParts() { + IllegalArgumentException thrown = + assertThrows( + IllegalArgumentException.class, + () -> + new AllocationToken.Builder() + .setDomainName("example") + .setTokenType(SINGLE_USE) + .setToken("barfoo") + .build()); + assertThat(thrown) + .hasCauseThat() + .hasMessageThat() + .isEqualTo("Domain name must have exactly one part above the TLD"); + assertThat(thrown).hasMessageThat().isEqualTo("Invalid domain name: example"); + } + + @Test + public void testBuild_invalidTLD() { + IllegalArgumentException thrown = + assertThrows( + IllegalArgumentException.class, + () -> + new AllocationToken.Builder() + .setDomainName("example.nosuchtld") + .setTokenType(SINGLE_USE) + .setToken("barfoo") + .build()); + assertThat(thrown) + .hasCauseThat() + .hasMessageThat() + .isEqualTo("Domain name is under tld nosuchtld which doesn't exist"); + assertThat(thrown).hasMessageThat().isEqualTo("Invalid domain name: example.nosuchtld"); + } + @Test public void testBuild_domainNameOnlyOnSingleUse() { AllocationToken.Builder builder = new AllocationToken.Builder() .setToken("foobar") .setTokenType(TokenType.UNLIMITED_USE) - .setDomainName("foo.example"); + .setDomainName("example.foo"); IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, builder::build); assertThat(thrown) .hasMessageThat() diff --git a/core/src/test/java/google/registry/tools/DeleteAllocationTokensCommandTest.java b/core/src/test/java/google/registry/tools/DeleteAllocationTokensCommandTest.java index e06b1f4a6..53b232fc1 100644 --- a/core/src/test/java/google/registry/tools/DeleteAllocationTokensCommandTest.java +++ b/core/src/test/java/google/registry/tools/DeleteAllocationTokensCommandTest.java @@ -17,6 +17,7 @@ package google.registry.tools; import static com.google.common.truth.Truth.assertThat; import static google.registry.model.domain.token.AllocationToken.TokenType.SINGLE_USE; import static google.registry.model.ofy.ObjectifyService.ofy; +import static google.registry.testing.DatastoreHelper.createTlds; import static google.registry.testing.DatastoreHelper.persistResource; import static google.registry.testing.JUnitBackports.assertThrows; @@ -42,6 +43,7 @@ public class DeleteAllocationTokensCommandTest @Before public void init() { + createTlds("foo", "bar"); preRed1 = persistToken("prefix12345AA", null, true); preRed2 = persistToken("prefixgh8907a", null, true); preNot1 = persistToken("prefix2978204", null, false); diff --git a/core/src/test/java/google/registry/tools/GenerateAllocationTokensCommandTest.java b/core/src/test/java/google/registry/tools/GenerateAllocationTokensCommandTest.java index 0a2cd64a3..d99cbfd09 100644 --- a/core/src/test/java/google/registry/tools/GenerateAllocationTokensCommandTest.java +++ b/core/src/test/java/google/registry/tools/GenerateAllocationTokensCommandTest.java @@ -18,6 +18,7 @@ import static com.google.common.truth.Truth.assertThat; import static google.registry.model.domain.token.AllocationToken.TokenType.SINGLE_USE; import static google.registry.model.domain.token.AllocationToken.TokenType.UNLIMITED_USE; import static google.registry.model.ofy.ObjectifyService.ofy; +import static google.registry.testing.DatastoreHelper.createTld; import static google.registry.testing.DatastoreHelper.persistResource; import static google.registry.testing.JUnitBackports.assertThrows; import static google.registry.util.DateTimeUtils.START_OF_TIME; @@ -134,6 +135,7 @@ public class GenerateAllocationTokensCommandTest @Test public void testSuccess_domainNames() throws Exception { + createTld("tld"); File domainNamesFile = tmpDir.newFile("domain_names.txt"); Files.asCharSink(domainNamesFile, UTF_8).write("foo1.tld\nboo2.tld\nbaz9.tld\n"); runCommand("--domain_names_file", domainNamesFile.getPath()); diff --git a/core/src/test/java/google/registry/tools/GetAllocationTokenCommandTest.java b/core/src/test/java/google/registry/tools/GetAllocationTokenCommandTest.java index 617ff7115..202c2aefc 100644 --- a/core/src/test/java/google/registry/tools/GetAllocationTokenCommandTest.java +++ b/core/src/test/java/google/registry/tools/GetAllocationTokenCommandTest.java @@ -17,6 +17,7 @@ package google.registry.tools; import static google.registry.model.domain.token.AllocationToken.TokenType.SINGLE_USE; import static google.registry.testing.DatastoreHelper.createHistoryEntryForEppResource; import static google.registry.testing.DatastoreHelper.createTld; +import static google.registry.testing.DatastoreHelper.createTlds; import static google.registry.testing.DatastoreHelper.persistActiveDomain; import static google.registry.testing.DatastoreHelper.persistResource; import static google.registry.testing.DatastoreHelper.persistSimpleResources; @@ -35,6 +36,7 @@ public class GetAllocationTokenCommandTest extends CommandTestCase tokens = persistSimpleResources( ImmutableList.of( @@ -90,6 +93,7 @@ public class GetAllocationTokenCommandTest extends CommandTestCase