Allow admins read/write access to all registrar in web console

This CL removes the "READ vs UPDATE" feature completely. Now anyone with access
has full read+write access.

We still keep track of which role a user has (did they get access "explicitly"
because they are an "allowed access" contact? Or do they have access because
they are admins?) for the logs and UI, and also so we could in the (very near)
future have features only available to admins.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=218169608
This commit is contained in:
guyben 2018-10-22 08:11:10 -07:00 committed by jianglai
parent 2020dcb50f
commit d2ca67460c
12 changed files with 138 additions and 288 deletions

View file

@ -19,13 +19,14 @@ import static google.registry.rdap.RdapAuthorization.Role.PUBLIC;
import static google.registry.rdap.RdapAuthorization.Role.REGISTRAR;
import static google.registry.request.Action.Method.GET;
import static google.registry.request.Action.Method.HEAD;
import static google.registry.ui.server.registrar.AuthenticatedRegistrarAccessor.Role.OWNER;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import com.google.appengine.api.users.User;
import com.google.common.collect.ImmutableSetMultimap;
import google.registry.model.ofy.Ofy;
import google.registry.request.Action;
import google.registry.request.HttpException.ForbiddenException;
import google.registry.request.auth.AuthLevel;
import google.registry.request.auth.AuthResult;
import google.registry.request.auth.UserAuthInfo;
@ -99,22 +100,27 @@ public class RdapActionBaseTestCase<A extends RdapActionBase> {
action.requestMethod = Action.Method.GET;
action.fullServletPath = "https://example.tld/rdap";
action.rdapWhoisServer = null;
logout();
}
protected void login(String clientId) {
when(registrarAccessor.guessClientId()).thenReturn(clientId);
when(registrarAccessor.getAllClientIdWithRoles())
.thenReturn(ImmutableSetMultimap.of(clientId, OWNER));
action.authResult = AUTH_RESULT;
metricRole = REGISTRAR;
}
protected void logout() {
when(registrarAccessor.guessClientId()).thenThrow(new ForbiddenException("not logged in"));
when(registrarAccessor.getAllClientIdWithRoles()).thenReturn(ImmutableSetMultimap.of());
action.authResult = AUTH_RESULT;
metricRole = PUBLIC;
}
protected void loginAsAdmin() {
when(registrarAccessor.guessClientId()).thenReturn("irrelevant");
// when admin, we don't actually check what they have access to - so it doesn't matter what we
// return.
// null isn't actually a legal value, we just want to make sure it's never actually used.
when(registrarAccessor.getAllClientIdWithRoles()).thenReturn(null);
action.authResult = AUTH_RESULT_ADMIN;
metricRole = ADMINISTRATOR;
}

View file

