Skip to content

Support wildcard values for content scope dimensions - #6114

Open
fraxachun wants to merge 2 commits into
mainfrom
claude/user-permissions-content-scope-wildcard
Open

Support wildcard values for content scope dimensions#6114
fraxachun wants to merge 2 commits into
mainfrom
claude/user-permissions-content-scope-wildcard

Conversation

@fraxachun

@fraxachun fraxachun commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Problem

Content scopes could only grant access to concrete dimension values that are part of availableContentScopes. There was no way to grant a user access to any value of a single dimension (e.g. every language within a domain, or every product) without enumerating all values — which is not feasible for dimensions with many (potentially thousands) of values.

Solution

getContentScopesForUser can now use the wildcard value "*" as the value of a single content scope dimension to grant access to any value for that dimension. The wildcard is matched during the content scope check, so it does not need to be part of availableContentScopes.

Note: contentScopesCount still counts a wildcard scope as a single scope and therefore under-reports the accessible scopes for such users. This is intentionally left untouched here (with a FIXME) and corrected in the next layer (#6135), which replaces it with a per-dimension summary.

Example

getContentScopesForUser(user: User): ContentScopesForUser {
    // Grant access to every language within the "main" domain
    return [{ domain: "main", language: "*" }];
}

Stacked pull request

This is layer 1 of 4 of a stacked pull request. Review and merge bottom-up (each layer targets the one above it):

  1. Support wildcard values for content scope dimensions (this PR) → main (Support wildcard values for content scope dimensions #6114)
  2. Summarize a user's content scopes per dimension in the users list → Support wildcard values for content scope dimensions #6114 (Remove the Permissions and Scopes columns from the user permissions users list #6135)
  3. Allow declaring content scope dimensions at runtime → Remove the Permissions and Scopes columns from the user permissions users list #6135 (Allow declaring content scope dimensions at runtime #6115)
  4. Rework content scopes management in the user permissions panel → Allow declaring content scope dimensions at runtime #6115 (Rework content scopes management in the user permissions panel #6116)

🤖 Generated with Claude Code

Comment thread packages/api/cms-api/src/user-permissions/user-permissions.types.ts Outdated
@fraxachun
fraxachun force-pushed the claude/user-permissions-content-scope-wildcard branch 2 times, most recently from 9f936dc to a55dff9 Compare August 6, 2026 08:50
Comment thread packages/api/cms-api/src/user-permissions/user-permissions.service.ts Outdated
Comment thread packages/api/cms-api/src/user-permissions/user-permissions.service.ts Outdated
Comment thread packages/api/cms-api/src/user-permissions/access-control.service.ts Outdated
Comment thread packages/api/cms-api/src/user-permissions/access-control.service.ts Outdated
Comment thread packages/api/cms-api/src/user-permissions/user-permissions.service.ts Outdated
Comment thread packages/api/cms-api/src/user-permissions/user-permissions.service.ts Outdated
@fraxachun
fraxachun force-pushed the claude/user-permissions-content-scope-wildcard branch 2 times, most recently from a5b4639 to ee0b3df Compare August 6, 2026 13:02
VPS-Obi
VPS-Obi previously approved these changes Aug 6, 2026
Comment on lines +122 to +123
// FIXME: this counts a wildcard content scope ("*") as a single scope and therefore under-reports the number of
// scopes a user can access. Replaced by a per-dimension summary in a follow-up pull request.

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.

When merging this, we shouldn't release a new version until #6135 has been merged as well.

@fraxachun
fraxachun force-pushed the claude/user-permissions-content-scope-wildcard branch 10 times, most recently from b444cca to 92b6d41 Compare August 7, 2026 10:20
@fraxachun
fraxachun force-pushed the claude/user-permissions-content-scope-wildcard branch 2 times, most recently from 9caabab to 6364c63 Compare August 7, 2026 11:38
@fraxachun
fraxachun force-pushed the claude/user-permissions-content-scope-wildcard branch from 6364c63 to 6e32162 Compare August 11, 2026 05:07
Comment thread packages/api/cms-api/src/user-permissions/access-control.service.ts
@fraxachun
fraxachun force-pushed the claude/user-permissions-content-scope-wildcard branch from 6e32162 to c5c3d5f Compare August 11, 2026 12:26
@VPS-Obi

VPS-Obi commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Needs human review — touches auth/permission logic.

This introduces wildcard ("*") matching for content-scope dimensions in getContentScopesForUser, changing how access is granted and checked. It's layer 1 of a 4-layer stacked PR and already has 16 review comments, but core authorization-matching logic like this warrants a careful human look before merge (e.g. correctness of the wildcard matching, and the noted contentScopesCount under-reporting left as a FIXME).


Generated by Claude Code

@fraxachun
fraxachun force-pushed the claude/user-permissions-content-scope-wildcard branch 4 times, most recently from e3da3d3 to bdd9ce8 Compare August 13, 2026 14:14
claude added 2 commits August 14, 2026 11:29
Allow getContentScopesForUser to return the wildcard value "*" as the value
of a single content scope dimension to grant access to any value for that
dimension. The wildcard is matched during the content scope check and does not
need to be part of availableContentScopes.

contentScopesCount still counts a wildcard as a single scope; that is corrected
in a follow-up pull request.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVwZJtKmfrQq2VZmPAB76j
Content scope checks compared scopes with a strict deep-equality check, so a
wildcard dimension ("*") was not handled. As a result:

- Impersonation broke: a user with a wildcard scope, or with access to all
  content scopes (UserPermissions.allContentScopes, which was expanded to the
  concrete available scopes), could not impersonate other users. getImpersonatedUser
  returned undefined and the UI silently stayed on the current user.
- The allowed content scopes (scope picker) and the warnings scope check did not
  recognize wildcard scopes either.

Handle the wildcard consistently:

- Represent access to all content scopes as a single scope that grants any value
  ("*") of every available dimension in the current user's permissions
  (getPermissionsAndContentScopes), instead of expanding it to the concrete
  available scopes. The content scopes list in the user permissions panel keeps
  the concrete scopes.
- Introduce isScopeWithin as the wildcard-aware content scope check and use it for
  isAllowed, the allowed content scopes, and the warnings scope check.
- Compare content scopes for impersonation (isEqualOrMorePermissions) with a
  stricter check that also requires the current user's scope not to be narrower on
  any dimension it constrains beyond the target.
- Make the admin's client-side isAllowed wildcard-aware as well.
@fraxachun
fraxachun force-pushed the claude/user-permissions-content-scope-wildcard branch from bdd9ce8 to e5a27dc Compare August 14, 2026 09:29
VPS-thodax
VPS-thodax previously approved these changes Aug 17, 2026
Comment on lines +2 to +3
"@comet/cms-api": minor
"@comet/cms-admin": minor

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.

We must change the changeset from @comet/* to @dextinity/*:

Suggested change
"@comet/cms-api": minor
"@comet/cms-admin": minor
"@dextinity/cms-api": minor
"@dextinity/cms-admin": minor

Comment on lines +299 to +304
const userContentScopes = await this.filterContentScopesForUser({
user,
availableContentScopes,
includeContentScopesManual: true,
representAllContentScopesAsWildcard: true,
});

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.

For users with allContentScopes the API now returns [{ domain: "*", language: "*" }] instead of the concrete scopes. Should we mention this in the changeset?

for (const contentScope of permission.contentScopes) {
if (!currentUserPermission.contentScopes.find((cs) => isEqual(cs, contentScope))) {
// The current user must have at least as much access as the target for this content scope. Unlike
// isScopeInTargetScope, the current user's scope must not be narrower on any dimension it constrains

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.

nit: There is no isScopeInTargetScope, the function is called isScopeWithin.

Suggested change
// isScopeInTargetScope, the current user's scope must not be narrower on any dimension it constrains
// isScopeWithin, the current user's scope must not be narrower on any dimension it constrains

@VPS-thodax
VPS-thodax dismissed their stale review August 17, 2026 11:30

Accidentally approved

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.

4 participants