diff --git a/docs/flows.md b/docs/flows.md index 64f338151..e8de6a1c2 100644 --- a/docs/flows.md +++ b/docs/flows.md @@ -1032,7 +1032,7 @@ allows creating a host name, and if necessary enqueues tasks to update DNS. * 2304 * Superordinate domain for this hostname is in pending delete. * 2306 - * Host names must be at least two levels below the public suffix. + * Host names must be at least two levels below the registry suffix. ## HostDeleteFlow @@ -1131,7 +1131,7 @@ are enqueued to update DNS accordingly. * Cannot rename an external host. * 2306 * Cannot add and remove the same value. - * Host names must be at least two levels below the public suffix. + * Host names must be at least two levels below the registry suffix. ## LoginFlow diff --git a/java/google/registry/flows/host/HostFlowUtils.java b/java/google/registry/flows/host/HostFlowUtils.java index 49b11df08..41da5bea9 100644 --- a/java/google/registry/flows/host/HostFlowUtils.java +++ b/java/google/registry/flows/host/HostFlowUtils.java @@ -57,32 +57,21 @@ public class HostFlowUtils { if (!name.equals(hostName.toString())) { throw new HostNameNotNormalizedException(hostName.toString()); } - // Checks whether a hostname is deep enough. Technically a host can be just one under a - // public suffix (e.g. example.com) but we require by policy that it has to be at least one - // part beyond that (e.g. ns1.example.com). The public suffix list includes all current - // ccTlds, so this check requires 4+ parts if it's a ccTld that doesn't delegate second - // level domains, such as .co.uk. But the list does not include new tlds, so in that case - // we just ensure 3+ parts. In the particular case where our own tld has a '.' in it, we know - // that there need to be 4 parts as well. - // TODO(b/63128999): Use better method (once implemented) that determines if it's a public - // suffix that domain names can be registered under. - if (hostName.isUnderPublicSuffix()) { - if (hostName.parent().isUnderPublicSuffix()) { - return hostName; - } - } else { - // We need to know how many parts the hostname has beyond the public suffix, but we don't - // know what the public suffix is. If the host is in bailiwick and we are hosting a - // multipart "tld" like .co.uk the public suffix might be 2 parts. Otherwise it's an - // unrecognized tld that's not on the public suffix list, so assume the tld alone is the - // public suffix. - Optional tldParsed = findTldForName(hostName); - int suffixSize = tldParsed.isPresent() ? tldParsed.get().parts().size() : 1; - if (hostName.parts().size() >= suffixSize + 2) { - return hostName; - } + // The effective TLD is, in order of preference, the registry suffix, if the TLD is a real TLD + // published in the public suffix list (https://publicsuffix.org/, note that a registry suffix + // is in the "ICANN DOMAINS" in that list); or a TLD managed by Nomulus (in-bailiwick), found + // by #findTldForName; or just the last part of a domain name. + InternetDomainName effectiveTld = + hostName.isUnderRegistrySuffix() + ? hostName.registrySuffix() + : findTldForName(hostName).orElse(InternetDomainName.from("invalid")); + // Checks whether a hostname is deep enough. Technically a host can be just one level beneath + // the effective TLD (e.g. example.com) but we require by policy that it has to be at least + // one part beyond that (e.g. ns1.example.com). + if (hostName.parts().size() < effectiveTld.parts().size() + 2) { + throw new HostNameTooShallowException(); } - throw new HostNameTooShallowException(); + return hostName; } catch (IllegalArgumentException e) { throw new InvalidHostNameException(); } @@ -119,8 +108,7 @@ public class HostFlowUtils { /** Ensure that the superordinate domain is sponsored by the provided clientId. */ static void verifySuperordinateDomainOwnership( - String clientId, - DomainResource superordinateDomain) throws EppException { + String clientId, DomainResource superordinateDomain) throws EppException { if (superordinateDomain != null && !clientId.equals(superordinateDomain.getCurrentSponsorClientId())) { throw new HostDomainNotOwnedException(); @@ -135,8 +123,8 @@ public class HostFlowUtils { } /** Ensure that the superordinate domain is not in pending delete. */ - static void verifySuperordinateDomainNotInPendingDelete( - DomainResource superordinateDomain) throws EppException { + static void verifySuperordinateDomainNotInPendingDelete(DomainResource superordinateDomain) + throws EppException { if ((superordinateDomain != null) && superordinateDomain.getStatusValues().contains(StatusValue.PENDING_DELETE)) { throw new SuperordinateDomainInPendingDeleteException(); @@ -158,10 +146,10 @@ public class HostFlowUtils { } } - /** Host names must be at least two levels below the public suffix. */ + /** Host names must be at least two levels below the registry suffix. */ static class HostNameTooShallowException extends ParameterValuePolicyErrorException { public HostNameTooShallowException() { - super("Host names must be at least two levels below the public suffix"); + super("Host names must be at least two levels below the registry suffix"); } } diff --git a/java/google/registry/repositories.bzl b/java/google/registry/repositories.bzl index 2f45acb77..e996043ce 100644 --- a/java/google/registry/repositories.bzl +++ b/java/google/registry/repositories.bzl @@ -1006,10 +1006,10 @@ def com_google_gdata_core(): def com_google_guava(): java_import_external( name = "com_google_guava", - jar_sha256 = "7baa80df284117e5b945b19b98d367a85ea7b7801bd358ff657946c3bd1b6596", + jar_sha256 = "4a87d5b1ca996e5e46a99594cf3566ae16885d0cf00b381b5e9f816a0e0125b3", jar_urls = [ - "http://repo1.maven.org/maven2/com/google/guava/guava/23.0/guava-23.0.jar", - "http://domain-registry-maven.storage.googleapis.com/repo1.maven.org/maven2/com/google/guava/guava/23.0/guava-23.0.jar", + "http://repo1.maven.org/maven2/com/google/guava/guava/23.3-jre/guava-23.3-jre.jar", + "http://maven.ibiblio.org/maven2/com/google/guava/guava/23.3-jre/guava-23.3-jre.jar", ], licenses = ["notice"], # The Apache Software License, Version 2.0 exports = [ @@ -1021,10 +1021,10 @@ def com_google_guava(): def com_google_guava_testlib(): java_import_external( name = "com_google_guava_testlib", - jar_sha256 = "7e328d0f89a5ea103de4f9b689130eb555ff277e83bf86294bc14c2c40a59a80", + jar_sha256 = "377c60720828a655ffd0f64d8b64643962b2957635323ddc9c5223827f6e5482", jar_urls = [ - "http://repo1.maven.org/maven2/com/google/guava/guava-testlib/23.0/guava-testlib-23.0.jar", - "http://domain-registry-maven.storage.googleapis.com/repo1.maven.org/maven2/com/google/guava/guava-testlib/23.0/guava-testlib-23.0.jar", + "http://maven.ibiblio.org/maven2/com/google/guava/guava-testlib/23.3-jre/guava-testlib-23.3-jre.jar", + "http://repo1.maven.org/maven2/com/google/guava/guava-testlib/23.3-jre/guava-testlib-23.3-jre.jar", ], licenses = ["notice"], # The Apache Software License, Version 2.0 testonly_ = True, diff --git a/javatests/google/registry/flows/host/HostFlowUtilsTest.java b/javatests/google/registry/flows/host/HostFlowUtilsTest.java index 8bcabfcf7..cda3194c7 100644 --- a/javatests/google/registry/flows/host/HostFlowUtilsTest.java +++ b/javatests/google/registry/flows/host/HostFlowUtilsTest.java @@ -16,6 +16,7 @@ package google.registry.flows.host; import static com.google.common.truth.Truth.assertThat; import static google.registry.flows.host.HostFlowUtils.validateHostName; +import static google.registry.testing.JUnitBackports.assertThrows; import com.google.common.base.Strings; import google.registry.flows.host.HostFlowUtils.HostNameNotLowerCaseException; @@ -25,8 +26,6 @@ import google.registry.flows.host.HostFlowUtils.HostNameTooLongException; import google.registry.flows.host.HostFlowUtils.HostNameTooShallowException; import google.registry.flows.host.HostFlowUtils.InvalidHostNameException; import google.registry.testing.AppEngineRule; -import google.registry.testing.ExceptionRule; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -36,8 +35,6 @@ import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public class HostFlowUtilsTest { - @Rule public final ExceptionRule thrown = new ExceptionRule(); - @Rule public final AppEngineRule appEngine = AppEngineRule.builder().withDatastore().build(); @Test @@ -46,47 +43,53 @@ public class HostFlowUtilsTest { } @Test - @Ignore - // TODO(b/63128999): Fix handling of public suffix lists so that hostnames that aren't on - // effective TLDs validate. - public void test_validExternalHostNameOnPublicSuffixList_validates() throws Exception { + public void test_validExternalHostNameOnRegistrySuffixList_validates() throws Exception { assertThat(validateHostName("host.blogspot.com").toString()).isEqualTo("host.blogspot.com"); } + @Test + public void test_validExternalHostNameOnRegistrySuffixList_multipartTLD_validates() + throws Exception { + assertThat(validateHostName("ns1.host.co.uk").toString()).isEqualTo("ns1.host.co.uk"); + } + + @Test + public void test_validExternalHostNameOnRegistrySuffixList_multipartTLD_tooShallow() + throws Exception { + assertThrows( + HostNameTooShallowException.class, () -> validateHostName("host.co.uk").toString()); + } @Test public void test_validateHostName_hostNameTooLong() throws Exception { - thrown.expect(HostNameTooLongException.class); - validateHostName(Strings.repeat("na", 200) + ".wat.man"); + assertThrows( + HostNameTooLongException.class, + () -> validateHostName(Strings.repeat("na", 200) + ".wat.man")); } @Test public void test_validateHostName_hostNameNotLowerCase() throws Exception { - thrown.expect(HostNameNotLowerCaseException.class); - validateHostName("NA.CAPS.TLD"); + assertThrows(HostNameNotLowerCaseException.class, () -> validateHostName("NA.CAPS.TLD")); } @Test public void test_validateHostName_hostNameNotPunyCoded() throws Exception { - thrown.expect(HostNameNotPunyCodedException.class); - validateHostName("motörhead.death.metal"); + assertThrows( + HostNameNotPunyCodedException.class, () -> validateHostName("motörhead.death.metal")); } @Test public void test_validateHostName_hostNameNotNormalized() throws Exception { - thrown.expect(HostNameNotNormalizedException.class); - validateHostName("root.node.yeah."); + assertThrows(HostNameNotNormalizedException.class, () -> validateHostName("root.node.yeah.")); } @Test public void test_validateHostName_hostNameHasLeadingHyphen() throws Exception { - thrown.expect(InvalidHostNameException.class); - validateHostName("-giga.mega.tld"); + assertThrows(InvalidHostNameException.class, () -> validateHostName("-giga.mega.tld")); } @Test public void test_validateHostName_hostNameTooShallow() throws Exception { - thrown.expect(HostNameTooShallowException.class); - validateHostName("domain.tld"); + assertThrows(HostNameTooShallowException.class, () -> validateHostName("domain.tld")); } }