Skip to content

Conversation

admin-coderabbit
Copy link
Owner

@admin-coderabbit admin-coderabbit commented Feb 4, 2026

This pull request was automatically created by @coderabbitai/e2e-reviewer.

Batch created pull request.

Summary by CodeRabbit

  • New Features

    • Added fine-grained authorization support for group management with expanded permission scopes: manage, view, manage-membership, manage-members, and view-members.
    • Introduced more granular permission controls for user and group visibility, enabling administrators to define detailed access policies for group operations.
  • Bug Fixes

    • Improved permission evaluation logic to use consistent visibility checks across group operations.

…ation implementation

Closes #35562

Signed-off-by: vramik <vramik@redhat.com>
@coderabbit-eval
Copy link

coderabbit-eval bot commented Feb 4, 2026

📝 Walkthrough

Walkthrough

This change introduces fine-grained group-based authorization support across Keycloak's admin API. It adds group resource types to the authorization schema, expands permission evaluators with granular access checks, replaces GroupModel references with group IDs in permission logic, and introduces a V2 implementation layer for group permissions evaluation.

Changes

Cohort / File(s) Summary
Authorization Schema & Core Permissions
server-spi-private/src/main/java/org/keycloak/authorization/AdminPermissionsSchema.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
Added GROUPS_RESOURCE_TYPE with scopes (manage, view, manage-membership, manage-members, view-members). Expanded GroupPermissionEvaluator interface with comprehensive permission-checking methods. Updated GroupPermissions with renamed getGroupIdsWithViewPermission, protected field visibility, and refined permission logic.
V2 Permission Implementations
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
Added GroupPermissionsV2 subclass with authorization-based access evaluation for group operations. Introduced lazy-initialized groups() accessor in MgmtPermissionsV2. Simplified UserPermissionsV2 permission checks to use direct role checks and broader scope coverage.
User & Role Permissions
services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java, services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissionEvaluator.java, services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java, services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissions.java
Removed canManageDefault/canViewDefault methods; updated canManage/canView/canQuery to direct admin role checks. Expanded UserPermissionEvaluator with group membership and impersonation-related methods. Updated RolePermissions role-mapping check and made AdminPermissions listener registration conditional on feature flag.
Admin Resource Endpoints
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
Simplified permission check logic in BruteForceUsersResource. Tightened subgroup visibility checks in GroupResource. Replaced attribute-based group search and refined visibility filtering in GroupsResource. Converted GroupModel references to group IDs in UsersResource permission checks.
Utility & Removed API
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 public searchGroupModelsByAttributes method. Updated ApiUtil.handleCreatedResponse to use try-with-resources for automatic resource management.
Test Coverage & 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 integration test for group resource type authorization scenarios. Extended RealmAdminPermissionsConfig with QUERY_GROUPS role. Removed unused import from UserResourceTypeEvaluationTest.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Admin Client
    participant Endpoint as GroupsResource
    participant Auth as GroupPermissions
    participant ResourceStore as Resource Store
    participant PolicyStore as Policy Store

    Client->>Endpoint: searchForGroups(with view check)
    Endpoint->>Auth: canView()
    Auth->>Auth: Check admin roles (MANAGE_USERS/VIEW_USERS)
    alt Has admin role
        Auth-->>Endpoint: true
    else No admin role
        Auth->>ResourceStore: Fetch group resources
        Auth->>PolicyStore: Evaluate policies
        Auth-->>Endpoint: filtered groups with IDs
    end
    Endpoint->>Endpoint: Filter results by per-group visibility
    Endpoint-->>Client: Filtered group list
Loading
sequenceDiagram
    participant User as User Request
    participant UsersResource as UsersResource
    participant GroupPerms as GroupPermissions
    participant Session as Session/Groups API
    participant Search as Search Stream

    User->>UsersResource: searchForUser(with group filters)
    UsersResource->>GroupPerms: getGroupIdsWithViewPermission()
    alt Has global view permission
        GroupPerms-->>UsersResource: empty set (global view)
    else Per-group permissions
        GroupPerms->>Session: Evaluate per-group visibility
        GroupPerms-->>UsersResource: Set of group IDs with permission
    end
    UsersResource->>Session: Set GROUPS attribute with IDs
    UsersResource->>Search: searchForUserStream(with group IDs)
    Search-->>UsersResource: Filtered user stream
    UsersResource-->>User: User representations
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Poem

🐰 Hop into realms of groups with care,
Fine-grained paths we now declare,
IDs replace models in the fray,
Permissions evaluated every way,
V2 versions dance in organized light,
Authorization schemes shining bright! 🔐

🚥 Pre-merge checks | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Description check ⚠️ Warning The description is minimal and indicates an automated PR creation with no meaningful detail about the changes, objectives, or implementation scope. Add a detailed description explaining the feature implementation, key changes to authorization APIs, new permission evaluators, and test coverage objectives.
Title check ❓ Inconclusive The title describes an automated test PR but doesn't convey what the actual implementation changes accomplish; it's vague about the feature's purpose. Consider a more descriptive title that reflects the primary changes, such as 'Add group authorization schema and permissions evaluation' or similar to clarify the feature's scope.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature-groups-authz-implementation

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

@coderabbit-eval coderabbit-eval bot closed this Feb 4, 2026
Copy link

@coderabbit-eval coderabbit-eval bot left a comment

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: ⚠️ Potential issue | 🟠 Major

Ensure listener also registers when FGAP V2 is enabled.

The registerListener method gates on ADMIN_FINE_GRAINED_AUTHZ only, but V2-only deployments won't register the cleanup listener for deleted roles, clients, and groups. This is inconsistent with other methods in this class (like evaluator and realms) which check both V1 and V2 flags. See MgmtPermissions.java for the same both-flags pattern.

Suggested fix
-        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)) {
🤖 Fix all issues with AI agents
In
`@services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionEvaluator.java`:
- Around line 142-147: The Javadoc for
GroupPermissionEvaluator#getGroupIdsWithViewPermission incorrectly refers to a
"Stream" while the method returns a Set; update the Javadoc to say it returns a
Set (e.g., "Set of IDs of groups with view permission") and ensure the
description and `@return` tag match the actual return type, keeping the reference
to UserPermissionEvaluator#canView() intact.
- Around line 132-135: Update the Javadoc for the requireManageMembers method in
GroupPermissionEvaluator to reference the correct predicate name
canManageMembers (not canManageMembership); change the `@link` target in the
comment above requireManageMembers so it points to canManageMembers to match the
actual predicate/method name.

In
`@services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionsV2.java`:
- Around line 105-128: In getGroupIdsWithViewPermission(), use the resource name
(groupResource.getName()) instead of the internal DB id (groupResource.getId())
when calling hasPermission and when adding to the granted set: update the
hasPermission(...) call and the granted.add(...) to pass
groupResource.getName(), since hasPermission uses findByName() and
AdminPermissionsSchema stores the group ID as the resource name.

In
`@tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/GroupResourceTypeEvaluationTest.java`:
- Around line 138-187: The testManageAllGroups method creates a group
(testGroupId via ApiUtil.handleCreatedResponse) but doesn't remove it; add
cleanup so the created group is deleted after the test to avoid test-order
leakage. Wrap the portion that creates/uses the group (creation, update,
subgroup add, role mapping) in a try/finally or append a finally-like cleanup
that calls
realmAdminClient.realm(realm.getName()).groups().group(testGroupId).remove() (or
equivalent removal API) and handle/remove only if testGroupId is non-null to
ensure the group is removed even if assertions throw.
🧹 Nitpick comments (1)
server-spi-private/src/main/java/org/keycloak/authorization/AdminPermissionsSchema.java (1)

55-90: Consider resolving GROUPS resource names to group display names.
With the new GROUPS resource type, getResourceName() currently returns raw IDs for groups. Mapping to group names (and "All groups") improves admin UI readability.

♻️ Suggested addition to getResourceName()
         if (supportsAuthorizationSchema(session, resourceServer)) {
             String resourceType = policy.getResourceType();

             if (USERS.getType().equals(resourceType)) {
                 if (resource.getName().equals(USERS_RESOURCE_TYPE)) {
                     return "All users";
                 }
@@
                 return user.getUsername();
             }
+            if (GROUPS.getType().equals(resourceType)) {
+                if (resource.getName().equals(GROUPS_RESOURCE_TYPE)) {
+                    return "All groups";
+                }
+                GroupModel group = session.groups().getGroupById(session.getContext().getRealm(), resource.getName());
+                if (group == null) {
+                    throw new ModelIllegalStateException("Group not found for resource [" + resource.getId() + "]");
+                }
+                return group.getName();
+            }
         }

Also applies to: 334-346

Comment on lines +132 to 135
/**
* Throws ForbiddenException if {@link #canManageMembership(GroupModel)} returns {@code false}.
*/
void requireManageMembers(GroupModel group);

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Javadoc points to the wrong predicate for requireManageMembers.
Line 132 should reference canManageMembers, not canManageMembership.

📝 Javadoc fix
-     * Throws ForbiddenException if {`@link` `#canManageMembership`(GroupModel)} returns {`@code` false}.
+     * Throws ForbiddenException if {`@link` `#canManageMembers`(GroupModel)} returns {`@code` false}.
📝 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 #canManageMembership(GroupModel)} returns {@code false}.
*/
void requireManageMembers(GroupModel group);
/**
* Throws ForbiddenException if {`@link` `#canManageMembers`(GroupModel)} returns {`@code` false}.
*/
void requireManageMembers(GroupModel group);
🤖 Prompt for AI Agents
In
`@services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionEvaluator.java`
around lines 132 - 135, Update the Javadoc for the requireManageMembers method
in GroupPermissionEvaluator to reference the correct predicate name
canManageMembers (not canManageMembership); change the `@link` target in the
comment above requireManageMembers so it points to canManageMembers to match the
actual predicate/method name.

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

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Return type in Javadoc should match Set.
Line 145 mentions “Stream” but the method returns a Set.

📝 Javadoc fix
-     * `@return` Stream of IDs of groups with view permission.
+     * `@return` Set of IDs of groups with view permission.
📝 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
/**
* If {@link UserPermissionEvaluator#canView()} evaluates to {@code true}, returns empty set.
*
* @return Stream of IDs of groups with view permission.
*/
Set<String> getGroupIdsWithViewPermission();
/**
* If {`@link` UserPermissionEvaluator#canView()} evaluates to {`@code` true}, returns empty set.
*
* `@return` Set 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 142 - 147, The Javadoc for
GroupPermissionEvaluator#getGroupIdsWithViewPermission incorrectly refers to a
"Stream" while the method returns a Set; update the Javadoc to say it returns a
Set (e.g., "Set of IDs of groups with view permission") and ensure the
description and `@return` tag match the actual return type, keeping the reference
to UserPermissionEvaluator#canView() intact.

Comment on lines +105 to +128
@Override
public Set<String> getGroupIdsWithViewPermission() {
if (root.users().canView()) return Collections.emptySet();

if (!root.isAdminSameRealm()) {
return Collections.emptySet();
}

ResourceServer server = root.realmResourceServer();

if (server == null) {
return Collections.emptySet();
}

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

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, find the file and check its structure
find . -name "GroupPermissionsV2.java" -type f

Repository: admin-coderabbit/coderabbit-keycloak-mirror

Length of output: 185


🏁 Script executed:

# Also search for the hasPermission method to understand what it expects
rg -n "hasPermission" services/src/main/java/org/keycloak/services/resources/admin/permissions/ -A 3 | head -80

Repository: admin-coderabbit/coderabbit-keycloak-mirror

Length of output: 8945


🏁 Script executed:

# Check AdminPermissionsSchema for GROUPS_RESOURCE_TYPE
find . -name "AdminPermissionsSchema.java" -type f

Repository: admin-coderabbit/coderabbit-keycloak-mirror

Length of output: 176


🏁 Script executed:

# Read the GroupPermissionsV2.java file to understand the context
cat -n services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionsV2.java | head -200

Repository: admin-coderabbit/coderabbit-keycloak-mirror

Length of output: 8105


🏁 Script executed:

# Look for the hasPermission method definition in GroupPermissionsV2
rg -n "hasPermission" services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionsV2.java -B 2 -A 10

Repository: admin-coderabbit/coderabbit-keycloak-mirror

Length of output: 2882


🏁 Script executed:

# Check AdminPermissionsSchema for GROUPS_RESOURCE_TYPE
cat -n server-spi-private/src/main/java/org/keycloak/authorization/AdminPermissionsSchema.java

Repository: admin-coderabbit/coderabbit-keycloak-mirror

Length of output: 17918


🏁 Script executed:

# Check if there are other methods in GroupPermissionsV2 that iterate resources
rg -n "resourceStore\.findByType|findByName" services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionsV2.java -A 5

Repository: admin-coderabbit/coderabbit-keycloak-mirror

Length of output: 838


🏁 Script executed:

# Check lines 130-165 (also mentioned as affected) to see if there's another method
sed -n '130,200p' services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionsV2.java | cat -n

Repository: admin-coderabbit/coderabbit-keycloak-mirror

Length of output: 2924


🏁 Script executed:

# Search for similar patterns in other files to understand the naming convention
rg -n "groupResource\.|\.getName\(\)|\.getId\(\)" services/src/main/java/org/keycloak/services/resources/admin/permissions/ -B 2 -A 2 | head -100

Repository: admin-coderabbit/coderabbit-keycloak-mirror

Length of output: 13098


🏁 Script executed:

# Check if there are other methods in GroupPermissionsV2 that might have the same issue
wc -l services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionsV2.java

Repository: admin-coderabbit/coderabbit-keycloak-mirror

Length of output: 187


🏁 Script executed:

# Look for other methods that iterate resources and might have the same pattern
rg -n "public.*getGroupIds" services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionsV2.java

Repository: admin-coderabbit/coderabbit-keycloak-mirror

Length of output: 147


🏁 Script executed:

# Check the entire file to look for more methods iterating over resources
cat services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionsV2.java | tail -100

Repository: admin-coderabbit/coderabbit-keycloak-mirror

Length of output: 3271


🏁 Script executed:

# Check if UserPermissionsV2 has a similar method that iterates resources
rg -n "getUsersWithPermission|resourceStore\.findByType" services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissionsV2.java -A 5

Repository: admin-coderabbit/coderabbit-keycloak-mirror

Length of output: 69


🏁 Script executed:

# Look at how Resource objects are used elsewhere to understand the pattern better
rg -n "\.findByType\(" services/src/main/java/org/keycloak/services/resources/admin/permissions/ -B 2 -A 8

Repository: admin-coderabbit/coderabbit-keycloak-mirror

Length of output: 7277


Use resource name instead of resource ID when checking group permissions.

Line 122–123 incorrectly use groupResource.getId() (the internal Resource database ID). Since hasPermission() calls findByName() expecting a resource name, and AdminPermissionsSchema sets the resource name to the group ID, this should use groupResource.getName() instead. The current code fails the per-group resource lookup and falls back to checking only the all-groups resource.

🛠️ Suggested fix
         resourceStore.findByType(server, AdminPermissionsSchema.GROUPS_RESOURCE_TYPE, groupResource -> {
-            if (hasPermission(groupResource.getId(), AdminPermissionsSchema.VIEW_MEMBERS, AdminPermissionsSchema.MANAGE_MEMBERS)) {
-                granted.add(groupResource.getId());
+            String groupId = groupResource.getName();
+            if (hasPermission(groupId, AdminPermissionsSchema.VIEW_MEMBERS, AdminPermissionsSchema.MANAGE_MEMBERS)) {
+                granted.add(groupId);
             }
         });
🤖 Prompt for AI Agents
In
`@services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionsV2.java`
around lines 105 - 128, In getGroupIdsWithViewPermission(), use the resource
name (groupResource.getName()) instead of the internal DB id
(groupResource.getId()) when calling hasPermission and when adding to the
granted set: update the hasPermission(...) call and the granted.add(...) to pass
groupResource.getName(), since hasPermission uses findByName() and
AdminPermissionsSchema stores the group ID as the resource name.

Comment on lines +138 to +187
@Test
public void testManageAllGroups() {
// myadmin shouldn't be able to create groups just yet
try (Response response = realmAdminClient.realm(realm.getName()).groups().add(new GroupRepresentation())) {
assertEquals(Response.Status.FORBIDDEN.getStatusCode(), response.getStatus());
}

// myadmin shouldn't be able to add child for a group
try (Response response = realmAdminClient.realm(realm.getName()).groups().group(topGroup.getId()).subGroup(new GroupRepresentation())) {
assertEquals(Response.Status.FORBIDDEN.getStatusCode(), response.getStatus());
}

// myadmin shouldn't be able to map roles for group
try {
realmAdminClient.realm(realm.getName()).groups().group(topGroup.getId()).roles().realmLevel().add(List.of());
fail("Expected Exception wasn't thrown.");
} catch (Exception ex) {
assertThat(ex, instanceOf(ForbiddenException.class));
}

//create all-groups permission for "myadmin" (so that myadmin can manage all groups in the realm)
UserPolicyRepresentation policy = createUserPolicy(realm, client, "Only My Admin User Policy", realm.admin().users().search("myadmin").get(0).getId());
createAllGroupsPermission(policy, Set.of(MANAGE));

// creating group requires manage scope
GroupRepresentation group = new GroupRepresentation();
group.setName("testGroup");
String testGroupId = ApiUtil.handleCreatedResponse(realmAdminClient.realm(realm.getName()).groups().add(group));
group.setId(testGroupId);

// it should be possible to update the group due to fallback to all-groups permission
group.setName("newGroup");
realmAdminClient.realm(realm.getName()).groups().group(testGroupId).update(group);
assertEquals("newGroup", realmAdminClient.realm(realm.getName()).groups().group(testGroupId).toRepresentation().getName());

// it should be possible to add the child to the group now
try (Response response = realmAdminClient.realm(realm.getName()).groups().group(topGroup.getId()).subGroup(group)) {
assertEquals(Response.Status.NO_CONTENT.getStatusCode(), response.getStatus());
}

// it should be possible to map roles now
// trying with non existent role as we need to test manage permission for groups (not `auth.roles().requireMapRole(roleModel);`)
// expecting NotFoundException
try {
realmAdminClient.realm(realm.getName()).groups().group(topGroup.getId()).roles().realmLevel().add(List.of(new RoleRepresentation("non_existent", null, false)));
fail("Expected Exception wasn't thrown.");
} catch (Exception ex) {
assertThat(ex, instanceOf(NotFoundException.class));
}
}

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add cleanup for the group created in this test.
testManageAllGroups() creates testGroup but never removes it, which can make later tests order-dependent if the realm isn’t reset between tests.

🧹 Suggested cleanup
         String testGroupId = ApiUtil.handleCreatedResponse(realmAdminClient.realm(realm.getName()).groups().add(group));
         group.setId(testGroupId);
+        realm.cleanup().add(r -> r.groups().group(testGroupId).remove());
📝 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
@Test
public void testManageAllGroups() {
// myadmin shouldn't be able to create groups just yet
try (Response response = realmAdminClient.realm(realm.getName()).groups().add(new GroupRepresentation())) {
assertEquals(Response.Status.FORBIDDEN.getStatusCode(), response.getStatus());
}
// myadmin shouldn't be able to add child for a group
try (Response response = realmAdminClient.realm(realm.getName()).groups().group(topGroup.getId()).subGroup(new GroupRepresentation())) {
assertEquals(Response.Status.FORBIDDEN.getStatusCode(), response.getStatus());
}
// myadmin shouldn't be able to map roles for group
try {
realmAdminClient.realm(realm.getName()).groups().group(topGroup.getId()).roles().realmLevel().add(List.of());
fail("Expected Exception wasn't thrown.");
} catch (Exception ex) {
assertThat(ex, instanceOf(ForbiddenException.class));
}
//create all-groups permission for "myadmin" (so that myadmin can manage all groups in the realm)
UserPolicyRepresentation policy = createUserPolicy(realm, client, "Only My Admin User Policy", realm.admin().users().search("myadmin").get(0).getId());
createAllGroupsPermission(policy, Set.of(MANAGE));
// creating group requires manage scope
GroupRepresentation group = new GroupRepresentation();
group.setName("testGroup");
String testGroupId = ApiUtil.handleCreatedResponse(realmAdminClient.realm(realm.getName()).groups().add(group));
group.setId(testGroupId);
// it should be possible to update the group due to fallback to all-groups permission
group.setName("newGroup");
realmAdminClient.realm(realm.getName()).groups().group(testGroupId).update(group);
assertEquals("newGroup", realmAdminClient.realm(realm.getName()).groups().group(testGroupId).toRepresentation().getName());
// it should be possible to add the child to the group now
try (Response response = realmAdminClient.realm(realm.getName()).groups().group(topGroup.getId()).subGroup(group)) {
assertEquals(Response.Status.NO_CONTENT.getStatusCode(), response.getStatus());
}
// it should be possible to map roles now
// trying with non existent role as we need to test manage permission for groups (not `auth.roles().requireMapRole(roleModel);`)
// expecting NotFoundException
try {
realmAdminClient.realm(realm.getName()).groups().group(topGroup.getId()).roles().realmLevel().add(List.of(new RoleRepresentation("non_existent", null, false)));
fail("Expected Exception wasn't thrown.");
} catch (Exception ex) {
assertThat(ex, instanceOf(NotFoundException.class));
}
}
`@Test`
public void testManageAllGroups() {
// myadmin shouldn't be able to create groups just yet
try (Response response = realmAdminClient.realm(realm.getName()).groups().add(new GroupRepresentation())) {
assertEquals(Response.Status.FORBIDDEN.getStatusCode(), response.getStatus());
}
// myadmin shouldn't be able to add child for a group
try (Response response = realmAdminClient.realm(realm.getName()).groups().group(topGroup.getId()).subGroup(new GroupRepresentation())) {
assertEquals(Response.Status.FORBIDDEN.getStatusCode(), response.getStatus());
}
// myadmin shouldn't be able to map roles for group
try {
realmAdminClient.realm(realm.getName()).groups().group(topGroup.getId()).roles().realmLevel().add(List.of());
fail("Expected Exception wasn't thrown.");
} catch (Exception ex) {
assertThat(ex, instanceOf(ForbiddenException.class));
}
//create all-groups permission for "myadmin" (so that myadmin can manage all groups in the realm)
UserPolicyRepresentation policy = createUserPolicy(realm, client, "Only My Admin User Policy", realm.admin().users().search("myadmin").get(0).getId());
createAllGroupsPermission(policy, Set.of(MANAGE));
// creating group requires manage scope
GroupRepresentation group = new GroupRepresentation();
group.setName("testGroup");
String testGroupId = ApiUtil.handleCreatedResponse(realmAdminClient.realm(realm.getName()).groups().add(group));
group.setId(testGroupId);
realm.cleanup().add(r -> r.groups().group(testGroupId).remove());
// it should be possible to update the group due to fallback to all-groups permission
group.setName("newGroup");
realmAdminClient.realm(realm.getName()).groups().group(testGroupId).update(group);
assertEquals("newGroup", realmAdminClient.realm(realm.getName()).groups().group(testGroupId).toRepresentation().getName());
// it should be possible to add the child to the group now
try (Response response = realmAdminClient.realm(realm.getName()).groups().group(topGroup.getId()).subGroup(group)) {
assertEquals(Response.Status.NO_CONTENT.getStatusCode(), response.getStatus());
}
// it should be possible to map roles now
// trying with non existent role as we need to test manage permission for groups (not `auth.roles().requireMapRole(roleModel);`)
// expecting NotFoundException
try {
realmAdminClient.realm(realm.getName()).groups().group(topGroup.getId()).roles().realmLevel().add(List.of(new RoleRepresentation("non_existent", null, false)));
fail("Expected Exception wasn't thrown.");
} catch (Exception ex) {
assertThat(ex, instanceOf(NotFoundException.class));
}
}
🤖 Prompt for AI Agents
In
`@tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/GroupResourceTypeEvaluationTest.java`
around lines 138 - 187, The testManageAllGroups method creates a group
(testGroupId via ApiUtil.handleCreatedResponse) but doesn't remove it; add
cleanup so the created group is deleted after the test to avoid test-order
leakage. Wrap the portion that creates/uses the group (creation, update,
subgroup add, role mapping) in a try/finally or append a finally-like cleanup
that calls
realmAdminClient.realm(realm.getName()).groups().group(testGroupId).remove() (or
equivalent removal API) and handle/remove only if testGroupId is non-null to
ensure the group is removed even if assertions throw.

Sign in to join this conversation on GitHub.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

1 participant