mirror of
https://github.com/google/nomulus.git
synced 2025-05-14 08:27:14 +02:00
Add RDAP search support for only contacts or only registrars
By default, RDAP entity searches return both contacts and registrars. This CL adds a new query parameter to request only one or the other. Among other benefits, this will allow a future CL to permit wildcard searches that return all registrars. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=181605990
This commit is contained in:
parent
e07d011bc6
commit
716ba726fc
4 changed files with 253 additions and 47 deletions
|
@ -341,6 +341,14 @@ formatted version can be requested by adding an extra parameter:
|
|||
The result is still valid JSON, but with extra whitespace added to align the
|
||||
data on the page.
|
||||
|
||||
### `subtype` parameter <a id="subtype_parameter"></a>
|
||||
|
||||
The subtype parameter is used only for entity searches, to select whether the
|
||||
results should include contacts, registrars or both. If specified, the subtype
|
||||
should be 'all', 'contacts' or 'registrars'. Setting the subtype to 'all'
|
||||
duplicates the normal behavior of returning both. Setting it to 'contacts' or
|
||||
'registrars' causes an entity search to return only contacts or only registrars.
|
||||
|
||||
### Next page links <a id="next_page_links"></a>
|
||||
|
||||
The number of results returned in a domain, nameserver or entity search is
|
||||
|
|
|
@ -86,6 +86,7 @@ public class RdapEntitySearchAction extends RdapSearchActionBase {
|
|||
@Inject Clock clock;
|
||||
@Inject @Parameter("fn") Optional<String> fnParam;
|
||||
@Inject @Parameter("handle") Optional<String> handleParam;
|
||||
@Inject @Parameter("subtype") Optional<String> subtypeParam;
|
||||
@Inject RdapEntitySearchAction() {}
|
||||
|
||||
private enum QueryType {
|
||||
|
@ -93,6 +94,12 @@ public class RdapEntitySearchAction extends RdapSearchActionBase {
|
|||
HANDLE
|
||||
}
|
||||
|
||||
private enum Subtype {
|
||||
ALL,
|
||||
CONTACTS,
|
||||
REGISTRARS
|
||||
}
|
||||
|
||||
private enum CursorType {
|
||||
NONE,
|
||||
CONTACT,
|
||||
|
@ -132,6 +139,18 @@ public class RdapEntitySearchAction extends RdapSearchActionBase {
|
|||
throw new BadRequestException("You must specify either fn=XXXX or handle=YYYY");
|
||||
}
|
||||
|
||||
// Check the subtype.
|
||||
Subtype subtype;
|
||||
if (!subtypeParam.isPresent() || subtypeParam.get().equalsIgnoreCase("all")) {
|
||||
subtype = Subtype.ALL;
|
||||
} else if (subtypeParam.get().equalsIgnoreCase("contacts")) {
|
||||
subtype = Subtype.CONTACTS;
|
||||
} else if (subtypeParam.get().equalsIgnoreCase("registrars")) {
|
||||
subtype = Subtype.REGISTRARS;
|
||||
} else {
|
||||
throw new BadRequestException("Subtype parameter must specify contacts, registrars or all");
|
||||
}
|
||||
|
||||
// Decode the cursor token and extract the prefix and string portions.
|
||||
decodeCursorToken();
|
||||
CursorType cursorType;
|
||||
|
@ -164,6 +183,7 @@ public class RdapEntitySearchAction extends RdapSearchActionBase {
|
|||
recordWildcardType(RdapSearchPattern.create(fnParam.get(), false)),
|
||||
cursorType,
|
||||
cursorQueryString,
|
||||
subtype,
|
||||
now);
|
||||
|
||||
// Search by handle.
|
||||
|
@ -175,6 +195,7 @@ public class RdapEntitySearchAction extends RdapSearchActionBase {
|
|||
searchByHandle(
|
||||
recordWildcardType(RdapSearchPattern.create(handleParam.get(), false)),
|
||||
cursorQueryString,
|
||||
subtype,
|
||||
now);
|
||||
}
|
||||
|
||||
|
@ -221,6 +242,7 @@ public class RdapEntitySearchAction extends RdapSearchActionBase {
|
|||
final RdapSearchPattern partialStringQuery,
|
||||
CursorType cursorType,
|
||||
Optional<String> cursorQueryString,
|
||||
Subtype subtype,
|
||||
DateTime now) {
|
||||
// For wildcard searches, make sure the initial string is long enough, and don't allow suffixes.
|
||||
if (partialStringQuery.getHasWildcard() && (partialStringQuery.getSuffix() != null)) {
|
||||
|
@ -238,8 +260,12 @@ public class RdapEntitySearchAction extends RdapSearchActionBase {
|
|||
: "Initial search string required when searching for deleted entities");
|
||||
}
|
||||
// Get the registrar matches. If we have a registrar cursor, weed out registrars up to and
|
||||
// including the one we ended with last time.
|
||||
ImmutableList<Registrar> registrars =
|
||||
// including the one we ended with last time. We can skip registrars if subtype is CONTACTS.
|
||||
ImmutableList<Registrar> registrars;
|
||||
if (subtype == Subtype.CONTACTS) {
|
||||
registrars = ImmutableList.of();
|
||||
} else {
|
||||
registrars =
|
||||
Streams.stream(Registrar.loadAllCached())
|
||||
.filter(
|
||||
registrar ->
|
||||
|
@ -250,11 +276,16 @@ public class RdapEntitySearchAction extends RdapSearchActionBase {
|
|||
&& shouldBeVisible(registrar))
|
||||
.limit(rdapResultSetMaxSize + 1)
|
||||
.collect(toImmutableList());
|
||||
}
|
||||
// Get the contact matches and return the results, fetching an additional contact to detect
|
||||
// truncation. Don't bother searching for contacts by name if the request would not be able to
|
||||
// see any names anyway. Also, if a registrar cursor is present, we have already moved past the
|
||||
// contacts, and don't need to fetch them this time.
|
||||
// contacts, and don't need to fetch them this time. We can skip contacts if subtype is
|
||||
// REGISTRARS.
|
||||
RdapResultSet<ContactResource> resultSet;
|
||||
if (subtype == Subtype.REGISTRARS) {
|
||||
resultSet = RdapResultSet.create(ImmutableList.of());
|
||||
} else {
|
||||
RdapAuthorization authorization = getAuthorization();
|
||||
if ((authorization.role() == RdapAuthorization.Role.PUBLIC)
|
||||
|| (cursorType == CursorType.REGISTRAR)) {
|
||||
|
@ -273,6 +304,7 @@ public class RdapEntitySearchAction extends RdapSearchActionBase {
|
|||
}
|
||||
resultSet = getMatchingResources(query, false, now, rdapResultSetMaxSize + 1);
|
||||
}
|
||||
}
|
||||
return makeSearchResults(resultSet, registrars, QueryType.FULL_NAME, now);
|
||||
}
|
||||
|
||||
|
@ -289,25 +321,39 @@ public class RdapEntitySearchAction extends RdapSearchActionBase {
|
|||
private RdapSearchResults searchByHandle(
|
||||
final RdapSearchPattern partialStringQuery,
|
||||
Optional<String> cursorQueryString,
|
||||
Subtype subtype,
|
||||
DateTime now) {
|
||||
if (partialStringQuery.getSuffix() != null) {
|
||||
throw new UnprocessableEntityException("Suffixes not allowed in entity handle searches");
|
||||
}
|
||||
// Handle queries without a wildcard (and not including deleted) -- load by ID.
|
||||
if (!partialStringQuery.getHasWildcard() && !shouldIncludeDeleted()) {
|
||||
ContactResource contactResource = ofy().load()
|
||||
ImmutableList<ContactResource> contactResourceList;
|
||||
if (subtype == Subtype.REGISTRARS) {
|
||||
contactResourceList = ImmutableList.of();
|
||||
} else {
|
||||
ContactResource contactResource =
|
||||
ofy()
|
||||
.load()
|
||||
.type(ContactResource.class)
|
||||
.id(partialStringQuery.getInitialString())
|
||||
.now();
|
||||
ImmutableList<ContactResource> contactResourceList =
|
||||
contactResourceList =
|
||||
((contactResource != null) && shouldBeVisible(contactResource, now))
|
||||
? ImmutableList.of(contactResource)
|
||||
: ImmutableList.of();
|
||||
}
|
||||
ImmutableList<Registrar> registrarList;
|
||||
if (subtype == Subtype.CONTACTS) {
|
||||
registrarList = ImmutableList.of();
|
||||
} else {
|
||||
registrarList = getMatchingRegistrars(partialStringQuery.getInitialString());
|
||||
}
|
||||
return makeSearchResults(
|
||||
contactResourceList,
|
||||
IncompletenessWarningType.COMPLETE,
|
||||
contactResourceList.size(),
|
||||
getMatchingRegistrars(partialStringQuery.getInitialString()),
|
||||
registrarList,
|
||||
QueryType.HANDLE,
|
||||
now);
|
||||
// Handle queries with a wildcard (or including deleted), but no suffix. Because the handle
|
||||
|
@ -316,7 +362,7 @@ public class RdapEntitySearchAction extends RdapSearchActionBase {
|
|||
// detect result set truncation.
|
||||
} else {
|
||||
ImmutableList<Registrar> registrars =
|
||||
partialStringQuery.getHasWildcard()
|
||||
((subtype == Subtype.CONTACTS) || partialStringQuery.getHasWildcard())
|
||||
? ImmutableList.of()
|
||||
: getMatchingRegistrars(partialStringQuery.getInitialString());
|
||||
// Get the contact matches and return the results, fetching an additional contact to detect
|
||||
|
@ -324,15 +370,24 @@ public class RdapEntitySearchAction extends RdapSearchActionBase {
|
|||
// get excluded due to permissioning. Any cursor present must be a contact cursor, because we
|
||||
// would never return a registrar for this search.
|
||||
int querySizeLimit = getStandardQuerySizeLimit();
|
||||
Query<ContactResource> query =
|
||||
RdapResultSet<ContactResource> contactResultSet;
|
||||
if (subtype == Subtype.REGISTRARS) {
|
||||
contactResultSet = RdapResultSet.create(ImmutableList.of());
|
||||
} else {
|
||||
contactResultSet =
|
||||
getMatchingResources(
|
||||
queryItemsByKey(
|
||||
ContactResource.class,
|
||||
partialStringQuery,
|
||||
cursorQueryString,
|
||||
getDeletedItemHandling(),
|
||||
querySizeLimit),
|
||||
shouldIncludeDeleted(),
|
||||
now,
|
||||
querySizeLimit);
|
||||
}
|
||||
return makeSearchResults(
|
||||
getMatchingResources(query, shouldIncludeDeleted(), now, querySizeLimit),
|
||||
contactResultSet,
|
||||
registrars,
|
||||
QueryType.HANDLE,
|
||||
now);
|
||||
|
|
|
@ -67,6 +67,12 @@ public final class RdapModule {
|
|||
return RequestParameters.extractOptionalParameter(req, "registrar");
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Parameter("subtype")
|
||||
static Optional<String> provideSubtype(HttpServletRequest req) {
|
||||
return RequestParameters.extractOptionalParameter(req, "subtype");
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Parameter("includeDeleted")
|
||||
static Optional<Boolean> provideIncludeDeleted(HttpServletRequest req) {
|
||||
|
|
|
@ -26,6 +26,7 @@ import static google.registry.testing.DatastoreHelper.persistSimpleResources;
|
|||
import static google.registry.testing.FullFieldsTestEntityHelper.makeAndPersistContactResource;
|
||||
import static google.registry.testing.FullFieldsTestEntityHelper.makeAndPersistDeletedContactResource;
|
||||
import static google.registry.testing.FullFieldsTestEntityHelper.makeContactResource;
|
||||
import static google.registry.testing.FullFieldsTestEntityHelper.makeHistoryEntry;
|
||||
import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrar;
|
||||
import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrarContacts;
|
||||
import static google.registry.testing.TestDataHelper.loadFile;
|
||||
|
@ -40,6 +41,7 @@ import google.registry.model.ImmutableObject;
|
|||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.ofy.Ofy;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.rdap.RdapMetrics.EndpointType;
|
||||
import google.registry.rdap.RdapMetrics.SearchType;
|
||||
import google.registry.rdap.RdapSearchResults.IncompletenessWarningType;
|
||||
|
@ -192,6 +194,7 @@ public class RdapEntitySearchActionTest extends RdapSearchActionTestCase {
|
|||
action.rdapWhoisServer = null;
|
||||
action.fnParam = Optional.empty();
|
||||
action.handleParam = Optional.empty();
|
||||
action.subtypeParam = Optional.empty();
|
||||
action.registrarParam = Optional.empty();
|
||||
action.includeDeletedParam = Optional.empty();
|
||||
action.formatOutputParam = Optional.empty();
|
||||
|
@ -281,16 +284,20 @@ public class RdapEntitySearchActionTest extends RdapSearchActionTestCase {
|
|||
.asBuilder()
|
||||
.setRepoId(String.format("%04d-ROID", i))
|
||||
.build();
|
||||
resourcesBuilder.add(makeHistoryEntry(
|
||||
contact, HistoryEntry.Type.CONTACT_CREATE, null, "created", clock.nowUtc()));
|
||||
resourcesBuilder.add(contact);
|
||||
}
|
||||
persistResources(resourcesBuilder.build());
|
||||
for (int i = 1; i <= numRegistrars; i++) {
|
||||
resourcesBuilder.add(
|
||||
Registrar registrar =
|
||||
makeRegistrar(
|
||||
String.format("registrar%d", i),
|
||||
String.format("Entity %d", i + numContacts),
|
||||
Registrar.State.ACTIVE,
|
||||
300L + i));
|
||||
300L + i);
|
||||
resourcesBuilder.add(registrar);
|
||||
resourcesBuilder.addAll(makeRegistrarContacts(registrar));
|
||||
}
|
||||
persistResources(resourcesBuilder.build());
|
||||
}
|
||||
|
@ -537,6 +544,19 @@ public class RdapEntitySearchActionTest extends RdapSearchActionTestCase {
|
|||
verifyErrorMetrics(Optional.empty(), 422);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidSubtype_rejected() throws Exception {
|
||||
action.subtypeParam = Optional.of("Space Aliens");
|
||||
assertThat(generateActualJsonWithFullName("Blinky (赤ベイ)"))
|
||||
.isEqualTo(
|
||||
generateExpectedJson(
|
||||
"Subtype parameter must specify contacts, registrars or all",
|
||||
"rdap_error_400.json"));
|
||||
assertThat(response.getStatus()).isEqualTo(400);
|
||||
metricSearchType = SearchType.NONE; // Error occurs before search type is set.
|
||||
verifyErrorMetrics(Optional.empty(), 400);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNameMatchContact_found() throws Exception {
|
||||
login("2-RegistrarTest");
|
||||
|
@ -544,6 +564,30 @@ public class RdapEntitySearchActionTest extends RdapSearchActionTestCase {
|
|||
verifyMetrics(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNameMatchContact_found_subtypeAll() throws Exception {
|
||||
login("2-RegistrarTest");
|
||||
action.subtypeParam = Optional.of("aLl");
|
||||
runSuccessfulNameTestWithBlinky("Blinky (赤ベイ)", "rdap_contact.json");
|
||||
verifyMetrics(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNameMatchContact_found_subtypeContacts() throws Exception {
|
||||
login("2-RegistrarTest");
|
||||
action.subtypeParam = Optional.of("cONTACTS");
|
||||
runSuccessfulNameTestWithBlinky("Blinky (赤ベイ)", "rdap_contact.json");
|
||||
verifyMetrics(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNameMatchContact_notFound_subtypeRegistrars() throws Exception {
|
||||
login("2-RegistrarTest");
|
||||
action.subtypeParam = Optional.of("Registrars");
|
||||
runNotFoundNameTest("Blinky (赤ベイ)");
|
||||
verifyErrorMetrics(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNameMatchContact_found_specifyingSameRegistrar() throws Exception {
|
||||
login("2-RegistrarTest");
|
||||
|
@ -653,6 +697,32 @@ public class RdapEntitySearchActionTest extends RdapSearchActionTestCase {
|
|||
verifyMetrics(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNameMatchRegistrar_found_subtypeAll() throws Exception {
|
||||
login("2-RegistrarTest");
|
||||
action.subtypeParam = Optional.of("all");
|
||||
runSuccessfulNameTest(
|
||||
"Yes Virginia <script>", "20", "Yes Virginia <script>", "rdap_registrar.json");
|
||||
verifyMetrics(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNameMatchRegistrar_found_subtypeRegistrars() throws Exception {
|
||||
login("2-RegistrarTest");
|
||||
action.subtypeParam = Optional.of("REGISTRARS");
|
||||
runSuccessfulNameTest(
|
||||
"Yes Virginia <script>", "20", "Yes Virginia <script>", "rdap_registrar.json");
|
||||
verifyMetrics(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNameMatchRegistrar_notFound_subtypeContacts() throws Exception {
|
||||
login("2-RegistrarTest");
|
||||
action.subtypeParam = Optional.of("contacts");
|
||||
runNotFoundNameTest("Yes Virginia <script>");
|
||||
verifyErrorMetrics(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNameMatchRegistrar_found_specifyingSameRegistrar() throws Exception {
|
||||
action.registrarParam = Optional.of("2-Registrar");
|
||||
|
@ -810,6 +880,28 @@ public class RdapEntitySearchActionTest extends RdapSearchActionTestCase {
|
|||
"Entity 6"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNameMatchMix_subtypeContacts() throws Exception {
|
||||
login("2-RegistrarTest");
|
||||
action.subtypeParam = Optional.of("contacts");
|
||||
createManyContactsAndRegistrars(4, 4, registrarTest);
|
||||
rememberWildcardType("Entity *");
|
||||
assertThat(generateActualJsonWithFullName("Entity *"))
|
||||
.isEqualTo(generateExpectedJson("rdap_nontruncated_contacts.json"));
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
verifyMetrics(4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNameMatchMix_subtypeRegistrars() throws Exception {
|
||||
login("2-RegistrarTest");
|
||||
action.subtypeParam = Optional.of("registrars");
|
||||
createManyContactsAndRegistrars(1, 1, registrarTest);
|
||||
runSuccessfulNameTest(
|
||||
"Entity *", "301", "Entity 2", "rdap_registrar.json");
|
||||
verifyMetrics(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNameMatchRegistrar_notFound_inactive() throws Exception {
|
||||
runNotFoundNameTest("No Way");
|
||||
|
@ -879,6 +971,30 @@ public class RdapEntitySearchActionTest extends RdapSearchActionTestCase {
|
|||
verifyMetrics(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleMatchContact_found_subtypeAll() throws Exception {
|
||||
login("2-RegistrarTest");
|
||||
action.subtypeParam = Optional.of("all");
|
||||
runSuccessfulHandleTestWithBlinky("2-ROID", "rdap_contact.json");
|
||||
verifyMetrics(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleMatchContact_found_subtypeContacts() throws Exception {
|
||||
login("2-RegistrarTest");
|
||||
action.subtypeParam = Optional.of("contacts");
|
||||
runSuccessfulHandleTestWithBlinky("2-ROID", "rdap_contact.json");
|
||||
verifyMetrics(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleMatchContact_notFound_subtypeRegistrars() throws Exception {
|
||||
login("2-RegistrarTest");
|
||||
action.subtypeParam = Optional.of("reGistrars");
|
||||
runNotFoundHandleTest("2-ROID");
|
||||
verifyErrorMetrics(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleMatchContact_found_specifyingSameRegistrar() throws Exception {
|
||||
action.registrarParam = Optional.of("2-RegistrarTest");
|
||||
|
@ -992,6 +1108,27 @@ public class RdapEntitySearchActionTest extends RdapSearchActionTestCase {
|
|||
verifyMetrics(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleMatchRegistrar_found_subtypeAll() throws Exception {
|
||||
action.subtypeParam = Optional.of("all");
|
||||
runSuccessfulHandleTest("20", "20", "Yes Virginia <script>", "rdap_registrar.json");
|
||||
verifyMetrics(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleMatchRegistrar_found_subtypeRegistrars() throws Exception {
|
||||
action.subtypeParam = Optional.of("registrars");
|
||||
runSuccessfulHandleTest("20", "20", "Yes Virginia <script>", "rdap_registrar.json");
|
||||
verifyMetrics(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleMatchRegistrar_notFound_subtypeContacts() throws Exception {
|
||||
action.subtypeParam = Optional.of("contacts");
|
||||
runNotFoundHandleTest("20");
|
||||
verifyErrorMetrics(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleMatchRegistrar_found_specifyingSameRegistrar() throws Exception {
|
||||
action.registrarParam = Optional.of("2-Registrar");
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue