Fix permissions for view-members and manage-members

Closes #38013

Signed-off-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
Pedro Igor 2025-03-11 09:55:29 -03:00
parent 2b245059e3
commit b200ab0792
9 changed files with 83 additions and 47 deletions

View File

@ -17,6 +17,7 @@
package org.keycloak.models.jpa;
import org.keycloak.authorization.AdminPermissionsSchema;
import org.keycloak.authorization.jpa.entities.ResourceEntity;
import org.keycloak.common.util.Time;
import org.keycloak.component.ComponentModel;
@ -1078,7 +1079,14 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
List<Predicate> subs = new ArrayList<>();
Expression<String> groupId = from.get("groupId");
subs.add(cb.like(from1.get("name"), cb.concat("group.resource.", 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));

View File

@ -225,8 +225,8 @@ public class AvailableRoleMappingResource extends RoleMappingResource {
}
private Set<String> getRoleIdsWithPermissions(String roleResourceScope, String clientResourceScope) {
Set<String> roleIds = this.auth.roles().getRolesWithPermission(roleResourceScope);
Set<String> clientIds = this.auth.clients().getClientsWithPermission(clientResourceScope);
Set<String> roleIds = this.auth.roles().getRoleIdsWithViewPermission(roleResourceScope);
Set<String> clientIds = this.auth.clients().getClientIdsWithViewPermission(clientResourceScope);
clientIds.stream().flatMap(cid -> realm.getClientById(cid).getRolesStream()).forEach(role -> roleIds.add(role.getId()));
return roleIds;
}

View File

@ -191,5 +191,11 @@ public interface ClientPermissionEvaluator {
Map<String, Boolean> getAccess(ClientModel client);
Set<String> getClientsWithPermission(String scope);
/**
* Returns the IDs of the clients that the current user can view..
*
* @return Stream of IDs of clients with view permission.
*/
Set<String> getClientIdsWithViewPermission(String scope);
}

View File

@ -26,6 +26,7 @@ import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.permission.ResourcePermission;
import org.keycloak.authorization.policy.evaluation.EvaluationContext;
import org.keycloak.authorization.store.PolicyStore;
import org.keycloak.authorization.store.ResourceStore;
import org.keycloak.models.AdminRoles;
import org.keycloak.models.ClientModel;
@ -63,6 +64,7 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionM
protected final AuthorizationProvider authz;
protected final MgmtPermissions root;
protected final ResourceStore resourceStore;
protected final PolicyStore policyStore;
private static final String RESOURCE_NAME_PREFIX = "client.resource.";
@ -73,8 +75,10 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionM
this.root = root;
if (authz != null) {
resourceStore = authz.getStoreFactory().getResourceStore();
policyStore = authz.getStoreFactory().getPolicyStore();
} else {
resourceStore = null;
policyStore = null;
}
}
@ -664,7 +668,7 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionM
}
@Override
public Set<String> getClientsWithPermission(String scope) {
public Set<String> getClientIdsWithViewPermission(String scope) {
if (!root.isAdminSameRealm()) {
return Collections.emptySet();
}

View File

@ -42,6 +42,8 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Stream;
import static org.keycloak.services.resources.admin.permissions.AdminPermissionManagement.TOKEN_EXCHANGE;
@ -122,7 +124,7 @@ public class ClientPermissionsV2 extends ClientPermissions {
}
@Override
public Set<String> getClientsWithPermission(String scope) {
public Set<String> getClientIdsWithViewPermission(String scope) {
if (!root.isAdminSameRealm()) {
return Collections.emptySet();
}
@ -135,11 +137,13 @@ public class ClientPermissionsV2 extends ClientPermissions {
Set<String> granted = new HashSet<>();
resourceStore.findByType(server, AdminPermissionsSchema.CLIENTS_RESOURCE_TYPE, resource -> {
if (hasGrantedPermission(resource, scope)) {
granted.add(resource.getName());
}
});
policyStore.findByResourceType(server, AdminPermissionsSchema.CLIENTS_RESOURCE_TYPE).stream()
.flatMap((Function<Policy, Stream<Resource>>) policy -> policy.getResources().stream())
.forEach(resource -> {
if (hasGrantedPermission(resource, scope)) {
granted.add(resource.getName());
}
});
return granted;
}
@ -224,9 +228,11 @@ public class ClientPermissionsV2 extends ClientPermissions {
Collection<Permission> permissions = root.evaluatePermission(new ResourcePermission(resource, resource.getScopes(), server), server);
List<String> expectedScopes = Arrays.asList(scope);
for (Permission permission : permissions) {
for (String s : permission.getScopes()) {
if (expectedScopes.contains(s)) {
return true;
if (permission.getResourceId().equals(resource.getId())) {
for (String s : permission.getScopes()) {
if (expectedScopes.contains(s)) {
return true;
}
}
}
}
@ -259,25 +265,15 @@ public class ClientPermissionsV2 extends ClientPermissions {
return false;
}
private EvaluationContext getEvaluationContext(ClientModel authorizedClient, AccessToken token) {
ClientModelIdentity identity = new ClientModelIdentity(session, authorizedClient, token);
return new DefaultEvaluationContext(identity, session) {
@Override
public Map<String, Collection<String>> getBaseAttributes() {
Map<String, Collection<String>> attributes = super.getBaseAttributes();
attributes.put("kc.client.id", List.of(authorizedClient.getClientId()));
return attributes;
}
};
}
private boolean hasGrantedPermission(Resource resource, String scope) {
ResourceServer server = root.realmResourceServer();
Collection<Permission> permissions = root.evaluatePermission(new ResourcePermission(resource, resource.getScopes(), server), server);
for (Permission permission : permissions) {
for (String s : permission.getScopes()) {
if (scope.equals(s)) {
return true;
if (permission.getResourceId().equals(resource.getId())) {
for (String s : permission.getScopes()) {
if (scope.equals(s)) {
return true;
}
}
}
}

View File

@ -23,6 +23,9 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Stream;
import org.keycloak.authorization.AdminPermissionsSchema;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.model.Policy;
@ -118,9 +121,11 @@ class GroupPermissionsV2 extends GroupPermissions {
Set<String> granted = new HashSet<>();
resourceStore.findByType(server, AdminPermissionsSchema.GROUPS_RESOURCE_TYPE, groupResource -> {
if (hasPermission(groupResource.getId(), AdminPermissionsSchema.VIEW_MEMBERS, AdminPermissionsSchema.MANAGE_MEMBERS)) {
granted.add(groupResource.getId());
policyStore.findByResourceType(server, AdminPermissionsSchema.GROUPS_RESOURCE_TYPE).stream()
.flatMap((Function<Policy, Stream<Resource>>) policy -> policy.getResources().stream())
.forEach(gr -> {
if (hasPermission(gr.getName(), AdminPermissionsSchema.VIEW_MEMBERS, AdminPermissionsSchema.MANAGE_MEMBERS)) {
granted.add(gr.getName());
}
});
@ -154,9 +159,11 @@ class GroupPermissionsV2 extends GroupPermissions {
List<String> expectedScopes = Arrays.asList(scopes);
for (Permission permission : permissions) {
for (String scope : permission.getScopes()) {
if (expectedScopes.contains(scope)) {
return true;
if (permission.getResourceId().equals(resource.getId())) {
for (String scope : permission.getScopes()) {
if (expectedScopes.contains(scope)) {
return true;
}
}
}
}

View File

@ -141,5 +141,10 @@ public interface RolePermissionEvaluator {
*/
void requireView(RoleContainerModel container);
Set<String> getRolesWithPermission(String scope);
/**
* Returns the IDs of the roles that the current user can view..
*
* @return Stream of IDs of roles with view permission.
*/
Set<String> getRoleIdsWithViewPermission(String scope);
}

View File

@ -24,6 +24,7 @@ import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.permission.ResourcePermission;
import org.keycloak.authorization.store.PolicyStore;
import org.keycloak.authorization.store.ResourceStore;
import org.keycloak.models.AdminRoles;
import org.keycloak.models.ClientModel;
@ -55,6 +56,7 @@ class RolePermissions implements RolePermissionEvaluator, RolePermissionManageme
protected final AuthorizationProvider authz;
protected final MgmtPermissions root;
protected final ResourceStore resourceStore;
protected final PolicyStore policyStore;
private static final String RESOURCE_NAME_PREFIX = "role.resource.";
public RolePermissions(KeycloakSession session, RealmModel realm, AuthorizationProvider authz, MgmtPermissions root) {
@ -64,8 +66,10 @@ class RolePermissions implements RolePermissionEvaluator, RolePermissionManageme
this.root = root;
if (authz != null) {
resourceStore = authz.getStoreFactory().getResourceStore();
policyStore = authz.getStoreFactory().getPolicyStore();
} else {
resourceStore = null;
policyStore = null;
}
}
@ -540,7 +544,7 @@ class RolePermissions implements RolePermissionEvaluator, RolePermissionManageme
}
@Override
public Set<String> getRolesWithPermission(String scope) {
public Set<String> getRoleIdsWithViewPermission(String scope) {
if (!root.isAdminSameRealm()) {
return Collections.emptySet();
}

View File

@ -35,6 +35,8 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Stream;
public class RolePermissionsV2 extends RolePermissions {
@ -76,7 +78,7 @@ public class RolePermissionsV2 extends RolePermissions {
}
@Override
public Set<String> getRolesWithPermission(String scope) {
public Set<String> getRoleIdsWithViewPermission(String scope) {
if (!root.isAdminSameRealm()) {
return Collections.emptySet();
}
@ -89,11 +91,13 @@ public class RolePermissionsV2 extends RolePermissions {
Set<String> granted = new HashSet<>();
resourceStore.findByType(server, AdminPermissionsSchema.ROLES_RESOURCE_TYPE, resource -> {
if (hasGrantedPermission(server, resource, scope)) {
granted.add(resource.getName());
}
});
policyStore.findByResourceType(server, AdminPermissionsSchema.ROLES_RESOURCE_TYPE).stream()
.flatMap((Function<Policy, Stream<Resource>>) policy -> policy.getResources().stream())
.forEach(gr -> {
if (hasGrantedPermission(server, gr, scope)) {
granted.add(gr.getName());
}
});
return granted;
}
@ -121,11 +125,13 @@ public class RolePermissionsV2 extends RolePermissions {
private boolean hasGrantedPermission(ResourceServer server, Resource resource, String scope) {
Collection<Permission> permissions = root.evaluatePermission(new ResourcePermission(resource, resource.getScopes(), server), server);
for (Permission permission : permissions) {
for (String s : permission.getScopes()) {
if (scope.equals(s)) {
return true;
if (permission.getResourceId().equals(resource.getId())) {
for (String s : permission.getScopes()) {
if (scope.equals(s)) {
return true;
}
}
}
};
}
return false;