KEYCLOAK-4978 Add endpoint to get groups by role
This commit is contained in:
parent
a3c175a21e
commit
e18fb56389
7 changed files with 133 additions and 2 deletions
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
package org.keycloak.admin.client.resource;
|
package org.keycloak.admin.client.resource;
|
||||||
|
|
||||||
|
import org.keycloak.representations.idm.GroupRepresentation;
|
||||||
import org.keycloak.representations.idm.ManagementPermissionReference;
|
import org.keycloak.representations.idm.ManagementPermissionReference;
|
||||||
import org.keycloak.representations.idm.ManagementPermissionRepresentation;
|
import org.keycloak.representations.idm.ManagementPermissionRepresentation;
|
||||||
import org.keycloak.representations.idm.RoleRepresentation;
|
import org.keycloak.representations.idm.RoleRepresentation;
|
||||||
|
@ -126,5 +127,31 @@ public interface RoleResource {
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
Set<UserRepresentation> getRoleUserMembers(@QueryParam("first") Integer firstResult,
|
Set<UserRepresentation> getRoleUserMembers(@QueryParam("first") Integer firstResult,
|
||||||
@QueryParam("max") Integer maxResults);
|
@QueryParam("max") Integer maxResults);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get role groups
|
||||||
|
* <p/>
|
||||||
|
* Returns groups that have the given role
|
||||||
|
*
|
||||||
|
* @return a list of groups with the given role
|
||||||
|
*/
|
||||||
|
@GET
|
||||||
|
@Path("groups")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
Set<GroupRepresentation> getRoleGroupMembers();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get role groups
|
||||||
|
* <p/>
|
||||||
|
* Returns groups that have the given role, paginated according to the query parameters
|
||||||
|
*
|
||||||
|
* @param firstResult Pagination offset
|
||||||
|
* @param maxResults Pagination size
|
||||||
|
* @return a list of groups with the given role
|
||||||
|
*/
|
||||||
|
@GET
|
||||||
|
@Path("groups")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
Set<GroupRepresentation> getRoleGroupMembers(@QueryParam("first") Integer firstResult,
|
||||||
|
@QueryParam("max") Integer maxResults);
|
||||||
}
|
}
|
||||||
|
|
|
@ -878,6 +878,11 @@ public class RealmCacheSession implements CacheRealmProvider {
|
||||||
public Long getGroupsCountByNameContaining(RealmModel realm, String search) {
|
public Long getGroupsCountByNameContaining(RealmModel realm, String search) {
|
||||||
return getRealmDelegate().getGroupsCountByNameContaining(realm, search);
|
return getRealmDelegate().getGroupsCountByNameContaining(realm, search);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<GroupModel> getGroupsByRole(RealmModel realm, RoleModel role, int firstResult, int maxResults) {
|
||||||
|
return getRealmDelegate().getGroupsByRole(realm, role, firstResult, maxResults);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<GroupModel> getTopLevelGroups(RealmModel realm) {
|
public List<GroupModel> getTopLevelGroups(RealmModel realm) {
|
||||||
|
|
|
@ -385,6 +385,25 @@ public class JpaRealmProvider implements RealmProvider {
|
||||||
public Long getGroupsCountByNameContaining(RealmModel realm, String search) {
|
public Long getGroupsCountByNameContaining(RealmModel realm, String search) {
|
||||||
return (long) searchForGroupByName(realm, search, null, null).size();
|
return (long) searchForGroupByName(realm, search, null, null).size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<GroupModel> getGroupsByRole(RealmModel realm, RoleModel role, int firstResult, int maxResults) {
|
||||||
|
TypedQuery<GroupEntity> query = em.createNamedQuery("groupsInRole", GroupEntity.class);
|
||||||
|
query.setParameter("roleId", role.getId());
|
||||||
|
if (firstResult != -1) {
|
||||||
|
query.setFirstResult(firstResult);
|
||||||
|
}
|
||||||
|
if (maxResults != -1) {
|
||||||
|
query.setMaxResults(maxResults);
|
||||||
|
}
|
||||||
|
List<GroupEntity> results = query.getResultList();
|
||||||
|
|
||||||
|
return results.stream()
|
||||||
|
.map(g -> new GroupAdapter(realm, em, g))
|
||||||
|
.sorted(Comparator.comparing(GroupModel::getName))
|
||||||
|
.collect(Collectors.collectingAndThen(
|
||||||
|
Collectors.toList(), Collections::unmodifiableList));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<GroupModel> getTopLevelGroups(RealmModel realm) {
|
public List<GroupModel> getTopLevelGroups(RealmModel realm) {
|
||||||
|
|
|
@ -34,6 +34,7 @@ import java.io.Serializable;
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
@NamedQueries({
|
@NamedQueries({
|
||||||
|
@NamedQuery(name="groupsInRole", query="select g from GroupRoleMappingEntity m, GroupEntity g where m.roleId=:roleId and g.id=m.group"),
|
||||||
@NamedQuery(name="groupHasRole", query="select m from GroupRoleMappingEntity m where m.group = :group and m.roleId = :roleId"),
|
@NamedQuery(name="groupHasRole", query="select m from GroupRoleMappingEntity m where m.group = :group and m.roleId = :roleId"),
|
||||||
@NamedQuery(name="groupRoleMappings", query="select m from GroupRoleMappingEntity m where m.group = :group"),
|
@NamedQuery(name="groupRoleMappings", query="select m from GroupRoleMappingEntity m where m.group = :group"),
|
||||||
@NamedQuery(name="groupRoleMappingIds", query="select m.roleId from GroupRoleMappingEntity m where m.group = :group"),
|
@NamedQuery(name="groupRoleMappingIds", query="select m.roleId from GroupRoleMappingEntity m where m.group = :group"),
|
||||||
|
|
|
@ -43,6 +43,8 @@ public interface RealmProvider extends Provider, ClientProvider {
|
||||||
Long getGroupsCount(RealmModel realm, Boolean onlyTopGroups);
|
Long getGroupsCount(RealmModel realm, Boolean onlyTopGroups);
|
||||||
|
|
||||||
Long getGroupsCountByNameContaining(RealmModel realm, String search);
|
Long getGroupsCountByNameContaining(RealmModel realm, String search);
|
||||||
|
|
||||||
|
List<GroupModel> getGroupsByRole(RealmModel realm, RoleModel role, int firstResult, int maxResults);
|
||||||
|
|
||||||
List<GroupModel> getTopLevelGroups(RealmModel realm);
|
List<GroupModel> getTopLevelGroups(RealmModel realm);
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ import org.keycloak.events.admin.OperationType;
|
||||||
import org.keycloak.events.admin.ResourceType;
|
import org.keycloak.events.admin.ResourceType;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.Constants;
|
import org.keycloak.models.Constants;
|
||||||
|
import org.keycloak.models.GroupModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.ModelDuplicateException;
|
import org.keycloak.models.ModelDuplicateException;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
@ -30,6 +31,7 @@ import org.keycloak.models.RoleContainerModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.UserModel;
|
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.ManagementPermissionReference;
|
import org.keycloak.representations.idm.ManagementPermissionReference;
|
||||||
import org.keycloak.representations.idm.RoleRepresentation;
|
import org.keycloak.representations.idm.RoleRepresentation;
|
||||||
import org.keycloak.representations.idm.UserRepresentation;
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
|
@ -41,6 +43,7 @@ import org.keycloak.services.resources.admin.permissions.AdminPermissions;
|
||||||
import javax.ws.rs.BadRequestException;
|
import javax.ws.rs.BadRequestException;
|
||||||
import javax.ws.rs.Consumes;
|
import javax.ws.rs.Consumes;
|
||||||
import javax.ws.rs.DELETE;
|
import javax.ws.rs.DELETE;
|
||||||
|
import javax.ws.rs.DefaultValue;
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.GET;
|
||||||
import javax.ws.rs.POST;
|
import javax.ws.rs.POST;
|
||||||
import javax.ws.rs.PUT;
|
import javax.ws.rs.PUT;
|
||||||
|
@ -54,6 +57,7 @@ import javax.ws.rs.core.UriInfo;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @resource Roles
|
* @resource Roles
|
||||||
|
@ -402,4 +406,35 @@ public class RoleContainerResource extends RoleResource {
|
||||||
return results;
|
return results;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return List of Groups that have the specified role name
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param roleName
|
||||||
|
* @param firstResult
|
||||||
|
* @param maxResults
|
||||||
|
* @param fullRepresentation if true, return a full representation of the GroupRepresentation objects
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Path("{role-name}/groups")
|
||||||
|
@GET
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@NoCache
|
||||||
|
public List<GroupRepresentation> getGroupsInRole(final @PathParam("role-name") String roleName,
|
||||||
|
@QueryParam("first") Integer firstResult,
|
||||||
|
@QueryParam("max") Integer maxResults,
|
||||||
|
@QueryParam("full") @DefaultValue("false") boolean fullRepresentation) {
|
||||||
|
|
||||||
|
auth.roles().requireView(roleContainer);
|
||||||
|
firstResult = firstResult != null ? firstResult : 0;
|
||||||
|
maxResults = maxResults != null ? maxResults : Constants.DEFAULT_MAX_RESULTS;
|
||||||
|
|
||||||
|
RoleModel role = roleContainer.getRole(roleName);
|
||||||
|
List<GroupModel> groupsModel = session.realms().getGroupsByRole(realm, role, firstResult, maxResults);
|
||||||
|
|
||||||
|
return groupsModel.stream()
|
||||||
|
.map(g -> ModelToRepresentation.toRepresentation(g, fullRepresentation))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ import org.keycloak.admin.client.resource.UserResource;
|
||||||
import org.keycloak.events.admin.OperationType;
|
import org.keycloak.events.admin.OperationType;
|
||||||
import org.keycloak.events.admin.ResourceType;
|
import org.keycloak.events.admin.ResourceType;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
|
import org.keycloak.representations.idm.GroupRepresentation;
|
||||||
import org.keycloak.representations.idm.RoleRepresentation;
|
import org.keycloak.representations.idm.RoleRepresentation;
|
||||||
import org.keycloak.representations.idm.UserRepresentation;
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
import org.keycloak.testsuite.Assert;
|
import org.keycloak.testsuite.Assert;
|
||||||
|
@ -110,7 +111,12 @@ public class RealmRolesTest extends AbstractAdminTest {
|
||||||
getCleanup().addRoleId(ids.get("role-without-users"));
|
getCleanup().addRoleId(ids.get("role-without-users"));
|
||||||
getCleanup().addUserId(adminClient.realm(REALM_NAME).users().search(userRep.getUsername()).get(0).getId());
|
getCleanup().addUserId(adminClient.realm(REALM_NAME).users().search(userRep.getUsername()).get(0).getId());
|
||||||
|
|
||||||
|
GroupRepresentation groupRep = new GroupRepresentation();
|
||||||
|
groupRep.setName("test-role-group");
|
||||||
|
groupRep.setPath("/test-role-group");
|
||||||
|
adminClient.realm(REALM_NAME).groups().add(groupRep);
|
||||||
|
getCleanup().addGroupId(adminClient.realm(REALM_NAME).groups().groups().get(0).getId());
|
||||||
|
|
||||||
resource = adminClient.realm(REALM_NAME).roles();
|
resource = adminClient.realm(REALM_NAME).roles();
|
||||||
|
|
||||||
assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.roleResourcePath("role-a"), roleA, ResourceType.REALM_ROLE);
|
assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.roleResourcePath("role-a"), roleA, ResourceType.REALM_ROLE);
|
||||||
|
@ -122,7 +128,7 @@ public class RealmRolesTest extends AbstractAdminTest {
|
||||||
assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.clientRoleResourcePath(clientUuid, "role-c"), roleC, ResourceType.CLIENT_ROLE);
|
assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.clientRoleResourcePath(clientUuid, "role-c"), roleC, ResourceType.CLIENT_ROLE);
|
||||||
|
|
||||||
assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.userResourcePath(adminClient.realm(REALM_NAME).users().search(userRep.getUsername()).get(0).getId()), userRep, ResourceType.USER);
|
assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.userResourcePath(adminClient.realm(REALM_NAME).users().search(userRep.getUsername()).get(0).getId()), userRep, ResourceType.USER);
|
||||||
|
assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.groupPath(adminClient.realm(REALM_NAME).groups().groups().get(0).getId()), groupRep, ResourceType.GROUP);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,6 +227,7 @@ public class RealmRolesTest extends AbstractAdminTest {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* KEYCLOAK-2035 Verifies that Role with no users assigned is being properly retrieved without members in API endpoint for role membership
|
* KEYCLOAK-2035 Verifies that Role with no users assigned is being properly retrieved without members in API endpoint for role membership
|
||||||
*/
|
*/
|
||||||
|
@ -234,6 +241,41 @@ public class RealmRolesTest extends AbstractAdminTest {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* KEYCLOAK-4978 Verifies that Groups assigned to Role are being properly retrieved as members in API endpoint for role membership
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testGroupsInRole() {
|
||||||
|
RoleResource role = resource.get("role-with-users");
|
||||||
|
|
||||||
|
List<GroupRepresentation> groups = adminClient.realm(REALM_NAME).groups().groups();
|
||||||
|
GroupRepresentation groupRep = groups.stream().filter(g -> g.getPath().equals("/test-role-group")).findFirst().get();
|
||||||
|
|
||||||
|
RoleResource roleResource = adminClient.realm(REALM_NAME).roles().get(role.toRepresentation().getName());
|
||||||
|
List<RoleRepresentation> rolesToAdd = new LinkedList<>();
|
||||||
|
rolesToAdd.add(roleResource.toRepresentation());
|
||||||
|
adminClient.realm(REALM_NAME).groups().group(groupRep.getId()).roles().realmLevel().add(rolesToAdd);
|
||||||
|
|
||||||
|
roleResource = adminClient.realm(REALM_NAME).roles().get(role.toRepresentation().getName());
|
||||||
|
|
||||||
|
Set<GroupRepresentation> groupsInRole = roleResource.getRoleGroupMembers();
|
||||||
|
assertTrue(groupsInRole.stream().filter(g -> g.getPath().equals("/test-role-group")).findFirst().isPresent());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* KEYCLOAK-4978 Verifies that Role with no users assigned is being properly retrieved without groups in API endpoint for role membership
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testGroupsNotInRole() {
|
||||||
|
RoleResource role = resource.get("role-without-users");
|
||||||
|
|
||||||
|
role = adminClient.realm(REALM_NAME).roles().get(role.toRepresentation().getName());
|
||||||
|
|
||||||
|
Set<GroupRepresentation> groupsInRole = role.getRoleGroupMembers();
|
||||||
|
assertTrue(groupsInRole.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* KEYCLOAK-2035 Verifies that Role Membership is ok after user removal
|
* KEYCLOAK-2035 Verifies that Role Membership is ok after user removal
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in a new issue