mirror of
https://github.com/keycloak/keycloak.git
synced 2026-02-03 20:39:33 -05:00
Only realm admins can manage workflows
Some checks failed
Weblate Sync / Trigger Weblate to pull the latest changes (push) Has been cancelled
Some checks failed
Weblate Sync / Trigger Weblate to pull the latest changes (push) Has been cancelled
Closes #45875 Signed-off-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
parent
2dab08d5ed
commit
13cf35ded3
8 changed files with 124 additions and 51 deletions
|
|
@ -18,6 +18,7 @@ to realm administrators but doing so will skip the mechanisms provided by this f
|
|||
|
||||
Enforcing access to realm resources only applies when managing resources through the administration console or the Admin API.
|
||||
|
||||
[[_understanding_different_types_realm_admins_]]
|
||||
==== Understanding the different types of realm administrators
|
||||
|
||||
There are three different types of realm administrators when managing a realm:
|
||||
|
|
@ -26,7 +27,7 @@ There are three different types of realm administrators when managing a realm:
|
|||
|
||||
* **Realm administrators**: These are users (or service accounts) that have been granted with the `realm-admin` role in a specific realm.
|
||||
|
||||
* **Delegated realm administrators**: These are users (or service accounts) that do not have the `realm-admin` role but have been granted access to manage
|
||||
* **Delegated realm administrators**: These are users (or service accounts) other than the types above but granted access to manage
|
||||
a realm through the fine-grained admin permissions feature.
|
||||
|
||||
Both server and realm administrators can manage a realm with full access to all their resources and
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@
|
|||
|
||||
Workflows can be managed through the Admin Console or the Admin REST API.
|
||||
|
||||
Only realm administrators with the appropriate permissions can manage workflows as they are considered sensitive operations.
|
||||
For more details, see <<_understanding_different_types_realm_admins_,Understanding different types of Realm Admins>>.
|
||||
|
||||
== Managing workflows through the Admin Console
|
||||
|
||||
To manage workflows through the Admin Console, you can follow these steps:
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ public interface AdminPermissionEvaluator {
|
|||
|
||||
void requireAnyAdminRole();
|
||||
boolean hasOneAdminRole(String... adminRoles);
|
||||
void requireRealmAdmin();
|
||||
|
||||
AdminAuth adminAuth();
|
||||
|
||||
|
|
@ -35,6 +36,8 @@ public interface AdminPermissionEvaluator {
|
|||
ClientPermissionEvaluator clients();
|
||||
GroupPermissionEvaluator groups();
|
||||
|
||||
boolean isRealmAdmin();
|
||||
|
||||
/**
|
||||
* Useful as a function pointer, i.e. RoleMapperResource is reused bewteen GroupResource and UserResource to manage role mappings.
|
||||
* We don't know what type of resource we're managing here (user or group), so we don't know how to query the policy engine to determine
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ import org.keycloak.models.Constants;
|
|||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.idm.authorization.Permission;
|
||||
|
|
@ -140,6 +141,14 @@ class MgmtPermissions implements AdminPermissionEvaluator, AdminPermissionManage
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requireRealmAdmin() {
|
||||
if (isRealmAdmin()) {
|
||||
return;
|
||||
}
|
||||
throw new ForbiddenException();
|
||||
}
|
||||
|
||||
public boolean hasAnyAdminRole() {
|
||||
return hasOneAdminRole(AdminRoles.ALL_REALM_ROLES);
|
||||
}
|
||||
|
|
@ -392,7 +401,42 @@ class MgmtPermissions implements AdminPermissionEvaluator, AdminPermissionManage
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRealmAdmin() {
|
||||
RealmModel masterRealm = getMasterRealm();
|
||||
UserModel admin = admin();
|
||||
RoleModel masterAdminRole = masterRealm.getRole(AdminRoles.ADMIN);
|
||||
|
||||
if (admin.hasRole(masterAdminRole)) {
|
||||
// server admin
|
||||
return true;
|
||||
}
|
||||
|
||||
ClientModel realmManagementClient = getRealmManagementClient();
|
||||
|
||||
if (realmManagementClient != null && !realmManagementClient.getRealm().equals(masterRealm)) {
|
||||
RoleModel realmAdminRole = realmManagementClient.getRole(AdminRoles.REALM_ADMIN);
|
||||
|
||||
if (realmAdminRole != null && admin.hasRole(realmAdminRole)) {
|
||||
// realm admin
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
RealmModel getMasterRealm() {
|
||||
return adminsRealm().getName().equals(Config.getAdminRealm()) ?
|
||||
adminsRealm():
|
||||
session.realms().getRealmByName(Config.getAdminRealm());
|
||||
}
|
||||
|
||||
ClientModel getRealmManagementClient() {
|
||||
if (realm.getName().equals(Config.getAdminRealm())) {
|
||||
return realm.getClientByClientId(Config.getAdminRealm() + "-realm");
|
||||
} else {
|
||||
return realm.getClientByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@ import org.keycloak.authorization.store.PolicyStore;
|
|||
import org.keycloak.authorization.store.ResourceStore;
|
||||
import org.keycloak.models.AdminRoles;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleContainerModel;
|
||||
|
|
@ -155,7 +154,7 @@ class RolePermissions implements RolePermissionEvaluator, RolePermissionManageme
|
|||
if (AdminRoles.ALL_ROLES.contains(role.getName())) {
|
||||
if (root.admin().hasRole(role)) return true;
|
||||
|
||||
ClientModel adminClient = getRealmManagementClient();
|
||||
ClientModel adminClient = root.getRealmManagementClient();
|
||||
// is this an admin role in 'realm-management' client of the realm we are managing?
|
||||
if (adminClient.equals(role.getContainer())) {
|
||||
// if this is realm admin role, then check to see if admin has similar permissions
|
||||
|
|
@ -669,13 +668,4 @@ class RolePermissions implements RolePermissionEvaluator, RolePermissionManageme
|
|||
}
|
||||
return resourceServer;
|
||||
}
|
||||
|
||||
protected ClientModel getRealmManagementClient() {
|
||||
if (realm.getName().equals(Config.getAdminRealm())) {
|
||||
return realm.getClientByClientId(Config.getAdminRealm() + "-realm");
|
||||
} else {
|
||||
return realm.getClientByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ import org.keycloak.models.KeycloakSession;
|
|||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleContainerModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.services.resources.admin.fgap.ModelRecord.RoleModelRecord;
|
||||
|
||||
import static org.keycloak.authorization.fgap.AdminPermissionsSchema.ROLES_RESOURCE_TYPE;
|
||||
|
|
@ -46,32 +45,12 @@ class RolePermissionsV2 extends RolePermissions {
|
|||
this.eval = new FineGrainedAdminPermissionEvaluator(session, root, resourceStore, policyStore);
|
||||
}
|
||||
|
||||
private boolean isRealmAdmin() {
|
||||
RealmModel masterRealm = getMasterRealm();
|
||||
UserModel admin = root.admin();
|
||||
RoleModel masterAdminRole = masterRealm.getRole(AdminRoles.ADMIN);
|
||||
|
||||
if (admin.hasRole(masterAdminRole)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ClientModel realmManagementClient = getRealmManagementClient();
|
||||
|
||||
if (realmManagementClient != null) {
|
||||
RoleModel realmAdminRole = realmManagementClient.getRole(AdminRoles.REALM_ADMIN);
|
||||
|
||||
return realmAdminRole != null && admin.hasRole(realmAdminRole);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canMapRole(RoleModel role) {
|
||||
if (isRealmAdminRole(role)) {
|
||||
if (realm.isAdminPermissionsEnabled()) {
|
||||
// only server or realm admins can map roles if FGAP is enabled
|
||||
return isRealmAdmin();
|
||||
return root.isRealmAdmin();
|
||||
}
|
||||
// otherwise, check if the user is granted with manage-users and is granted with the role being granted
|
||||
return root.hasOneAdminRole(AdminRoles.MANAGE_USERS) && checkAdminRoles(role);
|
||||
|
|
@ -96,7 +75,7 @@ class RolePermissionsV2 extends RolePermissions {
|
|||
if (isRealmAdminRole(role)) {
|
||||
if (realm.isAdminPermissionsEnabled()) {
|
||||
// only server or realm admins can map roles if FGAP is enabled
|
||||
return isRealmAdmin();
|
||||
return root.isRealmAdmin();
|
||||
}
|
||||
// otherwise, check if the user is granted with manage-realm or manage-client roles and is granted with the role being granted
|
||||
return canManageDefault(role) && checkAdminRoles(role);
|
||||
|
|
@ -203,11 +182,11 @@ class RolePermissionsV2 extends RolePermissions {
|
|||
|
||||
private boolean isRealmAdminRole(RoleModel role) {
|
||||
RoleContainerModel container = role.getContainer();
|
||||
boolean isMasterRealmRole = container.equals(getMasterRealm());
|
||||
boolean isMasterRealmRole = container.equals(root.getMasterRealm());
|
||||
boolean isMasterRealmManagementAdminRole = (container instanceof ClientModel c)
|
||||
&& c.getRealm().getName().equals(Config.getAdminRealm())
|
||||
&& c.getClientId().endsWith("-realm");
|
||||
boolean isRealmManagementAdminRole = container.equals(getRealmManagementClient());
|
||||
boolean isRealmManagementAdminRole = container.equals(root.getRealmManagementClient());
|
||||
|
||||
if (isMasterRealmRole|| isRealmManagementAdminRole || isMasterRealmManagementAdminRole) {
|
||||
return AdminRoles.ALL_ROLES.contains(role.getName());
|
||||
|
|
@ -215,10 +194,4 @@ class RolePermissionsV2 extends RolePermissions {
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
private RealmModel getMasterRealm() {
|
||||
return root.adminsRealm().getName().equals(Config.getAdminRealm()) ?
|
||||
root.adminsRealm():
|
||||
session.realms().getRealmByName(Config.getAdminRealm());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ public class WorkflowsResource {
|
|||
if (!Profile.isFeatureEnabled(Feature.WORKFLOWS)) {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
auth.requireRealmAdmin();
|
||||
this.session = session;
|
||||
this.provider = session.getProvider(WorkflowProvider.class);
|
||||
this.auth = auth;
|
||||
|
|
@ -64,8 +65,6 @@ public class WorkflowsResource {
|
|||
@APIResponse(responseCode = "400", description = "Bad Request")
|
||||
})
|
||||
public Response create(WorkflowRepresentation rep) {
|
||||
auth.realm().requireManageRealm();
|
||||
|
||||
try {
|
||||
Workflow workflow = provider.toModel(rep);
|
||||
return Response.created(session.getContext().getUri().getRequestUriBuilder().path(workflow.getId()).build()).build();
|
||||
|
|
@ -88,8 +87,6 @@ public class WorkflowsResource {
|
|||
@Parameter(description = "Workflow identifier")
|
||||
@PathParam("id") String id
|
||||
) {
|
||||
auth.realm().requireManageRealm();
|
||||
|
||||
Workflow workflow = provider.getWorkflow(id);
|
||||
|
||||
if (workflow == null) {
|
||||
|
|
@ -120,8 +117,6 @@ public class WorkflowsResource {
|
|||
@Parameter(description = "The maximum number of results to be returned - defaults to 10")
|
||||
@QueryParam("max") @DefaultValue("10") Integer maxResults
|
||||
) {
|
||||
auth.realm().requireManageRealm();
|
||||
|
||||
int first = Optional.ofNullable(firstResult).orElse(0);
|
||||
int max = Optional.ofNullable(maxResults).orElse(10);
|
||||
return provider.getWorkflows(search, exact, first, max).map(provider::toRepresentation).toList();
|
||||
|
|
@ -143,7 +138,6 @@ public class WorkflowsResource {
|
|||
@Parameter(description = "Identifier of the resource associated with the scheduled workflows")
|
||||
@PathParam("resource-id") String resourceId
|
||||
) {
|
||||
auth.realm().requireManageRealm();
|
||||
return provider.getScheduledWorkflowsByResource(resourceId).toList();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,8 +20,10 @@ package org.keycloak.tests.admin.authz.fgap;
|
|||
import java.util.List;
|
||||
|
||||
import jakarta.ws.rs.ForbiddenException;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
|
||||
import org.keycloak.admin.client.Keycloak;
|
||||
import org.keycloak.common.Profile.Feature;
|
||||
import org.keycloak.models.AdminRoles;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
|
|
@ -32,6 +34,11 @@ import org.keycloak.testframework.admin.AdminClientFactory;
|
|||
import org.keycloak.testframework.annotations.InjectAdminClient;
|
||||
import org.keycloak.testframework.annotations.InjectAdminClientFactory;
|
||||
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
|
||||
import org.keycloak.testframework.realm.UserConfigBuilder;
|
||||
import org.keycloak.testframework.server.KeycloakServerConfig;
|
||||
import org.keycloak.testframework.server.KeycloakServerConfigBuilder;
|
||||
import org.keycloak.testframework.util.ApiUtil;
|
||||
import org.keycloak.tests.admin.authz.fgap.RealmAdminAccessTest.ServerConfig;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
|
@ -43,7 +50,7 @@ import static org.hamcrest.Matchers.is;
|
|||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
@KeycloakIntegrationTest
|
||||
@KeycloakIntegrationTest(config = ServerConfig.class)
|
||||
public class RealmAdminAccessTest extends AbstractPermissionTest {
|
||||
|
||||
@InjectAdminClient(mode = InjectAdminClient.Mode.MANAGED_REALM, client = "myclient", user = "myadmin")
|
||||
|
|
@ -110,9 +117,67 @@ public class RealmAdminAccessTest extends AbstractPermissionTest {
|
|||
fail("Should not have access to other realm");
|
||||
} catch (ForbiddenException ignore) {
|
||||
}
|
||||
|
||||
assertWorkflowAccess(client);
|
||||
} finally {
|
||||
client.realm(myrealm.getRealm()).remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void assertWorkflowAccess(Keycloak serverAdminClient) {
|
||||
// server admin can access workflows
|
||||
serverAdminClient.realm(realm.getName()).workflows().list();
|
||||
|
||||
UserRepresentation myadmin = realm.admin().users().search("myadmin").get(0);
|
||||
ClientRepresentation realmManagement = realm.admin().clients().findByClientId("realm-management").get(0);
|
||||
RoleRepresentation realmAdminRole = realm.admin().clients().get(realmManagement.getId()).roles().get(AdminRoles.REALM_ADMIN).toRepresentation();
|
||||
|
||||
// can access workflows with realm-admin role
|
||||
realm.admin().users().get(myadmin.getId()).roles().clientLevel(realmManagement.getId()).add(List.of(realmAdminRole));
|
||||
realmAdminClient.realm(realm.getName()).workflows().list();
|
||||
|
||||
// cannot access workflows without realm-admin role
|
||||
realm.admin().users().get(myadmin.getId()).roles().clientLevel(realmManagement.getId()).remove(List.of(realmAdminRole));
|
||||
|
||||
try {
|
||||
realmAdminClient.realm(realm.getName()).workflows().list();
|
||||
fail("Should not have access to workflows");
|
||||
} catch (ForbiddenException ignore) {
|
||||
}
|
||||
|
||||
UserRepresentation masterUserRealmAdmin = UserConfigBuilder.create()
|
||||
.username("mymasteradmin")
|
||||
.password("password")
|
||||
.firstName("f")
|
||||
.lastName("l")
|
||||
.email("mymasteradmin@keycloak.org")
|
||||
.build();
|
||||
try (Response response = serverAdminClient.realm("master").users().create(masterUserRealmAdmin)) {
|
||||
masterUserRealmAdmin.setId(ApiUtil.getCreatedId(response));
|
||||
}
|
||||
|
||||
ClientRepresentation myRealmMasterClient = serverAdminClient.realm("master").clients().findByClientId(realm.getName() + "-realm").get(0);
|
||||
RoleRepresentation masterRealmAdminRole = serverAdminClient.realm("master").clients().get(myRealmMasterClient.getId())
|
||||
.roles().get(AdminRoles.MANAGE_REALM).toRepresentation();
|
||||
serverAdminClient.realm("master").users().get(masterUserRealmAdmin.getId())
|
||||
.roles().clientLevel(myRealmMasterClient.getId()).add(List.of(masterRealmAdminRole));
|
||||
try (Keycloak masterRealmAdminClient = adminClientFactory.create().realm("master")
|
||||
.username("mymasteradmin").password("password").clientId(Constants.ADMIN_CLI_CLIENT_ID).build()) {
|
||||
|
||||
// can not access workflows with manage-realm role in master realm
|
||||
try {
|
||||
masterRealmAdminClient.realm(realm.getName()).workflows().list();
|
||||
fail("Should not have access to manage workflows if user is master realm admin with manage-realm role in a realm");
|
||||
} catch (ForbiddenException ignore) {}
|
||||
}
|
||||
}
|
||||
|
||||
public static class ServerConfig implements KeycloakServerConfig {
|
||||
|
||||
@Override
|
||||
public KeycloakServerConfigBuilder configure(KeycloakServerConfigBuilder config) {
|
||||
return config.features(Feature.WORKFLOWS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue