mirror of
https://github.com/keycloak/keycloak.git
synced 2026-01-09 23:12:06 -03:30
Support partial evaluation for the group resource type
Closes #38273 Signed-off-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
parent
f2628a9615
commit
1c57035d41
@ -95,7 +95,6 @@ public class RealmCacheManager extends CacheManager {
|
||||
|
||||
public void groupQueriesInvalidations(String realmId, Set<String> invalidations) {
|
||||
invalidations.add(RealmCacheSession.getGroupsQueryCacheKey(realmId));
|
||||
invalidations.add(RealmCacheSession.getTopGroupsQueryCacheKey(realmId));
|
||||
addInvalidations(GroupListPredicate.create().realm(realmId), invalidations);
|
||||
}
|
||||
|
||||
|
||||
@ -632,10 +632,6 @@ public class RealmCacheSession implements CacheRealmProvider {
|
||||
return client + "." + (defaultScope ? SCOPE_KEY_DEFAULT : SCOPE_KEY_OPTIONAL) + ".clientscopes";
|
||||
}
|
||||
|
||||
static String getTopGroupsQueryCacheKey(String realm) {
|
||||
return realm + ".top.groups";
|
||||
}
|
||||
|
||||
static String getGroupByNameCacheKey(String realm, String parentId, String name) {
|
||||
if (parentId != null) {
|
||||
return realm + ".group." + parentId + "." + name;
|
||||
@ -1096,57 +1092,6 @@ public class RealmCacheSession implements CacheRealmProvider {
|
||||
|
||||
@Override
|
||||
public Stream<GroupModel> getTopLevelGroupsStream(RealmModel realm, String search, Boolean exact, Integer first, Integer max) {
|
||||
String cacheKey = getTopGroupsQueryCacheKey(realm.getId());
|
||||
|
||||
if (hasInvalidation(realm, cacheKey)) {
|
||||
return getGroupDelegate().getTopLevelGroupsStream(realm, search, exact, first, max);
|
||||
}
|
||||
|
||||
GroupListQuery query = cache.get(cacheKey, GroupListQuery.class);
|
||||
String searchKey = Optional.ofNullable(search).orElse("") + "." + Optional.ofNullable(first).orElse(-1) + "." + Optional.ofNullable(max).orElse(-1);
|
||||
Set<String> cached;
|
||||
|
||||
if (Objects.isNull(query)) {
|
||||
// not cached yet
|
||||
Long loaded = cache.getCurrentRevision(cacheKey);
|
||||
cached = getGroupDelegate().getTopLevelGroupsStream(realm, search, exact, first, max).map(GroupModel::getId).collect(Collectors.toSet());
|
||||
query = new GroupListQuery(loaded, cacheKey, realm, searchKey, cached);
|
||||
logger.tracev("adding realm getTopLevelGroups cache miss: realm {0} key {1}", realm.getName(), cacheKey);
|
||||
cache.addRevisioned(query, startupRevision);
|
||||
} else {
|
||||
logger.tracev("getTopLevelGroups cache hit: {0}", realm.getName());
|
||||
|
||||
cached = query.getGroups(searchKey);
|
||||
|
||||
if (hasInvalidation(realm, cacheKey) || cached == null) {
|
||||
// there is a cache entry, but the current search is not yet cached
|
||||
cache.invalidateObject(cacheKey);
|
||||
Long loaded = cache.getCurrentRevision(cacheKey);
|
||||
cached = getGroupDelegate().getTopLevelGroupsStream(realm, search, exact, first, max).map(GroupModel::getId).collect(Collectors.toSet());
|
||||
query = new GroupListQuery(loaded, cacheKey, realm, searchKey, cached, query);
|
||||
logger.tracev("adding realm getTopLevelGroups search cache miss: realm {0} key {1}", realm.getName(), searchKey);
|
||||
cache.addRevisioned(query, cache.getCurrentCounter());
|
||||
}
|
||||
}
|
||||
|
||||
AtomicBoolean invalidate = new AtomicBoolean(false);
|
||||
Stream<GroupModel> groups = cached.stream()
|
||||
.map((id) -> session.groups().getGroupById(realm, id))
|
||||
.takeWhile(group -> {
|
||||
if (Objects.isNull(group)) {
|
||||
invalidate.set(true);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.sorted(GroupModel.COMPARE_BY_NAME);
|
||||
|
||||
if (!invalidate.get()) {
|
||||
return groups;
|
||||
}
|
||||
|
||||
invalidations.add(cacheKey);
|
||||
|
||||
return getGroupDelegate().getTopLevelGroupsStream(realm, search, exact, first, max);
|
||||
}
|
||||
|
||||
|
||||
@ -17,6 +17,11 @@
|
||||
|
||||
package org.keycloak.models.jpa;
|
||||
|
||||
import jakarta.persistence.criteria.CriteriaBuilder;
|
||||
import jakarta.persistence.criteria.CriteriaQuery;
|
||||
import jakarta.persistence.criteria.Predicate;
|
||||
import jakarta.persistence.criteria.Root;
|
||||
import org.keycloak.authorization.AdminPermissionsSchema;
|
||||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
@ -136,17 +141,32 @@ public class GroupAdapter implements GroupModel , JpaModel<GroupEntity> {
|
||||
|
||||
@Override
|
||||
public Stream<GroupModel> getSubGroupsStream(String search, Boolean exact, Integer firstResult, Integer maxResults) {
|
||||
TypedQuery<String> query;
|
||||
if (Boolean.TRUE.equals(exact)) {
|
||||
query = em.createNamedQuery("getGroupIdsByParentAndName", String.class);
|
||||
} else {
|
||||
query = em.createNamedQuery("getGroupIdsByParentAndNameContaining", String.class);
|
||||
}
|
||||
query.setParameter("realm", realm.getId())
|
||||
.setParameter("parent", group.getId())
|
||||
.setParameter("search", search == null ? "" : search);
|
||||
CriteriaBuilder builder = em.getCriteriaBuilder();
|
||||
CriteriaQuery<String> queryBuilder = builder.createQuery(String.class);
|
||||
Root<GroupEntity> root = queryBuilder.from(GroupEntity.class);
|
||||
|
||||
return closing(paginateQuery(query, firstResult, maxResults).getResultStream()
|
||||
queryBuilder.select(root.get("id"));
|
||||
|
||||
List<Predicate> predicates = new ArrayList<>();
|
||||
|
||||
predicates.add(builder.equal(root.get("realm"), realm.getId()));
|
||||
predicates.add(builder.equal(root.get("type"), Type.REALM.intValue()));
|
||||
predicates.add(builder.equal(root.get("parentId"), group.getId()));
|
||||
|
||||
search = search == null ? "" : search;
|
||||
|
||||
if (Boolean.TRUE.equals(exact)) {
|
||||
predicates.add(builder.like(root.get("name"), search));
|
||||
} else {
|
||||
predicates.add(builder.like(builder.lower(root.get("name")), builder.lower(builder.literal("%" + search + "%"))));
|
||||
}
|
||||
|
||||
predicates.addAll(AdminPermissionsSchema.SCHEMA.applyAuthorizationFilters(session, AdminPermissionsSchema.GROUPS, realm, builder, queryBuilder, root));
|
||||
|
||||
queryBuilder.where(predicates.toArray(new Predicate[0]));
|
||||
queryBuilder.orderBy(builder.asc(root.get("name")));
|
||||
|
||||
return closing(paginateQuery(em.createQuery(queryBuilder), firstResult, maxResults).getResultStream()
|
||||
.map(realm::getGroupById)
|
||||
// In concurrent tests, the group might be deleted in another thread, therefore, skip those null values.
|
||||
.filter(Objects::nonNull)
|
||||
@ -155,10 +175,22 @@ public class GroupAdapter implements GroupModel , JpaModel<GroupEntity> {
|
||||
|
||||
@Override
|
||||
public Long getSubGroupsCount() {
|
||||
return em.createNamedQuery("getGroupCountByParent", Long.class)
|
||||
.setParameter("realm", realm.getId())
|
||||
.setParameter("parent", group.getId())
|
||||
.getSingleResult();
|
||||
CriteriaBuilder builder = em.getCriteriaBuilder();
|
||||
CriteriaQuery<Long> queryBuilder = builder.createQuery(Long.class);
|
||||
Root<GroupEntity> root = queryBuilder.from(GroupEntity.class);
|
||||
|
||||
queryBuilder.select(builder.count(root.get("id")));
|
||||
|
||||
List<Predicate> predicates = new ArrayList<>();
|
||||
|
||||
predicates.add(builder.equal(root.get("realm"), realm.getId()));
|
||||
predicates.add(builder.equal(root.get("type"), Type.REALM.intValue()));
|
||||
predicates.add(builder.equal(root.get("parentId"), group.getId()));
|
||||
predicates.addAll(AdminPermissionsSchema.SCHEMA.applyAuthorizationFilters(session, AdminPermissionsSchema.GROUPS, realm, builder, queryBuilder, root));
|
||||
|
||||
queryBuilder.where(predicates.toArray(new Predicate[0]));
|
||||
|
||||
return em.createQuery(queryBuilder).getSingleResult();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -43,6 +43,7 @@ import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import org.hibernate.Session;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.authorization.AdminPermissionsSchema;
|
||||
import org.keycloak.client.clienttype.ClientTypeManager;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.common.util.Time;
|
||||
@ -519,16 +520,29 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, ClientSc
|
||||
|
||||
@Override
|
||||
public GroupModel getGroupByName(RealmModel realm, GroupModel parent, String name) {
|
||||
TypedQuery<String> query = em.createNamedQuery("getGroupIdsByParentAndName", String.class);
|
||||
query.setParameter("search", name);
|
||||
query.setParameter("realm", realm.getId());
|
||||
query.setParameter("parent", parent != null ? parent.getId() : GroupEntity.TOP_PARENT_ID);
|
||||
List<String> entities = query.getResultList();
|
||||
if (entities.isEmpty()) return null;
|
||||
if (entities.size() > 1) throw new IllegalStateException("Should not be more than one Group with same name");
|
||||
String id = query.getResultList().get(0);
|
||||
CriteriaBuilder builder = em.getCriteriaBuilder();
|
||||
CriteriaQuery<String> queryBuilder = builder.createQuery(String.class);
|
||||
Root<GroupEntity> root = queryBuilder.from(GroupEntity.class);
|
||||
|
||||
return session.groups().getGroupById(realm, id);
|
||||
queryBuilder.select(root.get("id"));
|
||||
|
||||
List<Predicate> predicates = new ArrayList<>();
|
||||
|
||||
predicates.add(builder.equal(root.get("realm"), realm.getId()));
|
||||
predicates.add(builder.equal(root.get("type"), Type.REALM.intValue()));
|
||||
predicates.add(builder.equal(root.get("parentId"), parent != null ? parent.getId() : GroupEntity.TOP_PARENT_ID));
|
||||
predicates.add(builder.like(root.get("name"), name));
|
||||
predicates.addAll(AdminPermissionsSchema.SCHEMA.applyAuthorizationFilters(session, AdminPermissionsSchema.GROUPS, null, realm, builder, queryBuilder, root));
|
||||
|
||||
queryBuilder.where(predicates.toArray(new Predicate[0]));
|
||||
queryBuilder.orderBy(builder.asc(root.get("name")));
|
||||
|
||||
List<String> groups = em.createQuery(queryBuilder).getResultList();
|
||||
|
||||
if (groups.isEmpty()) return null;
|
||||
if (groups.size() > 1) throw new IllegalStateException("Should not be more than one Group with same name");
|
||||
|
||||
return session.groups().getGroupById(realm, groups.get(0));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -560,10 +574,24 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, ClientSc
|
||||
|
||||
@Override
|
||||
public Stream<GroupModel> getGroupsStream(RealmModel realm) {
|
||||
return closing(em.createNamedQuery("getGroupIdsByRealm", String.class)
|
||||
.setParameter("realm", realm.getId())
|
||||
.getResultStream())
|
||||
.map(g -> session.groups().getGroupById(realm, g));
|
||||
CriteriaBuilder builder = em.getCriteriaBuilder();
|
||||
CriteriaQuery<String> queryBuilder = builder.createQuery(String.class);
|
||||
Root<GroupEntity> root = queryBuilder.from(GroupEntity.class);
|
||||
|
||||
queryBuilder.select(root.get("id"));
|
||||
|
||||
List<Predicate> predicates = new ArrayList<>();
|
||||
|
||||
predicates.add(builder.equal(root.get("realm"), realm.getId()));
|
||||
predicates.add(builder.equal(root.get("type"), Type.REALM.intValue()));
|
||||
predicates.addAll(AdminPermissionsSchema.SCHEMA.applyAuthorizationFilters(session, AdminPermissionsSchema.GROUPS, null, realm, builder, queryBuilder, root));
|
||||
|
||||
queryBuilder.where(predicates.toArray(new Predicate[0]));
|
||||
queryBuilder.orderBy(builder.asc(root.get("name")));
|
||||
|
||||
return closing(em.createQuery(queryBuilder).getResultStream())
|
||||
.map(g -> session.groups().getGroupById(realm, g))
|
||||
.filter(Objects::nonNull);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -575,13 +603,26 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, ClientSc
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
TypedQuery<String> query = em.createNamedQuery("getGroupIdsByNameContainingFromIdList", String.class)
|
||||
.setParameter("realm", realm.getId())
|
||||
.setParameter("search", search)
|
||||
.setParameter("ids", idsList);
|
||||
CriteriaBuilder builder = em.getCriteriaBuilder();
|
||||
CriteriaQuery<String> queryBuilder = builder.createQuery(String.class);
|
||||
Root<GroupEntity> root = queryBuilder.from(GroupEntity.class);
|
||||
|
||||
return closing(paginateQuery(query, first, max).getResultStream())
|
||||
.map(g -> session.groups().getGroupById(realm, g)).filter(Objects::nonNull);
|
||||
queryBuilder.select(root.get("id"));
|
||||
|
||||
List<Predicate> predicates = new ArrayList<>();
|
||||
|
||||
predicates.add(builder.equal(root.get("realm"), realm.getId()));
|
||||
predicates.add(builder.equal(root.get("type"), Type.REALM.intValue()));
|
||||
predicates.add(builder.like(builder.lower(root.get("name")), builder.lower(builder.literal("%" + search + "%"))));
|
||||
predicates.add(root.get("id").in(idsList));
|
||||
predicates.addAll(AdminPermissionsSchema.SCHEMA.applyAuthorizationFilters(session, AdminPermissionsSchema.GROUPS, realm, builder, queryBuilder, root));
|
||||
|
||||
queryBuilder.where(predicates.toArray(new Predicate[0]));
|
||||
queryBuilder.orderBy(builder.asc(root.get("name")));
|
||||
|
||||
return closing(paginateQuery(em.createQuery(queryBuilder), first, max).getResultStream())
|
||||
.map(g -> session.groups().getGroupById(realm, g))
|
||||
.filter(Objects::nonNull);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -590,18 +631,30 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, ClientSc
|
||||
return getGroupsStream(realm, ids);
|
||||
}
|
||||
|
||||
List<String> idsList = ids.collect(Collectors.toList());
|
||||
List<String> idsList = ids.toList();
|
||||
if (idsList.isEmpty()) {
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
TypedQuery<String> query = em.createNamedQuery("getGroupIdsFromIdList", String.class)
|
||||
.setParameter("realm", realm.getId())
|
||||
.setParameter("ids", idsList);
|
||||
CriteriaBuilder builder = em.getCriteriaBuilder();
|
||||
CriteriaQuery<String> queryBuilder = builder.createQuery(String.class);
|
||||
Root<GroupEntity> root = queryBuilder.from(GroupEntity.class);
|
||||
|
||||
queryBuilder.select(root.get("id"));
|
||||
|
||||
return closing(paginateQuery(query, first, max).getResultStream())
|
||||
.map(g -> session.groups().getGroupById(realm, g)).filter(Objects::nonNull);
|
||||
List<Predicate> predicates = new ArrayList<>();
|
||||
|
||||
predicates.add(builder.equal(root.get("realm"), realm.getId()));
|
||||
predicates.add(builder.equal(root.get("type"), Type.REALM.intValue()));
|
||||
predicates.add(root.get("id").in(idsList));
|
||||
predicates.addAll(AdminPermissionsSchema.SCHEMA.applyAuthorizationFilters(session, AdminPermissionsSchema.GROUPS, realm, builder, queryBuilder, root));
|
||||
|
||||
queryBuilder.where(predicates.toArray(new Predicate[0]));
|
||||
queryBuilder.orderBy(builder.asc(root.get("name")));
|
||||
|
||||
return closing(em.createQuery(queryBuilder).getResultStream())
|
||||
.map(g -> session.groups().getGroupById(realm, g))
|
||||
.filter(Objects::nonNull);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -611,31 +664,52 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, ClientSc
|
||||
|
||||
@Override
|
||||
public Long getGroupsCount(RealmModel realm, Stream<String> ids, String search) {
|
||||
TypedQuery<Long> query;
|
||||
CriteriaBuilder builder = em.getCriteriaBuilder();
|
||||
CriteriaQuery<Long> queryBuilder = builder.createQuery(Long.class);
|
||||
Root<GroupEntity> root = queryBuilder.from(GroupEntity.class);
|
||||
|
||||
queryBuilder.select(builder.count(root.get("id")));
|
||||
|
||||
List<Predicate> predicates = new ArrayList<>();
|
||||
|
||||
predicates.add(builder.equal(root.get("realm"), realm.getId()));
|
||||
predicates.add(builder.equal(root.get("type"), Type.REALM.intValue()));
|
||||
|
||||
if (search != null && !search.isEmpty()) {
|
||||
query = em.createNamedQuery("getGroupCountByNameContainingFromIdList", Long.class)
|
||||
.setParameter("search", search);
|
||||
} else {
|
||||
query = em.createNamedQuery("getGroupIdsFromIdList", Long.class);
|
||||
predicates.add(builder.like(builder.lower(root.get("name")), builder.lower(builder.literal("%" + search + "%"))));
|
||||
}
|
||||
|
||||
return query.setParameter("realm", realm.getId())
|
||||
.setParameter("ids", ids.collect(Collectors.toList()))
|
||||
.getSingleResult();
|
||||
predicates.add(root.get("id").in(ids.toList()));
|
||||
predicates.addAll(AdminPermissionsSchema.SCHEMA.applyAuthorizationFilters(session, AdminPermissionsSchema.GROUPS, realm, builder, queryBuilder, root));
|
||||
|
||||
queryBuilder.where(predicates.toArray(new Predicate[0]));
|
||||
queryBuilder.orderBy(builder.asc(root.get("name")));
|
||||
|
||||
return em.createQuery(queryBuilder).getSingleResult();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getGroupsCount(RealmModel realm, Boolean onlyTopGroups) {
|
||||
if(Objects.equals(onlyTopGroups, Boolean.TRUE)) {
|
||||
return em.createNamedQuery("getGroupCountByParent", Long.class)
|
||||
.setParameter("realm", realm.getId())
|
||||
.setParameter("parent", GroupEntity.TOP_PARENT_ID)
|
||||
.getSingleResult();
|
||||
} else {
|
||||
return em.createNamedQuery("getGroupCount", Long.class)
|
||||
.setParameter("realm", realm.getId())
|
||||
.getSingleResult();
|
||||
CriteriaBuilder builder = em.getCriteriaBuilder();
|
||||
CriteriaQuery<Long> queryBuilder = builder.createQuery(Long.class);
|
||||
Root<GroupEntity> root = queryBuilder.from(GroupEntity.class);
|
||||
|
||||
queryBuilder.select(builder.count(root.get("id")));
|
||||
|
||||
List<Predicate> predicates = new ArrayList<>();
|
||||
|
||||
predicates.add(builder.equal(root.get("realm"), realm.getId()));
|
||||
predicates.add(builder.equal(root.get("type"), Type.REALM.intValue()));
|
||||
|
||||
if (Objects.equals(onlyTopGroups, Boolean.TRUE)) {
|
||||
predicates.add(builder.equal(root.get("parentId"), GroupEntity.TOP_PARENT_ID));
|
||||
}
|
||||
|
||||
predicates.addAll(AdminPermissionsSchema.SCHEMA.applyAuthorizationFilters(session, AdminPermissionsSchema.GROUPS, realm, builder, queryBuilder, root));
|
||||
|
||||
queryBuilder.where(predicates.toArray(new Predicate[0]));
|
||||
|
||||
return em.createQuery(queryBuilder).getSingleResult();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -665,18 +739,30 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, ClientSc
|
||||
|
||||
@Override
|
||||
public Stream<GroupModel> getTopLevelGroupsStream(RealmModel realm, String search, Boolean exact, Integer firstResult, Integer maxResults) {
|
||||
TypedQuery<String> groupsQuery;
|
||||
if(Boolean.TRUE.equals(exact)) {
|
||||
groupsQuery = em.createNamedQuery("getGroupIdsByParentAndName", String.class);
|
||||
CriteriaBuilder builder = em.getCriteriaBuilder();
|
||||
CriteriaQuery<String> queryBuilder = builder.createQuery(String.class);
|
||||
Root<GroupEntity> root = queryBuilder.from(GroupEntity.class);
|
||||
|
||||
queryBuilder.select(root.get("id"));
|
||||
|
||||
List<Predicate> predicates = new ArrayList<>();
|
||||
|
||||
predicates.add(builder.equal(root.get("realm"), realm.getId()));
|
||||
predicates.add(builder.equal(root.get("type"), Type.REALM.intValue()));
|
||||
predicates.add(builder.equal(root.get("parentId"), GroupEntity.TOP_PARENT_ID));
|
||||
|
||||
if (Boolean.TRUE.equals(exact)) {
|
||||
predicates.add(builder.like(root.get("name"), search));
|
||||
} else {
|
||||
groupsQuery = em.createNamedQuery("getGroupIdsByParentAndNameContaining", String.class);
|
||||
predicates.add(builder.like(builder.lower(root.get("name")), builder.lower(builder.literal("%" + search + "%"))));
|
||||
}
|
||||
|
||||
groupsQuery.setParameter("realm", realm.getId())
|
||||
.setParameter("parent", GroupEntity.TOP_PARENT_ID)
|
||||
.setParameter("search", search);
|
||||
predicates.addAll(AdminPermissionsSchema.SCHEMA.applyAuthorizationFilters(session, AdminPermissionsSchema.GROUPS, realm, builder, queryBuilder, root));
|
||||
|
||||
return closing(paginateQuery(groupsQuery, firstResult, maxResults).getResultStream()
|
||||
queryBuilder.where(predicates.toArray(new Predicate[0]));
|
||||
queryBuilder.orderBy(builder.asc(root.get("name")));
|
||||
|
||||
return closing(paginateQuery(em.createQuery(queryBuilder), firstResult, maxResults).getResultStream()
|
||||
.map(realm::getGroupById)
|
||||
// In concurrent tests, the group might be deleted in another thread, therefore, skip those null values.
|
||||
.filter(Objects::nonNull)
|
||||
@ -1176,17 +1262,33 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, ClientSc
|
||||
}
|
||||
@Override
|
||||
public Stream<GroupModel> searchForGroupByNameStream(RealmModel realm, String search, Boolean exact, Integer first, Integer max) {
|
||||
TypedQuery<String> query;
|
||||
if (Boolean.TRUE.equals(exact)) {
|
||||
query = em.createNamedQuery("getGroupIdsByName", String.class);
|
||||
} else {
|
||||
query = em.createNamedQuery("getGroupIdsByNameContaining", String.class);
|
||||
}
|
||||
query.setParameter("realm", realm.getId())
|
||||
.setParameter("search", search);
|
||||
Stream<String> groups = paginateQuery(query, first, max).getResultStream();
|
||||
CriteriaBuilder builder = em.getCriteriaBuilder();
|
||||
CriteriaQuery<String> queryBuilder = builder.createQuery(String.class);
|
||||
Root<GroupEntity> root = queryBuilder.from(GroupEntity.class);
|
||||
|
||||
return closing(groups.map(id -> session.groups().getGroupById(realm, id)).filter(Objects::nonNull).sorted(GroupModel.COMPARE_BY_NAME).distinct());
|
||||
queryBuilder.select(root.get("id"));
|
||||
|
||||
List<Predicate> predicates = new ArrayList<>();
|
||||
|
||||
predicates.add(builder.equal(root.get("realm"), realm.getId()));
|
||||
predicates.add(builder.equal(root.get("type"), Type.REALM.intValue()));
|
||||
|
||||
if (Boolean.TRUE.equals(exact)) {
|
||||
predicates.add(builder.equal(root.get("name"), search));
|
||||
} else {
|
||||
predicates.add(builder.like(builder.lower(root.get("name")), builder.lower(builder.literal("%" + search + "%"))));
|
||||
}
|
||||
|
||||
predicates.addAll(AdminPermissionsSchema.SCHEMA.applyAuthorizationFilters(session, AdminPermissionsSchema.GROUPS, realm, builder, queryBuilder, root));
|
||||
|
||||
queryBuilder.where(predicates.toArray(new Predicate[0]));
|
||||
queryBuilder.orderBy(builder.asc(root.get("name")));
|
||||
|
||||
return closing(paginateQuery(em.createQuery(queryBuilder), first, max).getResultStream()
|
||||
.map(id -> session.groups().getGroupById(realm, id))
|
||||
.filter(Objects::nonNull)
|
||||
.sorted(GroupModel.COMPARE_BY_NAME)
|
||||
.distinct());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1219,6 +1321,8 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, ClientSc
|
||||
predicates.add(builder.and(attrNamePredicate, attrValuePredicate));
|
||||
}
|
||||
|
||||
predicates.addAll(AdminPermissionsSchema.SCHEMA.applyAuthorizationFilters(session, AdminPermissionsSchema.GROUPS, realm, builder, queryBuilder, root));
|
||||
|
||||
Predicate finalPredicate = builder.and(predicates.toArray(new Predicate[0]));
|
||||
queryBuilder.where(finalPredicate).orderBy(builder.asc(root.get("name")));
|
||||
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
|
||||
package org.keycloak.models.jpa;
|
||||
|
||||
import jakarta.persistence.criteria.Path;
|
||||
import org.keycloak.authorization.AdminPermissionsSchema;
|
||||
import org.keycloak.authorization.jpa.entities.ResourceEntity;
|
||||
import org.keycloak.authorization.policy.provider.PartialEvaluationStorageProvider;
|
||||
@ -74,7 +75,6 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@ -496,9 +496,7 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore, Parti
|
||||
|
||||
@Override
|
||||
public Stream<UserModel> getGroupMembersStream(RealmModel realm, GroupModel group) {
|
||||
TypedQuery<UserEntity> query = em.createNamedQuery("groupMembership", UserEntity.class);
|
||||
query.setParameter("groupId", group.getId());
|
||||
return closing(query.getResultStream().map(entity -> new UserAdapter(session, realm, em, entity)));
|
||||
return getGroupMembersStream(realm, group, -1, -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -698,7 +696,7 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore, Parti
|
||||
|
||||
session.setAttribute(UserModel.GROUPS, groupIds);
|
||||
|
||||
restrictions.addAll(AdminPermissionsSchema.SCHEMA.applyAuthorizationFilters(session, AdminPermissionsSchema.USERS, this, realm, cb, countQuery));
|
||||
restrictions.addAll(AdminPermissionsSchema.SCHEMA.applyAuthorizationFilters(session, AdminPermissionsSchema.USERS, this, realm, cb, countQuery, root));
|
||||
|
||||
countQuery.where(restrictions.toArray(Predicate[]::new));
|
||||
TypedQuery<Long> query = em.createQuery(countQuery);
|
||||
@ -709,27 +707,70 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore, Parti
|
||||
|
||||
@Override
|
||||
public Stream<UserModel> getGroupMembersStream(RealmModel realm, GroupModel group, Integer firstResult, Integer maxResults) {
|
||||
TypedQuery<UserEntity> query = em.createNamedQuery("groupMembership", UserEntity.class);
|
||||
query.setParameter("groupId", group.getId());
|
||||
CriteriaBuilder builder = em.getCriteriaBuilder();
|
||||
CriteriaQuery<UserEntity> queryBuilder = builder.createQuery(UserEntity.class);
|
||||
Root<UserGroupMembershipEntity> root = queryBuilder.from(UserGroupMembershipEntity.class);
|
||||
Path<UserEntity> userPath = root.get("user");
|
||||
|
||||
return closing(paginateQuery(query, firstResult, maxResults).getResultStream().map(user -> new UserAdapter(session, realm, em, user)));
|
||||
queryBuilder.select(userPath);
|
||||
|
||||
List<Predicate> predicates = new ArrayList<>();
|
||||
|
||||
predicates.add(builder.equal(root.get("groupId"), group.getId()));
|
||||
|
||||
predicates.addAll(AdminPermissionsSchema.SCHEMA.applyAuthorizationFilters(session, AdminPermissionsSchema.USERS, this, realm, builder, queryBuilder, userPath));
|
||||
|
||||
queryBuilder.where(predicates.toArray(Predicate[]::new)).orderBy(builder.asc(userPath.get(UserModel.USERNAME)));
|
||||
|
||||
return closing(paginateQuery(em.createQuery(queryBuilder), firstResult, maxResults).getResultStream().map(user -> new UserAdapter(session, realm, em, user)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<UserModel> getGroupMembersStream(RealmModel realm, GroupModel group, String search, Boolean exact, Integer first, Integer max) {
|
||||
TypedQuery<UserEntity> query;
|
||||
if (StringUtil.isBlank(search)) {
|
||||
query = em.createNamedQuery("groupMembership", UserEntity.class);
|
||||
} else if (Boolean.TRUE.equals(exact)) {
|
||||
query = em.createNamedQuery("groupMembershipByUser", UserEntity.class);
|
||||
query.setParameter("search", search);
|
||||
} else {
|
||||
query = em.createNamedQuery("groupMembershipByUserContained", UserEntity.class);
|
||||
query.setParameter("search", search.toLowerCase());
|
||||
return getGroupMembersStream(realm, group, first, max);
|
||||
}
|
||||
query.setParameter("groupId", group.getId());
|
||||
|
||||
return closing(paginateQuery(query, first, max).getResultStream().map(user -> new UserAdapter(session, realm, em, user)));
|
||||
// select g.user from UserGroupMembershipEntity g where g.groupId = :groupId and " +
|
||||
// "(g.user.username = :search or g.user.email = :search or g.user.firstName = :search or g.user.lastName = :search) order by g.user.username
|
||||
|
||||
CriteriaBuilder builder = em.getCriteriaBuilder();
|
||||
CriteriaQuery<UserEntity> queryBuilder = builder.createQuery(UserEntity.class);
|
||||
Root<UserGroupMembershipEntity> root = queryBuilder.from(UserGroupMembershipEntity.class);
|
||||
Path<UserEntity> userPath = root.get("user");
|
||||
|
||||
queryBuilder.select(userPath);
|
||||
|
||||
List<Predicate> predicates = new ArrayList<>();
|
||||
|
||||
predicates.add(builder.equal(root.get("groupId"), group.getId()));
|
||||
|
||||
if (Boolean.TRUE.equals(exact)) {
|
||||
predicates.add(builder.and(
|
||||
builder.or(
|
||||
builder.equal(userPath.get("username"), search)),
|
||||
builder.equal(userPath.get("email"), search),
|
||||
builder.equal(userPath.get("firstName"), search),
|
||||
builder.equal(userPath.get("lastName"), search)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
predicates.add(builder.and(
|
||||
builder.or(
|
||||
builder.like(builder.lower(userPath.get("username")), builder.lower(builder.literal("%" + search + "%"))),
|
||||
builder.like(builder.lower(userPath.get("email")), builder.lower(builder.literal("%" + search + "%"))),
|
||||
builder.like(builder.lower(userPath.get("firstName")), builder.lower(builder.literal("%" + search + "%"))),
|
||||
builder.like(builder.lower(userPath.get("lastName")), builder.lower(builder.literal("%" + search + "%")))
|
||||
)
|
||||
));
|
||||
}
|
||||
|
||||
predicates.addAll(AdminPermissionsSchema.SCHEMA.applyAuthorizationFilters(session, AdminPermissionsSchema.USERS, this, realm, builder, queryBuilder, userPath));
|
||||
|
||||
queryBuilder.where(predicates.toArray(Predicate[]::new)).orderBy(builder.asc(userPath.get(UserModel.USERNAME)));
|
||||
|
||||
return closing(paginateQuery(em.createQuery(queryBuilder), first, max).getResultStream().map(user -> new UserAdapter(session, realm, em, user)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -764,7 +805,7 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore, Parti
|
||||
|
||||
predicates.add(builder.equal(root.get("realmId"), realm.getId()));
|
||||
|
||||
predicates.addAll(AdminPermissionsSchema.SCHEMA.applyAuthorizationFilters(session, AdminPermissionsSchema.USERS, this, realm, builder, queryBuilder));
|
||||
predicates.addAll(AdminPermissionsSchema.SCHEMA.applyAuthorizationFilters(session, AdminPermissionsSchema.USERS, this, realm, builder, queryBuilder, root));
|
||||
|
||||
queryBuilder.where(predicates.toArray(Predicate[]::new)).orderBy(builder.asc(root.get(UserModel.USERNAME)));
|
||||
|
||||
@ -1058,18 +1099,40 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore, Parti
|
||||
|
||||
@Override
|
||||
public List<Predicate> getFilters(EvaluationContext evaluationContext) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Set<String> userGroups = (Set<String>) session.getAttribute(UserModel.GROUPS);
|
||||
Predicate groupFilterPredicate = null;
|
||||
if (!AdminPermissionsSchema.SCHEMA.isAdminPermissionsEnabled(session.getContext().getRealm())) {
|
||||
// support for FGAP v1
|
||||
Set<String> userGroups = (Set<String>) session.getAttribute(UserModel.GROUPS);
|
||||
|
||||
if (userGroups != null) {
|
||||
groupFilterPredicate = groupsWithPermissionsSubquery(session, evaluationContext, userGroups);
|
||||
|
||||
if (userGroups != null) {
|
||||
return List.of(getFilterByGroupMembership(session, evaluationContext, userGroups));
|
||||
}
|
||||
|
||||
return List.of();
|
||||
}
|
||||
|
||||
return groupFilterPredicate == null ? List.of() : List.of(groupFilterPredicate);
|
||||
Predicate predicate = getFilterByGroupMembership(evaluationContext, false);
|
||||
|
||||
if (predicate != null) {
|
||||
return List.of(predicate);
|
||||
}
|
||||
|
||||
return List.of();
|
||||
}
|
||||
|
||||
private Predicate groupsWithPermissionsSubquery(KeycloakSession session, EvaluationContext evaluationContext, Set<String> groupIds) {
|
||||
@Override
|
||||
public List<Predicate> getNegateFilters(EvaluationContext evaluationContext) {
|
||||
Predicate predicate = getFilterByGroupMembership(evaluationContext, true);
|
||||
|
||||
if (predicate != null) {
|
||||
return List.of(predicate);
|
||||
}
|
||||
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
private Predicate getFilterByGroupMembership(KeycloakSession session, EvaluationContext evaluationContext, Set<String> groupIds) {
|
||||
CriteriaBuilder cb = em.getCriteriaBuilder();
|
||||
CriteriaQuery<?> query = evaluationContext.criteriaQuery();
|
||||
Subquery subquery = query.subquery(String.class);
|
||||
@ -1081,7 +1144,7 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore, Parti
|
||||
|
||||
subPredicates.add(from.get("groupId").in(groupIds));
|
||||
|
||||
Root<?> root = evaluationContext.getRootEntity();
|
||||
Path<?> root = evaluationContext.path();
|
||||
|
||||
subPredicates.add(cb.equal(from.get("user").get("id"), root.get("id")));
|
||||
|
||||
@ -1111,4 +1174,36 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore, Parti
|
||||
|
||||
return cb.exists(subquery);
|
||||
}
|
||||
|
||||
private Predicate getFilterByGroupMembership(EvaluationContext evaluationContext, boolean negate) {
|
||||
if (negate && evaluationContext.deniedGroupIds().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (!negate && evaluationContext.allowedGroupIds().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CriteriaBuilder cb = em.getCriteriaBuilder();
|
||||
CriteriaQuery<?> query = evaluationContext.criteriaQuery();
|
||||
Subquery subquery = query.subquery(String.class);
|
||||
Root<?> from = subquery.from(UserGroupMembershipEntity.class);
|
||||
|
||||
subquery.select(cb.literal(1));
|
||||
|
||||
List<Predicate> subPredicates = new ArrayList<>();
|
||||
|
||||
subPredicates.add(from.get("groupId").in(negate ? evaluationContext.deniedGroupIds() : evaluationContext.allowedGroupIds()));
|
||||
|
||||
Path<?> root = evaluationContext.path();
|
||||
|
||||
subPredicates.add(cb.equal(from.get("user").get("id"), root.get("id")));
|
||||
|
||||
subquery.where(subPredicates.toArray(Predicate[]::new));
|
||||
|
||||
if (negate) {
|
||||
return cb.not(cb.exists(subquery));
|
||||
}
|
||||
|
||||
return cb.exists(subquery);
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,16 +29,6 @@ import java.util.LinkedList;
|
||||
*/
|
||||
@NamedQueries({
|
||||
@NamedQuery(name="getGroupIdsByParent", query="select u.id from GroupEntity u where u.realm = :realm and u.type = 0 and u.parentId = :parent order by u.name ASC"),
|
||||
@NamedQuery(name="getGroupIdsByParentAndName", query="select u.id from GroupEntity u where u.realm = :realm and u.type = 0 and u.parentId = :parent and u.name = :search order by u.name ASC"),
|
||||
@NamedQuery(name="getGroupIdsByParentAndNameContaining", query="select u.id from GroupEntity u where u.realm = :realm and u.type = 0 and u.parentId = :parent and lower(u.name) like lower(concat('%',:search,'%')) order by u.name ASC"),
|
||||
@NamedQuery(name="getGroupIdsByRealm", query="select u.id from GroupEntity u where u.realm = :realm and u.type = 0 order by u.name ASC"),
|
||||
@NamedQuery(name="getGroupIdsByNameContaining", query="select u.id from GroupEntity u where u.realm = :realm and u.type = 0 and lower(u.name) like lower(concat('%',:search,'%')) order by u.name ASC"),
|
||||
@NamedQuery(name="getGroupIdsByNameContainingFromIdList", query="select u.id from GroupEntity u where u.realm = :realm and u.type = 0 and lower(u.name) like lower(concat('%',:search,'%')) and u.id in :ids order by u.name ASC"),
|
||||
@NamedQuery(name="getGroupIdsByName", query="select u.id from GroupEntity u where u.realm = :realm and u.type = 0 and u.name = :search order by u.name ASC"),
|
||||
@NamedQuery(name="getGroupIdsFromIdList", query="select u.id from GroupEntity u where u.realm = :realm and u.type = 0 and u.id in :ids order by u.name ASC"),
|
||||
@NamedQuery(name="getGroupCountByNameContainingFromIdList", query="select count(u) from GroupEntity u where u.realm = :realm and u.type = 0 and lower(u.name) like lower(concat('%',:search,'%')) and u.id in :ids"),
|
||||
@NamedQuery(name="getGroupCount", query="select count(u) from GroupEntity u where u.realm = :realm and u.type = 0"),
|
||||
@NamedQuery(name="getGroupCountByParent", query="select count(u) from GroupEntity u where u.realm = :realm and u.type = 0 and u.parentId = :parent"),
|
||||
@NamedQuery(name="deleteGroupsByRealm", query="delete from GroupEntity g where g.realm = :realm")
|
||||
})
|
||||
@Entity
|
||||
|
||||
@ -37,12 +37,6 @@ import org.keycloak.representations.idm.MembershipType;
|
||||
@NamedQueries({
|
||||
@NamedQuery(name="userMemberOf", query="select m from UserGroupMembershipEntity m where m.user = :user and m.groupId = :groupId"),
|
||||
@NamedQuery(name="userGroupMembership", query="select m from UserGroupMembershipEntity m where m.user = :user"),
|
||||
@NamedQuery(name="groupMembership", query="select g.user from UserGroupMembershipEntity g where g.groupId = :groupId order by g.user.username"),
|
||||
@NamedQuery(name="groupMembershipByUser", query="select g.user from UserGroupMembershipEntity g where g.groupId = :groupId and " +
|
||||
"(g.user.username = :search or g.user.email = :search or g.user.firstName = :search or g.user.lastName = :search) order by g.user.username"),
|
||||
@NamedQuery(name="groupMembershipByUserContained", query="select g.user from UserGroupMembershipEntity g where g.groupId = :groupId and " +
|
||||
"(g.user.username like concat('%',:search,'%') or g.user.email like concat('%',:search,'%') or lower(g.user.firstName) like concat('%',:search,'%') or " +
|
||||
"lower(g.user.lastName) like concat('%',:search,'%')) order by g.user.username"),
|
||||
@NamedQuery(name="deleteUserGroupMembershipByRealm", query="delete from UserGroupMembershipEntity mapping where mapping.user IN (select u from UserEntity u where u.realmId=:realmId)"),
|
||||
@NamedQuery(name="deleteUserGroupMembershipsByRealmAndLink", query="delete from UserGroupMembershipEntity mapping where mapping.user IN (select u from UserEntity u where u.realmId=:realmId and u.federationLink=:link)"),
|
||||
@NamedQuery(name="deleteUserGroupMembershipsByGroup", query="delete from UserGroupMembershipEntity m where m.groupId = :groupId"),
|
||||
|
||||
@ -145,9 +145,11 @@ public class BruteForceUsersResource {
|
||||
private Stream<BruteUser> searchForUser(Map<String, String> attributes, RealmModel realm, UserPermissionEvaluator usersEvaluator, Boolean briefRepresentation, Integer firstResult, Integer maxResults, Boolean includeServiceAccounts) {
|
||||
attributes.put(UserModel.INCLUDE_SERVICE_ACCOUNT, includeServiceAccounts.toString());
|
||||
|
||||
Set<String> groupIds = auth.groups().getGroupIdsWithViewPermission();
|
||||
if (!groupIds.isEmpty()) {
|
||||
session.setAttribute(UserModel.GROUPS, groupIds);
|
||||
if (!AdminPermissionsSchema.SCHEMA.isAdminPermissionsEnabled(realm)) {
|
||||
Set<String> groupIds = auth.groups().getGroupIdsWithViewPermission();
|
||||
if (!groupIds.isEmpty()) {
|
||||
session.setAttribute(UserModel.GROUPS, groupIds);
|
||||
}
|
||||
}
|
||||
|
||||
return toRepresentation(realm, usersEvaluator, briefRepresentation, session.users().searchForUserStream(realm, attributes, firstResult, maxResults));
|
||||
@ -158,6 +160,7 @@ public class BruteForceUsersResource {
|
||||
boolean briefRepresentationB = briefRepresentation != null && briefRepresentation;
|
||||
|
||||
if (!AdminPermissionsSchema.SCHEMA.isAdminPermissionsEnabled(realm)) {
|
||||
usersEvaluator.grantIfNoPermission(session.getAttribute(UserModel.GROUPS) != null);
|
||||
userModels = userModels.filter(usersEvaluator::canView);
|
||||
usersEvaluator.grantIfNoPermission(session.getAttribute(UserModel.GROUPS) != null);
|
||||
}
|
||||
|
||||
@ -24,6 +24,7 @@ import java.util.stream.Collectors;
|
||||
|
||||
import jakarta.persistence.criteria.CriteriaBuilder;
|
||||
import jakarta.persistence.criteria.CriteriaQuery;
|
||||
import jakarta.persistence.criteria.Path;
|
||||
import jakarta.persistence.criteria.Predicate;
|
||||
import org.keycloak.authorization.model.Policy;
|
||||
import org.keycloak.authorization.model.Resource;
|
||||
@ -436,8 +437,12 @@ public class AdminPermissionsSchema extends AuthorizationSchema {
|
||||
}
|
||||
}
|
||||
|
||||
public List<Predicate> applyAuthorizationFilters(KeycloakSession session, ResourceType resourceType, PartialEvaluationStorageProvider evaluator, RealmModel realm, CriteriaBuilder builder, CriteriaQuery<?> queryBuilder) {
|
||||
return partialEvaluator.applyAuthorizationFilters(session, resourceType, evaluator, realm, builder, queryBuilder);
|
||||
public List<Predicate> applyAuthorizationFilters(KeycloakSession session, ResourceType resourceType, RealmModel realm, CriteriaBuilder builder, CriteriaQuery<?> queryBuilder, Path<?> path) {
|
||||
return applyAuthorizationFilters(session, resourceType, null, realm, builder, queryBuilder, path);
|
||||
}
|
||||
|
||||
public List<Predicate> applyAuthorizationFilters(KeycloakSession session, ResourceType resourceType, PartialEvaluationStorageProvider evaluator, RealmModel realm, CriteriaBuilder builder, CriteriaQuery<?> queryBuilder, Path<?> path) {
|
||||
return partialEvaluator.applyAuthorizationFilters(session, resourceType, evaluator, realm, builder, queryBuilder, path);
|
||||
}
|
||||
|
||||
public PolicyEvaluator getPolicyEvaluator(KeycloakSession session, ResourceServer resourceServer) {
|
||||
|
||||
@ -23,13 +23,12 @@ import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import jakarta.persistence.criteria.CriteriaBuilder;
|
||||
import jakarta.persistence.criteria.CriteriaQuery;
|
||||
import jakarta.persistence.criteria.Path;
|
||||
import jakarta.persistence.criteria.Predicate;
|
||||
import jakarta.persistence.criteria.Root;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.authorization.model.Policy;
|
||||
import org.keycloak.authorization.model.Resource;
|
||||
@ -50,10 +49,10 @@ import org.keycloak.representations.idm.authorization.ResourceType;
|
||||
|
||||
public class PartialEvaluator {
|
||||
|
||||
public List<Predicate> applyAuthorizationFilters(KeycloakSession session, ResourceType resourceType, PartialEvaluationStorageProvider evaluator, RealmModel realm, CriteriaBuilder builder, CriteriaQuery<?> queryBuilder) {
|
||||
public List<Predicate> applyAuthorizationFilters(KeycloakSession session, ResourceType resourceType, PartialEvaluationStorageProvider storage, RealmModel realm, CriteriaBuilder builder, CriteriaQuery<?> queryBuilder, Path<?> path) {
|
||||
if (!AdminPermissionsSchema.SCHEMA.isAdminPermissionsEnabled(realm)) {
|
||||
// feature not enabled, if a storage evaluator is provided try to resolve any filter from there
|
||||
return evaluator == null ? List.of() : evaluator.getFilters(new EvaluationContext(resourceType, queryBuilder));
|
||||
return storage == null ? List.of() : storage.getFilters(new EvaluationContext(resourceType, queryBuilder, path, Set.of(), Set.of()));
|
||||
}
|
||||
|
||||
KeycloakContext context = session.getContext();
|
||||
@ -66,38 +65,48 @@ public class PartialEvaluator {
|
||||
|
||||
// collect the result from the partial evaluation so that the filters can be applied
|
||||
PartialResourceEvaluationResult result = evaluate(session, adminUser, resourceType);
|
||||
EvaluationContext evaluationContext = new EvaluationContext(resourceType, queryBuilder);
|
||||
Root<?> root = evaluationContext.getRootEntity();
|
||||
EvaluationContext evaluationContext = new EvaluationContext(resourceType, queryBuilder, path, new HashSet<>(), new HashSet<>());
|
||||
|
||||
if (AdminPermissionsSchema.USERS.equals(resourceType)) {
|
||||
PartialResourceEvaluationResult evaluateGroups = evaluate(session, adminUser, AdminPermissionsSchema.GROUPS);
|
||||
|
||||
evaluationContext.allowedGroupIds().addAll(evaluateGroups.allowedIds());
|
||||
evaluationContext.deniedGroupIds().addAll(evaluateGroups.deniedIds());
|
||||
}
|
||||
|
||||
List<Predicate> predicates = new ArrayList<>();
|
||||
Set<String> deniedIds = result.deniedIds();
|
||||
|
||||
if (!deniedIds.isEmpty()) {
|
||||
// add filters to remove denied resources from the result set
|
||||
predicates.add(builder.not(root.get("id").in(deniedIds)));
|
||||
predicates.add(builder.not(path.get("id").in(deniedIds)));
|
||||
}
|
||||
|
||||
List<Predicate> evaluate = evaluator == null ? List.of() : evaluator.getFilters(evaluationContext);
|
||||
List<Predicate> storageFilters = storage == null ? List.of() : storage.getFilters(evaluationContext);
|
||||
List<Predicate> storageNegateFilters = storage == null ? List.of() : storage.getNegateFilters(evaluationContext);
|
||||
|
||||
if (evaluate.isEmpty() && (result.isResourceTypedDenied() || (!deniedIds.isEmpty() && result.rawAllowedIds().isEmpty()))) {
|
||||
predicates.addAll(storageNegateFilters);
|
||||
|
||||
if (storageFilters.isEmpty() && (result.isResourceTypedDenied() || (!deniedIds.isEmpty() && result.rawAllowedIds().isEmpty()))) {
|
||||
// do not return any result because there is no filter from the evaluator, and access is denied for the resource type
|
||||
return List.of(builder.equal(root.get("id"), "none"));
|
||||
return List.of(builder.equal(path.get("id"), "none"));
|
||||
}
|
||||
|
||||
Set<String> allowedIds = result.allowedIds();
|
||||
|
||||
if (allowedIds.isEmpty()) {
|
||||
// no resources granted, filter them based on any filter previously set
|
||||
predicates.addAll(evaluate);
|
||||
predicates.addAll(storageFilters);
|
||||
return predicates;
|
||||
}
|
||||
|
||||
if (evaluate.isEmpty()) {
|
||||
if (storageFilters.isEmpty()) {
|
||||
// no filter from the evaluator, filter based on the resources that were granted
|
||||
predicates.add(builder.and(root.get("id").in(allowedIds)));
|
||||
predicates.add(builder.and(path.get("id").in(allowedIds)));
|
||||
} else {
|
||||
// there are filters from the evaluator, the resources granted will be a returned using a or condition
|
||||
List<Predicate> orPredicates = new ArrayList<>(evaluate);
|
||||
orPredicates.add(root.get("id").in(allowedIds));
|
||||
List<Predicate> orPredicates = new ArrayList<>(storageFilters);
|
||||
orPredicates.add(path.get("id").in(allowedIds));
|
||||
predicates.add(builder.or(orPredicates.toArray(new Predicate[0])));
|
||||
}
|
||||
|
||||
@ -124,30 +133,27 @@ public class PartialEvaluator {
|
||||
List<PartialEvaluationPolicyProvider> policyProviders = getPartialEvaluationPolicyProviders(session);
|
||||
|
||||
for (PartialEvaluationPolicyProvider policyProvider : policyProviders) {
|
||||
policyProvider.getPermissions(session, resourceType, adminUser).forEach(new Consumer<Policy>() {
|
||||
@Override
|
||||
public void accept(Policy permission) {
|
||||
Set<String> ids = permission.getResources().stream().map(Resource::getName).collect(Collectors.toSet());
|
||||
Set<Policy> policies = permission.getAssociatedPolicies();
|
||||
policyProvider.getPermissions(session, resourceType, adminUser).forEach(permission -> {
|
||||
Set<String> ids = permission.getResources().stream().map(Resource::getName).collect(Collectors.toSet());
|
||||
Set<Policy> policies = permission.getAssociatedPolicies();
|
||||
|
||||
for (Policy policy : policies) {
|
||||
PartialEvaluationPolicyProvider provider = policyProviders.stream().filter((p) -> p.supports(policy)).findAny().orElse(null);
|
||||
for (Policy policy : policies) {
|
||||
PartialEvaluationPolicyProvider provider = policyProviders.stream().filter((p) -> p.supports(policy)).findAny().orElse(null);
|
||||
|
||||
if (provider == null) {
|
||||
continue;
|
||||
}
|
||||
if (provider == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean granted = provider.evaluate(session, policy, adminUser);
|
||||
boolean granted = provider.evaluate(session, policy, adminUser);
|
||||
|
||||
if (Logic.NEGATIVE.equals(policy.getLogic())) {
|
||||
granted = !granted;
|
||||
}
|
||||
if (Logic.NEGATIVE.equals(policy.getLogic())) {
|
||||
granted = !granted;
|
||||
}
|
||||
|
||||
if (granted) {
|
||||
allowedIds.addAll(ids);
|
||||
} else {
|
||||
deniedIds.addAll(ids);
|
||||
}
|
||||
if (granted) {
|
||||
allowedIds.addAll(ids);
|
||||
} else {
|
||||
deniedIds.addAll(ids);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -22,9 +22,8 @@ import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import jakarta.persistence.criteria.CriteriaQuery;
|
||||
import jakarta.persistence.criteria.Path;
|
||||
import jakarta.persistence.criteria.Predicate;
|
||||
import jakarta.persistence.criteria.Root;
|
||||
import org.keycloak.models.ModelIllegalStateException;
|
||||
import org.keycloak.representations.idm.authorization.ResourceType;
|
||||
|
||||
/**
|
||||
@ -35,7 +34,7 @@ import org.keycloak.representations.idm.authorization.ResourceType;
|
||||
public interface PartialEvaluationStorageProvider {
|
||||
|
||||
/**
|
||||
* A callback method that will be called when building queries for realm resources. It returns a list of
|
||||
* A callback method that will be called when building queries for realm resources to grant access to resources. It returns a list of
|
||||
* {@link Predicate} instances representing the filters that should be applied to queries
|
||||
* when querying realm resources.
|
||||
*
|
||||
@ -44,22 +43,24 @@ public interface PartialEvaluationStorageProvider {
|
||||
*/
|
||||
List<Predicate> getFilters(EvaluationContext evaluationContext);
|
||||
|
||||
/**
|
||||
* A callback method that will be called when building queries for realm resources to deny access to resources. It returns a list of
|
||||
* {@link Predicate} instances representing the filters that should be applied to queries
|
||||
* when querying realm resources.
|
||||
*
|
||||
* @param evaluationContext the evaluation context.
|
||||
* @return the list of predicates
|
||||
*/
|
||||
List<Predicate> getNegateFilters(EvaluationContext evaluationContext);
|
||||
|
||||
/**
|
||||
* An {@link EvaluationContext} instance provides access to contextual information when building a query for realm
|
||||
* resources of a given {@link ResourceType}.
|
||||
*
|
||||
* @param resourceType the type of the resource to query
|
||||
* @param criteriaQuery the query to rely on when building predicates
|
||||
* @param path the path for the root entity
|
||||
*/
|
||||
record EvaluationContext(ResourceType resourceType, CriteriaQuery<?> criteriaQuery) {
|
||||
public Root<?> getRootEntity() {
|
||||
Set<Root<?>> roots = criteriaQuery.getRoots();
|
||||
|
||||
if (roots.size() != 1) {
|
||||
throw new ModelIllegalStateException("Could not find any root entity from query");
|
||||
}
|
||||
|
||||
return roots.iterator().next();
|
||||
}
|
||||
record EvaluationContext(ResourceType resourceType, CriteriaQuery<?> criteriaQuery, Path<?> path, Set<String> allowedGroupIds, Set<String> deniedGroupIds) {
|
||||
}
|
||||
}
|
||||
|
||||
@ -291,16 +291,23 @@ public abstract class DefaultKeycloakContext implements KeycloakContext {
|
||||
|
||||
@Override
|
||||
public UserModel getUser() {
|
||||
UserModel user = null;
|
||||
|
||||
if (bearerToken instanceof JsonWebToken jwt) {
|
||||
UserModel user = session.users().getUserById(realm, jwt.getSubject());
|
||||
String issuer = jwt.getIssuer();
|
||||
String realmName = issuer.substring(issuer.lastIndexOf("/") + 1);
|
||||
RealmModel realm = session.realms().getRealmByName(realmName);
|
||||
user = session.users().getUserById(realm, jwt.getSubject());
|
||||
}
|
||||
|
||||
if (user == null) {
|
||||
throw new IllegalStateException("Could not resolve subject with id: " + jwt.getSubject());
|
||||
}
|
||||
if (user == null) {
|
||||
user = userSession == null ? null : userSession.getUser();
|
||||
}
|
||||
|
||||
if (user != null) {
|
||||
return user;
|
||||
}
|
||||
|
||||
return userSession == null ? null : userSession.getUser();
|
||||
throw new IllegalStateException("Could not resolve subject");
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,6 +21,7 @@ import org.eclipse.microprofile.openapi.annotations.extensions.Extension;
|
||||
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
|
||||
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
||||
import org.jboss.resteasy.reactive.NoCache;
|
||||
import org.keycloak.authorization.AdminPermissionsSchema;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.common.util.ObjectUtil;
|
||||
import org.keycloak.events.admin.OperationType;
|
||||
@ -174,9 +175,14 @@ public class GroupResource {
|
||||
@Parameter(description = "The maximum number of results that are to be returned. Defaults to 10") @QueryParam("max") @DefaultValue("10") Integer max,
|
||||
@Parameter(description = "Boolean which defines whether brief groups representations are returned or not (default: false)") @QueryParam("briefRepresentation") @DefaultValue("false") Boolean briefRepresentation) {
|
||||
this.auth.groups().requireView(group);
|
||||
return paginatedStream(
|
||||
group.getSubGroupsStream(search, exact, -1, -1)
|
||||
.filter(auth.groups()::canView), first, max)
|
||||
|
||||
Stream<GroupModel> stream = group.getSubGroupsStream(search, exact, -1, -1);
|
||||
|
||||
if (!AdminPermissionsSchema.SCHEMA.isAdminPermissionsEnabled(realm)) {
|
||||
stream = stream.filter(auth.groups()::canView);
|
||||
}
|
||||
|
||||
return paginatedStream(stream, first, max)
|
||||
.map(g -> GroupUtils.populateSubGroupCount(g, GroupUtils.toRepresentation(auth.groups(), g, !briefRepresentation)));
|
||||
}
|
||||
|
||||
|
||||
@ -40,6 +40,7 @@ import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
|
||||
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
|
||||
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
||||
import org.jboss.resteasy.reactive.NoCache;
|
||||
import org.keycloak.authorization.AdminPermissionsSchema;
|
||||
import org.keycloak.common.util.ObjectUtil;
|
||||
import org.keycloak.events.admin.OperationType;
|
||||
import org.keycloak.events.admin.ResourceType;
|
||||
@ -110,8 +111,11 @@ public class GroupsResource {
|
||||
return GroupUtils.populateGroupHierarchyFromSubGroups(session, realm, stream, !briefRepresentation, groupsEvaluator);
|
||||
}
|
||||
|
||||
return stream.filter(groupsEvaluator::canView)
|
||||
.map(g -> GroupUtils.populateSubGroupCount(g, GroupUtils.toRepresentation(groupsEvaluator, g, !briefRepresentation)));
|
||||
if (!AdminPermissionsSchema.SCHEMA.isAdminPermissionsEnabled(realm)) {
|
||||
stream = stream.filter(groupsEvaluator::canView);
|
||||
}
|
||||
|
||||
return stream.map(g -> GroupUtils.populateSubGroupCount(g, GroupUtils.toRepresentation(groupsEvaluator, g, !briefRepresentation)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -417,7 +417,11 @@ public class UsersResource {
|
||||
} else if (userPermissionEvaluator.canView()) {
|
||||
return session.users().getUsersCount(realm, search.trim());
|
||||
} else {
|
||||
return session.users().getUsersCount(realm, search.trim(), auth.groups().getGroupIdsWithViewPermission());
|
||||
if (AdminPermissionsSchema.SCHEMA.isAdminPermissionsEnabled(realm)) {
|
||||
return session.users().getUsersCount(realm, search.trim());
|
||||
} else {
|
||||
return session.users().getUsersCount(realm, search.trim(), auth.groups().getGroupIdsWithViewPermission());
|
||||
}
|
||||
}
|
||||
} else if (last != null || first != null || email != null || username != null || emailVerified != null || enabled != null || !searchAttributes.isEmpty()) {
|
||||
Map<String, String> parameters = new HashMap<>();
|
||||
@ -444,11 +448,18 @@ public class UsersResource {
|
||||
if (userPermissionEvaluator.canView()) {
|
||||
return session.users().getUsersCount(realm, parameters);
|
||||
} else {
|
||||
return session.users().getUsersCount(realm, parameters, auth.groups().getGroupIdsWithViewPermission());
|
||||
if (AdminPermissionsSchema.SCHEMA.isAdminPermissionsEnabled(realm)) {
|
||||
return session.users().getUsersCount(realm, parameters);
|
||||
} else {
|
||||
return session.users().getUsersCount(realm, parameters, auth.groups().getGroupIdsWithViewPermission());
|
||||
}
|
||||
}
|
||||
} else if (userPermissionEvaluator.canView()) {
|
||||
return session.users().getUsersCount(realm);
|
||||
} else {
|
||||
if (AdminPermissionsSchema.SCHEMA.isAdminPermissionsEnabled(realm)) {
|
||||
return session.users().getUsersCount(realm);
|
||||
}
|
||||
return session.users().getUsersCount(realm, auth.groups().getGroupIdsWithViewPermission());
|
||||
}
|
||||
}
|
||||
@ -467,9 +478,11 @@ public class UsersResource {
|
||||
private Stream<UserRepresentation> searchForUser(Map<String, String> attributes, RealmModel realm, UserPermissionEvaluator usersEvaluator, Boolean briefRepresentation, Integer firstResult, Integer maxResults, Boolean includeServiceAccounts) {
|
||||
attributes.put(UserModel.INCLUDE_SERVICE_ACCOUNT, includeServiceAccounts.toString());
|
||||
|
||||
Set<String> groupIds = auth.groups().getGroupIdsWithViewPermission();
|
||||
if (!groupIds.isEmpty()) {
|
||||
session.setAttribute(UserModel.GROUPS, groupIds);
|
||||
if (!AdminPermissionsSchema.SCHEMA.isAdminPermissionsEnabled(realm)) {
|
||||
Set<String> groupIds = auth.groups().getGroupIdsWithViewPermission();
|
||||
if (!groupIds.isEmpty()) {
|
||||
session.setAttribute(UserModel.GROUPS, groupIds);
|
||||
}
|
||||
}
|
||||
|
||||
return toRepresentation(realm, usersEvaluator, briefRepresentation, session.users().searchForUserStream(realm, attributes, firstResult, maxResults));
|
||||
@ -479,6 +492,7 @@ public class UsersResource {
|
||||
boolean briefRepresentationB = briefRepresentation != null && briefRepresentation;
|
||||
|
||||
if (!AdminPermissionsSchema.SCHEMA.isAdminPermissionsEnabled(realm)) {
|
||||
usersEvaluator.grantIfNoPermission(session.getAttribute(UserModel.GROUPS) != null);
|
||||
userModels = userModels.filter(usersEvaluator::canView);
|
||||
usersEvaluator.grantIfNoPermission(session.getAttribute(UserModel.GROUPS) != null);
|
||||
}
|
||||
|
||||
@ -4,6 +4,8 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.keycloak.authorization.AdminPermissionsSchema;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
@ -27,8 +29,11 @@ public class GroupUtils {
|
||||
public static Stream<GroupRepresentation> populateGroupHierarchyFromSubGroups(KeycloakSession session, RealmModel realm, Stream<GroupModel> groups, boolean full, GroupPermissionEvaluator groupEvaluator) {
|
||||
Map<String, GroupRepresentation> groupIdToGroups = new HashMap<>();
|
||||
groups.forEach(group -> {
|
||||
//TODO GROUPS do permissions work in such a way that if you can view the children you can definitely view the parents?
|
||||
if(!groupEvaluator.canView() && !groupEvaluator.canView(group)) return;
|
||||
|
||||
if (!AdminPermissionsSchema.SCHEMA.isAdminPermissionsEnabled(realm)) {
|
||||
//TODO GROUPS do permissions work in such a way that if you can view the children you can definitely view the parents?
|
||||
if (!groupEvaluator.canView() && !groupEvaluator.canView(group)) return;
|
||||
}
|
||||
|
||||
GroupRepresentation currGroup = toRepresentation(groupEvaluator, group, full);
|
||||
populateSubGroupCount(group, currGroup);
|
||||
@ -37,11 +42,12 @@ public class GroupUtils {
|
||||
while(currGroup.getParentId() != null) {
|
||||
GroupModel parentModel = session.groups().getGroupById(realm, currGroup.getParentId());
|
||||
|
||||
//TODO GROUPS not sure if this is even necessary but if somehow you can't view the parent we need to remove the child and move on
|
||||
if(!groupEvaluator.canView() && !groupEvaluator.canView(parentModel)) {
|
||||
groupIdToGroups.remove(currGroup.getId());
|
||||
break;
|
||||
}
|
||||
if (!AdminPermissionsSchema.SCHEMA.isAdminPermissionsEnabled(realm)) {
|
||||
//TODO GROUPS not sure if this is even necessary but if somehow you can't view the parent we need to remove the child and move on
|
||||
if(!groupEvaluator.canView() && !groupEvaluator.canView(parentModel)) {
|
||||
groupIdToGroups.remove(currGroup.getId());
|
||||
break;
|
||||
} }
|
||||
|
||||
GroupRepresentation parent = groupIdToGroups.computeIfAbsent(currGroup.getParentId(),
|
||||
id -> toRepresentation(groupEvaluator, parentModel, full));
|
||||
|
||||
@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright 2025 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.tests.admin.authz.fgap;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.keycloak.authorization.AdminPermissionsSchema.GROUPS_RESOURCE_TYPE;
|
||||
import static org.keycloak.authorization.AdminPermissionsSchema.VIEW;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.keycloak.admin.client.Keycloak;
|
||||
import org.keycloak.admin.client.resource.GroupResource;
|
||||
import org.keycloak.admin.client.resource.GroupsResource;
|
||||
import org.keycloak.admin.client.resource.ScopePermissionsResource;
|
||||
import org.keycloak.authorization.AdminPermissionsSchema;
|
||||
import org.keycloak.representations.idm.GroupRepresentation;
|
||||
import org.keycloak.representations.idm.authorization.Logic;
|
||||
import org.keycloak.representations.idm.authorization.ScopePermissionRepresentation;
|
||||
import org.keycloak.representations.idm.authorization.UserPolicyRepresentation;
|
||||
import org.keycloak.testframework.annotations.InjectAdminClient;
|
||||
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
|
||||
import org.keycloak.testframework.util.ApiUtil;
|
||||
|
||||
@KeycloakIntegrationTest(config = KeycloakAdminPermissionsServerConfig.class)
|
||||
public class GroupResourceTypeFilteringTest extends AbstractPermissionTest {
|
||||
|
||||
@InjectAdminClient(mode = InjectAdminClient.Mode.MANAGED_REALM, client = "myclient", user = "myadmin")
|
||||
Keycloak realmAdminClient;
|
||||
|
||||
@BeforeEach
|
||||
public void onBeforeEach() {
|
||||
for (int i = 0; i < 50; i++) {
|
||||
GroupRepresentation group = new GroupRepresentation();
|
||||
|
||||
group.setName("group-" + i);
|
||||
|
||||
try (Response response = realm.admin().groups().add(group)) {
|
||||
group.setId(ApiUtil.getCreatedId(response));
|
||||
}
|
||||
|
||||
GroupResource groupResource = realm.admin().groups().group(group.getId());
|
||||
|
||||
for (int j = 0; j < 5; j++) {
|
||||
GroupRepresentation subGroup = new GroupRepresentation();
|
||||
|
||||
subGroup.setName("subgroup-" + i + "." + j);
|
||||
|
||||
groupResource.subGroup(subGroup).close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void onAfterEach() {
|
||||
ScopePermissionsResource permissions = getScopePermissionsResource(client);
|
||||
|
||||
for (ScopePermissionRepresentation permission : permissions.findAll(null, null, null, -1, -1)) {
|
||||
permissions.findById(permission.getId()).remove();
|
||||
}
|
||||
|
||||
GroupsResource groups = realm.admin().groups();
|
||||
groups.groups().forEach(group -> groups.group(group.getId()).remove());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testViewAllGroupsUsingUserPolicy() {
|
||||
List<GroupRepresentation> search = realmAdminClient.realm(realm.getName()).groups().groups();
|
||||
assertTrue(search.isEmpty());
|
||||
|
||||
UserPolicyRepresentation policy = createUserPolicy(realm, client,"Only My Admin User Policy", realm.admin().users().search("myadmin").get(0).getId());
|
||||
createAllPermission(client, GROUPS_RESOURCE_TYPE, policy, Set.of(VIEW));
|
||||
|
||||
search = realmAdminClient.realm(realm.getName()).groups().groups();
|
||||
assertFalse(search.isEmpty());
|
||||
assertEquals(50, search.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeniedResourcesPrecedenceOverGrantedResources() {
|
||||
UserPolicyRepresentation policy = createUserPolicy(realm, client,"Only My Admin User Policy", realm.admin().users().search("myadmin").get(0).getId());
|
||||
createAllPermission(client, GROUPS_RESOURCE_TYPE, policy, Set.of(VIEW));
|
||||
|
||||
List<GroupRepresentation> search = realmAdminClient.realm(realm.getName()).groups().groups();
|
||||
assertFalse(search.isEmpty());
|
||||
assertEquals(50, search.size());
|
||||
|
||||
UserPolicyRepresentation notMyAdminPolicy = createUserPolicy(Logic.NEGATIVE, realm, client,"Not My Admin User Policy", realm.admin().users().search("myadmin").get(0).getId());
|
||||
Set<String> notAllowedGroups = search.stream()
|
||||
.filter((g) -> Set.of("group-0", "group-15", "group-30", "group-45").contains(g.getName()))
|
||||
.map(GroupRepresentation::getId)
|
||||
.collect(Collectors.toSet());
|
||||
createPermission(client, notAllowedGroups, GROUPS_RESOURCE_TYPE, Set.of(VIEW), notMyAdminPolicy);
|
||||
search = realmAdminClient.realm(realm.getName()).groups().groups();
|
||||
assertFalse(search.isEmpty());
|
||||
assertTrue(search.stream().map(GroupRepresentation::getName).noneMatch(notAllowedGroups::contains));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFilterSubGroups() {
|
||||
UserPolicyRepresentation policy = createUserPolicy(realm, client,"Only My Admin User Policy", realm.admin().users().search("myadmin").get(0).getId());
|
||||
createAllPermission(client, GROUPS_RESOURCE_TYPE, policy, Set.of(VIEW));
|
||||
|
||||
List<GroupRepresentation> search = realmAdminClient.realm(realm.getName()).groups().groups("subgroup-0.0", -1, -1);
|
||||
assertFalse(search.isEmpty());
|
||||
assertEquals(1, search.size());
|
||||
|
||||
GroupRepresentation group = search.get(0);
|
||||
assertEquals(1, group.getSubGroups().size());
|
||||
GroupRepresentation subGroup = group.getSubGroups().get(0);
|
||||
assertEquals("subgroup-0.0", subGroup.getName());
|
||||
|
||||
UserPolicyRepresentation notMyAdminPolicy = createUserPolicy(Logic.NEGATIVE, realm, client,"Not My Admin User Policy", realm.admin().users().search("myadmin").get(0).getId());
|
||||
createPermission(client, subGroup.getId(), GROUPS_RESOURCE_TYPE, Set.of(VIEW), notMyAdminPolicy);
|
||||
search = realmAdminClient.realm(realm.getName()).groups().groups("subgroup-0.0", -1, -1);
|
||||
assertTrue(search.isEmpty());
|
||||
|
||||
List<GroupRepresentation> subGroups = realmAdminClient.realm(realm.getName()).groups().group(group.getId()).getSubGroups(-1, -1, false);
|
||||
assertTrue(subGroups.stream().map(GroupRepresentation::getId).noneMatch(subGroup.getId()::equals));
|
||||
}
|
||||
}
|
||||
@ -23,12 +23,15 @@ import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.keycloak.authorization.AdminPermissionsSchema.GROUPS_RESOURCE_TYPE;
|
||||
import static org.keycloak.authorization.AdminPermissionsSchema.USERS_RESOURCE_TYPE;
|
||||
import static org.keycloak.authorization.AdminPermissionsSchema.VIEW;
|
||||
import static org.keycloak.authorization.AdminPermissionsSchema.VIEW_MEMBERS;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import org.hamcrest.Matchers;
|
||||
@ -108,10 +111,13 @@ public class UserResourceTypeFilteringTest extends AbstractPermissionTest {
|
||||
|
||||
@Test
|
||||
public void testViewUserUsingUserPolicy() {
|
||||
List<UserRepresentation> search = realmAdminClient.realm(realm.getName()).users().search(null, 0, 10);
|
||||
assertTrue(search.isEmpty());
|
||||
|
||||
UserPolicyRepresentation policy = createUserPolicy(realm, client,"Only My Admin User Policy", realm.admin().users().search("myadmin").get(0).getId());
|
||||
createPermission(client, "user-9", usersType, Set.of(VIEW), policy);
|
||||
|
||||
List<UserRepresentation> search = realmAdminClient.realm(realm.getName()).users().search(null, 0, 10);
|
||||
search = realmAdminClient.realm(realm.getName()).users().search(null, 0, 10);
|
||||
assertFalse(search.isEmpty());
|
||||
assertEquals(1, search.size());
|
||||
}
|
||||
@ -227,6 +233,77 @@ public class UserResourceTypeFilteringTest extends AbstractPermissionTest {
|
||||
search = realmAdminClient.realm(realm.getName()).users().search(null, 0, 10);
|
||||
assertFalse(search.isEmpty());
|
||||
assertTrue(search.stream().map(UserRepresentation::getUsername).noneMatch("user-0"::equals));
|
||||
assertTrue(realmAdminClient.realm(realm.getName()).groups().group(group.getId()).members().stream().map(UserRepresentation::getUsername).noneMatch("user-0"::equals));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDenyGroupViewMembersPolicy() {
|
||||
List<UserRepresentation> search = realmAdminClient.realm(realm.getName()).users().search(null, 0, 10);
|
||||
assertTrue(search.isEmpty());
|
||||
|
||||
GroupRepresentation allowedMembers = new GroupRepresentation();
|
||||
allowedMembers.setName(KeycloakModelUtils.generateId());
|
||||
|
||||
Set<String> memberUsernames = Set.of("user-0", "user-15", "user-30", "user-45");
|
||||
|
||||
try (Response response = realm.admin().groups().add(allowedMembers)) {
|
||||
allowedMembers.setId(ApiUtil.getCreatedId(response));
|
||||
addGroupMember(allowedMembers.getId(), memberUsernames);
|
||||
}
|
||||
|
||||
GroupRepresentation deniedMembers = new GroupRepresentation();
|
||||
|
||||
deniedMembers.setName(KeycloakModelUtils.generateId());
|
||||
|
||||
Set<String> deniedMemberUsernames = Set.of("user-0", "user-45");
|
||||
|
||||
try (Response response = realm.admin().groups().add(deniedMembers)) {
|
||||
deniedMembers.setId(ApiUtil.getCreatedId(response));
|
||||
addGroupMember(deniedMembers.getId(), memberUsernames.stream().filter(deniedMemberUsernames::contains).collect(Collectors.toSet()));
|
||||
}
|
||||
|
||||
// grant access to se members of a group
|
||||
UserPolicyRepresentation permitPolicy = createUserPolicy(realm, client,"Only My Admin User Policy", realm.admin().users().search("myadmin").get(0).getId());
|
||||
createPermission(client, allowedMembers.getId(), AdminPermissionsSchema.GROUPS_RESOURCE_TYPE, Set.of(VIEW_MEMBERS), permitPolicy);
|
||||
|
||||
search = realmAdminClient.realm(realm.getName()).users().search(null, 0, 10);
|
||||
assertEquals(memberUsernames.size(), search.size());
|
||||
assertTrue(search.stream().map(UserRepresentation::getUsername).allMatch(memberUsernames::contains));
|
||||
|
||||
// deny access to the members of another group where access to some users in this group were previously granted
|
||||
UserPolicyRepresentation denyPolicy = createUserPolicy(Logic.NEGATIVE, realm, client,"Not My Admin User Policy", realm.admin().users().search("myadmin").get(0).getId());
|
||||
createPermission(client, deniedMembers.getId(), GROUPS_RESOURCE_TYPE, Set.of(VIEW_MEMBERS), denyPolicy);
|
||||
search = realmAdminClient.realm(realm.getName()).users().search(null, 0, 10);
|
||||
assertFalse(search.isEmpty());
|
||||
assertEquals(memberUsernames.size() - deniedMemberUsernames.size(), search.size());
|
||||
assertTrue(search.stream().map(UserRepresentation::getUsername).noneMatch(deniedMemberUsernames::contains));
|
||||
|
||||
// grant access to a specific user that is protected, the permission will have no effect because the user cannot be accessed due to the group permission
|
||||
String userId = realm.admin().users().search("user-0").get(0).getId();
|
||||
createPermission(client, userId, USERS_RESOURCE_TYPE, Set.of(VIEW), permitPolicy);
|
||||
search = realmAdminClient.realm(realm.getName()).users().search(null, 0, 10);
|
||||
Set<String> expected = new HashSet<>(memberUsernames);
|
||||
expected.removeAll(deniedMemberUsernames);
|
||||
assertFalse(search.isEmpty());
|
||||
assertEquals(expected.size(), search.size());
|
||||
assertTrue(search.stream().map(UserRepresentation::getUsername).allMatch(expected::contains));
|
||||
|
||||
// the user is no longer a member of the group that holds members that cannot be accessed, they can be accessed now
|
||||
realm.admin().users().get(userId).leaveGroup(deniedMembers.getId());
|
||||
search = realmAdminClient.realm(realm.getName()).users().search(null, 0, 10);
|
||||
expected = new HashSet<>(memberUsernames);
|
||||
expected.removeAll(deniedMemberUsernames);
|
||||
expected.add("user-0");
|
||||
assertFalse(search.isEmpty());
|
||||
assertEquals(expected.size(), search.size());
|
||||
assertTrue(search.stream().map(UserRepresentation::getUsername).allMatch(expected::contains));
|
||||
}
|
||||
|
||||
private void addGroupMember(String groupId, Set<String> usernames) {
|
||||
for (String username: usernames) {
|
||||
String id = realm.admin().users().search(username).get(0).getId();
|
||||
realm.admin().users().get(id).joinGroup(groupId);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user