Add RDAP warning when domain searches by nameserver may be incomplete

When searching for domains by nameserver name or IP address, we fetch the matching nameserver keys, then search for domains by those keys. We limit the number of nameserver keys returned, to avoid arbitrarily large domain queries. This CL adds a warning to the RDAP response if we retrieved the maximum number of nameservers. This may indicate that we have not found all the domains.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=168885124
This commit is contained in:
mountford 2017-09-15 14:04:18 -07:00 committed by jianglai
parent 7dc1940cdb
commit 1bb655267c
7 changed files with 266 additions and 43 deletions

View file

@ -17,7 +17,6 @@ package google.registry.rdap;
import static google.registry.model.EppResourceUtils.loadByForeignKey; import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.model.index.ForeignKeyIndex.loadAndGetKey; import static google.registry.model.index.ForeignKeyIndex.loadAndGetKey;
import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.rdap.RdapIcannStandardInformation.TRUNCATION_NOTICES;
import static google.registry.request.Action.Method.GET; import static google.registry.request.Action.Method.GET;
import static google.registry.request.Action.Method.HEAD; import static google.registry.request.Action.Method.HEAD;
import static google.registry.util.DateTimeUtils.END_OF_TIME; import static google.registry.util.DateTimeUtils.END_OF_TIME;
@ -36,6 +35,7 @@ import google.registry.model.domain.DomainResource;
import google.registry.model.host.HostResource; import google.registry.model.host.HostResource;
import google.registry.rdap.RdapJsonFormatter.BoilerplateType; import google.registry.rdap.RdapJsonFormatter.BoilerplateType;
import google.registry.rdap.RdapJsonFormatter.OutputDataType; import google.registry.rdap.RdapJsonFormatter.OutputDataType;
import google.registry.rdap.RdapSearchResults.IncompletenessWarningType;
import google.registry.request.Action; import google.registry.request.Action;
import google.registry.request.HttpException.BadRequestException; import google.registry.request.HttpException.BadRequestException;
import google.registry.request.HttpException.NotFoundException; import google.registry.request.HttpException.NotFoundException;
@ -73,6 +73,8 @@ public class RdapDomainSearchAction extends RdapActionBase {
public static final int RESULT_SET_SIZE_SCALING_FACTOR = 30; public static final int RESULT_SET_SIZE_SCALING_FACTOR = 30;
public static final int MAX_NAMESERVERS_IN_FIRST_STAGE = 1000;
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass(); private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
@Inject Clock clock; @Inject Clock clock;
@ -142,8 +144,7 @@ public class RdapDomainSearchAction extends RdapActionBase {
rdapJsonFormatter.addTopLevelEntries( rdapJsonFormatter.addTopLevelEntries(
builder, builder,
BoilerplateType.DOMAIN, BoilerplateType.DOMAIN,
results.isTruncated() results.getIncompletenessWarnings(),
? TRUNCATION_NOTICES : ImmutableList.<ImmutableMap<String, Object>>of(),
ImmutableList.<ImmutableMap<String, Object>>of(), ImmutableList.<ImmutableMap<String, Object>>of(),
rdapLinkBase); rdapLinkBase);
return builder.build(); return builder.build();
@ -167,7 +168,7 @@ public class RdapDomainSearchAction extends RdapActionBase {
ImmutableList<DomainResource> results = (domainResource == null) ImmutableList<DomainResource> results = (domainResource == null)
? ImmutableList.<DomainResource>of() ? ImmutableList.<DomainResource>of()
: ImmutableList.of(domainResource); : ImmutableList.of(domainResource);
return makeSearchResults(results, false /* isTruncated */, now); return makeSearchResults(results, now);
// Handle queries with a wildcard and no initial string. // Handle queries with a wildcard and no initial string.
} else if (partialStringQuery.getInitialString().isEmpty()) { } else if (partialStringQuery.getInitialString().isEmpty()) {
if (partialStringQuery.getSuffix() == null) { if (partialStringQuery.getSuffix() == null) {
@ -181,7 +182,7 @@ public class RdapDomainSearchAction extends RdapActionBase {
.filter("tld", partialStringQuery.getSuffix()) .filter("tld", partialStringQuery.getSuffix())
.filter("deletionTime >", now) .filter("deletionTime >", now)
.limit(rdapResultSetMaxSize + 1); .limit(rdapResultSetMaxSize + 1);
return makeSearchResults(query.list(), false /* isTruncated */, now); return makeSearchResults(query.list(), now);
// Handle queries with a wildcard and an initial string. // Handle queries with a wildcard and an initial string.
} else { } else {
if ((partialStringQuery.getSuffix() == null) if ((partialStringQuery.getSuffix() == null)
@ -214,12 +215,13 @@ public class RdapDomainSearchAction extends RdapActionBase {
query.limit(RESULT_SET_SIZE_SCALING_FACTOR * rdapResultSetMaxSize)) { query.limit(RESULT_SET_SIZE_SCALING_FACTOR * rdapResultSetMaxSize)) {
if (EppResourceUtils.isActive(domain, now)) { if (EppResourceUtils.isActive(domain, now)) {
if (domainList.size() >= rdapResultSetMaxSize) { if (domainList.size() >= rdapResultSetMaxSize) {
return makeSearchResults(ImmutableList.copyOf(domainList), true /* isTruncated */, now); return makeSearchResults(
ImmutableList.copyOf(domainList), IncompletenessWarningType.TRUNCATED, now);
} }
domainList.add(domain); domainList.add(domain);
} }
} }
return makeSearchResults(domainList, false /* isTruncated */, now); return makeSearchResults(domainList, now);
} }
} }
@ -293,10 +295,15 @@ public class RdapDomainSearchAction extends RdapActionBase {
// pending deletes for hosts, so we can call queryUndeleted. In this case, the initial string // pending deletes for hosts, so we can call queryUndeleted. In this case, the initial string
// must be present, to avoid querying every host in the system. This restriction is enforced // must be present, to avoid querying every host in the system. This restriction is enforced
// by queryUndeleted(). // by queryUndeleted().
// TODO (b/24463238): figure out how to limit the size of these queries effectively
} else { } else {
// Only return the first 1000 nameservers. This could result in an incomplete result set if
// a search asks for something like "ns*", but we need to enforce a limit in order to avoid
// arbitrarily long-running queries.
return queryUndeleted( return queryUndeleted(
HostResource.class, "fullyQualifiedHostName", partialStringQuery, 1000) HostResource.class,
"fullyQualifiedHostName",
partialStringQuery,
MAX_NAMESERVERS_IN_FIRST_STAGE)
.keys(); .keys();
} }
} }
@ -307,21 +314,23 @@ public class RdapDomainSearchAction extends RdapActionBase {
* *
* <p>This is a two-step process: get a list of host references by IP address, and then look up * <p>This is a two-step process: get a list of host references by IP address, and then look up
* domains by host reference. * domains by host reference.
*
* <p>In theory, we could have any number of hosts using the same IP address. To make sure we get
* all the associated domains, we have to retrieve all of them, and use them to look up domains.
* This could open us up to a kind of DoS attack if huge number of hosts are defined on a single
* IP. To avoid this, fetch only the first 1000 nameservers. In all normal circumstances, this
* should be orders of magnitude more than there actually are. But it could result in us missing
* some domains.
*/ */
private RdapSearchResults searchByNameserverIp( private RdapSearchResults searchByNameserverIp(
final InetAddress inetAddress, final DateTime now) { final InetAddress inetAddress, final DateTime now) {
// In theory, we could filter on the deletion time being in the future. But we can't do that in
// the query on nameserver name (because we're already using an inequality query), and it seems
// dangerous and confusing to filter on deletion time differently between the two queries.
// Find all domains that link to any of these hosts, and return information about them.
// TODO (b/24463238): figure out how to limit the size of these queries effectively
return searchByNameserverRefs( return searchByNameserverRefs(
ofy() ofy()
.load() .load()
.type(HostResource.class) .type(HostResource.class)
.filter("inetAddresses", inetAddress.getHostAddress()) .filter("inetAddresses", inetAddress.getHostAddress())
.filter("deletionTime", END_OF_TIME) .filter("deletionTime", END_OF_TIME)
.limit(1000) .limit(MAX_NAMESERVERS_IN_FIRST_STAGE)
.keys(), .keys(),
now); now);
} }
@ -329,8 +338,8 @@ public class RdapDomainSearchAction extends RdapActionBase {
/** /**
* Locates all domains which are linked to a set of host keys. * Locates all domains which are linked to a set of host keys.
* *
* <p>This method is called by {@link #searchByNameserverLdhName} and * <p>This method is called by {@link #searchByNameserverLdhName} and {@link
* {@link #searchByNameserverIp} after they assemble the relevant host keys. * #searchByNameserverIp} after they assemble the relevant host keys.
*/ */
private RdapSearchResults searchByNameserverRefs( private RdapSearchResults searchByNameserverRefs(
final Iterable<Key<HostResource>> hostKeys, final DateTime now) { final Iterable<Key<HostResource>> hostKeys, final DateTime now) {
@ -340,7 +349,9 @@ public class RdapDomainSearchAction extends RdapActionBase {
// domain), we must create a set of resulting {@link DomainResource} objects. But we use a // domain), we must create a set of resulting {@link DomainResource} objects. But we use a
// LinkedHashSet to preserve the order in which we found the domains. // LinkedHashSet to preserve the order in which we found the domains.
LinkedHashSet<DomainResource> domains = new LinkedHashSet<>(); LinkedHashSet<DomainResource> domains = new LinkedHashSet<>();
int numHostKeysSearched = 0;
for (List<Key<HostResource>> chunk : Iterables.partition(hostKeys, 30)) { for (List<Key<HostResource>> chunk : Iterables.partition(hostKeys, 30)) {
numHostKeysSearched += chunk.size();
for (DomainResource domain : ofy().load() for (DomainResource domain : ofy().load()
.type(DomainResource.class) .type(DomainResource.class)
.filter("nsHosts in", chunk) .filter("nsHosts in", chunk)
@ -348,23 +359,37 @@ public class RdapDomainSearchAction extends RdapActionBase {
.limit(rdapResultSetMaxSize + 1)) { .limit(rdapResultSetMaxSize + 1)) {
if (!domains.contains(domain)) { if (!domains.contains(domain)) {
if (domains.size() >= rdapResultSetMaxSize) { if (domains.size() >= rdapResultSetMaxSize) {
return makeSearchResults(ImmutableList.copyOf(domains), true /* isTruncated */, now); return makeSearchResults(
ImmutableList.copyOf(domains), IncompletenessWarningType.TRUNCATED, now);
} }
domains.add(domain); domains.add(domain);
} }
} }
} }
return makeSearchResults(ImmutableList.copyOf(domains), false /* isTruncated */, now); return makeSearchResults(
ImmutableList.copyOf(domains),
(numHostKeysSearched >= MAX_NAMESERVERS_IN_FIRST_STAGE)
? IncompletenessWarningType.MIGHT_BE_INCOMPLETE
: IncompletenessWarningType.NONE,
now);
}
/** Output JSON for a list of domains, with no incompleteness warnings. */
private RdapSearchResults makeSearchResults(List<DomainResource> domains, DateTime now) {
return makeSearchResults(domains, IncompletenessWarningType.NONE, now);
} }
/** /**
* Output JSON for a list of domains. * Output JSON for a list of domains.
* *
* <p>The isTruncated parameter should be true if the search found more results than are in the * <p>The incompletenessWarningType should be set to TRUNCATED if the search found more results
* list, meaning that the truncation notice should be added. * than are in the list, or MIGHT_BE_INCOMPLETE if a search for domains by nameserver returned the
* maximum number of nameservers in the first stage query.
*/ */
private RdapSearchResults makeSearchResults( private RdapSearchResults makeSearchResults(
List<DomainResource> domains, boolean isTruncated, DateTime now) { List<DomainResource> domains,
IncompletenessWarningType incompletenessWarningType,
DateTime now) {
OutputDataType outputDataType = OutputDataType outputDataType =
(domains.size() > 1) ? OutputDataType.SUMMARY : OutputDataType.FULL; (domains.size() > 1) ? OutputDataType.SUMMARY : OutputDataType.FULL;
RdapAuthorization authorization = getAuthorization(); RdapAuthorization authorization = getAuthorization();
@ -374,6 +399,6 @@ public class RdapDomainSearchAction extends RdapActionBase {
rdapJsonFormatter.makeRdapJsonForDomain( rdapJsonFormatter.makeRdapJsonForDomain(
domain, false, rdapLinkBase, rdapWhoisServer, now, outputDataType, authorization)); domain, false, rdapLinkBase, rdapWhoisServer, now, outputDataType, authorization));
} }
return RdapSearchResults.create(jsonBuilder.build(), isTruncated); return RdapSearchResults.create(jsonBuilder.build(), incompletenessWarningType);
} }
} }

View file

@ -15,7 +15,6 @@
package google.registry.rdap; package google.registry.rdap;
import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.rdap.RdapIcannStandardInformation.TRUNCATION_NOTICES;
import static google.registry.rdap.RdapUtils.getRegistrarByIanaIdentifier; import static google.registry.rdap.RdapUtils.getRegistrarByIanaIdentifier;
import static google.registry.request.Action.Method.GET; import static google.registry.request.Action.Method.GET;
import static google.registry.request.Action.Method.HEAD; import static google.registry.request.Action.Method.HEAD;
@ -35,6 +34,7 @@ import google.registry.model.domain.DesignatedContact;
import google.registry.model.registrar.Registrar; import google.registry.model.registrar.Registrar;
import google.registry.rdap.RdapJsonFormatter.BoilerplateType; import google.registry.rdap.RdapJsonFormatter.BoilerplateType;
import google.registry.rdap.RdapJsonFormatter.OutputDataType; import google.registry.rdap.RdapJsonFormatter.OutputDataType;
import google.registry.rdap.RdapSearchResults.IncompletenessWarningType;
import google.registry.request.Action; import google.registry.request.Action;
import google.registry.request.HttpException.BadRequestException; import google.registry.request.HttpException.BadRequestException;
import google.registry.request.HttpException.NotFoundException; import google.registry.request.HttpException.NotFoundException;
@ -113,8 +113,7 @@ public class RdapEntitySearchAction extends RdapActionBase {
rdapJsonFormatter.addTopLevelEntries( rdapJsonFormatter.addTopLevelEntries(
jsonBuilder, jsonBuilder,
BoilerplateType.ENTITY, BoilerplateType.ENTITY,
results.isTruncated() results.getIncompletenessWarnings(),
? TRUNCATION_NOTICES : ImmutableList.<ImmutableMap<String, Object>>of(),
ImmutableList.<ImmutableMap<String, Object>>of(), ImmutableList.<ImmutableMap<String, Object>>of(),
rdapLinkBase); rdapLinkBase);
return jsonBuilder.build(); return jsonBuilder.build();
@ -258,7 +257,8 @@ public class RdapEntitySearchAction extends RdapActionBase {
List<ImmutableMap<String, Object>> jsonOutputList = new ArrayList<>(); List<ImmutableMap<String, Object>> jsonOutputList = new ArrayList<>();
for (ContactResource contact : contacts) { for (ContactResource contact : contacts) {
if (jsonOutputList.size() >= rdapResultSetMaxSize) { if (jsonOutputList.size() >= rdapResultSetMaxSize) {
return RdapSearchResults.create(ImmutableList.copyOf(jsonOutputList), true); return RdapSearchResults.create(
ImmutableList.copyOf(jsonOutputList), IncompletenessWarningType.TRUNCATED);
} }
// As per Andy Newton on the regext mailing list, contacts by themselves have no role, since // As per Andy Newton on the regext mailing list, contacts by themselves have no role, since
// they are global, and might have different roles for different domains. // they are global, and might have different roles for different domains.
@ -275,7 +275,8 @@ public class RdapEntitySearchAction extends RdapActionBase {
for (Registrar registrar : registrars) { for (Registrar registrar : registrars) {
if (registrar.isActiveAndPubliclyVisible()) { if (registrar.isActiveAndPubliclyVisible()) {
if (jsonOutputList.size() >= rdapResultSetMaxSize) { if (jsonOutputList.size() >= rdapResultSetMaxSize) {
return RdapSearchResults.create(ImmutableList.copyOf(jsonOutputList), true); return RdapSearchResults.create(
ImmutableList.copyOf(jsonOutputList), IncompletenessWarningType.TRUNCATED);
} }
jsonOutputList.add(rdapJsonFormatter.makeRdapJsonForRegistrar( jsonOutputList.add(rdapJsonFormatter.makeRdapJsonForRegistrar(
registrar, false, rdapLinkBase, rdapWhoisServer, now, outputDataType)); registrar, false, rdapLinkBase, rdapWhoisServer, now, outputDataType));

View file

@ -105,6 +105,24 @@ public class RdapIcannStandardInformation {
static final ImmutableList<ImmutableMap<String, Object>> TRUNCATION_NOTICES = static final ImmutableList<ImmutableMap<String, Object>> TRUNCATION_NOTICES =
ImmutableList.of(TRUNCATED_RESULT_SET_NOTICE); ImmutableList.of(TRUNCATED_RESULT_SET_NOTICE);
/**
* Used when a search for domains by nameserver may have returned incomplete information because
* there were too many nameservers in the first stage results.
*/
static final ImmutableMap<String, Object> POSSIBLY_INCOMPLETE_RESULT_SET_NOTICE =
ImmutableMap.<String, Object>of(
"title",
"Search Policy",
"description",
ImmutableList.of(
"Search results may contain incomplete information due to first-stage query limits."),
"type",
"result set truncated due to unexplainable reasons");
/** Possibly incomplete notice as a singleton list, for easy use. */
static final ImmutableList<ImmutableMap<String, Object>> POSSIBLY_INCOMPLETE_NOTICES =
ImmutableList.of(POSSIBLY_INCOMPLETE_RESULT_SET_NOTICE);
/** Included when the requester is not logged in as the owner of the domain being returned. */ /** Included when the requester is not logged in as the owner of the domain being returned. */
static final ImmutableMap<String, Object> DOMAIN_CONTACTS_HIDDEN_DATA_REMARK = static final ImmutableMap<String, Object> DOMAIN_CONTACTS_HIDDEN_DATA_REMARK =
ImmutableMap.<String, Object> of( ImmutableMap.<String, Object> of(

View file

@ -16,7 +16,6 @@ package google.registry.rdap;
import static google.registry.model.EppResourceUtils.loadByForeignKey; import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.rdap.RdapIcannStandardInformation.TRUNCATION_NOTICES;
import static google.registry.request.Action.Method.GET; import static google.registry.request.Action.Method.GET;
import static google.registry.request.Action.Method.HEAD; import static google.registry.request.Action.Method.HEAD;
import static google.registry.util.DateTimeUtils.END_OF_TIME; import static google.registry.util.DateTimeUtils.END_OF_TIME;
@ -32,6 +31,7 @@ import google.registry.model.domain.DomainResource;
import google.registry.model.host.HostResource; import google.registry.model.host.HostResource;
import google.registry.rdap.RdapJsonFormatter.BoilerplateType; import google.registry.rdap.RdapJsonFormatter.BoilerplateType;
import google.registry.rdap.RdapJsonFormatter.OutputDataType; import google.registry.rdap.RdapJsonFormatter.OutputDataType;
import google.registry.rdap.RdapSearchResults.IncompletenessWarningType;
import google.registry.request.Action; import google.registry.request.Action;
import google.registry.request.HttpException.BadRequestException; import google.registry.request.HttpException.BadRequestException;
import google.registry.request.HttpException.NotFoundException; import google.registry.request.HttpException.NotFoundException;
@ -119,8 +119,7 @@ public class RdapNameserverSearchAction extends RdapActionBase {
rdapJsonFormatter.addTopLevelEntries( rdapJsonFormatter.addTopLevelEntries(
jsonBuilder, jsonBuilder,
BoilerplateType.NAMESERVER, BoilerplateType.NAMESERVER,
results.isTruncated() results.getIncompletenessWarnings(),
? TRUNCATION_NOTICES : ImmutableList.<ImmutableMap<String, Object>>of(),
ImmutableList.<ImmutableMap<String, Object>>of(), ImmutableList.<ImmutableMap<String, Object>>of(),
rdapLinkBase); rdapLinkBase);
return jsonBuilder.build(); return jsonBuilder.build();
@ -215,6 +214,10 @@ public class RdapNameserverSearchAction extends RdapActionBase {
host, false, rdapLinkBase, rdapWhoisServer, now, outputDataType)); host, false, rdapLinkBase, rdapWhoisServer, now, outputDataType));
} }
ImmutableList<ImmutableMap<String, Object>> jsonList = jsonListBuilder.build(); ImmutableList<ImmutableMap<String, Object>> jsonList = jsonListBuilder.build();
return RdapSearchResults.create(jsonList, jsonList.size() < hosts.size()); return RdapSearchResults.create(
jsonList,
(jsonList.size() < hosts.size())
? IncompletenessWarningType.TRUNCATED
: IncompletenessWarningType.NONE);
} }
} }

View file

@ -14,31 +14,61 @@
package google.registry.rdap; package google.registry.rdap;
import static google.registry.rdap.RdapIcannStandardInformation.POSSIBLY_INCOMPLETE_NOTICES;
import static google.registry.rdap.RdapIcannStandardInformation.TRUNCATION_NOTICES;
import com.google.auto.value.AutoValue; import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
/** /**
* Holds domain, nameserver and entity search results. * Holds domain, nameserver and entity search results.
* *
* <p>We need to know not only the list of things we found, but also whether the result set was * <p>We need to know not only the list of things we found, but also whether the result set was
* truncated to the limit. If it is, we must add the ICANN-mandated notice to that effect. * truncated to the limit. If it is, we must add the ICANN-mandated notice to that effect.
*/ */
@AutoValue @AutoValue
abstract class RdapSearchResults { abstract class RdapSearchResults {
static RdapSearchResults create(ImmutableList<ImmutableMap<String, Object>> jsonList) { enum IncompletenessWarningType {
return create(jsonList, false);
/** Result set is complete. */
NONE,
/** Result set has been limited to the maximum size. */
TRUNCATED,
/**
* Result set might be missing data because the first step of a two-step query returned a data
* set that was limited in size.
*/
MIGHT_BE_INCOMPLETE
} }
static RdapSearchResults create(ImmutableList<ImmutableMap<String, Object>> jsonList) {
return create(jsonList, IncompletenessWarningType.NONE);
}
static RdapSearchResults create( static RdapSearchResults create(
ImmutableList<ImmutableMap<String, Object>> jsonList, boolean isTruncated) { ImmutableList<ImmutableMap<String, Object>> jsonList,
return new AutoValue_RdapSearchResults(jsonList, isTruncated); IncompletenessWarningType incompletenessWarningType) {
return new AutoValue_RdapSearchResults(jsonList, incompletenessWarningType);
} }
/** List of JSON result object representations. */ /** List of JSON result object representations. */
abstract ImmutableList<ImmutableMap<String, Object>> jsonList(); abstract ImmutableList<ImmutableMap<String, Object>> jsonList();
/** True if the result set was truncated to the maximum size limit. */ /** Type of warning to display regarding possible incomplete data. */
abstract boolean isTruncated(); abstract IncompletenessWarningType incompletenessWarningType();
/** Convenience method to get the appropriate warnings for the incompleteness warning type. */
ImmutableList<ImmutableMap<String, Object>> getIncompletenessWarnings() {
if (incompletenessWarningType() == IncompletenessWarningType.TRUNCATED) {
return TRUNCATION_NOTICES;
}
if (incompletenessWarningType() == IncompletenessWarningType.MIGHT_BE_INCOMPLETE) {
return POSSIBLY_INCOMPLETE_NOTICES;
}
return ImmutableList.<ImmutableMap<String, Object>>of();
}
} }

View file

@ -631,7 +631,7 @@ public class RdapDomainSearchActionTest {
String hostName = String.format("ns%d.%s", i, mainDomainName); String hostName = String.format("ns%d.%s", i, mainDomainName);
subordinateHostsBuilder.add(hostName); subordinateHostsBuilder.add(hostName);
HostResource host = makeAndPersistHostResource( HostResource host = makeAndPersistHostResource(
hostName, String.format("5.5.5.%d", i), clock.nowUtc().minusYears(1)); hostName, String.format("5.5.%d.%d", 5 + i / 250, i % 250), clock.nowUtc().minusYears(1));
hostKeysBuilder.add(Key.create(host)); hostKeysBuilder.add(Key.create(host));
} }
ImmutableSet<Key<HostResource>> hostKeys = hostKeysBuilder.build(); ImmutableSet<Key<HostResource>> hostKeys = hostKeysBuilder.build();
@ -1066,6 +1066,23 @@ public class RdapDomainSearchActionTest {
assertThat(response.getStatus()).isEqualTo(200); assertThat(response.getStatus()).isEqualTo(200);
} }
@Test
public void testNameserverMatch_incompleteResultsSet() throws Exception {
createManyDomainsAndHosts(2, 1, 2500);
assertThat(generateActualJson(RequestType.NS_LDH_NAME, "ns*.domain1.lol"))
.isEqualTo(readMultiDomainFile(
"rdap_incomplete_domains.json",
"domain1.lol",
"41-LOL",
"domain2.lol",
"42-LOL",
"domain3.lol",
"43-LOL",
"domain4.lol",
"44-LOL"));
assertThat(response.getStatus()).isEqualTo(200);
}
@Test @Test
public void testAddressMatchV4Address_foundMultiple() throws Exception { public void testAddressMatchV4Address_foundMultiple() throws Exception {
assertThat(generateActualJson(RequestType.NS_IP, "1.2.3.4")) assertThat(generateActualJson(RequestType.NS_IP, "1.2.3.4"))

View file

@ -0,0 +1,129 @@
{
"domainSearchResults":[
{
"ldhName":"domain1.lol",
"status":[
"client delete prohibited",
"client renew prohibited",
"client transfer prohibited",
"server update prohibited"
],
"remarks":[
{
"title":"Incomplete Data",
"type":"object truncated due to unexplainable reasons",
"description":[
"Summary data only. For complete data, send a specific query for the object."
]
}
],
"handle":"13C5-LOL",
"links":[
{
"value":"https://example.com/rdap/domain/domain1.lol",
"type":"application/rdap+json",
"rel":"self",
"href":"https://example.com/rdap/domain/domain1.lol"
}
],
"objectClassName":"domain"
},
{
"ldhName":"domain2.lol",
"status":[
"client delete prohibited",
"client renew prohibited",
"client transfer prohibited",
"server update prohibited"
],
"remarks":[
{
"title":"Incomplete Data",
"type":"object truncated due to unexplainable reasons",
"description":[
"Summary data only. For complete data, send a specific query for the object."
]
}
],
"handle":"13C6-LOL",
"links":[
{
"value":"https://example.com/rdap/domain/domain2.lol",
"type":"application/rdap+json",
"rel":"self",
"href":"https://example.com/rdap/domain/domain2.lol"
}
],
"objectClassName":"domain"
}
],
"remarks":[
{
"description":[
"This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars version 1.0"
]
},
{
"title":"EPP Status Codes",
"description":[
"For more information on domain status codes, please visit https://icann.org/epp"
],
"links":[
{
"value":"https://icann.org/epp",
"type":"text/html",
"rel":"alternate",
"href":"https://icann.org/epp"
}
]
},
{
"description":[
"URL of the ICANN Whois Inaccuracy Complaint Form: https://www.icann.org/wicf"
],
"links":[
{
"value":"https://www.icann.org/wicf",
"type":"text/html",
"rel":"alternate",
"href":"https://www.icann.org/wicf"
}
]
}
],
"rdapConformance":[
"rdap_level_0"
],
"notices":[
{
"title":"Search Policy",
"type":"result set truncated due to unexplainable reasons",
"description":[
"Search results may contain incomplete information due to first-stage query limits."
]
},
{
"title":"RDAP Terms of Service",
"description":[
"By querying our Domain Database, you are agreeing to comply with these terms so please read them carefully.",
"Any information provided is 'as is' without any guarantee of accuracy.",
"Please do not misuse the Domain Database. It is intended solely for query-based access.",
"Don't use the Domain Database to allow, enable, or otherwise support the transmission of mass unsolicited, commercial advertising or solicitations.",
"Don't access our Domain Database through the use of high volume, automated electronic processes that send queries or data to the systems of any ICANN-accredited registrar.",
"You may only use the information contained in the Domain Database for lawful purposes.",
"Do not compile, repackage, disseminate, or otherwise use the information contained in the Domain Database in its entirety, or in any substantial portion, without our prior written permission.",
"We may retain certain details about queries to our Domain Database for the purposes of detecting and preventing misuse.",
"We reserve the right to restrict or deny your access to the database if we suspect that you have failed to comply with these terms.",
"We reserve the right to modify this agreement at any time."
],
"links":[
{
"value":"https://example.com/rdap/help/tos",
"type":"text/html",
"rel":"alternate",
"href":"https://www.registry.tld/about/rdap/tos.html"
}
]
}
]
}