diff --git a/java/google/registry/config/RegistryConfig.java b/java/google/registry/config/RegistryConfig.java index 551a7d184..91500a4ee 100644 --- a/java/google/registry/config/RegistryConfig.java +++ b/java/google/registry/config/RegistryConfig.java @@ -392,6 +392,19 @@ public final class RegistryConfig { return config.gSuite.adminAccountEmailAddress; } + /** + * Returns the email address of the group containing emails of support accounts. + * + *

These accounts will have "ADMIN" access to the registrar console. + * + * @see google.registry.groups.DirectoryGroupsConnection + */ + @Provides + @Config("gSuiteSupportGroupEmailAddress") + public static String provideGSuiteSupportGroupEmailAddress(RegistryConfigSettings config) { + return config.gSuite.supportGroupEmailAddress; + } + /** * Returns the email address(es) that notifications of registrar and/or registrar contact * updates should be sent to, or the empty list if updates should not be sent. diff --git a/java/google/registry/config/RegistryConfigSettings.java b/java/google/registry/config/RegistryConfigSettings.java index 404ce1254..95289073c 100644 --- a/java/google/registry/config/RegistryConfigSettings.java +++ b/java/google/registry/config/RegistryConfigSettings.java @@ -66,6 +66,7 @@ public class RegistryConfigSettings { public String outgoingEmailAddress; public String outgoingEmailDisplayName; public String adminAccountEmailAddress; + public String supportGroupEmailAddress; } /** Configuration options for registry policy. */ diff --git a/java/google/registry/config/files/default-config.yaml b/java/google/registry/config/files/default-config.yaml index b5deec22f..25323259d 100644 --- a/java/google/registry/config/files/default-config.yaml +++ b/java/google/registry/config/files/default-config.yaml @@ -32,6 +32,10 @@ gSuite: # logging in to perform administrative actions, not sending emails. adminAccountEmailAddress: admin@example.com + # Group containing the emails of the support accounts. These accounts will be + # given "ADMIN" role on the registrar console. + supportGroupEmailAddress: support@example.com + registryPolicy: # Repository identifier (ROID) suffix for contacts and hosts. contactAndHostRoidSuffix: ROID diff --git a/java/google/registry/config/files/nomulus-config-production-sample.yaml b/java/google/registry/config/files/nomulus-config-production-sample.yaml index 9d2e9ca44..4d065acbc 100644 --- a/java/google/registry/config/files/nomulus-config-production-sample.yaml +++ b/java/google/registry/config/files/nomulus-config-production-sample.yaml @@ -18,6 +18,7 @@ gSuite: outgoingEmailDisplayName: placeholder outgoingEmailAddress: placeholder adminAccountEmailAddress: placeholder + supportGroupEmailAddress: placeholder registryPolicy: contactAndHostRoidSuffix: placeholder diff --git a/java/google/registry/groups/DirectoryGroupsConnection.java b/java/google/registry/groups/DirectoryGroupsConnection.java index 7c4e0a91a..5cf0f35ed 100644 --- a/java/google/registry/groups/DirectoryGroupsConnection.java +++ b/java/google/registry/groups/DirectoryGroupsConnection.java @@ -46,6 +46,22 @@ public class DirectoryGroupsConnection implements GroupsConnection { private static final String MEMBER_NOT_FOUND_MSG = "Resource Not Found: memberKey"; private static final String MEMBER_ALREADY_EXISTS_MSG = "Member already exists."; + /** + * All possible errors from {@link Directory.Members#get} when an email doesn't belong to a group. + * + *

