google-nomulus/javatests/google/registry/request/auth/AuthenticatedRegistrarAccessorTest.java
guyben 19b7a7b3ec Allow only OWNERs to change owner-related data on registrar console
The console will have 2 different "updatable things":
- only ADMINs (GAE-admins and users in the support G-Suite group) can change the things in the "admin settings" tab (currently just the allowed TLDs)
- only OWNERs can change things from the other tabs: WHOIS info, certificates, whitelisted IPs, contacts etc.

Also, all ADMINs are now OWNERS of "non-REAL" registrars. Meaning - we're only
preventing ADMINs from editing "REAL" registrars (usually in production).

Specifically, OTE registrars on sandbox are NOT "REAL", meaning ADMINS will
still be able to update them.

This only changes the backend (registrar-settings endpoint). As-is, the console
website will still make ADMINs *think* they can change everything, but if they
try - they will get an error.

Changing the frontend will happen in the next CL - because I want to get this
out this release cycle and getting JS reviewed takes a long time :(

TESTED=deployed to alpha, and saw I can't update fields even as admin on REAL
registrars, but could change it on non-REAL registrars. Also checked that I can
update the allowed TLDs on REAL registrars

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=222698270
2018-12-03 18:56:28 -05:00

359 lines
14 KiB
Java

// Copyright 2018 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.request.auth;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.request.auth.AuthenticatedRegistrarAccessor.Role.ADMIN;
import static google.registry.request.auth.AuthenticatedRegistrarAccessor.Role.OWNER;
import static google.registry.testing.AppEngineRule.THE_REGISTRAR_GAE_USER_ID;
import static google.registry.testing.DatastoreHelper.loadRegistrar;
import static google.registry.testing.DatastoreHelper.persistResource;
import static google.registry.testing.JUnitBackports.assertThrows;
import static google.registry.testing.LogsSubject.assertAboutLogs;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import com.google.appengine.api.users.User;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.flogger.LoggerConfig;
import com.google.common.testing.NullPointerTester;
import com.google.common.testing.TestLogHandler;
import google.registry.groups.GroupsConnection;
import google.registry.model.registrar.Registrar;
import google.registry.request.auth.AuthenticatedRegistrarAccessor.RegistrarAccessDeniedException;
import google.registry.testing.AppEngineRule;
import google.registry.testing.InjectRule;
import java.util.logging.Level;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Unit tests for {@link AuthenticatedRegistrarAccessor}. */
@RunWith(JUnit4.class)
public class AuthenticatedRegistrarAccessorTest {
@Rule public final AppEngineRule appEngine = AppEngineRule.builder().withDatastore().build();
@Rule public final InjectRule inject = new InjectRule();
private final HttpServletRequest req = mock(HttpServletRequest.class);
private final HttpServletResponse rsp = mock(HttpServletResponse.class);
private final GroupsConnection groupsConnection = mock(GroupsConnection.class);
private final TestLogHandler testLogHandler = new TestLogHandler();
private static final AuthResult USER = createAuthResult(false);
private static final AuthResult GAE_ADMIN = createAuthResult(true);
private static final AuthResult NO_USER = AuthResult.create(AuthLevel.NONE);
private static final String SUPPORT_GROUP = "support@registry.example";
/** Client ID of a REAL registrar with a RegistrarContact for USER and GAE_ADMIN. */
private static final String CLIENT_ID_WITH_CONTACT = "TheRegistrar";
/** Client ID of a REAL registrar without a RegistrarContact. */
private static final String REAL_CLIENT_ID_WITHOUT_CONTACT = "NewRegistrar";
/** Client ID of an OTE registrar without a RegistrarContact. */
private static final String OTE_CLIENT_ID_WITHOUT_CONTACT = "OteRegistrar";
/** Client ID of the Admin registrar without a RegistrarContact. */
private static final String ADMIN_CLIENT_ID = "AdminRegistrar";
/**
* Creates an AuthResult for a fake user.
*
* The user will be a RegistrarContact for "TheRegistrar", but not for "NewRegistrar".
*
* @param isAdmin if true, the user is an administrator for the app-engine project.
*/
private static AuthResult createAuthResult(boolean isAdmin) {
return AuthResult.create(
AuthLevel.USER,
UserAuthInfo.create(
new User(
String.format(
"%s@gmail.com",
isAdmin ? "admin" : "user"),
"gmail.com",
THE_REGISTRAR_GAE_USER_ID),
isAdmin));
}
@Before
public void before() {
LoggerConfig.getConfig(AuthenticatedRegistrarAccessor.class).addHandler(testLogHandler);
// persistResource(loadRegistrar(ADMIN_CLIENT_ID));
persistResource(
loadRegistrar(REAL_CLIENT_ID_WITHOUT_CONTACT)
.asBuilder()
.setClientId(OTE_CLIENT_ID_WITHOUT_CONTACT)
.setType(Registrar.Type.OTE)
.setIanaIdentifier(null)
.build());
persistResource(
loadRegistrar(REAL_CLIENT_ID_WITHOUT_CONTACT)
.asBuilder()
.setClientId(ADMIN_CLIENT_ID)
.setType(Registrar.Type.OTE)
.setIanaIdentifier(null)
.build());
when(groupsConnection.isMemberOfGroup(any(), any())).thenReturn(false);
}
@After
public void after() {
LoggerConfig.getConfig(AuthenticatedRegistrarAccessor.class).removeHandler(testLogHandler);
}
/** Users are owners for registrars if and only if they are in the contacts for that registrar. */
@Test
public void getAllClientIdWithAccess_user() {
AuthenticatedRegistrarAccessor registrarAccessor =
new AuthenticatedRegistrarAccessor(
USER, ADMIN_CLIENT_ID, SUPPORT_GROUP, groupsConnection);
assertThat(registrarAccessor.getAllClientIdWithRoles())
.containsExactly(CLIENT_ID_WITH_CONTACT, OWNER);
}
/** Logged out users don't have access to anything. */
@Test
public void getAllClientIdWithAccess_loggedOutUser() {
AuthenticatedRegistrarAccessor registrarAccessor =
new AuthenticatedRegistrarAccessor(
NO_USER, ADMIN_CLIENT_ID, SUPPORT_GROUP, groupsConnection);
assertThat(registrarAccessor.getAllClientIdWithRoles()).isEmpty();
}
/**
* GAE admins have admin access to everything.
*
* <p>They also have OWNER access if they are in the RegistrarContacts.
*
* <p>They also have OWNER access to the Admin Registrar.
*
* <p>They also have OWNER access to non-REAL Registrars.
*
* <p>(in other words - they don't have OWNER access only to REAL registrars owned by others)
*/
@Test
public void getAllClientIdWithAccess_gaeAdmin() {
AuthenticatedRegistrarAccessor registrarAccessor =
new AuthenticatedRegistrarAccessor(
GAE_ADMIN, ADMIN_CLIENT_ID, SUPPORT_GROUP, groupsConnection);
assertThat(registrarAccessor.getAllClientIdWithRoles())
.containsExactly(
CLIENT_ID_WITH_CONTACT, ADMIN,
CLIENT_ID_WITH_CONTACT, OWNER,
REAL_CLIENT_ID_WITHOUT_CONTACT, ADMIN,
OTE_CLIENT_ID_WITHOUT_CONTACT, ADMIN,
OTE_CLIENT_ID_WITHOUT_CONTACT, OWNER,
ADMIN_CLIENT_ID, ADMIN,
ADMIN_CLIENT_ID, OWNER);
}
/**
* Users in support group have admin access to everything.
*
* <p>They also have OWNER access if they are in the RegistrarContacts.
*
* <p>They also have OWNER access to the Admin Registrar.
*
* <p>They also have OWNER access to non-REAL Registrars.
*
* <p>(in other words - they don't have OWNER access only to REAL registrars owned by others)
*/
@Test
public void getAllClientIdWithAccess_userInSupportGroup() {
when(groupsConnection.isMemberOfGroup("user@gmail.com", SUPPORT_GROUP)).thenReturn(true);
AuthenticatedRegistrarAccessor registrarAccessor =
new AuthenticatedRegistrarAccessor(
USER, ADMIN_CLIENT_ID, SUPPORT_GROUP, groupsConnection);
assertThat(registrarAccessor.getAllClientIdWithRoles())
.containsExactly(
CLIENT_ID_WITH_CONTACT, ADMIN,
CLIENT_ID_WITH_CONTACT, OWNER,
REAL_CLIENT_ID_WITHOUT_CONTACT, ADMIN,
OTE_CLIENT_ID_WITHOUT_CONTACT, ADMIN,
OTE_CLIENT_ID_WITHOUT_CONTACT, OWNER,
ADMIN_CLIENT_ID, ADMIN,
ADMIN_CLIENT_ID, OWNER);
}
/** Empty Support group email - skips check. */
@Test
public void getAllClientIdWithAccess_emptySupportEmail_works() {
AuthenticatedRegistrarAccessor registrarAccessor =
new AuthenticatedRegistrarAccessor(
USER, ADMIN_CLIENT_ID, "", groupsConnection);
verifyNoMoreInteractions(groupsConnection);
assertThat(registrarAccessor.getAllClientIdWithRoles())
.containsExactly(CLIENT_ID_WITH_CONTACT, OWNER);
}
/** Support group check throws - continue anyway. */
@Test
public void getAllClientIdWithAccess_throwingGroupCheck_stillWorks() {
when(groupsConnection.isMemberOfGroup(any(), any())).thenThrow(new RuntimeException("blah"));
AuthenticatedRegistrarAccessor registrarAccessor =
new AuthenticatedRegistrarAccessor(
USER, ADMIN_CLIENT_ID, SUPPORT_GROUP, groupsConnection);
verify(groupsConnection).isMemberOfGroup("user@gmail.com", SUPPORT_GROUP);
assertThat(registrarAccessor.getAllClientIdWithRoles())
.containsExactly(CLIENT_ID_WITH_CONTACT, OWNER);
}
/** Fail loading registrar if user doesn't have access to it. */
@Test
public void testGetRegistrarForUser_noAccess_isNotAdmin() {
expectGetRegistrarFailure(
REAL_CLIENT_ID_WITHOUT_CONTACT,
USER,
"user user@gmail.com doesn't have access to registrar NewRegistrar");
}
/** Fail loading registrar if user doesn't have access to it, even if it's not REAL. */
@Test
public void testGetRegistrarForUser_noAccess_isNotAdmin_notReal() {
expectGetRegistrarFailure(
OTE_CLIENT_ID_WITHOUT_CONTACT,
USER,
"user user@gmail.com doesn't have access to registrar OteRegistrar");
}
/** Fail loading registrar if there's no user associated with the request. */
@Test
public void testGetRegistrarForUser_noUser() {
expectGetRegistrarFailure(
CLIENT_ID_WITH_CONTACT,
NO_USER,
"<logged-out user> doesn't have access to registrar TheRegistrar");
}
/** Succeed loading registrar if user has access to it. */
@Test
public void testGetRegistrarForUser_inContacts_isNotAdmin() throws Exception {
expectGetRegistrarSuccess(
CLIENT_ID_WITH_CONTACT,
USER,
"user user@gmail.com has [OWNER] access to registrar TheRegistrar");
}
/** Succeed loading registrar if admin with access. */
@Test
public void testGetRegistrarForUser_inContacts_isAdmin() throws Exception {
expectGetRegistrarSuccess(
CLIENT_ID_WITH_CONTACT,
GAE_ADMIN,
"admin admin@gmail.com has [OWNER, ADMIN] access to registrar TheRegistrar");
}
/** Succeed loading registrar for admin even if they aren't on the approved contacts list. */
@Test
public void testGetRegistrarForUser_notInContacts_isAdmin() throws Exception {
expectGetRegistrarSuccess(
REAL_CLIENT_ID_WITHOUT_CONTACT,
GAE_ADMIN,
"admin admin@gmail.com has [ADMIN] access to registrar NewRegistrar.");
}
/** Succeed loading non-REAL registrar for admin. */
@Test
public void testGetRegistrarForUser_notInContacts_isAdmin_notReal() throws Exception {
expectGetRegistrarSuccess(
OTE_CLIENT_ID_WITHOUT_CONTACT,
GAE_ADMIN,
"admin admin@gmail.com has [OWNER, ADMIN] access to registrar OteRegistrar.");
}
/** Fail loading registrar even if admin, if registrar doesn't exist. */
@Test
public void testGetRegistrarForUser_doesntExist_isAdmin() {
expectGetRegistrarFailure(
"BadClientId",
GAE_ADMIN,
"admin admin@gmail.com doesn't have access to registrar BadClientId");
}
private void expectGetRegistrarSuccess(String clientId, AuthResult authResult, String message)
throws Exception {
AuthenticatedRegistrarAccessor registrarAccessor =
new AuthenticatedRegistrarAccessor(
authResult, ADMIN_CLIENT_ID, SUPPORT_GROUP, groupsConnection);
// make sure loading the registrar succeeds and returns a value
assertThat(registrarAccessor.getRegistrar(clientId)).isNotNull();
assertAboutLogs().that(testLogHandler).hasLogAtLevelWithMessage(Level.INFO, message);
}
private void expectGetRegistrarFailure(
String clientId, AuthResult authResult, String message) {
AuthenticatedRegistrarAccessor registrarAccessor =
new AuthenticatedRegistrarAccessor(
authResult, ADMIN_CLIENT_ID, SUPPORT_GROUP, groupsConnection);
// make sure getRegistrar fails
RegistrarAccessDeniedException exception =
assertThrows(
RegistrarAccessDeniedException.class, () -> registrarAccessor.getRegistrar(clientId));
assertThat(exception).hasMessageThat().contains(message);
}
/** guessClientIdForUser returns the first clientId in getAllClientIdWithRoles. */
@Test
public void testGuessClientIdForUser_hasAccess_returnsFirst() throws Exception {
AuthenticatedRegistrarAccessor registrarAccessor =
AuthenticatedRegistrarAccessor.createForTesting(
ImmutableSetMultimap.of(
"clientId-1", OWNER,
"clientId-2", OWNER,
"clientId-2", ADMIN));
assertThat(registrarAccessor.guessClientId()).isEqualTo("clientId-1");
}
/** If a user doesn't have access to any registrars, guess fails. */
@Test
public void testGuessClientIdForUser_noAccess_fails() {
AuthenticatedRegistrarAccessor registrarAccessor =
AuthenticatedRegistrarAccessor.createForTesting(ImmutableSetMultimap.of());
assertThat(assertThrows(RegistrarAccessDeniedException.class, registrarAccessor::guessClientId))
.hasMessageThat()
.isEqualTo("TestUserId isn't associated with any registrar");
}
@Test
public void testNullness() {
new NullPointerTester()
.setDefault(HttpServletRequest.class, req)
.setDefault(HttpServletResponse.class, rsp)
.testAllPublicStaticMethods(AuthenticatedRegistrarAccessor.class);
}
}