From 4518e4d1b33791fddc2d74423101375ca0813eb6 Mon Sep 17 00:00:00 2001 From: vramik Date: Mon, 27 Jul 2026 14:02:45 +0200 Subject: [PATCH] GET /roles/{role}/users returns user PII without the per-user view filter with FGAP V1 Closes #51142 Signed-off-by: vramik --- .../admin/RoleContainerResource.java | 17 +++++++- .../fgap/UserResourceTypeFilteringTest.java | 43 +++++++++++++++++++ 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java index a299a0b40da5..06d7d8e9def1 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java @@ -41,6 +41,7 @@ import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.UriInfo; +import org.keycloak.authorization.fgap.AdminPermissionsSchema; import org.keycloak.common.Profile; import org.keycloak.common.util.Encode; import org.keycloak.events.admin.OperationType; @@ -52,6 +53,7 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.RoleContainerModel; import org.keycloak.models.RoleModel; +import org.keycloak.models.UserModel; import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.representations.idm.GroupRepresentation; import org.keycloak.representations.idm.ManagementPermissionReference; @@ -62,6 +64,7 @@ import org.keycloak.services.resources.admin.fgap.AdminPermissionEvaluator; import org.keycloak.services.resources.admin.fgap.AdminPermissionManagement; import org.keycloak.services.resources.admin.fgap.AdminPermissions; +import org.keycloak.services.resources.admin.fgap.UserPermissionEvaluator; import org.keycloak.utils.ProfileHelper; import org.eclipse.microprofile.openapi.annotations.Operation; @@ -597,9 +600,19 @@ public Stream getUsersInRole(final @Parameter(description = } boolean briefRep = Boolean.TRUE.equals(briefRepresentation); + UserPermissionEvaluator usersEvaluator = auth.users(); - return session.users().getRoleMembersStream(realm, role, firstResult, maxResults) - .map((u) -> ModelToRepresentation.toRepresentation(session, u, briefRep)); + Stream members = session.users().getRoleMembersStream(realm, role, firstResult, maxResults); + + if (!AdminPermissionsSchema.SCHEMA.isAdminPermissionsEnabled(realm)) { + members = members.filter(usersEvaluator::canView); + } + + return members.map(user -> { + UserRepresentation userRep = ModelToRepresentation.toRepresentation(session, user, briefRep); + userRep.setAccess(usersEvaluator.getAccessForListing(user)); + return userRep; + }); } /** diff --git a/tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/UserResourceTypeFilteringTest.java b/tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/UserResourceTypeFilteringTest.java index 060a2323882a..da7913a2ddb0 100644 --- a/tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/UserResourceTypeFilteringTest.java +++ b/tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/UserResourceTypeFilteringTest.java @@ -568,6 +568,49 @@ public void testRoleMemberFilteringByViewPermission() { assertThat(roleMembers, hasItems(allowedUsers.toArray(new String[0]))); } + @Test + public void testRealmRoleMemberFilteringByViewPermission() { + RoleRepresentation role = new RoleRepresentation(); + role.setName("test_realm_role"); + realm.admin().roles().create(role); + role = realm.admin().roles().get(role.getName()).toRepresentation(); + realm.cleanup().add(r -> r.roles().deleteRole("test_realm_role")); + + for (String username : List.of("user_x", "user_y", "user_z")) { + String userId = ApiUtil.getCreatedId(realm.admin().users().create(UserBuilder.create() + .username(username) + .password("password") + .firstName("user") + .lastName(username) + .email(username + "@test") + .build())); + realm.admin().users().get(userId).roles().realmLevel().add(List.of(role)); + realm.cleanup().add(r -> r.users().delete(userId).close()); + } + + UserPolicyRepresentation policy = createUserPolicy(realm, adminPermissionsClient, "Myadmin user policy", + realm.admin().users().search("myadmin").get(0).getId()); + Set allowedUsers = Set.of("user_x", "user_y"); + createPermission(adminPermissionsClient, allowedUsers, AdminPermissionsSchema.USERS.getType(), + Set.of(AdminPermissionsSchema.VIEW), policy); + + String realmMgmtClientId = realm.admin().clients() + .findByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID).get(0).getId(); + RoleRepresentation viewRealmRole = realm.admin().clients().get(realmMgmtClientId) + .roles().get(AdminRoles.VIEW_REALM).toRepresentation(); + String myadminId = realm.admin().users().search("myadmin").get(0).getId(); + realm.admin().users().get(myadminId).roles().clientLevel(realmMgmtClientId).add(List.of(viewRealmRole)); + realm.cleanup().add(r -> r.users().get(r.users().search("myadmin").get(0).getId()) + .roles().clientLevel(realmMgmtClientId).remove(List.of(viewRealmRole))); + + List roleMembers = realmAdminClient.realm(realm.getName()) + .roles().get(role.getName()).getUserMembers().stream() + .map(UserRepresentation::getUsername).toList(); + + assertThat(roleMembers, hasSize(allowedUsers.size())); + assertThat(roleMembers, hasItems(allowedUsers.toArray(new String[0]))); + } + @Test public void testViewGroupMembersPolicyUsingAggregatedPolicy() { List search = realmAdminClient.realm(realm.getName()).users().search(null, 0, 10);