8000 [FGAP V2] `manage` scope on Clients does not imply `view` · Issue #47804 · keycloak/keycloak · GitHub
[go: up one dir, main page]

Skip to content

[FGAP V2] manage scope on Clients does not imply view #47804

Description

@Pepo48

Before reporting an issue

  • I have read and understood the above terms for submitting issues, and I understand that my issue may be closed without action if I do not follow them.

Area

admin/fine-grained-permissions

Describe the bug

With FGAP V2, a user granted only the manage scope on Clients cannot list or create clients - canView() only checks the view scope, so canList() returns false.

Version

nightly

Regression

  • The issue is a regression

Expected behavior

FGAP manage should imply view, matching the role-based behavior where MANAGE_CLIENTS implies VIEW_CLIENTS.

Actual behavior

A user with FGAP manage but not view can update an existing client but cannot:

  • list clients (canList() -> canView() -> false)
  • view a specific client (canView(client) -> only checks VIEW scope)

How to Reproduce?

  1. Enable admin permissions on a realm
  2. Create a user with no realm-management roles
  3. Grant an all-clients FGAP permission with only the manage scope
  4. Try to create a client via POST /admin/api/{realm}/clients/v2 - returns 403
  5. Add view scope to the permission - returns 201

Anything else?

Both Admin API v1 and v2 endpoints as both use ClientPermissionsV2 when FGAP V2 is enabled.

Activity

  1. added theissue type on Apr 7, 2026
  2. pedroigor commented on Apr 7, 2026

    @pedroigor
    Contributor
  3. keycloak-github-bot commented on Apr 7, 2026

    @keycloak-github-bot

    Thanks for reporting this issue, but there is insufficient information or lack of steps to reproduce.

    Please provide additional details, otherwise this issue will be automatically closed within 14 days.

  4. shawkins commented on Apr 8, 2026

    @shawkins
    Contributor

    @Pep 8000 o48 AFAIK, it is not a bug but an expected behavior. See https://www.keycloak.org/docs/latest/server_admin/#understanding-the-scopes-of-access.

    The 403 @Pepo48 is highlighting is from an anti-phishing check. We're causing some new behaviors because that check was added to a service layer, rather than the rest layer. To be consistent with client admin api v1 we only need to apply the check when obtaining the client resource. Correcting that will cause the 403 on a POST operation to go away.

    With that resolved, someone with manage only would still get a 403 on a PUT of a non-existant client - with view they would get a 404 instead. But this doesn't seem like a strong check - if the manage only user were then to attempt to do a POST they would get a 409 conflict if the resource already exists.

    A bigger concern is that having view be independent of manage does not follow well with what we're implementing with admin api v2 - that is we're returning the full resource from PUT / POST / PATCH requests - so sending a minimiltic patch effectively gives you view ability.

    To confirm, what should someone having solely manage (without view) be able to do? It appears with admin api v1 they can set the state via POST/create PUT/update, but not see the actual state - are there server managed / generated fields they cannot have access to?

    cc @vmuzikar

  5. vramik commented on Apr 8, 2026

    @vramik
    Contributor

    In FGAP v1, transitive relationships between scopes (e.g., manage implying map-roles) caused confusion — it was unclear what granting a scope actually meant in terms of other scopes being implicitly granted. For v2, all scopes were made independent to keep the permission model predictable and easy to reason about, as Pedro pointed out the docs.

    I agree this causes a practical paradox, where it makes no sense to have manage without view.

    To address this we could change the behavior that:

    1. manage implies view
      This would be a narrow fix targeting the most obvious case. However, the same problem exists for other scopes — map-roles without view etc. Treating manage implies view as a special case doesn't address these, and introducing selective transitive dependencies risks bringing back v1-style confusion about which scopes imply what.

    2. any permission on a resource makes it discoverable
      Scopes remain fully independent for their operations (granting manage still does not grant map-roles, etc.), but having any FGAP permission on a resource — regardless of scope — makes that resource visible in queries. The specific scope is then enforced when the admin actually performs the operation.
      This avoids transitive scope dependencies while addressing all "write-blind" scenarios uniformly.

    3. view as a mandatory default scope
      Rather than changing evaluation semantics, we could enforce that view is always included when creating a permission. The minimal permission would be "view-only", and admins add other scopes on top. Since view is always present, the existing partial evaluation logic (which filters by view (and view-members for groups)) works without changes.
      But that would need to be enforced both in admin UI as well as endpoints. And we'd need to be aware of negative permission scenarios, which makes this option a bit too much complex.

    To me, right now the best approach seems to be option 2. We also need to consider backward compatibility as this would be a behavioral change. Thoughts?

  6. vramik commented on Apr 8, 2026

    @vramik
    Contributor

    As the described behavior was a design choice, I'm changing the issue to enhancement.

  7. changed the issue type fromtoon Apr 8, 2026
  8. added
    kind/enhancementCategorizes a PR related to an enhancement
    and removed
    kind/bugCategorizes a PR related to a bug
    on Apr 8, 2026
  9. self-assigned this
    on Apr 8, 2026
  10. pedroigor commented on Apr 8, 2026

    @pedroigor
    Contributor

    @vramik But how 2 will play out with partial evaluation ? Or it won't apply when querying resources?

  11. vramik commented on Apr 8, 2026

    @vramik
    Contributor

    @pedroigor today when using partial evaluator, we are querying for all permissions of a given resource type that references an admin user (via user, group or role policy) and filters by view scope (or view-members for groups).

    Then it for each returned permission, we evaluate its associated policies and create allowedResources and deniedResources (deny wins).

    If the resource type has a group relationship (Users -> Groups), it runs a second runEvaluation for the group resource type, filtering by view-members. It produces allowedGroups and deniedGroups sets, which are attached to the context.

    So to change it to option 2, we'd need to fetch all permissions, but without the view filter, so that we'd get permissions with any scope. We'd need to consider users-groups permissions, like manage-members.

    Then when evaluating and creating allowedResources and deniedResources we'd have to add scope awareness to "deny" branch: we'd only add to deniedResources when the permission denies to view (view-members respectively).

    This way we'd get all permissions that references the admin user and filter out those resources which specifically denies viewing, right?

  12. Pepo48 commented on Apr 14, 2026

    @Pepo48
    ContributorAuthor

    Just to clarify on a specific example - a user has FGAP manage permission on all clients but not view. In FGAP v1 this works because the manage role implies view. In v2 the scopes are independent, so manage alone isn't enough to pass the list/view checks. This affects any code path that checks canView() or canList() before performing a manage operation.

    The list check exists to prevent probing for client IDs. If a user can only view specific clients (say client A), they could guess other IDs and distinguish 403 (exists) from 404 (doesn't) to discover other clients. This is a valid concern for per-client FGAP scenarios. On create it doesn't apply - creating a client always requires realm-level manage, not a per-client permission. A user who can't create gets 403 regardless of whether the client exists or not, so there's no information leak. And if they can create, they get 201 or 409 (conflict) - which already reveals existence anyway.

    In Admin API v1 this was never an issue - the phishing check only ran on single-client lookups (GET/DELETE) at the REST layer, and the manage role implied view. In v2 the check moved to the service layer where it also ran on create (PR #47029 removed it from POST as a workaround) and FGAP v2 scopes are independent - both changes together surfaced this.

    Keeping manage and view independent also doesn't hold up in practice - POST, PUT and PATCH all return the full client representation. A user with manage can send a minimal update and get back all fields, effectively gaining view through any write.

    The manage scope has never implied view in FGAP v2 since it was introduced. Thus, I think we can label this back as a bug.

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

Metadata

Metadata

Assignees

Fields

Value

None yet

Effort

None yet

Votes

None yet

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions

0