Group hierarchy search discloses hidden parent groups under FGAP v2 - #50967
Group hierarchy search discloses hidden parent groups under FGAP v2#50967vramik wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Pull request overview
Prevents FGAP v2 group searches from exposing protected parent-group content while preserving hierarchy structure.
Changes:
- Returns brief representations for non-viewable ancestors.
- Adds an integration test for redacted parent data.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
services/src/main/java/org/keycloak/utils/GroupUtils.java |
Applies permission-aware hierarchy redaction. |
tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/GroupResourceTypeFilteringTest.java |
Tests stripped hidden-parent responses. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/GroupResourceTypeFilteringTest.java:102
- This parent is top-level, so the regression test only exercises one hierarchy iteration and does not verify the stated requirement that traversal continues through multiple non-viewable ancestors. Add a grandparent (or deeper chain) and assert that every hidden ancestor is retained as a stripped representation around the visible descendant.
GroupRepresentation parentGroup = realm.admin().groups().groups("group-0", -1, -1).get(0);
Unreported flaky test detectedIf the flaky tests below are affected by the changes, please review and update the changes accordingly. Otherwise, a maintainer should report the flaky tests prior to merging the PR. org.keycloak.testsuite.forms.BrowserFlowTest#testUserWithOneAdditionalFactorOtpSuccess |
martin-kanis
left a comment
There was a problem hiding this comment.
@vramik Thanks for the PR! Few points:
- There might be UI regression when an admin has VIEW on a child group but not on its parent and searches for the child group - only parent is rendered as a leaf node and the child group admin searched for is invisible
- Consider adding an entry to "Notable changes" in upgrading guide as we have there some similar entries for listing sessions
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
js/apps/admin-ui/src/groups/GroupsSection.tsx:116
- Group names may contain
/(seeGroupSearchTest.testGroupsWithSlashes), and group paths are ambiguous when slash escaping is disabled by default. Splitting the leaf path positionally can therefore label an ancestor incorrectly—for example,/parent/slash/childassignsparentinstead ofparent/slash; obtain ancestor names from group representations keyed by the route IDs rather than parsingpath.
const segments = lastGroup.path.split("/").filter(Boolean);
martin-kanis
left a comment
There was a problem hiding this comment.
Thanks for the update.
I would like to invite @ssilvert to have a look at UI as there is more changed lines now.
Besides the Copilot's comment you are looking at, there might be a problem with GroupPickerDialog when clicking a stripped parent row, user will get 403 error page.
The backend changes, tests and documentation looks good to me.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
js/apps/admin-ui/src/groups/components/GroupTree.tsx:351
- This also blocks navigation for organization groups because their representations do not contain
access; organization authorization is enforced separately byOrganizationGroupsResource. IncludeisOrgGroupsin the allow condition, asGroupTable.tsx:212already does for the same reason.
const path = findGroup(data, item.id!, []);
if (!(canViewDetails || path.at(-1)?.access?.view)) {
return;
ccfbcdf to
3b0a90a
Compare
Unreported flaky test detectedIf the flaky tests below are affected by the changes, please review and update the changes accordingly. Otherwise, a maintainer should report the flaky tests prior to merging the PR. org.keycloak.testsuite.forms.MultipleTabsLoginTest#testLoginPageRefreshKeycloak CI - Forms IT (chrome) |
| error instanceof NetworkError && | ||
| error.response.status === 403 | ||
| ) { | ||
| group = undefined; |
There was a problem hiding this comment.
if there is an error wouldn't group still be undefined?
There was a problem hiding this comment.
Yes, group would still be undefined. The point of the catch is not to set the value — it's to prevent the 403 NetworkError from propagating to the error boundary, which would crash the entire page. Without the catch, useFetch passes the error to showBoundary() and the page is replaced with "You don't have access to the admin console." The catch swallows the 403 so execution continues to the if (!group) check, which shows a friendly "not found" message inside the dialog.
I am more than open to suggestions for improvemnt here.
There was a problem hiding this comment.
But I think, if we go with this approach then if (!group) throw new Error(t("notFound")) will throw then useFetch catches it and showBoundary(error) replaces the whole page.
Cannot we filter stripped parents out of the flattened list?
In GroupPickerDialog.tsx
// line 61
const groupResource = useGroupResource();
const isOrgGroups = groupResource.isOrgGroups();
// lines 258-260
.map((g) => deepGroup([g]))
.flat()
.filter((g) => isOrgGroups || g.access)
.map((g) => (
| <span | ||
| className={ | ||
| !canViewDetails && !isOrgGroups && !group.access?.view | ||
| ? "keycloak-groups-tree__non-viewable" | ||
| : undefined |
Closes keycloak#50966 Signed-off-by: vramik <vramik@redhat.com>
Closes #50966
Design decision: Align with industry standard — parent existence (name, ID, path) is inherent to the child's identity and not hidden. Only parent content (attributes, role mappings, access) is protected. Non-viewable parents are included in the hierarchy as stripped/brief representations.
Approach: when populating group hierarchy under FGAP v2, non-viewable parent groups are represented with only structural fields (id, name, description, path, parentId) — no attributes, role mappings, or access map. The hierarchy walk continues through stripped parents so the full tree structure is preserved.