Canonicalize domain/host names in async DS->SQL replay (#1350)

This commit is contained in:
gbrodman 2021-11-01 12:08:20 -04:00 committed by GitHub
parent 1b4b217588
commit 30c23efba9
8 changed files with 160 additions and 2 deletions

View file

@ -24,6 +24,7 @@ import google.registry.model.host.HostResource;
import google.registry.model.replay.DatastoreAndSqlEntity; import google.registry.model.replay.DatastoreAndSqlEntity;
import google.registry.persistence.VKey; import google.registry.persistence.VKey;
import google.registry.persistence.WithStringVKey; import google.registry.persistence.WithStringVKey;
import google.registry.util.DomainNameUtils;
import java.util.Set; import java.util.Set;
import javax.persistence.Access; import javax.persistence.Access;
import javax.persistence.AccessType; import javax.persistence.AccessType;
@ -164,6 +165,11 @@ public class DomainBase extends DomainContent
return cloneDomainProjectedAtTime(this, now); return cloneDomainProjectedAtTime(this, now);
} }
@Override
public void beforeSqlSaveOnReplay() {
fullyQualifiedDomainName = DomainNameUtils.canonicalizeDomainName(fullyQualifiedDomainName);
}
@Override @Override
public void beforeDatastoreSaveOnReplay() { public void beforeDatastoreSaveOnReplay() {
saveIndexesToDatastore(); saveIndexesToDatastore();

View file

@ -33,6 +33,7 @@ import google.registry.model.replay.SqlEntity;
import google.registry.model.reporting.DomainTransactionRecord; import google.registry.model.reporting.DomainTransactionRecord;
import google.registry.model.reporting.HistoryEntry; import google.registry.model.reporting.HistoryEntry;
import google.registry.persistence.VKey; import google.registry.persistence.VKey;
import google.registry.util.DomainNameUtils;
import java.io.Serializable; import java.io.Serializable;
import java.util.HashSet; import java.util.HashSet;
import java.util.Optional; import java.util.Optional;
@ -303,6 +304,8 @@ public class DomainHistory extends HistoryEntry implements SqlEntity {
public void beforeSqlSaveOnReplay() { public void beforeSqlSaveOnReplay() {
if (domainContent == null) { if (domainContent == null) {
domainContent = jpaTm().getEntityManager().find(DomainBase.class, getDomainRepoId()); domainContent = jpaTm().getEntityManager().find(DomainBase.class, getDomainRepoId());
domainContent.fullyQualifiedDomainName =
DomainNameUtils.canonicalizeDomainName(domainContent.fullyQualifiedDomainName);
fillAuxiliaryFieldsFromDomain(this); fillAuxiliaryFieldsFromDomain(this);
} }
} }

View file

@ -26,6 +26,7 @@ import google.registry.model.replay.DatastoreEntity;
import google.registry.model.replay.SqlEntity; import google.registry.model.replay.SqlEntity;
import google.registry.model.reporting.HistoryEntry; import google.registry.model.reporting.HistoryEntry;
import google.registry.persistence.VKey; import google.registry.persistence.VKey;
import google.registry.util.DomainNameUtils;
import java.io.Serializable; import java.io.Serializable;
import java.util.Optional; import java.util.Optional;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -141,6 +142,8 @@ public class HostHistory extends HistoryEntry implements SqlEntity, UnsafeSerial
public void beforeSqlSaveOnReplay() { public void beforeSqlSaveOnReplay() {
if (hostBase == null) { if (hostBase == null) {
hostBase = jpaTm().getEntityManager().find(HostResource.class, getHostRepoId()); hostBase = jpaTm().getEntityManager().find(HostResource.class, getHostRepoId());
hostBase.fullyQualifiedHostName =
DomainNameUtils.canonicalizeDomainName(hostBase.fullyQualifiedHostName);
} }
} }

View file

@ -22,6 +22,7 @@ import google.registry.model.annotations.ReportedOn;
import google.registry.model.replay.DatastoreAndSqlEntity; import google.registry.model.replay.DatastoreAndSqlEntity;
import google.registry.persistence.VKey; import google.registry.persistence.VKey;
import google.registry.persistence.WithStringVKey; import google.registry.persistence.WithStringVKey;
import google.registry.util.DomainNameUtils;
import javax.persistence.Access; import javax.persistence.Access;
import javax.persistence.AccessType; import javax.persistence.AccessType;
@ -51,6 +52,11 @@ public class HostResource extends HostBase
return VKey.create(HostResource.class, getRepoId(), Key.create(this)); return VKey.create(HostResource.class, getRepoId(), Key.create(this));
} }
@Override
public void beforeSqlSaveOnReplay() {
fullyQualifiedHostName = DomainNameUtils.canonicalizeDomainName(fullyQualifiedHostName);
}
@Override @Override
public void beforeDatastoreSaveOnReplay() { public void beforeDatastoreSaveOnReplay() {
saveIndexesToDatastore(); saveIndexesToDatastore();

View file

@ -921,6 +921,19 @@ public class DomainBaseTest extends EntityTestCase {
.containsExactly(EppResourceIndex.create(Key.create(domain))); .containsExactly(EppResourceIndex.create(Key.create(domain)));
} }
@Test
void testBeforeSqlSaveOnReplay_canonicalName() {
domain.fullyQualifiedDomainName = "EXAMPLE.COM";
assertThat(domain.getDomainName()).isEqualTo("EXAMPLE.COM");
domain.beforeSqlSaveOnReplay();
assertThat(domain.getDomainName()).isEqualTo("example.com");
domain.fullyQualifiedDomainName = "kittyçat.com";
assertThat(domain.getDomainName()).isEqualTo("kittyçat.com");
domain.beforeSqlSaveOnReplay();
assertThat(domain.getDomainName()).isEqualTo("xn--kittyat-yxa.com");
}
static class BillEventInfo extends ImmutableObject { static class BillEventInfo extends ImmutableObject {
VKey<BillingEvent.Recurring> billingEventRecurring; VKey<BillingEvent.Recurring> billingEventRecurring;
Long billingEventRecurringHistoryId; Long billingEventRecurringHistoryId;

View file

@ -56,6 +56,7 @@ import google.registry.testing.DualDatabaseTest;
import google.registry.testing.TestOfyOnly; import google.registry.testing.TestOfyOnly;
import google.registry.testing.TestSqlOnly; import google.registry.testing.TestSqlOnly;
import google.registry.util.SerializeUtils; import google.registry.util.SerializeUtils;
import java.lang.reflect.Field;
import java.util.Optional; import java.util.Optional;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
@ -136,8 +137,7 @@ public class DomainHistoryTest extends EntityTestCase {
DomainHistory domainHistory = createDomainHistory(domain); DomainHistory domainHistory = createDomainHistory(domain);
tm().transact(() -> tm().insert(domainHistory)); tm().transact(() -> tm().insert(domainHistory));
// retrieving a HistoryEntry or a DomainHistory with the same key should return the same // retrieving a HistoryEntry or a DomainHistory with the same key should return the same object
// object
// note: due to the @EntitySubclass annotation. all Keys for DomainHistory objects will have // note: due to the @EntitySubclass annotation. all Keys for DomainHistory objects will have
// type HistoryEntry // type HistoryEntry
VKey<DomainHistory> domainHistoryVKey = domainHistory.createVKey(); VKey<DomainHistory> domainHistoryVKey = domainHistory.createVKey();
@ -232,6 +232,66 @@ public class DomainHistoryTest extends EntityTestCase {
jpaTm().loadByEntity(historyWithoutResource).getDomainContent().get())); jpaTm().loadByEntity(historyWithoutResource).getDomainContent().get()));
} }
@TestSqlOnly
void testBeforeSqlSave_canonicalNameUncapitalized() throws Exception {
Field domainNameField = DomainContent.class.getDeclaredField("fullyQualifiedDomainName");
// reflection hacks to get around visibility issues
domainNameField.setAccessible(true);
DomainBase domain = createDomainWithContactsAndHosts();
domainNameField.set(domain, "EXAMPLE.TLD");
DomainHistory historyWithoutResource =
new DomainHistory.Builder()
.setType(HistoryEntry.Type.DOMAIN_CREATE)
.setXmlBytes("<xml></xml>".getBytes(UTF_8))
.setModificationTime(fakeClock.nowUtc())
.setRegistrarId("TheRegistrar")
.setTrid(Trid.create("ABC-123", "server-trid"))
.setBySuperuser(false)
.setReason("reason")
.setRequestedByRegistrar(true)
.setDomainRepoId(domain.getRepoId())
.setOtherRegistrarId("otherClient")
.setPeriod(Period.create(1, Period.Unit.YEARS))
.build();
DatabaseHelper.putInDb(domain, historyWithoutResource);
jpaTm().transact(historyWithoutResource::beforeSqlSaveOnReplay);
assertThat(historyWithoutResource.getDomainContent().get().getDomainName())
.isEqualTo("example.tld");
}
@TestSqlOnly
void testBeforeSqlSave_canonicalNameUtf8() throws Exception {
Field domainNameField = DomainContent.class.getDeclaredField("fullyQualifiedDomainName");
// reflection hacks to get around visibility issues
domainNameField.setAccessible(true);
DomainBase domain = createDomainWithContactsAndHosts();
domainNameField.set(domain, "kittyçat.tld");
DomainHistory historyWithoutResource =
new DomainHistory.Builder()
.setType(HistoryEntry.Type.DOMAIN_CREATE)
.setXmlBytes("<xml></xml>".getBytes(UTF_8))
.setModificationTime(fakeClock.nowUtc())
.setRegistrarId("TheRegistrar")
.setTrid(Trid.create("ABC-123", "server-trid"))
.setBySuperuser(false)
.setReason("reason")
.setRequestedByRegistrar(true)
.setDomainRepoId(domain.getRepoId())
.setOtherRegistrarId("otherClient")
.setPeriod(Period.create(1, Period.Unit.YEARS))
.build();
DatabaseHelper.putInDb(domain, historyWithoutResource);
jpaTm().transact(historyWithoutResource::beforeSqlSaveOnReplay);
assertThat(historyWithoutResource.getDomainContent().get().getDomainName())
.isEqualTo("xn--kittyat-yxa.tld");
}
static DomainBase createDomainWithContactsAndHosts() { static DomainBase createDomainWithContactsAndHosts() {
createTld("tld"); createTld("tld");
HostResource host = newHostResourceWithRoid("ns1.example.com", "host1"); HostResource host = newHostResourceWithRoid("ns1.example.com", "host1");

View file

@ -33,10 +33,12 @@ import google.registry.model.host.HostHistory;
import google.registry.model.host.HostResource; import google.registry.model.host.HostResource;
import google.registry.model.reporting.HistoryEntry; import google.registry.model.reporting.HistoryEntry;
import google.registry.persistence.VKey; import google.registry.persistence.VKey;
import google.registry.testing.DatabaseHelper;
import google.registry.testing.DualDatabaseTest; import google.registry.testing.DualDatabaseTest;
import google.registry.testing.TestOfyOnly; import google.registry.testing.TestOfyOnly;
import google.registry.testing.TestSqlOnly; import google.registry.testing.TestSqlOnly;
import google.registry.util.SerializeUtils; import google.registry.util.SerializeUtils;
import java.lang.reflect.Field;
/** Tests for {@link HostHistory}. */ /** Tests for {@link HostHistory}. */
@DualDatabaseTest @DualDatabaseTest
@ -146,6 +148,58 @@ public class HostHistoryTest extends EntityTestCase {
.hasFieldsEqualTo(jpaTm().loadByEntity(hostHistory).getHostBase().get())); .hasFieldsEqualTo(jpaTm().loadByEntity(hostHistory).getHostBase().get()));
} }
@TestSqlOnly
void testBeforeSqlSave_canonicalNameUncapitalized() throws Exception {
Field hostNameField = HostBase.class.getDeclaredField("fullyQualifiedHostName");
// reflection hacks to get around visibility issues
hostNameField.setAccessible(true);
HostResource hostResource = newHostResource("ns1.example.tld");
hostNameField.set(hostResource, "NS1.EXAMPLE.TLD");
HostHistory hostHistory =
new HostHistory.Builder()
.setType(HistoryEntry.Type.HOST_CREATE)
.setXmlBytes("<xml></xml>".getBytes(UTF_8))
.setModificationTime(fakeClock.nowUtc())
.setRegistrarId("TheRegistrar")
.setTrid(Trid.create("ABC-123", "server-trid"))
.setBySuperuser(false)
.setReason("reason")
.setRequestedByRegistrar(true)
.setHostRepoId(hostResource.getRepoId())
.build();
DatabaseHelper.putInDb(hostResource, hostHistory);
jpaTm().transact(hostHistory::beforeSqlSaveOnReplay);
assertThat(hostHistory.getHostBase().get().getHostName()).isEqualTo("ns1.example.tld");
}
@TestSqlOnly
void testBeforeSqlSave_canonicalNameUtf8() throws Exception {
Field hostNameField = HostBase.class.getDeclaredField("fullyQualifiedHostName");
// reflection hacks to get around visibility issues
hostNameField.setAccessible(true);
HostResource hostResource = newHostResource("ns1.example.tld");
hostNameField.set(hostResource, "ns1.kittyçat.tld");
HostHistory hostHistory =
new HostHistory.Builder()
.setType(HistoryEntry.Type.HOST_CREATE)
.setXmlBytes("<xml></xml>".getBytes(UTF_8))
.setModificationTime(fakeClock.nowUtc())
.setRegistrarId("TheRegistrar")
.setTrid(Trid.create("ABC-123", "server-trid"))
.setBySuperuser(false)
.setReason("reason")
.setRequestedByRegistrar(true)
.setHostRepoId(hostResource.getRepoId())
.build();
DatabaseHelper.putInDb(hostResource, hostHistory);
jpaTm().transact(hostHistory::beforeSqlSaveOnReplay);
assertThat(hostHistory.getHostBase().get().getHostName()).isEqualTo("ns1.xn--kittyat-yxa.tld");
}
private void assertHostHistoriesEqual(HostHistory one, HostHistory two) { private void assertHostHistoriesEqual(HostHistory one, HostHistory two) {
assertAboutImmutableObjects().that(one).isEqualExceptFields(two, "hostBase"); assertAboutImmutableObjects().that(one).isEqualExceptFields(two, "hostBase");
assertAboutImmutableObjects() assertAboutImmutableObjects()

View file

@ -319,4 +319,17 @@ class HostResourceTest extends EntityTestCase {
assertThat(ofyTm().loadAllOf(EppResourceIndex.class)) assertThat(ofyTm().loadAllOf(EppResourceIndex.class))
.containsExactly(EppResourceIndex.create(Key.create(host))); .containsExactly(EppResourceIndex.create(Key.create(host)));
} }
@TestOfyOnly
void testBeforeSqlSaveOnReplay_canonicalName() {
host.fullyQualifiedHostName = "NS1.EXAMPLE.COM";
assertThat(host.getHostName()).isEqualTo("NS1.EXAMPLE.COM");
host.beforeSqlSaveOnReplay();
assertThat(host.getHostName()).isEqualTo("ns1.example.com");
host.fullyQualifiedHostName = "ns1.kittyçat.com";
assertThat(host.getHostName()).isEqualTo("ns1.kittyçat.com");
host.beforeSqlSaveOnReplay();
assertThat(host.getHostName()).isEqualTo("ns1.xn--kittyat-yxa.com");
}
} }