Tighten up restrictions on domain/host naming on EPP resources

All domain/host names should be stored in their canonical forms (puny-
coded and lower-cased). This validation is already in the flows, but
this adds protection against bad data from other sources, e.g. admin
consoles or RDE imports.

This also removes an old work-around that temporarily suspended this
validation for superusers, because we used to have non-canonicalized
data in the system. The non-canonicalized data has since all been
cleaned up, so this work-around is no longer necessary.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=146799558
This commit is contained in:
mcilwain 2017-02-07 10:06:38 -08:00 committed by Ben McIlwain
parent ec55aa5361
commit d2bc569b4b
11 changed files with 49 additions and 31 deletions

View file

@ -123,11 +123,7 @@ public final class HostUpdateFlow implements TransactionalFlow {
Change change = command.getInnerChange(); Change change = command.getInnerChange();
String suppliedNewHostName = change.getFullyQualifiedHostName(); String suppliedNewHostName = change.getFullyQualifiedHostName();
DateTime now = ofy().getTransactionTime(); DateTime now = ofy().getTransactionTime();
// Validation is disabled for superusers to allow renaming of existing invalid hostnames. validateHostName(targetId);
// TODO(b/32328995): Remove superuser override once all bad data in prod has been fixed.
if (!isSuperuser) {
validateHostName(targetId);
}
HostResource existingHost = loadAndVerifyExistence(HostResource.class, targetId, now); HostResource existingHost = loadAndVerifyExistence(HostResource.class, targetId, now);
boolean isHostRename = suppliedNewHostName != null; boolean isHostRename = suppliedNewHostName != null;
String oldHostName = targetId; String oldHostName = targetId;

View file

@ -26,6 +26,7 @@ import static google.registry.util.CollectionUtils.nullToEmpty;
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy; import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import static google.registry.util.CollectionUtils.nullToEmptyImmutableSortedCopy; import static google.registry.util.CollectionUtils.nullToEmptyImmutableSortedCopy;
import static google.registry.util.CollectionUtils.union; import static google.registry.util.CollectionUtils.union;
import static google.registry.util.DomainNameUtils.canonicalizeDomainName;
import static google.registry.util.DomainNameUtils.getTldFromDomainName; import static google.registry.util.DomainNameUtils.getTldFromDomainName;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull; import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
@ -209,6 +210,9 @@ public abstract class DomainBase extends EppResource {
} }
public B setFullyQualifiedDomainName(String fullyQualifiedDomainName) { public B setFullyQualifiedDomainName(String fullyQualifiedDomainName) {
checkArgument(
fullyQualifiedDomainName.equals(canonicalizeDomainName(fullyQualifiedDomainName)),
"Domain name must be in puny-coded, lower-case form");
getInstance().fullyQualifiedDomainName = fullyQualifiedDomainName; getInstance().fullyQualifiedDomainName = fullyQualifiedDomainName;
return thisCastToDerived(); return thisCastToDerived();
} }

View file

@ -14,12 +14,14 @@
package google.registry.model.host; package google.registry.model.host;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.Sets.difference; import static com.google.common.collect.Sets.difference;
import static com.google.common.collect.Sets.union; import static com.google.common.collect.Sets.union;
import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.model.ofy.Ofy.RECOMMENDED_MEMCACHE_EXPIRATION; import static google.registry.model.ofy.Ofy.RECOMMENDED_MEMCACHE_EXPIRATION;
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy; import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import static google.registry.util.DateTimeUtils.START_OF_TIME; import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static google.registry.util.DomainNameUtils.canonicalizeDomainName;
import com.google.common.base.Optional; import com.google.common.base.Optional;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
@ -164,6 +166,9 @@ public class HostResource extends EppResource implements ForeignKeyedEppResource
} }
public Builder setFullyQualifiedHostName(String fullyQualifiedHostName) { public Builder setFullyQualifiedHostName(String fullyQualifiedHostName) {
checkArgument(
fullyQualifiedHostName.equals(canonicalizeDomainName(fullyQualifiedHostName)),
"Host name must be in puny-coded, lower-case form");
getInstance().fullyQualifiedHostName = fullyQualifiedHostName; getInstance().fullyQualifiedHostName = fullyQualifiedHostName;
return this; return this;
} }

View file

