Allow mapping Admin roles by server administrator only

Closes #39956


Signed-off-by: vramik <vramik@redhat.com>
This commit is contained in:
vramik 2025-05-26 12:45:16 +02:00 committed by Pedro Igor
parent a712692535
commit 9590221ef8
2 changed files with 47 additions and 91 deletions

View File

@ -18,12 +18,9 @@
package org.keycloak.services.resources.admin.fgap;
import static org.keycloak.authorization.fgap.AdminPermissionsSchema.ROLES_RESOURCE_TYPE;
import static org.keycloak.models.utils.KeycloakModelUtils.getMasterRealmAdminManagementClientId;
import static org.keycloak.services.managers.RealmManager.isAdministrationRealm;
import java.util.Map;
import java.util.Set;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.authorization.fgap.AdminPermissionsSchema;
import org.keycloak.authorization.AuthorizationProvider;
@ -34,13 +31,11 @@ import org.keycloak.models.AdminRoles;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleContainerModel;
import org.keycloak.models.RoleModel;
import org.keycloak.services.resources.admin.fgap.ModelRecord.RoleModelRecord;
class RolePermissionsV2 extends RolePermissions {
private static final Logger logger = Logger.getLogger(RolePermissionsV2.class);
private final FineGrainedAdminPermissionEvaluator eval;
RolePermissionsV2(KeycloakSession session, RealmModel realm, AuthorizationProvider authz, MgmtPermissions root) {
@ -48,9 +43,20 @@ class RolePermissionsV2 extends RolePermissions {
this.eval = new FineGrainedAdminPermissionEvaluator(session, root, resourceStore, policyStore);
}
private boolean hasMasterAdminRole() {
RealmModel masterRealm = root.adminsRealm().getName().equals(Config.getAdminRealm()) ?
root.adminsRealm():
session.realms().getRealmByName(Config.getAdminRealm());
RoleModel adminRole = masterRealm.getRole(AdminRoles.ADMIN);
return root.admin().hasRole(adminRole);
}
@Override
public boolean canMapRole(RoleModel role) {
if (!canMapAdminRole(role)) return false;
if (AdminRoles.ALL_ROLES.contains(role.getName()) && !hasMasterAdminRole()) {
return false;
}
if (root.hasOneAdminRole(AdminRoles.MANAGE_USERS)) {
return true;
@ -67,7 +73,9 @@ class RolePermissionsV2 extends RolePermissions {
@Override
public boolean canMapComposite(RoleModel role) {
if (!canMapAdminRole(role)) return false;
if (AdminRoles.ALL_ROLES.contains(role.getName()) && !hasMasterAdminRole()) {
return false;
}
if (role.getContainer() instanceof ClientModel clientModel) {
if (root.hasOneAdminRole(AdminRoles.MANAGE_CLIENTS)) {
@ -108,90 +116,6 @@ class RolePermissionsV2 extends RolePermissions {
return eval.getIdsByScope(ROLES_RESOURCE_TYPE, scope);
}
boolean canMapAdminRole(RoleModel role) {
if (!AdminRoles.ALL_ROLES.contains(role.getName())) {
return true;
}
if (root.admin().hasRole(role)) return true;
if (!role.isClientRole()) { //realm role
// if realm name is master realm, than we know this is a admin role ("admin" or "create-realm") in master realm.
return isAdministrationRealm((RealmModel) role.getContainer()) ? adminConflictMessage(role) : true;
} else {
// management client in master realm
ClientModel client = (ClientModel)role.getContainer();
if (isAdministrationRealm(client.getRealm()) && client.getClientId().equals(getMasterRealmAdminManagementClientId(client.getRealm().getName()))) {
return adminConflictMessage(role);
}
switch (role.getName()) {
case AdminRoles.REALM_ADMIN:
// check to see if we have masterRealm.admin role. Otherwise abort
if (root.adminsRealm() == null || !root.adminsRealm().getName().equals(Config.getAdminRealm())) {
return adminConflictMessage(role);
}
RealmModel masterRealm = root.adminsRealm();
RoleModel adminRole = masterRealm.getRole(AdminRoles.ADMIN);
return root.admin().hasRole(adminRole) ? true : adminConflictMessage(role);
case AdminRoles.QUERY_CLIENTS:
case AdminRoles.QUERY_GROUPS:
case AdminRoles.QUERY_REALMS:
case AdminRoles.QUERY_USERS:
return true;
case AdminRoles.MANAGE_CLIENTS:
case AdminRoles.CREATE_CLIENT:
return root.clients().canManage() ? true : adminConflictMessage(role);
case AdminRoles.VIEW_CLIENTS:
return root.clients().canView() ? true : adminConflictMessage(role);
case AdminRoles.MANAGE_USERS:
return root.users().canManage() ? true : adminConflictMessage(role);
case AdminRoles.VIEW_USERS:
return root.users().canView() ? true : adminConflictMessage(role);
case AdminRoles.IMPERSONATION:
return root.users().canImpersonate() ? true : adminConflictMessage(role);
case AdminRoles.MANAGE_REALM:
return root.realm().canManageRealm() ? true : adminConflictMessage(role);
case AdminRoles.VIEW_REALM:
return root.realm().canViewRealm() ? true : adminConflictMessage(role);
case AdminRoles.MANAGE_AUTHORIZATION:
return root.realm().canManageAuthorization(getResourceServer(role)) ? true : adminConflictMessage(role);
case AdminRoles.VIEW_AUTHORIZATION:
return root.realm().canViewAuthorization(getResourceServer(role)) ? true : adminConflictMessage(role);
case AdminRoles.MANAGE_EVENTS:
return root.realm().canManageEvents() ? true : adminConflictMessage(role);
case AdminRoles.VIEW_EVENTS:
return root.realm().canViewEvents() ? true : adminConflictMessage(role);
case AdminRoles.MANAGE_IDENTITY_PROVIDERS:
return root.realm().canManageIdentityProviders() ? true : adminConflictMessage(role);
case AdminRoles.VIEW_IDENTITY_PROVIDERS:
return root.realm().canViewIdentityProviders() ? true : adminConflictMessage(role);
default:
return adminConflictMessage(role);
}
}
}
private boolean adminConflictMessage(RoleModel role) {
logger.debugf("Trying to assign admin privileges of role: %s but admin doesn't have same privilege", role.getName());
return false;
}
private ResourceServer getResourceServer(RoleModel role) {
ResourceServer resourceServer = null;
if (role.isClientRole()) {
RoleContainerModel container = role.getContainer();
resourceServer = session.getProvider(AuthorizationProvider.class).getStoreFactory().getResourceServerStore().findById(container.getId());
}
return resourceServer;
}
@Override
public boolean isPermissionsEnabled(RoleModel role) {
throw new UnsupportedOperationException("Not supported in V2");

View File

@ -46,6 +46,7 @@ import static org.keycloak.authorization.fgap.AdminPermissionsSchema.MAP_ROLES;
import static org.keycloak.authorization.fgap.AdminPermissionsSchema.MAP_ROLE_CLIENT_SCOPE;
import static org.keycloak.authorization.fgap.AdminPermissionsSchema.MAP_ROLE_COMPOSITE;
import static org.keycloak.authorization.fgap.AdminPermissionsSchema.VIEW;
import org.keycloak.models.AdminRoles;
@KeycloakIntegrationTest
public class RoleResourceTypeEvaluationTest extends AbstractPermissionTest {
@ -162,4 +163,35 @@ public class RoleResourceTypeEvaluationTest extends AbstractPermissionTest {
assertThat(ex, instanceOf(ForbiddenException.class));
}
}
@Test
public void testMappingAdminRoles() {
UserRepresentation myadmin = realm.admin().users().search("myadmin").get(0);
ClientRepresentation realmManagement = realm.admin().clients().findByClientId("realm-management").get(0);
RoleRepresentation createClientRole = realm.admin().clients().get(realmManagement.getId()).roles().get(AdminRoles.CREATE_CLIENT).toRepresentation();
// create permission to map roles from all clients and to all users
UserPolicyRepresentation onlyMyAdminUserPolicy = createUserPolicy(realm, client, "Only My Admin User Policy", myadmin.getId());
createAllPermission(client, AdminPermissionsSchema.CLIENTS_RESOURCE_TYPE, onlyMyAdminUserPolicy, Set.of(MAP_ROLES));
createAllPermission(client, AdminPermissionsSchema.USERS_RESOURCE_TYPE, onlyMyAdminUserPolicy, Set.of(MAP_ROLES));
// create a role
RoleRepresentation role = new RoleRepresentation();
role.setName("myRole");
ClientRepresentation myclient = realm.admin().clients().findByClientId("myclient").get(0);
realm.admin().clients().get(myclient.getId()).roles().create(role);
role = realm.admin().clients().get(myclient.getId()).roles().get("myRole").toRepresentation();
// should pass
realmAdminClient.realm(realm.getName()).users().get(myadmin.getId()).roles().clientLevel(myclient.getId()).add(List.of(role));
// should fail as it is admin role and myadmin does not have master realm admin role assigned
try {
realmAdminClient.realm(realm.getName()).users().get(myadmin.getId()).roles().clientLevel(realmManagement.getId())
.add(List.of(createClientRole));
fail("Expected exception wasn't thrown.");
} catch (Exception ex) {
assertThat(ex, instanceOf(ForbiddenException.class));
}
}
}