mirror of
https://github.com/google/nomulus.git
synced 2025-05-13 07:57:13 +02:00
Change behavior when searching contacts by name
We no longer find contacts by name if the request is not authorized to see the name. Several changes cascade from this. Previously, the code assumed that deleted contacts might still have full names, and therefore be searchable. This is not possible in all cases, because Datastore doesn't have the right index to find deleted contacts by name with a matching registrar. However, luckily, this situation can never occur, because contacts always have their name fields nulled out when they are deleted. So instead, we simply ignore deleted records when searching by name, knowing that none can ever match. The tests were then changed so that deleted records look the way the really will, meaning devoid of personal information. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=172776926
This commit is contained in:
parent
f89ad27e17
commit
ac822053cc
5 changed files with 182 additions and 67 deletions
|
@ -130,17 +130,20 @@ public class RdapEntitySearchAction extends RdapActionBase {
|
|||
* <p>According to RFC 7482 section 6.1, punycode is only used for domain name labels, so we can
|
||||
* assume that entity names are regular unicode.
|
||||
*
|
||||
* <p>Searches for deleted entities are treated like wildcard searches, because they can return
|
||||
* multiple entities.
|
||||
* <p>The includeDeleted flag is ignored when searching for contacts, because contact names are
|
||||
* set to null when the contact is deleted, so a deleted contact can never have a name.
|
||||
*
|
||||
* <p>Since we are restricting access to contact names, we don't want name searches to return
|
||||
* contacts whose names are not visible. That would allow unscrupulous users to query by name
|
||||
* and infer that all returned contacts contain that name string. So we check the authorization
|
||||
* level to determine what to do.
|
||||
*
|
||||
* @see <a href="https://newgtlds.icann.org/sites/default/files/agreements/agreement-approved-09jan14-en.htm">1.6
|
||||
* of Section 4 of the Base Registry Agreement</a>
|
||||
*/
|
||||
private RdapSearchResults searchByName(final RdapSearchPattern partialStringQuery, DateTime now) {
|
||||
// For wildcard searches, and searches that include deleted items, make sure the initial string
|
||||
// is long enough, and don't allow suffixes.
|
||||
if ((partialStringQuery.getHasWildcard() || shouldIncludeDeleted())
|
||||
&& (partialStringQuery.getSuffix() != null)) {
|
||||
// For wildcard searches, make sure the initial string is long enough, and don't allow suffixes.
|
||||
if (partialStringQuery.getHasWildcard() && (partialStringQuery.getSuffix() != null)) {
|
||||
throw new UnprocessableEntityException(
|
||||
partialStringQuery.getHasWildcard()
|
||||
? "Suffixes not allowed in wildcard entity name searches"
|
||||
|
@ -164,19 +167,28 @@ public class RdapEntitySearchAction extends RdapActionBase {
|
|||
.limit(rdapResultSetMaxSize + 1)
|
||||
.collect(toImmutableList());
|
||||
// Get the contact matches and return the results, fetching an additional contact to detect
|
||||
// truncation. If we are including deleted entries, we must fetch more entries, in case some
|
||||
// get excluded due to permissioning.
|
||||
Query<ContactResource> query =
|
||||
queryItems(
|
||||
ContactResource.class,
|
||||
"searchName",
|
||||
partialStringQuery,
|
||||
shouldIncludeDeleted(),
|
||||
shouldIncludeDeleted()
|
||||
? (RESULT_SET_SIZE_SCALING_FACTOR * (rdapResultSetMaxSize + 1))
|
||||
: (rdapResultSetMaxSize + 1));
|
||||
return makeSearchResults(
|
||||
getMatchingResources(query, shouldIncludeDeleted(), now), registrars, now);
|
||||
// truncation. Don't bother searching for contacts by name if the request would not be able to
|
||||
// see any names anyway.
|
||||
RdapResourcesAndIncompletenessWarningType<ContactResource>
|
||||
resourcesAndIncompletenessWarningType;
|
||||
RdapAuthorization authorization = getAuthorization();
|
||||
if (authorization.role() == RdapAuthorization.Role.PUBLIC) {
|
||||
resourcesAndIncompletenessWarningType =
|
||||
RdapResourcesAndIncompletenessWarningType.create(ImmutableList.of());
|
||||
} else {
|
||||
Query<ContactResource> query =
|
||||
queryItems(
|
||||
ContactResource.class,
|
||||
"searchName",
|
||||
partialStringQuery,
|
||||
false,
|
||||
rdapResultSetMaxSize + 1);
|
||||
if (authorization.role() != RdapAuthorization.Role.ADMINISTRATOR) {
|
||||
query = query.filter("currentSponsorClientId in", authorization.clientIds());
|
||||
}
|
||||
resourcesAndIncompletenessWarningType = getMatchingResources(query, false, now);
|
||||
}
|
||||
return makeSearchResults(resourcesAndIncompletenessWarningType, registrars, now);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -185,7 +197,7 @@ public class RdapEntitySearchAction extends RdapActionBase {
|
|||
* <p>Searches for deleted entities are treated like wildcard searches.
|
||||
*
|
||||
* <p>We don't allow suffixes after a wildcard in entity searches. Suffixes are used in domain
|
||||
* searches to specify a TLD, and in nameserver searches to specify an in-bailiwick domain name.
|
||||
* searches to specify a TLD, and in nameserver searches to specify a locally managed domain name.
|
||||
* In both cases, the suffix can be turned into an additional query filter field. For contacts,
|
||||
* there is no equivalent string suffix that can be used as a query filter, so we disallow use.
|
||||
*/
|
||||
|
|
|
@ -19,6 +19,7 @@ import static google.registry.testing.DatastoreHelper.createTld;
|
|||
import static google.registry.testing.DatastoreHelper.persistResource;
|
||||
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.makeDomainResource;
|
||||
import static google.registry.testing.FullFieldsTestEntityHelper.makeHostResource;
|
||||
import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrar;
|
||||
|
@ -148,11 +149,8 @@ public class RdapEntityActionTest {
|
|||
clock.nowUtc(),
|
||||
registrarLol);
|
||||
deletedContact =
|
||||
makeAndPersistContactResource(
|
||||
makeAndPersistDeletedContactResource(
|
||||
"8372808-DEL",
|
||||
"(◕‿◕)",
|
||||
"lol@cat.みんな",
|
||||
ImmutableList.of("2 Smiley Row"),
|
||||
clock.nowUtc().minusYears(1),
|
||||
registrarLol,
|
||||
clock.nowUtc().minusMonths(6));
|
||||
|
@ -400,9 +398,9 @@ public class RdapEntityActionTest {
|
|||
action.includeDeletedParam = Optional.of(true);
|
||||
runSuccessfulTest(
|
||||
deletedContact.getRepoId(),
|
||||
"(◕‿◕)",
|
||||
"active",
|
||||
"\"2 Smiley Row\"",
|
||||
"",
|
||||
"removed",
|
||||
"",
|
||||
false,
|
||||
"rdap_contact_deleted.json");
|
||||
}
|
||||
|
@ -413,9 +411,9 @@ public class RdapEntityActionTest {
|
|||
action.includeDeletedParam = Optional.of(true);
|
||||
runSuccessfulTest(
|
||||
deletedContact.getRepoId(),
|
||||
"(◕‿◕)",
|
||||
"active",
|
||||
"\"2 Smiley Row\"",
|
||||
"",
|
||||
"removed",
|
||||
"",
|
||||
false,
|
||||
"rdap_contact_deleted.json");
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import static google.registry.testing.DatastoreHelper.persistResource;
|
|||
import static google.registry.testing.DatastoreHelper.persistResources;
|
||||
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.makeRegistrar;
|
||||
import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrarContacts;
|
||||
|
@ -48,6 +49,7 @@ import java.util.Optional;
|
|||
import javax.annotation.Nullable;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import org.joda.time.DateTime;
|
||||
import org.json.simple.JSONObject;
|
||||
import org.json.simple.JSONValue;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
|
@ -130,11 +132,8 @@ public class RdapEntitySearchActionTest {
|
|||
clock.nowUtc(),
|
||||
registrarTest);
|
||||
|
||||
makeAndPersistContactResource(
|
||||
makeAndPersistDeletedContactResource(
|
||||
"clyde",
|
||||
"Clyde (愚図た)",
|
||||
"clyde@c.tld",
|
||||
ImmutableList.of("123 Example Blvd <script>"),
|
||||
clock.nowUtc().minusYears(1),
|
||||
registrarDeleted,
|
||||
clock.nowUtc().minusMonths(6));
|
||||
|
@ -219,7 +218,7 @@ public class RdapEntitySearchActionTest {
|
|||
builder.put("rdapConformance", ImmutableList.of("rdap_level_0"));
|
||||
RdapTestHelper.addNotices(builder, "https://example.com/rdap/");
|
||||
RdapTestHelper.addNonDomainBoilerplateRemarks(builder);
|
||||
return builder.build();
|
||||
return new JSONObject(builder.build());
|
||||
}
|
||||
|
||||
private void createManyContactsAndRegistrars(
|
||||
|
@ -421,16 +420,14 @@ public class RdapEntitySearchActionTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testNameMatchContact_found_notLoggedIn() throws Exception {
|
||||
runSuccessfulNameTestWithBlinky(
|
||||
"Blinky (赤ベイ)", "rdap_contact_no_personal_data_with_remark.json");
|
||||
public void testNameMatchContact_notFound_notLoggedIn() throws Exception {
|
||||
runNotFoundNameTest("Blinky (赤ベイ)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNameMatchContact_found_loggedInAsOtherRegistrar() throws Exception {
|
||||
public void testNameMatchContact_notFound_loggedInAsOtherRegistrar() throws Exception {
|
||||
login("2-Registrar");
|
||||
runSuccessfulNameTestWithBlinky(
|
||||
"Blinky (赤ベイ)", "rdap_contact_no_personal_data_with_remark.json");
|
||||
runNotFoundNameTest("Blinky (赤ベイ)");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -475,17 +472,17 @@ public class RdapEntitySearchActionTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testNameMatchContact_found_deletedWhenLoggedInAsSameRegistrar() throws Exception {
|
||||
public void testNameMatchContact_notFound_deletedWhenLoggedInAsSameRegistrar() throws Exception {
|
||||
login("2-Registrar");
|
||||
action.includeDeletedParam = Optional.of(true);
|
||||
runSuccessfulNameTest(
|
||||
"Cl*",
|
||||
"6-ROID",
|
||||
"Clyde (愚図た)",
|
||||
"removed",
|
||||
"clyde@c.tld",
|
||||
"\"123 Example Blvd <script>\"",
|
||||
"rdap_contact_deleted.json");
|
||||
runNotFoundNameTest("Cl*");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNameMatchContact_notFound_deletedWhenLoggedInAsAdmin() throws Exception {
|
||||
loginAsAdmin();
|
||||
action.includeDeletedParam = Optional.of(true);
|
||||
runNotFoundNameTest("Cl*");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -640,6 +637,91 @@ public class RdapEntitySearchActionTest {
|
|||
runNotFoundHandleTest("2-ROID");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleMatchContact_notFound_deleted() throws Exception {
|
||||
login("2-RegistrarTest");
|
||||
runNotFoundHandleTest("6-ROID");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleMatchContact_notFound_deletedWhenLoggedInAsOtherRegistrar()
|
||||
throws Exception {
|
||||
login("2-RegistrarTest");
|
||||
action.includeDeletedParam = Optional.of(true);
|
||||
runNotFoundHandleTest("6-ROID");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleMatchContact_found_deletedWhenLoggedInAsSameRegistrar() throws Exception {
|
||||
login("2-Registrar");
|
||||
action.includeDeletedParam = Optional.of(true);
|
||||
runSuccessfulHandleTest(
|
||||
"6-ROID",
|
||||
"6-ROID",
|
||||
"",
|
||||
"removed",
|
||||
"",
|
||||
"",
|
||||
"rdap_contact_deleted.json");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleMatchContact_found_deletedWhenLoggedInAsAdmin() throws Exception {
|
||||
loginAsAdmin();
|
||||
action.includeDeletedParam = Optional.of(true);
|
||||
runSuccessfulHandleTest(
|
||||
"6-ROID",
|
||||
"6-ROID",
|
||||
"",
|
||||
"removed",
|
||||
"",
|
||||
"",
|
||||
"rdap_contact_deleted.json");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleMatchContact_notFound_deletedWildcard() throws Exception {
|
||||
login("2-RegistrarTest");
|
||||
runNotFoundHandleTest("6-ROI*");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleMatchContact_notFound_deletedWildcardWhenLoggedInAsOtherRegistrar()
|
||||
throws Exception {
|
||||
login("2-RegistrarTest");
|
||||
action.includeDeletedParam = Optional.of(true);
|
||||
runNotFoundHandleTest("6-ROI*");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleMatchContact_found_deletedWildcardWhenLoggedInAsSameRegistrar()
|
||||
throws Exception {
|
||||
login("2-Registrar");
|
||||
action.includeDeletedParam = Optional.of(true);
|
||||
runSuccessfulHandleTest(
|
||||
"6-ROI*",
|
||||
"6-ROID",
|
||||
"",
|
||||
"removed",
|
||||
"",
|
||||
"",
|
||||
"rdap_contact_deleted.json");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleMatchContact_found_deletedWildcardWhenLoggedInAsAdmin() throws Exception {
|
||||
loginAsAdmin();
|
||||
action.includeDeletedParam = Optional.of(true);
|
||||
runSuccessfulHandleTest(
|
||||
"6-ROI*",
|
||||
"6-ROID",
|
||||
"",
|
||||
"removed",
|
||||
"",
|
||||
"",
|
||||
"rdap_contact_deleted.json");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleMatchRegistrar_found() throws Exception {
|
||||
runSuccessfulHandleTest("20", "20", "Yes Virginia <script>", "rdap_registrar.json");
|
||||
|
@ -685,6 +767,12 @@ public class RdapEntitySearchActionTest {
|
|||
runNotFoundHandleTest("2-RO*");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleMatchContact_found_deleted() throws Exception {
|
||||
login("2-RegistrarTest");
|
||||
runSuccessfulHandleTestWithBlinky("2-RO*", "rdap_contact.json");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleMatchContact_notFound_wildcard() throws Exception {
|
||||
runNotFoundHandleTest("20*");
|
||||
|
|
|
@ -32,23 +32,7 @@
|
|||
[
|
||||
"vcard",
|
||||
[
|
||||
["version", {}, "text", "4.0"],
|
||||
["fn", {}, "text", "%FULLNAME%"],
|
||||
["org", {}, "text", "GOOGLE INCORPORATED <script>"],
|
||||
["adr", {}, "text",
|
||||
[
|
||||
"",
|
||||
"",
|
||||
%ADDRESS%,
|
||||
"KOKOMO",
|
||||
"BM",
|
||||
"31337",
|
||||
"United States"
|
||||
]
|
||||
],
|
||||
["tel", {"type" : ["voice"]}, "uri", "tel:+1.2126660420"],
|
||||
["tel", {"type" : ["fax"]}, "uri", "tel:+1.2126660420"],
|
||||
["email", {}, "text", "%EMAIL%"]
|
||||
["version", {}, "text", "4.0"]
|
||||
]
|
||||
]
|
||||
}
|
||||
|
|
|
@ -228,6 +228,25 @@ public final class FullFieldsTestEntityHelper {
|
|||
return builder.build();
|
||||
}
|
||||
|
||||
public static ContactResource makeWipedOutContactResource(
|
||||
String id,
|
||||
@Nullable Registrar registrar,
|
||||
@Nullable DateTime deletionTime) {
|
||||
ContactResource.Builder builder = new ContactResource.Builder()
|
||||
.setContactId(id)
|
||||
.setRepoId(generateNewContactHostRoid())
|
||||
.setCreationTimeForTest(DateTime.parse("2000-10-08T00:45:00Z"));
|
||||
if (registrar != null) {
|
||||
builder
|
||||
.setCreationClientId(registrar.getClientId())
|
||||
.setPersistedCurrentSponsorClientId(registrar.getClientId());
|
||||
}
|
||||
if (deletionTime != null) {
|
||||
builder.setDeletionTime(deletionTime);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public static ContactResource makeAndPersistContactResource(
|
||||
String id,
|
||||
String name,
|
||||
|
@ -284,6 +303,20 @@ public final class FullFieldsTestEntityHelper {
|
|||
return contactResource;
|
||||
}
|
||||
|
||||
public static ContactResource makeAndPersistDeletedContactResource(
|
||||
String id,
|
||||
DateTime creationTime,
|
||||
Registrar registrar,
|
||||
DateTime deletionTime) {
|
||||
ContactResource contactResource =
|
||||
persistResource(makeWipedOutContactResource(id, registrar, deletionTime));
|
||||
persistResource(makeHistoryEntry(
|
||||
contactResource, HistoryEntry.Type.CONTACT_CREATE, null, "created", creationTime));
|
||||
persistResource(makeHistoryEntry(
|
||||
contactResource, HistoryEntry.Type.CONTACT_DELETE, null, "deleted", deletionTime));
|
||||
return contactResource;
|
||||
}
|
||||
|
||||
public static DomainResource makeDomainResource(
|
||||
String domain,
|
||||
@Nullable ContactResource registrant,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue