From b1189be821519ded877273b0164e3bccfb9820bd Mon Sep 17 00:00:00 2001 From: Bernhard Date: Wed, 13 Nov 2024 14:05:41 -0300 Subject: [PATCH 1/4] fix: multilevel permissions in can() method refactor authorization checks to support nested permissions in can() --- src/Authorization/Traits/Authorizable.php | 12 +++++++++--- src/Entities/Group.php | 12 ++++++++++-- tests/Authorization/GroupTest.php | 24 +++++++++++++++++++++++ 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/src/Authorization/Traits/Authorizable.php b/src/Authorization/Traits/Authorizable.php index c0b1acf2a..d49628641 100644 --- a/src/Authorization/Traits/Authorizable.php +++ b/src/Authorization/Traits/Authorizable.php @@ -258,7 +258,7 @@ public function can(string ...$permissions): bool if (strpos($permission, '.') === false) { throw new LogicException( 'A permission must be a string consisting of a scope and action, like `users.create`.' - . ' Invalid permission: ' . $permission + . ' Invalid permission: ' . $permission ); } @@ -280,8 +280,14 @@ public function can(string ...$permissions): bool } // Check wildcard match - $check = substr($permission, 0, strpos($permission, '.')) . '.*'; - if (isset($matrix[$group]) && in_array($check, $matrix[$group], true)) { + $checks = []; + $parts = explode('.', $permission); + + for ($i = count($parts); $i > 0; $i--) { + $check = implode('.', array_slice($parts, 0, $i)) . '.*'; + $checks[] = $check; + } + if (isset($matrix[$group]) && array_intersect($checks, $matrix[$group])) { return true; } } diff --git a/src/Entities/Group.php b/src/Entities/Group.php index b63707929..50fd6c162 100644 --- a/src/Entities/Group.php +++ b/src/Entities/Group.php @@ -85,9 +85,17 @@ public function can(string $permission): bool } // Check wildcard match - $check = substr($permission, 0, strpos($permission, '.')) . '.*'; + $checks = []; + $parts = explode('.', $permission); - return $this->permissions !== null && $this->permissions !== [] && in_array($check, $this->permissions, true); + for ($i = count($parts); $i > 0; $i--) { + $check = implode('.', array_slice($parts, 0, $i)) . '.*'; + $checks[] = $check; + } + + return $this->permissions !== null + && $this->permissions !== [] + && array_intersect($checks, $this->permissions); } /** diff --git a/tests/Authorization/GroupTest.php b/tests/Authorization/GroupTest.php index 68c190be8..e3479c9fa 100644 --- a/tests/Authorization/GroupTest.php +++ b/tests/Authorization/GroupTest.php @@ -87,4 +87,28 @@ public function testCan(): void $this->assertTrue($group2->can('users.edit')); $this->assertFalse($group2->can('foo.bar')); } + + public function testCanNestedPerms(): void + { + $group = $this->groups->info('user'); + + $group->addPermission('foo.bar.*'); + $group->addPermission('foo.biz.buz.*'); + + $this->assertTrue($group->can('foo.bar')); + $this->assertTrue($group->can('foo.bar.*')); + $this->assertTrue($group->can('foo.bar.baz')); + $this->assertTrue($group->can('foo.bar.buz')); + $this->assertTrue($group->can('foo.bar.buz.biz')); + $this->assertTrue($group->can('foo.biz.buz')); + $this->assertTrue($group->can('foo.biz.buz.*')); + $this->assertTrue($group->can('foo.biz.buz.bar')); + $this->assertFalse($group->can('foo')); + $this->assertFalse($group->can('foo.*')); + $this->assertFalse($group->can('foo.biz')); + $this->assertFalse($group->can('foo.buz')); + $this->assertFalse($group->can('foo.biz.*')); + $this->assertFalse($group->can('foo.biz.bar')); + $this->assertFalse($group->can('foo.biz.bar.buz')); + } } From 25834d3a65a76b37a948abe36e0e0c6c6ec7645b Mon Sep 17 00:00:00 2001 From: Bernhard Enders Date: Wed, 13 Nov 2024 18:50:54 -0300 Subject: [PATCH 2/4] Update Group.php added !empty() so it returns bool and passes phpstan --- src/Entities/Group.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Entities/Group.php b/src/Entities/Group.php index 50fd6c162..c9ed0c4cf 100644 --- a/src/Entities/Group.php +++ b/src/Entities/Group.php @@ -95,7 +95,7 @@ public function can(string $permission): bool return $this->permissions !== null && $this->permissions !== [] - && array_intersect($checks, $this->permissions); + && !empty(array_intersect($checks, $this->permissions)); } /** From 8a77efd1f020bf04b2e1892371fdeaeb9be6f8f5 Mon Sep 17 00:00:00 2001 From: Bernhard Enders Date: Wed, 13 Nov 2024 18:55:17 -0300 Subject: [PATCH 3/4] Update Group.php added space after ! --- src/Entities/Group.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Entities/Group.php b/src/Entities/Group.php index c9ed0c4cf..93e06be79 100644 --- a/src/Entities/Group.php +++ b/src/Entities/Group.php @@ -95,7 +95,7 @@ public function can(string $permission): bool return $this->permissions !== null && $this->permissions !== [] - && !empty(array_intersect($checks, $this->permissions)); + && ! empty(array_intersect($checks, $this->permissions)); } /** From ec8b41ed4f278604e11482eac3d2a5c9012556c9 Mon Sep 17 00:00:00 2001 From: Bernhard Date: Thu, 14 Nov 2024 01:25:55 -0300 Subject: [PATCH 4/4] fix: multilevel permissions in can() method refactor authorization checks to support nested permissions in can() --- src/Authorization/Traits/Authorizable.php | 2 +- src/Entities/Group.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Authorization/Traits/Authorizable.php b/src/Authorization/Traits/Authorizable.php index d49628641..72fd527a3 100644 --- a/src/Authorization/Traits/Authorizable.php +++ b/src/Authorization/Traits/Authorizable.php @@ -287,7 +287,7 @@ public function can(string ...$permissions): bool $check = implode('.', array_slice($parts, 0, $i)) . '.*'; $checks[] = $check; } - if (isset($matrix[$group]) && array_intersect($checks, $matrix[$group])) { + if (isset($matrix[$group]) && array_intersect($checks, $matrix[$group]) !== []) { return true; } } diff --git a/src/Entities/Group.php b/src/Entities/Group.php index 93e06be79..5b417a6b2 100644 --- a/src/Entities/Group.php +++ b/src/Entities/Group.php @@ -95,7 +95,7 @@ public function can(string $permission): bool return $this->permissions !== null && $this->permissions !== [] - && ! empty(array_intersect($checks, $this->permissions)); + && array_intersect($checks, $this->permissions) !== []; } /**