From dbb0179a93a8bcc581b76c707ea78f8efbee0780 Mon Sep 17 00:00:00 2001 From: Pedro Igor Date: Wed, 2 Apr 2025 22:29:26 -0300 Subject: [PATCH] Aligning partial evaluation with the outcome from regular evaluations Closes #38626 Signed-off-by: Pedro Igor --- .../idm/authorization/ResourceType.java | 11 + .../jpa/JpaUserPartialEvaluationProvider.java | 212 +++++++++ .../keycloak/models/jpa/JpaUserProvider.java | 113 +---- .../authorization/AdminPermissionsSchema.java | 4 +- .../authorization/PartialEvaluator.java | 248 ++++++----- .../provider/PartialEvaluationContext.java | 119 +++++ .../PartialEvaluationStorageProvider.java | 21 +- .../UserResourceTypeEvaluationSpecTest.java | 415 ++++++++++-------- 8 files changed, 728 insertions(+), 415 deletions(-) create mode 100644 model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserPartialEvaluationProvider.java create mode 100644 server-spi-private/src/main/java/org/keycloak/authorization/policy/provider/PartialEvaluationContext.java diff --git a/core/src/main/java/org/keycloak/representations/idm/authorization/ResourceType.java b/core/src/main/java/org/keycloak/representations/idm/authorization/ResourceType.java index 8bd3b33e415..ce48472fb22 100644 --- a/core/src/main/java/org/keycloak/representations/idm/authorization/ResourceType.java +++ b/core/src/main/java/org/keycloak/representations/idm/authorization/ResourceType.java @@ -23,9 +23,11 @@ import java.util.Map; import java.util.Set; public class ResourceType { + private final String type; private final Set scopes; private final Map> scopeAliases; + private final String groupType; @JsonCreator public ResourceType(@JsonProperty("type") String type, @JsonProperty("scopes") Set scopes) { @@ -33,9 +35,14 @@ public class ResourceType { } public ResourceType(String type, Set scopes, Map> scopeAliases) { + this(type, scopes, scopeAliases, null); + } + + public ResourceType(String type, Set scopes, Map> scopeAliases, String groupType) { this.type = type; this.scopes = Collections.unmodifiableSet(scopes); this.scopeAliases = scopeAliases; + this.groupType = groupType; } public String getType() { @@ -49,4 +56,8 @@ public class ResourceType { public Map> getScopeAliases() { return Collections.unmodifiableMap(scopeAliases); } + + public String getGroupType() { + return groupType; + } } diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserPartialEvaluationProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserPartialEvaluationProvider.java new file mode 100644 index 00000000000..187d87d68f1 --- /dev/null +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserPartialEvaluationProvider.java @@ -0,0 +1,212 @@ +/* + * 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.models.jpa; + +import static org.keycloak.authorization.AdminPermissionsSchema.GROUPS_RESOURCE_TYPE; +import static org.keycloak.authorization.AdminPermissionsSchema.USERS_RESOURCE_TYPE; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Expression; +import jakarta.persistence.criteria.Path; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; +import jakarta.persistence.criteria.Subquery; +import org.keycloak.authorization.AdminPermissionsSchema; +import org.keycloak.authorization.jpa.entities.ResourceEntity; +import org.keycloak.authorization.policy.provider.PartialEvaluationContext; +import org.keycloak.authorization.policy.provider.PartialEvaluationStorageProvider; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import org.keycloak.models.jpa.entities.UserGroupMembershipEntity; + +/** + * A {@link PartialEvaluationStorageProvider} that provides support for partial evaluation when querying {@link UserModel}. + */ +public interface JpaUserPartialEvaluationProvider extends PartialEvaluationStorageProvider { + + KeycloakSession getSession(); + EntityManager getEntityManager(); + + @Override + default List getFilters(PartialEvaluationContext context) { + KeycloakSession session = getSession(); + + if (!AdminPermissionsSchema.SCHEMA.isAdminPermissionsEnabled(session.getContext().getRealm())) { + // support for FGAP v1, remove once v1 is removed + Set userGroups = (Set) session.getAttribute(UserModel.GROUPS); + + + if (userGroups != null) { + return List.of(getFilterByGroupMembership(session, context, userGroups)); + } + + return List.of(); + } + + return Optional.ofNullable(getAllowedGroupFilters(context)).map(List::of).orElse(List.of()); + } + + @Override + default List getNegateFilters(PartialEvaluationContext context) { + return Optional.ofNullable(getDeniedGroupsFilters(context)).map(List::of).orElse(List.of()); + } + + private Predicate getAllowedGroupFilters(PartialEvaluationContext context) { + Set allowedGroups = context.getAllowedGroups(); + + if (allowedGroups.isEmpty()) { + return null; + } + + if (context.deniedResources().contains(USERS_RESOURCE_TYPE)) { + return null; + } + + if (context.isResourceTypeAllowed()) { + return null; + } + + EntityManager em = getEntityManager(); + CriteriaBuilder cb = em.getCriteriaBuilder(); + + if (allowedGroups.contains(GROUPS_RESOURCE_TYPE)) { + return cb.exists(createUserMembershipSubquery(context)); + } + + return cb.exists(createUserMembershipSubquery(context, root -> root.get("groupId").in(allowedGroups))); + } + + private Predicate getDeniedGroupsFilters(PartialEvaluationContext context) { + CriteriaBuilder cb = context.getCriteriaBuilder(); + Set allowedGroups = context.getAllowedGroups(); + Set deniedGroups = context.getDeniedGroups(); + + if (deniedGroups.contains(GROUPS_RESOURCE_TYPE)) { + // no access granted to group resources + Predicate notMembers = cb.not(cb.exists(createUserMembershipSubquery(context))); + + // access denied for the group resource type + if (context.isResourceTypeAllowed()) { + // access granted to all resources + if (allowedGroups.isEmpty()) { + if (context.getDeniedGroupIds().isEmpty()) { + // filter group members but allow + return cb.and(cb.or(notMembers, context.getPath().get("id").in(context.getAllowedResourceIds()))); + } + + return notMembers; + } + + Predicate onlySpecificGroups = cb.exists(createUserMembershipSubquery(context, root -> root.get("groupId").in(allowedGroups))); + return cb.and(cb.or(notMembers, onlySpecificGroups)); + } + + return cb.not(cb.exists(createUserMembershipSubquery(context, root -> root.get("groupId").in(context.getDeniedGroupIds())))); + } + + if (context.getAllowedResources().isEmpty() && (allowedGroups.isEmpty() || context.deniedResources().contains(USERS_RESOURCE_TYPE))) { + return null; + } + + return cb.not(cb.exists(createUserMembershipSubquery(context, root -> root.get("groupId").in(deniedGroups)))); + } + + private Subquery createUserMembershipSubquery(PartialEvaluationContext context) { + return createUserMembershipSubquery(context, null); + } + + private Subquery createUserMembershipSubquery(PartialEvaluationContext context, Function, Predicate> predicate) { + EntityManager em = getEntityManager(); + CriteriaBuilder cb = em.getCriteriaBuilder(); + CriteriaQuery query = context.criteriaQuery(); + Subquery subquery = query.subquery(Integer.class); + Root from = subquery.from(UserGroupMembershipEntity.class); + + subquery.select(cb.literal(1)); + + Path root = context.getPath(); + List finalPredicates = new ArrayList<>(); + + if (predicate != null) { + finalPredicates.add(predicate.apply(from)); + } + + finalPredicates.add(cb.equal(from.get("user").get("id"), root.get("id"))); + + subquery.where(finalPredicates.toArray(Predicate[]::new)); + + return subquery; + } + + /** + * @deprecated remove once FGAP v1 is removed + */ + @Deprecated + private Predicate getFilterByGroupMembership(KeycloakSession session, PartialEvaluationContext context, Set groupIds) { + EntityManager em = getEntityManager(); + CriteriaBuilder cb = em.getCriteriaBuilder(); + CriteriaQuery query = context.criteriaQuery(); + Subquery subquery = query.subquery(Integer.class); + Root from = subquery.from(UserGroupMembershipEntity.class); + + subquery.select(cb.literal(1)); + + List subPredicates = new ArrayList<>(); + + subPredicates.add(from.get("groupId").in(groupIds)); + + Path root = context.getPath(); + + subPredicates.add(cb.equal(from.get("user").get("id"), root.get("id"))); + + Subquery subquery1 = query.subquery(Integer.class); + + subquery1.select(cb.literal(1)); + + Root from1 = subquery1.from(ResourceEntity.class); + + List subs = new ArrayList<>(); + + Expression groupId = from.get("groupId"); + + RealmModel realm = session.getContext().getRealm(); + + if (AdminPermissionsSchema.SCHEMA.isAdminPermissionsEnabled(realm)) { + subs.add(cb.like(from1.get("name"), groupId)); + } else { + subs.add(cb.like(from1.get("name"), cb.concat("group.resource.", groupId))); + } + + subquery1.where(subs.toArray(Predicate[]::new)); + + subPredicates.add(cb.exists(subquery1)); + + subquery.where(subPredicates.toArray(Predicate[]::new)); + + return cb.exists(subquery); + } +} diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java index eb1c397551a..b9e4df4e4f9 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java @@ -19,8 +19,6 @@ 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; import org.keycloak.common.util.Time; import org.keycloak.component.ComponentModel; import org.keycloak.credential.CredentialModel; @@ -63,7 +61,6 @@ import jakarta.persistence.criteria.Join; import jakarta.persistence.criteria.JoinType; import jakarta.persistence.criteria.Predicate; import jakarta.persistence.criteria.Root; -import jakarta.persistence.criteria.Subquery; import org.keycloak.storage.jpa.JpaHashUtils; import org.keycloak.utils.StringUtil; @@ -88,7 +85,7 @@ import static org.keycloak.utils.StreamsUtil.closing; * @version $Revision: 1 $ */ @SuppressWarnings("JpaQueryApiInspection") -public class JpaUserProvider implements UserProvider, UserCredentialStore, PartialEvaluationStorageProvider { +public class JpaUserProvider implements UserProvider, UserCredentialStore, JpaUserPartialEvaluationProvider { private static final String EMAIL = "email"; private static final String EMAIL_VERIFIED = "emailVerified"; @@ -1098,112 +1095,12 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore, Parti } @Override - public List getFilters(EvaluationContext evaluationContext) { - if (!AdminPermissionsSchema.SCHEMA.isAdminPermissionsEnabled(session.getContext().getRealm())) { - // support for FGAP v1 - Set userGroups = (Set) session.getAttribute(UserModel.GROUPS); - - - if (userGroups != null) { - return List.of(getFilterByGroupMembership(session, evaluationContext, userGroups)); - } - - return List.of(); - } - - Predicate predicate = getFilterByGroupMembership(evaluationContext, false); - - if (predicate != null) { - return List.of(predicate); - } - - return List.of(); + public KeycloakSession getSession() { + return session; } @Override - public List 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 groupIds) { - 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 subPredicates = new ArrayList<>(); - - subPredicates.add(from.get("groupId").in(groupIds)); - - Path root = evaluationContext.path(); - - subPredicates.add(cb.equal(from.get("user").get("id"), root.get("id"))); - - Subquery subquery1 = query.subquery(String.class); - - subquery1.select(cb.literal(1)); - - Root from1 = subquery1.from(ResourceEntity.class); - - List subs = new ArrayList<>(); - - Expression groupId = from.get("groupId"); - - RealmModel realm = session.getContext().getRealm(); - - if (AdminPermissionsSchema.SCHEMA.isAdminPermissionsEnabled(realm)) { - subs.add(cb.like(from1.get("name"), groupId)); - } else { - subs.add(cb.like(from1.get("name"), cb.concat("group.resource.", groupId))); - } - - subquery1.where(subs.toArray(Predicate[]::new)); - - subPredicates.add(cb.exists(subquery1)); - - subquery.where(subPredicates.toArray(Predicate[]::new)); - - 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 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); + public EntityManager getEntityManager() { + return em; } } diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/AdminPermissionsSchema.java b/server-spi-private/src/main/java/org/keycloak/authorization/AdminPermissionsSchema.java index ef35ed4bef0..1e27c698966 100644 --- a/server-spi-private/src/main/java/org/keycloak/authorization/AdminPermissionsSchema.java +++ b/server-spi-private/src/main/java/org/keycloak/authorization/AdminPermissionsSchema.java @@ -98,7 +98,7 @@ public class AdminPermissionsSchema extends AuthorizationSchema { public static final ResourceType CLIENTS = new ResourceType(CLIENTS_RESOURCE_TYPE, Set.of(CONFIGURE, MANAGE, MAP_ROLES, MAP_ROLES_CLIENT_SCOPE, MAP_ROLES_COMPOSITE, VIEW)); public static final ResourceType GROUPS = new ResourceType(GROUPS_RESOURCE_TYPE, Set.of(MANAGE, VIEW, MANAGE_MEMBERSHIP, MANAGE_MEMBERS, VIEW_MEMBERS)); public static final ResourceType ROLES = new ResourceType(ROLES_RESOURCE_TYPE, Set.of(MAP_ROLE, MAP_ROLE_CLIENT_SCOPE, MAP_ROLE_COMPOSITE)); - public static final ResourceType USERS = new ResourceType(USERS_RESOURCE_TYPE, Set.of(MANAGE, VIEW, IMPERSONATE, MAP_ROLES, MANAGE_GROUP_MEMBERSHIP), Map.of(VIEW, Set.of(VIEW_MEMBERS), MANAGE, Set.of(MANAGE_MEMBERS))); + public static final ResourceType USERS = new ResourceType(USERS_RESOURCE_TYPE, Set.of(MANAGE, VIEW, IMPERSONATE, MAP_ROLES, MANAGE_GROUP_MEMBERSHIP), Map.of(VIEW, Set.of(VIEW_MEMBERS), MANAGE, Set.of(MANAGE_MEMBERS)), GROUPS.getType()); public static final AdminPermissionsSchema SCHEMA = new AdminPermissionsSchema(); private final PartialEvaluator partialEvaluator = new PartialEvaluator(); @@ -444,7 +444,7 @@ public class AdminPermissionsSchema extends AuthorizationSchema { } public List 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); + return partialEvaluator.getPredicates(session, resourceType, evaluator, realm, builder, queryBuilder, path); } public PolicyEvaluator getPolicyEvaluator(KeycloakSession session, ResourceServer resourceServer) { diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/PartialEvaluator.java b/server-spi-private/src/main/java/org/keycloak/authorization/PartialEvaluator.java index aadde023d44..fa1b596c14f 100644 --- a/server-spi-private/src/main/java/org/keycloak/authorization/PartialEvaluator.java +++ b/server-spi-private/src/main/java/org/keycloak/authorization/PartialEvaluator.java @@ -17,8 +17,6 @@ package org.keycloak.authorization; -import static java.util.function.Predicate.not; - import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -33,14 +31,13 @@ import org.keycloak.Config; import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.Resource; import org.keycloak.authorization.model.Scope; +import org.keycloak.authorization.policy.provider.PartialEvaluationContext; import org.keycloak.authorization.policy.provider.PartialEvaluationPolicyProvider; import org.keycloak.authorization.policy.provider.PartialEvaluationStorageProvider; -import org.keycloak.authorization.policy.provider.PartialEvaluationStorageProvider.EvaluationContext; import org.keycloak.authorization.policy.provider.PolicyProvider; import org.keycloak.models.AdminRoles; import org.keycloak.models.ClientModel; import org.keycloak.models.Constants; -import org.keycloak.models.KeycloakContext; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; @@ -50,99 +47,45 @@ import org.keycloak.representations.idm.authorization.ResourceType; public class PartialEvaluator { - public List applyAuthorizationFilters(KeycloakSession session, ResourceType resourceType, PartialEvaluationStorageProvider storage, RealmModel realm, CriteriaBuilder builder, CriteriaQuery queryBuilder, Path path) { + private static final String NO_ID = "none"; + private static final String ID_FIELD = "id"; + + public List getPredicates(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 storage == null ? List.of() : storage.getFilters(new EvaluationContext(resourceType, queryBuilder, path, Set.of(), Set.of())); + return storage == null ? List.of() : storage.getFilters(new PartialEvaluationContext(storage, builder, queryBuilder, path)); } - KeycloakContext context = session.getContext(); - UserModel adminUser = context.getUser(); + UserModel adminUser = session.getContext().getUser(); - if (skipPartialEvaluation(session, adminUser, realm, resourceType)) { + if (shouldSkipPartialEvaluation(session, adminUser, realm, resourceType)) { // only run partial evaluation if the admin user does not have view-* or manage-* role for specified resourceType or has any query-* role return List.of(); } // 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, path, new HashSet<>(), new HashSet<>()); + PartialEvaluationContext context = runEvaluation(session, adminUser, resourceType, storage, builder, queryBuilder, path); - if (AdminPermissionsSchema.USERS.equals(resourceType)) { - PartialResourceEvaluationResult evaluateGroups = evaluate(session, adminUser, AdminPermissionsSchema.GROUPS); - - evaluationContext.allowedGroupIds().addAll(evaluateGroups.allowedIds()); - evaluationContext.deniedGroupIds().addAll(evaluateGroups.deniedIds()); - } - - List predicates = new ArrayList<>(); - Set deniedIds = result.deniedIds(); - - if (!deniedIds.isEmpty()) { - // add filters to remove denied resources from the result set - predicates.add(builder.not(path.get("id").in(deniedIds))); - } - - List storageFilters = storage == null ? List.of() : storage.getFilters(evaluationContext); - List storageNegateFilters = storage == null ? List.of() : storage.getNegateFilters(evaluationContext); - - 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(path.get("id"), "none")); - } - - Set allowedIds = result.allowedIds(); - - if (allowedIds.isEmpty()) { - // no resources granted, filter them based on any filter previously set - predicates.addAll(storageFilters); - return predicates; - } - - if (storageFilters.isEmpty()) { - // no filter from the evaluator, filter based on the resources that were granted - 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 orPredicates = new ArrayList<>(storageFilters); - orPredicates.add(path.get("id").in(allowedIds)); - predicates.add(builder.or(orPredicates.toArray(new Predicate[0]))); - } - - return predicates; + return buildPredicates(context); } - private record PartialResourceEvaluationResult(ResourceType resourceType, Set rawAllowedIds, Set rawDeniedIds) { - Set allowedIds() { - return rawAllowedIds.stream().filter(not(resourceType.getType()::equals)).collect(Collectors.toSet()); - } - - Set deniedIds() { - return rawDeniedIds.stream().filter(not(resourceType.getType()::equals)).collect(Collectors.toSet()); - } - - boolean isResourceTypedDenied() { - return rawAllowedIds.isEmpty() && (rawDeniedIds.isEmpty() || (rawDeniedIds.size() == 1 && rawDeniedIds.contains(resourceType.getType()))); - } - } - - private PartialResourceEvaluationResult evaluate(KeycloakSession session, UserModel adminUser, ResourceType resourceType) { - Set allowedIds = new HashSet<>(); - Set deniedIds = new HashSet<>(); + private PartialEvaluationContext runEvaluation(KeycloakSession session, UserModel adminUser, ResourceType resourceType, PartialEvaluationStorageProvider storage, CriteriaBuilder builder, CriteriaQuery queryBuilder, Path path) { + Set allowedResources = new HashSet<>(); + Set deniedResources = new HashSet<>(); List policyProviders = getPartialEvaluationPolicyProviders(session); for (PartialEvaluationPolicyProvider policyProvider : policyProviders) { policyProvider.getPermissions(session, resourceType, adminUser).forEach(permission -> { - if (permission.getScopes().stream().map(Scope::getName).noneMatch(name -> name.startsWith(AdminPermissionsSchema.VIEW))) { + if (!hasViewScope(permission)) { + // only run partial evaluation for permissions with any view scope return; } + Set ids = permission.getResources().stream().map(Resource::getName).collect(Collectors.toSet()); Set policies = permission.getAssociatedPolicies(); for (Policy policy : policies) { - PartialEvaluationPolicyProvider provider = policyProviders.stream().filter((p) -> p.supports(policy)).findAny().orElse(null); + PartialEvaluationPolicyProvider provider = getPartialEvaluationPolicyProvider(policy, policyProviders); if (provider == null) { continue; @@ -155,25 +98,107 @@ public class PartialEvaluator { } if (granted) { - allowedIds.addAll(ids); + allowedResources.addAll(ids); } else { - deniedIds.addAll(ids); + deniedResources.addAll(ids); } } }); } - allowedIds.removeAll(deniedIds); + allowedResources.removeAll(deniedResources); - if (allowedIds.contains(resourceType.getType())) { - allowedIds.removeIf(not(resourceType.getType()::equals)); + return createEvaluationContext(session, resourceType, allowedResources, deniedResources, storage, builder, queryBuilder, path, adminUser); + } + + private List buildPredicates(PartialEvaluationContext context) { + List storageFilters = getStorageFilters(context); + CriteriaBuilder builder = context.getCriteriaBuilder(); + Path path = context.getPath(); + + if (isDenied(storageFilters, context)) { + // 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(path.get(ID_FIELD), NO_ID)); } + Set deniedIds = context.getDeniedResources(); + ResourceType resourceType = context.getResourceType(); + if (deniedIds.contains(resourceType.getType())) { - deniedIds.removeIf(not(resourceType.getType()::equals)); + // do not filter by id if access is granted to the resource type + deniedIds = Set.of(); } - return new PartialResourceEvaluationResult(resourceType, allowedIds, deniedIds); + List predicates = new ArrayList<>(); + + if (!deniedIds.isEmpty()) { + // add filters to remove denied resources from the result set + predicates.add(builder.not(path.get(ID_FIELD).in(deniedIds))); + } + + List storageNegateFilters = getStorageNegateFilters(context); + + // add filters from the storage that deny access to resources + predicates.addAll(storageNegateFilters); + + Set allowedResourceIds = context.getAllowedResources(); + + if (allowedResourceIds.contains(resourceType.getType())) { + // do not filter by id if access is granted to the resource type + allowedResourceIds = Set.of(); + } + + if (allowedResourceIds.isEmpty()) { + // no resources granted, return any predicates created until now + predicates.addAll(storageFilters); + return predicates; + } + + if (storageFilters.isEmpty()) { + // no filter from the evaluator, filter based on the resources that were granted + predicates.add(builder.and(path.get(ID_FIELD).in(allowedResourceIds))); + } else { + // there are filters from the evaluator, the resources granted will be a returned using a or condition + List orPredicates = new ArrayList<>(storageFilters); + orPredicates.add(path.get(ID_FIELD).in(allowedResourceIds)); + predicates.add(builder.or(orPredicates.toArray(new Predicate[0]))); + } + + return predicates; + } + + private PartialEvaluationContext createEvaluationContext(KeycloakSession session, ResourceType resourceType, Set allowedResources, Set deniedResources, PartialEvaluationStorageProvider storage, CriteriaBuilder builder, CriteriaQuery queryBuilder, Path path, UserModel adminUser) { + PartialEvaluationContext context = new PartialEvaluationContext(resourceType, allowedResources, deniedResources, storage, builder, queryBuilder, path); + String groupType = resourceType.getGroupType(); + + if (groupType != null) { + // if the resource type has support for groups, evaluate permissions for the group + ResourceType groupResourceType = AdminPermissionsSchema.SCHEMA.getResourceTypes().get(groupType); + + if (groupResourceType == null) { + return context; + } + + PartialEvaluationContext evaluateGroups = runEvaluation(session, adminUser, groupResourceType, storage, builder, queryBuilder, path); + context.setAllowedGroups(evaluateGroups.getAllowedResources()); + context.setDeniedGroups(evaluateGroups.getDeniedResources()); + } + + return context; + } + + private List getStorageFilters(PartialEvaluationContext context) { + PartialEvaluationStorageProvider storage = context.getStorage(); + return storage == null ? List.of() : storage.getFilters(context); + } + + private List getStorageNegateFilters(PartialEvaluationContext context) { + PartialEvaluationStorageProvider storage = context.getStorage(); + return storage == null ? List.of() : storage.getNegateFilters(context); + } + + private boolean isDenied(List storageFilters, PartialEvaluationContext context) { + return context.getAllowedResources().isEmpty() && storageFilters.isEmpty(); } private List getPartialEvaluationPolicyProviders(KeycloakSession session) { @@ -183,22 +208,23 @@ public class PartialEvaluator { .toList(); } - private boolean skipPartialEvaluation(KeycloakSession session, UserModel user, RealmModel realm, ResourceType resourceType) { + private PartialEvaluationPolicyProvider getPartialEvaluationPolicyProvider(Policy policy, List policyProviders) { + return policyProviders.stream() + .filter((p) -> p.supports(policy)) + .findAny() + .orElse(null); + } + + private boolean hasViewScope(Policy permission) { + return permission.getScopes().stream().map(Scope::getName).anyMatch(name -> name.startsWith(AdminPermissionsSchema.VIEW)); + } + + private boolean shouldSkipPartialEvaluation(KeycloakSession session, UserModel user, RealmModel realm, ResourceType resourceType) { if (user == null) { return false; } - String clientId; - - if (realm.getName().equals(Config.getAdminRealm())) { - clientId = realm.getMasterAdminClient().getClientId(); - } else { - ClientModel client = realm.getClientByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID); - clientId = client == null ? null : client.getClientId(); - } - - // client probably removed when removing the realm - ClientModel client = clientId == null ? null : session.clients().getClientByClientId(realm, clientId); + ClientModel client = getRealmManagementClient(session); if (client == null) { return true; @@ -208,20 +234,32 @@ public class PartialEvaluator { return user.hasRole(client.getRole(AdminRoles.VIEW_USERS)) || user.hasRole(client.getRole(AdminRoles.MANAGE_USERS)) || !hasAnyQueryAdminRole(client, user); } else if (resourceType.equals(AdminPermissionsSchema.CLIENTS)) { return user.hasRole(client.getRole(AdminRoles.VIEW_CLIENTS)) || user.hasRole(client.getRole(AdminRoles.MANAGE_CLIENTS)) || !hasAnyQueryAdminRole(client, user); - } else { - return false; - } - } - - private boolean hasAnyQueryAdminRole(ClientModel client, UserModel user) { - for (String adminRole : List.of(AdminRoles.QUERY_CLIENTS, AdminRoles.QUERY_GROUPS, AdminRoles.QUERY_USERS)) { - RoleModel role = client.getRole(adminRole); - - if (user.hasRole(role)) { - return true; - } } return false; } + + private ClientModel getRealmManagementClient(KeycloakSession session) { + RealmModel realm = session.getContext().getRealm(); + + if (realm.getName().equals(Config.getAdminRealm())) { + return session.clients().getClientByClientId(realm, realm.getMasterAdminClient().getClientId()); + } + + return realm.getClientByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID); + } + + private boolean hasAnyQueryAdminRole(ClientModel client, UserModel user) { + boolean result = false; + for (String adminRole : List.of(AdminRoles.QUERY_CLIENTS, AdminRoles.QUERY_GROUPS, AdminRoles.QUERY_USERS)) { + RoleModel role = client.getRole(adminRole); + + if (user.hasRole(role)) { + result = true; + break; + } + } + + return result; + } } diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/policy/provider/PartialEvaluationContext.java b/server-spi-private/src/main/java/org/keycloak/authorization/policy/provider/PartialEvaluationContext.java new file mode 100644 index 00000000000..771e02a474e --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/authorization/policy/provider/PartialEvaluationContext.java @@ -0,0 +1,119 @@ +/* + * 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.authorization.policy.provider; + +import static java.util.function.Predicate.not; + +import java.util.Set; +import java.util.stream.Collectors; + +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Path; +import org.keycloak.representations.idm.authorization.ResourceType; + +/** + * An {@link PartialEvaluationContext} instance provides access to contextual information when building a query for realm + * resources of a given {@link ResourceType}. + */ +public class PartialEvaluationContext { + + private final ResourceType resourceType; + private final CriteriaQuery criteriaQuery; + private final Path path; + private final PartialEvaluationStorageProvider storage; + private final CriteriaBuilder criteriaBuilder; + private final Set allowedResources; + private final Set deniedResources; + private Set allowedGroups = Set.of(); + private Set deniedGroups = Set.of(); + + public PartialEvaluationContext(PartialEvaluationStorageProvider storage, CriteriaBuilder criteriaBuilder, CriteriaQuery criteriaQuery, Path path) { + this(null, Set.of(), Set.of(), storage, criteriaBuilder, criteriaQuery, path); + } + + public PartialEvaluationContext(ResourceType resourceType, Set allowedResources, Set deniedResources, PartialEvaluationStorageProvider storage, CriteriaBuilder criteriaBuilder, CriteriaQuery criteriaQuery, Path path) { + this.allowedResources = allowedResources; + this.deniedResources = deniedResources; + this.storage = storage; + this.criteriaBuilder = criteriaBuilder; + this.criteriaQuery = criteriaQuery; + this.path = path; + this.resourceType = resourceType; + } + + public boolean isResourceTypeAllowed() { + return allowedResources.contains(resourceType.getType()); + } + + public Set getAllowedResourceIds() { + return allowedResources.stream().filter(not(resourceType.getType()::equals)).collect(Collectors.toSet()); + } + + public Set getDeniedGroupIds() { + return deniedGroups.stream().filter(not(resourceType.getGroupType()::equals)).collect(Collectors.toSet()); + } + + public void setAllowedGroups(Set allowedGroups) { + this.allowedGroups = allowedGroups; + } + + public void setDeniedGroups(Set deniedGroups) { + this.deniedGroups = deniedGroups; + } + + public Set getDeniedGroups() { + return deniedGroups; + } + + public Set getAllowedGroups() { + return allowedGroups; + } + + public Set getAllowedResources() { + return allowedResources; + } + + public Path getPath() { + return path; + } + + public Set deniedResources() { + return deniedResources; + } + + public CriteriaQuery criteriaQuery() { + return criteriaQuery; + } + + public CriteriaBuilder getCriteriaBuilder() { + return criteriaBuilder; + } + + public ResourceType getResourceType() { + return resourceType; + } + + public PartialEvaluationStorageProvider getStorage() { + return storage; + } + + public Set getDeniedResources() { + return deniedResources; + } +} diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/policy/provider/PartialEvaluationStorageProvider.java b/server-spi-private/src/main/java/org/keycloak/authorization/policy/provider/PartialEvaluationStorageProvider.java index df9e63e8475..996e26bb138 100644 --- a/server-spi-private/src/main/java/org/keycloak/authorization/policy/provider/PartialEvaluationStorageProvider.java +++ b/server-spi-private/src/main/java/org/keycloak/authorization/policy/provider/PartialEvaluationStorageProvider.java @@ -19,12 +19,8 @@ package org.keycloak.authorization.policy.provider; import java.util.List; -import java.util.Set; -import jakarta.persistence.criteria.CriteriaQuery; -import jakarta.persistence.criteria.Path; import jakarta.persistence.criteria.Predicate; -import org.keycloak.representations.idm.authorization.ResourceType; /** * If a realm has the {@link org.keycloak.common.Profile.Feature#ADMIN_FINE_GRAINED_AUTHZ} feature enabled, @@ -41,26 +37,15 @@ public interface PartialEvaluationStorageProvider { * @param evaluationContext the evaluation context. * @return the list of predicates */ - List getFilters(EvaluationContext evaluationContext); + List getFilters(PartialEvaluationContext 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. + * @param context the evaluation context. * @return the list of predicates */ - List 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, Path path, Set allowedGroupIds, Set deniedGroupIds) { - } + List getNegateFilters(PartialEvaluationContext context); } diff --git a/tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/UserResourceTypeEvaluationSpecTest.java b/tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/UserResourceTypeEvaluationSpecTest.java index 8caa25f0bc8..45f41d05350 100644 --- a/tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/UserResourceTypeEvaluationSpecTest.java +++ b/tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/UserResourceTypeEvaluationSpecTest.java @@ -28,9 +28,15 @@ import static org.keycloak.authorization.AdminPermissionsSchema.USERS_RESOURCE_T import static org.keycloak.authorization.AdminPermissionsSchema.VIEW; import static org.keycloak.authorization.AdminPermissionsSchema.VIEW_MEMBERS; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import java.util.function.Predicate; +import java.util.stream.Stream; import jakarta.ws.rs.ForbiddenException; import org.junit.jupiter.api.AfterEach; @@ -67,6 +73,17 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { @InjectUser(ref = "jdoe") ManagedUser userJdoe; + @InjectUser(ref = "tom") + ManagedUser userTom; + + @InjectUser(ref = "mary") + ManagedUser userMary; + + ManagedUser myadmin; + + private List ALL_GROUP_MEMBERS; + private List ALL_USERS; + @InjectAdminClient(mode = InjectAdminClient.Mode.MANAGED_REALM, client = "myclient", user = "myadmin") Keycloak realmAdminClient; @@ -75,17 +92,36 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { @BeforeEach public void onBefore() { - for (int i = 0; i < 2; i++) { - GroupRepresentation group = new GroupRepresentation(); - group.setName("group-" + i); - realm.admin().groups().add(group).close(); + Map> groupMembers = new HashMap<>(); + + groupMembers.put("group-0", List.of(userAlice)); + groupMembers.put("group-1", List.of(userBob)); + groupMembers.put("group-2", List.of(userMary, userTom)); + + for (Entry> group : groupMembers.entrySet()) { + String name = group.getKey(); + + GroupRepresentation rep = new GroupRepresentation(); + rep.setName(name); + realm.admin().groups().add(rep).close(); + + List members = group.getValue(); + + for (ManagedUser member : members) { + joinGroup(member, name); + } } - joinGroup(userAlice, "group-0"); - joinGroup(userBob, "group-1"); + UserRepresentation myadmin = realm.admin().users().search("myadmin").get(0); + allowPolicy = createUserPolicy(realm, client, "Only My Admin User Policy", myadmin.getId()); + denyPolicy = createUserPolicy(Logic.NEGATIVE, realm, client,"Not My Admin User Policy", myadmin.getId()); + this.myadmin = new ManagedUser(myadmin, realm.admin().users().get(myadmin.getId())); - allowPolicy = createUserPolicy(realm, client, "Only My Admin User Policy", realm.admin().users().search("myadmin").get(0).getId()); - denyPolicy = createUserPolicy(Logic.NEGATIVE, realm, client,"Not My Admin User Policy", realm.admin().users().search("myadmin").get(0).getId()); + ALL_USERS = new ArrayList<>(); + groupMembers.values().forEach(ALL_USERS::addAll); + ALL_USERS.add(this.myadmin); + ALL_USERS.add(userJdoe); + ALL_GROUP_MEMBERS = groupMembers.values().stream().flatMap(Collection::stream).toList(); } @AfterEach @@ -101,8 +137,7 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { @Test public void test_01() { - List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - assertThat(search.isEmpty(), is(true)); + assertFilter(); assertUpdate(userAlice, false); assertUpdate(userBob, false); @@ -113,8 +148,7 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { public void test_02() { allowAllGroups(); - // TODO: should see only user members of groups - //assertThat(search.stream().map(UserRepresentation::getUsername).toList(), containsInAnyOrder("alice", "bob")); + assertFilter(ALL_GROUP_MEMBERS); assertUpdate(userAlice, true); assertUpdate(userBob, true); @@ -125,8 +159,7 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { public void test_03() { denyAllGroups(); - List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - assertThat(search.isEmpty(), is(true)); + assertFilter(); assertUpdate(userAlice, false); assertUpdate(userBob, false); @@ -137,8 +170,7 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { public void test_04() { allowGroup("group-0"); - List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - assertThat(search.stream().map(UserRepresentation::getUsername).toList(), containsInAnyOrder("alice")); + assertFilter(userAlice); assertUpdate(userAlice, true); assertUpdate(userBob, false); @@ -150,9 +182,7 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { allowGroup("group-0"); allowAllGroups(); - // TODO: should only see users that are members of groups - //List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - //assertThat(search.stream().map(UserRepresentation::getUsername).toList(), containsInAnyOrder("alice", "bob")); + assertFilter(ALL_GROUP_MEMBERS); assertUpdate(userAlice, true); assertUpdate(userBob, true); @@ -164,9 +194,7 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { allowGroup("group-0"); denyAllGroups(); - // TODO: should see only members of single group - //List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - //assertThat(search.stream().map(UserRepresentation::getUsername).toList(), containsInAnyOrder("alice", "bob")); + assertFilter(userAlice); assertUpdate(userAlice, true); assertUpdate(userBob, false); @@ -177,8 +205,7 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { public void test_07() { denyGroup("group-0"); - List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - assertThat(search.isEmpty(), is(true)); + assertFilter(); assertUpdate(userAlice, false); assertUpdate(userBob, false); @@ -190,9 +217,7 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { denyGroup("group-0"); allowAllGroups(); - // TODO: should see only members of groups except those from single group - //List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - //assertThat(search.stream().map(UserRepresentation::getUsername).toList(), containsInAnyOrder("bob")); + assertFilter(userBob, userTom, userMary); assertUpdate(userAlice, false); assertUpdate(userBob, true); @@ -216,8 +241,7 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { public void test_10() { allowAllUsers(); - List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - assertThat(search.stream().map(UserRepresentation::getUsername).toList(), containsInAnyOrder(userAlice.getUsername(), userBob.getUsername(), userJdoe.getUsername(), "myadmin")); + assertFilter(ALL_USERS); assertUpdate(userAlice, true); assertUpdate(userBob, true); @@ -229,8 +253,7 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { allowAllUsers(); allowAllGroups(); - List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - assertThat(search.stream().map(UserRepresentation::getUsername).toList(), containsInAnyOrder(userAlice.getUsername(), userBob.getUsername(), userJdoe.getUsername(), "myadmin")); + assertFilter(ALL_USERS); assertUpdate(userAlice, true); assertUpdate(userBob, true); @@ -242,9 +265,8 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { allowAllUsers(); denyAllGroups(); - // TODO: should not see users members of a group -// List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); -// assertThat(search.stream().map(UserRepresentation::getUsername).toList(), containsInAnyOrder("jdoe", "myadmin")); + List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); + assertThat(search.stream().map(UserRepresentation::getUsername).toList(), containsInAnyOrder(userJdoe.getUsername(), "myadmin")); assertUpdate(userAlice, false); assertUpdate(userBob, false); @@ -256,9 +278,7 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { allowAllUsers(); allowGroup("group-0"); - // TODO: should return all users -// List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); -// assertThat(search.stream().map(UserRepresentation::getUsername).toList(), containsInAnyOrder(userAlice.getUsername(), userBob.getUsername(), userJdoe.getUsername(), "myadmin")); + assertFilter(ALL_USERS); assertUpdate(userAlice, true); assertUpdate(userBob, true); @@ -271,8 +291,7 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { allowGroup("group-0"); allowAllGroups(); - List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - assertThat(search.stream().map(UserRepresentation::getUsername).toList(), containsInAnyOrder(userAlice.getUsername(), userBob.getUsername(), userJdoe.getUsername(), "myadmin")); + assertFilter(ALL_USERS); assertUpdate(userAlice, true); assertUpdate(userBob, true); @@ -285,9 +304,7 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { allowGroup("group-0"); denyAllGroups(); - // TODO: should see user that are not a member of a group or members of the single group - //List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - //assertThat(search.stream().map(UserRepresentation::getUsername).toList(), containsInAnyOrder(userAlice.getUsername())); + assertFilter(userAlice, userJdoe, myadmin); assertUpdate(userAlice, true); assertUpdate(userBob, false); @@ -299,8 +316,7 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { allowAllUsers(); denyGroup("group-0"); - List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - assertThat(search.stream().map(UserRepresentation::getUsername).toList(), containsInAnyOrder(userBob.getUsername(), userJdoe.getUsername(), "myadmin")); + assertFilter(userBob, userJdoe, myadmin, userTom, userMary); assertUpdate(userAlice, false); assertUpdate(userBob, true); @@ -313,8 +329,7 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { denyGroup("group-0"); allowAllGroups(); - List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - assertThat(search.stream().map(UserRepresentation::getUsername).toList(), containsInAnyOrder(userBob.getUsername(), userJdoe.getUsername(), "myadmin")); + assertFilter(userBob, userJdoe, myadmin, userTom, userMary); assertUpdate(userAlice, false); assertUpdate(userBob, true); @@ -327,9 +342,7 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { denyGroup("group-0"); denyAllGroups(); - // TODO: should see only users not members of groups - //List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - //assertThat(search.stream().map(UserRepresentation::getUsername).toList(), containsInAnyOrder(userJdoe.getUsername(), "myadmin")); + assertFilter(userJdoe, myadmin); assertUpdate(userAlice, false); assertUpdate(userBob, false); @@ -340,8 +353,7 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { public void test_19() { denyAllUsers(); - List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - assertThat(search.isEmpty(), is(true)); + assertFilter(); assertUpdate(userAlice, false); assertUpdate(userBob, false); @@ -353,8 +365,7 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { denyAllUsers(); allowAllGroups(); - List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - assertThat(search.isEmpty(), is(true)); + assertFilter(); assertUpdate(userAlice, false); assertUpdate(userBob, false); @@ -366,8 +377,7 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { denyAllUsers(); denyAllGroups(); - List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - assertThat(search.isEmpty(), is(true)); + assertFilter(); assertUpdate(userAlice, false); assertUpdate(userBob, false); @@ -379,9 +389,7 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { denyAllUsers(); allowGroup("group-0"); - // NOK denying all users permissions should have precedence over group permissions and not return users - //List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - //assertThat(search.isEmpty(), is(true)); + assertFilter(); assertUpdate(userAlice, false); assertUpdate(userBob, false); @@ -394,9 +402,7 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { allowGroup("group-0"); allowAllGroups(); - // NOK denying all users permissions should have precedence over group permissions and not return users - //List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - //assertThat(search.isEmpty(), is(true)); + assertFilter(); assertUpdate(userAlice, false); assertUpdate(userBob, false); @@ -409,9 +415,7 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { allowGroup("group-0"); denyAllGroups(); - // NOK denying all users permissions should have precedence over group permissions and not return users -// List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); -// assertThat(search.isEmpty(), is(true)); + assertFilter(); assertUpdate(userAlice, false); assertUpdate(userBob, false); @@ -423,8 +427,7 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { denyAllUsers(); denyGroup("group-0"); - List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - assertThat(search.isEmpty(), is(true)); + assertFilter(); assertUpdate(userAlice, false); assertUpdate(userBob, false); @@ -437,8 +440,7 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { denyGroup("group-0"); allowAllGroups(); - List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - assertThat(search.isEmpty(), is(true)); + assertFilter(); assertUpdate(userAlice, false); assertUpdate(userBob, false); @@ -451,8 +453,7 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { denyGroup("group-0"); denyAllGroups(); - List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - assertThat(search.isEmpty(), is(true)); + assertFilter(); assertUpdate(userAlice, false); assertUpdate(userBob, false); @@ -461,10 +462,9 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { @Test public void test_28() { - allowUser(userAlice.getId()); + allowUser(userAlice); - List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - assertThat(search.stream().map(UserRepresentation::getUsername).toList(), containsInAnyOrder(userAlice.getUsername())); + assertFilter(userAlice); assertUpdate(userAlice, true); assertUpdate(userBob, false); @@ -473,11 +473,10 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { @Test public void test_29() { - allowUser(userAlice.getId()); + allowUser(userAlice); allowAllGroups(); - List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - assertThat(search.stream().map(UserRepresentation::getUsername).toList(), containsInAnyOrder(userAlice.getUsername())); + assertFilter(userAlice, userBob, userTom, userMary); assertUpdate(userAlice, true); assertUpdate(userBob, true); @@ -486,11 +485,10 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { @Test public void test_30() { - allowUser(userAlice.getId()); + allowUser(userAlice); denyAllGroups(); - List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - assertThat(search.stream().map(UserRepresentation::getUsername).toList(), containsInAnyOrder(userAlice.getUsername())); + assertFilter(userAlice); assertUpdate(userAlice, true); assertUpdate(userBob, false); @@ -499,11 +497,10 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { @Test public void test_31() { - allowUser(userAlice.getId()); + allowUser(userAlice); allowGroup("group-1"); - List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - assertThat(search.stream().map(UserRepresentation::getUsername).toList(), containsInAnyOrder(userAlice.getUsername(), userBob.getUsername())); + assertFilter(userAlice, userBob); assertUpdate(userAlice, true); assertUpdate(userBob, true); @@ -512,13 +509,11 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { @Test public void test_32() { - allowUser(userAlice.getId()); + allowUser(userAlice); allowGroup("group-0"); allowAllGroups(); - //NOK all group members should be granted - //List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - //assertThat(search.stream().map(UserRepresentation::getUsername).toList(), containsInAnyOrder(userAlice.getUsername(), userBob.getUsername())); + assertFilter(userAlice, userBob, userTom, userMary); assertUpdate(userAlice, true); assertUpdate(userBob, true); @@ -527,12 +522,11 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { @Test public void test_33() { - allowUser(userAlice.getId()); + allowUser(userAlice); allowGroup("group-0"); denyAllGroups(); - List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - assertThat(search.stream().map(UserRepresentation::getUsername).toList(), containsInAnyOrder(userAlice.getUsername())); + assertFilter(userAlice); assertUpdate(userAlice, true); assertUpdate(userBob, false); @@ -541,12 +535,11 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { @Test public void test_33_a() { - allowUser(userAlice.getId()); + allowUser(userAlice); allowGroup("group-1"); denyAllGroups(); - List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - assertThat(search.stream().map(UserRepresentation::getUsername).toList(), containsInAnyOrder(userAlice.getUsername(), userBob.getUsername())); + assertFilter(userAlice, userBob); assertUpdate(userAlice, true); assertUpdate(userBob, true); @@ -555,11 +548,10 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { @Test public void test_34() { - allowUser(userAlice.getId()); + allowUser(userAlice); denyGroup("group-0"); - List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - assertThat(search.isEmpty(), is(true)); + assertFilter(); assertUpdate(userAlice, false); assertUpdate(userBob, false); @@ -568,13 +560,11 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { @Test public void test_35() { - allowUser(userAlice.getId()); + allowUser(userAlice); denyGroup("group-0"); allowAllGroups(); - //NOK should grant access to users from other groups - //List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - //assertThat(search.stream().map(UserRepresentation::getUsername).toList(), containsInAnyOrder(userBob.getUsername())); + assertFilter(userBob, userTom, userMary); assertUpdate(userAlice, false); assertUpdate(userBob, true); @@ -583,12 +573,11 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { @Test public void test_35_a() { - allowUser(userAlice.getId()); + allowUser(userAlice); denyGroup("group-1"); allowAllGroups(); - List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - assertThat(search.stream().map(UserRepresentation::getUsername).toList(), containsInAnyOrder(userAlice.getUsername())); + assertFilter(userAlice, userTom, userMary); assertUpdate(userAlice, true); assertUpdate(userBob, false); @@ -597,13 +586,11 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { @Test public void test_36() { - allowUser(userAlice.getId()); + allowUser(userAlice); denyGroup("group-0"); denyAllGroups(); - //NOK should grant access to users from other groups -// List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); -// assertThat(search.isEmpty(), is(true)); + assertFilter(); assertUpdate(userAlice, false); assertUpdate(userBob, false); @@ -612,11 +599,10 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { @Test public void test_37() { - allowUser(userAlice.getId()); + allowUser(userAlice); allowAllUsers(); - List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - assertThat(search.stream().map(UserRepresentation::getUsername).toList(), containsInAnyOrder(userAlice.getUsername(), userBob.getUsername(), userJdoe.getUsername(), "myadmin")); + assertFilter(ALL_USERS); assertUpdate(userAlice, true); assertUpdate(userBob, true); @@ -625,12 +611,11 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { @Test public void test_38() { - allowUser(userAlice.getId()); + allowUser(userAlice); allowAllUsers(); allowAllGroups(); - List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - assertThat(search.stream().map(UserRepresentation::getUsername).toList(), containsInAnyOrder(userAlice.getUsername(), userBob.getUsername(), userJdoe.getUsername(), "myadmin")); + assertFilter(ALL_USERS); assertUpdate(userAlice, true); assertUpdate(userBob, true); @@ -639,13 +624,11 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { @Test public void test_39() { - allowUser(userAlice.getId()); + allowUser(userAlice); allowAllUsers(); denyAllGroups(); - //NOK should not return members from groups other than the user that was granted - //List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - //assertThat(search.stream().map(UserRepresentation::getUsername).toList(), containsInAnyOrder(userAlice.getUsername(), userBob.getUsername(), userJdoe.getUsername(), "myadmin")); + assertFilter(userAlice, userJdoe, myadmin); assertUpdate(userAlice, true); assertUpdate(userBob, false); @@ -654,13 +637,11 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { @Test public void test_40() { - allowUser(userAlice.getId()); + allowUser(userAlice); allowAllUsers(); allowGroup("group-0"); - //NOK not sure if it should restrict or grant access -// List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); -// assertThat(search.stream().map(UserRepresentation::getUsername).toList(), containsInAnyOrder(userAlice.getUsername(), userBob.getUsername(), userJdoe.getUsername(), "myadmin")); + assertFilter(ALL_USERS); assertUpdate(userAlice, true); assertUpdate(userBob, true); @@ -669,13 +650,11 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { @Test public void test_40_a() { - allowUser(userAlice.getId()); + allowUser(userAlice); allowAllUsers(); allowGroup("group-1"); - //NOK not sure if it should restrict or grant access - //List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - //assertThat(search.stream().map(UserRepresentation::getUsername).toList(), containsInAnyOrder(userAlice.getUsername(), userBob.getUsername(), userJdoe.getUsername(), "myadmin")); + assertFilter(ALL_USERS); assertUpdate(userAlice, true); assertUpdate(userBob, true); @@ -684,13 +663,12 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { @Test public void test_41() { - allowUser(userAlice.getId()); + allowUser(userAlice); allowAllUsers(); allowGroup("group-1"); allowAllGroups(); - List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - assertThat(search.stream().map(UserRepresentation::getUsername).toList(), containsInAnyOrder(userAlice.getUsername(), userBob.getUsername(), userJdoe.getUsername(), "myadmin")); + assertFilter(ALL_USERS); assertUpdate(userAlice, true); assertUpdate(userBob, true); @@ -699,14 +677,12 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { @Test public void test_42() { - allowUser(userAlice.getId()); + allowUser(userAlice); allowAllUsers(); allowGroup("group-0"); denyAllGroups(); - //TODO should return single resource and users not members of groups -// List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); -// assertThat(search.stream().map(UserRepresentation::getUsername).toList(), containsInAnyOrder(userAlice.getUsername(), userJdoe.getUsername(), "myadmin")); + assertFilter(userAlice, userJdoe, myadmin); assertUpdate(userAlice, true); assertUpdate(userBob, false); @@ -715,14 +691,11 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { @Test public void test_42_a() { - allowUser(userAlice.getId()); + allowUser(userAlice); allowAllUsers(); allowGroup("group-1"); - denyAllGroups(); - //NOK not sure if it should restrict or grant access - //List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - //assertThat(search.stream().map(UserRepresentation::getUsername).toList(), containsInAnyOrder(userAlice.getUsername(), userBob.getUsername(), userJdoe.getUsername(), "myadmin")); + assertFilter(ALL_USERS); assertUpdate(userAlice, true); assertUpdate(userBob, true); @@ -731,12 +704,11 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { @Test public void test_43() { - allowUser(userAlice.getId()); + allowUser(userAlice); allowAllUsers(); denyGroup("group-0"); - List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - assertThat(search.stream().map(UserRepresentation::getUsername).toList(), containsInAnyOrder(userBob.getUsername(), userJdoe.getUsername(), "myadmin")); + assertFilter(userBob, userJdoe, myadmin, userTom, userMary); assertUpdate(userAlice, false); assertUpdate(userBob, true); @@ -745,13 +717,12 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { @Test public void test_44() { - allowUser(userAlice.getId()); + allowUser(userAlice); allowAllUsers(); denyGroup("group-0"); allowAllGroups(); - List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - assertThat(search.stream().map(UserRepresentation::getUsername).toList(), containsInAnyOrder(userBob.getUsername(), userJdoe.getUsername(), "myadmin")); + assertFilter(userBob, userJdoe, myadmin, userTom, userMary); assertUpdate(userAlice, false); assertUpdate(userBob, true); @@ -760,14 +731,12 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { @Test public void test_45() { - allowUser(userAlice.getId()); + allowUser(userAlice); allowAllUsers(); denyGroup("group-0"); denyAllGroups(); - //NOK filtering is not taking into account when group membership is denied to all groups - //List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - //assertThat(search.stream().map(UserRepresentation::getUsername).toList(), containsInAnyOrder(userBob.getUsername(), userJdoe.getUsername(), "myadmin")); + assertFilter(userJdoe, myadmin); assertUpdate(userAlice, false); assertUpdate(userBob, false); @@ -776,11 +745,10 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { @Test public void test_46() { - allowUser(userAlice.getId()); + allowUser(userAlice); denyAllUsers(); - List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - assertThat(search.stream().map(UserRepresentation::getUsername).toList(), containsInAnyOrder(userAlice.getUsername())); + assertFilter(userAlice); assertUpdate(userAlice, true); assertUpdate(userBob, false); @@ -789,12 +757,11 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { @Test public void test_47() { - allowUser(userAlice.getId()); + allowUser(userAlice); denyAllUsers(); allowAllGroups(); - List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - assertThat(search.stream().map(UserRepresentation::getUsername).toList(), containsInAnyOrder(userAlice.getUsername())); + assertFilter(userAlice); assertUpdate(userAlice, true); assertUpdate(userBob, false); @@ -803,12 +770,11 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { @Test public void test_48() { - allowUser(userAlice.getId()); + allowUser(userAlice); denyAllUsers(); denyAllGroups(); - List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - assertThat(search.stream().map(UserRepresentation::getUsername).toList(), containsInAnyOrder(userAlice.getUsername())); + assertFilter(userAlice); assertUpdate(userAlice, true); assertUpdate(userBob, false); @@ -817,12 +783,11 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { @Test public void test_49() { - allowUser(userAlice.getId()); + allowUser(userAlice); denyAllUsers(); allowGroup("group-0"); - List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - assertThat(search.stream().map(UserRepresentation::getUsername).toList(), containsInAnyOrder(userAlice.getUsername())); + assertFilter(userAlice); assertUpdate(userAlice, true); assertUpdate(userBob, false); @@ -831,12 +796,11 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { @Test public void test_49_a() { - allowUser(userAlice.getId()); + allowUser(userAlice); denyAllUsers(); allowGroup("group-1"); - List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - assertThat(search.stream().map(UserRepresentation::getUsername).toList(), containsInAnyOrder(userAlice.getUsername(), userBob.getUsername())); + assertFilter(userAlice); assertUpdate(userAlice, true); assertUpdate(userBob, false); @@ -845,13 +809,12 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { @Test public void test_50() { - allowUser(userAlice.getId()); + allowUser(userAlice); denyAllUsers(); allowGroup("group-0"); allowAllGroups(); - List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - assertThat(search.stream().map(UserRepresentation::getUsername).toList(), containsInAnyOrder(userAlice.getUsername())); + assertFilter(userAlice); assertUpdate(userAlice, true); assertUpdate(userBob, false); @@ -860,13 +823,12 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { @Test public void test_51() { - allowUser(userAlice.getId()); + allowUser(userAlice); denyAllUsers(); allowGroup("group-0"); denyAllGroups(); - List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - assertThat(search.stream().map(UserRepresentation::getUsername).toList(), containsInAnyOrder(userAlice.getUsername())); + assertFilter(userAlice); assertUpdate(userAlice, true); assertUpdate(userBob, false); @@ -875,12 +837,11 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { @Test public void test_52() { - allowUser(userAlice.getId()); + allowUser(userAlice); denyAllUsers(); denyGroup("group-0"); - List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - assertThat(search.isEmpty(), is(true)); + assertFilter(); assertUpdate(userAlice, false); assertUpdate(userBob, false); @@ -889,12 +850,11 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { @Test public void test_52_a() { - allowUser(userAlice.getId()); + allowUser(userAlice); denyAllUsers(); denyGroup("group-1"); - List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - assertThat(search.stream().map(UserRepresentation::getUsername).toList(), containsInAnyOrder(userAlice.getUsername())); + assertFilter(userAlice); assertUpdate(userAlice, true); assertUpdate(userBob, false); @@ -903,13 +863,12 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { @Test public void test_53() { - allowUser(userAlice.getId()); + allowUser(userAlice); denyAllUsers(); denyGroup("group-0"); allowAllGroups(); - List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); - assertThat(search.isEmpty(), is(true)); + assertFilter(); assertUpdate(userAlice, false); assertUpdate(userBob, false); @@ -918,26 +877,104 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { @Test public void test_54() { - allowUser(userAlice.getId()); + allowUser(userAlice); denyAllUsers(); denyGroup("group-0"); denyAllGroups(); - // NOK should return alice because the resource is granted -// List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); -// assertThat(search.isEmpty(), is(true)); + assertFilter(); assertUpdate(userAlice, false); assertUpdate(userBob, false); assertUpdate(userJdoe, false); } + @Test + public void test_55() { + allowAllUsers(); + denyUser(userAlice); + denyUser(userJdoe); + + assertFilter(userBob, myadmin, userMary, userTom); + + assertUpdate(userBob, true); + assertUpdate(userAlice, false); + assertUpdate(userJdoe, false); + } + + @Test + public void test_56() { + allowUser(userJdoe); + denyUser(userBob); + + assertFilter(userJdoe); + + assertUpdate(userJdoe, true); + assertUpdate(userBob, false); + assertUpdate(userAlice, false); + } + + @Test + public void test_57() { + denyUser(userAlice); + denyUser(userJdoe); + + assertFilter(); + + assertUpdate(userBob, false); + assertUpdate(userAlice, false); + assertUpdate(userJdoe, false); + } + + @Test + public void test_58() { + denyUser(userTom); + allowGroup("group-2"); + + assertFilter(userMary); + + assertUpdate(userMary, true); + assertUpdate(userTom, false); + assertUpdate(userJdoe, false); + } + + @Test + public void test_59() { + allowAllUsers(); + denyUser(userTom); + allowGroup("group-1"); + + assertFilter(userAlice, userBob, userMary, userJdoe, myadmin); + + assertUpdate(userMary, true); + assertUpdate(userJdoe, true); + assertUpdate(userTom, false); + } + + @Test + public void test_60() { + allowAllUsers(); + denyUser(userTom); + allowGroup("group-1"); + denyGroup("group-2"); + + assertFilter(userAlice, userBob, userJdoe, myadmin); + + assertUpdate(userJdoe, true); + assertUpdate(userMary, false); + assertUpdate(userTom, false); + } + + private void denyUser(ManagedUser user) { + createPermission(client, user.getId(), USERS_RESOURCE_TYPE, Set.of(VIEW, MANAGE), denyPolicy); + } + private void allowAllUsers() { createAllPermission(client, USERS_RESOURCE_TYPE, allowPolicy, Set.of(VIEW, MANAGE)); } - private void allowUser(String id) { - createPermission(client, id , USERS_RESOURCE_TYPE, Set.of(VIEW, MANAGE), allowPolicy); + private void allowUser(ManagedUser user) { + createPermission(client, user.getId() , USERS_RESOURCE_TYPE, Set.of(VIEW, MANAGE), allowPolicy); } private void denyAllUsers() { @@ -1011,4 +1048,18 @@ public class UserResourceTypeEvaluationSpecTest extends AbstractPermissionTest { } } } + + private void assertFilter(List expected) { + assertFilter(expected.toArray(new ManagedUser[0])); + } + + private void assertFilter(ManagedUser... expected) { + List search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1); + + if (expected.length == 0) { + assertThat(search.isEmpty(), is(true)); + } else { + assertThat(search.stream().map(UserRepresentation::getUsername).toList(), containsInAnyOrder(Stream.of(expected).map(ManagedUser::getUsername).toArray())); + } + } }