KEYCLOAK-4978 Add endpoint to get groups by role

This commit is contained in:
Axel Messinese 2018-12-14 00:47:27 +01:00 committed by Marek Posolda
parent a3c175a21e
commit e18fb56389
7 changed files with 133 additions and 2 deletions

View file

@ -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;
@ -127,4 +128,30 @@ public interface RoleResource {
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);
} }

View file

@ -879,6 +879,11 @@ public class RealmCacheSession implements CacheRealmProvider {
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) {
String cacheKey = getTopGroupsQueryCacheKey(realm.getId()); String cacheKey = getTopGroupsQueryCacheKey(realm.getId());

View file

@ -386,6 +386,25 @@ public class JpaRealmProvider implements RealmProvider {
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) {
RealmEntity ref = em.getReference(RealmEntity.class, realm.getId()); RealmEntity ref = em.getReference(RealmEntity.class, realm.getId());

View file

@ -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"),

View file

@ -44,6 +44,8 @@ public interface RealmProvider extends Provider, ClientProvider {
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);
List<GroupModel> getTopLevelGroups(RealmModel realm, Integer first, Integer max); List<GroupModel> getTopLevelGroups(RealmModel realm, Integer first, Integer max);

View file

@ -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());
}
} }

View file

@ -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,6 +111,11 @@ 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();
@ -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
*/ */