Add administrator privileges to RDAP

RDAP filters some contact data when the request is not authenticated as coming from a user associated with the registrar owning the contact. This CL adds an exception for admin users, which for the App Engine Users API are defined as App Engine project viewers. This means that the registry team will always get all information when logged in. This will also be useful when building tools that use RDAP output.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=168293820
This commit is contained in:
mountford 2017-09-11 15:18:43 -07:00 committed by jianglai
parent 211d89695a
commit 5b444d93fb
12 changed files with 152 additions and 40 deletions

View file

@ -150,20 +150,23 @@ public abstract class RdapActionBase implements Runnable {
}
}
Optional<String> getLoggedInClientId() {
RdapAuthorization getAuthorization() {
if (!authResult.userAuthInfo().isPresent()) {
return Optional.<String>absent();
return RdapAuthorization.PUBLIC_AUTHORIZATION;
}
UserAuthInfo userAuthInfo = authResult.userAuthInfo().get();
if (userAuthInfo.isUserAdmin()) {
return RdapAuthorization.ADMINISTRATOR_AUTHORIZATION;
}
if (!sessionUtils.checkRegistrarConsoleLogin(request, userAuthInfo)) {
return Optional.<String>absent();
return RdapAuthorization.PUBLIC_AUTHORIZATION;
}
String clientId = sessionUtils.getRegistrarClientId(request);
Optional<Registrar> registrar = Registrar.loadByClientIdCached(clientId);
if (!registrar.isPresent()) {
return Optional.<String>absent();
return RdapAuthorization.PUBLIC_AUTHORIZATION;
}
return Optional.of(clientId);
return RdapAuthorization.create(RdapAuthorization.Role.REGISTRAR, clientId);
}
void validateDomainName(String name) {

View file

@ -0,0 +1,62 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.rdap;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import google.registry.model.ImmutableObject;
/** Authorization information for RDAP data access. */
@AutoValue
public abstract class RdapAuthorization extends ImmutableObject {
enum Role {
ADMINISTRATOR,
REGISTRAR,
PUBLIC
}
/** The role to be used for access. */
public abstract Role role();
/** The registrar client IDs for which access is granted (used only if the role is REGISTRAR. */
public abstract ImmutableList<String> clientIds();
static RdapAuthorization create(Role role, String clientId) {
return new AutoValue_RdapAuthorization(role, ImmutableList.of(clientId));
}
static RdapAuthorization create(Role role, ImmutableList<String> clientIds) {
return new AutoValue_RdapAuthorization(role, clientIds);
}
boolean isAuthorizedForClientId(String clientId) {
switch (role()) {
case ADMINISTRATOR:
return true;
case REGISTRAR:
return clientIds().contains(clientId);
default:
return false;
}
}
public static final RdapAuthorization PUBLIC_AUTHORIZATION =
create(Role.PUBLIC, ImmutableList.<String>of());
public static final RdapAuthorization ADMINISTRATOR_AUTHORIZATION =
create(Role.ADMINISTRATOR, ImmutableList.<String>of());
}

View file

@ -70,6 +70,6 @@ public class RdapDomainAction extends RdapActionBase {
rdapWhoisServer,
now,
OutputDataType.FULL,
getLoggedInClientId());
getAuthorization());
}
}

View file

@ -375,12 +375,12 @@ public class RdapDomainSearchAction extends RdapActionBase {
List<DomainResource> domains, boolean isTruncated, DateTime now) {
OutputDataType outputDataType =
(domains.size() > 1) ? OutputDataType.SUMMARY : OutputDataType.FULL;
Optional<String> loggedInClientId = getLoggedInClientId();
RdapAuthorization authorization = getAuthorization();
ImmutableList.Builder<ImmutableMap<String, Object>> jsonBuilder = new ImmutableList.Builder<>();
for (DomainResource domain : domains) {
jsonBuilder.add(
rdapJsonFormatter.makeRdapJsonForDomain(
domain, false, rdapLinkBase, rdapWhoisServer, now, outputDataType, loggedInClientId));
domain, false, rdapLinkBase, rdapWhoisServer, now, outputDataType, authorization));
}
return RdapSearchResults.create(jsonBuilder.build(), isTruncated);
}

View file

@ -94,7 +94,7 @@ public class RdapEntityAction extends RdapActionBase {
rdapWhoisServer,
now,
OutputDataType.FULL,
getLoggedInClientId());
getAuthorization());
}
}
Long ianaIdentifier = Longs.tryParse(pathSearchString);

View file

