diff --git a/java/google/registry/rdap/RdapActionBase.java b/java/google/registry/rdap/RdapActionBase.java index 4b345e7b8..06d8d09ad 100644 --- a/java/google/registry/rdap/RdapActionBase.java +++ b/java/google/registry/rdap/RdapActionBase.java @@ -150,20 +150,23 @@ public abstract class RdapActionBase implements Runnable { } } - Optional getLoggedInClientId() { + RdapAuthorization getAuthorization() { if (!authResult.userAuthInfo().isPresent()) { - return Optional.absent(); + return RdapAuthorization.PUBLIC_AUTHORIZATION; } UserAuthInfo userAuthInfo = authResult.userAuthInfo().get(); + if (userAuthInfo.isUserAdmin()) { + return RdapAuthorization.ADMINISTRATOR_AUTHORIZATION; + } if (!sessionUtils.checkRegistrarConsoleLogin(request, userAuthInfo)) { - return Optional.absent(); + return RdapAuthorization.PUBLIC_AUTHORIZATION; } String clientId = sessionUtils.getRegistrarClientId(request); Optional registrar = Registrar.loadByClientIdCached(clientId); if (!registrar.isPresent()) { - return Optional.absent(); + return RdapAuthorization.PUBLIC_AUTHORIZATION; } - return Optional.of(clientId); + return RdapAuthorization.create(RdapAuthorization.Role.REGISTRAR, clientId); } void validateDomainName(String name) { diff --git a/java/google/registry/rdap/RdapAuthorization.java b/java/google/registry/rdap/RdapAuthorization.java new file mode 100644 index 000000000..7e09c3032 --- /dev/null +++ b/java/google/registry/rdap/RdapAuthorization.java @@ -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 clientIds(); + + static RdapAuthorization create(Role role, String clientId) { + return new AutoValue_RdapAuthorization(role, ImmutableList.of(clientId)); + } + + static RdapAuthorization create(Role role, ImmutableList 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.of()); + + public static final RdapAuthorization ADMINISTRATOR_AUTHORIZATION = + create(Role.ADMINISTRATOR, ImmutableList.of()); +} + diff --git a/java/google/registry/rdap/RdapDomainAction.java b/java/google/registry/rdap/RdapDomainAction.java index 34f69da99..356e49f9c 100644 --- a/java/google/registry/rdap/RdapDomainAction.java +++ b/java/google/registry/rdap/RdapDomainAction.java @@ -70,6 +70,6 @@ public class RdapDomainAction extends RdapActionBase { rdapWhoisServer, now, OutputDataType.FULL, - getLoggedInClientId()); + getAuthorization()); } } diff --git a/java/google/registry/rdap/RdapDomainSearchAction.java b/java/google/registry/rdap/RdapDomainSearchAction.java index 4b24bd266..1e15ea868 100644 --- a/java/google/registry/rdap/RdapDomainSearchAction.java +++ b/java/google/registry/rdap/RdapDomainSearchAction.java @@ -375,12 +375,12 @@ public class RdapDomainSearchAction extends RdapActionBase { List domains, boolean isTruncated, DateTime now) { OutputDataType outputDataType = (domains.size() > 1) ? OutputDataType.SUMMARY : OutputDataType.FULL; - Optional loggedInClientId = getLoggedInClientId(); + RdapAuthorization authorization = getAuthorization(); ImmutableList.Builder> 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); } diff --git a/java/google/registry/rdap/RdapEntityAction.java b/java/google/registry/rdap/RdapEntityAction.java index 4a04d5d2a..3fd51f2ed 100644 --- a/java/google/registry/rdap/RdapEntityAction.java +++ b/java/google/registry/rdap/RdapEntityAction.java @@ -94,7 +94,7 @@ public class RdapEntityAction extends RdapActionBase { rdapWhoisServer, now, OutputDataType.FULL, - getLoggedInClientId()); + getAuthorization()); } } Long ianaIdentifier = Longs.tryParse(pathSearchString); diff --git a/java/google/registry/rdap/RdapEntitySearchAction.java b/java/google/registry/rdap/RdapEntitySearchAction.java index cdf52d76a..e3c5eb39b 100644 --- a/java/google/registry/rdap/RdapEntitySearchAction.java +++ b/java/google/registry/rdap/RdapEntitySearchAction.java @@ -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 loggedInClientId = getLoggedInClientId(); + RdapAuthorization authorization = getAuthorization(); List> 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()) { diff --git a/java/google/registry/rdap/RdapJsonFormatter.java b/java/google/registry/rdap/RdapJsonFormatter.java index b001a5e65..d32341ba9 100644 --- a/java/google/registry/rdap/RdapJsonFormatter.java +++ b/java/google/registry/rdap/RdapJsonFormatter.java @@ -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 makeRdapJsonForDomain( DomainResource domainResource, @@ -454,7 +454,7 @@ public class RdapJsonFormatter { @Nullable String whoisServer, DateTime now, OutputDataType outputDataType, - Optional loggedInClientId) { + RdapAuthorization authorization) { // Start with the domain-level information. ImmutableMap.Builder 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 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 makeRdapJsonForContact( ContactResource contactResource, @@ -655,7 +655,7 @@ public class RdapJsonFormatter { @Nullable String whoisServer, DateTime now, OutputDataType outputDataType, - Optional loggedInClientId) { + RdapAuthorization authorization) { ImmutableMap.Builder jsonBuilder = new ImmutableMap.Builder<>(); ImmutableList.Builder> 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 vcardBuilder = new ImmutableList.Builder<>(); vcardBuilder.add(VCARD_ENTRY_VERSION); PostalInfo postalInfo = contactResource.getInternationalizedPostalInfo(); diff --git a/javatests/google/registry/rdap/RdapDomainActionTest.java b/javatests/google/registry/rdap/RdapDomainActionTest.java index d267b9540..4652d9cc8 100644 --- a/javatests/google/registry/rdap/RdapDomainActionTest.java +++ b/javatests/google/registry/rdap/RdapDomainActionTest.java @@ -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); diff --git a/javatests/google/registry/rdap/RdapDomainSearchActionTest.java b/javatests/google/registry/rdap/RdapDomainSearchActionTest.java index 4149e4b3c..55e062f70 100644 --- a/javatests/google/registry/rdap/RdapDomainSearchActionTest.java +++ b/javatests/google/registry/rdap/RdapDomainSearchActionTest.java @@ -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); } diff --git a/javatests/google/registry/rdap/RdapEntityActionTest.java b/javatests/google/registry/rdap/RdapEntityActionTest.java index 62212f1f3..53d3a55d3 100644 --- a/javatests/google/registry/rdap/RdapEntityActionTest.java +++ b/javatests/google/registry/rdap/RdapEntityActionTest.java @@ -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); diff --git a/javatests/google/registry/rdap/RdapEntitySearchActionTest.java b/javatests/google/registry/rdap/RdapEntitySearchActionTest.java index 34c665255..e6b29e011 100644 --- a/javatests/google/registry/rdap/RdapEntitySearchActionTest.java +++ b/javatests/google/registry/rdap/RdapEntitySearchActionTest.java @@ -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); diff --git a/javatests/google/registry/rdap/RdapJsonFormatterTest.java b/javatests/google/registry/rdap/RdapJsonFormatterTest.java index 518aa2a55..e196f8205 100644 --- a/javatests/google/registry/rdap/RdapJsonFormatterTest.java +++ b/javatests/google/registry/rdap/RdapJsonFormatterTest.java @@ -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.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.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")); }