Fine grained admin permissions feature V2

Closes #34563

Signed-off-by: vramik <vramik@redhat.com>
This commit is contained in:
vramik 2024-11-01 09:53:51 +01:00 committed by Alexander Schwartz
parent 33cae33ae4
commit b1ff9511d1
10 changed files with 42 additions and 23 deletions

View file

@ -240,7 +240,7 @@ jobs:
- name: Start Keycloak server
run: |
tar xfvz keycloak-999.0.0-SNAPSHOT.tar.gz
keycloak-999.0.0-SNAPSHOT/bin/kc.sh start-dev --features=admin-fine-grained-authz,transient-users &> ~/server.log &
keycloak-999.0.0-SNAPSHOT/bin/kc.sh start-dev --features=admin-fine-grained-authz:v1,transient-users &> ~/server.log &
env:
KC_BOOTSTRAP_ADMIN_USERNAME: admin
KC_BOOTSTRAP_ADMIN_PASSWORD: admin

View file

@ -53,7 +53,9 @@ public class Profile {
ACCOUNT_V3("Account Console version 3", Type.DEFAULT, 3, Feature.ACCOUNT_API),
ADMIN_FINE_GRAINED_AUTHZ("Fine-Grained Admin Permissions", Type.PREVIEW),
ADMIN_FINE_GRAINED_AUTHZ("Fine-Grained Admin Permissions", Type.PREVIEW, 1),
ADMIN_FINE_GRAINED_AUTHZ_V2("Fine-Grained Admin Permissions version 2", Type.EXPERIMENTAL, 2, Feature.AUTHORIZATION),
ADMIN_API("Admin API", Type.DEFAULT),

View file

@ -29,7 +29,7 @@ public class ProfileTest {
private static final Profile.Feature DEFAULT_FEATURE = Profile.Feature.AUTHORIZATION;
private static final Profile.Feature DISABLED_BY_DEFAULT_FEATURE = Profile.Feature.DOCKER;
private static final Profile.Feature PREVIEW_FEATURE = Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ;
private static final Profile.Feature PREVIEW_FEATURE = Profile.Feature.TOKEN_EXCHANGE;
private static final Profile.Feature EXPERIMENTAL_FEATURE = Profile.Feature.DYNAMIC_SCOPES;
private static Profile.Feature DEPRECATED_FEATURE = Profile.Feature.LOGIN_V1;

View file

@ -89,7 +89,7 @@ public class FeaturesDistTest {
@Test
@EnabledOnOs(value = { OS.LINUX, OS.MAC }, disabledReason = "different shell escaping behaviour on Windows.")
@Launch({StartDev.NAME, "--features=token-exchange,admin-fine-grained-authz"})
@Launch({StartDev.NAME, "--features=token-exchange,admin-fine-grained-authz:v1"})
public void testEnableMultipleFeatures(LaunchResult result) {
CLIResult cliResult = (CLIResult) result;
cliResult.assertStartedDevMode();
@ -100,7 +100,7 @@ public class FeaturesDistTest {
@Test
@EnabledOnOs(value = { OS.WINDOWS }, disabledReason = "different shell escaping behaviour on Windows.")
@Launch({StartDev.NAME, "--features=\"token-exchange,admin-fine-grained-authz\""})
@Launch({StartDev.NAME, "--features=\"token-exchange,admin-fine-grained-authz:v1\""})
public void testWinEnableMultipleFeatures(LaunchResult result) {
CLIResult cliResult = (CLIResult) result;
cliResult.assertStartedDevMode();

View file

@ -20,7 +20,6 @@ import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.extensions.Extension;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import jakarta.ws.rs.core.Response.Status;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import org.jboss.logging.Logger;
import org.jboss.resteasy.reactive.NoCache;
@ -90,6 +89,7 @@ import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
@ -702,6 +702,7 @@ public class ClientResource {
@Tag(name = KeycloakOpenAPI.Admin.Tags.CLIENTS)
@Operation( summary = "Return object stating whether client Authorization permissions have been initialized or not and a reference")
public ManagementPermissionReference getManagementPermissions() {
ProfileHelper.requireFeature(Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ);
auth.roles().requireView(client);
AdminPermissionManagement permissions = AdminPermissions.management(session, realm);
@ -711,7 +712,7 @@ public class ClientResource {
return toMgmtRef(client, permissions);
}
public static ManagementPermissionReference toMgmtRef(ClientModel client, AdminPermissionManagement permissions) {
private ManagementPermissionReference toMgmtRef(ClientModel client, AdminPermissionManagement permissions) {
ManagementPermissionReference ref = new ManagementPermissionReference();
ref.setEnabled(true);
ref.setResource(permissions.clients().resource(client).getId());
@ -734,6 +735,7 @@ public class ClientResource {
@Tag(name = KeycloakOpenAPI.Admin.Tags.CLIENTS)
@Operation( summary = "Return object stating whether client Authorization permissions have been initialized or not and a reference")
public ManagementPermissionReference setManagementPermissionsEnabled(ManagementPermissionReference ref) {
ProfileHelper.requireFeature(Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ);
auth.clients().requireManage(client);
AdminPermissionManagement permissions = AdminPermissions.management(session, realm);
permissions.clients().setPermissionsEnabled(client, ref.isEnabled());

View file

@ -16,13 +16,12 @@
*/
package org.keycloak.services.resources.admin;
import jakarta.ws.rs.DefaultValue;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.extensions.Extension;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import org.jboss.resteasy.reactive.NoCache;
import jakarta.ws.rs.NotFoundException;
import org.keycloak.common.Profile;
import org.keycloak.common.util.ObjectUtil;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
@ -42,10 +41,14 @@ import org.keycloak.services.resources.KeycloakOpenAPI;
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
import org.keycloak.services.resources.admin.permissions.AdminPermissionManagement;
import org.keycloak.services.resources.admin.permissions.AdminPermissions;
import org.keycloak.utils.GroupUtils;
import org.keycloak.utils.ProfileHelper;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.DefaultValue;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
@ -60,7 +63,6 @@ import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Stream;
import org.keycloak.utils.GroupUtils;
import static org.keycloak.utils.StreamsUtil.paginatedStream;
@ -318,6 +320,7 @@ public class GroupResource {
@Tag(name = KeycloakOpenAPI.Admin.Tags.GROUPS)
@Operation( summary = "Return object stating whether client Authorization permissions have been initialized or not and a reference")
public ManagementPermissionReference getManagementPermissions() {
ProfileHelper.requireFeature(Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ);
auth.groups().requireView(group);
AdminPermissionManagement permissions = AdminPermissions.management(session, realm);
@ -327,7 +330,7 @@ public class GroupResource {
return toMgmtRef(group, permissions);
}
public static ManagementPermissionReference toMgmtRef(GroupModel group, AdminPermissionManagement permissions) {
private ManagementPermissionReference toMgmtRef(GroupModel group, AdminPermissionManagement permissions) {
ManagementPermissionReference ref = new ManagementPermissionReference();
ref.setEnabled(true);
ref.setResource(permissions.groups().resource(group).getId());
@ -350,6 +353,7 @@ public class GroupResource {
@Tag(name = KeycloakOpenAPI.Admin.Tags.GROUPS)
@Operation( summary = "Return object stating whether client Authorization permissions have been initialized or not and a reference")
public ManagementPermissionReference setManagementPermissionsEnabled(ManagementPermissionReference ref) {
ProfileHelper.requireFeature(Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ);
auth.groups().requireManage(group);
AdminPermissionManagement permissions = AdminPermissions.management(session, realm);

View file

@ -24,11 +24,11 @@ import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import org.jboss.logging.Logger;
import org.jboss.resteasy.reactive.NoCache;
import jakarta.ws.rs.NotFoundException;
import org.keycloak.broker.provider.IdentityProvider;
import org.keycloak.broker.provider.IdentityProviderFactory;
import org.keycloak.broker.provider.IdentityProviderMapper;
import org.keycloak.broker.social.SocialIdentityProvider;
import org.keycloak.common.Profile;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
import org.keycloak.models.FederatedIdentityModel;
@ -53,10 +53,12 @@ import org.keycloak.services.resources.KeycloakOpenAPI;
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
import org.keycloak.services.resources.admin.permissions.AdminPermissionManagement;
import org.keycloak.services.resources.admin.permissions.AdminPermissions;
import org.keycloak.utils.ProfileHelper;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
@ -69,7 +71,6 @@ import jakarta.ws.rs.core.Response;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -440,6 +441,7 @@ public class IdentityProviderResource {
@Tag(name = KeycloakOpenAPI.Admin.Tags.IDENTITY_PROVIDERS)
@Operation( summary = "Return object stating whether client Authorization permissions have been initialized or not and a reference")
public ManagementPermissionReference getManagementPermissions() {
ProfileHelper.requireFeature(Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ);
this.auth.realm().requireViewIdentityProviders();
AdminPermissionManagement permissions = AdminPermissions.management(session, realm);
@ -449,7 +451,7 @@ public class IdentityProviderResource {
return toMgmtRef(identityProviderModel, permissions);
}
public static ManagementPermissionReference toMgmtRef(IdentityProviderModel model, AdminPermissionManagement permissions) {
private ManagementPermissionReference toMgmtRef(IdentityProviderModel model, AdminPermissionManagement permissions) {
ManagementPermissionReference ref = new ManagementPermissionReference();
ref.setEnabled(true);
ref.setResource(permissions.idps().resource(model).getId());
@ -472,6 +474,7 @@ public class IdentityProviderResource {
@Tag(name = KeycloakOpenAPI.Admin.Tags.IDENTITY_PROVIDERS)
@Operation( summary = "Return object stating whether client Authorization permissions have been initialized or not and a reference")
public ManagementPermissionReference setManagementPermissionsEnabled(ManagementPermissionReference ref) {
ProfileHelper.requireFeature(Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ);
this.auth.realm().requireManageIdentityProviders();
AdminPermissionManagement permissions = AdminPermissions.management(session, realm);
permissions.idps().setPermissionsEnabled(identityProviderModel, ref.isEnabled());

View file

@ -30,12 +30,9 @@ import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import jakarta.ws.rs.DefaultValue;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.extensions.Extension;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DefaultValue;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.FormParam;
import jakarta.ws.rs.GET;
@ -54,6 +51,9 @@ import jakarta.ws.rs.core.StreamingOutput;
import com.fasterxml.jackson.core.type.TypeReference;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.extensions.Extension;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import org.jboss.logging.Logger;
import org.jboss.resteasy.reactive.NoCache;
@ -515,6 +515,7 @@ public class RealmAdminResource {
@Tag(name = KeycloakOpenAPI.Admin.Tags.REALMS_ADMIN)
@Operation()
public ManagementPermissionReference getUserMgmtPermissions() {
ProfileHelper.requireFeature(Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ);
auth.realm().requireViewRealm();
AdminPermissionManagement permissions = AdminPermissions.management(session, realm);
@ -534,6 +535,7 @@ public class RealmAdminResource {
@Tag(name = KeycloakOpenAPI.Admin.Tags.REALMS_ADMIN)
@Operation()
public ManagementPermissionReference setUsersManagementPermissionsEnabled(ManagementPermissionReference ref) {
ProfileHelper.requireFeature(Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ);
auth.realm().requireManageRealm();
AdminPermissionManagement permissions = AdminPermissions.management(session, realm);
@ -545,8 +547,7 @@ public class RealmAdminResource {
}
}
public static ManagementPermissionReference toUsersMgmtRef(AdminPermissionManagement permissions) {
private ManagementPermissionReference toUsersMgmtRef(AdminPermissionManagement permissions) {
ManagementPermissionReference ref = new ManagementPermissionReference();
ref.setEnabled(true);
ref.setResource(permissions.users().resource().getId());

View file

@ -23,7 +23,7 @@ import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import org.jboss.logging.Logger;
import org.jboss.resteasy.reactive.NoCache;
import jakarta.ws.rs.NotFoundException;
import org.keycloak.common.Profile;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
import org.keycloak.models.ClientModel;
@ -34,15 +34,16 @@ import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.representations.idm.ManagementPermissionReference;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.services.ErrorResponse;
import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.resources.KeycloakOpenAPI;
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
import org.keycloak.services.resources.admin.permissions.AdminPermissionManagement;
import org.keycloak.services.resources.admin.permissions.AdminPermissions;
import org.keycloak.utils.ProfileHelper;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
@ -285,6 +286,7 @@ public class RoleByIdResource extends RoleResource {
@Tag(name = KeycloakOpenAPI.Admin.Tags.ROLES_BY_ID)
@Operation( summary = "Return object stating whether role Authorization permissions have been initialized or not and a reference")
public ManagementPermissionReference getManagementPermissions(final @PathParam("role-id") String id) {
ProfileHelper.requireFeature(Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ);
RoleModel role = getRoleModel(id);
auth.roles().requireView(role);
@ -318,6 +320,7 @@ public class RoleByIdResource extends RoleResource {
@Tag(name = KeycloakOpenAPI.Admin.Tags.ROLES_BY_ID)
@Operation( summary = "Return object stating whether role Authorization permissions have been initialized or not and a reference")
public ManagementPermissionReference setManagementPermissionsEnabled(final @PathParam("role-id") String id, ManagementPermissionReference ref) {
ProfileHelper.requireFeature(Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ);
RoleModel role = getRoleModel(id);
auth.roles().requireManage(role);

View file

@ -23,7 +23,7 @@ import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import org.jboss.resteasy.reactive.NoCache;
import jakarta.ws.rs.NotFoundException;
import org.keycloak.common.Profile;
import org.keycloak.common.util.Encode;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
@ -45,12 +45,14 @@ import org.keycloak.services.resources.KeycloakOpenAPI;
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
import org.keycloak.services.resources.admin.permissions.AdminPermissionManagement;
import org.keycloak.services.resources.admin.permissions.AdminPermissions;
import org.keycloak.utils.ProfileHelper;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.DefaultValue;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
@ -428,6 +430,7 @@ public class RoleContainerResource extends RoleResource {
@Tag(name = KeycloakOpenAPI.Admin.Tags.ROLES)
@Operation( summary = "Return object stating whether role Authorization permissions have been initialized or not and a reference")
public ManagementPermissionReference getManagementPermissions(final @PathParam("role-name") String roleName) {
ProfileHelper.requireFeature(Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ);
auth.roles().requireView(roleContainer);
RoleModel role = roleContainer.getRole(roleName);
if (role == null) {
@ -456,6 +459,7 @@ public class RoleContainerResource extends RoleResource {
@Tag(name = KeycloakOpenAPI.Admin.Tags.ROLES)
@Operation( summary = "Return object stating whether role Authorization permissions have been initialized or not and a reference")
public ManagementPermissionReference setManagementPermissionsEnabled(final @PathParam("role-name") String roleName, ManagementPermissionReference ref) {
ProfileHelper.requireFeature(Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ);
auth.roles().requireManage(roleContainer);
RoleModel role = roleContainer.getRole(roleName);
if (role == null) {