Skip to content

Add Client resource type and its scopes to authorization schema and e… - #1

Open
linxia0415 wants to merge 1 commit into
mainfrom
pr-36880
Open

Add Client resource type and its scopes to authorization schema and e…#1
linxia0415 wants to merge 1 commit into
mainfrom
pr-36880

Conversation

@linxia0415

@linxia0415 linxia0415 commented Jun 3, 2026

Copy link
Copy Markdown

…valuation implementation for ClientsPermissionsV2

Closes #35564

Summary by CodeRabbit

Release Notes

  • New Features

    • Added client permission controls enabling fine-grained authorization over client management operations including configuration, viewing, and role mapping.
    • Implemented feature flag support for controlled rollout of admin fine-grained authorization features.
  • Documentation

    • Expanded permission evaluator documentation with comprehensive JavaDoc coverage.
  • Tests

    • Refactored test infrastructure with enhanced permission and policy management utilities.

…valuation implementation for ClientsPermissionsV2

Closes #35564

Signed-off-by: Martin Kanis <mkanis@redhat.com>
@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This pull request extends Keycloak's fine-grained authorization system to support client resource management. The changes add a new CLIENTS resource type to the authorization schema, implement V2-style client permission evaluation, integrate client permissions into the service layer, and provide comprehensive test coverage for client permission enforcement across various scopes and operations.

Changes

Client Authorization Implementation

Layer / File(s) Summary
Authorization schema extension for client resources
server-spi-private/src/main/java/org/keycloak/authorization/AdminPermissionsSchema.java
Introduces a new CLIENTS resource type alongside the existing USERS type, with client-scoped permission scopes (configure, view, manage, role-mapping). Adds a resolveClient helper that translates client identifiers to ClientModel instances by lookup, and routes client resource resolution through the new helper.
Permission event handling with feature flag gating
services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissions.java
Wraps the role/client/group permission cleanup logic on entity deletion inside the ADMIN_FINE_GRAINED_AUTHZ feature flag, ensuring permissions are only disabled for removed entities when fine-grained authorization is enabled.
Client permission evaluator interface documentation
services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionEvaluator.java
Adds comprehensive JavaDoc documentation to the ClientPermissionEvaluator interface describing permission checks for client-scoped operations (configure, view, manage, role-mapping, client-scope management), including when ForbiddenException is expected and required admin roles.
V2 client permission evaluation implementation
services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionsV2.java
Implements ClientPermissionsV2 with core permission evaluation (canConfigure, canManage, canView, role-mapping methods) that query evaluated permissions for client resources. Includes hasPermission(ClientModel, String) for scope evaluation against specific or all-clients resources, internal evaluation-context building with kc.client.id injection, and stubs for unsupported V2 operations (exchange, policy/resource management).
Client permissions service registration
services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissionsV2.java
Adds a cached clientPermissions field and overrides clients() to lazily instantiate and cache ClientPermissionsV2, making client permission evaluation accessible through the MgmtPermissionsV2 service layer.

Test Framework and Authorization Tests

Layer / File(s) Summary
Test framework helper refactoring for client scope
tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/AbstractPermissionTest.java
Refactors permission-resource helpers to be static and accept a ManagedClient parameter for client-scoped access. Adds new policy-creation utilities (createUserPolicy, createClientPolicy) with automatic cleanup registration, and provides ScopePermissionRepresentation builders that construct permissions from abstract policies.
Client permission enforcement tests
tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/PermissionClientTest.java
Introduces test infrastructure with injected managed realm and client instances, adds @AfterEach cleanup for scope permissions, and implements comprehensive integration tests validating client permission enforcement: managing/configuring/viewing single and all clients, role-mapping permission restrictions, and protocol mapper access control.
User and resource permission test updates
tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/UserResourceTypeEvaluationTest.java, tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/UserResourceTypePermissionTest.java, tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/PermissionRESTTest.java
Updates all test classes to use the refactored client-aware permission helpers. Removes local permission-building methods, switches scope-permission resource access to the client-scoped API, and standardizes permission creation calls to accept the ManagedClient parameter.

🎯 3 (Moderate) | ⏱️ ~25 minutes

