Skip to content

Commit b0150be

Browse files
committed
refactor(auth): make wildcard grants descendants-only
Signed-off-by: memleakd <121398829+memleakd@users.noreply.github.com>
1 parent 6b59a9c commit b0150be

6 files changed

Lines changed: 14 additions & 21 deletions

File tree

docs/quick_start_guide/using_authorization.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,9 @@ public array $matrix = [
5656
];
5757
```
5858

59-
A trailing `*` wildcard on a dotted scope matches the scope itself and all child permission segments. For example, `forum.posts.*` matches `forum.posts`, `forum.posts.create`, and `forum.posts.comments.delete`.
59+
A trailing `*` wildcard matches descendant permission segments only. For example, `forum.posts.*` matches `forum.posts.create` and `forum.posts.comments.delete`, but not `forum.posts`.
6060
When `*` appears between segments, it matches exactly one segment. For example, `forum.*.create` matches `forum.posts.create`.
61-
Parent matching applies to dotted scopes like `forum.posts`, not root labels like `forum`. The first segment cannot be `*`, and a standalone `*` permission does not grant all permissions.
61+
The first segment cannot be `*`, and a standalone `*` permission does not grant all permissions.
6262

6363
## Assign Permissions to a User
6464

docs/references/authorization.md

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public array $matrix = [
7474
```
7575

7676
You can use `*` as a wildcard segment to allow permissions under a scope. A wildcard matches one full segment.
77-
When the wildcard is trailing on a dotted scope, it also grants the parent scope itself and all descendant permissions.
77+
When the wildcard is trailing, it grants descendant permissions only.
7878
The first segment cannot be `*`, and a standalone `*` permission does not grant all permissions.
7979

8080
```php
@@ -83,11 +83,10 @@ public array $matrix = [
8383
];
8484
```
8585

86-
For example, `forum.posts.*` matches `forum.posts`, `forum.posts.create`, and `forum.posts.comments.delete`.
86+
For example, `forum.posts.*` matches `forum.posts.create` and `forum.posts.comments.delete`, but not
87+
`forum.posts`.
8788
Wildcards can also appear between segments: `forum.*.create` matches `forum.posts.create` and
8889
`forum.comments.create`, but does not match `forum.create` or `forum.posts.comments.create`.
89-
Since `$user->can()` expects dot-separated permissions like `scope.action`, parent matching applies to dotted
90-
permission scopes like `forum.posts`, not to root labels like `forum`.
9190

9291
Exact child permissions do not grant their parent permission. For example, `forum.posts.create` does not grant
9392
`forum.posts`.
@@ -96,9 +95,8 @@ Wildcard matching is used by `$user->can()` and `$group->can()` for both user-le
9695

9796
!!! warning
9897

99-
Wildcard permissions can grant access to the parent scope and to future child permissions added under the
100-
same scope. Use broad wildcards like `admin.*` carefully, and prefer literal permissions for highly sensitive
101-
access.
98+
Wildcard permissions can grant access to future child permissions added under the same scope. Use broad
99+
wildcards like `admin.*` carefully, and prefer literal permissions for highly sensitive access.
102100

103101
## Authorizing Users
104102

src/Authorization/PermissionMatcher.php

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,7 @@ private static function matchesWildcardGrant(string $grant, string $permission):
5252
if (end($grantSegments) === '*') {
5353
array_pop($grantSegments);
5454

55-
// Root labels like `admin` are not permission scopes, so `admin.*` should not grant `admin`.
56-
if (count($grantSegments) === 1 && count($permissionSegments) === 1) {
57-
return false;
58-
}
59-
60-
return count($permissionSegments) >= count($grantSegments)
55+
return count($permissionSegments) > count($grantSegments)
6156
&& self::segmentsMatch($grantSegments, array_slice($permissionSegments, 0, count($grantSegments)));
6257
}
6358

tests/Authorization/AuthorizableTest.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -317,9 +317,9 @@ public function testCanCascadesToGroupsWithHierarchicalWildcards(): void
317317
$this->assertTrue($this->user->can('forum.posts.comments.delete'));
318318
$this->assertTrue($this->user->can('admin.users.create'));
319319
$this->assertTrue($this->user->can('reports.daily.view'));
320-
$this->assertTrue($this->user->can('forum.posts'));
321-
$this->assertTrue($this->user->can('reports.daily'));
322320

321+
$this->assertFalse($this->user->can('forum.posts'));
322+
$this->assertFalse($this->user->can('reports.daily'));
323323
$this->assertFalse($this->user->can('admin.create'));
324324
$this->assertFalse($this->user->can('admin.users.roles.create'));
325325
$this->assertFalse($this->user->can('admin.users.delete'));
@@ -335,7 +335,7 @@ public function testCanChecksUserLevelHierarchicalWildcards(): void
335335

336336
$this->assertTrue($this->user->can('forum.posts.create'));
337337
$this->assertTrue($this->user->can('forum.posts.comments.delete'));
338-
$this->assertTrue($this->user->can('forum.posts'));
338+
$this->assertFalse($this->user->can('forum.posts'));
339339
$this->assertFalse($this->user->can('forum.users.create'));
340340
}
341341

tests/Authorization/GroupTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,8 @@ public function testCanWithHierarchicalWildcards(): void
9898
$this->assertTrue($group->can('forum.posts.create'));
9999
$this->assertTrue($group->can('forum.posts.comments.delete'));
100100
$this->assertTrue($group->can('admin.users.create'));
101-
$this->assertTrue($group->can('forum.posts'));
102101

102+
$this->assertFalse($group->can('forum.posts'));
103103
$this->assertFalse($group->can('admin.create'));
104104
$this->assertFalse($group->can('admin.users.roles.create'));
105105
$this->assertFalse($group->can('admin.users.delete'));

tests/Authorization/PermissionMatcherTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public static function provideMatches(): iterable
4444
'trailing wildcard matches child' => ['admin.users.create', ['admin.users.*'], true],
4545
'trailing wildcard matches deeper child' => ['admin.users.roles.create', ['admin.users.*'], true],
4646
'broad trailing wildcard matches child' => ['admin.users.create', ['admin.*'], true],
47-
'trailing wildcard matches parent' => ['admin.users', ['admin.users.*'], true],
47+
'trailing wildcard does not match parent' => ['admin.users', ['admin.users.*'], false],
4848
'broad wildcard does not match root permission' => ['admin', ['admin.*'], false],
4949
'standalone wildcard does not match child' => ['admin.users.create', ['*'], false],
5050
'leading wildcard does not match child' => ['admin.users.create', ['*.users.create'], false],
@@ -54,7 +54,7 @@ public static function provideMatches(): iterable
5454
'middle wildcard does not match multiple segments' => ['admin.users.roles.create', ['admin.*.create'], false],
5555
'middle wildcard does not match no segment' => ['admin.create', ['admin.*.create'], false],
5656
'middle wildcard does not match sibling' => ['admin.users.delete', ['admin.*.create'], false],
57-
'middle and trailing wildcards match parent' => ['admin.users.create', ['admin.*.create.*'], true],
57+
'middle and trailing wildcards do not match parent' => ['admin.users.create', ['admin.*.create.*'], false],
5858
'middle and trailing wildcards match child' => ['admin.users.create.view', ['admin.*.create.*'], true],
5959
'middle and trailing wildcards do not match multiple segments' => ['admin.users.roles.create', ['admin.*.create.*'], false],
6060
'middle and trailing wildcards require segment' => ['admin.create', ['admin.*.create.*'], false],

0 commit comments

Comments
 (0)