Fix obscure bug when checking restore prices of duplicate domain names (#968)

* Fix obscure bug when checking restore prices of duplicate domain names

There were instances of "java.lang.IllegalArgumentException: Multiple entries
with same key" in the logs, caused by attempting to construct an ImmutableMap
containing duplicate keys. It turns out this was happening in the domain check
flow when the following conditions were all simultaneously met:

1. The older v06 fee extension is used
2. The same domain name is being queried multiple times in a single check
   command (which is valid per the spec but doesn't actually make any sense)
3. Said domain exists
4. The cost of a restore (an uncommon operation) is being checked

When all of those conditions were met, an error was being thrown when the
dupe-containing list of domain names was used as the keys of a new Map. This
fixes that bug by calling .distinct() first.

Give enough registrars enough typewriters ...

BUG=179052195
This commit is contained in:
Ben McIlwain 2021-02-17 12:09:19 -05:00 committed by GitHub
parent bcc1924b24
commit 4ce790d29e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 474 additions and 0 deletions

View file

@ -301,6 +301,7 @@ public final class DomainCheckFlow implements Flow {
feeCheck.getItems().stream()
.filter(fc -> fc.getCommandName() == CommandName.RESTORE)
.map(FeeCheckCommandExtensionItem::getDomainName)
.distinct()
.collect(toImmutableList());
} else if (feeCheck.getItems().stream()
.anyMatch(fc -> fc.getCommandName() == CommandName.RESTORE)) {

View file

@ -887,6 +887,20 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase<DomainCheckFlow, Dom
runFlowAssertResponse(loadFile("domain_check_fee_reserved_response_v06.xml"));
}
@Test
void testFeeExtension_reservedName_restoreFeeWithDupes_v06() throws Exception {
persistResource(
Registry.get("tld")
.asBuilder()
.setReservedLists(createReservedList())
.setPremiumList(persistPremiumList("tld", "premiumcollision,USD 70"))
.build());
// The domain needs to exist in order for it to be loaded to check for restore fee.
persistActiveDomain("allowedinsunrise.tld");
setEppInput("domain_check_fee_reserved_dupes_v06.xml");
runFlowAssertResponse(loadFile("domain_check_fee_reserved_response_dupes_v06.xml"));
}
/** The tests must be split up for version 11, which allows only one command at a time. */
@Test
void testFeeExtension_reservedName_v11_create() throws Exception {
@ -965,6 +979,20 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase<DomainCheckFlow, Dom
runFlowAssertResponse(loadFile("domain_check_fee_reserved_response_v12.xml"));
}
@Test
void testFeeExtension_reservedName_restoreFeeWithDupes_v12() throws Exception {
persistResource(
Registry.get("tld")
.asBuilder()
.setReservedLists(createReservedList())
.setPremiumList(persistPremiumList("tld", "premiumcollision,USD 70"))
.build());
// The domain needs to exist in order for it to be loaded to check for restore fee.
persistActiveDomain("allowedinsunrise.tld");
setEppInput("domain_check_fee_reserved_dupes_v12.xml");
runFlowAssertResponse(loadFile("domain_check_fee_reserved_dupes_response_v12.xml"));
}
@Test
void testFeeExtension_feesNotOmittedOnReservedNamesInSunrise_v06() throws Exception {
createTld("tld", START_DATE_SUNRISE);

View file

@ -0,0 +1,147 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<response>
<result code="1000">
<msg>Command completed successfully</msg>
</result>
<resData>
<domain:chkData xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:cd>
<domain:name avail="0">reserved.tld</domain:name>
<domain:reason>Reserved</domain:reason>
</domain:cd>
<domain:cd>
<domain:name avail="0">allowedinsunrise.tld</domain:name>
<domain:reason>In use</domain:reason>
</domain:cd>
<domain:cd>
<domain:name avail="0">allowedinsunrise.tld</domain:name>
<domain:reason>In use</domain:reason>
</domain:cd>
<domain:cd>
<domain:name avail="0">premiumcollision.tld</domain:name>
<domain:reason>Cannot be delegated</domain:reason>
</domain:cd>
</domain:chkData>
</resData>
<extension>
<fee:chkData xmlns:fee="urn:ietf:params:xml:ns:fee-0.12"
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<fee:currency>USD</fee:currency>
<fee:cd>
<fee:object>
<domain:name>reserved.tld</domain:name>
</fee:object>
<fee:command name="create">
<fee:period unit="y">1</fee:period>
<fee:class>reserved</fee:class>
</fee:command>
</fee:cd>
<fee:cd>
<fee:object>
<domain:name>reserved.tld</domain:name>
</fee:object>
<fee:command name="renew">
<fee:period unit="y">1</fee:period>
<fee:fee description="renew">11.00</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:object>
<domain:name>reserved.tld</domain:name>
</fee:object>
<fee:command name="transfer">
<fee:period unit="y">1</fee:period>
<fee:fee description="renew">11.00</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:object>
<domain:name>reserved.tld</domain:name>
</fee:object>
<fee:command name="restore">
<fee:period unit="y">1</fee:period>
<fee:fee description="restore">17.00</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:object>
<domain:name>allowedinsunrise.tld</domain:name>
</fee:object>
<fee:command name="create">
<fee:period unit="y">1</fee:period>
<fee:class>reserved</fee:class>
</fee:command>
</fee:cd>
<fee:cd>
<fee:object>
<domain:name>allowedinsunrise.tld</domain:name>
</fee:object>
<fee:command name="renew">
<fee:period unit="y">1</fee:period>
<fee:fee description="renew">11.00</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:object>
<domain:name>allowedinsunrise.tld</domain:name>
</fee:object>
<fee:command name="transfer">
<fee:period unit="y">1</fee:period>
<fee:fee description="renew">11.00</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:object>
<domain:name>allowedinsunrise.tld</domain:name>
</fee:object>
<fee:command name="restore">
<fee:period unit="y">1</fee:period>
<fee:fee description="restore">17.00</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:object>
<domain:name>premiumcollision.tld</domain:name>
</fee:object>
<fee:command name="create">
<fee:period unit="y">1</fee:period>
<fee:class>reserved</fee:class>
</fee:command>
</fee:cd>
<fee:cd>
<fee:object>
<domain:name>premiumcollision.tld</domain:name>
</fee:object>
<fee:command name="renew">
<fee:period unit="y">1</fee:period>
<fee:fee description="renew">70.00</fee:fee>
<fee:class>premium</fee:class>
</fee:command>
</fee:cd>
<fee:cd>
<fee:object>
<domain:name>premiumcollision.tld</domain:name>
</fee:object>
<fee:command name="transfer">
<fee:period unit="y">1</fee:period>
<fee:fee description="renew">70.00</fee:fee>
<fee:class>premium</fee:class>
</fee:command>
</fee:cd>
<fee:cd>
<fee:object>
<domain:name>premiumcollision.tld</domain:name>
</fee:object>
<fee:command name="restore">
<fee:period unit="y">1</fee:period>
<fee:fee description="restore">17.00</fee:fee>
</fee:command>
</fee:cd>
</fee:chkData>
</extension>
<trID>
<clTRID>ABC-12345</clTRID>
<svTRID>server-trid</svTRID>
</trID>
</response>
</epp>

View file

@ -0,0 +1,116 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<check>
<domain:check xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>reserved.tld</domain:name>
<domain:name>allowedinsunrise.tld</domain:name>
<domain:name>allowedinsunrise.tld</domain:name>
<domain:name>premiumcollision.tld</domain:name>
</domain:check>
</check>
<extension>
<launch:check xmlns:launch="urn:ietf:params:xml:ns:launch-1.0" type="avail">
<launch:phase name="foo">custom</launch:phase>
</launch:check>
<fee:check xmlns:fee="urn:ietf:params:xml:ns:fee-0.6">
<fee:domain>
<fee:name>reserved.tld</fee:name>
<fee:currency>USD</fee:currency>
<fee:command>create</fee:command>
<fee:period unit="y">1</fee:period>
</fee:domain>
<fee:domain>
<fee:name>reserved.tld</fee:name>
<fee:currency>USD</fee:currency>
<fee:command>renew</fee:command>
<fee:period unit="y">1</fee:period>
</fee:domain>
<fee:domain>
<fee:name>reserved.tld</fee:name>
<fee:currency>USD</fee:currency>
<fee:command>transfer</fee:command>
<fee:period unit="y">1</fee:period>
</fee:domain>
<fee:domain>
<fee:name>reserved.tld</fee:name>
<fee:currency>USD</fee:currency>
<fee:command>restore</fee:command>
<fee:period unit="y">1</fee:period>
</fee:domain>
<fee:domain>
<fee:name>allowedinsunrise.tld</fee:name>
<fee:currency>USD</fee:currency>
<fee:command>create</fee:command>
<fee:period unit="y">1</fee:period>
</fee:domain>
<fee:domain>
<fee:name>allowedinsunrise.tld</fee:name>
<fee:currency>USD</fee:currency>
<fee:command>renew</fee:command>
<fee:period unit="y">1</fee:period>
</fee:domain>
<fee:domain>
<fee:name>allowedinsunrise.tld</fee:name>
<fee:currency>USD</fee:currency>
<fee:command>transfer</fee:command>
<fee:period unit="y">1</fee:period>
</fee:domain>
<fee:domain>
<fee:name>allowedinsunrise.tld</fee:name>
<fee:currency>USD</fee:currency>
<fee:command>restore</fee:command>
<fee:period unit="y">1</fee:period>
</fee:domain>
<fee:domain>
<fee:name>allowedinsunrise.tld</fee:name>
<fee:currency>USD</fee:currency>
<fee:command>create</fee:command>
<fee:period unit="y">1</fee:period>
</fee:domain>
<fee:domain>
<fee:name>allowedinsunrise.tld</fee:name>
<fee:currency>USD</fee:currency>
<fee:command>renew</fee:command>
<fee:period unit="y">1</fee:period>
</fee:domain>
<fee:domain>
<fee:name>allowedinsunrise.tld</fee:name>
<fee:currency>USD</fee:currency>
<fee:command>transfer</fee:command>
<fee:period unit="y">1</fee:period>
</fee:domain>
<fee:domain>
<fee:name>allowedinsunrise.tld</fee:name>
<fee:currency>USD</fee:currency>
<fee:command>restore</fee:command>
<fee:period unit="y">1</fee:period>
</fee:domain>
<fee:domain>
<fee:name>premiumcollision.tld</fee:name>
<fee:currency>USD</fee:currency>
<fee:command>create</fee:command>
<fee:period unit="y">1</fee:period>
</fee:domain>
<fee:domain>
<fee:name>premiumcollision.tld</fee:name>
<fee:currency>USD</fee:currency>
<fee:command>renew</fee:command>
<fee:period unit="y">1</fee:period>
</fee:domain>
<fee:domain>
<fee:name>premiumcollision.tld</fee:name>
<fee:currency>USD</fee:currency>
<fee:command>transfer</fee:command>
<fee:period unit="y">1</fee:period>
</fee:domain>
<fee:domain>
<fee:name>premiumcollision.tld</fee:name>
<fee:currency>USD</fee:currency>
<fee:command>restore</fee:command>
<fee:period unit="y">1</fee:period>
</fee:domain>
</fee:check>
</extension>
<clTRID>ABC-12345</clTRID>
</command>
</epp>

View file

@ -0,0 +1,33 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<check>
<domain:check xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>reserved.tld</domain:name>
<domain:name>allowedinsunrise.tld</domain:name>
<domain:name>allowedinsunrise.tld</domain:name>
<domain:name>premiumcollision.tld</domain:name>
</domain:check>
</check>
<extension>
<launch:check xmlns:launch="urn:ietf:params:xml:ns:launch-1.0" type="avail">
<launch:phase name="foo">custom</launch:phase>
</launch:check>
<fee:check xmlns:fee="urn:ietf:params:xml:ns:fee-0.12">
<fee:currency>USD</fee:currency>
<fee:command name="create">
<fee:period unit="y">1</fee:period>
</fee:command>
<fee:command name="renew">
<fee:period unit="y">1</fee:period>
</fee:command>
<fee:command name="transfer">
<fee:period unit="y">1</fee:period>
</fee:command>
<fee:command name="restore">
<fee:period unit="y">1</fee:period>
</fee:command>
</fee:check>
</extension>
<clTRID>ABC-12345</clTRID>
</command>
</epp>

View file

@ -0,0 +1,149 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<response>
<result code="1000">
<msg>Command completed successfully</msg>
</result>
<resData>
<domain:chkData xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:cd>
<domain:name avail="0">reserved.tld</domain:name>
<domain:reason>Reserved</domain:reason>
</domain:cd>
<domain:cd>
<domain:name avail="0">allowedinsunrise.tld</domain:name>
<domain:reason>In use</domain:reason>
</domain:cd>
<domain:cd>
<domain:name avail="0">allowedinsunrise.tld</domain:name>
<domain:reason>In use</domain:reason>
</domain:cd>
<domain:cd>
<domain:name avail="0">premiumcollision.tld</domain:name>
<domain:reason>Cannot be delegated</domain:reason>
</domain:cd>
</domain:chkData>
</resData>
<extension>
<fee:chkData xmlns:fee="urn:ietf:params:xml:ns:fee-0.6">
<fee:cd xmlns:fee="urn:ietf:params:xml:ns:fee-0.6">
<fee:name>reserved.tld</fee:name>
<fee:currency>USD</fee:currency>
<fee:command>create</fee:command>
<fee:period unit="y">1</fee:period>
<fee:class>reserved</fee:class>
</fee:cd>
<fee:cd xmlns:fee="urn:ietf:params:xml:ns:fee-0.6">
<fee:name>reserved.tld</fee:name>
<fee:currency>USD</fee:currency>
<fee:command>renew</fee:command>
<fee:period unit="y">1</fee:period>
<fee:fee description="renew">11.00</fee:fee>
</fee:cd>
<fee:cd xmlns:fee="urn:ietf:params:xml:ns:fee-0.6">
<fee:name>reserved.tld</fee:name>
<fee:currency>USD</fee:currency>
<fee:command>transfer</fee:command>
<fee:period unit="y">1</fee:period>
<fee:fee description="renew">11.00</fee:fee>
</fee:cd>
<fee:cd xmlns:fee="urn:ietf:params:xml:ns:fee-0.6">
<fee:name>reserved.tld</fee:name>
<fee:currency>USD</fee:currency>
<fee:command>restore</fee:command>
<fee:period unit="y">1</fee:period>
<fee:fee description="restore">17.00</fee:fee>
</fee:cd>
<fee:cd xmlns:fee="urn:ietf:params:xml:ns:fee-0.6">
<fee:name>allowedinsunrise.tld</fee:name>
<fee:currency>USD</fee:currency>
<fee:command>create</fee:command>
<fee:period unit="y">1</fee:period>
<fee:class>reserved</fee:class>
</fee:cd>
<fee:cd xmlns:fee="urn:ietf:params:xml:ns:fee-0.6">
<fee:name>allowedinsunrise.tld</fee:name>
<fee:currency>USD</fee:currency>
<fee:command>renew</fee:command>
<fee:period unit="y">1</fee:period>
<fee:fee description="renew">11.00</fee:fee>
</fee:cd>
<fee:cd xmlns:fee="urn:ietf:params:xml:ns:fee-0.6">
<fee:name>allowedinsunrise.tld</fee:name>
<fee:currency>USD</fee:currency>
<fee:command>transfer</fee:command>
<fee:period unit="y">1</fee:period>
<fee:fee description="renew">11.00</fee:fee>
</fee:cd>
<fee:cd xmlns:fee="urn:ietf:params:xml:ns:fee-0.6">
<fee:name>allowedinsunrise.tld</fee:name>
<fee:currency>USD</fee:currency>
<fee:command>restore</fee:command>
<fee:period unit="y">1</fee:period>
<fee:fee description="restore">17.00</fee:fee>
</fee:cd>
<fee:cd xmlns:fee="urn:ietf:params:xml:ns:fee-0.6">
<fee:name>allowedinsunrise.tld</fee:name>
<fee:currency>USD</fee:currency>
<fee:command>create</fee:command>
<fee:period unit="y">1</fee:period>
<fee:class>reserved</fee:class>
</fee:cd>
<fee:cd xmlns:fee="urn:ietf:params:xml:ns:fee-0.6">
<fee:name>allowedinsunrise.tld</fee:name>
<fee:currency>USD</fee:currency>
<fee:command>renew</fee:command>
<fee:period unit="y">1</fee:period>
<fee:fee description="renew">11.00</fee:fee>
</fee:cd>
<fee:cd xmlns:fee="urn:ietf:params:xml:ns:fee-0.6">
<fee:name>allowedinsunrise.tld</fee:name>
<fee:currency>USD</fee:currency>
<fee:command>transfer</fee:command>
<fee:period unit="y">1</fee:period>
<fee:fee description="renew">11.00</fee:fee>
</fee:cd>
<fee:cd xmlns:fee="urn:ietf:params:xml:ns:fee-0.6">
<fee:name>allowedinsunrise.tld</fee:name>
<fee:currency>USD</fee:currency>
<fee:command>restore</fee:command>
<fee:period unit="y">1</fee:period>
<fee:fee description="restore">17.00</fee:fee>
</fee:cd>
<fee:cd xmlns:fee="urn:ietf:params:xml:ns:fee-0.6">
<fee:name>premiumcollision.tld</fee:name>
<fee:currency>USD</fee:currency>
<fee:command>create</fee:command>
<fee:period unit="y">1</fee:period>
<fee:class>reserved</fee:class>
</fee:cd>
<fee:cd xmlns:fee="urn:ietf:params:xml:ns:fee-0.6">
<fee:name>premiumcollision.tld</fee:name>
<fee:currency>USD</fee:currency>
<fee:command>renew</fee:command>
<fee:period unit="y">1</fee:period>
<fee:fee description="renew">70.00</fee:fee>
<fee:class>premium</fee:class>
</fee:cd>
<fee:cd xmlns:fee="urn:ietf:params:xml:ns:fee-0.6">
<fee:name>premiumcollision.tld</fee:name>
<fee:currency>USD</fee:currency>
<fee:command>transfer</fee:command>
<fee:period unit="y">1</fee:period>
<fee:fee description="renew">70.00</fee:fee>
<fee:class>premium</fee:class>
</fee:cd>
<fee:cd xmlns:fee="urn:ietf:params:xml:ns:fee-0.6">
<fee:name>premiumcollision.tld</fee:name>
<fee:currency>USD</fee:currency>
<fee:command>restore</fee:command>
<fee:period unit="y">1</fee:period>
<fee:fee description="restore">17.00</fee:fee>
</fee:cd>
</fee:chkData>
</extension>
<trID>
<clTRID>ABC-12345</clTRID>
<svTRID>server-trid</svTRID>
</trID>
</response>
</epp>