Add briefRepresentation query parameter to getUsersInRole endpoint

Closes #29480

Signed-off-by: rmartinc <rmartinc@redhat.com>
This commit is contained in:
rmartinc 2024-06-20 12:26:30 +02:00 committed by Marek Posolda
parent 6b135ff6e7
commit 592c2250fc
8 changed files with 60 additions and 22 deletions

View file

@ -120,9 +120,8 @@ public interface RoleResource {
/** /**
* Get role members. * Get role members.
* <p/> * <p>Returns users that have the given role, sorted by username ascending, paginated according to the query
* Returns users that have the given role, sorted by username ascending, paginated according to the query * parameters.</p>
* parameters.
* *
* @param firstResult Pagination offset * @param firstResult Pagination offset
* @param maxResults Pagination size * @param maxResults Pagination size
@ -135,9 +134,25 @@ public interface RoleResource {
@QueryParam("max") Integer maxResults); @QueryParam("max") Integer maxResults);
/** /**
* Get role groups * Get role members.
* <p/> * <p>Returns users that have the given role, sorted by username ascending, paginated according to the query
* Returns groups that have the given role * parameters.</p>
*
* @param briefRepresentation If the user should be returned in brief or full representation
* @param firstResult Pagination offset
* @param maxResults Pagination size
* @return a list of users with the given role
*/
@GET
@Path("users")
@Produces(MediaType.APPLICATION_JSON)
List<UserRepresentation> getUserMembers(@QueryParam("briefRepresentation") Boolean briefRepresentation,
@QueryParam("first") Integer firstResult,
@QueryParam("max") Integer maxResults);
/**
* Get role groups.
* <p>Returns groups that have the given role.</p>
* *
* @return a list of groups with the given role * @return a list of groups with the given role
*/ */
@ -147,9 +162,8 @@ public interface RoleResource {
Set<GroupRepresentation> getRoleGroupMembers(); Set<GroupRepresentation> getRoleGroupMembers();
/** /**
* Get role groups * Get role groups.
* <p/> * <p>Returns groups that have the given role, paginated according to the query parameters.</p>
* Returns groups that have the given role, paginated according to the query parameters
* *
* @param firstResult Pagination offset * @param firstResult Pagination offset
* @param maxResults Pagination size * @param maxResults Pagination size
@ -162,9 +176,8 @@ public interface RoleResource {
@QueryParam("max") Integer maxResults); @QueryParam("max") Integer maxResults);
/** /**
* Get role members * Get role members.
* <p/> * <p>Returns users that have the given role.</p>
* Returns users that have the given role
* *
* @return a set of users with the given role * @return a set of users with the given role
* *
@ -177,9 +190,8 @@ public interface RoleResource {
Set<UserRepresentation> getRoleUserMembers(); Set<UserRepresentation> getRoleUserMembers();
/** /**
* Get role members * Get role members.
* <p/> * <p>Returns users that have the given role, paginated according to the query parameters.</p>
* Returns users that have the given role, paginated according to the query parameters
* *
* @param firstResult Pagination offset * @param firstResult Pagination offset
* @param maxResults Pagination size * @param maxResults Pagination size

View file

@ -30,6 +30,7 @@ export const UsersInRoleTab = () => {
return adminClient.clients.findUsersWithRole({ return adminClient.clients.findUsersWithRole({
roleName: role.name!, roleName: role.name!,
id: clientId, id: clientId,
briefRepresentation: true,
first, first,
max, max,
}); });
@ -37,6 +38,7 @@ export const UsersInRoleTab = () => {
return adminClient.roles.findUsersWithRole({ return adminClient.roles.findUsersWithRole({
name: role.name!, name: role.name!,
briefRepresentation: true,
first, first,
max, max,
}); });

View file

@ -139,7 +139,13 @@ export class Clients extends Resource<{ realm?: string }> {
}); });
public findUsersWithRole = this.makeRequest< public findUsersWithRole = this.makeRequest<
{ id: string; roleName: string; first?: number; max?: number }, {
id: string;
roleName: string;
briefRepresentation?: boolean;
first?: number;
max?: number;
},
UserRepresentation[] UserRepresentation[]
>({ >({
method: "GET", method: "GET",

View file

@ -58,7 +58,12 @@ export class Roles extends Resource<{ realm?: string }> {
}); });
public findUsersWithRole = this.makeRequest< public findUsersWithRole = this.makeRequest<
{ name: string; first?: number; max?: number }, {
name: string;
briefRepresentation?: boolean;
first?: number;
max?: number;
},
UserRepresentation[] UserRepresentation[]
>({ >({
method: "GET", method: "GET",

View file

@ -733,7 +733,10 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
TypedQuery<UserEntity> query = em.createNamedQuery("usersInRole", UserEntity.class); TypedQuery<UserEntity> query = em.createNamedQuery("usersInRole", UserEntity.class);
query.setParameter("roleId", role.getId()); query.setParameter("roleId", role.getId());
return closing(paginateQuery(query, firstResult, maxResults).getResultStream().map(user -> new UserAdapter(session, realm, em, user))); final UserProvider users = session.users();
return closing(paginateQuery(query, firstResult, maxResults).getResultStream())
.map(userEntity -> users.getUserById(realm, userEntity.getId()))
.filter(Objects::nonNull);
} }
@Override @Override

View file

@ -34,6 +34,7 @@ import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleContainerModel; import org.keycloak.models.RoleContainerModel;
import org.keycloak.models.RoleModel; import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.representations.idm.GroupRepresentation; import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.representations.idm.ManagementPermissionReference; import org.keycloak.representations.idm.ManagementPermissionReference;
@ -477,6 +478,7 @@ public class RoleContainerResource extends RoleResource {
* @param roleName the role name. * @param roleName the role name.
* @param firstResult first result to return. Ignored if negative or {@code null}. * @param firstResult first result to return. Ignored if negative or {@code null}.
* @param maxResults maximum number of results to return. Ignored if negative or {@code null}. * @param maxResults maximum number of results to return. Ignored if negative or {@code null}.
* @param briefRepresentation Boolean which defines whether brief representations are returned (default: false)
* @return a non-empty {@code Stream} of users. * @return a non-empty {@code Stream} of users.
*/ */
@Path("{role-name}/users") @Path("{role-name}/users")
@ -486,6 +488,7 @@ public class RoleContainerResource extends RoleResource {
@Tag(name = KeycloakOpenAPI.Admin.Tags.ROLES) @Tag(name = KeycloakOpenAPI.Admin.Tags.ROLES)
@Operation( summary = "Returns a stream of users that have the specified role name.") @Operation( summary = "Returns a stream of users that have the specified role name.")
public Stream<UserRepresentation> getUsersInRole(final @Parameter(description = "the role name.") @PathParam("role-name") String roleName, public Stream<UserRepresentation> getUsersInRole(final @Parameter(description = "the role name.") @PathParam("role-name") String roleName,
@Parameter(description = "Boolean which defines whether brief representations are returned (default: false)") @QueryParam("briefRepresentation") Boolean briefRepresentation,
@Parameter(description = "first result to return. Ignored if negative or {@code null}.") @QueryParam("first") Integer firstResult, @Parameter(description = "first result to return. Ignored if negative or {@code null}.") @QueryParam("first") Integer firstResult,
@Parameter(description = "maximum number of results to return. Ignored if negative or {@code null}.") @QueryParam("max") Integer maxResults) { @Parameter(description = "maximum number of results to return. Ignored if negative or {@code null}.") @QueryParam("max") Integer maxResults) {
@ -498,8 +501,11 @@ public class RoleContainerResource extends RoleResource {
throw new NotFoundException("Could not find role"); throw new NotFoundException("Could not find role");
} }
final Function<UserModel, UserRepresentation> toRepresentation = briefRepresentation != null && briefRepresentation
? ModelToRepresentation::toBriefRepresentation
: user -> ModelToRepresentation.toRepresentation(session, realm, user);
return session.users().getRoleMembersStream(realm, role, firstResult, maxResults) return session.users().getRoleMembersStream(realm, role, firstResult, maxResults)
.map(user -> ModelToRepresentation.toRepresentation(session, realm, user)); .map(toRepresentation);
} }
/** /**

View file

@ -239,8 +239,10 @@ public class ClientRolesTest extends AbstractClientTest {
// pagination // pagination
List<UserRepresentation> usersInRole1 = roleResource.getUserMembers(0, 5); List<UserRepresentation> usersInRole1 = roleResource.getUserMembers(0, 5);
assertEquals(createUsernames(0, 5), extractUsernames(usersInRole1)); assertEquals(createUsernames(0, 5), extractUsernames(usersInRole1));
List<UserRepresentation> usersInRole2 = roleResource.getUserMembers(5, 10); Assert.assertNotNull("Not in full representation", usersInRole1.get(0).getNotBefore());
List<UserRepresentation> usersInRole2 = roleResource.getUserMembers(true, 5, 10);
assertEquals(createUsernames(5, 10), extractUsernames(usersInRole2)); assertEquals(createUsernames(5, 10), extractUsernames(usersInRole2));
Assert.assertNull("Not in brief representation", usersInRole2.get(0).getNotBefore());
} }
private static List<String> createUsernames(int startIndex, int endIndex) { private static List<String> createUsernames(int startIndex, int endIndex) {

View file

@ -352,12 +352,14 @@ public class RealmRolesTest extends AbstractAdminTest {
List<UserRepresentation> roleUserMembers = roleResource.getUserMembers(0, 1); List<UserRepresentation> roleUserMembers = roleResource.getUserMembers(0, 1);
assertEquals(Collections.singletonList("test-role-member"), extractUsernames(roleUserMembers)); assertEquals(Collections.singletonList("test-role-member"), extractUsernames(roleUserMembers));
Assert.assertNotNull("Not in full representation", roleUserMembers.get(0).getNotBefore());
roleUserMembers = roleResource.getUserMembers(1, 1); roleUserMembers = roleResource.getUserMembers(true, 1, 1);
assertThat(roleUserMembers, hasSize(1)); assertThat(roleUserMembers, hasSize(1));
assertEquals(Collections.singletonList("test-role-member2"), extractUsernames(roleUserMembers)); assertEquals(Collections.singletonList("test-role-member2"), extractUsernames(roleUserMembers));
Assert.assertNull("Not in brief representation", roleUserMembers.get(0).getNotBefore());
roleUserMembers = roleResource.getUserMembers(2, 1); roleUserMembers = roleResource.getUserMembers(true, 2, 1);
assertThat(roleUserMembers, is(empty())); assertThat(roleUserMembers, is(empty()));
} }