@ -360,19 +360,6 @@ public class HostUpdateFlowTest extends ResourceFlowTestCase<HostUpdateFlow, Hos
.hasStatusValue(StatusValue.SERVER_UPDATE_PROHIBITED); .hasStatusValue(StatusValue.SERVER_UPDATE_PROHIBITED);
} }
@Test
public void testSuccess_superuserCanSpecifyInvalidExistingHostName() throws Exception {
persistActiveHost("NS1.çiça199.tld.");
clock.advanceOneMilli();
setEppHostUpdateInput("NS1.çiça199.tld.", "ns1.xn--ia199-xrab.tld", null, null);
runFlowAssertResponse(
CommitMode.LIVE, UserPrivileges.SUPERUSER, readFile("host_update_response.xml"));
clock.advanceOneMilli();
assertThat(loadByForeignKey(HostResource.class, "NS1.çiça199.tld.", clock.nowUtc())).isNull();
assertThat(loadByForeignKey(HostResource.class, "ns1.xn--ia199-xrab.tld", clock.nowUtc()))
.isNotNull();
}
@Test @Test
public void testSuccess_subordToSubord_lastTransferTimeFromPreviousSuperordinateWinsOut() public void testSuccess_subordToSubord_lastTransferTimeFromPreviousSuperordinateWinsOut()
throws Exception { throws Exception {
@ -720,7 +707,6 @@ public class HostUpdateFlowTest extends ResourceFlowTestCase<HostUpdateFlow, Hos
@Test @Test
public void testFailure_referToNonLowerCaseHostname() throws Exception { public void testFailure_referToNonLowerCaseHostname() throws Exception {
persistActiveHost("ns1.EXAMPLE.tld");
setEppHostUpdateInput("ns1.EXAMPLE.tld", "ns2.example.tld", null, null); setEppHostUpdateInput("ns1.EXAMPLE.tld", "ns2.example.tld", null, null);
thrown.expect(HostNameNotLowerCaseException.class); thrown.expect(HostNameNotLowerCaseException.class);
runFlow(); runFlow();
@ -736,7 +722,6 @@ public class HostUpdateFlowTest extends ResourceFlowTestCase<HostUpdateFlow, Hos
@Test @Test
public void testFailure_referToNonPunyCodedHostname() throws Exception { public void testFailure_referToNonPunyCodedHostname() throws Exception {
persistActiveHost("ns1.çauçalito.tld");
setEppHostUpdateInput("ns1.çauçalito.tld", "ns1.sausalito.tld", null, null); setEppHostUpdateInput("ns1.çauçalito.tld", "ns1.sausalito.tld", null, null);
thrown.expect(HostNameNotPunyCodedException.class, "expected ns1.xn--aualito-txac.tld"); thrown.expect(HostNameNotPunyCodedException.class, "expected ns1.xn--aualito-txac.tld");
runFlow(); runFlow();

View file

@ -422,4 +422,18 @@ public class DomainResourceTest extends EntityTestCase {
public void testToHydratedString_notCircular() { public void testToHydratedString_notCircular() {
domain.toHydratedString(); // If there are circular references, this will overflow the stack. domain.toHydratedString(); // If there are circular references, this will overflow the stack.
} }
@Test
public void testFailure_uppercaseDomainName() {
thrown.expect(
IllegalArgumentException.class, "Domain name must be in puny-coded, lower-case form");
domain.asBuilder().setFullyQualifiedDomainName("AAA.BBB");
}
@Test
public void testFailure_utf8DomainName() {
thrown.expect(
IllegalArgumentException.class, "Domain name must be in puny-coded, lower-case form");
domain.asBuilder().setFullyQualifiedDomainName("みんな.みんな");
}
} }

View file

@ -264,4 +264,18 @@ public class HostResourceTest extends EntityTestCase {
// If there are circular references, this will overflow the stack. // If there are circular references, this will overflow the stack.
hostResource.toHydratedString(); hostResource.toHydratedString();
} }
@Test
public void testFailure_uppercaseHostName() {
thrown.expect(
IllegalArgumentException.class, "Host name must be in puny-coded, lower-case form");
hostResource.asBuilder().setFullyQualifiedHostName("AAA.BBB.CCC");
}
@Test
public void testFailure_utf8HostName() {
thrown.expect(
IllegalArgumentException.class, "Host name must be in puny-coded, lower-case form");
hostResource.asBuilder().setFullyQualifiedHostName("みんな.みんな.みんな");
}
} }

View file

@ -71,12 +71,12 @@ public class DomainWhoisResponseTest {
createTld("tld"); createTld("tld");
hostResource1 = persistResource(new HostResource.Builder() hostResource1 = persistResource(new HostResource.Builder()
.setFullyQualifiedHostName("NS01.EXAMPLEREGISTRAR.tld") .setFullyQualifiedHostName("ns01.exampleregistrar.tld")
.setRepoId("1-TLD") .setRepoId("1-TLD")
.build()); .build());
hostResource2 = persistResource(new HostResource.Builder() hostResource2 = persistResource(new HostResource.Builder()
.setFullyQualifiedHostName("NS02.EXAMPLEREGISTRAR.tld") .setFullyQualifiedHostName("ns02.exampleregistrar.tld")
.setRepoId("2-TLD") .setRepoId("2-TLD")
.build()); .build());
@ -212,7 +212,7 @@ public class DomainWhoisResponseTest {
Key<ContactResource> techResourceKey = Key.create(techContact); Key<ContactResource> techResourceKey = Key.create(techContact);
domainResource = persistResource(new DomainResource.Builder() domainResource = persistResource(new DomainResource.Builder()
.setFullyQualifiedDomainName("EXAMPLE.tld") .setFullyQualifiedDomainName("example.tld")
.setRepoId("3-TLD") .setRepoId("3-TLD")
.setLastEppUpdateTime(DateTime.parse("2009-05-29T20:13:00Z")) .setLastEppUpdateTime(DateTime.parse("2009-05-29T20:13:00Z"))
.setCreationTimeForTest(DateTime.parse("2000-10-08T00:45:00Z")) .setCreationTimeForTest(DateTime.parse("2000-10-08T00:45:00Z"))

View file

@ -59,7 +59,7 @@ public class NameserverWhoisResponseTest {
createTld("tld"); createTld("tld");
hostResource1 = new HostResource.Builder() hostResource1 = new HostResource.Builder()
.setFullyQualifiedHostName("NS1.EXAMPLE.tld") .setFullyQualifiedHostName("ns1.example.tld")
.setCurrentSponsorClientId("example") .setCurrentSponsorClientId("example")
.setInetAddresses(ImmutableSet.of( .setInetAddresses(ImmutableSet.of(
InetAddresses.forString("192.0.2.123"), InetAddresses.forString("192.0.2.123"),
@ -68,7 +68,7 @@ public class NameserverWhoisResponseTest {
.build(); .build();
hostResource2 = new HostResource.Builder() hostResource2 = new HostResource.Builder()
.setFullyQualifiedHostName("NS2.EXAMPLE.tld") .setFullyQualifiedHostName("ns2.example.tld")
.setCurrentSponsorClientId("example") .setCurrentSponsorClientId("example")
.setInetAddresses(ImmutableSet.of( .setInetAddresses(ImmutableSet.of(
InetAddresses.forString("192.0.2.123"), InetAddresses.forString("192.0.2.123"),

View file

@ -1,4 +1,4 @@
Domain Name: EXAMPLE.tld Domain Name: example.tld
Domain ID: 3-TLD Domain ID: 3-TLD
WHOIS Server: whois.nic.fakewhois.example WHOIS Server: whois.nic.fakewhois.example
Referral URL: http://www.referral.example/path Referral URL: http://www.referral.example/path
@ -52,8 +52,8 @@ Tech Phone Ext: 1234
Tech Fax: +1.5555551213 Tech Fax: +1.5555551213
Tech Fax Ext: 93 Tech Fax Ext: 93
Tech Email: EMAIL@EXAMPLE.tld Tech Email: EMAIL@EXAMPLE.tld
Name Server: NS01.EXAMPLEREGISTRAR.tld Name Server: ns01.exampleregistrar.tld
Name Server: NS02.EXAMPLEREGISTRAR.tld Name Server: ns02.exampleregistrar.tld
DNSSEC: signedDelegation DNSSEC: signedDelegation
>>> Last update of WHOIS database: 2009-05-29T20:15:00Z <<< >>> Last update of WHOIS database: 2009-05-29T20:15:00Z <<<

View file

@ -1,11 +1,11 @@
Server Name: NS1.EXAMPLE.tld Server Name: ns1.example.tld
IP Address: 192.0.2.123 IP Address: 192.0.2.123
IP Address: 2001:db8::1 IP Address: 2001:db8::1
Registrar: Example Registrar, Inc. Registrar: Example Registrar, Inc.
WHOIS Server: whois.nic.fakewhois.example WHOIS Server: whois.nic.fakewhois.example
Referral URL: http://www.referral.example/path Referral URL: http://www.referral.example/path
Server Name: NS2.EXAMPLE.tld Server Name: ns2.example.tld
IP Address: 192.0.2.123 IP Address: 192.0.2.123
IP Address: 2001:db8::1 IP Address: 2001:db8::1
Registrar: Example Registrar, Inc. Registrar: Example Registrar, Inc.

View file

@ -1,4 +1,4 @@
Server Name: NS1.EXAMPLE.tld Server Name: ns1.example.tld
IP Address: 192.0.2.123 IP Address: 192.0.2.123
IP Address: 2001:db8::1 IP Address: 2001:db8::1
Registrar: Example Registrar, Inc. Registrar: Example Registrar, Inc.