@ -254,7 +254,7 @@ public class RdapEntitySearchAction extends RdapActionBase {
// There can be more results than our max size, partially because we have two pools to draw from
// (contacts and registrars), and partially because we try to fetch one more than the max size,
// so we can tell whether to display the truncation notification.
Optional<String> loggedInClientId = getLoggedInClientId();
RdapAuthorization authorization = getAuthorization();
List<ImmutableMap<String, Object>> jsonOutputList = new ArrayList<>();
for (ContactResource contact : contacts) {
if (jsonOutputList.size() >= rdapResultSetMaxSize) {
@ -270,7 +270,7 @@ public class RdapEntitySearchAction extends RdapActionBase {
rdapWhoisServer,
now,
outputDataType,
loggedInClientId));
authorization));
}
for (Registrar registrar : registrars) {
if (registrar.isActiveAndPubliclyVisible()) {

View file

@ -444,8 +444,8 @@ public class RdapJsonFormatter {
* port43 field; if null, port43 is not added to the object
* @param now the as-date
* @param outputDataType whether to generate full or summary data
* @param loggedInClientId the logged-in client ID (or null if not logged in); if the requester is
* not logged in as the registrar owning the domain, no contact information is included
* @param authorization the authorization level of the request; if not authorized for the
* registrar owning the domain, no contact information is included
*/
ImmutableMap<String, Object> makeRdapJsonForDomain(
DomainResource domainResource,
@ -454,7 +454,7 @@ public class RdapJsonFormatter {
@Nullable String whoisServer,
DateTime now,
OutputDataType outputDataType,
Optional<String> loggedInClientId) {
RdapAuthorization authorization) {
// Start with the domain-level information.
ImmutableMap.Builder<String, Object> jsonBuilder = new ImmutableMap.Builder<>();
jsonBuilder.put("objectClassName", "domain");
@ -467,8 +467,8 @@ public class RdapJsonFormatter {
jsonBuilder.put("status", makeStatusValueList(domainResource.getStatusValues()));
jsonBuilder.put("links", ImmutableList.of(
makeLink("domain", domainResource.getFullyQualifiedDomainName(), linkBase)));
boolean displayContacts = loggedInClientId.isPresent()
&& loggedInClientId.get().equals(domainResource.getCurrentSponsorClientId());
boolean displayContacts =
authorization.isAuthorizedForClientId(domainResource.getCurrentSponsorClientId());
// If we are outputting all data (not just summary data), also add information about hosts,
// contacts and events (history entries). If we are outputting summary data, instead add a
// remark indicating that fact.
@ -505,7 +505,7 @@ public class RdapJsonFormatter {
null,
now,
outputDataType,
loggedInClientId));
authorization));
}
ImmutableList<Object> entities = entitiesBuilder.build();
if (!entities.isEmpty()) {
@ -644,8 +644,8 @@ public class RdapJsonFormatter {
* port43 field; if null, port43 is not added to the object
* @param now the as-date
* @param outputDataType whether to generate full or summary data
* @param loggedInClientId the logged-in client ID (or null if not logged in); personal contact
* data is only shown if the contact is owned by the logged-in client
* @param authorization the authorization level of the request; personal contact data is only
* shown if the contact is owned by a registrar for which the request is authorized
*/
ImmutableMap<String, Object> makeRdapJsonForContact(
ContactResource contactResource,
@ -655,7 +655,7 @@ public class RdapJsonFormatter {
@Nullable String whoisServer,
DateTime now,
OutputDataType outputDataType,
Optional<String> loggedInClientId) {
RdapAuthorization authorization) {
ImmutableMap.Builder<String, Object> jsonBuilder = new ImmutableMap.Builder<>();
ImmutableList.Builder<ImmutableMap<String, Object>> remarksBuilder
= new ImmutableList.Builder<>();
@ -672,8 +672,7 @@ public class RdapJsonFormatter {
jsonBuilder.put("links",
ImmutableList.of(makeLink("entity", contactResource.getRepoId(), linkBase)));
// If we are logged in as the owner of this contact, create the vCard.
if (loggedInClientId.isPresent()
&& loggedInClientId.get().equals(contactResource.getCurrentSponsorClientId())) {
if (authorization.isAuthorizedForClientId(contactResource.getCurrentSponsorClientId())) {
ImmutableList.Builder<Object> vcardBuilder = new ImmutableList.Builder<>();
vcardBuilder.add(VCARD_ENTRY_VERSION);
PostalInfo postalInfo = contactResource.getInternationalizedPostalInfo();

View file

@ -350,6 +350,23 @@ public class RdapDomainActionTest {
assertThat(response.getStatus()).isEqualTo(200);
}
@Test
public void testValidDomain_asAdministrator_works() throws Exception {
UserAuthInfo adminUserAuthInfo = UserAuthInfo.create(user, true);
action.authResult = AuthResult.create(AuthLevel.USER, adminUserAuthInfo);
when(sessionUtils.checkRegistrarConsoleLogin(request, adminUserAuthInfo)).thenReturn(false);
when(sessionUtils.getRegistrarClientId(request)).thenReturn("noregistrar");
assertJsonEqual(
generateActualJson("cat.lol"),
generateExpectedJsonWithTopLevelEntries(
"cat.lol",
null,
"C-LOL",
ImmutableList.of("4-ROID", "6-ROID", "2-ROID"),
"rdap_domain.json"));
assertThat(response.getStatus()).isEqualTo(200);
}
@Test
public void testValidDomain_notLoggedIn_noContacts() throws Exception {
when(sessionUtils.checkRegistrarConsoleLogin(request, userAuthInfo)).thenReturn(false);

View file

@ -455,17 +455,19 @@ public class RdapDomainSearchActionTest {
}
@Test
public void testDomainMatch_found_notLoggedIn() throws Exception {
when(sessionUtils.checkRegistrarConsoleLogin(request, userAuthInfo)).thenReturn(false);
when(sessionUtils.getRegistrarClientId(request)).thenReturn("evilregistrar");
public void testDomainMatch_found_asAdministrator() throws Exception {
UserAuthInfo adminUserAuthInfo = UserAuthInfo.create(user, true);
action.authResult = AuthResult.create(AuthLevel.USER, adminUserAuthInfo);
when(sessionUtils.checkRegistrarConsoleLogin(request, adminUserAuthInfo)).thenReturn(false);
when(sessionUtils.getRegistrarClientId(request)).thenReturn("noregistrar");
assertThat(generateActualJson(RequestType.NAME, "cat.lol"))
.isEqualTo(
generateExpectedJsonForDomain(
"cat.lol",
null,
"C-LOL",
null,
"rdap_domain_no_contacts_with_remark.json"));
ImmutableList.of("4-ROID", "6-ROID", "2-ROID"),
"rdap_domain.json"));
assertThat(response.getStatus()).isEqualTo(200);
}

View file

@ -235,6 +235,18 @@ public class RdapEntityActionTest {
assertThat(response.getStatus()).isEqualTo(200);
}
@Test
public void testValidRegistrantContact_works_asAdministrator() throws Exception {
UserAuthInfo adminUserAuthInfo = UserAuthInfo.create(user, true);
action.authResult = AuthResult.create(AuthLevel.USER, adminUserAuthInfo);
when(sessionUtils.checkRegistrarConsoleLogin(request, adminUserAuthInfo)).thenReturn(false);
when(sessionUtils.getRegistrarClientId(request)).thenReturn("noregistrar");
assertThat(generateActualJson(registrant.getRepoId())).isEqualTo(
generateExpectedJsonWithTopLevelEntries(
registrant.getRepoId(), "rdap_associated_contact.json"));
assertThat(response.getStatus()).isEqualTo(200);
}
@Test
public void testValidRegistrantContact_works_notLoggedIn() throws Exception {
when(sessionUtils.checkRegistrarConsoleLogin(request, userAuthInfo)).thenReturn(false);

View file

@ -308,6 +308,23 @@ public class RdapEntitySearchActionTest {
assertThat(response.getStatus()).isEqualTo(200);
}
@Test
public void testNameMatch_contactFound_asAdministrator() throws Exception {
UserAuthInfo adminUserAuthInfo = UserAuthInfo.create(user, true);
action.authResult = AuthResult.create(AuthLevel.USER, adminUserAuthInfo);
when(sessionUtils.checkRegistrarConsoleLogin(request, adminUserAuthInfo)).thenReturn(false);
when(sessionUtils.getRegistrarClientId(request)).thenReturn("noregistrar");
assertThat(generateActualJsonWithFullName("Blinky (赤ベイ)"))
.isEqualTo(
generateExpectedJsonForEntity(
"2-ROID",
"Blinky (赤ベイ)",
"blinky@b.tld",
"\"123 Blinky St\", \"Blinkyland\"",
"rdap_contact.json"));
assertThat(response.getStatus()).isEqualTo(200);
}
@Test
public void testNameMatch_contactFound_notLoggedIn() throws Exception {
when(sessionUtils.checkRegistrarConsoleLogin(request, userAuthInfo)).thenReturn(false);

View file

@ -370,7 +370,7 @@ public class RdapJsonFormatterTest {
WHOIS_SERVER,
clock.nowUtc(),
OutputDataType.FULL,
Optional.of("unicoderegistrar")))
RdapAuthorization.create(RdapAuthorization.Role.REGISTRAR, "unicoderegistrar")))
.isEqualTo(loadJson("rdapjson_registrant.json"));
}
@ -385,7 +385,7 @@ public class RdapJsonFormatterTest {
WHOIS_SERVER,
clock.nowUtc(),
OutputDataType.SUMMARY,
Optional.of("unicoderegistrar")))
RdapAuthorization.create(RdapAuthorization.Role.REGISTRAR, "unicoderegistrar")))
.isEqualTo(loadJson("rdapjson_registrant_summary.json"));
}
@ -400,7 +400,7 @@ public class RdapJsonFormatterTest {
WHOIS_SERVER,
clock.nowUtc(),
OutputDataType.FULL,
Optional.<String>absent()))
RdapAuthorization.PUBLIC_AUTHORIZATION))
.isEqualTo(loadJson("rdapjson_registrant_logged_out.json"));
}
@ -415,7 +415,7 @@ public class RdapJsonFormatterTest {
WHOIS_SERVER,
clock.nowUtc(),
OutputDataType.FULL,
Optional.of("unicoderegistrar")))
RdapAuthorization.create(RdapAuthorization.Role.REGISTRAR, "unicoderegistrar")))
.isEqualTo(loadJson("rdapjson_registrant.json"));
}
@ -430,7 +430,7 @@ public class RdapJsonFormatterTest {
WHOIS_SERVER,
clock.nowUtc(),
OutputDataType.FULL,
Optional.of("unicoderegistrar")))
RdapAuthorization.create(RdapAuthorization.Role.REGISTRAR, "unicoderegistrar")))
.isEqualTo(loadJson("rdapjson_registrant_nobase.json"));
}
@ -445,7 +445,7 @@ public class RdapJsonFormatterTest {
WHOIS_SERVER,
clock.nowUtc(),
OutputDataType.FULL,
Optional.of("unicoderegistrar")))
RdapAuthorization.create(RdapAuthorization.Role.REGISTRAR, "unicoderegistrar")))
.isEqualTo(loadJson("rdapjson_admincontact.json"));
}
@ -460,7 +460,7 @@ public class RdapJsonFormatterTest {
WHOIS_SERVER,
clock.nowUtc(),
OutputDataType.FULL,
Optional.of("unicoderegistrar")))
RdapAuthorization.create(RdapAuthorization.Role.REGISTRAR, "unicoderegistrar")))
.isEqualTo(loadJson("rdapjson_techcontact.json"));
}
@ -475,7 +475,7 @@ public class RdapJsonFormatterTest {
WHOIS_SERVER,
clock.nowUtc(),
OutputDataType.FULL,
Optional.of("unicoderegistrar")))
RdapAuthorization.create(RdapAuthorization.Role.REGISTRAR, "unicoderegistrar")))
.isEqualTo(loadJson("rdapjson_rolelesscontact.json"));
}
@ -490,7 +490,7 @@ public class RdapJsonFormatterTest {
WHOIS_SERVER,
clock.nowUtc(),
OutputDataType.FULL,
Optional.of("unicoderegistrar")))
RdapAuthorization.create(RdapAuthorization.Role.REGISTRAR, "unicoderegistrar")))
.isEqualTo(loadJson("rdapjson_unlinkedcontact.json"));
}
@ -503,7 +503,7 @@ public class RdapJsonFormatterTest {
WHOIS_SERVER,
clock.nowUtc(),
OutputDataType.FULL,
Optional.of("unicoderegistrar")))
RdapAuthorization.create(RdapAuthorization.Role.REGISTRAR, "unicoderegistrar")))
.isEqualTo(loadJson("rdapjson_domain_full.json"));
}
@ -516,7 +516,7 @@ public class RdapJsonFormatterTest {
WHOIS_SERVER,
clock.nowUtc(),
OutputDataType.SUMMARY,
Optional.of("unicoderegistrar")))
RdapAuthorization.create(RdapAuthorization.Role.REGISTRAR, "unicoderegistrar")))
.isEqualTo(loadJson("rdapjson_domain_summary.json"));
}
@ -529,7 +529,7 @@ public class RdapJsonFormatterTest {
WHOIS_SERVER,
clock.nowUtc(),
OutputDataType.FULL,
Optional.<String>absent()))
RdapAuthorization.PUBLIC_AUTHORIZATION))
.isEqualTo(loadJson("rdapjson_domain_logged_out.json"));
}
@ -542,7 +542,7 @@ public class RdapJsonFormatterTest {
WHOIS_SERVER,
clock.nowUtc(),
OutputDataType.FULL,
Optional.of("unicoderegistrar")))
RdapAuthorization.create(RdapAuthorization.Role.REGISTRAR, "unicoderegistrar")))
.isEqualTo(loadJson("rdapjson_domain_no_nameservers.json"));
}