See {@link #isMemberOfGroup} for details. + * + *

TODO(b/119220829): remove once we transition to using hasMember + * + *

TODO(b/119221854): update error messages if and when they change + */ + private static final ImmutableSet ERROR_MESSAGES_MEMBER_NOT_FOUND = + ImmutableSet.of( + // The given email corresponds to an actual account, but isn't part of this group + "Resource Not Found: memberKey", + // There's no account corresponding to this email + "Missing required field: memberKey"); + private static final FluentLogger logger = FluentLogger.forEnclosingClass(); private static final Groups defaultGroupPermissions = getDefaultGroupPermissions(); @@ -163,4 +179,45 @@ public class DirectoryGroupsConnection implements GroupsConnection { } } } + + @Override + public boolean isMemberOfGroup(String memberEmail, String groupKey) { + // We're using "get" instead of "hasMember" because "hasMember" fails for emails that don't + // belong to the G-Suite domain. + // + // "get" fails for users that aren't part of the group, but it also might fail for other + // reasons (no access, group doesn't exist etc.). + // Which error is caused by "user isn't in that group" isn't documented, and was found using + // trial and error. + // + // TODO(b/119221676): transition to using hasMember + // + // Documentation for the API of "get": + // https://developers.google.com/admin-sdk/directory/v1/reference/members/get + // + // Documentation for the API of "hasMember": + // https://developers.google.com/admin-sdk/directory/v1/reference/members/hasMember + try { + Directory.Members.Get getRequest = directory.members().get(groupKey, memberEmail); + Member getReply = getRequest.execute(); + logger.atInfo().log( + "%s is a member of the group %s. Got reply: %s", memberEmail, groupKey, getReply); + return true; + } catch (GoogleJsonResponseException e) { + if (ERROR_MESSAGES_MEMBER_NOT_FOUND.contains(e.getDetails().getMessage())) { + // This means the "get" request failed because the email wasn't part of the group. + // This is expected behavior for any visitor that isn't a support group member. + logger.atInfo().log( + "%s isn't a member of the group %s. Got reply %s", + memberEmail, groupKey, e.getMessage()); + return false; + } + // If we got here - we had an unexpected error. Rethrow. + throw new RuntimeException( + String.format("Error checking whether %s is in group %s", memberEmail, groupKey), e); + } catch (IOException e) { + throw new RuntimeException( + String.format("Error checking whether %s is in group %s", memberEmail, groupKey), e); + } + } } diff --git a/java/google/registry/groups/GroupsConnection.java b/java/google/registry/groups/GroupsConnection.java index 23ed0af26..5f7aaf0f9 100644 --- a/java/google/registry/groups/GroupsConnection.java +++ b/java/google/registry/groups/GroupsConnection.java @@ -58,4 +58,7 @@ public interface GroupsConnection { * automatically added as an owner. */ Group createGroup(String groupKey) throws IOException; + + /** Checks whether the given email belongs to the "support" group. */ + boolean isMemberOfGroup(String memberEmail, String groupKey); } diff --git a/java/google/registry/module/frontend/BUILD b/java/google/registry/module/frontend/BUILD index 88f243784..796bee607 100644 --- a/java/google/registry/module/frontend/BUILD +++ b/java/google/registry/module/frontend/BUILD @@ -11,6 +11,7 @@ java_library( "//java/google/registry/config", "//java/google/registry/dns", "//java/google/registry/flows", + "//java/google/registry/groups", "//java/google/registry/keyring", "//java/google/registry/keyring/api", "//java/google/registry/keyring/kms", diff --git a/java/google/registry/module/frontend/FrontendComponent.java b/java/google/registry/module/frontend/FrontendComponent.java index 1c96edc5b..04314dbe2 100644 --- a/java/google/registry/module/frontend/FrontendComponent.java +++ b/java/google/registry/module/frontend/FrontendComponent.java @@ -21,6 +21,9 @@ import google.registry.config.CredentialModule; import google.registry.config.RegistryConfig.ConfigModule; import google.registry.flows.ServerTridProviderModule; import google.registry.flows.custom.CustomLogicFactoryModule; +import google.registry.groups.DirectoryModule; +import google.registry.groups.GroupsModule; +import google.registry.groups.GroupssettingsModule; import google.registry.keyring.KeyringModule; import google.registry.keyring.api.DummyKeyringModule; import google.registry.keyring.api.KeyModule; @@ -48,8 +51,11 @@ import javax.inject.Singleton; ConsoleConfigModule.class, CredentialModule.class, CustomLogicFactoryModule.class, + DirectoryModule.class, DummyKeyringModule.class, FrontendRequestComponentModule.class, + GroupsModule.class, + GroupssettingsModule.class, Jackson2Module.class, KeyModule.class, KeyringModule.class, diff --git a/java/google/registry/module/pubapi/BUILD b/java/google/registry/module/pubapi/BUILD index 40d4b233b..5d0307c85 100644 --- a/java/google/registry/module/pubapi/BUILD +++ b/java/google/registry/module/pubapi/BUILD @@ -11,6 +11,7 @@ java_library( "//java/google/registry/config", "//java/google/registry/dns", "//java/google/registry/flows", + "//java/google/registry/groups", "//java/google/registry/keyring", "//java/google/registry/keyring/api", "//java/google/registry/keyring/kms", diff --git a/java/google/registry/module/pubapi/PubApiComponent.java b/java/google/registry/module/pubapi/PubApiComponent.java index 966ad54f0..1b65390a0 100644 --- a/java/google/registry/module/pubapi/PubApiComponent.java +++ b/java/google/registry/module/pubapi/PubApiComponent.java @@ -21,6 +21,9 @@ import google.registry.config.CredentialModule; import google.registry.config.RegistryConfig.ConfigModule; import google.registry.flows.ServerTridProviderModule; import google.registry.flows.custom.CustomLogicFactoryModule; +import google.registry.groups.DirectoryModule; +import google.registry.groups.GroupsModule; +import google.registry.groups.GroupssettingsModule; import google.registry.keyring.KeyringModule; import google.registry.keyring.api.DummyKeyringModule; import google.registry.keyring.api.KeyModule; @@ -46,7 +49,10 @@ import javax.inject.Singleton; ConfigModule.class, CredentialModule.class, CustomLogicFactoryModule.class, + DirectoryModule.class, DummyKeyringModule.class, + GroupsModule.class, + GroupssettingsModule.class, Jackson2Module.class, KeyModule.class, KeyringModule.class, diff --git a/java/google/registry/ui/server/registrar/AuthenticatedRegistrarAccessor.java b/java/google/registry/ui/server/registrar/AuthenticatedRegistrarAccessor.java index f1a531d93..c2d4bd77c 100644 --- a/java/google/registry/ui/server/registrar/AuthenticatedRegistrarAccessor.java +++ b/java/google/registry/ui/server/registrar/AuthenticatedRegistrarAccessor.java @@ -17,11 +17,14 @@ package google.registry.ui.server.registrar; import static google.registry.model.ofy.ObjectifyService.ofy; import com.google.appengine.api.users.User; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Strings; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSetMultimap; import com.google.common.flogger.FluentLogger; +import dagger.Lazy; import google.registry.config.RegistryConfig.Config; +import google.registry.groups.GroupsConnection; import google.registry.model.registrar.Registrar; import google.registry.model.registrar.RegistrarContact; import google.registry.request.HttpException.ForbiddenException; @@ -61,12 +64,44 @@ public class AuthenticatedRegistrarAccessor { */ private final ImmutableSetMultimap roleMap; + /** + * Overriding the injected {@link GroupsConnection} for tests. + * + *

{@link GroupsConnection} needs the injected DelegatedCredential GoogleCredential. However, + * this can't be initialized in the test environment. + * + *

The test server used in the javatests/google/registry/webdriver/ tests will hang if we try + * to instantiate the {@link GroupsConnection}. So instead we inject a {@link Lazy} version and + * allow tests to override the injected instace with (presumabley) a mock insteance. + */ + @VisibleForTesting public static GroupsConnection overrideGroupsConnection = null; + @Inject public AuthenticatedRegistrarAccessor( - AuthResult authResult, @Config("registryAdminClientId") String registryAdminClientId) { + AuthResult authResult, + @Config("registryAdminClientId") String registryAdminClientId, + @Config("gSuiteSupportGroupEmailAddress") String gSuiteSupportGroupEmailAddress, + Lazy groupsConnection) { + this( + authResult, + registryAdminClientId, + gSuiteSupportGroupEmailAddress, + overrideGroupsConnection != null ? overrideGroupsConnection : groupsConnection.get()); + } + + AuthenticatedRegistrarAccessor( + AuthResult authResult, + @Config("registryAdminClientId") String registryAdminClientId, + @Config("gSuiteSupportGroupEmailAddress") String gSuiteSupportGroupEmailAddress, + GroupsConnection groupsConnection) { this.authResult = authResult; this.registryAdminClientId = registryAdminClientId; - this.roleMap = createRoleMap(authResult, registryAdminClientId); + this.roleMap = + createRoleMap( + authResult, + registryAdminClientId, + groupsConnection, + gSuiteSupportGroupEmailAddress); logger.atInfo().log( "%s has the following roles: %s", authResult.userIdForLogging(), roleMap); @@ -156,8 +191,27 @@ public class AuthenticatedRegistrarAccessor { return registrar; } + private static boolean checkIsSupport( + GroupsConnection groupsConnection, String userEmail, String supportEmail) { + if (Strings.isNullOrEmpty(supportEmail)) { + return false; + } + try { + return groupsConnection.isMemberOfGroup(userEmail, supportEmail); + } catch (RuntimeException e) { + logger.atSevere().withCause(e).log( + "Error checking whether email %s belongs to support group %s." + + " Skipping support role check", + userEmail, supportEmail); + return false; + } + } + private static ImmutableSetMultimap createRoleMap( - AuthResult authResult, String registryAdminClientId) { + AuthResult authResult, + String registryAdminClientId, + GroupsConnection groupsConnection, + String gSuiteSupportGroupEmailAddress) { if (!authResult.userAuthInfo().isPresent()) { return ImmutableSetMultimap.of(); @@ -165,8 +219,10 @@ public class AuthenticatedRegistrarAccessor { UserAuthInfo userAuthInfo = authResult.userAuthInfo().get(); - boolean isAdmin = userAuthInfo.isUserAdmin(); User user = userAuthInfo.user(); + boolean isAdmin = userAuthInfo.isUserAdmin(); + boolean isSupport = + checkIsSupport(groupsConnection, user.getEmail(), gSuiteSupportGroupEmailAddress); ImmutableSetMultimap.Builder builder = new ImmutableSetMultimap.Builder<>(); @@ -174,17 +230,13 @@ public class AuthenticatedRegistrarAccessor { .load() .type(RegistrarContact.class) .filter("gaeUserId", user.getUserId()) - .forEach( - contact -> - builder - .put(contact.getParent().getName(), Role.OWNER)); + .forEach(contact -> builder.put(contact.getParent().getName(), Role.OWNER)); if (isAdmin && !Strings.isNullOrEmpty(registryAdminClientId)) { - builder - .put(registryAdminClientId, Role.OWNER); + builder.put(registryAdminClientId, Role.OWNER); } - if (isAdmin) { - // Admins have access to all registrars + if (isAdmin || isSupport) { + // Admins and support have access to all registrars ofy() .load() .type(Registrar.class) diff --git a/java/google/registry/ui/server/registrar/BUILD b/java/google/registry/ui/server/registrar/BUILD index e815fa505..a84e33db0 100644 --- a/java/google/registry/ui/server/registrar/BUILD +++ b/java/google/registry/ui/server/registrar/BUILD @@ -15,6 +15,7 @@ java_library( "//java/google/registry/config", "//java/google/registry/export/sheet", "//java/google/registry/flows", + "//java/google/registry/groups", "//java/google/registry/model", "//java/google/registry/request", "//java/google/registry/request/auth", @@ -24,6 +25,7 @@ java_library( "//java/google/registry/ui/soy:soy_java_wrappers", "//java/google/registry/ui/soy/registrar:soy_java_wrappers", "//java/google/registry/util", + "//third_party/java/inject_common", "//third_party/objectify:objectify-v4_1", "@com_google_appengine_api_1_0_sdk", "@com_google_auto_value", diff --git a/javatests/google/registry/groups/DirectoryGroupsConnectionTest.java b/javatests/google/registry/groups/DirectoryGroupsConnectionTest.java index f557f88a8..efe06cfb4 100644 --- a/javatests/google/registry/groups/DirectoryGroupsConnectionTest.java +++ b/javatests/google/registry/groups/DirectoryGroupsConnectionTest.java @@ -216,6 +216,45 @@ public class DirectoryGroupsConnectionTest { assertThat(connection.getMembersOfGroup("nonexistent_group@fake.notreal")).isEmpty(); } + @Test + public void test_isMemberOfSupportGroup_userInGroup_True() throws Exception { + when(members.get("support@domain-registry.example", "user@example.com")).thenReturn(membersGet); + when(membersGet.execute()).thenReturn(new Member()); + assertThat(connection.isMemberOfGroup("user@example.com", "support@domain-registry.example")) + .isTrue(); + } + + @Test + public void test_isMemberOfSupportGroup_userExistButNotInGroup_returnsFalse() throws Exception { + when(members.get("support@domain-registry.example", "user@example.com")) + .thenThrow(makeResponseException(0, "Resource Not Found: memberKey")); + when(membersGet.execute()).thenReturn(new Member()); + assertThat(connection.isMemberOfGroup("user@example.com", "support@domain-registry.example")) + .isFalse(); + } + + @Test + public void test_isMemberOfSupportGroup_userDoesntExist_returnsFalse() throws Exception { + when(members.get("support@domain-registry.example", "user@example.com")) + .thenThrow(makeResponseException(0, "Missing required field: memberKey")); + when(membersGet.execute()).thenReturn(new Member()); + assertThat(connection.isMemberOfGroup("user@example.com", "support@domain-registry.example")) + .isFalse(); + } + + @Test + public void test_isMemberOfSupportGroup_otherError_throws() throws Exception { + when(members.get("support@domain-registry.example", "user@example.com")) + .thenThrow(makeResponseException(0, "some error")); + when(membersGet.execute()).thenReturn(new Member()); + RuntimeException e = + assertThrows( + RuntimeException.class, + () -> + connection.isMemberOfGroup("user@example.com", "support@domain-registry.example")); + assertThat(e).hasCauseThat().isInstanceOf(GoogleJsonResponseException.class); + } + /** Runs a full test to add a member to the group and returns the expected Member. */ private Member runAddMemberTest() throws Exception { connection.addMemberToGroup("spam@example.com", "jim@example.com", Role.MEMBER); diff --git a/javatests/google/registry/ui/server/registrar/AuthenticatedRegistrarAccessorTest.java b/javatests/google/registry/ui/server/registrar/AuthenticatedRegistrarAccessorTest.java index dda2fde6c..8c6341d04 100644 --- a/javatests/google/registry/ui/server/registrar/AuthenticatedRegistrarAccessorTest.java +++ b/javatests/google/registry/ui/server/registrar/AuthenticatedRegistrarAccessorTest.java @@ -22,12 +22,17 @@ import static google.registry.testing.JUnitBackports.assertThrows; import static google.registry.testing.LogsSubject.assertAboutLogs; import static google.registry.ui.server.registrar.AuthenticatedRegistrarAccessor.Role.ADMIN; import static google.registry.ui.server.registrar.AuthenticatedRegistrarAccessor.Role.OWNER; +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.flogger.LoggerConfig; import com.google.common.testing.NullPointerTester; import com.google.common.testing.TestLogHandler; +import google.registry.groups.GroupsConnection; import google.registry.request.HttpException.ForbiddenException; import google.registry.request.auth.AuthLevel; import google.registry.request.auth.AuthResult; @@ -53,6 +58,7 @@ public class AuthenticatedRegistrarAccessorTest { 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 AUTHORIZED_USER = createAuthResult(true, false); @@ -60,6 +66,7 @@ public class AuthenticatedRegistrarAccessorTest { private static final AuthResult AUTHORIZED_ADMIN = createAuthResult(true, true); private static final AuthResult UNAUTHORIZED_ADMIN = createAuthResult(false, true); private static final AuthResult NO_USER = AuthResult.create(AuthLevel.NONE); + private static final String SUPPORT_GROUP = "support@registry.example"; private static final String DEFAULT_CLIENT_ID = "TheRegistrar"; private static final String ADMIN_CLIENT_ID = "NewRegistrar"; @@ -79,6 +86,7 @@ public class AuthenticatedRegistrarAccessorTest { public void before() { LoggerConfig.getConfig(AuthenticatedRegistrarAccessor.class).addHandler(testLogHandler); persistResource(loadRegistrar(ADMIN_CLIENT_ID)); + when(groupsConnection.isMemberOfGroup(any(), any())).thenReturn(false); } @After @@ -96,17 +104,34 @@ public class AuthenticatedRegistrarAccessorTest { @Test public void getAllClientIdWithAccess_authorizedUser() { AuthenticatedRegistrarAccessor registrarAccessor = - new AuthenticatedRegistrarAccessor(AUTHORIZED_USER, ADMIN_CLIENT_ID); + new AuthenticatedRegistrarAccessor( + AUTHORIZED_USER, ADMIN_CLIENT_ID, SUPPORT_GROUP, groupsConnection); assertThat(registrarAccessor.getAllClientIdWithRoles()) .containsExactly(DEFAULT_CLIENT_ID, OWNER); } + /** Users in support group have admin access to everything. */ + @Test + public void getAllClientIdWithAccess_authorizedUser_isSupportGroup() { + when(groupsConnection.isMemberOfGroup("good_user@gmail.com", SUPPORT_GROUP)).thenReturn(true); + AuthenticatedRegistrarAccessor registrarAccessor = + new AuthenticatedRegistrarAccessor( + AUTHORIZED_USER, ADMIN_CLIENT_ID, SUPPORT_GROUP, groupsConnection); + + assertThat(registrarAccessor.getAllClientIdWithRoles()) + .containsExactly( + DEFAULT_CLIENT_ID, OWNER, + DEFAULT_CLIENT_ID, ADMIN, + ADMIN_CLIENT_ID, ADMIN); + } + /** Logged out users don't have access to anything. */ @Test public void getAllClientIdWithAccess_loggedOutUser() { AuthenticatedRegistrarAccessor registrarAccessor = - new AuthenticatedRegistrarAccessor(NO_USER, ADMIN_CLIENT_ID); + new AuthenticatedRegistrarAccessor( + NO_USER, ADMIN_CLIENT_ID, SUPPORT_GROUP, groupsConnection); assertThat(registrarAccessor.getAllClientIdWithRoles()).isEmpty(); } @@ -115,16 +140,56 @@ public class AuthenticatedRegistrarAccessorTest { @Test public void getAllClientIdWithAccess_unauthorizedUser() { AuthenticatedRegistrarAccessor registrarAccessor = - new AuthenticatedRegistrarAccessor(UNAUTHORIZED_USER, ADMIN_CLIENT_ID); + new AuthenticatedRegistrarAccessor( + UNAUTHORIZED_USER, ADMIN_CLIENT_ID, SUPPORT_GROUP, groupsConnection); assertThat(registrarAccessor.getAllClientIdWithRoles()).isEmpty(); } + /** Unauthorized users who are in support group have admin access. */ + @Test + public void getAllClientIdWithAccess_unauthorizedUser_inSupportGroup() { + when(groupsConnection.isMemberOfGroup("evil_user@gmail.com", SUPPORT_GROUP)).thenReturn(true); + AuthenticatedRegistrarAccessor registrarAccessor = + new AuthenticatedRegistrarAccessor( + UNAUTHORIZED_USER, ADMIN_CLIENT_ID, SUPPORT_GROUP, groupsConnection); + + assertThat(registrarAccessor.getAllClientIdWithRoles()) + .containsExactly( + DEFAULT_CLIENT_ID, ADMIN, + ADMIN_CLIENT_ID, ADMIN); + } + + /** Empty Support group email - skips check. */ + @Test + public void getAllClientIdWithAccess_emptySupportEmail_works() { + AuthenticatedRegistrarAccessor registrarAccessor = + new AuthenticatedRegistrarAccessor(AUTHORIZED_USER, ADMIN_CLIENT_ID, "", groupsConnection); + + verifyNoMoreInteractions(groupsConnection); + assertThat(registrarAccessor.getAllClientIdWithRoles()) + .containsExactly(DEFAULT_CLIENT_ID, 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( + AUTHORIZED_USER, ADMIN_CLIENT_ID, SUPPORT_GROUP, groupsConnection); + + verify(groupsConnection).isMemberOfGroup("good_user@gmail.com", SUPPORT_GROUP); + assertThat(registrarAccessor.getAllClientIdWithRoles()) + .containsExactly(DEFAULT_CLIENT_ID, OWNER); + } + /** Admins have read/write access to the authorized registrars, AND the admin registrar. */ @Test public void getAllClientIdWithAccess_authorizedAdmin() { AuthenticatedRegistrarAccessor registrarAccessor = - new AuthenticatedRegistrarAccessor(AUTHORIZED_ADMIN, ADMIN_CLIENT_ID); + new AuthenticatedRegistrarAccessor( + AUTHORIZED_ADMIN, ADMIN_CLIENT_ID, SUPPORT_GROUP, groupsConnection); assertThat(registrarAccessor.getAllClientIdWithRoles()) .containsExactly( @@ -141,7 +206,8 @@ public class AuthenticatedRegistrarAccessorTest { @Test public void getAllClientIdWithAccess_unauthorizedAdmin() { AuthenticatedRegistrarAccessor registrarAccessor = - new AuthenticatedRegistrarAccessor(UNAUTHORIZED_ADMIN, ADMIN_CLIENT_ID); + new AuthenticatedRegistrarAccessor( + UNAUTHORIZED_ADMIN, ADMIN_CLIENT_ID, SUPPORT_GROUP, groupsConnection); assertThat(registrarAccessor.getAllClientIdWithRoles()) .containsExactly( @@ -199,7 +265,8 @@ public class AuthenticatedRegistrarAccessorTest { private void expectGetRegistrarSuccess( AuthResult authResult, String message) { AuthenticatedRegistrarAccessor registrarAccessor = - new AuthenticatedRegistrarAccessor(authResult, ADMIN_CLIENT_ID); + new AuthenticatedRegistrarAccessor( + authResult, ADMIN_CLIENT_ID, SUPPORT_GROUP, groupsConnection); assertThat(registrarAccessor.getRegistrar(DEFAULT_CLIENT_ID)).isNotNull(); assertAboutLogs() @@ -211,7 +278,8 @@ public class AuthenticatedRegistrarAccessorTest { private void expectGetRegistrarFailure( String clientId, AuthResult authResult, String message) { AuthenticatedRegistrarAccessor registrarAccessor = - new AuthenticatedRegistrarAccessor(authResult, ADMIN_CLIENT_ID); + new AuthenticatedRegistrarAccessor( + authResult, ADMIN_CLIENT_ID, SUPPORT_GROUP, groupsConnection); ForbiddenException exception = assertThrows( @@ -224,7 +292,8 @@ public class AuthenticatedRegistrarAccessorTest { @Test public void testGuessClientIdForUser_hasAccess_isNotAdmin() { AuthenticatedRegistrarAccessor registrarAccessor = - new AuthenticatedRegistrarAccessor(AUTHORIZED_USER, ADMIN_CLIENT_ID); + new AuthenticatedRegistrarAccessor( + AUTHORIZED_USER, ADMIN_CLIENT_ID, SUPPORT_GROUP, groupsConnection); assertThat(registrarAccessor.guessClientId()).isEqualTo(DEFAULT_CLIENT_ID); } @@ -242,7 +311,8 @@ public class AuthenticatedRegistrarAccessorTest { @Test public void testGuessClientIdForUser_hasAccess_isAdmin() { AuthenticatedRegistrarAccessor registrarAccessor = - new AuthenticatedRegistrarAccessor(AUTHORIZED_ADMIN, ADMIN_CLIENT_ID); + new AuthenticatedRegistrarAccessor( + AUTHORIZED_ADMIN, ADMIN_CLIENT_ID, SUPPORT_GROUP, groupsConnection); assertThat(registrarAccessor.guessClientId()).isEqualTo(DEFAULT_CLIENT_ID); } @@ -251,7 +321,8 @@ public class AuthenticatedRegistrarAccessorTest { @Test public void testGuessClientIdForUser_noAccess_isAdmin() { AuthenticatedRegistrarAccessor registrarAccessor = - new AuthenticatedRegistrarAccessor(UNAUTHORIZED_ADMIN, ADMIN_CLIENT_ID); + new AuthenticatedRegistrarAccessor( + UNAUTHORIZED_ADMIN, ADMIN_CLIENT_ID, SUPPORT_GROUP, groupsConnection); assertThat(registrarAccessor.guessClientId()).isEqualTo(ADMIN_CLIENT_ID); } @@ -264,7 +335,7 @@ public class AuthenticatedRegistrarAccessorTest { @Test public void testGuessClientIdForUser_noAccess_isAdmin_adminClientIdEmpty() { AuthenticatedRegistrarAccessor registrarAccessor = - new AuthenticatedRegistrarAccessor(UNAUTHORIZED_ADMIN, ""); + new AuthenticatedRegistrarAccessor(UNAUTHORIZED_ADMIN, "", SUPPORT_GROUP, groupsConnection); assertThat(registrarAccessor.guessClientId()).isAnyOf(ADMIN_CLIENT_ID, DEFAULT_CLIENT_ID); } @@ -276,14 +347,16 @@ public class AuthenticatedRegistrarAccessorTest { @Test public void testGuessClientIdForUser_noAccess_isAdmin_adminClientIdInvalid() { AuthenticatedRegistrarAccessor registrarAccessor = - new AuthenticatedRegistrarAccessor(UNAUTHORIZED_ADMIN, "NonexistentRegistrar"); + new AuthenticatedRegistrarAccessor( + UNAUTHORIZED_ADMIN, "NonexistentRegistrar", SUPPORT_GROUP, groupsConnection); assertThat(registrarAccessor.guessClientId()).isEqualTo("NonexistentRegistrar"); } private void expectGuessRegistrarFailure(AuthResult authResult, String message) { AuthenticatedRegistrarAccessor registrarAccessor = - new AuthenticatedRegistrarAccessor(authResult, ADMIN_CLIENT_ID); + new AuthenticatedRegistrarAccessor( + authResult, ADMIN_CLIENT_ID, SUPPORT_GROUP, groupsConnection); ForbiddenException exception = assertThrows(ForbiddenException.class, () -> registrarAccessor.guessClientId()); diff --git a/javatests/google/registry/ui/server/registrar/BUILD b/javatests/google/registry/ui/server/registrar/BUILD index e7a04c602..7e2aa43eb 100644 --- a/javatests/google/registry/ui/server/registrar/BUILD +++ b/javatests/google/registry/ui/server/registrar/BUILD @@ -14,6 +14,7 @@ java_library( deps = [ "//java/google/registry/config", "//java/google/registry/export/sheet", + "//java/google/registry/groups", "//java/google/registry/model", "//java/google/registry/request", "//java/google/registry/request/auth",