Skip to content

Add wildcard support for content scope dimensions - #5985

Closed
fraxachun wants to merge 34 commits into
mainfrom
claude/wildcard-content-scopes-62o4jq
Closed

Add wildcard support for content scope dimensions#5985
fraxachun wants to merge 34 commits into
mainfrom
claude/wildcard-content-scopes-62o4jq

Conversation

@fraxachun

Copy link
Copy Markdown
Contributor

Summary

This PR adds support for wildcard values in content scope dimensions, allowing getContentScopesForUser to grant access to any value for a specific dimension using UserPermissions.allValues.

Key Changes

  • API Changes:

    • Added UserPermissions.allValues constant ("all-values") to represent wildcard dimensions
    • Updated AbstractAccessControlService.isAllowed() to match wildcard dimensions against any value
    • Modified UserContentScopesResolver.userPermissionsContentScopes() to preserve wildcard scopes that aren't in available content scopes
    • Added comprehensive test coverage for wildcard matching behavior
  • Admin UI Changes:

    • Updated ContentScopeGrid to handle wildcard dimensions by displaying "All" label
    • Fixed column generation to include dimensions from both available and user-assigned scopes
    • Removed lodash.isequal dependency from grid cell rendering, replacing with per-dimension label resolution
    • Wildcard dimensions now display correctly even when not present in available content scopes
  • Documentation & Examples:

    • Added setup documentation explaining wildcard usage
    • Updated demo implementation to use wildcard for language dimension
    • Added changeset documenting the new feature

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 in availableContentScopes.

https://claude.ai/code/session_01JVwZJtKmfrQq2VZmPAB76j

@fraxachun
fraxachun force-pushed the claude/wildcard-content-scopes-62o4jq branch 3 times, most recently from 1575d8c to 6a67078 Compare July 15, 2026 14:56
claude added 27 commits August 4, 2026 08:28
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
claude added 7 commits August 4, 2026 08:28
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).

Copy link
Copy Markdown
Contributor Author

Closing in favor of a stacked pull request that splits this change into three reviewable layers (review and merge bottom-up):

  1. Support wildcard values for content scope dimensions #6114 — Support wildcard values for content scope dimensions (@comet/cms-api) → main
  2. Allow declaring content scope dimensions at runtime #6115 — Allow declaring content scope dimensions at runtime (@comet/cms-api) → Support wildcard values for content scope dimensions #6114
  3. Rework content scopes management in the user permissions panel #6116 — Rework content scopes management in the user permissions panel (@comet/cms-admin) → Allow declaring content scope dimensions at runtime #6115

The combined code is unchanged; each layer builds, lints and tests on its own and carries its own changeset.


Generated by Claude Code

@fraxachun fraxachun closed this Aug 5, 2026
@VPS-Obi
VPS-Obi deleted the claude/wildcard-content-scopes-62o4jq branch August 5, 2026 10:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants