Skip to content

Apply FGAP view authorization to SCIM filter predicates - #51464

Open
sguilhen wants to merge 1 commit into
keycloak:mainfrom
sguilhen:50985-scim-filter-fgap-bypass
Open

Apply FGAP view authorization to SCIM filter predicates#51464
sguilhen wants to merge 1 commit into
keycloak:mainfrom
sguilhen:50985-scim-filter-fgap-bypass

Conversation

@sguilhen

@sguilhen sguilhen commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Closes #50985
Closes #50986

Copilot AI balanced review requested due to automatic review settings August 5, 2026 15:56
@sguilhen
sguilhen requested a review from a team as a code owner August 5, 2026 15:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Applies FGAP authorization checks to SCIM membership filter predicates.

Changes:

  • Adds authorization callbacks to JPA predicate evaluation.
  • Checks referenced group/user visibility.
  • Adds FGAP regression tests.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
AuthorizationTest.java Adds membership-filter authorization tests.
UserResourceTypeProvider.java Checks group visibility for user filters.
GroupResourceTypeProvider.java Checks user visibility for group filters.
ScimJPAPredicateProvider.java Invokes filter authorization callbacks.
ScimJPAPredicateEvaluator.java Propagates authorization callbacks.

BiPredicate<String, String> authCheck = (path, value) -> {
if ("members.value".equals(path)) {
UserModel user = session.users().getUserById(realm, value);
return user != null && permissions.hasPermission(user, AdminPermissionsSchema.USERS_RESOURCE_TYPE, AdminPermissionsSchema.VIEW);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The problem is that the filter introduces a new way to query groups that bypasses the VIEW_MEMBERS gate. Without filters, membership can only be discovered by fetching a group and looking at its members attribute, which VIEW_MEMBERS properly controls. But members.value eq "<user-id>" uses membership as a selection criterion in the query itself - the group appearing in the results already reveals the relationship, making the response-level VIEW_MEMBERS check too late.

A caller with VIEW on the group and VIEW on the user (but not VIEW_MEMBERS) can probe membership status even though the GET response hides the members list. Fixing this requires either extending applyAuthorizationFilters() to support a VIEW_MEMBERS scope (touches the core authz pipeline across 4-6 files) or post-filtering results in Java (breaks pagination/count). Both approaches warrant a dedicated issue IMO.

@pedroigor FYI

Copilot AI review requested due to automatic review settings August 5, 2026 20:55
@sguilhen
sguilhen force-pushed the 50985-scim-filter-fgap-bypass branch from 30ff834 to 872247c Compare August 5, 2026 20:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Suppressed comments (2)

scim/model/src/main/java/org/keycloak/scim/model/group/GroupResourceTypeProvider.java:200

  • This authorizes only the referenced user, not whether membership may be viewed for each returned group. The new test demonstrates the gap: get(..., "members") is hidden with only group VIEW (AuthorizationTest.java:916-917), yet after granting user VIEW this predicate returns that group (lines 953-956), so callers can still infer membership without the group's VIEW_MEMBERS scope required by #50986; constrain candidates by VIEW_MEMBERS or reject the predicate.
                UserModel user = session.users().getUserById(realm, value);
                return user != null && permissions.hasPermission(user, AdminPermissionsSchema.USERS_RESOURCE_TYPE, AdminPermissionsSchema.VIEW);

scim/model/src/main/java/org/keycloak/scim/model/group/GroupResourceTypeProvider.java:195

  • The accepted members path resolves to the same complex attribute and is mapped to the user-membership join, but only members.value is authorized here. Filters such as members eq "<hidden-user-id>" and members pr consequently bypass this check.
            if ("members.value".equalsIgnoreCase(path)) {

Copilot AI review requested due to automatic review settings August 6, 2026 09:50
@sguilhen
sguilhen force-pushed the 50985-scim-filter-fgap-bypass branch from 872247c to 80cce3d Compare August 6, 2026 09:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Suppressed comments (1)

scim/model/src/main/java/org/keycloak/scim/model/group/GroupResourceTypeProvider.java:200

  • This validates only permission to view the referenced user, not permission to view members of each returned group. Consequently, after the test grants user VIEW, a group with only group VIEW is returned even though its members attribute is hidden; constrain results by GROUPS/VIEW_MEMBERS (or reject the filter without that permission) before exposing the membership predicate.
                UserModel user = session.users().getUserById(realm, value);
                return user != null && permissions.hasPermission(user, AdminPermissionsSchema.USERS_RESOURCE_TYPE, AdminPermissionsSchema.VIEW);

Copilot AI review requested due to automatic review settings August 6, 2026 11:41
@sguilhen
sguilhen force-pushed the 50985-scim-filter-fgap-bypass branch from 80cce3d to b5cdfa0 Compare August 6, 2026 11:41

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Suppressed comments (2)

scim/model/src/main/java/org/keycloak/scim/model/user/UserResourceTypeProvider.java:235

  • This check also applies when FGAP is disabled, so a caller with only QUERY_USERS can no longer use an equality membership filter: legacy GroupPermissions.canView(group) does not grant group view for that role, and the predicate becomes empty. Make the authorization callback a no-op when adminPermissionsEnabled is false, as already intended for the non-eq branch.
                if (value == null) {
                    return !realm.isAdminPermissionsEnabled();
                }
                GroupModel group = session.groups().getGroupById(realm, value);
                return group != null && permissions.hasPermission(group, AdminPermissionsSchema.GROUPS_RESOURCE_TYPE, AdminPermissionsSchema.VIEW);

scim/model/src/main/java/org/keycloak/scim/model/group/GroupResourceTypeProvider.java:203

  • This check also applies when FGAP is disabled, so a caller with only QUERY_GROUPS can no longer use an equality member filter: that role does not grant user view, and the predicate becomes empty. Make the authorization callback a no-op when adminPermissionsEnabled is false, matching the existing behavior for non-eq operators.
                if (value == null) {
                    return !realm.isAdminPermissionsEnabled();
                }
                UserModel user = session.users().getUserById(realm, value);
                return user != null && permissions.hasPermission(user, AdminPermissionsSchema.USERS_RESOURCE_TYPE, AdminPermissionsSchema.VIEW);

Copilot AI review requested due to automatic review settings August 6, 2026 11:52
@sguilhen
sguilhen force-pushed the 50985-scim-filter-fgap-bypass branch from b5cdfa0 to 44110af Compare August 6, 2026 11:52

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Closes keycloak#50985
Closes keycloak#50986

Signed-off-by: Stefan Guilhen <sguilhen@redhat.com>
Copilot AI review requested due to automatic review settings August 6, 2026 12:02
@sguilhen
sguilhen force-pushed the 50985-scim-filter-fgap-bypass branch from 44110af to 6970347 Compare August 6, 2026 12:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Suppressed comments (2)

scim/model/src/main/java/org/keycloak/scim/model/user/UserResourceTypeProvider.java:233

  • This also authorizes eq filters when FGAP is disabled. A SCIM client with only QUERY_USERS can search users but groups.canView(group) is false (it requires VIEW_USERS/MANAGE_USERS), so every existing groups.value eq ... query now returns zero; bypass the callback entirely when realm.isAdminPermissionsEnabled() is false, as the non-eq branch already does.
            if ("groups.value".equalsIgnoreCase(path) || "groups".equalsIgnoreCase(path)) {
                if (value == null) {
                    return !realm.isAdminPermissionsEnabled();
                }
                GroupModel group = session.groups().getGroupById(realm, value);
                return group != null && permissions.hasPermission(group, AdminPermissionsSchema.GROUPS_RESOURCE_TYPE, AdminPermissionsSchema.VIEW);

scim/model/src/main/java/org/keycloak/scim/model/group/GroupResourceTypeProvider.java:201

  • The user permission lookup runs for eq even when FGAP is disabled. A client with only QUERY_GROUPS is allowed to search groups but cannot VIEW a user, so legitimate members.value eq ... filters regress to an empty result; return true for protected paths whenever admin permissions are disabled.
            if ("members.value".equalsIgnoreCase(path) || "members".equalsIgnoreCase(path)) {
                if (value == null) {
                    return !realm.isAdminPermissionsEnabled();
                }
                UserModel user = session.users().getUserById(realm, value);
                return user != null && permissions.hasPermission(user, AdminPermissionsSchema.USERS_RESOURCE_TYPE, AdminPermissionsSchema.VIEW);

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SCIM Groups filter leaks hidden user membership under FGAP SCIM Users filter leaks hidden group membership under FGAP

2 participants