* fix: closes #21095 * Added overloaded version of GroupUtils.toGroupHierarchy with additional full parameter.
This commit is contained in:
parent
561bd65b4a
commit
817f129484
4 changed files with 96 additions and 60 deletions
|
@ -1,8 +1,6 @@
|
|||
package org.keycloak.admin.ui.rest;
|
||||
|
||||
import static org.keycloak.models.utils.ModelToRepresentation.toRepresentation;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.DefaultValue;
|
||||
|
@ -15,15 +13,13 @@ import org.eclipse.microprofile.openapi.annotations.enums.SchemaType;
|
|||
import org.eclipse.microprofile.openapi.annotations.media.Content;
|
||||
import org.eclipse.microprofile.openapi.annotations.media.Schema;
|
||||
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.utils.ModelToRepresentation;
|
||||
import org.keycloak.representations.idm.GroupRepresentation;
|
||||
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
|
||||
import org.keycloak.services.resources.admin.permissions.GroupPermissionEvaluator;
|
||||
import org.keycloak.utils.StringUtil;
|
||||
import org.keycloak.utils.GroupUtils;
|
||||
|
||||
public class GroupsResource {
|
||||
private final KeycloakSession session;
|
||||
|
@ -68,49 +64,6 @@ public class GroupsResource {
|
|||
|
||||
boolean canViewGlobal = groupsEvaluator.canView();
|
||||
return stream.filter(group -> canViewGlobal || groupsEvaluator.canView(group))
|
||||
.map(group -> toGroupHierarchy(group, search, exact));
|
||||
}
|
||||
|
||||
private GroupRepresentation toGroupHierarchy(GroupModel group, final String search, boolean exact) {
|
||||
GroupRepresentation rep = toRepresentation(group, true);
|
||||
rep.setSubGroups(group.getSubGroupsStream().filter(g ->
|
||||
groupMatchesSearchOrIsPathElement(
|
||||
g, search
|
||||
)
|
||||
).map(subGroup ->
|
||||
ModelToRepresentation.toGroupHierarchy(
|
||||
subGroup, true, search, exact
|
||||
)
|
||||
|
||||
).collect(Collectors.toList()));
|
||||
|
||||
if (Profile.isFeatureEnabled(Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ)) {
|
||||
setAccess(group, rep);
|
||||
}
|
||||
|
||||
return rep;
|
||||
}
|
||||
|
||||
// set fine-grained access for each group in the tree
|
||||
private void setAccess(GroupModel groupTree, GroupRepresentation rootGroup) {
|
||||
if (rootGroup == null) return;
|
||||
|
||||
rootGroup.setAccess(auth.groups().getAccess(groupTree));
|
||||
|
||||
rootGroup.getSubGroups().stream().forEach(subGroup -> {
|
||||
GroupModel foundGroupModel = groupTree.getSubGroupsStream().filter(g -> g.getId().equals(subGroup.getId())).findFirst().get();
|
||||
setAccess(foundGroupModel, subGroup);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private static boolean groupMatchesSearchOrIsPathElement(GroupModel group, String search) {
|
||||
if (StringUtil.isBlank(search)) {
|
||||
return true;
|
||||
}
|
||||
if (group.getName().contains(search)) {
|
||||
return true;
|
||||
}
|
||||
return group.getSubGroupsStream().findAny().isPresent();
|
||||
.map(group -> GroupUtils.toGroupHierarchy(groupsEvaluator, group, search, exact));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -151,6 +151,13 @@ public class ModelToRepresentation {
|
|||
}
|
||||
|
||||
public static Stream<GroupRepresentation> searchGroupsByAttributes(KeycloakSession session, RealmModel realm, boolean full, boolean populateHierarchy, Map<String,String> attributes, Integer first, Integer max) {
|
||||
Stream<GroupModel> groups = searchGroupModelsByAttributes(session, realm, full, populateHierarchy, attributes, first, max);
|
||||
// and then turn the result into GroupRepresentations creating whole hierarchy of child groups for each root group
|
||||
return groups.map(g -> toGroupHierarchy(g, full, attributes));
|
||||
|
||||
}
|
||||
|
||||
public static Stream<GroupModel> searchGroupModelsByAttributes(KeycloakSession session, RealmModel realm, boolean full, boolean populateHierarchy, Map<String,String> attributes, Integer first, Integer max) {
|
||||
Stream<GroupModel> groups = session.groups().searchGroupsByAttributes(realm, attributes, first, max);
|
||||
if(populateHierarchy) {
|
||||
groups = groups
|
||||
|
@ -166,13 +173,16 @@ public class ModelToRepresentation {
|
|||
// More child groups of one root can fulfill the search, so we need to filter duplicates
|
||||
.filter(StreamsUtil.distinctByKey(GroupModel::getId));
|
||||
}
|
||||
// and then turn the result into GroupRepresentations creating whole hierarchy of child groups for each root group
|
||||
return groups.map(g -> toGroupHierarchy(g, full, attributes));
|
||||
return groups;
|
||||
}
|
||||
|
||||
public static Stream<GroupRepresentation> searchForGroupByName(KeycloakSession session, RealmModel realm, boolean full, String search, Boolean exact, Integer first, Integer max) {
|
||||
return session.groups().searchForGroupByNameStream(realm, search, exact, first, max)
|
||||
.map(g -> toGroupHierarchy(g, full, search, exact));
|
||||
return searchForGroupModelByName(session, realm, full, search, exact, first, max)
|
||||
.map(g -> toGroupHierarchy(g, full, search, exact));
|
||||
}
|
||||
|
||||
public static Stream<GroupModel> searchForGroupModelByName(KeycloakSession session, RealmModel realm, boolean full, String search, Boolean exact, Integer first, Integer max) {
|
||||
return session.groups().searchForGroupByNameStream(realm, search, exact, first, max);
|
||||
}
|
||||
|
||||
public static Stream<GroupRepresentation> searchForGroupByName(UserModel user, boolean full, String search, Integer first, Integer max) {
|
||||
|
@ -181,8 +191,12 @@ public class ModelToRepresentation {
|
|||
}
|
||||
|
||||
public static Stream<GroupRepresentation> toGroupHierarchy(RealmModel realm, boolean full, Integer first, Integer max) {
|
||||
return realm.getTopLevelGroupsStream(first, max)
|
||||
.map(g -> toGroupHierarchy(g, full));
|
||||
return toGroupModelHierarchy(realm, full, first, max)
|
||||
.map(g -> toGroupHierarchy(g, full));
|
||||
}
|
||||
|
||||
public static Stream<GroupModel> toGroupModelHierarchy(RealmModel realm, boolean full, Integer first, Integer max) {
|
||||
return realm.getTopLevelGroupsStream(first, max);
|
||||
}
|
||||
|
||||
public static Stream<GroupRepresentation> toGroupHierarchy(UserModel user, boolean full, Integer first, Integer max) {
|
||||
|
|
|
@ -21,6 +21,7 @@ import org.eclipse.microprofile.openapi.annotations.extensions.Extension;
|
|||
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||
import jakarta.ws.rs.NotFoundException;
|
||||
|
||||
import org.keycloak.common.util.ObjectUtil;
|
||||
import org.keycloak.events.admin.OperationType;
|
||||
import org.keycloak.events.admin.ResourceType;
|
||||
|
@ -33,6 +34,8 @@ import org.keycloak.representations.idm.GroupRepresentation;
|
|||
import org.keycloak.services.ErrorResponse;
|
||||
import org.keycloak.services.resources.KeycloakOpenAPI;
|
||||
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
|
||||
import org.keycloak.services.resources.admin.permissions.GroupPermissionEvaluator;
|
||||
import org.keycloak.utils.GroupUtils;
|
||||
import org.keycloak.utils.SearchQueryUtils;
|
||||
|
||||
import jakarta.ws.rs.Consumes;
|
||||
|
@ -88,18 +91,24 @@ public class GroupsResource {
|
|||
@QueryParam("max") Integer maxResults,
|
||||
@QueryParam("briefRepresentation") @DefaultValue("true") boolean briefRepresentation,
|
||||
@QueryParam("populateHierarchy") @DefaultValue("true") boolean populateHierarchy) {
|
||||
auth.groups().requireList();
|
||||
GroupPermissionEvaluator groupsEvaluator = auth.groups();
|
||||
groupsEvaluator.requireList();
|
||||
|
||||
Stream<GroupModel> stream = null;
|
||||
if (Objects.nonNull(searchQuery)) {
|
||||
Map<String, String> attributes = SearchQueryUtils.getFields(searchQuery);
|
||||
return ModelToRepresentation.searchGroupsByAttributes(session, realm, !briefRepresentation, populateHierarchy, attributes, firstResult, maxResults);
|
||||
stream = ModelToRepresentation.searchGroupModelsByAttributes(session, realm, !briefRepresentation, populateHierarchy, attributes, firstResult, maxResults);
|
||||
} else if (Objects.nonNull(search)) {
|
||||
return ModelToRepresentation.searchForGroupByName(session, realm, !briefRepresentation, search.trim(), exact, firstResult, maxResults);
|
||||
stream = ModelToRepresentation.searchForGroupModelByName(session, realm, !briefRepresentation, search.trim(), exact, firstResult, maxResults);
|
||||
} else if(Objects.nonNull(firstResult) && Objects.nonNull(maxResults)) {
|
||||
return ModelToRepresentation.toGroupHierarchy(realm, !briefRepresentation, firstResult, maxResults);
|
||||
stream = ModelToRepresentation.toGroupModelHierarchy(realm, !briefRepresentation, firstResult, maxResults);
|
||||
} else {
|
||||
return ModelToRepresentation.toGroupHierarchy(realm, !briefRepresentation);
|
||||
stream = realm.getTopLevelGroupsStream();
|
||||
}
|
||||
|
||||
boolean canViewGlobal = groupsEvaluator.canView();
|
||||
return stream.filter(group -> canViewGlobal || groupsEvaluator.canView(group))
|
||||
.map(group -> GroupUtils.toGroupHierarchy(groupsEvaluator, group, search, exact, !briefRepresentation));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
60
services/src/main/java/org/keycloak/utils/GroupUtils.java
Normal file
60
services/src/main/java/org/keycloak/utils/GroupUtils.java
Normal file
|
@ -0,0 +1,60 @@
|
|||
package org.keycloak.utils;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.utils.ModelToRepresentation;
|
||||
import org.keycloak.representations.idm.GroupRepresentation;
|
||||
import org.keycloak.services.resources.admin.permissions.GroupPermissionEvaluator;
|
||||
|
||||
public class GroupUtils {
|
||||
// Moved out from org.keycloak.admin.ui.rest.GroupsResource
|
||||
public static GroupRepresentation toGroupHierarchy(GroupPermissionEvaluator groupsEvaluator, GroupModel group, final String search, boolean exact) {
|
||||
return toGroupHierarchy(groupsEvaluator, group, search, exact, true);
|
||||
}
|
||||
|
||||
public static GroupRepresentation toGroupHierarchy(GroupPermissionEvaluator groupsEvaluator, GroupModel group, final String search, boolean exact, boolean full) {
|
||||
GroupRepresentation rep = ModelToRepresentation.toRepresentation(group, full);
|
||||
rep.setSubGroups(group.getSubGroupsStream().filter(g ->
|
||||
groupMatchesSearchOrIsPathElement(
|
||||
g, search
|
||||
)
|
||||
).map(subGroup ->
|
||||
ModelToRepresentation.toGroupHierarchy(
|
||||
subGroup, full, search, exact
|
||||
)
|
||||
|
||||
).collect(Collectors.toList()));
|
||||
|
||||
if (Profile.isFeatureEnabled(Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ)) {
|
||||
setAccess(groupsEvaluator, group, rep);
|
||||
}
|
||||
|
||||
return rep;
|
||||
}
|
||||
|
||||
//From org.keycloak.admin.ui.rest.GroupsResource
|
||||
// set fine-grained access for each group in the tree
|
||||
private static void setAccess(GroupPermissionEvaluator groupsEvaluator, GroupModel groupTree, GroupRepresentation rootGroup) {
|
||||
if (rootGroup == null) return;
|
||||
|
||||
rootGroup.setAccess(groupsEvaluator.getAccess(groupTree));
|
||||
|
||||
rootGroup.getSubGroups().stream().forEach(subGroup -> {
|
||||
GroupModel foundGroupModel = groupTree.getSubGroupsStream().filter(g -> g.getId().equals(subGroup.getId())).findFirst().get();
|
||||
setAccess(groupsEvaluator, foundGroupModel, subGroup);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private static boolean groupMatchesSearchOrIsPathElement(GroupModel group, String search) {
|
||||
if (StringUtil.isBlank(search)) {
|
||||
return true;
|
||||
}
|
||||
if (group.getName().contains(search)) {
|
||||
return true;
|
||||
}
|
||||
return group.getSubGroupsStream().findAny().isPresent();
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue