Skip to content

Add Groups resource type and scopes to authorization schema#6

Open
ShashankFC wants to merge 1 commit into
feature-groups-authz-baselinefrom
feature-groups-authz-implementation
Open

Add Groups resource type and scopes to authorization schema#6
ShashankFC wants to merge 1 commit into
feature-groups-authz-baselinefrom
feature-groups-authz-implementation

Conversation

@ShashankFC

@ShashankFC ShashankFC commented Jan 5, 2026

Copy link
Copy Markdown
Collaborator

Test 6

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced group-based access control with granular permissions for viewing members, managing membership, and managing groups.
  • Tests

    • Added comprehensive test coverage for group resource-type authorization and permission evaluation scenarios.

✏️ Tip: You can customize this high-level summary in your review settings.


Replicated from ai-code-review-evaluation/keycloak-coderabbit#6

…ation implementation

Closes #35562

Signed-off-by: vramik <vramik@redhat.com>
@ShashankFC

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jan 12, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jan 12, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This pull request refactors Keycloak's admin authorization system to introduce fine-grained group-based permissions. It adds a GROUPS resource type with dedicated scopes (MANAGE, VIEW, MANAGE_MEMBERS, MANAGE_MEMBERSHIP, VIEW_MEMBERS), replaces group model-based permission lookups with group ID-based approaches, introduces a V2 permission evaluation system that coexists with V1, and expands permission evaluator interfaces with granular permission-checking methods across users and groups.

Changes

Cohort / File(s) Summary
Permission Schema & Resource Types
server-spi-private/src/main/java/org/keycloak/authorization/AdminPermissionsSchema.java
Added GROUPS resource type constant and new group-related scopes (MANAGE_MEMBERSHIP, MANAGE_MEMBERS, VIEW_MEMBERS); converted resource-type branching to switch-based mapping; added group resolution logic alongside user/client resolution
Group Permission Evaluators
services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionEvaluator.java,
services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissions.java
Expanded GroupPermissionEvaluator API with new permission checks (canManageMembers, canViewMembers, canManageMembership, etc.); replaced getGroupsWithViewPermission with getGroupIdsWithViewPermission; increased field visibility in GroupPermissions from private to protected
User Permission Evaluators
services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissionEvaluator.java,
services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java
Added canMapRoles, canManageGroupMembership, canImpersonate variants; removed canManageDefault/canViewDefault helpers; updated to direct AdminRoles-based checks; refactored canViewByGroup/canManageByGroup to use method references
V2 Permission System
services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionsV2.java,
services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissionsV2.java,
services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissionsV2.java
Introduced new GroupPermissionsV2 class with V2-specific permission evaluation logic; added lazy-loaded groupPermissions field to MgmtPermissionsV2; simplified UserPermissionsV2 permission checks to use AdminRoles checks first
Group & User Access Resources
rest/admin-ui-ext/src/main/java/org/keycloak/admin/ui/rest/BruteForceUsersResource.java,
services/src/main/java/org/keycloak/services/resources/admin/GroupResource.java,
services/src/main/java/org/keycloak/services/resources/admin/GroupsResource.java,
services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
Replaced group model-based permission lookups with group IDs; updated filters to use method references for canView/canManage checks; replaced ModelToRepresentation.searchGroupModelsByAttributes with direct session.groups().searchGroupsByAttributes
Authorization Infrastructure
services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissions.java,
services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java
Guarded listener registration with ADMIN_FINE_GRAINED_AUTHZ feature flag; updated RolePermissions to use direct hasOneAdminRole checks
Utilities & Removed Methods
server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java,
test-framework/core/src/main/java/org/keycloak/testframework/util/ApiUtil.java
Removed searchGroupModelsByAttributes public method; refactored ApiUtil.handleCreatedResponse to use try-with-resources for Response closure
Tests & Configuration
tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/GroupResourceTypeEvaluationTest.java,
tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/RealmAdminPermissionsConfig.java,
tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/UserResourceTypeEvaluationTest.java
Added comprehensive GroupResourceTypeEvaluationTest with group-scoped permission scenarios; added QUERY_GROUPS role to admin config; removed unused import from UserResourceTypeEvaluationTest

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes


🐰 Hop along the permission tree so fine,
Groups now shimmer with V2 design,
From IDs to scopes, the logic takes flight,
Fine-grained access makes authorization right! ✨🔐

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The pull request description is essentially empty, containing only 'Test 6' and a reference note, with no substantive information about changes, motivation, or testing. Add a comprehensive description explaining the Groups resource type implementation, its purpose, affected components, testing performed, and any migration considerations for users or integrators.
Docstring Coverage ⚠️ Warning Docstring coverage is 35.90% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add Groups resource type and scopes to authorization schema' accurately summarizes the main change—introducing a new Groups resource type with associated scopes in the authorization schema.

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

✨ Finishing touches
  • 📝 Generate docstrings

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: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissions.java (1)

73-98: Listener guard is incomplete—should check both feature flags.

The listener registration guard only checks ADMIN_FINE_GRAINED_AUTHZ, but this misses the V2 case. The MgmtPermissions class (line 85 of MgmtPermissions.java) uses an OR condition to initialize when either flag is enabled:

if (Profile.isFeatureEnabled(Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ) || Profile.isFeatureEnabled(Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ_V2))

The test configuration enables only ADMIN_FINE_GRAINED_AUTHZ_V2 without the base feature, meaning the listener will never register in that scenario. The cleanup logic for removed roles, clients, and groups would be skipped entirely when V2 is enabled alone.

Update the guard to:

if (Profile.isFeatureEnabled(Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ) || Profile.isFeatureEnabled(Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ_V2))
🤖 Fix all issues with AI agents
In
@services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionEvaluator.java:
- Around line 99-148: The Javadoc for requireManageMembers incorrectly
references canManageMembership; update the Javadoc on the
requireManageMembers(GroupModel) method to reference
canManageMembers(GroupModel) instead (use {@link #canManageMembers(GroupModel)})
so the documentation matches the method naming convention and behavior.

In
@services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionsV2.java:
- Around line 64-71: The canManage() method wrongly calls hasPermission(null,
AdminPermissionsSchema.VIEW, AdminPermissionsSchema.MANAGE) — remove the VIEW
scope so it only checks for MANAGE; update the hasPermission invocation in
canManage() to pass only AdminPermissionsSchema.MANAGE (matching
canManage(GroupModel)) while keeping the existing
root.hasOneAdminRole(AdminRoles.MANAGE_USERS) check.
- Around line 119-128: The code incorrectly uses groupResource.getId() (the
internal Resource DB id) when calling hasPermission and when adding to the
result set; replace both uses with groupResource.getName() so the lookup and
returned identifiers use the resource name (which per
AdminPermissionsSchema.resolveGroup() is set to the actual group ID). Update the
lambda in resourceStore.findByType to call
hasPermission(groupResource.getName(), AdminPermissionsSchema.VIEW_MEMBERS,
AdminPermissionsSchema.MANAGE_MEMBERS) and to
granted.add(groupResource.getName()).

In
@tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/GroupResourceTypeEvaluationTest.java:
- Line 68: In GroupResourceTypeEvaluationTest remove the stray extra semicolon
after the GroupRepresentation constructor in the declaration of the field
topGroup (private final GroupRepresentation topGroup = new
GroupRepresentation();;) so it becomes a single-semicolon termination; update
only the declaration to use a single semicolon to fix the syntax/style issue.
🧹 Nitpick comments (3)
test-framework/core/src/main/java/org/keycloak/testframework/util/ApiUtil.java (1)

9-13: Good use of try-with-resources for automatic resource management.

This refactor ensures the Response is properly closed even if an assertion fails or an exception occurs during path extraction, which is cleaner than explicit close() calls.

Optional improvement: Consider adding a null-check or assertion for response.getLocation() to provide a clearer error message if the Location header is unexpectedly missing:

💡 Optional null-safety enhancement
 try (response) {
     Assertions.assertEquals(201, response.getStatus());
-    String path = response.getLocation().getPath();
+    Assertions.assertNotNull(response.getLocation(), "Expected Location header in 201 response");
+    String path = response.getLocation().getPath();
     return path.substring(path.lastIndexOf('/') + 1);
 }
server-spi-private/src/main/java/org/keycloak/authorization/AdminPermissionsSchema.java (1)

181-186: Consider adding fallback resolution for groups by path.

resolveUser and resolveClient attempt secondary lookups (by username/clientId) when the initial ID lookup fails. resolveGroup only resolves by ID. If consistency is desired, consider adding a fallback lookup by group path, though this may be intentional if groups should only be referenced by ID in the permission context.

services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissions.java (1)

298-320: Clarify the semantic of returning an empty set when canView() is true.

When root.users().canView() returns true, this method returns an empty set. Callers must understand that an empty set means "view all" rather than "view none." This inverted semantic could lead to bugs if callers interpret an empty set as no permissions.

Consider adding a clarifying comment or using an Optional / dedicated result type to distinguish between "all groups" and "no groups."

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7a8d181 and 679f446.

📒 Files selected for processing (19)
  • rest/admin-ui-ext/src/main/java/org/keycloak/admin/ui/rest/BruteForceUsersResource.java
  • server-spi-private/src/main/java/org/keycloak/authorization/AdminPermissionsSchema.java
  • server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
  • services/src/main/java/org/keycloak/services/resources/admin/GroupResource.java
  • services/src/main/java/org/keycloak/services/resources/admin/GroupsResource.java
  • services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
  • services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissions.java
  • services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionEvaluator.java
  • services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissions.java
  • services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionsV2.java
  • services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissionsV2.java
  • services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java
  • services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissionEvaluator.java
  • services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java
  • services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissionsV2.java
  • test-framework/core/src/main/java/org/keycloak/testframework/util/ApiUtil.java
  • tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/GroupResourceTypeEvaluationTest.java
  • tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/RealmAdminPermissionsConfig.java
  • tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/UserResourceTypeEvaluationTest.java
💤 Files with no reviewable changes (2)
  • tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/UserResourceTypeEvaluationTest.java
  • server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
🧰 Additional context used
🧬 Code graph analysis (2)
services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionsV2.java (1)
server-spi-private/src/main/java/org/keycloak/authorization/AdminPermissionsSchema.java (1)
  • AdminPermissionsSchema (53-351)
services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionEvaluator.java (1)
server-spi-private/src/main/java/org/keycloak/authorization/AdminPermissionsSchema.java (1)
  • AdminPermissionsSchema (53-351)
🔇 Additional comments (38)
tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/RealmAdminPermissionsConfig.java (1)

34-36: LGTM!

The addition of AdminRoles.QUERY_GROUPS alongside QUERY_USERS is a necessary change to support testing the new GROUPS resource type and scopes introduced in this PR. The test admin user now has the appropriate permissions to query both users and groups.

services/src/main/java/org/keycloak/services/resources/admin/GroupResource.java (2)

177-181: Clean simplification of per-item permission filtering.

The method reference auth.groups()::canView is a cleaner approach that properly delegates the permission check to the GroupPermissionEvaluator. This aligns well with the broader PR goal of fine-grained per-group permissions.


204-234: LGTM - Definite assignment is guaranteed.

Removing the explicit null initialization is safe here. The control flow ensures child is assigned in both branches before use at line 233:

  • If branch: assigned at line 208 (or throws NotFoundException if null)
  • Else branch: assigned at line 217

This is a minor improvement as the compiler's definite assignment analysis will catch any future refactoring that inadvertently breaks this invariant.

services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java (1)

301-312: The change is aligned with codebase design for global admin roles.

The short-circuit for MANAGE_USERS role holders bypassing isAdminSameRealm() is intentional and consistent with how other global admin roles are handled throughout the codebase (see UserPermissions.java lines 234, 298; GroupPermissionsV2.java; ClientPermissionsV2.java). Global admin roles like MANAGE_USERS are designed to operate across realms without fine-grained authorization service checks. The checkAdminRoles(role) call still enforces privilege escalation prevention by validating that admins cannot map roles with higher permissions than they currently hold, maintaining the security boundary.

services/src/main/java/org/keycloak/services/resources/admin/GroupsResource.java (2)

102-102: LGTM!

The direct call to session.groups().searchGroupsByAttributes() aligns with the broader refactor to use session-based search rather than delegating through ModelToRepresentation.


112-114: LGTM!

The per-item visibility filtering via groupsEvaluator::canView before mapping to representation is correct and aligns with the fine-grained permission model introduced in this PR.

server-spi-private/src/main/java/org/keycloak/authorization/AdminPermissionsSchema.java (3)

54-80: LGTM!

The new GROUPS_RESOURCE_TYPE and associated scopes (MANAGE_MEMBERSHIP, MANAGE_MEMBERS, VIEW_MEMBERS) are well-defined and consistent with the existing pattern for CLIENTS and USERS resource types.


85-89: LGTM!

The constructor registration of all three resource types (CLIENTS, GROUPS, USERS) provides a complete schema for admin permissions.


105-112: LGTM!

The switch expression cleanly handles resource type resolution with appropriate error handling for unknown types.

services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissions.java (4)

56-58: LGTM!

Changing field visibility to protected enables the V2 subclass (GroupPermissionsV2) to access these fields, following a proper extension pattern.


148-150: LGTM!

The updated canList logic correctly prioritizes the QUERY_GROUPS admin role before falling back to canView(), which aligns with the expected permission hierarchy.


274-288: LGTM!

Simplifying canManage() and canView() to delegate directly to hasOneAdminRole with the appropriate roles (MANAGE_USERS and MANAGE_USERS/VIEW_USERS respectively) is a cleaner implementation that aligns with the admin role-based permission model.


323-341: LGTM!

The updated requireViewMembers and canViewMembers methods correctly check both VIEW_MEMBERS_SCOPE and MANAGE_MEMBERS_SCOPE since manage permission implies view permission.

services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissionsV2.java (2)

27-27: LGTM!

The new groupPermissions field follows the same pattern as userPermissions and clientPermissions.


57-62: LGTM!

The lazy initialization pattern for GroupPermissionsV2 is consistent with the existing patterns for users() and clients() methods. The unsynchronized lazy initialization is appropriate for request-scoped instances.

services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java (4)

398-398: LGTM!

The switch to getGroupIdsWithViewPermission() aligns with the ID-based permission model introduced in this PR.


425-425: LGTM!

Consistent use of getGroupIdsWithViewPermission() for the parameterized user count query.


430-430: LGTM!

Consistent use of getGroupIdsWithViewPermission() for the default user count query.


448-453: Semantics of UserModel.GROUPS session attribute are consistent across the codebase.

The empty set semantics you identified are correctly implemented:

  • null → user has global view permission (when getGroupIdsWithViewPermission() returns an empty set, the attribute is not set)
  • non-empty set → user can only view members of these specific groups

All consumers interpret this correctly:

  • UsersResource and BruteForceUsersResource pass != null to grantIfNoPermission() to determine permission scope
  • JpaUserProvider uses the null check to conditionally apply group-based filtering via groupsWithPermissionsSubquery()

No inconsistencies found.

rest/admin-ui-ext/src/main/java/org/keycloak/admin/ui/rest/BruteForceUsersResource.java (1)

147-152: LGTM - Clean migration to group ID-based permission checks.

The refactor from group models to group IDs aligns with the broader V2 permission system changes. The simplified stream handling directly passing to toRepresentation is cleaner.

services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissionsV2.java (3)

47-53: LGTM - V2 permission check logic is correct.

The cascading permission evaluation with admin role check first, then permission-based check, and finally group-based fallback follows a sensible and secure pattern.


56-62: LGTM - canManage implementation is consistent with V2 semantics.


77-92: LGTM - canMapRoles and canManageGroupMembership properly include both MANAGE and specific scopes.

The OR logic in hasPermission correctly allows either the broad MANAGE scope or the specific scope (MAP_ROLES / MANAGE_GROUP_MEMBERSHIP) to grant access, aligning with the documented behavior in UserPermissionEvaluator.

services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionsV2.java (2)

46-62: LGTM - View permission checks are correctly implemented.

The canView() and canView(GroupModel) methods properly check admin roles first, then fall back to permission-based evaluation with both VIEW and MANAGE scopes.


82-103: LGTM - Member-related permission checks are correctly implemented.

The canViewMembers, canManageMembers, and canManageMembership methods properly delegate to user permissions or check group-specific scopes.

services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java (4)

233-243: LGTM - Simplified canManage() with direct admin role check.

The refactor to check AdminRoles.MANAGE_USERS first before falling back to realm-based permission evaluation is cleaner and follows the V2 pattern.


271-274: LGTM - Simplified canQuery() implementation.

The simplified expression root.hasOneAdminRole(AdminRoles.QUERY_USERS) || canView() is more readable.


296-307: LGTM - canView() follows consistent admin role prioritization.


582-590: LGTM - Method references improve readability.

Using root.groups()::canManageMembers and root.groups()::canViewMembers is cleaner than equivalent lambda expressions.

services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissionEvaluator.java (2)

19-25: LGTM - New imports support the enhanced permission documentation.

The imports for AdminPermissionsSchema, AdminRoles, and ImpersonationConstants are correctly added to support the detailed Javadoc references.


32-165: LGTM - Comprehensive Javadoc documentation added.

The detailed documentation for each method clearly explains the permission evaluation logic, including which admin roles grant access and which permission scopes are checked. This significantly improves API discoverability and maintainability.

tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/GroupResourceTypeEvaluationTest.java (5)

90-136: LGTM - Comprehensive test coverage for VIEW_MEMBERS and MANAGE_MEMBERS permissions.

The tests properly verify:

  1. Users are hidden when no permissions exist
  2. VIEW_MEMBERS grants visibility to group members
  3. MANAGE_MEMBERS grants both visibility and update capabilities

138-187: LGTM - testManageAllGroups thoroughly exercises all-groups permission.

The test validates creating groups, adding children, and role mapping with the MANAGE scope on all groups.


189-247: LGTM - testManageGroup correctly validates per-group MANAGE permission isolation.

The test confirms that MANAGE permission on one group doesn't leak to other groups.


249-281: LGTM - testViewGroups validates VIEW permission semantics correctly.


283-319: LGTM - testManageGroupMembership tests the combination of user and group permissions.

Good test coverage for the MANAGE_GROUP_MEMBERSHIP and MANAGE_MEMBERSHIP scopes working together to allow group membership management.

services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionEvaluator.java (2)

19-25: LGTM - Appropriate imports added for documentation references.


31-97: LGTM - Well-documented interface methods for group permission evaluation.

The Javadoc clearly specifies which admin roles and permission scopes grant access for each method.

Comment on lines +99 to 148
/**
* Throws ForbiddenException if {@link #canViewMembers(GroupModel)} returns {@code false}.
*/
void requireViewMembers(GroupModel group);

/**
* Returns {@code true} if {@link UserPermissionEvaluator#canManage()} evaluates to {@code true}.
* <p/>
* Or if it has a permission to {@link AdminPermissionsSchema#MANAGE_MEMBERS} of the group.
*/
boolean canManageMembers(GroupModel group);

/**
* Returns {@code true} if the caller has one of {@link AdminRoles#MANAGE_USERS} role.
* <p/>
* Or if it has a permission to {@link AdminPermissionsSchema#MANAGE} the group or
* {@link AdminPermissionsSchema#MANAGE_MEMBERSHIP} of the group.
*/
boolean canManageMembership(GroupModel group);


/**
* Returns {@code true} if {@link UserPermissionEvaluator#canView()} evaluates to {@code true}.
* <p/>
* Or if it has a permission to {@link AdminPermissionsSchema#VIEW_MEMBERS} or
* {@link AdminPermissionsSchema#MANAGE_MEMBERS} of the group.
*/
boolean canViewMembers(GroupModel group);


/**
* Throws ForbiddenException if {@link #canManageMembership(GroupModel)} returns {@code false}.
*/
void requireManageMembership(GroupModel group);

/**
* Throws ForbiddenException if {@link #canManageMembership(GroupModel)} returns {@code false}.
*/
void requireManageMembers(GroupModel group);

/**
* Returns Map with information what access the caller for the provided group has.
*/
Map<String, Boolean> getAccess(GroupModel group);

Set<String> getGroupsWithViewPermission();
/**
* If {@link UserPermissionEvaluator#canView()} evaluates to {@code true}, returns empty set.
*
* @return Stream of IDs of groups with view permission.
*/
Set<String> getGroupIdsWithViewPermission();
}

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 | 🟡 Minor

Javadoc inconsistency: requireManageMembers documentation references wrong method.

At lines 132-134, the Javadoc for requireManageMembers states it throws if canManageMembership returns false, but it should reference canManageMembers based on the method naming convention used elsewhere in this interface.

📝 Proposed fix
     /**
-     * Throws ForbiddenException if {@link #canManageMembership(GroupModel)} returns {@code false}.
+     * Throws ForbiddenException if {@link #canManageMembers(GroupModel)} returns {@code false}.
      */
     void requireManageMembers(GroupModel group);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* Throws ForbiddenException if {@link #canViewMembers(GroupModel)} returns {@code false}.
*/
void requireViewMembers(GroupModel group);
/**
* Returns {@code true} if {@link UserPermissionEvaluator#canManage()} evaluates to {@code true}.
* <p/>
* Or if it has a permission to {@link AdminPermissionsSchema#MANAGE_MEMBERS} of the group.
*/
boolean canManageMembers(GroupModel group);
/**
* Returns {@code true} if the caller has one of {@link AdminRoles#MANAGE_USERS} role.
* <p/>
* Or if it has a permission to {@link AdminPermissionsSchema#MANAGE} the group or
* {@link AdminPermissionsSchema#MANAGE_MEMBERSHIP} of the group.
*/
boolean canManageMembership(GroupModel group);
/**
* Returns {@code true} if {@link UserPermissionEvaluator#canView()} evaluates to {@code true}.
* <p/>
* Or if it has a permission to {@link AdminPermissionsSchema#VIEW_MEMBERS} or
* {@link AdminPermissionsSchema#MANAGE_MEMBERS} of the group.
*/
boolean canViewMembers(GroupModel group);
/**
* Throws ForbiddenException if {@link #canManageMembership(GroupModel)} returns {@code false}.
*/
void requireManageMembership(GroupModel group);
/**
* Throws ForbiddenException if {@link #canManageMembership(GroupModel)} returns {@code false}.
*/
void requireManageMembers(GroupModel group);
/**
* Returns Map with information what access the caller for the provided group has.
*/
Map<String, Boolean> getAccess(GroupModel group);
Set<String> getGroupsWithViewPermission();
/**
* If {@link UserPermissionEvaluator#canView()} evaluates to {@code true}, returns empty set.
*
* @return Stream of IDs of groups with view permission.
*/
Set<String> getGroupIdsWithViewPermission();
}
/**
* Throws ForbiddenException if {@link #canViewMembers(GroupModel)} returns {@code false}.
*/
void requireViewMembers(GroupModel group);
/**
* Returns {@code true} if {@link UserPermissionEvaluator#canManage()} evaluates to {@code true}.
* <p/>
* Or if it has a permission to {@link AdminPermissionsSchema#MANAGE_MEMBERS} of the group.
*/
boolean canManageMembers(GroupModel group);
/**
* Returns {@code true} if the caller has one of {@link AdminRoles#MANAGE_USERS} role.
* <p/>
* Or if it has a permission to {@link AdminPermissionsSchema#MANAGE} the group or
* {@link AdminPermissionsSchema#MANAGE_MEMBERSHIP} of the group.
*/
boolean canManageMembership(GroupModel group);
/**
* Returns {@code true} if {@link UserPermissionEvaluator#canView()} evaluates to {@code true}.
* <p/>
* Or if it has a permission to {@link AdminPermissionsSchema#VIEW_MEMBERS} or
* {@link AdminPermissionsSchema#MANAGE_MEMBERS} of the group.
*/
boolean canViewMembers(GroupModel group);
/**
* Throws ForbiddenException if {@link #canManageMembership(GroupModel)} returns {@code false}.
*/
void requireManageMembership(GroupModel group);
/**
* Throws ForbiddenException if {@link #canManageMembers(GroupModel)} returns {@code false}.
*/
void requireManageMembers(GroupModel group);
/**
* Returns Map with information what access the caller for the provided group has.
*/
Map<String, Boolean> getAccess(GroupModel group);
/**
* If {@link UserPermissionEvaluator#canView()} evaluates to {@code true}, returns empty set.
*
* @return Stream of IDs of groups with view permission.
*/
Set<String> getGroupIdsWithViewPermission();
}
🤖 Prompt for AI Agents
In
@services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionEvaluator.java
around lines 99 - 148, The Javadoc for requireManageMembers incorrectly
references canManageMembership; update the Javadoc on the
requireManageMembers(GroupModel) method to reference
canManageMembers(GroupModel) instead (use {@link #canManageMembers(GroupModel)})
so the documentation matches the method naming convention and behavior.

Comment on lines +64 to +71
@Override
public boolean canManage() {
if (root.hasOneAdminRole(AdminRoles.MANAGE_USERS)) {
return true;
}

return hasPermission(null, AdminPermissionsSchema.VIEW, AdminPermissionsSchema.MANAGE);
}

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

Bug: canManage() incorrectly includes VIEW scope.

The canManage() method at line 70 checks for both VIEW and MANAGE scopes, but it should only check for MANAGE. Compare with canManage(GroupModel) at line 79 which correctly uses only MANAGE.

This appears to be a copy-paste error from canView().

🐛 Proposed fix
     @Override
     public boolean canManage() {
         if (root.hasOneAdminRole(AdminRoles.MANAGE_USERS)) {
             return true;
         }

-        return hasPermission(null, AdminPermissionsSchema.VIEW, AdminPermissionsSchema.MANAGE);
+        return hasPermission(null, AdminPermissionsSchema.MANAGE);
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Override
public boolean canManage() {
if (root.hasOneAdminRole(AdminRoles.MANAGE_USERS)) {
return true;
}
return hasPermission(null, AdminPermissionsSchema.VIEW, AdminPermissionsSchema.MANAGE);
}
@Override
public boolean canManage() {
if (root.hasOneAdminRole(AdminRoles.MANAGE_USERS)) {
return true;
}
return hasPermission(null, AdminPermissionsSchema.MANAGE);
}
🤖 Prompt for AI Agents
In
@services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionsV2.java
around lines 64 - 71, The canManage() method wrongly calls hasPermission(null,
AdminPermissionsSchema.VIEW, AdminPermissionsSchema.MANAGE) — remove the VIEW
scope so it only checks for MANAGE; update the hasPermission invocation in
canManage() to pass only AdminPermissionsSchema.MANAGE (matching
canManage(GroupModel)) while keeping the existing
root.hasOneAdminRole(AdminRoles.MANAGE_USERS) check.

Comment on lines +119 to +128
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());
}
});

