Skip to content

feat: support hierarchical permission wildcards#1327

Open
memleakd wants to merge 2 commits into
codeigniter4:developfrom
memleakd:feat/hierarchical-permission-wildcards
Open

feat: support hierarchical permission wildcards#1327
memleakd wants to merge 2 commits into
codeigniter4:developfrom
memleakd:feat/hierarchical-permission-wildcards

Conversation

@memleakd
Copy link
Copy Markdown

@memleakd memleakd commented May 23, 2026

This PR intends to finalize the hierarchical permissions work from #1253 and #1309, which had stalled for some time.

Shield already supports wildcard permissions, but the current behavior is limited when permissions are organized into deeper namespaces.

In larger, real-world applications, permissions often grow beyond two segments. The flat/single-level wildcard behavior makes those permission sets harder to express and maintain cleanly, often requiring custom workarounds.

This PR makes wildcard permissions work with deeper structures while keeping the existing $user->can() and $group->can() APIs unchanged.

A trailing wildcard now matches descendant permissions:

'forum.posts.*'

matches forum.posts.create and forum.posts.comments.delete, but not forum.posts.

Wildcards can also be used in the middle of a permission, so forum.*.create matches forum.posts.create and forum.comments.create, but not forum.posts.comments.create.

I tried to address the concerns and review feedback from the earlier attempts:

  • wildcard matching is shared instead of duplicated;
  • both group-level and direct user-level permissions use the same matcher;
  • direct user wildcard permissions still need to be listed in Config\AuthGroups::$permissions before assignment;
  • * must be a complete segment;
  • * cannot be the first segment;
  • standalone * does not grant everything;
  • trailing wildcards do not grant the parent permission itself;
  • tests cover the matcher itself and the public $user->can() / $group->can() paths.

I’d appreciate any feedback. Hopefully this can help move this long-standing and requested feature forward.

Add hierarchical wildcard matching for Shield permissions.

- Support nested trailing wildcards like forum.posts.*
- Support middle-segment wildcards like forum.*.create
- Share wildcard matching between user and group permission checks
- Document wildcard semantics and direct user wildcard assignment
- Cover matcher behavior and public authorization paths

Co-authored-by: bgeneto <bgeneto@duck.com>
Co-authored-by: christianberkman <christianberkman@users.noreply.github.com>

Signed-off-by: memleakd <121398829+memleakd@users.noreply.github.com>
Copy link
Copy Markdown
Member

@michalsn michalsn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to make wildcards simpler: trailing wildcards match children only, never the parent. So forum.posts.* would match forum.posts.create but NOT forum.posts.

The current "parent matching" feels like hidden privilege escalation. If someone sets up forum.posts.* thinking "all actions on posts", they probably don't expect it also grants the bare forum.posts scope - which could be used as a different kind of gate elsewhere in their app. Current behavior is non-intuitive to me.

@michalsn michalsn requested a review from datamweb May 27, 2026 15:13
@datamweb
Copy link
Copy Markdown
Collaborator

@michalsn I agree, and I think this is the direction we should standardize on as a team.

The overall goal of supporting deeper hierarchical wildcard permissions still seems valid, but I do not think trailing wildcards should also grant the parent scope itself.

Allowing scope.* to match scope introduces implicit privilege expansion. In an authorization system, that is too broad a default because the granted access becomes wider than the permission string naturally suggests. If forum.posts is used elsewhere as its own gate, then assigning forum.posts.* may unintentionally grant that too.

The clearer contract, in my view, is:

1- scope => exact match only
2- scope.* => descendants only
3- never the parent itself

That keeps permission grants explicit, avoids hidden coupling between namespace-like labels and actual permission checks, and gives us simpler semantics to document and test consistently across both group-level and direct user permissions.

So my preference would be to keep the feature, but adjust the trailing-wildcard semantics to follow this rule before merging.

@datamweb datamweb added the enhancement New feature or request label May 29, 2026
Signed-off-by: memleakd <121398829+memleakd@users.noreply.github.com>
@memleakd
Copy link
Copy Markdown
Author

memleakd commented May 29, 2026

Thanks for your reviews @michalsn, @datamweb

Just pushed an update. Trailing wildcards now match descendants only, never the parent scope itself. Tests and docs were updated to cover the new semantics.

Copy link
Copy Markdown
Member

@michalsn michalsn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this looks good!

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

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants