Add wildcard support for content scope dimensions - #5985
Closed
fraxachun wants to merge 34 commits into
Closed
Conversation
fraxachun
force-pushed
the
claude/wildcard-content-scopes-62o4jq
branch
3 times, most recently
from
July 15, 2026 14:56
1575d8c to
6a67078
Compare
Allow getContentScopesForUser to return UserPermissions.allValues as the value of a content scope dimension to grant access to any value for that dimension. The wildcard is matched during the content scope check in AbstractAccessControlService, so it does not need to be part of availableContentScopes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVwZJtKmfrQq2VZmPAB76j
A content scope with a wildcard dimension (UserPermissions.allValues) is not part of availableContentScopes, so it was filtered out by the userPermissionsContentScopes resolver and never reached the admin. Keep these wildcard scopes in the resolver response and render the wildcard dimension as "All" in the content scope grid, resolving the remaining dimensions per dimension. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVwZJtKmfrQq2VZmPAB76j
Content scope columns were derived solely from availableContentScopes. A dimension granted only via a wildcard (UserPermissions.allValues) may not appear there at all, so no column was rendered and the wildcard value was hidden. Include the displayed scopes' dimensions when generating the columns so wildcard-only dimensions get a column and render as "All". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVwZJtKmfrQq2VZmPAB76j
Grant the non-admin demo user access to every language within the "main" domain via UserPermissions.allValues to showcase wildcard content scope dimensions. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVwZJtKmfrQq2VZmPAB76j
Change the UserPermissions.allValues sentinel from "all-values" to "*". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVwZJtKmfrQq2VZmPAB76j
Add an optional "product" dimension to the demo content scope to model a dimension with potentially thousands of values that is not enumerated in availableContentScopes and only used by certain resolvers. Grant the non-admin user all products via UserPermissions.allValues to demonstrate a wildcard dimension that is not part of availableContentScopes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVwZJtKmfrQq2VZmPAB76j
Content scope dimensions were only known implicitly from the keys of the availableContentScopes values, so a dimension not part of availableContentScopes (e.g. one with too many values to enumerate) had no runtime representation and was missing from the user permissions panel. Add an optional availableContentScopeDimensions option to declare the dimensions (with labels) at runtime, expose it via userPermissionsAvailableContentScopeDimensions, and use it as the column source in the content scope grid so every user shows a consistent set of columns. Falls back to deriving dimensions from availableContentScopes when not configured. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVwZJtKmfrQq2VZmPAB76j
Declare the content scope dimensions (domain, language, product) via the new availableContentScopeDimensions option so the optional product dimension shows up in the user permissions panel for every user, even though it is not part of availableContentScopes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVwZJtKmfrQq2VZmPAB76j
… panel A user with all content scopes (UserPermissions.allContentScopes) also has all values for dimensions that are not part of availableContentScopes (e.g. an optional product dimension). In the user permissions panel, fill those dimensions with the all-values wildcard so they are shown as "all" instead of empty. This is limited to the panel display: it is not applied to the rule-based scopes used when manually assigning scopes, and does not affect the content scopes used for access control or the scope picker. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVwZJtKmfrQq2VZmPAB76j
Allow content scopes to carry values for dimensions that are not part of availableContentScopes (e.g. an optional product dimension with too many values to enumerate): - Validate only the enumerable part of a content scope against the available content scopes; declared dimensions outside of them may hold any value. - Keep such free values when reading content scopes (getContentScopes) and return them for display. - In the "Assign scopes" dialog, build a scope from the enumerable part plus a free text input per non-enumerable dimension. - Show the free value (or "All" for the wildcard) in the content scope grid, and show all-values for a user with all content scopes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVwZJtKmfrQq2VZmPAB76j
Replace the single content scope dropdown with a dropdown per enumerable dimension (free text stays for dimensions that are not part of the available content scopes). Only a combination of enumerable values that exists in the available content scopes can be added. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVwZJtKmfrQq2VZmPAB76j
Wrap the dialog content in DialogContent so it has padding instead of sticking to the dialog edges, and align the "Add scope" button with the dimension inputs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVwZJtKmfrQq2VZmPAB76j
Show whether a content scope is assigned by rule or manually in the assigned scopes grid, and allow deleting manually assigned scopes directly there. The assign scopes dialog is now only used for adding scopes: it starts empty and appends the added scopes to the existing manually assigned ones on save. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVwZJtKmfrQq2VZmPAB76j
The assign scopes dialog previously embedded the full assigned-scopes grid with its own add/delete controls, duplicating the grid shown behind it. The grid now owns listing and deleting scopes, so the dialog is reduced to selecting a single scope: one input per content scope dimension (a dropdown for enumerable dimensions, a free-text field otherwise). Confirming with the "Add scope" button appends the scope and closes the dialog. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVwZJtKmfrQq2VZmPAB76j
Narrow the dialog and stack the dimension inputs vertically instead of wrapping them in a row. Use Comet form fields with explicit labels (dropdown for enumerable dimensions, text input otherwise) and title the dialog "Add scope" to match its single purpose. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVwZJtKmfrQq2VZmPAB76j
Rename the toolbar button to "Add scope" (matching the dialog) and require a confirmation dialog before removing a manually assigned scope, mirroring the delete flow in the permissions panel. Add a hint to free-text scope dimensions that "*" grants all values. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVwZJtKmfrQq2VZmPAB76j
…rids Move the assignment type column in the permissions panel to the last position and render BY_RULE/MANUAL as "By rule"/"Manual", matching the assigned scopes grid. Render the first content scope column without bold emphasis so all columns look alike. Shorten the free-text scope hint to "* for All". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVwZJtKmfrQq2VZmPAB76j
The assigned permissions grid deleted via the deprecated TableDeleteButton, which showed a bare "Delete item?" confirmation, while the assigned scopes grid used DeleteDialog. Switch the permissions grid to the same DeleteDialog so both confirmations look and behave identically. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVwZJtKmfrQq2VZmPAB76j
Enable client-side pagination in the assigned scopes grid with a default page size of 10 and selectable sizes, so users with many assigned scopes can page through them. Drop the now-redundant rowCount prop, which the grid derives from the rows in client-side pagination mode. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVwZJtKmfrQq2VZmPAB76j
The project configures DataGridPro, which has pagination disabled by default, so the assigned scopes grid rendered all rows without a pager. Pass the pagination prop to actually turn paging on. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVwZJtKmfrQq2VZmPAB76j
Sort the assigned scopes grid so manually assigned scopes appear before rule-based ones, keeping the scopes a user can act on at the top. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVwZJtKmfrQq2VZmPAB76j
Render the permission-specific content scopes grid the same way as the assigned scopes grid: pass the runtime content scope dimensions so the columns match, and enable pagination (DataGridPro needs the explicit prop) with the same page-size options. Drop the redundant rowCount prop. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVwZJtKmfrQq2VZmPAB76j
The permission-specific content scopes dialog had its own DataGrid setup that drifted from the assigned scopes grid. Extract a shared ContentScopeDataGrid that owns the content scope dimension columns, pagination and an optional checkbox selection, and render it from both the assigned scopes grid and the override content scopes dialog so they stay in sync. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVwZJtKmfrQq2VZmPAB76j
The permission-specific content scopes dialog used checkbox selection over the available scopes, unlike the assigned scopes grid. Make it use the same flow: list the selected scopes in the shared ContentScopeDataGrid, add them through the shared add-scope dialog and remove them per row. Generalize SelectScopesDialogContent to report the built scope via an onSubmit callback instead of persisting itself, and extract a reusable AddContentScopeDialog. The assigned scopes grid persists the change via a mutation; the override dialog appends it to its form field. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVwZJtKmfrQq2VZmPAB76j
…sions Build the content scope grid columns solely from availableContentScopeDimensions instead of also unioning in the keys of the available and displayed scopes. The declared dimensions are authoritative, so every grid shows a consistent set of columns. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVwZJtKmfrQq2VZmPAB76j
The permission-specific content scopes dialog recreated its initialValues object on every render. Opening the add-scope dialog re-renders the component, and react-final-form reinitialized the form from the new initialValues (the contentScopes array is a fresh reference each render), discarding the override toggle and any added scopes so saving never persisted them. Memoize initialValues so re-renders no longer reset the form. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVwZJtKmfrQq2VZmPAB76j
…gle separately The permission-specific content scopes dialog deferred all changes to a form submit, but Comet's FinalForm skips submitting when the form is not dirty, so saving after only changing scopes did nothing and adding a scope never persisted. Persist scope add/remove immediately via the mutation (like the assigned scopes grid) and make the save button an explicit action that only persists the override toggle and closes the dialog. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVwZJtKmfrQq2VZmPAB76j
Adding or removing a permission-specific content scope now persists the current toggle value with it, instead of the previously saved one. Scopes can only be edited while the toggle is on, so persisting it on add/remove keeps added scopes visible after reopening the dialog (previously they were saved with the override still disabled and hidden again on reopen). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVwZJtKmfrQq2VZmPAB76j
The add-scope dialog rendered one dropdown per enumerable dimension, each offering every value, which let users build content scope combinations that don't exist in availableContentScopes. Each dropdown now only offers values that form a valid combination with the values already selected in the other dropdowns, and changing a dropdown clears the other selections that are no longer part of a valid combination. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVwZJtKmfrQq2VZmPAB76j
The users list showed wrong permission and content scope counts. In the admin, the columns were memoized on the locale only while the permission and scope columns were pushed onto that memoized array on every render, so their cells captured a stale (undefined) available-permissions/scopes result: the denominator was missing and the "All permissions/scopes" chip never showed. Build the full column set inside the memo with the correct dependencies. On the api, the counts were computed from the raw permission/content scope entries: a permission granted both by rule and manually was counted twice (exceeding the available count), and a wildcard content scope counted as one regardless of how many available scopes it grants access to. Count distinct permissions and the available content scopes the user can access (resolving wildcards) instead. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVwZJtKmfrQq2VZmPAB76j
With wildcard content scope dimensions the number of accessible scopes
can't be counted against a fixed total, so drop the "of X" part and show
just "{count} scopes". The "All scopes" chip is kept for users that can
access all available content scopes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVwZJtKmfrQq2VZmPAB76j
Drop the "All scopes" chip: with wildcard content scope dimensions, having
access to all currently available scopes doesn't reliably mean "all", so
the label was misleading. Always show the number of accessible scopes
("No scopes" for zero). The now-unused available content scopes are no
longer queried.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVwZJtKmfrQq2VZmPAB76j
When the current user may manage permissions, open the user detail on the permissions tab (now the first, default tab) instead of basic data, since that is what this page is primarily used for. Users without the permission still see basic data as the only/default tab. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVwZJtKmfrQq2VZmPAB76j
Deep-link the user grid's row action to the permissions sub-tab so opening a user jumps straight to Permissions (when allowed), without changing the tab order (Basic Data stays first).
fraxachun
force-pushed
the
claude/wildcard-content-scopes-62o4jq
branch
from
August 4, 2026 08:29
255db14 to
081d764
Compare
Contributor
Author
|
Closing in favor of a stacked pull request that splits this change into three reviewable layers (review and merge bottom-up):
The combined code is unchanged; each layer builds, lints and tests on its own and carries its own changeset. Generated by Claude Code |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR adds support for wildcard values in content scope dimensions, allowing
getContentScopesForUserto grant access to any value for a specific dimension usingUserPermissions.allValues.Key Changes
API Changes:
UserPermissions.allValuesconstant ("all-values") to represent wildcard dimensionsAbstractAccessControlService.isAllowed()to match wildcard dimensions against any valueUserContentScopesResolver.userPermissionsContentScopes()to preserve wildcard scopes that aren't in available content scopesAdmin UI Changes:
ContentScopeGridto handle wildcard dimensions by displaying "All" labellodash.isequaldependency from grid cell rendering, replacing with per-dimension label resolutionDocumentation & Examples:
Implementation Details
The wildcard matching works by checking if a user's content scope dimension equals
UserPermissions.allValues. During access control checks, this matches any target value for that dimension while still requiring other dimensions to match exactly. This allows flexible permission grants like "access all languages in the main domain" without needing to enumerate every language inavailableContentScopes.https://claude.ai/code/session_01JVwZJtKmfrQq2VZmPAB76j