@ -62,8 +62,8 @@ registry.registrar.ConsoleTestUtil.renderConsoleMain = function(
logoutUrl: args.logoutUrl || 'https://logout.url.com',
isAdmin: goog.isDefAndNotNull(args.isAdmin) ? args.isAdmin : true,
clientId: args.clientId || 'ignore',
readWriteClientIds: args.readWriteClientIds || ['readWrite'],
readOnlyClientIds: args.readOnlyClientIds || ['readOnly'],
ownerClientIds: args.ownerClientIds || ['owner'],
adminClientIds: args.adminClientIds || ['admin'],
logoFilename: args.logoFilename || 'logo.png',
productName: args.productName || 'Nomulus',
integrationEmail: args.integrationEmail || 'integration@example.com',

View file

@ -20,8 +20,8 @@ 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 google.registry.ui.server.registrar.AuthenticatedRegistrarAccessor.AccessType.READ;
import static google.registry.ui.server.registrar.AuthenticatedRegistrarAccessor.AccessType.UPDATE;
import static google.registry.ui.server.registrar.AuthenticatedRegistrarAccessor.Role.ADMIN;
import static google.registry.ui.server.registrar.AuthenticatedRegistrarAccessor.Role.OWNER;
import static org.mockito.Mockito.mock;
import com.google.appengine.api.users.User;
@ -34,7 +34,6 @@ import google.registry.request.auth.AuthResult;
import google.registry.request.auth.UserAuthInfo;
import google.registry.testing.AppEngineRule;
import google.registry.testing.InjectRule;
import google.registry.ui.server.registrar.AuthenticatedRegistrarAccessor.AccessType;
import java.util.logging.Level;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -99,11 +98,8 @@ public class AuthenticatedRegistrarAccessorTest {
AuthenticatedRegistrarAccessor registrarAccessor =
new AuthenticatedRegistrarAccessor(AUTHORIZED_USER, ADMIN_CLIENT_ID);
assertThat(registrarAccessor.getAllClientIdWithAccess())
.containsExactly(
UPDATE, DEFAULT_CLIENT_ID,
READ, DEFAULT_CLIENT_ID)
.inOrder();
assertThat(registrarAccessor.getAllClientIdWithRoles())
.containsExactly(DEFAULT_CLIENT_ID, OWNER);
}
/** Logged out users don't have access to anything. */
@ -112,9 +108,7 @@ public class AuthenticatedRegistrarAccessorTest {
AuthenticatedRegistrarAccessor registrarAccessor =
new AuthenticatedRegistrarAccessor(NO_USER, ADMIN_CLIENT_ID);
ForbiddenException exception =
assertThrows(ForbiddenException.class, () -> registrarAccessor.getAllClientIdWithAccess());
assertThat(exception).hasMessageThat().contains("Not logged in");
assertThat(registrarAccessor.getAllClientIdWithRoles()).isEmpty();
}
/** Unauthorized users don't have access to anything. */
@ -123,7 +117,7 @@ public class AuthenticatedRegistrarAccessorTest {
AuthenticatedRegistrarAccessor registrarAccessor =
new AuthenticatedRegistrarAccessor(UNAUTHORIZED_USER, ADMIN_CLIENT_ID);
assertThat(registrarAccessor.getAllClientIdWithAccess()).isEmpty();
assertThat(registrarAccessor.getAllClientIdWithRoles()).isEmpty();
}
/** Admins have read/write access to the authorized registrars, AND the admin registrar. */
@ -132,12 +126,12 @@ public class AuthenticatedRegistrarAccessorTest {
AuthenticatedRegistrarAccessor registrarAccessor =
new AuthenticatedRegistrarAccessor(AUTHORIZED_ADMIN, ADMIN_CLIENT_ID);
assertThat(registrarAccessor.getAllClientIdWithAccess())
assertThat(registrarAccessor.getAllClientIdWithRoles())
.containsExactly(
UPDATE, DEFAULT_CLIENT_ID,
READ, DEFAULT_CLIENT_ID,
UPDATE, ADMIN_CLIENT_ID,
READ, ADMIN_CLIENT_ID)
DEFAULT_CLIENT_ID, OWNER,
DEFAULT_CLIENT_ID, ADMIN,
ADMIN_CLIENT_ID, OWNER,
ADMIN_CLIENT_ID, ADMIN)
.inOrder();
}
@ -149,117 +143,65 @@ public class AuthenticatedRegistrarAccessorTest {
AuthenticatedRegistrarAccessor registrarAccessor =
new AuthenticatedRegistrarAccessor(UNAUTHORIZED_ADMIN, ADMIN_CLIENT_ID);
assertThat(registrarAccessor.getAllClientIdWithAccess())
assertThat(registrarAccessor.getAllClientIdWithRoles())
.containsExactly(
UPDATE, ADMIN_CLIENT_ID,
READ, ADMIN_CLIENT_ID,
READ, DEFAULT_CLIENT_ID)
ADMIN_CLIENT_ID, OWNER,
ADMIN_CLIENT_ID, ADMIN,
DEFAULT_CLIENT_ID, ADMIN)
.inOrder();
}
/** Fail loading registrar if user doesn't have access to it. */
@Test
public void testGetRegistrarForUser_readOnly_noAccess_isNotAdmin() {
public void testGetRegistrarForUser_noAccess_isNotAdmin() {
expectGetRegistrarFailure(
DEFAULT_CLIENT_ID,
READ,
UNAUTHORIZED_USER,
"User {user} doesn't have READ access to registrar {clientId}");
}
/** Fail loading registrar if user doesn't have access to it. */
@Test
public void testGetRegistrarForUser_readWrite_noAccess_isNotAdmin() {
expectGetRegistrarFailure(
DEFAULT_CLIENT_ID,
UPDATE,
UNAUTHORIZED_USER,
"User {user} doesn't have UPDATE access to registrar {clientId}");
"{user} doesn't have access to registrar {clientId}");
}
/** Fail loading registrar if there's no user associated with the request. */
@Test
public void testGetRegistrarForUser_readOnly_noUser() {
expectGetRegistrarFailure(DEFAULT_CLIENT_ID, READ, NO_USER, "Not logged in");
public void testGetRegistrarForUser_noUser() {
expectGetRegistrarFailure(DEFAULT_CLIENT_ID, NO_USER, "Not logged in");
}
/** Fail loading registrar if there's no user associated with the request. */
/** Succeed loading registrar if user has access to it. */
@Test
public void testGetRegistrarForUser_readWrite_noUser() {
expectGetRegistrarFailure(DEFAULT_CLIENT_ID, UPDATE, NO_USER, "Not logged in");
}
/** Succeed loading registrar in read-only mode if user has access to it. */
@Test
public void testGetRegistrarForUser_readOnly_hasAccess_isNotAdmin() {
public void testGetRegistrarForUser_hasAccess_isNotAdmin() {
expectGetRegistrarSuccess(
AUTHORIZED_USER, READ, "User {user} has READ access to registrar {clientId}");
AUTHORIZED_USER, "{user} has [OWNER] access to registrar {clientId}");
}
/** Succeed loading registrar in read-write mode if user has access to it. */
/** Succeed loading registrar if admin with access. */
@Test
public void testGetRegistrarForUser_readWrite_hasAccess_isNotAdmin() {
public void testGetRegistrarForUser_hasAccess_isAdmin() {
expectGetRegistrarSuccess(
AUTHORIZED_USER, UPDATE, "User {user} has UPDATE access to registrar {clientId}");
AUTHORIZED_ADMIN, "{user} has [OWNER, ADMIN] access to registrar {clientId}");
}
/** Succeed loading registrar in read-only mode if admin with access. */
/** Succeed loading registrar for admin even if they aren't on the approved contacts list. */
@Test
public void testGetRegistrarForUser_readOnly_hasAccess_isAdmin() {
public void testGetRegistrarForUser_noAccess_isAdmin() {
expectGetRegistrarSuccess(
AUTHORIZED_ADMIN, READ, "User {user} has READ access to registrar {clientId}");
}
/** Succeed loading registrar in read-write mode if admin with access. */
@Test
public void testGetRegistrarForUser_readWrite_hasAccess_isAdmin() {
expectGetRegistrarSuccess(
AUTHORIZED_ADMIN, UPDATE, "User {user} has UPDATE access to registrar {clientId}");
}
/** Succeed loading registrar for read-only when admin isn't on the approved contacts list. */
@Test
public void testGetRegistrarForUser_readOnly_noAccess_isAdmin() {
expectGetRegistrarSuccess(
UNAUTHORIZED_ADMIN, READ, "User {user} has READ access to registrar {clientId}.");
}
/** Fail loading registrar for read-write when admin isn't on the approved contacts list. */
@Test
public void testGetRegistrarForUser_readWrite_noAccess_isAdmin() {
expectGetRegistrarFailure(
DEFAULT_CLIENT_ID,
UPDATE,
UNAUTHORIZED_ADMIN,
"User {user} doesn't have UPDATE access to registrar {clientId}");
UNAUTHORIZED_ADMIN, "{user} has [ADMIN] access to registrar {clientId}.");
}
/** Fail loading registrar even if admin, if registrar doesn't exist. */
@Test
public void testGetRegistrarForUser_readOnly_doesntExist_isAdmin() {
public void testGetRegistrarForUser_doesntExist_isAdmin() {
expectGetRegistrarFailure(
"BadClientId",
READ,
AUTHORIZED_ADMIN,
"User {user} doesn't have READ access to registrar {clientId}");
}
/** Fail loading registrar even if admin, if registrar doesn't exist. */
@Test
public void testGetRegistrarForUser_readWrite_doesntExist_isAdmin() {
expectGetRegistrarFailure(
"BadClientId",
UPDATE,
AUTHORIZED_ADMIN,
"User {user} doesn't have UPDATE access to registrar {clientId}");
"{user} doesn't have access to registrar {clientId}");
}
private void expectGetRegistrarSuccess(
AuthResult authResult, AccessType accessType, String message) {
AuthResult authResult, String message) {
AuthenticatedRegistrarAccessor registrarAccessor =
new AuthenticatedRegistrarAccessor(authResult, ADMIN_CLIENT_ID);
assertThat(registrarAccessor.getRegistrar(DEFAULT_CLIENT_ID, accessType)).isNotNull();
assertThat(registrarAccessor.getRegistrar(DEFAULT_CLIENT_ID)).isNotNull();
assertAboutLogs()
.that(testLogHandler)
.hasLogAtLevelWithMessage(
@ -267,13 +209,13 @@ public class AuthenticatedRegistrarAccessorTest {
}
private void expectGetRegistrarFailure(
String clientId, AccessType accessType, AuthResult authResult, String message) {
String clientId, AuthResult authResult, String message) {
AuthenticatedRegistrarAccessor registrarAccessor =
new AuthenticatedRegistrarAccessor(authResult, ADMIN_CLIENT_ID);
ForbiddenException exception =
assertThrows(
ForbiddenException.class, () -> registrarAccessor.getRegistrar(clientId, accessType));
ForbiddenException.class, () -> registrarAccessor.getRegistrar(clientId));
assertThat(exception).hasMessageThat().contains(formatMessage(message, authResult, clientId));
}
@ -290,8 +232,7 @@ public class AuthenticatedRegistrarAccessorTest {
/** If a user doesn't have access to any registrars, guess returns nothing. */
@Test
public void testGuessClientIdForUser_noAccess_isNotAdmin() {
expectGuessRegistrarFailure(
UNAUTHORIZED_USER, "User {user} isn't associated with any registrar");
expectGuessRegistrarFailure(UNAUTHORIZED_USER, "{user} isn't associated with any registrar");
}
/**

View file

@ -17,8 +17,8 @@ package google.registry.ui.server.registrar;
import static com.google.common.net.HttpHeaders.LOCATION;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.testing.DatastoreHelper.loadRegistrar;
import static google.registry.ui.server.registrar.AuthenticatedRegistrarAccessor.AccessType.READ;
import static google.registry.ui.server.registrar.AuthenticatedRegistrarAccessor.AccessType.UPDATE;
import static google.registry.ui.server.registrar.AuthenticatedRegistrarAccessor.Role.ADMIN;
import static google.registry.ui.server.registrar.AuthenticatedRegistrarAccessor.Role.OWNER;
import static javax.servlet.http.HttpServletResponse.SC_MOVED_TEMPORARILY;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ -79,16 +79,15 @@ public class ConsoleUiActionTest {
action.paramClientId = Optional.empty();
AuthResult authResult = AuthResult.create(AuthLevel.USER, UserAuthInfo.create(user, false));
action.authResult = authResult;
when(registrarAccessor.getRegistrar("TheRegistrar", READ))
when(registrarAccessor.getRegistrar("TheRegistrar"))
.thenReturn(loadRegistrar("TheRegistrar"));
when(registrarAccessor.getAllClientIdWithAccess())
when(registrarAccessor.getAllClientIdWithRoles())
.thenReturn(
ImmutableSetMultimap.of(
UPDATE, "TheRegistrar",
READ, "TheRegistrar",
UPDATE, "ReadWriteRegistrar",
READ, "ReadWriteRegistrar",
READ, "ReadOnlyRegistrar"));
"TheRegistrar", OWNER,
"OtherRegistrar", OWNER,
"OtherRegistrar", ADMIN,
"AdminRegistrar", ADMIN));
when(registrarAccessor.guessClientId()).thenCallRealMethod();
// Used for error message in guessClientId
registrarAccessor.authResult = authResult;
@ -128,7 +127,7 @@ public class ConsoleUiActionTest {
@Test
public void testUserDoesntHaveAccessToAnyRegistrar_showsWhoAreYouPage() {
when(registrarAccessor.getAllClientIdWithAccess()).thenReturn(ImmutableSetMultimap.of());
when(registrarAccessor.getAllClientIdWithRoles()).thenReturn(ImmutableSetMultimap.of());
action.run();
assertThat(response.getPayload()).contains("<h1>You need permission</h1>");
assertThat(response.getPayload()).contains("not associated with Nomulus.");
@ -155,7 +154,7 @@ public class ConsoleUiActionTest {
@Test
public void testSettingClientId_notAllowed_showsNeedPermissionPage() {
action.paramClientId = Optional.of("OtherClientId");
when(registrarAccessor.getRegistrar("OtherClientId", READ))
when(registrarAccessor.getRegistrar("OtherClientId"))
.thenThrow(new ForbiddenException("forbidden"));
action.run();
assertThat(response.getPayload()).contains("<h1>You need permission</h1>");
@ -165,7 +164,7 @@ public class ConsoleUiActionTest {
@Test
public void testSettingClientId_allowed_showsRegistrarConsole() {
action.paramClientId = Optional.of("OtherClientId");
when(registrarAccessor.getRegistrar("OtherClientId", READ))
when(registrarAccessor.getRegistrar("OtherClientId"))
.thenReturn(loadRegistrar("TheRegistrar"));
action.run();
assertThat(response.getPayload()).contains("Registrar Console");
@ -176,7 +175,7 @@ public class ConsoleUiActionTest {
public void testUserHasAccessAsTheRegistrar_showsClientIdChooser() {
action.run();
assertThat(response.getPayload()).contains("<option value=\"TheRegistrar\" selected>");
assertThat(response.getPayload()).contains("<option value=\"ReadWriteRegistrar\">");
assertThat(response.getPayload()).contains("<option value=\"ReadOnlyRegistrar\">");
assertThat(response.getPayload()).contains("<option value=\"OtherRegistrar\">");
assertThat(response.getPayload()).contains("<option value=\"AdminRegistrar\">");
}
}

View file

@ -101,18 +101,6 @@ public class RegistrarSettingsActionTest extends RegistrarSettingsActionTestCase
"results", asList(loadRegistrar(CLIENT_ID).toJsonMap()));
}
/** This is the default read test for the registrar settings actions. */
@Test
public void testSuccess_readRegistrarInfo_authorizedReadOnly() {
setUserReadOnlyAccess();
Map<String, Object> response = action.handleJsonRequest(ImmutableMap.of("id", CLIENT_ID));
assertThat(response)
.containsExactly(
"status", "SUCCESS",
"message", "Success",
"results", asList(loadRegistrar(CLIENT_ID).toJsonMap()));
}
@Test
public void testUpdate_emptyJsonObject_errorLastUpdateTimeFieldRequired() {
Map<String, Object> response = action.handleJsonRequest(ImmutableMap.of(
@ -169,20 +157,6 @@ public class RegistrarSettingsActionTest extends RegistrarSettingsActionTestCase
assertNoTasksEnqueued("sheet");
}
@Test
public void testFailure_updateRegistrarInfo_readOnlyAccess() {
setUserReadOnlyAccess();
Map<String, Object> response = action.handleJsonRequest(ImmutableMap.of(
"op", "update",
"id", CLIENT_ID,
"args", ImmutableMap.of("lastUpdateTime", getLastUpdateTime())));
assertThat(response).containsExactly(
"status", "ERROR",
"results", ImmutableList.of(),
"message", "forbidden test error");
assertNoTasksEnqueued("sheet");
}
@Test
public void testUpdate_badEmail_errorEmailField() {
Map<String, Object> response = action.handleJsonRequest(ImmutableMap.of(

View file

@ -18,8 +18,6 @@ import static google.registry.config.RegistryConfig.getGSuiteOutgoingEmailAddres
import static google.registry.config.RegistryConfig.getGSuiteOutgoingEmailDisplayName;
import static google.registry.security.JsonHttpTestUtils.createJsonPayload;
import static google.registry.testing.DatastoreHelper.loadRegistrar;
import static google.registry.ui.server.registrar.AuthenticatedRegistrarAccessor.AccessType.READ;
import static google.registry.ui.server.registrar.AuthenticatedRegistrarAccessor.AccessType.UPDATE;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ -105,41 +103,24 @@ public class RegistrarSettingsActionTestCase {
when(rsp.getWriter()).thenReturn(new PrintWriter(writer));
when(req.getContentType()).thenReturn("application/json");
when(req.getReader()).thenReturn(createJsonPayload(ImmutableMap.of("op", "read")));
// We set the default user to one with read-write access, as that's the most common test case.
// When we want to specifically check read-only or unauthorized, we can switch the user here.
setUserReadWriteAccess();
// We set the default to a user with access, as that's the most common test case. When we want
// to specifically check a user without access, we can switch user for that specific test.
setUserWithAccess();
}
/** Sets registrarAccessor.getRegistrar to succeed for all AccessTypes. */
protected void setUserReadWriteAccess() {
protected void setUserWithAccess() {
action.registrarAccessor = mock(AuthenticatedRegistrarAccessor.class);
when(action.registrarAccessor.getRegistrar(CLIENT_ID, READ))
when(action.registrarAccessor.getRegistrar(CLIENT_ID))
.thenAnswer(x -> loadRegistrar(CLIENT_ID));
when(action.registrarAccessor.getRegistrar(CLIENT_ID, UPDATE))
.thenAnswer(x -> loadRegistrar(CLIENT_ID));
}
/** Sets registrarAccessor.getRegistrar to only succeed for READ. */
protected void setUserReadOnlyAccess() {
action.registrarAccessor = mock(AuthenticatedRegistrarAccessor.class);
when(action.registrarAccessor.getRegistrar(CLIENT_ID, READ))
.thenAnswer(x -> loadRegistrar(CLIENT_ID));
when(action.registrarAccessor.getRegistrar(CLIENT_ID, UPDATE))
.thenThrow(new ForbiddenException("forbidden test error"));
}
/** Sets registrarAccessor.getRegistrar to always fail. */
protected void setUserWithoutAccess() {
action.registrarAccessor = mock(AuthenticatedRegistrarAccessor.class);
when(action.registrarAccessor.getRegistrar(CLIENT_ID, READ))
.thenThrow(new ForbiddenException("forbidden test error"));
when(action.registrarAccessor.getRegistrar(CLIENT_ID, UPDATE))
when(action.registrarAccessor.getRegistrar(CLIENT_ID))
.thenThrow(new ForbiddenException("forbidden test error"));
}
}