return granted;
}

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

Bug: Using groupResource.getId() instead of groupResource.getName() for group ID lookup.

The code uses groupResource.getId() which returns the Resource entity's internal database ID. However:

  1. At line 122, hasPermission(groupResource.getId(), ...) will fail because hasPermission at line 141 does resourceStore.findByName(server, groupId), expecting the group ID (which is the resource name).
  2. At line 123, granted.add(groupResource.getId()) adds the wrong identifier to the result set.

Based on AdminPermissionsSchema.resolveGroup(), the resource name is set to group.getId(), so you should use groupResource.getName().

🐛 Proposed fix
         resourceStore.findByType(server, AdminPermissionsSchema.GROUPS_RESOURCE_TYPE, groupResource -> {
-            if (hasPermission(groupResource.getId(), AdminPermissionsSchema.VIEW_MEMBERS, AdminPermissionsSchema.MANAGE_MEMBERS)) {
-                granted.add(groupResource.getId());
+            if (hasPermission(groupResource.getName(), AdminPermissionsSchema.VIEW_MEMBERS, AdminPermissionsSchema.MANAGE_MEMBERS)) {
+                granted.add(groupResource.getName());
             }
         });
🤖 Prompt for AI Agents
In
@services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionsV2.java
around lines 119 - 128, The code incorrectly uses groupResource.getId() (the
internal Resource DB id) when calling hasPermission and when adding to the
result set; replace both uses with groupResource.getName() so the lookup and
returned identifiers use the resource name (which per
AdminPermissionsSchema.resolveGroup() is set to the actual group ID). Update the
lambda in resourceStore.findByType to call
hasPermission(groupResource.getName(), AdminPermissionsSchema.VIEW_MEMBERS,
AdminPermissionsSchema.MANAGE_MEMBERS) and to
granted.add(groupResource.getName()).

Keycloak realmAdminClient;

private final String groupName = "top_group";
private final GroupRepresentation topGroup = new GroupRepresentation();;

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 | 🟡 Minor

Minor: Double semicolon in variable initialization.

There's an extra semicolon after the GroupRepresentation constructor call.

🧹 Proposed fix
-    private final GroupRepresentation topGroup = new GroupRepresentation();;
+    private final GroupRepresentation topGroup = new GroupRepresentation();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private final GroupRepresentation topGroup = new GroupRepresentation();;
private final GroupRepresentation topGroup = new GroupRepresentation();
🤖 Prompt for AI Agents
In
@tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/GroupResourceTypeEvaluationTest.java
at line 68, In GroupResourceTypeEvaluationTest remove the stray extra semicolon
after the GroupRepresentation constructor in the declaration of the field
topGroup (private final GroupRepresentation topGroup = new
GroupRepresentation();;) so it becomes a single-semicolon termination; update
only the declaration to use a single semicolon to fix the syntax/style issue.

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