🐰 In a realm where clients now reign free,
Fine-grained permissions, guarded with care,
Scopes for managing, configuring with flair,
Test helpers dance in refactored delight,
Authorization flows shining so bright!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.94% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The description is minimal but includes the issue reference (#35564) and contributor guidelines header; however, it lacks detailed explanation of changes, motivation, and testing impact. Expand the description to explain what was changed, why these changes were made, and how they address issue #35564 for better context during review.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding Client resource type and scopes to authorization schema with evaluation implementation.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pr-36880

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionsV2.java (1)

262-272: ⚡ Quick win

Unused method - consider removing or documenting future intent.

The getEvaluationContext method is not called anywhere in the codebase. Since canExchangeTo throws UnsupportedOperationException, this appears to be dead code. Consider either removing it or adding a comment explaining its intended future use.

♻️ Suggested cleanup

If this method is not needed for future functionality, remove it:

-    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;
-            }
-        };
-    }
-

Alternatively, if planned for future use, add a TODO comment explaining when it will be needed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionsV2.java`
around lines 262 - 272, The getEvaluationContext method appears to be dead code
(not referenced) while canExchangeTo currently throws
UnsupportedOperationException; either remove getEvaluationContext or document
its intended future use. Locate the getEvaluationContext method and either
delete it to remove unused code or add a concise TODO/Javadoc above it
referencing ClientModelIdentity and DefaultEvaluationContext and explaining the
expected future scenario (e.g., when canExchangeTo will be implemented) that
will require this helper; ensure the comment names getEvaluationContext and the
related classes so its purpose and future necessity are clear.
tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/PermissionClientTest.java (1)

319-322: 💤 Low value

Passing null to clients().create(null) may assert FORBIDDEN for the wrong reason.

A null representation can be rejected during request/body handling rather than by the authorization check, masking the intent of this negative test. Consider passing a minimal valid ClientRepresentation so the 403 unambiguously comes from the missing manage permission.

♻️ Suggested change
-        // can't create a new client
-        try (Response response = realmAdminClient.realm(realm.getName()).clients().create(null)) {
+        // can't create a new client
+        ClientRepresentation forbiddenClient = new ClientRepresentation();
+        forbiddenClient.setClientId("forbiddenClient");
+        try (Response response = realmAdminClient.realm(realm.getName()).clients().create(forbiddenClient)) {
             Assertions.assertEquals(Status.FORBIDDEN.getStatusCode(), response.getStatus());
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/PermissionClientTest.java`
around lines 319 - 322, The test passes null to
realmAdminClient.realm(realm.getName()).clients().create(null), which can fail
during request validation instead of an authorization check; replace the null
with a minimal valid ClientRepresentation (e.g., set at least the clientId and
enabled fields) so the call reaches the authorization layer and the
Assertions.assertEquals(Status.FORBIDDEN.getStatusCode(), response.getStatus())
unambiguously verifies missing manage permission in PermissionClientTest.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissions.java`:
- Around line 77-96: The current event-listener block in AdminPermissions checks
only Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, skipping cleanup when only
ADMIN_FINE_GRAINED_AUTHZ_V2 is enabled; update the guard to run when either
feature flag is enabled (e.g.,
Profile.isFeatureEnabled(Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ) ||
Profile.isFeatureEnabled(Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ_V2)) so the
existing role/client/group removal branches still call
management(...).roles()/clients()/groups().setPermissionsEnabled(...) and thus
invoke the MgmtPermissionsV2 cleanup path when V2 is active.

---

Nitpick comments:
In
`@services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionsV2.java`:
- Around line 262-272: The getEvaluationContext method appears to be dead code
(not referenced) while canExchangeTo currently throws
UnsupportedOperationException; either remove getEvaluationContext or document
its intended future use. Locate the getEvaluationContext method and either
delete it to remove unused code or add a concise TODO/Javadoc above it
referencing ClientModelIdentity and DefaultEvaluationContext and explaining the
expected future scenario (e.g., when canExchangeTo will be implemented) that
will require this helper; ensure the comment names getEvaluationContext and the
related classes so its purpose and future necessity are clear.

In
`@tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/PermissionClientTest.java`:
- Around line 319-322: The test passes null to
realmAdminClient.realm(realm.getName()).clients().create(null), which can fail
during request validation instead of an authorization check; replace the null
with a minimal valid ClientRepresentation (e.g., set at least the clientId and
enabled fields) so the call reaches the authorization layer and the
Assertions.assertEquals(Status.FORBIDDEN.getStatusCode(), response.getStatus())
unambiguously verifies missing manage permission in PermissionClientTest.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: cc1d7a4f-b734-4555-b4ba-d24fdb9e0f89

📥 Commits

Reviewing files that changed from the base of the PR and between 43a20a0 and 1950a51.

📒 Files selected for processing (10)
  • server-spi-private/src/main/java/org/keycloak/authorization/AdminPermissionsSchema.java
  • services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissions.java
  • services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionEvaluator.java
  • services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionsV2.java
  • services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissionsV2.java
  • tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/AbstractPermissionTest.java
  • tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/PermissionClientTest.java
  • tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/PermissionRESTTest.java
  • tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/UserResourceTypeEvaluationTest.java
  • tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/UserResourceTypePermissionTest.java

Comment on lines +77 to 96
if (Profile.isFeatureEnabled(Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ)) {
if (event instanceof RoleContainerModel.RoleRemovedEvent) {
RoleContainerModel.RoleRemovedEvent cast = (RoleContainerModel.RoleRemovedEvent) event;
RoleModel role = cast.getRole();
RealmModel realm;
if (role.getContainer() instanceof ClientModel) {
realm = ((ClientModel) role.getContainer()).getRealm();

} else {
realm = (RealmModel)role.getContainer();
} else {
realm = (RealmModel) role.getContainer();
}
management(cast.getKeycloakSession(), realm).roles().setPermissionsEnabled(role, false);
} else if (event instanceof ClientModel.ClientRemovedEvent) {
ClientModel.ClientRemovedEvent cast = (ClientModel.ClientRemovedEvent) event;
management(cast.getKeycloakSession(), cast.getClient().getRealm()).clients().setPermissionsEnabled(cast.getClient(), false);
} else if (event instanceof GroupModel.GroupRemovedEvent) {
GroupModel.GroupRemovedEvent cast = (GroupModel.GroupRemovedEvent) event;
management(cast.getKeycloakSession(), cast.getRealm()).groups().setPermissionsEnabled(cast.getGroup(), false);
}
management(cast.getKeycloakSession(), realm).roles().setPermissionsEnabled(role, false);
} else if (event instanceof ClientModel.ClientRemovedEvent) {
ClientModel.ClientRemovedEvent cast = (ClientModel.ClientRemovedEvent)event;
management(cast.getKeycloakSession(), cast.getClient().getRealm()).clients().setPermissionsEnabled(cast.getClient(), false);
} else if (event instanceof GroupModel.GroupRemovedEvent) {
GroupModel.GroupRemovedEvent cast = (GroupModel.GroupRemovedEvent)event;
management(cast.getKeycloakSession(), cast.getRealm()).groups().setPermissionsEnabled(cast.getGroup(), false);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Feature flag check excludes V2, preventing cleanup when only V2 is enabled.

The listener only checks ADMIN_FINE_GRAINED_AUTHZ (V1), but the management() method on lines 66-71 returns MgmtPermissionsV2 when ADMIN_FINE_GRAINED_AUTHZ_V2 is enabled. If V2 is enabled but V1 is disabled, this cleanup logic will not execute, leaving orphaned permission data (policies, resources) in the authorization store when roles, clients, or groups are removed.

Since V1 and V2 appear to be independently enabled features (per context snippet 1, V2 depends on AUTHORIZATION, not V1), the check should gate on either flag.

🔧 Proposed fix to check both feature flags
-            if (Profile.isFeatureEnabled(Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ)) {
+            if (Profile.isFeatureEnabled(Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ) 
+                    || Profile.isFeatureEnabled(Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ_V2)) {
                 if (event instanceof RoleContainerModel.RoleRemovedEvent) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissions.java`
around lines 77 - 96, The current event-listener block in AdminPermissions
checks only Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, skipping cleanup when only
ADMIN_FINE_GRAINED_AUTHZ_V2 is enabled; update the guard to run when either
feature flag is enabled (e.g.,
Profile.isFeatureEnabled(Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ) ||
Profile.isFeatureEnabled(Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ_V2)) so the
existing role/client/group removal branches still call
management(...).roles()/clients()/groups().setPermissionsEnabled(...) and thus
invoke the MgmtPermissionsV2 cleanup path when V2 is active.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants