From 1ba2a28f55c4cd8e891773248877fca1cbdfebbf Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Wed, 13 Jan 2021 10:31:30 +0000 Subject: [PATCH 001/648] Fix bug when adding roles to a model that doesn't yet exist --- src/Traits/HasRoles.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index 55376d4b4..9abf8906d 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -124,9 +124,9 @@ function ($object) use ($roles, $model) { if ($modelLastFiredOn !== null && $modelLastFiredOn === $model) { return; } - $object->roles()->sync($roles, false); - $object->load('roles'); - $modelLastFiredOn = $object; + $model->roles()->sync($roles, false); + $model->load('roles'); + $modelLastFiredOn = $model; } ); } From bd8ec48e5f9eb24df5441c371e13a5b9a4965c63 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Wed, 20 Jan 2021 08:39:17 +0000 Subject: [PATCH 002/648] Apply the same fix to the HasPermissions trait --- src/Traits/HasPermissions.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index 532e815ce..fe6bc3c7e 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -352,9 +352,9 @@ function ($object) use ($permissions, $model) { if ($modelLastFiredOn !== null && $modelLastFiredOn === $model) { return; } - $object->permissions()->sync($permissions, false); - $object->load('permissions'); - $modelLastFiredOn = $object; + $model->permissions()->sync($permissions, false); + $model->load('permissions'); + $modelLastFiredOn = $model; } ); } From 33dedec04265a6be22fdea70a3b3b8bc5efa5934 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Wed, 20 Jan 2021 08:52:38 +0000 Subject: [PATCH 003/648] Update the HasRolesTest to cover the bug --- tests/HasRolesTest.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/HasRolesTest.php b/tests/HasRolesTest.php index bd4c58395..be20b73e1 100644 --- a/tests/HasRolesTest.php +++ b/tests/HasRolesTest.php @@ -247,6 +247,9 @@ public function calling_syncRoles_before_saving_object_doesnt_interfere_with_oth $user2->syncRoles('testRole2'); $user2->save(); + $this->assertTrue($user->fresh()->hasRole('testRole')); + $this->assertFalse($user->fresh()->hasRole('testRole2')); + $this->assertTrue($user2->fresh()->hasRole('testRole2')); $this->assertFalse($user2->fresh()->hasRole('testRole')); } @@ -262,6 +265,9 @@ public function calling_assignRole_before_saving_object_doesnt_interfere_with_ot $admin_user->assignRole('testRole2'); $admin_user->save(); + $this->assertTrue($user->fresh()->hasRole('testRole')); + $this->assertFalse($user->fresh()->hasRole('testRole2')); + $this->assertTrue($admin_user->fresh()->hasRole('testRole2')); $this->assertFalse($admin_user->fresh()->hasRole('testRole')); } From 8f02175c7a974c1ee21fd2edc9a249b934d8d599 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Wed, 20 Jan 2021 08:55:07 +0000 Subject: [PATCH 004/648] Update the HasPermissionsTest to cover the bug --- tests/HasPermissionsTest.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/HasPermissionsTest.php b/tests/HasPermissionsTest.php index 8ef5c4714..c772bda47 100644 --- a/tests/HasPermissionsTest.php +++ b/tests/HasPermissionsTest.php @@ -476,6 +476,9 @@ public function calling_givePermissionTo_before_saving_object_doesnt_interfere_w $user2->givePermissionTo('edit-articles'); $user2->save(); + $this->assertTrue($user->fresh()->hasPermissionTo('edit-news')); + $this->assertFalse($user->fresh()->hasPermissionTo('edit-articles')); + $this->assertTrue($user2->fresh()->hasPermissionTo('edit-articles')); $this->assertFalse($user2->fresh()->hasPermissionTo('edit-news')); } @@ -491,6 +494,9 @@ public function calling_syncPermissions_before_saving_object_doesnt_interfere_wi $user2->syncPermissions('edit-articles'); $user2->save(); + $this->assertTrue($user->fresh()->hasPermissionTo('edit-news')); + $this->assertFalse($user->fresh()->hasPermissionTo('edit-articles')); + $this->assertTrue($user2->fresh()->hasPermissionTo('edit-articles')); $this->assertFalse($user2->fresh()->hasPermissionTo('edit-news')); } From df4e9c66fc2cc0bfc74c0261ffe0101c9988b81d Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Thu, 21 Jan 2021 10:35:35 -0500 Subject: [PATCH 005/648] Updates #975 to avoid static var and fix #1663 By avoiding the temporary/static variable here, and using $model directly, the detection of persisted object is more accurate. --- src/Traits/HasPermissions.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index fe6bc3c7e..4b7d6a3a5 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -348,13 +348,8 @@ public function givePermissionTo(...$permissions) $class::saved( function ($object) use ($permissions, $model) { - static $modelLastFiredOn; - if ($modelLastFiredOn !== null && $modelLastFiredOn === $model) { - return; - } $model->permissions()->sync($permissions, false); $model->load('permissions'); - $modelLastFiredOn = $model; } ); } From 224fac226c9b404546c360a829fc0ee990c30be1 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Thu, 21 Jan 2021 10:36:13 -0500 Subject: [PATCH 006/648] Updates #975 to avoid static var and fix #1663 By avoiding the temporary/static variable here, and using $model directly, the detection of persisted object is more accurate. --- src/Traits/HasRoles.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index 9abf8906d..cb1086523 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -120,13 +120,8 @@ public function assignRole(...$roles) $class::saved( function ($object) use ($roles, $model) { - static $modelLastFiredOn; - if ($modelLastFiredOn !== null && $modelLastFiredOn === $model) { - return; - } $model->roles()->sync($roles, false); $model->load('roles'); - $modelLastFiredOn = $model; } ); } From 4b9f076be3f1239fd96834e820aa76989f707c67 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Wed, 27 Jan 2021 17:45:03 -0500 Subject: [PATCH 007/648] Update CHANGELOG.md --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd9f63c90..e954f0f7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ All notable changes to `laravel-permission` will be documented in this file +## 3.18.0 - 2020-11-27 +- Allow PHP 8.0 + ## 3.17.0 - 2020-09-16 - Optional `$guard` parameter may be passed to `RoleMiddleware`, `PermissionMiddleware`, and `RoleOrPermissionMiddleware`. See #1565 From 7936ea9ebbd0d91877109dfeda1659fc4570a6cc Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Wed, 27 Jan 2021 18:03:34 -0500 Subject: [PATCH 008/648] Update CHANGELOG.md --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e954f0f7a..82e3b4a18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ All notable changes to `laravel-permission` will be documented in this file +## 4.0.0 - 2021-01-27 +- Drop support on Laravel 5.8 #1615 +- Fix bug when adding roles to a model that doesn't yet exist #1663 +- Enforce unique constraints on database level #1261 +- Changed PermissionRegistrar::initializeCache() public to allow reinitializing cache in custom situations. #1521 +- Use Eloquent\Collection instead of Support\Collection for consistency, collection merging, etc #1630 + +This package now requires PHP 7.2.5 and Laravel 6.0 or higher. +If you are on a PHP version below 7.2.5 or a Laravel version below 6.0 you can use an older version of this package. + ## 3.18.0 - 2020-11-27 - Allow PHP 8.0 From 099ec8e6584669308e6cddeadafacf61eae2c8b0 Mon Sep 17 00:00:00 2001 From: Freek Van der Herten Date: Thu, 28 Jan 2021 08:34:48 +0100 Subject: [PATCH 009/648] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 278e3e3a8..0aa6e4479 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,7 @@ We publish all received postcards [on our company website](https://spatie.be/en/ ## Credits +- [Chris Brown](https://github.com/drbyte) - [Freek Van der Herten](https://github.com/freekmurze) - [All Contributors](../../contributors) From 8dca397be04ad7a2b489034be42a1f8cc312df24 Mon Sep 17 00:00:00 2001 From: Freek Van der Herten Date: Thu, 28 Jan 2021 08:39:47 +0100 Subject: [PATCH 010/648] Update _index.md --- docs/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_index.md b/docs/_index.md index f6aefce38..390f0bb4e 100644 --- a/docs/_index.md +++ b/docs/_index.md @@ -1,5 +1,5 @@ --- -title: v3 +title: v4 slogan: Associate users with roles and permissions githubUrl: https://github.com/spatie/laravel-permission branch: master From f9518f6efc810bd1825fad6cb95258605646e257 Mon Sep 17 00:00:00 2001 From: freek Date: Thu, 28 Jan 2021 08:44:29 +0100 Subject: [PATCH 011/648] wip --- .github/ISSUE_TEMPLATE/bug_report.md | 34 ++++++++++++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 11 +++++++++ 2 files changed, 45 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..3084e278a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,34 @@ +--- +name: Bug report +about: Report or reproducable bug +title: '' +labels: '' +assignees: '' + +--- + +**Before creating a new bug report** +Please check if there isn't a similar issue on [the issue tracker](https://github.com/spatie/laravel-persmission/issues) or in [the discussions](https://github.com/spatie/laravel-permission/discussions). + +**Describe the bug** +A clear and concise description of what the bug is. + +**Versions** +You can use `composer show` to get the version numbers of: +- spatie/laravel-permission package version: +- illuminate/framework package + +PHP version: + +**To Reproduce** +Steps to reproduce the behavior: + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Desktop (please complete the following information):** + - OS: [e.g. macOS] + - Version [e.g. 22] + + **Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..eb06ccd7f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +blank_issues_enabled: false +contact_links: + - name: Ask a Question + url: https://github.com/spatie/laravel-permission/discussions/new?category=q-a + about: Ask the community for help + - name: Feature Request + url: https://github.com/spatie/laravel-permission/discussions/new?category=ideas + about: Share ideas for new features + - name: Bug Report + url: https://github.com/spatie/laravel-permission/issues/new + about: Report a reproducable bug From d3568dad67c826765f9fed80d2a1ab51e95bf0c4 Mon Sep 17 00:00:00 2001 From: freek Date: Thu, 28 Jan 2021 08:45:15 +0100 Subject: [PATCH 012/648] wip --- .github/ISSUE_TEMPLATE/config.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index eb06ccd7f..5940c1979 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -6,6 +6,3 @@ contact_links: - name: Feature Request url: https://github.com/spatie/laravel-permission/discussions/new?category=ideas about: Share ideas for new features - - name: Bug Report - url: https://github.com/spatie/laravel-permission/issues/new - about: Report a reproducable bug From 7f4b19557d4646320f1218720dc49b91eba7cc52 Mon Sep 17 00:00:00 2001 From: freek Date: Thu, 28 Jan 2021 08:49:56 +0100 Subject: [PATCH 013/648] wip --- .github/SECURITY.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .github/SECURITY.md diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 000000000..ca9134343 --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1,3 @@ +# Security Policy + +If you discover any security related issues, please email freek@spatie.be instead of using the issue tracker. From d110964cb7d481becd90b3465d77b316cd81be6c Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 2 Feb 2021 12:47:24 -0500 Subject: [PATCH 014/648] Note about filesystem ownership on cache files --- docs/advanced-usage/cache.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/advanced-usage/cache.md b/docs/advanced-usage/cache.md index 492bd11b5..1272b7ea6 100644 --- a/docs/advanced-usage/cache.md +++ b/docs/advanced-usage/cache.md @@ -71,3 +71,12 @@ In `config/permission.php` set `cache.store` to the name of any one of the `conf Setting `'cache.store' => 'array'` in `config/permission.php` will effectively disable caching by this package between requests (it will only cache in-memory until the current request is completed processing, never persisting it). Alternatively, in development mode you can bypass ALL of Laravel's caching between visits by setting `CACHE_DRIVER=array` in `.env`. You can see an example of this in the default `phpunit.xml` file that comes with a new Laravel install. Of course, don't do this in production though! + + +## File cache driver + +This situation is not specific to this package, but is mentioned here due to the common question being asked. + +If you are using the `File` cache driver and run into problems clearing the cache, it is most likely because your filesystem's permissions are preventing the PHP CLI from altering the cache files because the PHP-FPM process is running as a different user. + +Work with your server administrator to fix filesystem ownership on your cache files. From d5200af82334e300188bf64a6502c528d75c242c Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 23 Feb 2021 16:02:54 -0500 Subject: [PATCH 015/648] Update bug_report.md --- .github/ISSUE_TEMPLATE/bug_report.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 3084e278a..f4c3a68a3 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -20,6 +20,9 @@ You can use `composer show` to get the version numbers of: PHP version: +Database version: + + **To Reproduce** Steps to reproduce the behavior: From 47f3706a10300239454c0ea995929404469eec89 Mon Sep 17 00:00:00 2001 From: Tiago Lucas Flach Date: Tue, 23 Feb 2021 20:39:31 -0300 Subject: [PATCH 016/648] Update role-permission.md Removed a duplicate paragraph --- docs/basic-usage/role-permissions.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/docs/basic-usage/role-permissions.md b/docs/basic-usage/role-permissions.md index e4d0875cd..bbfd85d4d 100644 --- a/docs/basic-usage/role-permissions.md +++ b/docs/basic-usage/role-permissions.md @@ -140,11 +140,6 @@ $user->getAllPermissions(); All these responses are collections of `Spatie\Permission\Models\Permission` objects. - - -If we follow the previous example, the first response will be a collection with the `delete article` permission and -the second will be a collection with the `edit article` permission and the third will contain both. - If we follow the previous example, the first response will be a collection with the `delete article` permission and the second will be a collection with the `edit article` permission and the third will contain both. From 6a66dea9af1712e9e457f1a38a98f2013d9f012d Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Wed, 24 Feb 2021 13:39:56 -0500 Subject: [PATCH 017/648] Update extending.md --- docs/advanced-usage/extending.md | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/docs/advanced-usage/extending.md b/docs/advanced-usage/extending.md index 6f1068a5e..5fd067925 100644 --- a/docs/advanced-usage/extending.md +++ b/docs/advanced-usage/extending.md @@ -32,13 +32,28 @@ Note the following requirements when extending/replacing the models: ### Extending If you need to EXTEND the existing `Role` or `Permission` models note that: -- Your `Role` model needs to extend the `Spatie\Permission\Models\Role` model -- Your `Permission` model needs to extend the `Spatie\Permission\Models\Permission` model +- Your `Role` model needs to `extend` the `Spatie\Permission\Models\Role` model +- Your `Permission` model needs to `extend` the `Spatie\Permission\Models\Permission` model - You need to update `config/permission.php` to specify your namespaced model +eg: +```php + Date: Thu, 4 Mar 2021 12:32:03 -0300 Subject: [PATCH 018/648] hasExactRoles method added --- docs/basic-usage/role-permissions.md | 8 +++++++- src/Traits/HasRoles.php | 28 ++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/docs/basic-usage/role-permissions.md b/docs/basic-usage/role-permissions.md index bbfd85d4d..8d1cb4350 100644 --- a/docs/basic-usage/role-permissions.md +++ b/docs/basic-usage/role-permissions.md @@ -54,7 +54,13 @@ You can also determine if a user has all of a given list of roles: $user->hasAllRoles(Role::all()); ``` -The `assignRole`, `hasRole`, `hasAnyRole`, `hasAllRoles` and `removeRole` functions can accept a +You can also determine if a user has exactly all of a given list of roles: + +```php +$user->hasExactRoles(Role::all()); +``` + +The `assignRole`, `hasRole`, `hasAnyRole`, `hasAllRoles`, `hasExactRoles` and `removeRole` functions can accept a string, a `\Spatie\Permission\Models\Role` object or an `\Illuminate\Support\Collection` object. diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index cb1086523..f109fb0b1 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -251,6 +251,34 @@ public function hasAllRoles($roles, string $guard = null): bool ) == $roles; } + /** + * Determine if the model has exactly all of the given role(s). + * + * @param string|array|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection $roles + * @param string|null $guard + * @return bool + */ + public function hasExactRoles($roles, string $guard = null): bool + { + if (is_string($roles) && false !== strpos($roles, '|')) { + $roles = $this->convertPipeToArray($roles); + } + + if (is_string($roles)) { + $roles = [$roles]; + } + + if ($roles instanceof Role) { + $roles = [$roles->name]; + } + + $roles = collect()->make($roles)->map(function ($role) { + return $role instanceof Role ? $role->name : $role; + }); + + return $this->roles->count() == $roles->count() && $this->hasAllRoles($roles, $guard); + } + /** * Return all permissions directly coupled to the model. */ From 3e42044ab6e8cec95e9b1a0bcb3ecfb470ed21cb Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Mon, 8 Mar 2021 00:55:39 -0500 Subject: [PATCH 019/648] Laravel 6 --- docs/prerequisites.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/prerequisites.md b/docs/prerequisites.md index 57d133439..377b91845 100644 --- a/docs/prerequisites.md +++ b/docs/prerequisites.md @@ -3,7 +3,7 @@ title: Prerequisites weight: 3 --- -This package can be used in Laravel 5.8 or higher. +This package can be used in Laravel 6 or higher. This package uses Laravel's Gate layer to provide Authorization capabilities. The Gate/authorization layer requires that your `User` model implement the `Illuminate\Contracts\Auth\Access\Authorizable` contract. From 835daa4a35971474ff5ccef0e3b20a4562f6acec Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Mon, 8 Mar 2021 01:01:14 -0500 Subject: [PATCH 020/648] Schema::defaultStringLength(125) for MySQL 8.0 --- docs/prerequisites.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/prerequisites.md b/docs/prerequisites.md index 377b91845..ddcafbdb6 100644 --- a/docs/prerequisites.md +++ b/docs/prerequisites.md @@ -3,8 +3,12 @@ title: Prerequisites weight: 3 --- +## Laravel Version + This package can be used in Laravel 6 or higher. +## User Model / Contract/Interface + This package uses Laravel's Gate layer to provide Authorization capabilities. The Gate/authorization layer requires that your `User` model implement the `Illuminate\Contracts\Auth\Access\Authorizable` contract. Otherwise the `can()` and `authorize()` methods will not work in your controllers, policies, templates, etc. @@ -25,9 +29,21 @@ class User extends Authenticatable } ``` +## Must not have a `role` or `roles` property, nor a `roles()` method + Additionally, your `User` model/object MUST NOT have a `role` or `roles` property (or field in the database), nor a `roles()` method on it. Those will interfere with the properties and methods added by the `HasRoles` trait provided by this package, thus causing unexpected outcomes when this package's methods are used to inspect roles and permissions. +## Must not have a `permission` or `permissions` property, nor a `permissions()` method + Similarly, your `User` model/object MUST NOT have a `permission` or `permissions` property (or field in the database), nor a `permissions()` method on it. Those will interfere with the properties and methods added by the `HasPermissions` trait provided by this package (which is invoked via the `HasRoles` trait). +## Config file + This package publishes a `config/permission.php` file. If you already have a file by that name, you must rename or remove it, as it will conflict with this package. You could optionally merge your own values with those required by this package, as long as the keys that this package expects are present. See the source file for more details. +## Schema Limitation in MySQL + +MySQL 8.0 limits index keys to 1000 characters. This package publishes a migration which combines multiple columns in single index. With `utf8mb4` the 4-bytes-per-character requirement of `mb4` means the max length of the columns in the hybrid index can only be `125` characters. + +Thus in your AppServiceProvider you will need to set `Schema::defaultStringLength(125)`. [See the Laravel Docs for instructions](https://laravel.com/docs/master/migrations#index-lengths-mysql-mariadb). + From b84ae203f68c203b0fac2a0828967ce01d8dc069 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Mon, 8 Mar 2021 01:05:15 -0500 Subject: [PATCH 021/648] Add comments for optional change required by MySQL 8.0 limitations --- database/migrations/create_permission_tables.php.stub | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/database/migrations/create_permission_tables.php.stub b/database/migrations/create_permission_tables.php.stub index 504f861d1..edf92e7a1 100644 --- a/database/migrations/create_permission_tables.php.stub +++ b/database/migrations/create_permission_tables.php.stub @@ -22,8 +22,8 @@ class CreatePermissionTables extends Migration Schema::create($tableNames['permissions'], function (Blueprint $table) { $table->bigIncrements('id'); - $table->string('name'); - $table->string('guard_name'); + $table->string('name'); // For MySQL 8.0 use string('name', 125); + $table->string('guard_name'); // For MySQL 8.0 use string('guard_name', 125); $table->timestamps(); $table->unique(['name', 'guard_name']); @@ -31,8 +31,8 @@ class CreatePermissionTables extends Migration Schema::create($tableNames['roles'], function (Blueprint $table) { $table->bigIncrements('id'); - $table->string('name'); - $table->string('guard_name'); + $table->string('name'); // For MySQL 8.0 use string('name', 125); + $table->string('guard_name'); // For MySQL 8.0 use string('guard_name', 125); $table->timestamps(); $table->unique(['name', 'guard_name']); From 6adaab5be0b671a80b1d647da1ed7ff0c7d0b01e Mon Sep 17 00:00:00 2001 From: kidii <2613451+kidii@users.noreply.github.com> Date: Sun, 21 Mar 2021 16:16:40 +0100 Subject: [PATCH 022/648] Update the documention link to v4 instead of v3 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0aa6e4479..c06c55a16 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ ## Documentation, Installation, and Usage Instructions -See the [DOCUMENTATION](https://docs.spatie.be/laravel-permission/v3/introduction/) for detailed installation and usage instructions. +See the [DOCUMENTATION](https://docs.spatie.be/laravel-permission/v4/introduction/) for detailed installation and usage instructions. ## What It Does This package allows you to manage user permissions and roles in a database. From 29c05324c170c0be108ccb86dd29f6a719c0a617 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Mon, 22 Mar 2021 14:38:29 -0400 Subject: [PATCH 023/648] Update CHANGELOG.md --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 82e3b4a18..3aeb1a822 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ All notable changes to `laravel-permission` will be documented in this file +## 4.0.1 - 2021-03-22 +- Added note in migration for field lengths on MySQL 8. (either shorten the columns to 125 or use InnoDB) + ## 4.0.0 - 2021-01-27 - Drop support on Laravel 5.8 #1615 - Fix bug when adding roles to a model that doesn't yet exist #1663 From 062ae10a79ebb5c4ea4bd25d9ed303c407fcb8f7 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 23 Mar 2021 17:25:17 -0400 Subject: [PATCH 024/648] Refactor to extract methods --- src/PermissionServiceProvider.php | 43 ++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/src/PermissionServiceProvider.php b/src/PermissionServiceProvider.php index e9e20e660..724b42894 100644 --- a/src/PermissionServiceProvider.php +++ b/src/PermissionServiceProvider.php @@ -14,24 +14,11 @@ class PermissionServiceProvider extends ServiceProvider { public function boot(PermissionRegistrar $permissionLoader, Filesystem $filesystem) { - if (function_exists('config_path')) { // function not available and 'publish' not relevant in Lumen - $this->publishes([ - __DIR__.'/../config/permission.php' => config_path('permission.php'), - ], 'config'); - - $this->publishes([ - __DIR__.'/../database/migrations/create_permission_tables.php.stub' => $this->getMigrationFileName($filesystem, 'create_permission_tables.php'), - ], 'migrations'); - } + $this->offerPublishing(); $this->registerMacroHelpers(); - $this->commands([ - Commands\CacheReset::class, - Commands\CreateRole::class, - Commands\CreatePermission::class, - Commands\Show::class, - ]); + $this->registerCommands(); $this->registerModelBindings(); @@ -53,6 +40,32 @@ public function register() $this->registerBladeExtensions(); } + protected function offerPublishing() + { + if (!function_exists('config_path')) { + // function not available and 'publish' not relevant in Lumen + return; + } + + $this->publishes([ + __DIR__.'/../config/permission.php' => config_path('permission.php'), + ], 'config'); + + $this->publishes([ + __DIR__.'/../database/migrations/create_permission_tables.php.stub' => $this->getMigrationFileName($filesystem, 'create_permission_tables.php'), + ], 'migrations'); + } + + protected function registerCommands() + { + $this->commands([ + Commands\CacheReset::class, + Commands\CreateRole::class, + Commands\CreatePermission::class, + Commands\Show::class, + ]); + } + protected function registerModelBindings() { $config = $this->app->config['permission.models']; From 491be445ffc99d76b92a298011bc27431c5697bb Mon Sep 17 00:00:00 2001 From: drbyte Date: Tue, 23 Mar 2021 21:25:53 +0000 Subject: [PATCH 025/648] Fix styling --- src/PermissionServiceProvider.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PermissionServiceProvider.php b/src/PermissionServiceProvider.php index 724b42894..b3abe94d9 100644 --- a/src/PermissionServiceProvider.php +++ b/src/PermissionServiceProvider.php @@ -42,7 +42,7 @@ public function register() protected function offerPublishing() { - if (!function_exists('config_path')) { + if (! function_exists('config_path')) { // function not available and 'publish' not relevant in Lumen return; } From b12159e61f94680be0a95ee5b50d9f1099040714 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 23 Mar 2021 22:28:59 -0400 Subject: [PATCH 026/648] Re-refactor to fix resolver --- src/PermissionServiceProvider.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/PermissionServiceProvider.php b/src/PermissionServiceProvider.php index b3abe94d9..b9f17398a 100644 --- a/src/PermissionServiceProvider.php +++ b/src/PermissionServiceProvider.php @@ -12,7 +12,7 @@ class PermissionServiceProvider extends ServiceProvider { - public function boot(PermissionRegistrar $permissionLoader, Filesystem $filesystem) + public function boot(PermissionRegistrar $permissionLoader) { $this->offerPublishing(); @@ -52,7 +52,7 @@ protected function offerPublishing() ], 'config'); $this->publishes([ - __DIR__.'/../database/migrations/create_permission_tables.php.stub' => $this->getMigrationFileName($filesystem, 'create_permission_tables.php'), + __DIR__.'/../database/migrations/create_permission_tables.php.stub' => $this->getMigrationFileName('create_permission_tables.php'), ], 'migrations'); } @@ -167,13 +167,14 @@ protected function registerMacroHelpers() /** * Returns existing migration file if found, else uses the current timestamp. * - * @param Filesystem $filesystem * @return string */ - protected function getMigrationFileName(Filesystem $filesystem, $migrationFileName): string + protected function getMigrationFileName($migrationFileName): string { $timestamp = date('Y_m_d_His'); + $filesystem = $this->app->make(Filesystem::class); + return Collection::make($this->app->databasePath().DIRECTORY_SEPARATOR.'migrations'.DIRECTORY_SEPARATOR) ->flatMap(function ($path) use ($filesystem) { return $filesystem->glob($path.'*_create_permission_tables.php'); From 5cf582f57f7d2c1b965825d96498817eb3d34ffb Mon Sep 17 00:00:00 2001 From: abenerd Date: Sun, 28 Mar 2021 19:25:06 +0200 Subject: [PATCH 027/648] Retrieve guard only once in middlewares --- src/Middlewares/PermissionMiddleware.php | 6 ++++-- src/Middlewares/RoleMiddleware.php | 6 ++++-- src/Middlewares/RoleOrPermissionMiddleware.php | 5 +++-- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/Middlewares/PermissionMiddleware.php b/src/Middlewares/PermissionMiddleware.php index f1eca5a9a..73dec2ef8 100644 --- a/src/Middlewares/PermissionMiddleware.php +++ b/src/Middlewares/PermissionMiddleware.php @@ -9,7 +9,9 @@ class PermissionMiddleware { public function handle($request, Closure $next, $permission, $guard = null) { - if (app('auth')->guard($guard)->guest()) { + $authGuard = app('auth')->guard($guard); + + if ($authGuard->guest()) { throw UnauthorizedException::notLoggedIn(); } @@ -18,7 +20,7 @@ public function handle($request, Closure $next, $permission, $guard = null) : explode('|', $permission); foreach ($permissions as $permission) { - if (app('auth')->guard($guard)->user()->can($permission)) { + if ($authGuard->user()->can($permission)) { return $next($request); } } diff --git a/src/Middlewares/RoleMiddleware.php b/src/Middlewares/RoleMiddleware.php index 2c679d5c9..34e91e241 100644 --- a/src/Middlewares/RoleMiddleware.php +++ b/src/Middlewares/RoleMiddleware.php @@ -10,7 +10,9 @@ class RoleMiddleware { public function handle($request, Closure $next, $role, $guard = null) { - if (Auth::guard($guard)->guest()) { + $authGuard = Auth::guard($guard); + + if ($authGuard->guest()) { throw UnauthorizedException::notLoggedIn(); } @@ -18,7 +20,7 @@ public function handle($request, Closure $next, $role, $guard = null) ? $role : explode('|', $role); - if (! Auth::guard($guard)->user()->hasAnyRole($roles)) { + if (! $authGuard->user()->hasAnyRole($roles)) { throw UnauthorizedException::forRoles($roles); } diff --git a/src/Middlewares/RoleOrPermissionMiddleware.php b/src/Middlewares/RoleOrPermissionMiddleware.php index f8d5fa3b8..b9149f2f6 100644 --- a/src/Middlewares/RoleOrPermissionMiddleware.php +++ b/src/Middlewares/RoleOrPermissionMiddleware.php @@ -10,7 +10,8 @@ class RoleOrPermissionMiddleware { public function handle($request, Closure $next, $roleOrPermission, $guard = null) { - if (Auth::guard($guard)->guest()) { + $authGuard = Auth::guard($guard); + if ($authGuard->guest()) { throw UnauthorizedException::notLoggedIn(); } @@ -18,7 +19,7 @@ public function handle($request, Closure $next, $roleOrPermission, $guard = null ? $roleOrPermission : explode('|', $roleOrPermission); - if (! Auth::guard($guard)->user()->hasAnyRole($rolesOrPermissions) && ! Auth::guard($guard)->user()->hasAnyPermission($rolesOrPermissions)) { + if (! $authGuard->user()->hasAnyRole($rolesOrPermissions) && ! $authGuard->user()->hasAnyPermission($rolesOrPermissions)) { throw UnauthorizedException::forRolesOrPermissions($rolesOrPermissions); } From 70d1b3a01a08b1ad64a04ed5f3972db677d53c44 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Sun, 28 Mar 2021 14:57:56 -0400 Subject: [PATCH 028/648] Add link to Lumen docs --- docs/installation-lumen.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/installation-lumen.md b/docs/installation-lumen.md index b2b350b51..4e7d85f7a 100644 --- a/docs/installation-lumen.md +++ b/docs/installation-lumen.md @@ -3,9 +3,11 @@ title: Installation in Lumen weight: 5 --- -NOTE: Lumen is not officially supported by this package. However, the following are some steps which may help get you started. +NOTE: Lumen is **not** officially supported by this package. However, the following are some steps which may help get you started. -First, install the package via Composer: +Lumen installation instructions can be found in the [Lumen documentation](https://lumen.laravel.com/docs/master). + +Install the permissions package via Composer: ``` bash composer require spatie/laravel-permission @@ -48,7 +50,7 @@ $app->register(Spatie\Permission\PermissionServiceProvider::class); $app->register(App\Providers\AuthServiceProvider::class); ``` -Ensure your database configuration is set in your `.env` (or `config/database.php` if you have one). +Ensure the application's database name/credentials are set in your `.env` (or `config/database.php` if you have one), and that the database exists. Run the migrations to create the tables for this package: From 90c0c930458facad1298baa24320fdd214652499 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Sun, 28 Mar 2021 15:07:22 -0400 Subject: [PATCH 029/648] Fix typo --- docs/installation-lumen.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/installation-lumen.md b/docs/installation-lumen.md index 4e7d85f7a..08881eb90 100644 --- a/docs/installation-lumen.md +++ b/docs/installation-lumen.md @@ -66,8 +66,8 @@ NOTE: Remember that Laravel's authorization layer requires that your `User` mode ### User Table NOTE: If you are working with a fresh install of Lumen, then you probably also need a migration file for your Users table. You can create your own, or you can copy a basic one from Laravel: -https://github.com/laravel/laravel/blob/master/database/migrations/2014_10_12_000000_create_users_table.php +https://github.com/laravel/laravel/blob/master/database/migrations/2014_10_12_000000_create_users_table.php(https://github.com/laravel/laravel/blob/master/database/migrations/2014_10_12_000000_create_users_table.php) (You will need to run `php artisan migrate` after adding this file.) -Remember to update your ModelFactory.php to match the fields in the migration you create/copy. +Remember to update your UserFactory.php to match the fields in the migration you create/copy. From f0fe2c879478f332b72633b14468c98a7f2c84c5 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Sun, 28 Mar 2021 15:07:55 -0400 Subject: [PATCH 030/648] Fix typo --- docs/installation-lumen.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation-lumen.md b/docs/installation-lumen.md index 08881eb90..be484756b 100644 --- a/docs/installation-lumen.md +++ b/docs/installation-lumen.md @@ -66,7 +66,7 @@ NOTE: Remember that Laravel's authorization layer requires that your `User` mode ### User Table NOTE: If you are working with a fresh install of Lumen, then you probably also need a migration file for your Users table. You can create your own, or you can copy a basic one from Laravel: -https://github.com/laravel/laravel/blob/master/database/migrations/2014_10_12_000000_create_users_table.php(https://github.com/laravel/laravel/blob/master/database/migrations/2014_10_12_000000_create_users_table.php) +[https://github.com/laravel/laravel/blob/master/database/migrations/2014_10_12_000000_create_users_table.php](https://github.com/laravel/laravel/blob/master/database/migrations/2014_10_12_000000_create_users_table.php) (You will need to run `php artisan migrate` after adding this file.) From 3b1017904edc63905dcc6fd3795fff95c01377c9 Mon Sep 17 00:00:00 2001 From: Jochen Sengier Date: Wed, 12 May 2021 10:45:52 +0200 Subject: [PATCH 031/648] Remove redundant code in getMigrationFileName() function Unless I'm mistaken, these lines can be safely deleted. --- src/PermissionServiceProvider.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/PermissionServiceProvider.php b/src/PermissionServiceProvider.php index b9f17398a..867d081af 100644 --- a/src/PermissionServiceProvider.php +++ b/src/PermissionServiceProvider.php @@ -176,9 +176,6 @@ protected function getMigrationFileName($migrationFileName): string $filesystem = $this->app->make(Filesystem::class); return Collection::make($this->app->databasePath().DIRECTORY_SEPARATOR.'migrations'.DIRECTORY_SEPARATOR) - ->flatMap(function ($path) use ($filesystem) { - return $filesystem->glob($path.'*_create_permission_tables.php'); - })->push($this->app->databasePath()."/migrations/{$timestamp}_create_permission_tables.php") ->flatMap(function ($path) use ($filesystem, $migrationFileName) { return $filesystem->glob($path.'*_'.$migrationFileName); }) From 00a21daa278a09f51610097f4b7df6d05f3ac1de Mon Sep 17 00:00:00 2001 From: Joost de Bruijn Date: Tue, 18 May 2021 13:24:17 +0200 Subject: [PATCH 032/648] feat: add blade directives for hasExactRoles --- docs/basic-usage/blade-directives.md | 9 +++++++++ src/PermissionServiceProvider.php | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/docs/basic-usage/blade-directives.md b/docs/basic-usage/blade-directives.md index 1c1aaaaff..aafa016b4 100644 --- a/docs/basic-usage/blade-directives.md +++ b/docs/basic-usage/blade-directives.md @@ -89,3 +89,12 @@ Alternatively, `@unlessrole` gives the reverse for checking a singular role, lik @endunlessrole ``` +You can also determine if a user has exactly all of a given list of roles: + +```php +@hasexactroles('writer|admin'); + I am both a writer and an admin and nothing else! +@else + I do not have all of these roles or have more other roles... +@endhasexactroles +``` diff --git a/src/PermissionServiceProvider.php b/src/PermissionServiceProvider.php index e9e20e660..f2e44ec18 100644 --- a/src/PermissionServiceProvider.php +++ b/src/PermissionServiceProvider.php @@ -117,6 +117,15 @@ protected function registerBladeExtensions() $bladeCompiler->directive('endunlessrole', function () { return ''; }); + + $bladeCompiler->directive('hasexactroles', function ($arguments) { + list($roles, $guard) = explode(',', $arguments.','); + + return "check() && auth({$guard})->user()->hasExactRoles({$roles})): ?>"; + }); + $bladeCompiler->directive('endhasexactroles', function () { + return ''; + }); }); } From fd6512ecb03bcd0469e2886656e19310d2ae7212 Mon Sep 17 00:00:00 2001 From: Joost de Bruijn Date: Tue, 18 May 2021 13:24:26 +0200 Subject: [PATCH 033/648] feat: add tests for hasExactRoles --- tests/HasRolesTest.php | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/HasRolesTest.php b/tests/HasRolesTest.php index be20b73e1..661bf2f7d 100644 --- a/tests/HasRolesTest.php +++ b/tests/HasRolesTest.php @@ -484,6 +484,45 @@ public function it_can_determine_that_a_user_has_all_of_the_given_roles() $this->assertFalse($this->testUser->hasAllRoles(['testRole', 'second role'], 'fakeGuard')); } + /** @test */ + public function it_can_determine_that_a_user_has_exact_all_of_the_given_roles() + { + $roleModel = app(Role::class); + + $this->assertFalse($this->testUser->hasExactRoles($roleModel->first())); + + $this->assertFalse($this->testUser->hasExactRoles('testRole')); + + $this->assertFalse($this->testUser->hasExactRoles($roleModel->all())); + + $roleModel->create(['name' => 'second role']); + + $this->testUser->assignRole($this->testUserRole); + + $this->assertTrue($this->testUser->hasExactRoles('testRole')); + $this->assertTrue($this->testUser->hasExactRoles('testRole', 'web')); + $this->assertFalse($this->testUser->hasExactRoles('testRole', 'fakeGuard')); + + $this->assertFalse($this->testUser->hasExactRoles(['testRole', 'second role'])); + $this->assertFalse($this->testUser->hasExactRoles(['testRole', 'second role'], 'web')); + + $this->testUser->assignRole('second role'); + + $this->assertTrue($this->testUser->hasExactRoles(['testRole', 'second role'])); + $this->assertTrue($this->testUser->hasExactRoles(['testRole', 'second role'], 'web')); + $this->assertFalse($this->testUser->hasExactRoles(['testRole', 'second role'], 'fakeGuard')); + + $roleModel->create(['name' => 'third role']); + $this->testUser->assignRole('third role'); + + $this->assertFalse($this->testUser->hasExactRoles(['testRole', 'second role'])); + $this->assertFalse($this->testUser->hasExactRoles(['testRole', 'second role'], 'web')); + $this->assertFalse($this->testUser->hasExactRoles(['testRole', 'second role'], 'fakeGuard')); + $this->assertTrue($this->testUser->hasExactRoles(['testRole', 'second role', 'third role'])); + $this->assertTrue($this->testUser->hasExactRoles(['testRole', 'second role', 'third role'], 'web')); + $this->assertFalse($this->testUser->hasExactRoles(['testRole', 'second role', 'third role'], 'fakeGuard')); + } + /** @test */ public function it_can_determine_that_a_user_does_not_have_a_role_from_another_guard() { From 4a89dd3f083ddb89153367e6d087de3872c724ff Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 1 Jun 2021 20:39:38 -0400 Subject: [PATCH 034/648] Update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3aeb1a822..8296e4fce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to `laravel-permission` will be documented in this file +## 4.1.0 - 2021-06-01 +- Refactor to resolve guard only once during middleware +- Refactor service provider by extracting some methods + ## 4.0.1 - 2021-03-22 - Added note in migration for field lengths on MySQL 8. (either shorten the columns to 125 or use InnoDB) From 39d8b0078f8e092c2d0b77a78347876e2cbdc562 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 1 Jun 2021 20:45:21 -0400 Subject: [PATCH 035/648] Update php-cs-fixer.yml --- .github/workflows/php-cs-fixer.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/php-cs-fixer.yml b/.github/workflows/php-cs-fixer.yml index 84ab01ad2..b3c985609 100644 --- a/.github/workflows/php-cs-fixer.yml +++ b/.github/workflows/php-cs-fixer.yml @@ -13,7 +13,7 @@ jobs: - name: Fix style uses: docker://oskarstark/php-cs-fixer-ga with: - args: --config=.php_cs --allow-risky=yes + args: --config=.php_cs.dist.php --allow-risky=yes - name: Extract branch name shell: bash From e9555ad90c852232f72ff4dd3b8b44f503cd3aab Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 1 Jun 2021 20:46:10 -0400 Subject: [PATCH 036/648] Update .gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index f7129a1c7..ba274b9c4 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,5 @@ vendor tests/temp .idea .phpunit.result.cache -.php_cs.cache +.php-cs-fixer.cache From 9c2380884cca1a29714b8e43d4c95d8b5f8c0f1c Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 1 Jun 2021 20:48:51 -0400 Subject: [PATCH 037/648] Update php cs fixer --- .php_cs => .php_cs.dist.php | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) rename .php_cs => .php_cs.dist.php (73%) diff --git a/.php_cs b/.php_cs.dist.php similarity index 73% rename from .php_cs rename to .php_cs.dist.php index 1c4e7d562..bd7148822 100644 --- a/.php_cs +++ b/.php_cs.dist.php @@ -1,9 +1,6 @@ notPath('bootstrap/*') - ->notPath('storage/*') - ->notPath('resources/view/mail/*') ->in([ __DIR__ . '/src', __DIR__ . '/tests', @@ -13,14 +10,14 @@ ->ignoreDotFiles(true) ->ignoreVCS(true); -return PhpCsFixer\Config::create() +return (new PhpCsFixer\Config()) ->setRules([ '@PSR2' => true, 'array_syntax' => ['syntax' => 'short'], - 'ordered_imports' => ['sortAlgorithm' => 'alpha'], + 'ordered_imports' => ['sort_algorithm' => 'alpha'], 'no_unused_imports' => true, 'not_operator_with_successor_space' => true, - 'trailing_comma_in_multiline_array' => true, + 'trailing_comma_in_multiline' => true, 'phpdoc_scalar' => true, 'unary_operator_spaces' => true, 'binary_operator_spaces' => true, @@ -29,9 +26,15 @@ ], 'phpdoc_single_line_var_spacing' => true, 'phpdoc_var_without_name' => true, + 'class_attributes_separation' => [ + 'elements' => [ + 'method' => 'one', + ], + ], 'method_argument_space' => [ 'on_multiline' => 'ensure_fully_multiline', 'keep_multiple_spaces_after_comma' => true, - ] + ], + 'single_trait_insert_per_statement' => true, ]) ->setFinder($finder); From 8660d86a2b4f35007406742a473e33952de9f428 Mon Sep 17 00:00:00 2001 From: drbyte Date: Wed, 2 Jun 2021 00:49:19 +0000 Subject: [PATCH 038/648] Fix styling --- tests/Admin.php | 4 +++- tests/Manager.php | 4 +++- tests/User.php | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/Admin.php b/tests/Admin.php index ede545b9e..b942a15d4 100644 --- a/tests/Admin.php +++ b/tests/Admin.php @@ -11,7 +11,9 @@ class Admin extends Model implements AuthorizableContract, AuthenticatableContract { - use HasRoles, Authorizable, Authenticatable; + use HasRoles; + use Authorizable; + use Authenticatable; protected $fillable = ['email']; diff --git a/tests/Manager.php b/tests/Manager.php index b1be89b7c..000150913 100644 --- a/tests/Manager.php +++ b/tests/Manager.php @@ -11,7 +11,9 @@ class Manager extends Model implements AuthorizableContract, AuthenticatableContract { - use HasRoles, Authorizable, Authenticatable; + use HasRoles; + use Authorizable; + use Authenticatable; protected $fillable = ['email']; diff --git a/tests/User.php b/tests/User.php index dc8e2c568..886fd8fd2 100644 --- a/tests/User.php +++ b/tests/User.php @@ -11,7 +11,9 @@ class User extends Model implements AuthorizableContract, AuthenticatableContract { - use HasRoles, Authorizable, Authenticatable; + use HasRoles; + use Authorizable; + use Authenticatable; protected $fillable = ['email']; From d4ec9f3f4ee3f3b81f8906322b9ab07f68949443 Mon Sep 17 00:00:00 2001 From: David Adi Nugroho Date: Fri, 4 Jun 2021 15:19:42 +0700 Subject: [PATCH 039/648] Update exceptions.md --- docs/advanced-usage/exceptions.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/advanced-usage/exceptions.md b/docs/advanced-usage/exceptions.md index fcc7f9d9e..9e2425c51 100644 --- a/docs/advanced-usage/exceptions.md +++ b/docs/advanced-usage/exceptions.md @@ -3,7 +3,7 @@ title: Exceptions weight: 3 --- -If you need to override exceptions thrown by this package, you can simply use normal [Laravel practices for handling exceptions](https://laravel.com/docs/errors#render-method). +If you need to override exceptions thrown by this package, you can simply use normal [Laravel practices for handling exceptions](https://laravel.com/docs/8.x/errors#rendering-exceptions). An example is shown below for your convenience, but nothing here is specific to this package other than the name of the exception. @@ -12,15 +12,14 @@ You can find all the exceptions added by this package in the code here: https:// **app/Exceptions/Handler.php** ```php -public function render($request, Throwable $exception) + +public function register() { - if ($exception instanceof \Spatie\Permission\Exceptions\UnauthorizedException) { + $this->renderable(function (\Spatie\Permission\Exceptions\UnauthorizedException $e, $request) { return response()->json([ 'responseMessage' => 'You do not have the required authorization.', 'responseStatus' => 403, ]); - } - - return parent::render($request, $exception); + }); } ``` From a6e4122b65094baba7f98df153af0768ef910c85 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 4 Jun 2021 19:47:08 -0400 Subject: [PATCH 040/648] Update CHANGELOG.md --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8296e4fce..0cd58c39c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ All notable changes to `laravel-permission` will be documented in this file +## 4.2.0 - 2021-06-04 +- Add hasExactRoles method #1696 + ## 4.1.0 - 2021-06-01 - Refactor to resolve guard only once during middleware - Refactor service provider by extracting some methods From c740f5b99b75d195eceb791e0dd2a8e3dd2b8bb8 Mon Sep 17 00:00:00 2001 From: Freek Van der Herten Date: Fri, 9 Jul 2021 11:13:12 +0200 Subject: [PATCH 041/648] Update _index.md --- docs/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_index.md b/docs/_index.md index 390f0bb4e..2bf4d0fd1 100644 --- a/docs/_index.md +++ b/docs/_index.md @@ -2,5 +2,5 @@ title: v4 slogan: Associate users with roles and permissions githubUrl: https://github.com/spatie/laravel-permission -branch: master +branch: main --- From 52606d1ada2f2b48cc35add2126afb0adc417da1 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Mon, 19 Jul 2021 18:07:43 -0400 Subject: [PATCH 042/648] Remind readers to use the same role name throughout their app --- docs/basic-usage/new-app.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/basic-usage/new-app.md b/docs/basic-usage/new-app.md index b35d50928..32829562f 100644 --- a/docs/basic-usage/new-app.md +++ b/docs/basic-usage/new-app.md @@ -119,8 +119,9 @@ php artisan migrate:fresh --seed --seeder=PermissionsDemoSeeder ``` ### Grant Super-Admin access -Super-Admins are a common feature. Using the following approach allows that when your Super-Admin user is logged in, all permission-checks in your app which call `can()` or `@can()` will return true. +Super-Admins are a common feature. The following approach allows that when your Super-Admin user is logged in, all permission-checks in your app which call `can()` or `@can()` will return true. +- Create a role named `Super-Admin`. (Or whatever name you wish; but use it consistently just like you must with any role name.) - Add a Gate::before check in your `AuthServiceProvider`: ```diff @@ -130,7 +131,7 @@ Super-Admins are a common feature. Using the following approach allows that when // -+ // Implicitly grant "Super Admin" role all permission checks using can() ++ // Implicitly grant "Super-Admin" role all permission checks using can() + Gate::before(function ($user, $ability) { + if ($user->hasRole('Super-Admin')) { + return true; From 0e39dcca89f01432207c1cbc1661ce06608ff52c Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Mon, 19 Jul 2021 21:18:58 -0400 Subject: [PATCH 043/648] Make role name in seeder match rest of example --- docs/basic-usage/new-app.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/basic-usage/new-app.md b/docs/basic-usage/new-app.md index 32829562f..8952029b7 100644 --- a/docs/basic-usage/new-app.md +++ b/docs/basic-usage/new-app.md @@ -86,7 +86,7 @@ class PermissionsDemoSeeder extends Seeder $role2->givePermissionTo('publish articles'); $role2->givePermissionTo('unpublish articles'); - $role3 = Role::create(['name' => 'super-admin']); + $role3 = Role::create(['name' => 'Super-Admin']); // gets all permissions via Gate::before rule; see AuthServiceProvider // create demo users From 7449dcb77e58a5ce3e884ca26666468acb3c1ea5 Mon Sep 17 00:00:00 2001 From: Aju Chacko Date: Thu, 22 Jul 2021 21:43:45 +0530 Subject: [PATCH 044/648] Update seeding.md --- docs/advanced-usage/seeding.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/advanced-usage/seeding.md b/docs/advanced-usage/seeding.md index e8b1fa266..fcff50ce7 100644 --- a/docs/advanced-usage/seeding.md +++ b/docs/advanced-usage/seeding.md @@ -75,7 +75,7 @@ $permissionsByRole = [ ]; $insertPermissions = fn ($role) => collect($permissionsByRole[$role]) - ->map(fn ($name) => DB::table()->insertGetId(['name' => $name])) + ->map(fn ($name) => DB::table('permissions')->insertGetId(['name' => $name, 'guard_name' => 'web'])) ->toArray(); $permissionIdsByRole = [ From 57b9ddb073fe554d1436b3fb95f69b8560ada220 Mon Sep 17 00:00:00 2001 From: Erik Niebla Date: Tue, 27 Jul 2021 17:30:16 -0500 Subject: [PATCH 045/648] Make cache more than 90% smaller --- src/Models/Permission.php | 8 ++++++++ src/PermissionRegistrar.php | 37 +++++++++++++++++++++++++++++++++---- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/src/Models/Permission.php b/src/Models/Permission.php index 4de94c966..607d2a206 100644 --- a/src/Models/Permission.php +++ b/src/Models/Permission.php @@ -144,4 +144,12 @@ protected static function getPermissions(array $params = []): Collection ->setPermissionClass(static::class) ->getPermissions($params); } + + /** + * Set roles for cache load. + */ + public function setRolesCollection(Collection $roles) + { + $this->relations['roles'] = $roles; + } } diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index 9dca1d5c4..fa6dcb3a2 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -125,13 +125,42 @@ public function getPermissions(array $params = []): Collection { if ($this->permissions === null) { $this->permissions = $this->cache->remember(self::$cacheKey, self::$cacheExpirationTime, function () { - return $this->getPermissionClass() - ->with('roles') - ->get(); + $permissions = $this->getPermissionClass()->select('id', 'name', 'guard_name') + ->with(['roles' => function ($q) { + $q->select('id', 'name', 'guard_name'); + }]) + ->get()->toArray(); + foreach ($permissions as $i => $permission) { + foreach ($permission['roles'] ?? [] as $j => $roles) { + unset($permissions[$i]['roles'][$j]['pivot']); + } + } + return $permissions; }); + + $permissions = new Collection(); + foreach ($this->permissions as $permission_array) { + $permission = new $this->permissionClass(); + foreach ($permission_array as $key => $value) { + if($key == 'roles') continue; + $permission->$key = $value; + } + + $roles = new Collection(); + foreach ($permission_array['roles'] ?? [] as $role_array) { + $role = new $this->roleClass(); + foreach ($role_array as $key => $value) { + $role->$key = $value; + } + $roles->push($role); + } + $permission->setRolesCollection($roles); + $permissions->push($permission); + } + $this->permissions = $permissions; } - $permissions = clone $this->permissions; + $permissions = $this->permissions; foreach ($params as $attr => $value) { $permissions = $permissions->where($attr, $value); From 4764a6a4c80c1c42be96e44e5c9becd129546efb Mon Sep 17 00:00:00 2001 From: Erik Niebla Date: Wed, 28 Jul 2021 11:40:24 -0500 Subject: [PATCH 046/648] Speed up permissions cache lookups --- src/Models/Permission.php | 31 +++++++++++++++++++++++++------ src/PermissionRegistrar.php | 37 ++++++++++++++++++++++++++++--------- 2 files changed, 53 insertions(+), 15 deletions(-) diff --git a/src/Models/Permission.php b/src/Models/Permission.php index 607d2a206..452e5a9d3 100644 --- a/src/Models/Permission.php +++ b/src/Models/Permission.php @@ -36,7 +36,7 @@ public static function create(array $attributes = []) { $attributes['guard_name'] = $attributes['guard_name'] ?? Guard::getDefaultName(static::class); - $permission = static::getPermissions(['name' => $attributes['name'], 'guard_name' => $attributes['guard_name']])->first(); + $permission = static::getPermission(['name' => $attributes['name'], 'guard_name' => $attributes['guard_name']]); if ($permission) { throw PermissionAlreadyExists::create($attributes['name'], $attributes['guard_name']); @@ -85,7 +85,7 @@ public function users(): BelongsToMany public static function findByName(string $name, $guardName = null): PermissionContract { $guardName = $guardName ?? Guard::getDefaultName(static::class); - $permission = static::getPermissions(['name' => $name, 'guard_name' => $guardName])->first(); + $permission = static::getPermission(['name' => $name, 'guard_name' => $guardName]); if (! $permission) { throw PermissionDoesNotExist::create($name, $guardName); } @@ -106,7 +106,7 @@ public static function findByName(string $name, $guardName = null): PermissionCo public static function findById(int $id, $guardName = null): PermissionContract { $guardName = $guardName ?? Guard::getDefaultName(static::class); - $permission = static::getPermissions(['id' => $id, 'guard_name' => $guardName])->first(); + $permission = static::getPermission(['id' => $id, 'guard_name' => $guardName]); if (! $permission) { throw PermissionDoesNotExist::withId($id, $guardName); @@ -126,7 +126,7 @@ public static function findById(int $id, $guardName = null): PermissionContract public static function findOrCreate(string $name, $guardName = null): PermissionContract { $guardName = $guardName ?? Guard::getDefaultName(static::class); - $permission = static::getPermissions(['name' => $name, 'guard_name' => $guardName])->first(); + $permission = static::getPermission(['name' => $name, 'guard_name' => $guardName]); if (! $permission) { return static::query()->create(['name' => $name, 'guard_name' => $guardName]); @@ -137,16 +137,35 @@ public static function findOrCreate(string $name, $guardName = null): Permission /** * Get the current cached permissions. + * + * @param array $params + * @param bool $onlyOne + * + * @return \Illuminate\Database\Eloquent\Collection */ - protected static function getPermissions(array $params = []): Collection + protected static function getPermissions(array $params = [], bool $onlyOne = false): Collection { return app(PermissionRegistrar::class) ->setPermissionClass(static::class) - ->getPermissions($params); + ->getPermissions($params, $onlyOne); + } + + /** + * Get the current cached first permission. + * + * @param array $params + * + * @return \Spatie\Permission\Contracts\Permission + */ + protected static function getPermission(array $params = []): ?PermissionContract + { + return static::getPermissions($params, true)->first(); } /** * Set roles for cache load. + * + * @param \Illuminate\Database\Eloquent\Collection $roles */ public function setRolesCollection(Collection $roles) { diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index fa6dcb3a2..56bae28cb 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -115,13 +115,10 @@ public function clearClassPermissions() } /** - * Get the permissions based on the passed params. - * - * @param array $params - * - * @return \Illuminate\Database\Eloquent\Collection + * Load permissions from cache + * This get cache and turns array into \Illuminate\Database\Eloquent\Collection */ - public function getPermissions(array $params = []): Collection + private function loadPermissions() { if ($this->permissions === null) { $this->permissions = $this->cache->remember(self::$cacheKey, self::$cacheExpirationTime, function () { @@ -159,11 +156,33 @@ public function getPermissions(array $params = []): Collection } $this->permissions = $permissions; } + } - $permissions = $this->permissions; + /** + * Get the permissions based on the passed params. + * + * @param array $params + * @param bool $onlyOne + * + * @return \Illuminate\Database\Eloquent\Collection + */ + public function getPermissions(array $params = [], bool $onlyOne = false): Collection + { + $this->loadPermissions(); + + $method = $onlyOne ? 'first' : 'filter'; + + $permissions = $this->permissions->$method(static function ($permission) use ($params) { + foreach ($params as $attr => $value) { + if ($permission->getAttribute($attr) != $value) { + return false; + } + } + return true; + }); - foreach ($params as $attr => $value) { - $permissions = $permissions->where($attr, $value); + if ($onlyOne) { + $permissions = new Collection($permissions ? [$permissions] : []); } return $permissions; From 8ac7f7bd76b7ef3cf58e26cca429bbc73fcd238b Mon Sep 17 00:00:00 2001 From: Erik Niebla Date: Thu, 29 Jul 2021 11:13:23 -0500 Subject: [PATCH 047/648] Better readability and performance, easy transition from Collection to array --- config/permission.php | 11 ----------- src/Models/Permission.php | 36 ++++++++++++++++++++++++++++++++---- src/Models/Role.php | 28 ++++++++++++++++++++++++++++ src/PermissionRegistrar.php | 37 +++++++++---------------------------- 4 files changed, 69 insertions(+), 43 deletions(-) diff --git a/config/permission.php b/config/permission.php index 1a4207e6c..c306a8d50 100644 --- a/config/permission.php +++ b/config/permission.php @@ -121,17 +121,6 @@ 'key' => 'spatie.permission.cache', - /* - * When checking for a permission against a model by passing a Permission - * instance to the check, this key determines what attribute on the - * Permissions model is used to cache against. - * - * Ideally, this should match your preferred way of checking permissions, eg: - * `$user->can('view-posts')` would be 'name'. - */ - - 'model_key' => 'name', - /* * You may optionally indicate a specific cache driver to use for permission and * role caching using any of the `store` drivers listed in the cache.php config diff --git a/src/Models/Permission.php b/src/Models/Permission.php index 452e5a9d3..37f72c3aa 100644 --- a/src/Models/Permission.php +++ b/src/Models/Permission.php @@ -163,12 +163,40 @@ protected static function getPermission(array $params = []): ?PermissionContract } /** - * Set roles for cache load. + * Fill model from array. * - * @param \Illuminate\Database\Eloquent\Collection $roles + * @param array $attributes */ - public function setRolesCollection(Collection $roles) + protected function fillModelFromArray(array $attributes) { - $this->relations['roles'] = $roles; + if (isset($attributes['roles'])) { + $roleClass = app(PermissionRegistrar::class)->getRoleClass(); + $this->relations['roles'] = new Collection(); + + foreach ($attributes['roles'] as $value) { + $this->relations['roles']->push($roleClass::getModelFromArray($value)); + } + unset($attributes['roles']); + } + + $this->attributes = $attributes; + if (isset($attributes['id'])) { + $this->exists = true; + $this->original['id'] = $attributes['id']; + } + return $this; + } + + /** + * Get model from array. + * + * @param array $attributes + * + * @return \Spatie\Permission\Contracts\Permission + */ + public static function getModelFromArray(array $attributes): ?PermissionContract + { + $permission = new static; + return $permission->fillModelFromArray($attributes); } } diff --git a/src/Models/Role.php b/src/Models/Role.php index 5fd3177fc..a9bbe2d43 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -157,4 +157,32 @@ public function hasPermissionTo($permission): bool return $this->permissions->contains('id', $permission->id); } + + /** + * Fill model from array. + * + * @param array $attributes + */ + protected function fillModelFromArray(array $attributes) + { + $this->attributes = $attributes; + if (isset($attributes['id'])) { + $this->exists = true; + $this->original['id'] = $attributes['id']; + } + return $this; + } + + /** + * Get model from array. + * + * @param array $attributes + * + * @return \Spatie\Permission\Contracts\Role + */ + public static function getModelFromArray(array $attributes): ?RoleContract + { + $roles = new static; + return $roles->fillModelFromArray($attributes); + } } diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index 56bae28cb..70d344353 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -32,9 +32,6 @@ class PermissionRegistrar /** @var string */ public static $cacheKey; - /** @var string */ - public static $cacheModelKey; - /** * PermissionRegistrar constructor. * @@ -51,17 +48,17 @@ public function __construct(CacheManager $cacheManager) public function initializeCache() { - self::$cacheExpirationTime = config('permission.cache.expiration_time', config('permission.cache_expiration_time')); + self::$cacheExpirationTime = config('permission.cache.expiration_time') ?: \DateInterval::createFromDateString('24 hours'); self::$cacheKey = config('permission.cache.key'); - self::$cacheModelKey = config('permission.cache.model_key'); $this->cache = $this->getCacheStoreFromConfig(); } protected function getCacheStoreFromConfig(): \Illuminate\Contracts\Cache\Repository { - // the 'default' fallback here is from the permission.php config file, where 'default' means to use config(cache.default) + // the 'default' fallback here is from the permission.php config file, + // where 'default' means to use config(cache.default) $cacheDriver = config('permission.cache.store', 'default'); // when 'default' is specified, no action is required since we already have the default instance @@ -123,9 +120,7 @@ private function loadPermissions() if ($this->permissions === null) { $this->permissions = $this->cache->remember(self::$cacheKey, self::$cacheExpirationTime, function () { $permissions = $this->getPermissionClass()->select('id', 'name', 'guard_name') - ->with(['roles' => function ($q) { - $q->select('id', 'name', 'guard_name'); - }]) + ->with('roles:id,name,guard_name') ->get()->toArray(); foreach ($permissions as $i => $permission) { foreach ($permission['roles'] ?? [] as $j => $roles) { @@ -134,27 +129,13 @@ private function loadPermissions() } return $permissions; }); - - $permissions = new Collection(); - foreach ($this->permissions as $permission_array) { - $permission = new $this->permissionClass(); - foreach ($permission_array as $key => $value) { - if($key == 'roles') continue; - $permission->$key = $value; - } - - $roles = new Collection(); - foreach ($permission_array['roles'] ?? [] as $role_array) { - $role = new $this->roleClass(); - foreach ($role_array as $key => $value) { - $role->$key = $value; - } - $roles->push($role); + if (is_array($this->permissions)) { + $permissions = new Collection(); + foreach ($this->permissions as $value) { + $permissions->push($this->permissionClass::getModelFromArray($value)); } - $permission->setRolesCollection($roles); - $permissions->push($permission); + $this->permissions = $permissions; } - $this->permissions = $permissions; } } From 34ad214677ec6471782b876dcfdfed0bf62bae9a Mon Sep 17 00:00:00 2001 From: drbyte Date: Tue, 17 Aug 2021 18:36:09 +0000 Subject: [PATCH 048/648] Fix styling --- src/Models/Permission.php | 2 ++ src/Models/Role.php | 2 ++ src/PermissionRegistrar.php | 2 ++ 3 files changed, 6 insertions(+) diff --git a/src/Models/Permission.php b/src/Models/Permission.php index 37f72c3aa..5f3e060f8 100644 --- a/src/Models/Permission.php +++ b/src/Models/Permission.php @@ -184,6 +184,7 @@ protected function fillModelFromArray(array $attributes) $this->exists = true; $this->original['id'] = $attributes['id']; } + return $this; } @@ -197,6 +198,7 @@ protected function fillModelFromArray(array $attributes) public static function getModelFromArray(array $attributes): ?PermissionContract { $permission = new static; + return $permission->fillModelFromArray($attributes); } } diff --git a/src/Models/Role.php b/src/Models/Role.php index a9bbe2d43..6810b5ae5 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -170,6 +170,7 @@ protected function fillModelFromArray(array $attributes) $this->exists = true; $this->original['id'] = $attributes['id']; } + return $this; } @@ -183,6 +184,7 @@ protected function fillModelFromArray(array $attributes) public static function getModelFromArray(array $attributes): ?RoleContract { $roles = new static; + return $roles->fillModelFromArray($attributes); } } diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index 70d344353..527f017c9 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -127,6 +127,7 @@ private function loadPermissions() unset($permissions[$i]['roles'][$j]['pivot']); } } + return $permissions; }); if (is_array($this->permissions)) { @@ -159,6 +160,7 @@ public function getPermissions(array $params = [], bool $onlyOne = false): Colle return false; } } + return true; }); From 78eaa5e06c313a9f3672a7571b4d83b913721b72 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 17 Aug 2021 14:37:17 -0400 Subject: [PATCH 049/648] Update CHANGELOG.md --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0cd58c39c..78d91fd05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ All notable changes to `laravel-permission` will be documented in this file +## 4.3.0 - 2021-08-17 +- Speed up permissions cache lookups, and make cache smaller #1799 + ## 4.2.0 - 2021-06-04 - Add hasExactRoles method #1696 From d38849e928d6e4396fed36d9bf2f9597ac9ce7b9 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 17 Aug 2021 18:13:04 -0400 Subject: [PATCH 050/648] Update bug_report.md --- .github/ISSUE_TEMPLATE/bug_report.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index f4c3a68a3..328f945dd 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -8,7 +8,7 @@ assignees: '' --- **Before creating a new bug report** -Please check if there isn't a similar issue on [the issue tracker](https://github.com/spatie/laravel-persmission/issues) or in [the discussions](https://github.com/spatie/laravel-permission/discussions). +Please check if there isn't a similar issue on [the issue tracker](https://github.com/spatie/laravel-permission/issues) or in [the discussions](https://github.com/spatie/laravel-permission/discussions). **Describe the bug** A clear and concise description of what the bug is. @@ -26,12 +26,18 @@ Database version: **To Reproduce** Steps to reproduce the behavior: +Here is my example code and/or tests showing the problem in my app: + +**Example Application** +Here is a link to my Github repo containing a minimal Laravel application which shows my problem: + **Expected behavior** A clear and concise description of what you expected to happen. -**Desktop (please complete the following information):** + **Additional context** +Add any other context about the problem here. + +**Environment (please complete the following information, because it helps us investigate better):** - OS: [e.g. macOS] - Version [e.g. 22] - **Additional context** -Add any other context about the problem here. From 5e13d582a1b9ced7e4aa74ecf115b5f1b2e35383 Mon Sep 17 00:00:00 2001 From: gitetsu Date: Wed, 18 Aug 2021 11:22:43 +0900 Subject: [PATCH 051/648] Update docblocks scopeRole/assignRole/removeRole/syncRoles can accept an integer. --- src/Traits/HasRoles.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index f109fb0b1..c9d816692 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -52,7 +52,7 @@ public function roles(): BelongsToMany * Scope the model query to certain roles only. * * @param \Illuminate\Database\Eloquent\Builder $query - * @param string|array|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection $roles + * @param string|int|array|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection $roles * @param string $guard * * @return \Illuminate\Database\Eloquent\Builder @@ -86,7 +86,7 @@ public function scopeRole(Builder $query, $roles, $guard = null): Builder /** * Assign the given role to the model. * - * @param array|string|\Spatie\Permission\Contracts\Role ...$roles + * @param array|string|int|\Spatie\Permission\Contracts\Role ...$roles * * @return $this */ @@ -134,7 +134,7 @@ function ($object) use ($roles, $model) { /** * Revoke the given role from the model. * - * @param string|\Spatie\Permission\Contracts\Role $role + * @param string|int|\Spatie\Permission\Contracts\Role $role */ public function removeRole($role) { @@ -150,7 +150,7 @@ public function removeRole($role) /** * Remove all current roles and set the given ones. * - * @param array|\Spatie\Permission\Contracts\Role|string ...$roles + * @param array|\Spatie\Permission\Contracts\Role|string|int ...$roles * * @return $this */ From 5b3ea6b0e3ed08a0efc8d9b016fb0750fda7ccac Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 17 Aug 2021 23:56:34 -0400 Subject: [PATCH 052/648] Update version link --- docs/introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/introduction.md b/docs/introduction.md index f3dc122d3..17baa86d2 100644 --- a/docs/introduction.md +++ b/docs/introduction.md @@ -17,7 +17,7 @@ $user->assignRole('writer'); $role->givePermissionTo('edit articles'); ``` -If you're using multiple guards we've got you covered as well. Every guard will have its own set of permissions and roles that can be assigned to the guard's users. Read about it in the [using multiple guards](https://docs.spatie.be/laravel-permission/v3/basic-usage/multiple-guards/) section. +If you're using multiple guards we've got you covered as well. Every guard will have its own set of permissions and roles that can be assigned to the guard's users. Read about it in the [using multiple guards](https://docs.spatie.be/laravel-permission/v4/basic-usage/multiple-guards/) section. Because all permissions will be registered on [Laravel's gate](https://laravel.com/docs/authorization), you can check if a user has a permission with Laravel's default `can` function: From 479d1ed37f29477332117ff9a9e27826994d5b27 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Thu, 19 Aug 2021 19:57:45 -0400 Subject: [PATCH 053/648] Rename some tests --- tests/PermissionMiddlewareTest.php | 4 ++-- tests/RoleMiddlewareTest.php | 4 ++-- tests/RoleOrPermissionMiddlewareTest.php | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/PermissionMiddlewareTest.php b/tests/PermissionMiddlewareTest.php index fa187f78f..55e3ff7a6 100644 --- a/tests/PermissionMiddlewareTest.php +++ b/tests/PermissionMiddlewareTest.php @@ -177,7 +177,7 @@ public function use_not_existing_custom_guard_in_permission() } /** @test */ - public function user_can_not_access_permission_with_guard_admin_while_using_default_guard() + public function user_can_not_access_permission_with_guard_admin_while_login_using_default_guard() { Auth::login($this->testUser); @@ -190,7 +190,7 @@ public function user_can_not_access_permission_with_guard_admin_while_using_defa } /** @test */ - public function user_can_access_permission_with_guard_admin_while_using_default_guard() + public function user_can_access_permission_with_guard_admin_while_login_using_admin_guard() { Auth::guard('admin')->login($this->testAdmin); diff --git a/tests/RoleMiddlewareTest.php b/tests/RoleMiddlewareTest.php index 991e73597..70fd27e86 100644 --- a/tests/RoleMiddlewareTest.php +++ b/tests/RoleMiddlewareTest.php @@ -143,7 +143,7 @@ public function use_not_existing_custom_guard_in_role() } /** @test */ - public function user_can_not_access_role_with_guard_admin_while_using_default_guard() + public function user_can_not_access_role_with_guard_admin_while_login_using_default_guard() { Auth::login($this->testUser); @@ -156,7 +156,7 @@ public function user_can_not_access_role_with_guard_admin_while_using_default_gu } /** @test */ - public function user_can_access_role_with_guard_admin_while_using_default_guard() + public function user_can_access_role_with_guard_admin_while_login_using_admin_guard() { Auth::guard('admin')->login($this->testAdmin); diff --git a/tests/RoleOrPermissionMiddlewareTest.php b/tests/RoleOrPermissionMiddlewareTest.php index da41c13fd..00e81b290 100644 --- a/tests/RoleOrPermissionMiddlewareTest.php +++ b/tests/RoleOrPermissionMiddlewareTest.php @@ -96,7 +96,7 @@ public function use_not_existing_custom_guard_in_role_or_permission() } /** @test */ - public function user_can_not_access_permission_or_role_with_guard_admin_while_using_default_guard() + public function user_can_not_access_permission_or_role_with_guard_admin_while_login_using_default_guard() { Auth::login($this->testUser); @@ -110,7 +110,7 @@ public function user_can_not_access_permission_or_role_with_guard_admin_while_us } /** @test */ - public function user_can_access_permission_or_role_with_guard_admin_while_using_default_guard() + public function user_can_access_permission_or_role_with_guard_admin_while_login_using_admin_guard() { Auth::guard('admin')->login($this->testAdmin); From f18c4f0cf42b81ff98c204278af84354f3efb374 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Thu, 19 Aug 2021 20:14:11 -0400 Subject: [PATCH 054/648] Historically, since v2.0.0, the "detected guard_name" that was applied during lookups was detected from the "first possible defined guard" for that model, even if the "current" guard wasn't "first in the list". This has led to some confusion when expecting that the current user's guard would actually be used. This PR changes things to **use the current user's guard** first, assuming it's in the list of potential guards acceptable for the current User model. After that it will fallback to the old behavior of selecting the first available matchable guard for the current User model. THIS IS A BREAKING CHANGE, so be sure to test all authorization aspects of your application to ensure desired behavior. You MAY also be able to remove some old complex monkey-patching that you might have done to work around this issue in prior versions. Fixes #1682 Fixes #1608 Fixes #1384 Fixes #1515 Fixes #1516 --- src/Guard.php | 34 +++++++++++++++++++++++----- tests/HasPermissionsTest.php | 36 ++++++++++++++++++++++++++++++ tests/PermissionMiddlewareTest.php | 18 +++++++-------- tests/TestCase.php | 16 +++++++++++++ 4 files changed, 90 insertions(+), 14 deletions(-) diff --git a/src/Guard.php b/src/Guard.php index 9875aca3e..41d802c10 100644 --- a/src/Guard.php +++ b/src/Guard.php @@ -8,14 +8,16 @@ class Guard { /** - * return collection of (guard_name) property if exist on class or object - * otherwise will return collection of guards names that exists in config/auth.php. + * Return a collection of guard names suitable for the $model, + * as indicated by the presence of a $guard_name property or a guardName() method on the model. * * @param string|Model $model model class object or name * @return Collection */ public static function getNames($model): Collection { + $class = is_object($model) ? get_class($model) : $model; + if (is_object($model)) { if (\method_exists($model, 'guardName')) { $guardName = $model->guardName(); @@ -25,8 +27,6 @@ public static function getNames($model): Collection } if (! isset($guardName)) { - $class = is_object($model) ? get_class($model) : $model; - $guardName = (new \ReflectionClass($class))->getDefaultProperties()['guard_name'] ?? null; } @@ -34,6 +34,23 @@ public static function getNames($model): Collection return collect($guardName); } + return self::getConfigAuthGuards($class); + } + + /** + * Get list of relevant guards for the $class model based on config(auth) settings. + * + * Lookup flow: + * - get names of models for guards defined in auth.guards where a provider is set + * - filter for provider models matching the model $class being checked (important for Lumen) + * - keys() gives just the names of the matched guards + * - return collection of guard names + * + * @param string $class + * @return Collection + */ + protected static function getConfigAuthGuards(string $class): Collection + { return collect(config('auth.guards')) ->map(function ($guard) { if (! isset($guard['provider'])) { @@ -58,6 +75,13 @@ public static function getDefaultName($class): string { $default = config('auth.defaults.guard'); - return static::getNames($class)->first() ?: $default; + $possible_guards = static::getNames($class); + + // return current-detected auth.defaults.guard if it matches one of those that have been checked + if ($possible_guards->contains($default)) { + return $default; + } + + return $possible_guards->first() ?: $default; } } diff --git a/tests/HasPermissionsTest.php b/tests/HasPermissionsTest.php index c772bda47..b50646aca 100644 --- a/tests/HasPermissionsTest.php +++ b/tests/HasPermissionsTest.php @@ -529,4 +529,40 @@ public function it_can_check_if_there_is_any_of_the_direct_permissions_given() $this->assertTrue($this->testUser->hasAnyDirectPermission('edit-news', 'edit-blog')); $this->assertFalse($this->testUser->hasAnyDirectPermission('edit-blog', 'Edit News', ['Edit News'])); } + + /** @test */ + public function it_can_check_permission_based_on_logged_in_user_guard() + { + $this->testUser->givePermissionTo(app(Permission::class)::create([ + 'name' => 'do_that', + 'guard_name' => 'api', + ])); + $response = $this->actingAs($this->testUser, 'api') + ->json('GET', '/check-api-guard-permission'); + $response->assertJson([ + 'status' => true, + ]); + } + + /** @test */ + public function it_can_reject_permission_based_on_logged_in_user_guard() + { + $unassignedPermission = app(Permission::class)::create([ + 'name' => 'do_that', + 'guard_name' => 'api', + ]); + + $assignedPermission = app(Permission::class)::create([ + 'name' => 'do_that', + 'guard_name' => 'web', + ]); + + $this->testUser->givePermissionTo($assignedPermission); + $response = $this->withExceptionHandling() + ->actingAs($this->testUser, 'api') + ->json('GET', '/check-api-guard-permission'); + $response->assertJson([ + 'status' => false, + ]); + } } diff --git a/tests/PermissionMiddlewareTest.php b/tests/PermissionMiddlewareTest.php index 55e3ff7a6..fb49073d4 100644 --- a/tests/PermissionMiddlewareTest.php +++ b/tests/PermissionMiddlewareTest.php @@ -35,36 +35,36 @@ public function a_user_cannot_access_a_route_protected_by_the_permission_middlew { // These permissions are created fresh here in reverse order of guard being applied, so they are not "found first" in the db lookup when matching app(Permission::class)->create(['name' => 'admin-permission2', 'guard_name' => 'web']); - app(Permission::class)->create(['name' => 'admin-permission2', 'guard_name' => 'admin']); + $p1 = app(Permission::class)->create(['name' => 'admin-permission2', 'guard_name' => 'admin']); app(Permission::class)->create(['name' => 'edit-articles2', 'guard_name' => 'admin']); - app(Permission::class)->create(['name' => 'edit-articles2', 'guard_name' => 'web']); + $p2 = app(Permission::class)->create(['name' => 'edit-articles2', 'guard_name' => 'web']); - Auth::login($this->testAdmin); + Auth::guard('admin')->login($this->testAdmin); - $this->testAdmin->givePermissionTo('admin-permission2'); + $this->testAdmin->givePermissionTo($p1); $this->assertEquals( 200, - $this->runMiddleware($this->permissionMiddleware, 'admin-permission2') + $this->runMiddleware($this->permissionMiddleware, 'admin-permission2', 'admin') ); $this->assertEquals( 403, - $this->runMiddleware($this->permissionMiddleware, 'edit-articles2') + $this->runMiddleware($this->permissionMiddleware, 'edit-articles2', 'admin') ); Auth::login($this->testUser); - $this->testUser->givePermissionTo('edit-articles2'); + $this->testUser->givePermissionTo($p2); $this->assertEquals( 200, - $this->runMiddleware($this->permissionMiddleware, 'edit-articles2') + $this->runMiddleware($this->permissionMiddleware, 'edit-articles2', 'web') ); $this->assertEquals( 403, - $this->runMiddleware($this->permissionMiddleware, 'admin-permission2') + $this->runMiddleware($this->permissionMiddleware, 'admin-permission2', 'web') ); } diff --git a/tests/TestCase.php b/tests/TestCase.php index a789cc882..9a7069558 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -3,7 +3,9 @@ namespace Spatie\Permission\Test; use Illuminate\Database\Schema\Blueprint; +use Illuminate\Http\Request; use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Schema; use Orchestra\Testbench\TestCase as Orchestra; use Spatie\Permission\Contracts\Permission; @@ -45,6 +47,8 @@ public function setUp(): void $this->testAdmin = Admin::first(); $this->testAdminRole = app(Role::class)->find(3); $this->testAdminPermission = app(Permission::class)->find(4); + + $this->setUpRoutes(); } /** @@ -142,4 +146,16 @@ public function createCacheTable() $table->integer('expiration'); }); } + + /** + * Create routes to test authentication with guards. + */ + public function setUpRoutes(): void + { + Route::middleware('auth:api')->get('/check-api-guard-permission', function (Request $request) { + return [ + 'status' => $request->user()->hasPermissionTo('do_that'), + ]; + }); + } } From 3cc2cbc62766d2d2e9c2f3e5be787f10d9ee74a2 Mon Sep 17 00:00:00 2001 From: Erik Niebla Date: Fri, 20 Aug 2021 09:17:54 -0500 Subject: [PATCH 055/648] Avoid sync again other objects on Eloquent::saved --- src/Traits/HasPermissions.php | 3 +++ src/Traits/HasRoles.php | 3 +++ tests/HasPermissionsTest.php | 10 ++++++++++ tests/HasRolesTest.php | 10 ++++++++++ 4 files changed, 26 insertions(+) diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index 4b7d6a3a5..c1401536f 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -348,6 +348,9 @@ public function givePermissionTo(...$permissions) $class::saved( function ($object) use ($permissions, $model) { + if ($model->getKey() != $object->getKey()) { + return; + } $model->permissions()->sync($permissions, false); $model->load('permissions'); } diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index c9d816692..3d775ad2a 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -120,6 +120,9 @@ public function assignRole(...$roles) $class::saved( function ($object) use ($roles, $model) { + if ($model->getKey() != $object->getKey()) { + return; + } $model->roles()->sync($roles, false); $model->load('roles'); } diff --git a/tests/HasPermissionsTest.php b/tests/HasPermissionsTest.php index c772bda47..87be413b7 100644 --- a/tests/HasPermissionsTest.php +++ b/tests/HasPermissionsTest.php @@ -474,13 +474,18 @@ public function calling_givePermissionTo_before_saving_object_doesnt_interfere_w $user2 = new User(['email' => 'test2@user.com']); $user2->givePermissionTo('edit-articles'); + + \DB::enableQueryLog(); $user2->save(); + $querys = \DB::getQueryLog(); + \DB::disableQueryLog(); $this->assertTrue($user->fresh()->hasPermissionTo('edit-news')); $this->assertFalse($user->fresh()->hasPermissionTo('edit-articles')); $this->assertTrue($user2->fresh()->hasPermissionTo('edit-articles')); $this->assertFalse($user2->fresh()->hasPermissionTo('edit-news')); + $this->assertSame(4, count($querys)); //avoid unnecessary sync } /** @test */ @@ -492,13 +497,18 @@ public function calling_syncPermissions_before_saving_object_doesnt_interfere_wi $user2 = new User(['email' => 'test2@user.com']); $user2->syncPermissions('edit-articles'); + + \DB::enableQueryLog(); $user2->save(); + $querys = \DB::getQueryLog(); + \DB::disableQueryLog(); $this->assertTrue($user->fresh()->hasPermissionTo('edit-news')); $this->assertFalse($user->fresh()->hasPermissionTo('edit-articles')); $this->assertTrue($user2->fresh()->hasPermissionTo('edit-articles')); $this->assertFalse($user2->fresh()->hasPermissionTo('edit-news')); + $this->assertSame(4, count($querys)); //avoid unnecessary sync } /** @test */ diff --git a/tests/HasRolesTest.php b/tests/HasRolesTest.php index 661bf2f7d..405e6eac1 100644 --- a/tests/HasRolesTest.php +++ b/tests/HasRolesTest.php @@ -245,13 +245,18 @@ public function calling_syncRoles_before_saving_object_doesnt_interfere_with_oth $user2 = new User(['email' => 'admin@user.com']); $user2->syncRoles('testRole2'); + + \DB::enableQueryLog(); $user2->save(); + $querys = \DB::getQueryLog(); + \DB::disableQueryLog(); $this->assertTrue($user->fresh()->hasRole('testRole')); $this->assertFalse($user->fresh()->hasRole('testRole2')); $this->assertTrue($user2->fresh()->hasRole('testRole2')); $this->assertFalse($user2->fresh()->hasRole('testRole')); + $this->assertSame(4, count($querys)); //avoid unnecessary sync } /** @test */ @@ -263,13 +268,18 @@ public function calling_assignRole_before_saving_object_doesnt_interfere_with_ot $admin_user = new User(['email' => 'admin@user.com']); $admin_user->assignRole('testRole2'); + + \DB::enableQueryLog(); $admin_user->save(); + $querys = \DB::getQueryLog(); + \DB::disableQueryLog(); $this->assertTrue($user->fresh()->hasRole('testRole')); $this->assertFalse($user->fresh()->hasRole('testRole2')); $this->assertTrue($admin_user->fresh()->hasRole('testRole2')); $this->assertFalse($admin_user->fresh()->hasRole('testRole')); + $this->assertSame(4, count($querys)); //avoid unnecessary sync } /** @test */ From 9d012d730912d0bfe63e74e44e705a0a8a114c68 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Sun, 22 Aug 2021 13:46:13 -0400 Subject: [PATCH 056/648] Add Eloquent Collection note regarding roles --- docs/basic-usage/role-permissions.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/docs/basic-usage/role-permissions.md b/docs/basic-usage/role-permissions.md index 8d1cb4350..c2c8fb764 100644 --- a/docs/basic-usage/role-permissions.md +++ b/docs/basic-usage/role-permissions.md @@ -88,9 +88,26 @@ The `givePermissionTo` and `revokePermissionTo` functions can accept a string or a `Spatie\Permission\Models\Permission` object. -**Permissions are inherited from roles automatically.** +**NOTE: Permissions are inherited from roles automatically.** +### What Permissions Does A Role Have? + +The `permissions` property on any given role returns a collection with all the related permission objects. This collection can respond to usual Eloquent Collection operations, such as count, sort, etc. + +```php +// get collection +$role->permissions; + +// return only the permission names: +$role->permissions->pluck('name'); + +// count the number of permissions assigned to a role +count($role->permissions); +// or +$role->permissions->count(); +``` + ## Assigning Direct Permissions To A User Additionally, individual permissions can be assigned to the user too. From 361af88bde9117bdc6b29556704d623bfb51a824 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Sun, 22 Aug 2021 19:27:26 -0400 Subject: [PATCH 057/648] Update sed for 8.55 compatibility --- docs/basic-usage/new-app.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/basic-usage/new-app.md b/docs/basic-usage/new-app.md index 8952029b7..03762bd4b 100644 --- a/docs/basic-usage/new-app.md +++ b/docs/basic-usage/new-app.md @@ -35,8 +35,8 @@ git commit -m "Add Spatie Laravel Permissions package" php artisan migrate:fresh # Add `HasRoles` trait to User model -sed -i '' $'s/use Notifiable;/use Notifiable;\\\n use \\\\Spatie\\\\Permission\\\\Traits\\\\HasRoles;/' app/User.php sed -i '' $'s/use HasFactory, Notifiable;/use HasFactory, Notifiable;\\\n use \\\\Spatie\\\\Permission\\\\Traits\\\\HasRoles;/' app/Models/User.php +sed -i '' $'s/use HasApiTokens, HasFactory, Notifiable;/use HasApiTokens, HasFactory, Notifiable;\\\n use \\\\Spatie\\\\Permission\\\\Traits\\\\HasRoles;/' app/Models/User.php git add . && git commit -m "Add HasRoles trait" # Add Laravel's basic auth scaffolding From dfcd5c745e31d4ca1daaf0213c904c3afb7ecc7c Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Sun, 22 Aug 2021 19:44:19 -0400 Subject: [PATCH 058/648] master->main --- docs/basic-usage/new-app.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/basic-usage/new-app.md b/docs/basic-usage/new-app.md index 03762bd4b..449955d9d 100644 --- a/docs/basic-usage/new-app.md +++ b/docs/basic-usage/new-app.md @@ -156,7 +156,7 @@ To share your app on Github for easy collaboration: ```sh git remote add origin git@github.com:YOURUSERNAME/REPONAME.git -git push -u origin master +git push -u origin main ``` The above only needs to be done once. @@ -165,7 +165,7 @@ The above only needs to be done once. ```sh git add . git commit -m "Explain what your commit is about here" -git push origin master +git push origin main ``` Repeat the above process whenever you change code that you want to share. From ccb1a90b89408a355e0faf6910cc007de1cd70ac Mon Sep 17 00:00:00 2001 From: Erik Niebla Date: Mon, 23 Aug 2021 12:44:31 -0500 Subject: [PATCH 059/648] Customised pivots instead of `role_id`,`permission_id` --- .github/workflows/run-tests-L7.yml | 4 ++-- .github/workflows/run-tests-L8.yml | 4 ++-- config/permission.php | 5 ++++ .../create_permission_tables.php.stub | 23 ++++++++++--------- src/Models/Permission.php | 6 ++--- src/Models/Role.php | 7 +++--- src/PermissionRegistrar.php | 9 ++++++++ src/Traits/HasPermissions.php | 2 +- src/Traits/HasRoles.php | 2 +- tests/TestCase.php | 3 ++- 10 files changed, 41 insertions(+), 24 deletions(-) diff --git a/.github/workflows/run-tests-L7.yml b/.github/workflows/run-tests-L7.yml index f601ce491..bde4ee667 100644 --- a/.github/workflows/run-tests-L7.yml +++ b/.github/workflows/run-tests-L7.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: - fail-fast: true + fail-fast: false matrix: php: [8.0, 7.4, 7.3, 7.2] laravel: [7.*, 6.*] @@ -39,7 +39,7 @@ jobs: - name: Install dependencies run: | - composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" "symfony/console:>=4.3.4" --no-interaction --no-update + composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" "symfony/console:>=4.3.4" "mockery/mockery:^1.3.2" --no-interaction --no-update composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction - name: Execute tests diff --git a/.github/workflows/run-tests-L8.yml b/.github/workflows/run-tests-L8.yml index d8844b560..de738eb00 100644 --- a/.github/workflows/run-tests-L8.yml +++ b/.github/workflows/run-tests-L8.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: - fail-fast: true + fail-fast: false matrix: php: [8.0, 7.4, 7.3] laravel: [8.*] @@ -37,7 +37,7 @@ jobs: - name: Install dependencies run: | - composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" "symfony/console:>=4.3.4" --no-interaction --no-update + composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" "symfony/console:>=4.3.4" "mockery/mockery:^1.3.2" --no-interaction --no-update composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction - name: Execute tests diff --git a/config/permission.php b/config/permission.php index c306a8d50..7e31a6c2a 100644 --- a/config/permission.php +++ b/config/permission.php @@ -72,6 +72,11 @@ ], 'column_names' => [ + /* + * Change this if you want to name the related pivots other than defaults + */ + 'role_pivot_key' => null, //default 'role_id', + 'permission_pivot_key' => null, //default 'permission_id', /* * Change this if you want to name the related model primary key other than diff --git a/database/migrations/create_permission_tables.php.stub b/database/migrations/create_permission_tables.php.stub index edf92e7a1..5ad2571a3 100644 --- a/database/migrations/create_permission_tables.php.stub +++ b/database/migrations/create_permission_tables.php.stub @@ -3,6 +3,7 @@ use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; +use Spatie\Permission\PermissionRegistrar; class CreatePermissionTables extends Migration { @@ -39,52 +40,52 @@ class CreatePermissionTables extends Migration }); Schema::create($tableNames['model_has_permissions'], function (Blueprint $table) use ($tableNames, $columnNames) { - $table->unsignedBigInteger('permission_id'); + $table->unsignedBigInteger(PermissionRegistrar::$pivotPermission); $table->string('model_type'); $table->unsignedBigInteger($columnNames['model_morph_key']); $table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index'); - $table->foreign('permission_id') + $table->foreign(PermissionRegistrar::$pivotPermission) ->references('id') ->on($tableNames['permissions']) ->onDelete('cascade'); - $table->primary(['permission_id', $columnNames['model_morph_key'], 'model_type'], + $table->primary([PermissionRegistrar::$pivotPermission, $columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_permission_model_type_primary'); }); Schema::create($tableNames['model_has_roles'], function (Blueprint $table) use ($tableNames, $columnNames) { - $table->unsignedBigInteger('role_id'); + $table->unsignedBigInteger(PermissionRegistrar::$pivotRole); $table->string('model_type'); $table->unsignedBigInteger($columnNames['model_morph_key']); $table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index'); - $table->foreign('role_id') + $table->foreign(PermissionRegistrar::$pivotRole) ->references('id') ->on($tableNames['roles']) ->onDelete('cascade'); - $table->primary(['role_id', $columnNames['model_morph_key'], 'model_type'], + $table->primary([PermissionRegistrar::$pivotRole, $columnNames['model_morph_key'], 'model_type'], 'model_has_roles_role_model_type_primary'); }); Schema::create($tableNames['role_has_permissions'], function (Blueprint $table) use ($tableNames) { - $table->unsignedBigInteger('permission_id'); - $table->unsignedBigInteger('role_id'); + $table->unsignedBigInteger(PermissionRegistrar::$pivotPermission); + $table->unsignedBigInteger(PermissionRegistrar::$pivotRole); - $table->foreign('permission_id') + $table->foreign(PermissionRegistrar::$pivotPermission) ->references('id') ->on($tableNames['permissions']) ->onDelete('cascade'); - $table->foreign('role_id') + $table->foreign(PermissionRegistrar::$pivotRole) ->references('id') ->on($tableNames['roles']) ->onDelete('cascade'); - $table->primary(['permission_id', 'role_id'], 'role_has_permissions_permission_id_role_id_primary'); + $table->primary([PermissionRegistrar::$pivotPermission, PermissionRegistrar::$pivotRole], 'role_has_permissions_permission_id_role_id_primary'); }); app('cache') diff --git a/src/Models/Permission.php b/src/Models/Permission.php index 5f3e060f8..44355a048 100644 --- a/src/Models/Permission.php +++ b/src/Models/Permission.php @@ -53,8 +53,8 @@ public function roles(): BelongsToMany return $this->belongsToMany( config('permission.models.role'), config('permission.table_names.role_has_permissions'), - 'permission_id', - 'role_id' + PermissionRegistrar::$pivotPermission, + PermissionRegistrar::$pivotRole ); } @@ -67,7 +67,7 @@ public function users(): BelongsToMany getModelForGuard($this->attributes['guard_name']), 'model', config('permission.table_names.model_has_permissions'), - 'permission_id', + PermissionRegistrar::$pivotPermission, config('permission.column_names.model_morph_key') ); } diff --git a/src/Models/Role.php b/src/Models/Role.php index 6810b5ae5..860210884 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -9,6 +9,7 @@ use Spatie\Permission\Exceptions\RoleAlreadyExists; use Spatie\Permission\Exceptions\RoleDoesNotExist; use Spatie\Permission\Guard; +use Spatie\Permission\PermissionRegistrar; use Spatie\Permission\Traits\HasPermissions; use Spatie\Permission\Traits\RefreshesPermissionCache; @@ -50,8 +51,8 @@ public function permissions(): BelongsToMany return $this->belongsToMany( config('permission.models.permission'), config('permission.table_names.role_has_permissions'), - 'role_id', - 'permission_id' + PermissionRegistrar::$pivotRole, + PermissionRegistrar::$pivotPermission ); } @@ -64,7 +65,7 @@ public function users(): BelongsToMany getModelForGuard($this->attributes['guard_name']), 'model', config('permission.table_names.model_has_roles'), - 'role_id', + PermissionRegistrar::$pivotRole, config('permission.column_names.model_morph_key') ); } diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index 527f017c9..3e2a94b48 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -26,6 +26,12 @@ class PermissionRegistrar /** @var \Illuminate\Database\Eloquent\Collection */ protected $permissions; + /** @var string */ + public static $pivotRole; + + /** @var string */ + public static $pivotPermission; + /** @var \DateInterval|int */ public static $cacheExpirationTime; @@ -52,6 +58,9 @@ public function initializeCache() self::$cacheKey = config('permission.cache.key'); + self::$pivotRole = config('permission.column_names.role_pivot_key') ?: 'role_id'; + self::$pivotPermission = config('permission.column_names.permission_pivot_key') ?: 'permission_id'; + $this->cache = $this->getCacheStoreFromConfig(); } diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index 4b7d6a3a5..df2d71440 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -47,7 +47,7 @@ public function permissions(): BelongsToMany 'model', config('permission.table_names.model_has_permissions'), config('permission.column_names.model_morph_key'), - 'permission_id' + PermissionRegistrar::$pivotPermission ); } diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index c9d816692..ad099e064 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -44,7 +44,7 @@ public function roles(): BelongsToMany 'model', config('permission.table_names.model_has_roles'), config('permission.column_names.model_morph_key'), - 'role_id' + PermissionRegistrar::$pivotRole ); } diff --git a/tests/TestCase.php b/tests/TestCase.php index a789cc882..bbbd67b06 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -72,7 +72,8 @@ protected function getEnvironmentSetUp($app) 'database' => ':memory:', 'prefix' => '', ]); - + $app['config']->set('permission.column_names.role_pivot_key', 'role_test_id'); + $app['config']->set('permission.column_names.permission_pivot_key', 'permission_test_id'); $app['config']->set('view.paths', [__DIR__.'/resources/views']); // Set-up admin guard From d9115447245755f0fe20a40ef52e69b2998649e6 Mon Sep 17 00:00:00 2001 From: drbyte Date: Tue, 24 Aug 2021 04:00:59 +0000 Subject: [PATCH 060/648] Fix styling --- src/Traits/HasPermissions.php | 2 +- src/Traits/HasRoles.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index c1401536f..00107b942 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -349,7 +349,7 @@ public function givePermissionTo(...$permissions) $class::saved( function ($object) use ($permissions, $model) { if ($model->getKey() != $object->getKey()) { - return; + return; } $model->permissions()->sync($permissions, false); $model->load('permissions'); diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index 3d775ad2a..0c57fdd68 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -121,7 +121,7 @@ public function assignRole(...$roles) $class::saved( function ($object) use ($roles, $model) { if ($model->getKey() != $object->getKey()) { - return; + return; } $model->roles()->sync($roles, false); $model->load('roles'); From dab7234595d9e4810e4e458570d11c2d9f1f2850 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 24 Aug 2021 00:01:31 -0400 Subject: [PATCH 061/648] Update CHANGELOG.md --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78d91fd05..9dc2d3e0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ All notable changes to `laravel-permission` will be documented in this file +## unreleased +- Avoid re-sync on non-persisted objects when firing Eloquent::saved #1819 + ## 4.3.0 - 2021-08-17 - Speed up permissions cache lookups, and make cache smaller #1799 From f320c7cfc63adf6b2601aaf99dce4e198e2fc30d Mon Sep 17 00:00:00 2001 From: Erik Niebla Date: Tue, 24 Aug 2021 10:00:54 -0500 Subject: [PATCH 062/648] Avoid custom hidden fields, avoid break replaced models --- src/Models/Permission.php | 8 +++---- src/PermissionRegistrar.php | 22 ++++++++++---------- tests/HasPermissionsWithCustomModelsTest.php | 16 ++++++++++++++ tests/Permission.php | 11 ++++++++++ tests/Role.php | 11 ++++++++++ tests/TestCase.php | 8 ++++++- 6 files changed, 59 insertions(+), 17 deletions(-) create mode 100644 tests/HasPermissionsWithCustomModelsTest.php create mode 100644 tests/Permission.php create mode 100644 tests/Role.php diff --git a/src/Models/Permission.php b/src/Models/Permission.php index 5f3e060f8..32aca39a6 100644 --- a/src/Models/Permission.php +++ b/src/Models/Permission.php @@ -171,11 +171,9 @@ protected function fillModelFromArray(array $attributes) { if (isset($attributes['roles'])) { $roleClass = app(PermissionRegistrar::class)->getRoleClass(); - $this->relations['roles'] = new Collection(); - - foreach ($attributes['roles'] as $value) { - $this->relations['roles']->push($roleClass::getModelFromArray($value)); - } + $this->relations['roles'] = (new Collection($attributes['roles']))->map(function ($role) use ($roleClass) { + return $roleClass::getModelFromArray($role); + }); unset($attributes['roles']); } diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index 527f017c9..1b9879792 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -121,21 +121,21 @@ private function loadPermissions() $this->permissions = $this->cache->remember(self::$cacheKey, self::$cacheExpirationTime, function () { $permissions = $this->getPermissionClass()->select('id', 'name', 'guard_name') ->with('roles:id,name,guard_name') - ->get()->toArray(); - foreach ($permissions as $i => $permission) { - foreach ($permission['roles'] ?? [] as $j => $roles) { - unset($permissions[$i]['roles'][$j]['pivot']); - } + ->get(); + + if (! method_exists($this->getPermissionClass(), 'getModelFromArray')) { + return $permissions; } - return $permissions; + // make the cache smaller using an array with only required fields + return $permissions->map(function ($permission) { + return $permission->only('id', 'name', 'guard_name') + ['roles' => $permission->roles->map->only('id', 'name', 'guard_name')->all()]; + })->all(); }); if (is_array($this->permissions)) { - $permissions = new Collection(); - foreach ($this->permissions as $value) { - $permissions->push($this->permissionClass::getModelFromArray($value)); - } - $this->permissions = $permissions; + $this->permissions = (new Collection($this->permissions))->map(function ($permission) { + return $this->permissionClass::getModelFromArray($permission); + }); } } } diff --git a/tests/HasPermissionsWithCustomModelsTest.php b/tests/HasPermissionsWithCustomModelsTest.php new file mode 100644 index 000000000..40c5b3d46 --- /dev/null +++ b/tests/HasPermissionsWithCustomModelsTest.php @@ -0,0 +1,16 @@ +assertSame(get_class($this->testUserPermission), \Spatie\Permission\Test\Permission::class); + $this->assertSame(get_class($this->testUserRole), \Spatie\Permission\Test\Role::class); + } +} diff --git a/tests/Permission.php b/tests/Permission.php new file mode 100644 index 000000000..9587e0469 --- /dev/null +++ b/tests/Permission.php @@ -0,0 +1,11 @@ +set('auth.guards.admin', ['driver' => 'session', 'provider' => 'admins']); $app['config']->set('auth.providers.admins', ['driver' => 'eloquent', 'model' => Admin::class]); - + if ($this->useCustomModels) { + $app['config']->set('permission.models.permission', \Spatie\Permission\Test\Permission::class); + $app['config']->set('permission.models.role', \Spatie\Permission\Test\Role::class); + } // Use test User model for users provider $app['config']->set('auth.providers.users.model', User::class); From dc1266bc40e4dd3820c20d659fb97d42a6ddaed9 Mon Sep 17 00:00:00 2001 From: drbyte Date: Tue, 24 Aug 2021 18:59:44 +0000 Subject: [PATCH 063/648] Fix styling --- tests/HasPermissionsWithCustomModelsTest.php | 2 +- tests/Permission.php | 2 +- tests/Role.php | 2 +- tests/TestCase.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/HasPermissionsWithCustomModelsTest.php b/tests/HasPermissionsWithCustomModelsTest.php index 40c5b3d46..8d0ab6442 100644 --- a/tests/HasPermissionsWithCustomModelsTest.php +++ b/tests/HasPermissionsWithCustomModelsTest.php @@ -5,7 +5,7 @@ class HasPermissionsWithCustomModelsTest extends HasPermissionsTest { /** @var bool */ - protected $useCustomModels=true; + protected $useCustomModels = true; /** @test */ public function it_can_use_custom_models() diff --git a/tests/Permission.php b/tests/Permission.php index 9587e0469..a621c53fc 100644 --- a/tests/Permission.php +++ b/tests/Permission.php @@ -5,7 +5,7 @@ class Permission extends \Spatie\Permission\Models\Permission { protected $visible = [ - 'id', + 'id', 'name', ]; } diff --git a/tests/Role.php b/tests/Role.php index f71b2378f..1977b00c2 100644 --- a/tests/Role.php +++ b/tests/Role.php @@ -5,7 +5,7 @@ class Role extends \Spatie\Permission\Models\Role { protected $visible = [ - 'id', + 'id', 'name', ]; } diff --git a/tests/TestCase.php b/tests/TestCase.php index e97b246ce..d4fe78cc6 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -32,7 +32,7 @@ abstract class TestCase extends Orchestra protected $testAdminPermission; /** @var bool */ - protected $useCustomModels=false; + protected $useCustomModels = false; public function setUp(): void { From 029884033dc6d495fcd24650a49cce34e0071502 Mon Sep 17 00:00:00 2001 From: Erik Niebla Date: Tue, 24 Aug 2021 17:38:26 -0500 Subject: [PATCH 064/648] Avoid BC break --- src/Models/Permission.php | 38 ------------------------------------- src/Models/Role.php | 30 ----------------------------- src/PermissionRegistrar.php | 26 ++++++++++++------------- 3 files changed, 13 insertions(+), 81 deletions(-) diff --git a/src/Models/Permission.php b/src/Models/Permission.php index 32aca39a6..a42b696cc 100644 --- a/src/Models/Permission.php +++ b/src/Models/Permission.php @@ -161,42 +161,4 @@ protected static function getPermission(array $params = []): ?PermissionContract { return static::getPermissions($params, true)->first(); } - - /** - * Fill model from array. - * - * @param array $attributes - */ - protected function fillModelFromArray(array $attributes) - { - if (isset($attributes['roles'])) { - $roleClass = app(PermissionRegistrar::class)->getRoleClass(); - $this->relations['roles'] = (new Collection($attributes['roles']))->map(function ($role) use ($roleClass) { - return $roleClass::getModelFromArray($role); - }); - unset($attributes['roles']); - } - - $this->attributes = $attributes; - if (isset($attributes['id'])) { - $this->exists = true; - $this->original['id'] = $attributes['id']; - } - - return $this; - } - - /** - * Get model from array. - * - * @param array $attributes - * - * @return \Spatie\Permission\Contracts\Permission - */ - public static function getModelFromArray(array $attributes): ?PermissionContract - { - $permission = new static; - - return $permission->fillModelFromArray($attributes); - } } diff --git a/src/Models/Role.php b/src/Models/Role.php index 6810b5ae5..5fd3177fc 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -157,34 +157,4 @@ public function hasPermissionTo($permission): bool return $this->permissions->contains('id', $permission->id); } - - /** - * Fill model from array. - * - * @param array $attributes - */ - protected function fillModelFromArray(array $attributes) - { - $this->attributes = $attributes; - if (isset($attributes['id'])) { - $this->exists = true; - $this->original['id'] = $attributes['id']; - } - - return $this; - } - - /** - * Get model from array. - * - * @param array $attributes - * - * @return \Spatie\Permission\Contracts\Role - */ - public static function getModelFromArray(array $attributes): ?RoleContract - { - $roles = new static; - - return $roles->fillModelFromArray($attributes); - } } diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index 1b9879792..660eb9222 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -119,22 +119,22 @@ private function loadPermissions() { if ($this->permissions === null) { $this->permissions = $this->cache->remember(self::$cacheKey, self::$cacheExpirationTime, function () { - $permissions = $this->getPermissionClass()->select('id', 'name', 'guard_name') - ->with('roles:id,name,guard_name') - ->get(); - - if (! method_exists($this->getPermissionClass(), 'getModelFromArray')) { - return $permissions; - } - // make the cache smaller using an array with only required fields - return $permissions->map(function ($permission) { - return $permission->only('id', 'name', 'guard_name') + ['roles' => $permission->roles->map->only('id', 'name', 'guard_name')->all()]; - })->all(); + return $this->getPermissionClass()->select('id', 'name', 'guard_name') + ->with('roles:id,name,guard_name')->get() + ->map(function ($permission) { + return $permission->only('id', 'name', 'guard_name') + + ['roles' => $permission->roles->map->only('id', 'name', 'guard_name')->all()]; + })->all(); }); if (is_array($this->permissions)) { - $this->permissions = (new Collection($this->permissions))->map(function ($permission) { - return $this->permissionClass::getModelFromArray($permission); + $this->permissions = $this->getPermissionClass()::hydrate( + collect($this->permissions)->map(function ($item) { + return collect($item)->only('id', 'name', 'guard_name')->all(); + })->all() + ) + ->each(function ($permission, $i) { + $permission->setRelation('roles', $this->getRoleClass()::hydrate($this->permissions[$i]['roles'] ?? [])); }); } } From 0c80537bd67cb6a33b4ecc7df36c73b6dd3f69b8 Mon Sep 17 00:00:00 2001 From: Erik Niebla Date: Wed, 25 Aug 2021 09:45:11 -0500 Subject: [PATCH 065/648] Use alias for even smaller cache --- src/PermissionRegistrar.php | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index 660eb9222..30c63d1e6 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -120,21 +120,25 @@ private function loadPermissions() if ($this->permissions === null) { $this->permissions = $this->cache->remember(self::$cacheKey, self::$cacheExpirationTime, function () { // make the cache smaller using an array with only required fields - return $this->getPermissionClass()->select('id', 'name', 'guard_name') - ->with('roles:id,name,guard_name')->get() + return $this->getPermissionClass()->select('id', 'id as i', 'name as n', 'guard_name as g') + ->with('roles:id,id as i,name as n,guard_name as g')->get() ->map(function ($permission) { - return $permission->only('id', 'name', 'guard_name') + - ['roles' => $permission->roles->map->only('id', 'name', 'guard_name')->all()]; + return $permission->only('i', 'n', 'g') + + ['r' => $permission->roles->map->only('i', 'n', 'g')->all()]; })->all(); }); if (is_array($this->permissions)) { $this->permissions = $this->getPermissionClass()::hydrate( collect($this->permissions)->map(function ($item) { - return collect($item)->only('id', 'name', 'guard_name')->all(); + return ['id' => $item['i'] ?? $item['id'], 'name' => $item['n'] ?? $item['name'], 'guard_name' => $item['g'] ?? $item['guard_name']]; })->all() ) ->each(function ($permission, $i) { - $permission->setRelation('roles', $this->getRoleClass()::hydrate($this->permissions[$i]['roles'] ?? [])); + $permission->setRelation('roles', $this->getRoleClass()::hydrate( + collect($this->permissions[$i]['r'] ?? $this->permissions[$i]['roles'] ?? [])->map(function ($item) { + return ['id' => $item['i'] ?? $item['id'], 'name' => $item['n'] ?? $item['name'], 'guard_name' => $item['g'] ?? $item['guard_name']]; + })->all() + )); }); } } From 7257756725c8e28706db1dc444e88558cc39d377 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Sat, 28 Aug 2021 19:24:45 -0400 Subject: [PATCH 066/648] Update CHANGELOG.md --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9dc2d3e0c..42a896f54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,9 @@ All notable changes to `laravel-permission` will be documented in this file -## unreleased +## 4.4.0 - 2021-08-28 +- Avoid BC break (removed interface change) on cache change added in 4.3.0 #1826 +- Made cache even smaller #1826 - Avoid re-sync on non-persisted objects when firing Eloquent::saved #1819 ## 4.3.0 - 2021-08-17 From f57eca701320816e49fe557884e0bed3f627dee5 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 31 Aug 2021 00:27:49 -0400 Subject: [PATCH 067/648] Update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42a896f54..357d9b698 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to `laravel-permission` will be documented in this file +## PENDING 5.0.0 - 2021-08-31 +- Change default-guard-lookup to prefer current user's guard (see BC note in #1817 ) + + ## 4.4.0 - 2021-08-28 - Avoid BC break (removed interface change) on cache change added in 4.3.0 #1826 - Made cache even smaller #1826 From c1b27d19d86ff35f6290b2650321f19e45f36bdc Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 31 Aug 2021 00:32:00 -0400 Subject: [PATCH 068/648] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 357d9b698..16263e84e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ All notable changes to `laravel-permission` will be documented in this file ## PENDING 5.0.0 - 2021-08-31 - Change default-guard-lookup to prefer current user's guard (see BC note in #1817 ) +- Customized pivots instead of `role_id`,`permission_id` #1823 ## 4.4.0 - 2021-08-28 From e0868b4d7aa88b21e755ebbe6a20b9af086bf8c5 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 31 Aug 2021 04:15:26 -0400 Subject: [PATCH 069/648] Update composer.json --- composer.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/composer.json b/composer.json index 2de4b582e..86e3f8017 100644 --- a/composer.json +++ b/composer.json @@ -51,6 +51,9 @@ "providers": [ "Spatie\\Permission\\PermissionServiceProvider" ] + }, + "branch-alias": { + "dev-master": "5.x-dev" } }, "scripts": { From baeee799c73bca27b41e50f7ed76ae5da0a113a3 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 31 Aug 2021 04:29:13 -0400 Subject: [PATCH 070/648] Update TestCase.php --- tests/TestCase.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/TestCase.php b/tests/TestCase.php index 5b260dbd2..7fad8cd6f 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -83,6 +83,9 @@ protected function getEnvironmentSetUp($app) $app['config']->set('permission.column_names.permission_pivot_key', 'permission_test_id'); $app['config']->set('view.paths', [__DIR__.'/resources/views']); + // ensure api guard exists (required since Laravel 8.55) + $app['config']->set('auth.guards.api', ['driver' => 'session', 'provider' => 'users']); + // Set-up admin guard $app['config']->set('auth.guards.admin', ['driver' => 'session', 'provider' => 'admins']); $app['config']->set('auth.providers.admins', ['driver' => 'eloquent', 'model' => Admin::class]); From 80499095e3220d78eec2008f6ccfdf9bb72ca825 Mon Sep 17 00:00:00 2001 From: Erik Niebla Date: Tue, 31 Aug 2021 09:14:36 -0500 Subject: [PATCH 071/648] Teams support --- config/permission.php | 17 +++ database/migrations/add_teams_fields.php.stub | 73 ++++++++++ .../create_permission_tables.php.stub | 40 +++++- docs/basic-usage/artisan.md | 7 + docs/basic-usage/teams-permissions.md | 68 ++++++++++ docs/installation-laravel.md | 1 + docs/installation-lumen.md | 2 + src/Commands/CreatePermission.php | 2 +- src/Commands/CreateRole.php | 20 ++- src/Commands/Show.php | 35 +++-- src/Commands/UpgradeForTeams.php | 125 ++++++++++++++++++ src/Models/Role.php | 33 ++++- src/PermissionRegistrar.php | 27 ++++ src/PermissionServiceProvider.php | 1 + src/Traits/HasPermissions.php | 22 ++- src/Traits/HasRoles.php | 30 ++++- src/helpers.php | 11 ++ tests/CommandTest.php | 50 +++++++ tests/HasPermissionsTest.php | 4 +- tests/HasRolesTest.php | 2 +- tests/TeamHasPermissionsTest.php | 74 +++++++++++ tests/TeamHasRolesTest.php | 62 +++++++++ tests/TestCase.php | 20 ++- 23 files changed, 689 insertions(+), 37 deletions(-) create mode 100644 database/migrations/add_teams_fields.php.stub create mode 100644 docs/basic-usage/teams-permissions.md create mode 100644 src/Commands/UpgradeForTeams.php create mode 100644 tests/TeamHasPermissionsTest.php create mode 100644 tests/TeamHasRolesTest.php diff --git a/config/permission.php b/config/permission.php index 7e31a6c2a..c7029fa5b 100644 --- a/config/permission.php +++ b/config/permission.php @@ -87,8 +87,25 @@ */ 'model_morph_key' => 'model_id', + + /* + * Change this if you want to use the teams feature and your related model's + * foreign key is other than `team_id`. + */ + + 'team_foreign_key' => 'team_id', ], + /* + * When set to true the package implements teams using the 'team_foreign_key'. If you want + * the migrations to register the 'team_foreign_key', you must set this to true + * before doing the migration. If you already did the migration then you must make a new + * migration to also add 'team_foreign_key' to 'roles', 'model_has_roles', and + * 'model_has_permissions'(view the latest version of package's migration file) + */ + + 'teams' => false, + /* * When set to true, the required permission names are added to the exception * message. This could be considered an information leak in some contexts, so diff --git a/database/migrations/add_teams_fields.php.stub b/database/migrations/add_teams_fields.php.stub new file mode 100644 index 000000000..727104f17 --- /dev/null +++ b/database/migrations/add_teams_fields.php.stub @@ -0,0 +1,73 @@ +unsignedBigInteger($columnNames['team_foreign_key'])->nullable(); + $table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index'); + } + }); + + Schema::table($tableNames['model_has_permissions'], function (Blueprint $table) use ($tableNames, $columnNames) { + if (! Schema::hasColumn($tableNames['model_has_permissions'], $columnNames['team_foreign_key'])) { + $table->unsignedBigInteger($columnNames['team_foreign_key'])->default('1');; + $table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index'); + + $table->dropPrimary(); + $table->primary([$columnNames['team_foreign_key'], 'permission_id', $columnNames['model_morph_key'], 'model_type'], + 'model_has_permissions_permission_model_type_primary'); + } + }); + + Schema::table($tableNames['model_has_roles'], function (Blueprint $table) use ($tableNames, $columnNames) { + if (! Schema::hasColumn($tableNames['model_has_roles'], $columnNames['team_foreign_key'])) { + $table->unsignedBigInteger($columnNames['team_foreign_key'])->default('1');; + $table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index'); + + $table->dropPrimary(); + $table->primary([$columnNames['team_foreign_key'], 'role_id', $columnNames['model_morph_key'], 'model_type'], + 'model_has_roles_role_model_type_primary'); + } + }); + + app('cache') + ->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null) + ->forget(config('permission.cache.key')); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + + } +} diff --git a/database/migrations/create_permission_tables.php.stub b/database/migrations/create_permission_tables.php.stub index 5ad2571a3..f20ef752b 100644 --- a/database/migrations/create_permission_tables.php.stub +++ b/database/migrations/create_permission_tables.php.stub @@ -16,10 +16,14 @@ class CreatePermissionTables extends Migration { $tableNames = config('permission.table_names'); $columnNames = config('permission.column_names'); + $teams = config('permission.teams'); if (empty($tableNames)) { throw new \Exception('Error: config/permission.php not loaded. Run [php artisan config:clear] and try again.'); } + if ($teams && empty($columnNames['team_foreign_key'] ?? null)) { + throw new \Exception('Error: team_foreign_key on config/permission.php not loaded. Run [php artisan config:clear] and try again.'); + } Schema::create($tableNames['permissions'], function (Blueprint $table) { $table->bigIncrements('id'); @@ -30,16 +34,23 @@ class CreatePermissionTables extends Migration $table->unique(['name', 'guard_name']); }); - Schema::create($tableNames['roles'], function (Blueprint $table) { + Schema::create($tableNames['roles'], function (Blueprint $table) use ($teams, $columnNames) { $table->bigIncrements('id'); + if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing + $table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable(); + $table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index'); + } $table->string('name'); // For MySQL 8.0 use string('name', 125); $table->string('guard_name'); // For MySQL 8.0 use string('guard_name', 125); $table->timestamps(); - - $table->unique(['name', 'guard_name']); + if ($teams || config('permission.testing')) { + $table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']); + } else { + $table->unique(['name', 'guard_name']); + } }); - Schema::create($tableNames['model_has_permissions'], function (Blueprint $table) use ($tableNames, $columnNames) { + Schema::create($tableNames['model_has_permissions'], function (Blueprint $table) use ($tableNames, $columnNames, $teams) { $table->unsignedBigInteger(PermissionRegistrar::$pivotPermission); $table->string('model_type'); @@ -50,12 +61,20 @@ class CreatePermissionTables extends Migration ->references('id') ->on($tableNames['permissions']) ->onDelete('cascade'); + if ($teams) { + $table->unsignedBigInteger($columnNames['team_foreign_key']); + $table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index'); - $table->primary([PermissionRegistrar::$pivotPermission, $columnNames['model_morph_key'], 'model_type'], + $table->primary([$columnNames['team_foreign_key'], PermissionRegistrar::$pivotPermission, $columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_permission_model_type_primary'); + } else { + $table->primary([PermissionRegistrar::$pivotPermission, $columnNames['model_morph_key'], 'model_type'], + 'model_has_permissions_permission_model_type_primary'); + } + }); - Schema::create($tableNames['model_has_roles'], function (Blueprint $table) use ($tableNames, $columnNames) { + Schema::create($tableNames['model_has_roles'], function (Blueprint $table) use ($tableNames, $columnNames, $teams) { $table->unsignedBigInteger(PermissionRegistrar::$pivotRole); $table->string('model_type'); @@ -66,9 +85,16 @@ class CreatePermissionTables extends Migration ->references('id') ->on($tableNames['roles']) ->onDelete('cascade'); + if ($teams) { + $table->unsignedBigInteger($columnNames['team_foreign_key']); + $table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index'); - $table->primary([PermissionRegistrar::$pivotRole, $columnNames['model_morph_key'], 'model_type'], + $table->primary([$columnNames['team_foreign_key'], PermissionRegistrar::$pivotRole, $columnNames['model_morph_key'], 'model_type'], + 'model_has_roles_role_model_type_primary'); + } else { + $table->primary([PermissionRegistrar::$pivotRole, $columnNames['model_morph_key'], 'model_type'], 'model_has_roles_role_model_type_primary'); + } }); Schema::create($tableNames['role_has_permissions'], function (Blueprint $table) use ($tableNames) { diff --git a/docs/basic-usage/artisan.md b/docs/basic-usage/artisan.md index b8c98dd53..8cf340d1b 100644 --- a/docs/basic-usage/artisan.md +++ b/docs/basic-usage/artisan.md @@ -31,6 +31,13 @@ When creating roles you can also create and link permissions at the same time: php artisan permission:create-role writer web "create articles|edit articles" ``` +When creating roles with teams enabled you can set the team id by adding the `--team-id` parameter: + +```bash +php artisan permission:create-role --team-id=1 writer +php artisan permission:create-role writer api --team-id=1 +``` + ## Displaying roles and permissions in the console There is also a `show` command to show a table of roles and permissions per guard: diff --git a/docs/basic-usage/teams-permissions.md b/docs/basic-usage/teams-permissions.md new file mode 100644 index 000000000..58e0055b7 --- /dev/null +++ b/docs/basic-usage/teams-permissions.md @@ -0,0 +1,68 @@ +--- +title: Teams permissions +weight: 3 +--- + +NOTE: Those changes must be made before performing the migration. If you have already run the migration and want to upgrade your solution, you can run the artisan console command `php artisan permission:setup-teams`, to create a new migration file named [xxxx_xx_xx_xx_add_teams_fields.php](https://github.com/spatie/laravel-permission/blob/master/database/migrations/add_teams_fields.php.stub) and then run `php artisan migrate` to upgrade your database tables. + +When enabled, teams permissions offers you a flexible control for a variety of scenarios. The idea behind teams permissions is inspired by the default permission implementation of [Laratrust](https://laratrust.santigarcor.me/). + + +Teams permissions can be enabled in the permission config file: + +```php +// config/permission.php +'teams' => true, +``` + +Also, if you want to use a custom foreign key for teams you must change in the permission config file: +```php +// config/permission.php +'team_foreign_key' => 'custom_team_id', +``` + +## Working with Teams Permissions + +After implements on login a solution for select a team on authentication (for example set `team_id` of the current selected team on **session**: `session(['team_id' => $team->team_id]);` ), +we can set global `team_id` from anywhere, but works better if you create a `Middleware`, example: + +```php +namespace App\Http\Middleware; + +class TeamsPermission{ + + public function handle($request, \Closure $next){ + if(!empty(auth()->user())){ + // session value set on login + app(\Spatie\Permission\PermissionRegistrar::class)->setPermissionsTeamId(session('team_id')); + } + // other custom ways to get team_id + /*if(!empty(auth('api')->user())){ + // `getTeamIdFromToken()` example of custom method for getting the set team_id + app(\Spatie\Permission\PermissionRegistrar::class)->setPermissionsTeamId(auth('api')->user()->getTeamIdFromToken()); + }*/ + + return $next($request); + } +} +``` +NOTE: You must add your custom `Middleware` to `$middlewarePriority` on `app/Http/Kernel.php`. + +## Roles Creating + +When creating a role you can pass the `team_id` as an optional parameter + +```php +// with null team_id it creates a global role, global roles can be used from any team and they are unique +Role::create(['name' => 'writer', 'team_id' => null]); + +// creates a role with team_id = 1, team roles can have the same name on different teams +Role::create(['name' => 'reader', 'team_id' => 1]); + +// creating a role without team_id makes the role take the default global team_id +Role::create(['name' => 'reviewer']); +``` + +## Roles/Permissions Assignment & Removal + +The role/permission assignment and removal are the same, but they take the global `team_id` set on login for sync. diff --git a/docs/installation-laravel.md b/docs/installation-laravel.md index 5d6baf281..c87a5b29f 100644 --- a/docs/installation-laravel.md +++ b/docs/installation-laravel.md @@ -33,6 +33,7 @@ This package can be used with Laravel 6.0 or higher. ``` 6. NOTE: If you are using UUIDs, see the Advanced section of the docs on UUID steps, before you continue. It explains some changes you may want to make to the migrations and config file before continuing. It also mentions important considerations after extending this package's models for UUID capability. + If you are going to use teams feature, you have to update your [`config/permission.php` config file](https://github.com/spatie/laravel-permission/blob/master/config/permission.php) and set `'teams' => true,`, if you want to use a custom foreign key for teams you must change `team_foreign_key`. 7. Clear your config cache. This package requires access to the `permission` config. Generally it's bad practice to do config-caching in a development environment. If you've been caching configurations locally, clear your config cache with either of these commands: diff --git a/docs/installation-lumen.md b/docs/installation-lumen.md index be484756b..e1a825170 100644 --- a/docs/installation-lumen.md +++ b/docs/installation-lumen.md @@ -52,6 +52,8 @@ $app->register(App\Providers\AuthServiceProvider::class); Ensure the application's database name/credentials are set in your `.env` (or `config/database.php` if you have one), and that the database exists. +NOTE: If you are going to use teams feature, you have to update your [`config/permission.php` config file](https://github.com/spatie/laravel-permission/blob/master/config/permission.php) and set `'teams' => true,`, if you want to use a custom foreign key for teams you must change `team_foreign_key`. + Run the migrations to create the tables for this package: ```bash diff --git a/src/Commands/CreatePermission.php b/src/Commands/CreatePermission.php index f71a63240..c3bc20693 100644 --- a/src/Commands/CreatePermission.php +++ b/src/Commands/CreatePermission.php @@ -19,6 +19,6 @@ public function handle() $permission = $permissionClass::findOrCreate($this->argument('name'), $this->argument('guard')); - $this->info("Permission `{$permission->name}` created"); + $this->info("Permission `{$permission->name}` ".($permission->wasRecentlyCreated ? 'created' : 'already exists')); } } diff --git a/src/Commands/CreateRole.php b/src/Commands/CreateRole.php index 426d6f0c7..805d5eeb3 100644 --- a/src/Commands/CreateRole.php +++ b/src/Commands/CreateRole.php @@ -5,13 +5,15 @@ use Illuminate\Console\Command; use Spatie\Permission\Contracts\Permission as PermissionContract; use Spatie\Permission\Contracts\Role as RoleContract; +use Spatie\Permission\PermissionRegistrar; class CreateRole extends Command { protected $signature = 'permission:create-role {name : The name of the role} {guard? : The name of the guard} - {permissions? : A list of permissions to assign to the role, separated by | }'; + {permissions? : A list of permissions to assign to the role, separated by | } + {--team-id=}'; protected $description = 'Create a role'; @@ -19,11 +21,25 @@ public function handle() { $roleClass = app(RoleContract::class); + $teamIdAux = app(PermissionRegistrar::class)->getPermissionsTeamId(); + app(PermissionRegistrar::class)->setPermissionsTeamId($this->option('team-id') ?: null); + + if (! PermissionRegistrar::$teams && $this->option('team-id')) { + $this->warn("Teams feature disabled, argument --team-id has no effect. Either enable it in permissions config file or remove --team-id parameter"); + return; + } + $role = $roleClass::findOrCreate($this->argument('name'), $this->argument('guard')); + app(PermissionRegistrar::class)->setPermissionsTeamId($teamIdAux); + + $teams_key = PermissionRegistrar::$teamsKey; + if (PermissionRegistrar::$teams && $this->option('team-id') && is_null($role->$teams_key)) { + $this->warn("Role `{$role->name}` already exists on the global team; argument --team-id has no effect"); + } $role->givePermissionTo($this->makePermissions($this->argument('permissions'))); - $this->info("Role `{$role->name}` created"); + $this->info("Role `{$role->name}` ".($role->wasRecentlyCreated ? 'created' : 'updated')); } /** diff --git a/src/Commands/Show.php b/src/Commands/Show.php index c0aa0e33d..3aded0491 100644 --- a/src/Commands/Show.php +++ b/src/Commands/Show.php @@ -6,6 +6,7 @@ use Illuminate\Support\Collection; use Spatie\Permission\Contracts\Permission as PermissionContract; use Spatie\Permission\Contracts\Role as RoleContract; +use Symfony\Component\Console\Helper\TableCell; class Show extends Command { @@ -19,6 +20,7 @@ public function handle() { $permissionClass = app(PermissionContract::class); $roleClass = app(RoleContract::class); + $team_key = config('permission.column_names.team_foreign_key'); $style = $this->argument('style') ?? 'default'; $guard = $this->argument('guard'); @@ -32,20 +34,37 @@ public function handle() foreach ($guards as $guard) { $this->info("Guard: $guard"); - $roles = $roleClass::whereGuardName($guard)->orderBy('name')->get()->mapWithKeys(function ($role) { - return [$role->name => $role->permissions->pluck('name')]; - }); + $roles = $roleClass::whereGuardName($guard) + ->when(config('permission.teams'), function ($q) use ($team_key) { + $q->orderBy($team_key); + }) + ->orderBy('name')->get()->mapWithKeys(function ($role) use ($team_key) { + return [$role->name.'_'.($role->$team_key ?: '') => ['permissions' => $role->permissions->pluck('id'), $team_key => $role->$team_key ]]; + }); - $permissions = $permissionClass::whereGuardName($guard)->orderBy('name')->pluck('name'); + $permissions = $permissionClass::whereGuardName($guard)->orderBy('name')->pluck('name', 'id'); - $body = $permissions->map(function ($permission) use ($roles) { - return $roles->map(function (Collection $role_permissions) use ($permission) { - return $role_permissions->contains($permission) ? ' ✔' : ' ·'; + $body = $permissions->map(function ($permission, $id) use ($roles) { + return $roles->map(function (array $role_data) use ($id) { + return $role_data['permissions']->contains($id) ? ' ✔' : ' ·'; })->prepend($permission); }); + if (config('permission.teams')) { + $teams = $roles->groupBy($team_key)->values()->map(function ($group, $id) { + return new TableCell('Team ID: '.($id ?: 'NULL'), ['colspan' => $group->count()]); + }); + } + $this->table( - $roles->keys()->prepend('')->toArray(), + array_merge([ + config('permission.teams') ? $teams->prepend('')->toArray() : [], + $roles->keys()->map(function ($val) { + $name = explode('_', $val); + return $name[0]; + }) + ->prepend('')->toArray() + ]), $body->toArray(), $style ); diff --git a/src/Commands/UpgradeForTeams.php b/src/Commands/UpgradeForTeams.php new file mode 100644 index 000000000..7e70ef5f8 --- /dev/null +++ b/src/Commands/UpgradeForTeams.php @@ -0,0 +1,125 @@ +error('Teams feature is disabled in your permission.php file.'); + $this->warn('Please enable the teams setting in your configuration.'); + return; + } + + $this->line(''); + $this->info("The teams feature setup is going to add a migration and a model"); + + $existingMigrations = $this->alreadyExistingMigrations(); + + if ($existingMigrations) { + $this->line(''); + + $this->warn($this->getExistingMigrationsWarning($existingMigrations)); + } + + $this->line(''); + + if (! $this->confirm("Proceed with the migration creation?", "yes")) { + return; + } + + $this->line(''); + + $this->line("Creating migration"); + + if ($this->createMigration()) { + $this->info("Migration created successfully."); + } else { + $this->error( + "Couldn't create migration.\n". + "Check the write permissions within the database/migrations directory." + ); + } + + $this->line(''); + } + + /** + * Create the migration. + * + * @return bool + */ + protected function createMigration() + { + try { + $migrationStub = __DIR__."/../../database/migrations/{$this->migrationSuffix}.stub"; + copy($migrationStub, $this->getMigrationPath()); + return true; + } catch (\Throwable $e) { + $this->error($e->getMessage()); + return false; + } + } + + /** + * Build a warning regarding possible duplication + * due to already existing migrations. + * + * @param array $existingMigrations + * @return string + */ + protected function getExistingMigrationsWarning(array $existingMigrations) + { + if (count($existingMigrations) > 1) { + $base = "Setup teams migrations already exist.\nFollowing files were found: "; + } else { + $base = "Setup teams migration already exists.\nFollowing file was found: "; + } + + return $base . array_reduce($existingMigrations, function ($carry, $fileName) { + return $carry . "\n - " . $fileName; + }); + } + + /** + * Check if there is another migration + * with the same suffix. + * + * @return array + */ + protected function alreadyExistingMigrations() + { + $matchingFiles = glob($this->getMigrationPath('*')); + + return array_map(function ($path) { + return basename($path); + }, $matchingFiles); + } + + /** + * Get the migration path. + * + * The date parameter is optional for ability + * to provide a custom value or a wildcard. + * + * @param string|null $date + * @return string + */ + protected function getMigrationPath($date = null) + { + $date = $date ?: date('Y_m_d_His'); + + return database_path("migrations/${date}_{$this->migrationSuffix}"); + } +} diff --git a/src/Models/Role.php b/src/Models/Role.php index 11c497280..3ef1a8313 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -36,7 +36,15 @@ public static function create(array $attributes = []) { $attributes['guard_name'] = $attributes['guard_name'] ?? Guard::getDefaultName(static::class); - if (static::where('name', $attributes['name'])->where('guard_name', $attributes['guard_name'])->first()) { + $params = ['name' => $attributes['name'], 'guard_name' => $attributes['guard_name']]; + if (PermissionRegistrar::$teams) { + if (array_key_exists(PermissionRegistrar::$teamsKey, $attributes)) { + $params[PermissionRegistrar::$teamsKey] = $attributes[PermissionRegistrar::$teamsKey]; + } else { + $attributes[PermissionRegistrar::$teamsKey] = app(PermissionRegistrar::class)->getPermissionsTeamId(); + } + } + if (static::findByParam($params)) { throw RoleAlreadyExists::create($attributes['name'], $attributes['guard_name']); } @@ -84,7 +92,7 @@ public static function findByName(string $name, $guardName = null): RoleContract { $guardName = $guardName ?? Guard::getDefaultName(static::class); - $role = static::where('name', $name)->where('guard_name', $guardName)->first(); + $role = static::findByParam(['name' => $name, 'guard_name' => $guardName]); if (! $role) { throw RoleDoesNotExist::named($name); @@ -97,7 +105,7 @@ public static function findById(int $id, $guardName = null): RoleContract { $guardName = $guardName ?? Guard::getDefaultName(static::class); - $role = static::where('id', $id)->where('guard_name', $guardName)->first(); + $role = static::findByParam(['id' => $id, 'guard_name' => $guardName]); if (! $role) { throw RoleDoesNotExist::withId($id); @@ -118,15 +126,30 @@ public static function findOrCreate(string $name, $guardName = null): RoleContra { $guardName = $guardName ?? Guard::getDefaultName(static::class); - $role = static::where('name', $name)->where('guard_name', $guardName)->first(); + $role = static::findByParam(['name' => $name, 'guard_name' => $guardName]); if (! $role) { - return static::query()->create(['name' => $name, 'guard_name' => $guardName]); + return static::query()->create(['name' => $name, 'guard_name' => $guardName] + (PermissionRegistrar::$teams ? [PermissionRegistrar::$teamsKey => app(PermissionRegistrar::class)->getPermissionsTeamId()] : [])); } return $role; } + protected static function findByParam(array $params = []) + { + $query = static::when(PermissionRegistrar::$teams, function ($q) use ($params) { + $q->where(function ($q) use ($params) { + $q->whereNull(PermissionRegistrar::$teamsKey) + ->orWhere(PermissionRegistrar::$teamsKey, $params[PermissionRegistrar::$teamsKey] ?? app(PermissionRegistrar::class)->getPermissionsTeamId()); + }); + }); + unset($params[PermissionRegistrar::$teamsKey]); + foreach ($params as $key => $value) { + $query->where($key, $value); + } + return $query->first(); + } + /** * Determine if the user may perform the given permission. * diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index 4f23c9f7f..b3ae4a7b5 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -35,6 +35,15 @@ class PermissionRegistrar /** @var \DateInterval|int */ public static $cacheExpirationTime; + /** @var bool */ + public static $teams; + + /** @var string */ + public static $teamsKey; + + /** @var int */ + protected $teamId = null; + /** @var string */ public static $cacheKey; @@ -56,6 +65,9 @@ public function initializeCache() { self::$cacheExpirationTime = config('permission.cache.expiration_time') ?: \DateInterval::createFromDateString('24 hours'); + self::$teams = config('permission.teams', false); + self::$teamsKey = config('permission.column_names.team_foreign_key'); + self::$cacheKey = config('permission.cache.key'); self::$pivotRole = config('permission.column_names.role_pivot_key') ?: 'role_id'; @@ -83,6 +95,21 @@ protected function getCacheStoreFromConfig(): \Illuminate\Contracts\Cache\Reposi return $this->cacheManager->store($cacheDriver); } + /** + * Set the team id for teams/groups support, this id is used when querying permissions/roles + * + * @param int $id + */ + public function setPermissionsTeamId(?int $id) + { + $this->teamId = $id; + } + + public function getPermissionsTeamId(): ?int + { + return $this->teamId; + } + /** * Register the permission check method on the gate. * We resolve the Gate fresh here, for benefit of long-running instances. diff --git a/src/PermissionServiceProvider.php b/src/PermissionServiceProvider.php index 6a237fece..c0ae0ed15 100644 --- a/src/PermissionServiceProvider.php +++ b/src/PermissionServiceProvider.php @@ -63,6 +63,7 @@ protected function registerCommands() Commands\CreateRole::class, Commands\CreatePermission::class, Commands\Show::class, + Commands\UpgradeForTeams::class, ]); } diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index b0776f089..7d3451db4 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -6,6 +6,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Support\Collection; use Spatie\Permission\Contracts\Permission; +use Spatie\Permission\Contracts\Role; use Spatie\Permission\Exceptions\GuardDoesNotMatch; use Spatie\Permission\Exceptions\PermissionDoesNotExist; use Spatie\Permission\Exceptions\WildcardPermissionInvalidArgument; @@ -48,7 +49,12 @@ public function permissions(): BelongsToMany config('permission.table_names.model_has_permissions'), config('permission.column_names.model_morph_key'), PermissionRegistrar::$pivotPermission - ); + ) + ->where(function ($q) { + $q->when(PermissionRegistrar::$teams, function ($q) { + $q->where(PermissionRegistrar::$teamsKey, app(PermissionRegistrar::class)->getPermissionsTeamId()); + }); + }); } /** @@ -335,13 +341,21 @@ public function givePermissionTo(...$permissions) ->each(function ($permission) { $this->ensureModelSharesGuard($permission); }) - ->map->id - ->all(); + ->map(function ($permission) { + return ['id' => $permission->id, 'values' => PermissionRegistrar::$teams && !is_a($this, Role::class) ? + [PermissionRegistrar::$teamsKey => app(PermissionRegistrar::class)->getPermissionsTeamId()] : [] + ]; + }) + ->pluck('values', 'id')->toArray(); $model = $this->getModel(); if ($model->exists) { - $this->permissions()->sync($permissions, false); + if (PermissionRegistrar::$teams && !is_a($this, Role::class)) { + $this->permissions()->wherePivot(PermissionRegistrar::$teamsKey, app(PermissionRegistrar::class)->getPermissionsTeamId())->sync($permissions, false); + } else { + $this->permissions()->sync($permissions, false); + } $model->load('permissions'); } else { $class = \get_class($model); diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index e5470322d..b0003e5d3 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -5,6 +5,7 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Support\Collection; +use Spatie\Permission\Contracts\Permission; use Spatie\Permission\Contracts\Role; use Spatie\Permission\PermissionRegistrar; @@ -39,13 +40,24 @@ public function getRoleClass() */ public function roles(): BelongsToMany { + $model_has_roles = config('permission.table_names.model_has_roles'); return $this->morphToMany( config('permission.models.role'), 'model', - config('permission.table_names.model_has_roles'), + $model_has_roles, config('permission.column_names.model_morph_key'), PermissionRegistrar::$pivotRole - ); + ) + ->where(function ($q) use ($model_has_roles) { + $q->when(PermissionRegistrar::$teams, function ($q) use ($model_has_roles) { + $teamId = app(PermissionRegistrar::class)->getPermissionsTeamId(); + $q->where($model_has_roles.'.'.PermissionRegistrar::$teamsKey, $teamId) + ->where(function ($q) use ($teamId) { + $teamField = config('permission.table_names.roles').'.'.PermissionRegistrar::$teamsKey; + $q->whereNull($teamField)->orWhere($teamField, $teamId); + }); + }); + }); } /** @@ -107,13 +119,21 @@ public function assignRole(...$roles) ->each(function ($role) { $this->ensureModelSharesGuard($role); }) - ->map->id - ->all(); + ->map(function ($role) { + return ['id' => $role->id, 'values' => PermissionRegistrar::$teams && !is_a($this, Permission::class) ? + [PermissionRegistrar::$teamsKey => app(PermissionRegistrar::class)->getPermissionsTeamId()] : [] + ]; + }) + ->pluck('values', 'id')->toArray(); $model = $this->getModel(); if ($model->exists) { - $this->roles()->sync($roles, false); + if (PermissionRegistrar::$teams && !is_a($this, Permission::class)) { + $this->roles()->wherePivot(PermissionRegistrar::$teamsKey, app(PermissionRegistrar::class)->getPermissionsTeamId())->sync($roles, false); + } else { + $this->roles()->sync($roles, false); + } $model->load('roles'); } else { $class = \get_class($model); diff --git a/src/helpers.php b/src/helpers.php index 7f79e5764..2b80ea417 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -18,3 +18,14 @@ function getModelForGuard(string $guard) })->get($guard); } } + +if (! function_exists('setPermissionsTeamId')) { + /** + * @param int $id + * + */ + function setPermissionsTeamId(int $id) + { + app(\Spatie\Permission\PermissionRegistrar::class)->setPermissionsTeamId($id); + } +} diff --git a/tests/CommandTest.php b/tests/CommandTest.php index 2071e45a9..603af80d2 100644 --- a/tests/CommandTest.php +++ b/tests/CommandTest.php @@ -130,4 +130,54 @@ public function it_can_show_permissions_for_guard() $this->assertTrue(strpos($output, 'Guard: web') !== false); $this->assertTrue(strpos($output, 'Guard: admin') === false); } + + /** @test */ + public function it_can_setup_teams_upgrade() + { + config()->set('permission.teams', true); + + $this->artisan('permission:setup-teams') + ->expectsQuestion('Proceed with the migration creation?', 'yes') + ->assertExitCode(0); + + $matchingFiles = glob(database_path('migrations/*_add_teams_fields.php')); + $this->assertTrue(count($matchingFiles) > 0); + + include_once $matchingFiles[count($matchingFiles)-1]; + (new \AddTeamsFields())->up(); + (new \AddTeamsFields())->up(); //test upgrade teams migration fresh + + Role::create(['name' => 'new-role', 'team_test_id' => 1]); + $role = Role::where('name', 'new-role')->first(); + $this->assertNotNull($role); + $this->assertSame(1, (int) $role->team_test_id); + + // remove migration + foreach ($matchingFiles as $file) { + unlink($file); + } + } + + /** @test */ + public function it_can_show_roles_by_teams() + { + config()->set('permission.teams', true); + app(\Spatie\Permission\PermissionRegistrar::class)->initializeCache(); + + Role::create(['name' => 'testRoleTeam', 'team_test_id' => 1]); + Role::create(['name' => 'testRoleTeam', 'team_test_id' => 2]); // same name different team + Artisan::call('permission:show'); + + $output = Artisan::output(); + + // | | Team ID: NULL | Team ID: 1 | Team ID: 2 | + // | | testRole | testRole2 | testRoleTeam | testRoleTeam | + if (method_exists($this, 'assertMatchesRegularExpression')) { + $this->assertMatchesRegularExpression('/\|\s+\|\s+Team ID: NULL\s+\|\s+Team ID: 1\s+\|\s+Team ID: 2\s+\|/', $output); + $this->assertMatchesRegularExpression('/\|\s+\|\s+testRole\s+\|\s+testRole2\s+\|\s+testRoleTeam\s+\|\s+testRoleTeam\s+\|/', $output); + } else { // phpUnit 9/8 + $this->assertRegExp('/\|\s+\|\s+Team ID: NULL\s+\|\s+Team ID: 1\s+\|\s+Team ID: 2\s+\|/', $output); + $this->assertRegExp('/\|\s+\|\s+testRole\s+\|\s+testRole2\s+\|\s+testRoleTeam\s+\|\s+testRoleTeam\s+\|/', $output); + } + } } diff --git a/tests/HasPermissionsTest.php b/tests/HasPermissionsTest.php index c67c20c68..7507837b6 100644 --- a/tests/HasPermissionsTest.php +++ b/tests/HasPermissionsTest.php @@ -516,8 +516,8 @@ public function it_can_retrieve_permission_names() { $this->testUser->givePermissionTo('edit-news', 'edit-articles'); $this->assertEquals( - collect(['edit-news', 'edit-articles']), - $this->testUser->getPermissionNames() + collect(['edit-articles', 'edit-news']), + $this->testUser->getPermissionNames()->sort()->values() ); } diff --git a/tests/HasRolesTest.php b/tests/HasRolesTest.php index 405e6eac1..daa556444 100644 --- a/tests/HasRolesTest.php +++ b/tests/HasRolesTest.php @@ -568,7 +568,7 @@ public function it_can_retrieve_role_names() $this->assertEquals( collect(['testRole', 'testRole2']), - $this->testUser->getRoleNames() + $this->testUser->getRoleNames()->sort()->values() ); } diff --git a/tests/TeamHasPermissionsTest.php b/tests/TeamHasPermissionsTest.php new file mode 100644 index 000000000..9170166ae --- /dev/null +++ b/tests/TeamHasPermissionsTest.php @@ -0,0 +1,74 @@ +setPermissionsTeamId(1); + $this->testUser->load('permissions'); + $this->testUser->givePermissionTo('edit-articles', 'edit-news'); + + $this->setPermissionsTeamId(2); + $this->testUser->load('permissions'); + $this->testUser->givePermissionTo('edit-articles', 'edit-blog'); + + $this->setPermissionsTeamId(1); + $this->testUser->load('permissions'); + $this->assertEquals( + collect(['edit-articles', 'edit-news']), + $this->testUser->getPermissionNames()->sort()->values() + ); + $this->assertTrue($this->testUser->hasAllDirectPermissions(['edit-articles', 'edit-news'])); + $this->assertFalse($this->testUser->hasAllDirectPermissions(['edit-articles', 'edit-blog'])); + + $this->setPermissionsTeamId(2); + $this->testUser->load('permissions'); + $this->assertEquals( + collect(['edit-articles', 'edit-blog']), + $this->testUser->getPermissionNames()->sort()->values() + ); + $this->assertTrue($this->testUser->hasAllDirectPermissions(['edit-articles', 'edit-blog'])); + $this->assertFalse($this->testUser->hasAllDirectPermissions(['edit-articles', 'edit-news'])); + } + + /** @test */ + public function it_can_list_all_the_coupled_permissions_both_directly_and_via_roles_on_same_user_on_different_teams() + { + $this->testUserRole->givePermissionTo('edit-articles'); + + $this->setPermissionsTeamId(1); + $this->testUser->load('permissions'); + $this->testUser->assignRole('testRole'); + $this->testUser->givePermissionTo('edit-news'); + + $this->setPermissionsTeamId(2); + $this->testUser->load('permissions'); + $this->testUser->assignRole('testRole'); + $this->testUser->givePermissionTo('edit-blog'); + + $this->setPermissionsTeamId(1); + $this->testUser->load('roles'); + $this->testUser->load('permissions'); + + $this->assertEquals( + collect(['edit-articles', 'edit-news']), + $this->testUser->getAllPermissions()->pluck('name')->sort()->values() + ); + + $this->setPermissionsTeamId(2); + $this->testUser->load('roles'); + $this->testUser->load('permissions'); + + $this->assertEquals( + collect(['edit-articles', 'edit-blog']), + $this->testUser->getAllPermissions()->pluck('name')->sort()->values() + ); + } +} diff --git a/tests/TeamHasRolesTest.php b/tests/TeamHasRolesTest.php new file mode 100644 index 000000000..cdf120513 --- /dev/null +++ b/tests/TeamHasRolesTest.php @@ -0,0 +1,62 @@ +create(['name' => 'testRole3']); //team_test_id = 1 by main class + app(Role::class)->create(['name' => 'testRole3', 'team_test_id' => 2]); + app(Role::class)->create(['name' => 'testRole4', 'team_test_id' => null]); //global role + + $testRole3Team1 = app(Role::class)->where(['name' => 'testRole3', 'team_test_id' => 1])->first(); + $testRole3Team2 = app(Role::class)->where(['name' => 'testRole3', 'team_test_id' => 2])->first(); + $testRole4NoTeam = app(Role::class)->where(['name' => 'testRole4', 'team_test_id' => null])->first(); + $this->assertNotNull($testRole3Team1); + $this->assertNotNull($testRole4NoTeam); + + $this->setPermissionsTeamId(1); + $this->testUser->load('roles'); + + $this->testUser->assignRole('testRole', 'testRole2'); + + $this->setPermissionsTeamId(2); + $this->testUser->load('roles'); + + $this->testUser->assignRole('testRole', 'testRole3'); + + $this->setPermissionsTeamId(1); + $this->testUser->load('roles'); + + $this->assertEquals( + collect(['testRole', 'testRole2']), + $this->testUser->getRoleNames()->sort()->values() + ); + $this->assertTrue($this->testUser->hasExactRoles(['testRole', 'testRole2'])); + + $this->testUser->assignRole('testRole3', 'testRole4'); + $this->assertTrue($this->testUser->hasExactRoles(['testRole', 'testRole2', 'testRole3', 'testRole4'])); + $this->assertTrue($this->testUser->hasRole($testRole3Team1)); //testRole3 team=1 + $this->assertTrue($this->testUser->hasRole($testRole4NoTeam)); // global role team=null + + $this->setPermissionsTeamId(2); + $this->testUser->load('roles'); + + $this->assertEquals( + collect(['testRole', 'testRole3']), + $this->testUser->getRoleNames()->sort()->values() + ); + $this->assertTrue($this->testUser->hasExactRoles(['testRole', 'testRole3'])); + $this->assertTrue($this->testUser->hasRole($testRole3Team2)); //testRole3 team=2 + $this->testUser->assignRole('testRole4'); + $this->assertTrue($this->testUser->hasExactRoles(['testRole', 'testRole3', 'testRole4'])); + $this->assertTrue($this->testUser->hasRole($testRole4NoTeam)); // global role team=null + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php index 7fad8cd6f..1ea141566 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -36,12 +36,18 @@ abstract class TestCase extends Orchestra /** @var bool */ protected $useCustomModels = false; + /** @var bool */ + protected $hasTeams=false; + public function setUp(): void { parent::setUp(); // Note: this also flushes the cache from within the migration $this->setUpDatabase($this->app); + if ($this->hasTeams) { + $this->setPermissionsTeamId(1); + } $this->testUser = User::first(); $this->testUserRole = app(Role::class)->find(1); @@ -73,6 +79,10 @@ protected function getPackageProviders($app) */ protected function getEnvironmentSetUp($app) { + $app['config']->set('permission.teams', $this->hasTeams); + $app['config']->set('permission.testing', true); //fix sqlite + $app['config']->set('permission.column_names.model_morph_key', 'model_test_id'); + $app['config']->set('permission.column_names.team_foreign_key', 'team_test_id'); $app['config']->set('database.default', 'sqlite'); $app['config']->set('database.connections.sqlite', [ 'driver' => 'sqlite', @@ -106,8 +116,6 @@ protected function getEnvironmentSetUp($app) */ protected function setUpDatabase($app) { - $app['config']->set('permission.column_names.model_morph_key', 'model_test_id'); - $app['db']->connection()->getSchemaBuilder()->create('users', function (Blueprint $table) { $table->increments('id'); $table->string('email'); @@ -148,6 +156,14 @@ protected function reloadPermissions() app(PermissionRegistrar::class)->forgetCachedPermissions(); } + /** + * Change the team_id + */ + protected function setPermissionsTeamId(int $id) + { + app(PermissionRegistrar::class)->setPermissionsTeamId($id); + } + public function createCacheTable() { Schema::create('cache', function ($table) { From faf255feca50ffc32a33703a94aae870714c07bd Mon Sep 17 00:00:00 2001 From: drbyte Date: Tue, 31 Aug 2021 18:10:52 +0000 Subject: [PATCH 072/648] Fix styling --- src/Commands/CreateRole.php | 1 + src/Commands/Show.php | 3 ++- src/Commands/UpgradeForTeams.php | 6 ++++-- src/Models/Role.php | 3 ++- src/Traits/HasPermissions.php | 6 +++--- src/Traits/HasRoles.php | 7 ++++--- tests/CommandTest.php | 2 +- tests/TeamHasPermissionsTest.php | 3 +-- tests/TeamHasRolesTest.php | 2 +- tests/TestCase.php | 2 +- 10 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/Commands/CreateRole.php b/src/Commands/CreateRole.php index 805d5eeb3..fdd76ad10 100644 --- a/src/Commands/CreateRole.php +++ b/src/Commands/CreateRole.php @@ -26,6 +26,7 @@ public function handle() if (! PermissionRegistrar::$teams && $this->option('team-id')) { $this->warn("Teams feature disabled, argument --team-id has no effect. Either enable it in permissions config file or remove --team-id parameter"); + return; } diff --git a/src/Commands/Show.php b/src/Commands/Show.php index 3aded0491..f7cc2b343 100644 --- a/src/Commands/Show.php +++ b/src/Commands/Show.php @@ -61,9 +61,10 @@ public function handle() config('permission.teams') ? $teams->prepend('')->toArray() : [], $roles->keys()->map(function ($val) { $name = explode('_', $val); + return $name[0]; }) - ->prepend('')->toArray() + ->prepend('')->toArray(), ]), $body->toArray(), $style diff --git a/src/Commands/UpgradeForTeams.php b/src/Commands/UpgradeForTeams.php index 7e70ef5f8..39dd1376c 100644 --- a/src/Commands/UpgradeForTeams.php +++ b/src/Commands/UpgradeForTeams.php @@ -7,7 +7,6 @@ class UpgradeForTeams extends Command { - protected $signature = 'permission:setup-teams'; protected $description = 'Setup the teams feature by generating the associated migration.'; @@ -16,9 +15,10 @@ class UpgradeForTeams extends Command public function handle() { - if (!Config::get('permission.teams')) { + if (! Config::get('permission.teams')) { $this->error('Teams feature is disabled in your permission.php file.'); $this->warn('Please enable the teams setting in your configuration.'); + return; } @@ -65,9 +65,11 @@ protected function createMigration() try { $migrationStub = __DIR__."/../../database/migrations/{$this->migrationSuffix}.stub"; copy($migrationStub, $this->getMigrationPath()); + return true; } catch (\Throwable $e) { $this->error($e->getMessage()); + return false; } } diff --git a/src/Models/Role.php b/src/Models/Role.php index 3ef1a8313..a0fd14e92 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -105,7 +105,7 @@ public static function findById(int $id, $guardName = null): RoleContract { $guardName = $guardName ?? Guard::getDefaultName(static::class); - $role = static::findByParam(['id' => $id, 'guard_name' => $guardName]); + $role = static::findByParam(['id' => $id, 'guard_name' => $guardName]); if (! $role) { throw RoleDoesNotExist::withId($id); @@ -147,6 +147,7 @@ protected static function findByParam(array $params = []) foreach ($params as $key => $value) { $query->where($key, $value); } + return $query->first(); } diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index 7d3451db4..128351186 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -342,8 +342,8 @@ public function givePermissionTo(...$permissions) $this->ensureModelSharesGuard($permission); }) ->map(function ($permission) { - return ['id' => $permission->id, 'values' => PermissionRegistrar::$teams && !is_a($this, Role::class) ? - [PermissionRegistrar::$teamsKey => app(PermissionRegistrar::class)->getPermissionsTeamId()] : [] + return ['id' => $permission->id, 'values' => PermissionRegistrar::$teams && ! is_a($this, Role::class) ? + [PermissionRegistrar::$teamsKey => app(PermissionRegistrar::class)->getPermissionsTeamId()] : [], ]; }) ->pluck('values', 'id')->toArray(); @@ -351,7 +351,7 @@ public function givePermissionTo(...$permissions) $model = $this->getModel(); if ($model->exists) { - if (PermissionRegistrar::$teams && !is_a($this, Role::class)) { + if (PermissionRegistrar::$teams && ! is_a($this, Role::class)) { $this->permissions()->wherePivot(PermissionRegistrar::$teamsKey, app(PermissionRegistrar::class)->getPermissionsTeamId())->sync($permissions, false); } else { $this->permissions()->sync($permissions, false); diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index b0003e5d3..f9f70b498 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -41,6 +41,7 @@ public function getRoleClass() public function roles(): BelongsToMany { $model_has_roles = config('permission.table_names.model_has_roles'); + return $this->morphToMany( config('permission.models.role'), 'model', @@ -120,8 +121,8 @@ public function assignRole(...$roles) $this->ensureModelSharesGuard($role); }) ->map(function ($role) { - return ['id' => $role->id, 'values' => PermissionRegistrar::$teams && !is_a($this, Permission::class) ? - [PermissionRegistrar::$teamsKey => app(PermissionRegistrar::class)->getPermissionsTeamId()] : [] + return ['id' => $role->id, 'values' => PermissionRegistrar::$teams && ! is_a($this, Permission::class) ? + [PermissionRegistrar::$teamsKey => app(PermissionRegistrar::class)->getPermissionsTeamId()] : [], ]; }) ->pluck('values', 'id')->toArray(); @@ -129,7 +130,7 @@ public function assignRole(...$roles) $model = $this->getModel(); if ($model->exists) { - if (PermissionRegistrar::$teams && !is_a($this, Permission::class)) { + if (PermissionRegistrar::$teams && ! is_a($this, Permission::class)) { $this->roles()->wherePivot(PermissionRegistrar::$teamsKey, app(PermissionRegistrar::class)->getPermissionsTeamId())->sync($roles, false); } else { $this->roles()->sync($roles, false); diff --git a/tests/CommandTest.php b/tests/CommandTest.php index 603af80d2..be1541f88 100644 --- a/tests/CommandTest.php +++ b/tests/CommandTest.php @@ -143,7 +143,7 @@ public function it_can_setup_teams_upgrade() $matchingFiles = glob(database_path('migrations/*_add_teams_fields.php')); $this->assertTrue(count($matchingFiles) > 0); - include_once $matchingFiles[count($matchingFiles)-1]; + include_once $matchingFiles[count($matchingFiles) - 1]; (new \AddTeamsFields())->up(); (new \AddTeamsFields())->up(); //test upgrade teams migration fresh diff --git a/tests/TeamHasPermissionsTest.php b/tests/TeamHasPermissionsTest.php index 9170166ae..62d3545e0 100644 --- a/tests/TeamHasPermissionsTest.php +++ b/tests/TeamHasPermissionsTest.php @@ -5,8 +5,7 @@ class TeamHasPermissionsTest extends HasPermissionsTest { /** @var bool */ - protected $hasTeams=true; - + protected $hasTeams = true; /** @test */ public function it_can_assign_same_and_different_permission_on_same_user_on_different_teams() diff --git a/tests/TeamHasRolesTest.php b/tests/TeamHasRolesTest.php index cdf120513..19c745e2a 100644 --- a/tests/TeamHasRolesTest.php +++ b/tests/TeamHasRolesTest.php @@ -7,7 +7,7 @@ class TeamHasRolesTest extends HasRolesTest { /** @var bool */ - protected $hasTeams=true; + protected $hasTeams = true; /** @test */ public function it_can_assign_same_and_different_roles_on_same_user_different_teams() diff --git a/tests/TestCase.php b/tests/TestCase.php index 1ea141566..f282b915d 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -37,7 +37,7 @@ abstract class TestCase extends Orchestra protected $useCustomModels = false; /** @var bool */ - protected $hasTeams=false; + protected $hasTeams = false; public function setUp(): void { From 579cc110f4afdf8a29d412f7aa153fc5559484d5 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 31 Aug 2021 14:11:38 -0400 Subject: [PATCH 073/648] Update CHANGELOG.md --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16263e84e..b0a014730 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,9 @@ All notable changes to `laravel-permission` will be documented in this file -## PENDING 5.0.0 - 2021-08-31 +## 5.0.0 - 2021-08-31 - Change default-guard-lookup to prefer current user's guard (see BC note in #1817 ) +- Teams/Groups feature (see docs, or PR #1804) - Customized pivots instead of `role_id`,`permission_id` #1823 From e50b26851d202c657638bf07310db39639c25ff1 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 31 Aug 2021 14:17:50 -0400 Subject: [PATCH 074/648] Update version link --- docs/introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/introduction.md b/docs/introduction.md index 17baa86d2..466402858 100644 --- a/docs/introduction.md +++ b/docs/introduction.md @@ -17,7 +17,7 @@ $user->assignRole('writer'); $role->givePermissionTo('edit articles'); ``` -If you're using multiple guards we've got you covered as well. Every guard will have its own set of permissions and roles that can be assigned to the guard's users. Read about it in the [using multiple guards](https://docs.spatie.be/laravel-permission/v4/basic-usage/multiple-guards/) section. +If you're using multiple guards we've got you covered as well. Every guard will have its own set of permissions and roles that can be assigned to the guard's users. Read about it in the [using multiple guards](https://docs.spatie.be/laravel-permission/v5/basic-usage/multiple-guards/) section. Because all permissions will be registered on [Laravel's gate](https://laravel.com/docs/authorization), you can check if a user has a permission with Laravel's default `can` function: From e5f8f818f1783d3f1b8cfd8912f947bc056cf421 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 31 Aug 2021 14:22:06 -0400 Subject: [PATCH 075/648] Update docs link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c06c55a16..df0e89b52 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ ## Documentation, Installation, and Usage Instructions -See the [DOCUMENTATION](https://docs.spatie.be/laravel-permission/v4/introduction/) for detailed installation and usage instructions. +See the [DOCUMENTATION](https://docs.spatie.be/laravel-permission/) for detailed installation and usage instructions. ## What It Does This package allows you to manage user permissions and roles in a database. From 8f966252547a8d0d8187897b586f67142106e286 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 31 Aug 2021 15:05:40 -0400 Subject: [PATCH 076/648] Update changelog.md --- docs/changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.md b/docs/changelog.md index 27d734a9d..3280d4f29 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -3,4 +3,4 @@ title: Changelog weight: 10 --- -All notable changes to laravel-permission are documented [on GitHub](https://github.com/spatie/laravel-permission/blob/master/CHANGELOG.md) +All notable changes to laravel-permission are documented [on GitHub](https://github.com/spatie/laravel-permission/blob/main/CHANGELOG.md) From dfcbbe0f9c5c8d8d06fccaa9dab894236ce4c491 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 31 Aug 2021 15:15:24 -0400 Subject: [PATCH 077/648] Update branch links --- CHANGELOG.md | 4 ++-- composer.json | 1 + docs/advanced-usage/exceptions.md | 2 +- docs/basic-usage/teams-permissions.md | 2 +- docs/installation-laravel.md | 6 +++--- docs/installation-lumen.md | 6 +++--- docs/prerequisites.md | 2 +- 7 files changed, 12 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0a014730..64c43460c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -193,8 +193,8 @@ https://github.com/laravel/framework/commit/fd6eb89b62ec09df1ffbee164831a827e83f The following changes are not "breaking", but worth making the updates to your app for consistency. 1. Config file: The `config/permission.php` file changed to move cache-related settings into a sub-array. **You should review the changes and merge the updates into your own config file.** Specifically the `expiration_time` value has moved into a sub-array entry, and the old top-level entry is no longer used. -See the master config file here: -https://github.com/spatie/laravel-permission/blob/master/config/permission.php +See the original config file here: +https://github.com/spatie/laravel-permission/blob/main/config/permission.php 2. Cache Resets: If your `app` or `tests` are clearing the cache by specifying the cache key, **it is better to use the built-in forgetCachedPermissions() method** so that it properly handles tagged cache entries. Here is the recommended change: ```diff diff --git a/composer.json b/composer.json index 86e3f8017..2810431cc 100644 --- a/composer.json +++ b/composer.json @@ -53,6 +53,7 @@ ] }, "branch-alias": { + "dev-main": "5.x-dev", "dev-master": "5.x-dev" } }, diff --git a/docs/advanced-usage/exceptions.md b/docs/advanced-usage/exceptions.md index 9e2425c51..b71939a3d 100644 --- a/docs/advanced-usage/exceptions.md +++ b/docs/advanced-usage/exceptions.md @@ -7,7 +7,7 @@ If you need to override exceptions thrown by this package, you can simply use no An example is shown below for your convenience, but nothing here is specific to this package other than the name of the exception. -You can find all the exceptions added by this package in the code here: https://github.com/spatie/laravel-permission/tree/master/src/Exceptions +You can find all the exceptions added by this package in the code here: https://github.com/spatie/laravel-permission/tree/main/src/Exceptions **app/Exceptions/Handler.php** diff --git a/docs/basic-usage/teams-permissions.md b/docs/basic-usage/teams-permissions.md index 58e0055b7..b0688ba66 100644 --- a/docs/basic-usage/teams-permissions.md +++ b/docs/basic-usage/teams-permissions.md @@ -3,7 +3,7 @@ title: Teams permissions weight: 3 --- -NOTE: Those changes must be made before performing the migration. If you have already run the migration and want to upgrade your solution, you can run the artisan console command `php artisan permission:setup-teams`, to create a new migration file named [xxxx_xx_xx_xx_add_teams_fields.php](https://github.com/spatie/laravel-permission/blob/master/database/migrations/add_teams_fields.php.stub) and then run `php artisan migrate` to upgrade your database tables. +NOTE: Those changes must be made before performing the migration. If you have already run the migration and want to upgrade your solution, you can run the artisan console command `php artisan permission:setup-teams`, to create a new migration file named [xxxx_xx_xx_xx_add_teams_fields.php](https://github.com/spatie/laravel-permission/blob/main/database/migrations/add_teams_fields.php.stub) and then run `php artisan migrate` to upgrade your database tables. When enabled, teams permissions offers you a flexible control for a variety of scenarios. The idea behind teams permissions is inspired by the default permission implementation of [Laratrust](https://laratrust.santigarcor.me/). diff --git a/docs/installation-laravel.md b/docs/installation-laravel.md index c87a5b29f..c37db5d91 100644 --- a/docs/installation-laravel.md +++ b/docs/installation-laravel.md @@ -26,14 +26,14 @@ This package can be used with Laravel 6.0 or higher. ]; ``` -5. You should publish [the migration](https://github.com/spatie/laravel-permission/blob/master/database/migrations/create_permission_tables.php.stub) and the [`config/permission.php` config file](https://github.com/spatie/laravel-permission/blob/master/config/permission.php) with: +5. You should publish [the migration](https://github.com/spatie/laravel-permission/blob/main/database/migrations/create_permission_tables.php.stub) and the [`config/permission.php` config file](https://github.com/spatie/laravel-permission/blob/main/config/permission.php) with: ``` php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" ``` 6. NOTE: If you are using UUIDs, see the Advanced section of the docs on UUID steps, before you continue. It explains some changes you may want to make to the migrations and config file before continuing. It also mentions important considerations after extending this package's models for UUID capability. - If you are going to use teams feature, you have to update your [`config/permission.php` config file](https://github.com/spatie/laravel-permission/blob/master/config/permission.php) and set `'teams' => true,`, if you want to use a custom foreign key for teams you must change `team_foreign_key`. + If you are going to use teams feature, you have to update your [`config/permission.php` config file](https://github.com/spatie/laravel-permission/blob/main/config/permission.php) and set `'teams' => true,`, if you want to use a custom foreign key for teams you must change `team_foreign_key`. 7. Clear your config cache. This package requires access to the `permission` config. Generally it's bad practice to do config-caching in a development environment. If you've been caching configurations locally, clear your config cache with either of these commands: @@ -52,4 +52,4 @@ This package can be used with Laravel 6.0 or higher. You can view the default config file contents at: -[https://github.com/spatie/laravel-permission/blob/master/config/permission.php](https://github.com/spatie/laravel-permission/blob/master/config/permission.php) +[https://github.com/spatie/laravel-permission/blob/main/config/permission.php](https://github.com/spatie/laravel-permission/blob/main/config/permission.php) diff --git a/docs/installation-lumen.md b/docs/installation-lumen.md index e1a825170..ff3d98455 100644 --- a/docs/installation-lumen.md +++ b/docs/installation-lumen.md @@ -5,7 +5,7 @@ weight: 5 NOTE: Lumen is **not** officially supported by this package. However, the following are some steps which may help get you started. -Lumen installation instructions can be found in the [Lumen documentation](https://lumen.laravel.com/docs/master). +Lumen installation instructions can be found in the [Lumen documentation](https://lumen.laravel.com/docs/main). Install the permissions package via Composer: @@ -52,7 +52,7 @@ $app->register(App\Providers\AuthServiceProvider::class); Ensure the application's database name/credentials are set in your `.env` (or `config/database.php` if you have one), and that the database exists. -NOTE: If you are going to use teams feature, you have to update your [`config/permission.php` config file](https://github.com/spatie/laravel-permission/blob/master/config/permission.php) and set `'teams' => true,`, if you want to use a custom foreign key for teams you must change `team_foreign_key`. +NOTE: If you are going to use teams feature, you have to update your [`config/permission.php` config file](https://github.com/spatie/laravel-permission/blob/main/config/permission.php) and set `'teams' => true,`, if you want to use a custom foreign key for teams you must change `team_foreign_key`. Run the migrations to create the tables for this package: @@ -68,7 +68,7 @@ NOTE: Remember that Laravel's authorization layer requires that your `User` mode ### User Table NOTE: If you are working with a fresh install of Lumen, then you probably also need a migration file for your Users table. You can create your own, or you can copy a basic one from Laravel: -[https://github.com/laravel/laravel/blob/master/database/migrations/2014_10_12_000000_create_users_table.php](https://github.com/laravel/laravel/blob/master/database/migrations/2014_10_12_000000_create_users_table.php) +[https://github.com/laravel/laravel/blob/main/database/migrations/2014_10_12_000000_create_users_table.php](https://github.com/laravel/laravel/blob/main/database/migrations/2014_10_12_000000_create_users_table.php) (You will need to run `php artisan migrate` after adding this file.) diff --git a/docs/prerequisites.md b/docs/prerequisites.md index ddcafbdb6..4508c6cdd 100644 --- a/docs/prerequisites.md +++ b/docs/prerequisites.md @@ -45,5 +45,5 @@ This package publishes a `config/permission.php` file. If you already have a fil MySQL 8.0 limits index keys to 1000 characters. This package publishes a migration which combines multiple columns in single index. With `utf8mb4` the 4-bytes-per-character requirement of `mb4` means the max length of the columns in the hybrid index can only be `125` characters. -Thus in your AppServiceProvider you will need to set `Schema::defaultStringLength(125)`. [See the Laravel Docs for instructions](https://laravel.com/docs/master/migrations#index-lengths-mysql-mariadb). +Thus in your AppServiceProvider you will need to set `Schema::defaultStringLength(125)`. [See the Laravel Docs for instructions](https://laravel.com/docs/migrations#index-lengths-mysql-mariadb). From e62b309d266373d06b96eb5e2300522b02e1524d Mon Sep 17 00:00:00 2001 From: Erik Niebla Date: Tue, 31 Aug 2021 14:37:59 -0500 Subject: [PATCH 078/648] Avoid unnecessary cache forget --- src/Traits/HasPermissions.php | 8 ++++-- src/Traits/HasRoles.php | 8 ++++-- tests/CacheTest.php | 46 ++++++++++++++++++++++++++++++++++- 3 files changed, 57 insertions(+), 5 deletions(-) diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index 128351186..5ac94cbda 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -371,7 +371,9 @@ function ($object) use ($permissions, $model) { ); } - $this->forgetCachedPermissions(); + if (is_a($this, get_class(app(PermissionRegistrar::class)->getRoleClass()))) { + $this->forgetCachedPermissions(); + } return $this; } @@ -401,7 +403,9 @@ public function revokePermissionTo($permission) { $this->permissions()->detach($this->getStoredPermission($permission)); - $this->forgetCachedPermissions(); + if (is_a($this, get_class(app(PermissionRegistrar::class)->getRoleClass()))) { + $this->forgetCachedPermissions(); + } $this->load('permissions'); diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index f9f70b498..006b742ed 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -150,7 +150,9 @@ function ($object) use ($roles, $model) { ); } - $this->forgetCachedPermissions(); + if (is_a($this, get_class($this->getPermissionClass()))) { + $this->forgetCachedPermissions(); + } return $this; } @@ -166,7 +168,9 @@ public function removeRole($role) $this->load('roles'); - $this->forgetCachedPermissions(); + if (is_a($this, get_class($this->getPermissionClass()))) { + $this->forgetCachedPermissions(); + } return $this; } diff --git a/tests/CacheTest.php b/tests/CacheTest.php index 1a1caa0ff..abb718bc7 100644 --- a/tests/CacheTest.php +++ b/tests/CacheTest.php @@ -104,7 +104,23 @@ public function it_flushes_the_cache_when_updating_a_role() } /** @test */ - public function it_flushes_the_cache_when_removing_a_role_from_a_user() + public function removing_a_permission_from_a_user_should_not_flush_the_cache() + { + $this->testUser->givePermissionTo('edit-articles'); + + $this->registrar->getPermissions(); + + $this->testUser->revokePermissionTo('edit-articles'); + + $this->resetQueryCount(); + + $this->registrar->getPermissions(); + + $this->assertQueryCount(0); + } + + /** @test */ + public function removing_a_role_from_a_user_should_not_flush_the_cache() { $this->testUser->assignRole('testRole'); @@ -116,6 +132,34 @@ public function it_flushes_the_cache_when_removing_a_role_from_a_user() $this->registrar->getPermissions(); + $this->assertQueryCount(0); + } + + /** @test */ + public function it_flushes_the_cache_when_removing_a_role_from_a_permission() + { + $this->testUserPermission->assignRole('testRole'); + + $this->registrar->getPermissions(); + + $this->testUserPermission->removeRole('testRole'); + + $this->resetQueryCount(); + + $this->registrar->getPermissions(); + + $this->assertQueryCount($this->cache_init_count + $this->cache_load_count + $this->cache_run_count); + } + + /** @test */ + public function it_flushes_the_cache_when_assign_a_permission_to_a_role() + { + $this->testUserRole->givePermissionTo('edit-articles'); + + $this->resetQueryCount(); + + $this->registrar->getPermissions(); + $this->assertQueryCount($this->cache_init_count + $this->cache_load_count + $this->cache_run_count); } From c2dee58d820711249d54773ed798fdad9f799dea Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 31 Aug 2021 16:21:28 -0400 Subject: [PATCH 079/648] Update CHANGELOG.md --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64c43460c..cf2a5cdf6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ All notable changes to `laravel-permission` will be documented in this file +## 5.1.0 - 2021-08-31 +- No longer flush cache on User role/perm assignment changes #1832 + NOTE: You should test your app to be sure that you don't accidentally have deep dependencies on cache resets happening automatically in these cases. + ALSO NOTE: If you have added custom code which depended on these flush operations, you may need to add your own cache-reset calls. + ## 5.0.0 - 2021-08-31 - Change default-guard-lookup to prefer current user's guard (see BC note in #1817 ) - Teams/Groups feature (see docs, or PR #1804) From 0ad6bf81d06a0f416740301c1b3dd7eb85fb8c92 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 31 Aug 2021 16:28:10 -0400 Subject: [PATCH 080/648] Update cache.md --- docs/advanced-usage/cache.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/docs/advanced-usage/cache.md b/docs/advanced-usage/cache.md index 1272b7ea6..3b1f8160e 100644 --- a/docs/advanced-usage/cache.md +++ b/docs/advanced-usage/cache.md @@ -10,9 +10,6 @@ Role and Permission data are cached to speed up performance. When you **use the built-in functions** for manipulating roles and permissions, the cache is automatically reset for you, and relations are automatically reloaded for the current model record: ```php -$user->assignRole('writer'); -$user->removeRole('writer'); -$user->syncRoles(params); $role->givePermissionTo('edit articles'); $role->revokePermissionTo('edit articles'); $role->syncPermissions(params); @@ -25,6 +22,14 @@ HOWEVER, if you manipulate permission/role data directly in the database instead Additionally, because the Role and Permission models are Eloquent models which implement the `RefreshesPermissionCache` trait, creating and deleting Roles and Permissions will automatically clear the cache. If you have created your own models which do not extend the default models then you will need to implement the trait yourself. +**NOTE: User-specific role/permission assignments are kept in-memory since v4.4.0, so the cache-reset is no longer called since v5.1.0 when updating User-related assignments.** +Examples: +```php +// These do not call a cache-reset, because the User-related assignments are in-memory. +$user->assignRole('writer'); +$user->removeRole('writer'); +$user->syncRoles(params); +``` ### Manual cache reset To manually reset the cache for this package, you can run the following in your app code: From 9b7523b5e76e8667813f35a884c52c15cf6c5caa Mon Sep 17 00:00:00 2001 From: Freek Van der Herten Date: Wed, 1 Sep 2021 09:34:47 +0200 Subject: [PATCH 081/648] Update _index.md --- docs/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_index.md b/docs/_index.md index 2bf4d0fd1..591491f89 100644 --- a/docs/_index.md +++ b/docs/_index.md @@ -1,5 +1,5 @@ --- -title: v4 +title: v5 slogan: Associate users with roles and permissions githubUrl: https://github.com/spatie/laravel-permission branch: main From 9006b9d9fcac07a74ce3ed7aca8d81887cf02604 Mon Sep 17 00:00:00 2001 From: Danilo Pinotti Date: Wed, 1 Sep 2021 11:16:51 -0300 Subject: [PATCH 082/648] Avoid Roles over-hydratation --- src/PermissionRegistrar.php | 73 +++++++++++++++++++++++++------------ 1 file changed, 50 insertions(+), 23 deletions(-) diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index 30c63d1e6..f46e7fbb7 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -32,6 +32,9 @@ class PermissionRegistrar /** @var string */ public static $cacheKey; + /** @var array */ + private $cachedRoles = []; + /** * PermissionRegistrar constructor. * @@ -117,30 +120,36 @@ public function clearClassPermissions() */ private function loadPermissions() { - if ($this->permissions === null) { - $this->permissions = $this->cache->remember(self::$cacheKey, self::$cacheExpirationTime, function () { - // make the cache smaller using an array with only required fields - return $this->getPermissionClass()->select('id', 'id as i', 'name as n', 'guard_name as g') - ->with('roles:id,id as i,name as n,guard_name as g')->get() - ->map(function ($permission) { - return $permission->only('i', 'n', 'g') + - ['r' => $permission->roles->map->only('i', 'n', 'g')->all()]; - })->all(); + if ($this->permissions !== null) { + return; + } + + $this->permissions = $this->cache->remember(self::$cacheKey, self::$cacheExpirationTime, function () { + // make the cache smaller using an array with only required fields + return $this->getPermissionClass()->select('id', 'id as i', 'name as n', 'guard_name as g') + ->with('roles:id,id as i,name as n,guard_name as g')->get() + ->map(function ($permission) { + return $permission->only('i', 'n', 'g') + + ['r' => $permission->roles->map->only('i', 'n', 'g')->all()]; + })->all(); + }); + + if (is_array($this->permissions)) { + $this->permissions = $this->getPermissionClass()::hydrate( + collect($this->permissions)->map(function ($item) { + return ['id' => $item['i'] ?? $item['id'], 'name' => $item['n'] ?? $item['name'], 'guard_name' => $item['g'] ?? $item['guard_name']]; + })->all() + ) + ->each(function ($permission, $i) { + $roles = Collection::make($this->permissions[$i]['r'] ?? $this->permissions[$i]['roles'] ?? []) + ->map(function ($item) { + return $this->getHydratedRole($item); + }); + + $permission->setRelation('roles', $roles); }); - if (is_array($this->permissions)) { - $this->permissions = $this->getPermissionClass()::hydrate( - collect($this->permissions)->map(function ($item) { - return ['id' => $item['i'] ?? $item['id'], 'name' => $item['n'] ?? $item['name'], 'guard_name' => $item['g'] ?? $item['guard_name']]; - })->all() - ) - ->each(function ($permission, $i) { - $permission->setRelation('roles', $this->getRoleClass()::hydrate( - collect($this->permissions[$i]['r'] ?? $this->permissions[$i]['roles'] ?? [])->map(function ($item) { - return ['id' => $item['i'] ?? $item['id'], 'name' => $item['n'] ?? $item['name'], 'guard_name' => $item['g'] ?? $item['guard_name']]; - })->all() - )); - }); - } + + $this->cachedRoles = []; } } @@ -211,4 +220,22 @@ public function getCacheStore(): \Illuminate\Contracts\Cache\Store { return $this->cache->getStore(); } + + private function getHydratedRole(array $item) + { + $roleId = $item['i'] ?? $item['id']; + + if (isset($this->cachedRoles[$roleId])) { + return $this->cachedRoles[$roleId]; + } + + $roleClass = $this->getRoleClass(); + $roleInstance = new $roleClass; + return $this->cachedRoles[$roleId] = $roleInstance->newFromBuilder([ + 'id' => $roleId, + 'name' => $item['n'] ?? $item['name'], + 'guard_name' => $item['g'] ?? $item['guard_name'], + ]); + + } } From 43e848efcd84b2f199c7a51a94d4db69d51b9632 Mon Sep 17 00:00:00 2001 From: Danilo Pinotti Date: Wed, 1 Sep 2021 12:24:36 -0300 Subject: [PATCH 083/648] Add test --- tests/CacheTest.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/CacheTest.php b/tests/CacheTest.php index 1a1caa0ff..19406486d 100644 --- a/tests/CacheTest.php +++ b/tests/CacheTest.php @@ -203,6 +203,17 @@ public function get_all_permissions_should_use_the_cache() $this->assertQueryCount(2); } + /** @test */ + public function get_all_permissions_should_not_over_hydrate_roles() + { + $this->testUserRole->givePermissionTo(['edit-articles', 'edit-news']); + $permissions = $this->registrar->getPermissions(); + $roles = $permissions->flatMap->roles; + + // Should have same object reference + $this->assertSame($roles[0], $roles[1]); + } + /** @test */ public function it_can_reset_the_cache_with_artisan_command() { From 75c52d2948f87fa887065b1d42d71f1d9b90a6a5 Mon Sep 17 00:00:00 2001 From: drbyte Date: Wed, 1 Sep 2021 17:39:35 +0000 Subject: [PATCH 084/648] Fix styling --- src/PermissionRegistrar.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index f46e7fbb7..c1d630970 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -231,11 +231,11 @@ private function getHydratedRole(array $item) $roleClass = $this->getRoleClass(); $roleInstance = new $roleClass; + return $this->cachedRoles[$roleId] = $roleInstance->newFromBuilder([ 'id' => $roleId, 'name' => $item['n'] ?? $item['name'], 'guard_name' => $item['g'] ?? $item['guard_name'], ]); - } } From 3c9d7ae7683081ee90a4e2297f4e58aff3492a1e Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Wed, 1 Sep 2021 13:40:58 -0400 Subject: [PATCH 085/648] Update CHANGELOG.md --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42a896f54..1a9242af6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ All notable changes to `laravel-permission` will be documented in this file +## 4.4.1 - 2021-09-01 +- Avoid Roles over-hydration #1834 + ## 4.4.0 - 2021-08-28 - Avoid BC break (removed interface change) on cache change added in 4.3.0 #1826 - Made cache even smaller #1826 From 27a7ad757157666eeb9401d17ae6e082b4b021b0 Mon Sep 17 00:00:00 2001 From: Niels Vanpachtenbeke <10651054+Nielsvanpach@users.noreply.github.com> Date: Tue, 7 Sep 2021 09:41:58 +0200 Subject: [PATCH 086/648] Update .php_cs.dist.php --- .php_cs.dist.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.php_cs.dist.php b/.php_cs.dist.php index bd7148822..e89f88628 100644 --- a/.php_cs.dist.php +++ b/.php_cs.dist.php @@ -12,7 +12,7 @@ return (new PhpCsFixer\Config()) ->setRules([ - '@PSR2' => true, + '@PSR12' => true, 'array_syntax' => ['syntax' => 'short'], 'ordered_imports' => ['sort_algorithm' => 'alpha'], 'no_unused_imports' => true, From 2512d5a22aaae0bab8d62a02e502a603776da9da Mon Sep 17 00:00:00 2001 From: Nielsvanpach Date: Tue, 7 Sep 2021 07:42:33 +0000 Subject: [PATCH 087/648] Fix styling --- src/PermissionRegistrar.php | 2 +- src/Traits/HasPermissions.php | 4 ++-- src/WildcardPermission.php | 6 +++--- tests/HasPermissionsWithCustomModelsTest.php | 2 +- tests/TestCase.php | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index c82309f8c..c3308b201 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -266,7 +266,7 @@ private function getHydratedRole(array $item) } $roleClass = $this->getRoleClass(); - $roleInstance = new $roleClass; + $roleInstance = new $roleClass(); return $this->cachedRoles[$roleId] = $roleInstance->newFromBuilder([ 'id' => $roleId, diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index 5ac94cbda..00b7a82ae 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -139,7 +139,7 @@ public function hasPermissionTo($permission, $guardName = null): bool } if (! $permission instanceof Permission) { - throw new PermissionDoesNotExist; + throw new PermissionDoesNotExist(); } return $this->hasDirectPermission($permission) || $this->hasPermissionViaRole($permission); @@ -285,7 +285,7 @@ public function hasDirectPermission($permission): bool } if (! $permission instanceof Permission) { - throw new PermissionDoesNotExist; + throw new PermissionDoesNotExist(); } return $this->permissions->contains('id', $permission->id); diff --git a/src/WildcardPermission.php b/src/WildcardPermission.php index 22d142ba0..a1b20a707 100644 --- a/src/WildcardPermission.php +++ b/src/WildcardPermission.php @@ -8,13 +8,13 @@ class WildcardPermission { /** @var string */ - const WILDCARD_TOKEN = '*'; + public const WILDCARD_TOKEN = '*'; /** @var string */ - const PART_DELIMITER = '.'; + public const PART_DELIMITER = '.'; /** @var string */ - const SUBPART_DELIMITER = ','; + public const SUBPART_DELIMITER = ','; /** @var string */ protected $permission; diff --git a/tests/HasPermissionsWithCustomModelsTest.php b/tests/HasPermissionsWithCustomModelsTest.php index 8d0ab6442..92970121a 100644 --- a/tests/HasPermissionsWithCustomModelsTest.php +++ b/tests/HasPermissionsWithCustomModelsTest.php @@ -6,7 +6,7 @@ class HasPermissionsWithCustomModelsTest extends HasPermissionsTest { /** @var bool */ protected $useCustomModels = true; - + /** @test */ public function it_can_use_custom_models() { diff --git a/tests/TestCase.php b/tests/TestCase.php index f282b915d..350d90188 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -32,7 +32,7 @@ abstract class TestCase extends Orchestra /** @var \Spatie\Permission\Models\Permission */ protected $testAdminPermission; - + /** @var bool */ protected $useCustomModels = false; From f827b6dc097e9d019946d6a81c0d3c782afb4d37 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Thu, 28 Oct 2021 02:38:55 -0500 Subject: [PATCH 088/648] Fix detaching on all teams intstead of only current #1888 (#1890) Co-authored-by: Erik Niebla --- src/Traits/HasPermissions.php | 24 +++++++++++++++------ src/Traits/HasRoles.php | 24 +++++++++++++++------ tests/TeamHasPermissionsTest.php | 33 ++++++++++++++++++++++++++++ tests/TeamHasRolesTest.php | 37 ++++++++++++++++++++++++++++++-- 4 files changed, 102 insertions(+), 16 deletions(-) diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index 00b7a82ae..d6377a4ce 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -317,6 +317,20 @@ public function getAllPermissions(): Collection return $permissions->sort()->values(); } + /** + * Add teams pivot if teams are enabled + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + */ + protected function getPermissionsRelation(){ + $relation = $this->permissions(); + if (PermissionRegistrar::$teams && ! is_a($this, Role::class)) { + $relation->wherePivot(PermissionRegistrar::$teamsKey, app(PermissionRegistrar::class)->getPermissionsTeamId()); + } + + return $relation; + } + /** * Grant the given permission(s) to a role. * @@ -351,11 +365,7 @@ public function givePermissionTo(...$permissions) $model = $this->getModel(); if ($model->exists) { - if (PermissionRegistrar::$teams && ! is_a($this, Role::class)) { - $this->permissions()->wherePivot(PermissionRegistrar::$teamsKey, app(PermissionRegistrar::class)->getPermissionsTeamId())->sync($permissions, false); - } else { - $this->permissions()->sync($permissions, false); - } + $this->getPermissionsRelation()->sync($permissions, false); $model->load('permissions'); } else { $class = \get_class($model); @@ -387,7 +397,7 @@ function ($object) use ($permissions, $model) { */ public function syncPermissions(...$permissions) { - $this->permissions()->detach(); + $this->getPermissionsRelation()->detach(); return $this->givePermissionTo($permissions); } @@ -401,7 +411,7 @@ public function syncPermissions(...$permissions) */ public function revokePermissionTo($permission) { - $this->permissions()->detach($this->getStoredPermission($permission)); + $this->getPermissionsRelation()->detach($this->getStoredPermission($permission)); if (is_a($this, get_class(app(PermissionRegistrar::class)->getRoleClass()))) { $this->forgetCachedPermissions(); diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index 006b742ed..214c34c40 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -96,6 +96,20 @@ public function scopeRole(Builder $query, $roles, $guard = null): Builder }); } + /** + * Add teams pivot if teams are enabled + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + */ + protected function getRolesRelation(){ + $relation = $this->roles(); + if (PermissionRegistrar::$teams && ! is_a($this, Permission::class)) { + $relation->wherePivot(PermissionRegistrar::$teamsKey, app(PermissionRegistrar::class)->getPermissionsTeamId()); + } + + return $relation; + } + /** * Assign the given role to the model. * @@ -130,11 +144,7 @@ public function assignRole(...$roles) $model = $this->getModel(); if ($model->exists) { - if (PermissionRegistrar::$teams && ! is_a($this, Permission::class)) { - $this->roles()->wherePivot(PermissionRegistrar::$teamsKey, app(PermissionRegistrar::class)->getPermissionsTeamId())->sync($roles, false); - } else { - $this->roles()->sync($roles, false); - } + $this->getRolesRelation()->sync($roles, false); $model->load('roles'); } else { $class = \get_class($model); @@ -164,7 +174,7 @@ function ($object) use ($roles, $model) { */ public function removeRole($role) { - $this->roles()->detach($this->getStoredRole($role)); + $this->getRolesRelation()->detach($this->getStoredRole($role)); $this->load('roles'); @@ -184,7 +194,7 @@ public function removeRole($role) */ public function syncRoles(...$roles) { - $this->roles()->detach(); + $this->getRolesRelation()->detach(); return $this->assignRole($roles); } diff --git a/tests/TeamHasPermissionsTest.php b/tests/TeamHasPermissionsTest.php index 62d3545e0..4fc345225 100644 --- a/tests/TeamHasPermissionsTest.php +++ b/tests/TeamHasPermissionsTest.php @@ -70,4 +70,37 @@ public function it_can_list_all_the_coupled_permissions_both_directly_and_via_ro $this->testUser->getAllPermissions()->pluck('name')->sort()->values() ); } + + /** @test */ + public function it_can_sync_or_remove_permission_without_detach_on_different_teams() + { + $this->setPermissionsTeamId(1); + $this->testUser->load('permissions'); + $this->testUser->syncPermissions('edit-articles', 'edit-news'); + + $this->setPermissionsTeamId(2); + $this->testUser->load('permissions'); + $this->testUser->syncPermissions('edit-articles', 'edit-blog'); + + $this->setPermissionsTeamId(1); + $this->testUser->load('permissions'); + + $this->assertEquals( + collect(['edit-articles', 'edit-news']), + $this->testUser->getPermissionNames()->sort()->values() + ); + + $this->testUser->revokePermissionTo('edit-articles'); + $this->assertEquals( + collect(['edit-news']), + $this->testUser->getPermissionNames()->sort()->values() + ); + + $this->setPermissionsTeamId(2); + $this->testUser->load('permissions'); + $this->assertEquals( + collect(['edit-articles', 'edit-blog']), + $this->testUser->getPermissionNames()->sort()->values() + ); + } } diff --git a/tests/TeamHasRolesTest.php b/tests/TeamHasRolesTest.php index 19c745e2a..387322f44 100644 --- a/tests/TeamHasRolesTest.php +++ b/tests/TeamHasRolesTest.php @@ -24,12 +24,10 @@ public function it_can_assign_same_and_different_roles_on_same_user_different_te $this->setPermissionsTeamId(1); $this->testUser->load('roles'); - $this->testUser->assignRole('testRole', 'testRole2'); $this->setPermissionsTeamId(2); $this->testUser->load('roles'); - $this->testUser->assignRole('testRole', 'testRole3'); $this->setPermissionsTeamId(1); @@ -59,4 +57,39 @@ public function it_can_assign_same_and_different_roles_on_same_user_different_te $this->assertTrue($this->testUser->hasExactRoles(['testRole', 'testRole3', 'testRole4'])); $this->assertTrue($this->testUser->hasRole($testRole4NoTeam)); // global role team=null } + + /** @test */ + public function it_can_sync_or_remove_roles_without_detach_on_different_teams(){ + app(Role::class)->create(['name' => 'testRole3', 'team_test_id' => 2]); + + $this->setPermissionsTeamId(1); + $this->testUser->load('roles'); + $this->testUser->syncRoles('testRole', 'testRole2'); + + $this->setPermissionsTeamId(2); + $this->testUser->load('roles'); + $this->testUser->syncRoles('testRole', 'testRole3'); + + $this->setPermissionsTeamId(1); + $this->testUser->load('roles'); + + $this->assertEquals( + collect(['testRole', 'testRole2']), + $this->testUser->getRoleNames()->sort()->values() + ); + + $this->testUser->removeRole('testRole'); + $this->assertEquals( + collect(['testRole2']), + $this->testUser->getRoleNames()->sort()->values() + ); + + $this->setPermissionsTeamId(2); + $this->testUser->load('roles'); + + $this->assertEquals( + collect(['testRole', 'testRole3']), + $this->testUser->getRoleNames()->sort()->values() + ); + } } From 05b8206832314b2efdcaa12c1efcec563def54b0 Mon Sep 17 00:00:00 2001 From: freekmurze Date: Thu, 28 Oct 2021 07:39:33 +0000 Subject: [PATCH 089/648] Fix styling --- src/Traits/HasPermissions.php | 3 ++- src/Traits/HasRoles.php | 3 ++- tests/TeamHasRolesTest.php | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index d6377a4ce..3e0d4072b 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -322,7 +322,8 @@ public function getAllPermissions(): Collection * * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany */ - protected function getPermissionsRelation(){ + protected function getPermissionsRelation() + { $relation = $this->permissions(); if (PermissionRegistrar::$teams && ! is_a($this, Role::class)) { $relation->wherePivot(PermissionRegistrar::$teamsKey, app(PermissionRegistrar::class)->getPermissionsTeamId()); diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index 214c34c40..24283ff66 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -101,7 +101,8 @@ public function scopeRole(Builder $query, $roles, $guard = null): Builder * * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany */ - protected function getRolesRelation(){ + protected function getRolesRelation() + { $relation = $this->roles(); if (PermissionRegistrar::$teams && ! is_a($this, Permission::class)) { $relation->wherePivot(PermissionRegistrar::$teamsKey, app(PermissionRegistrar::class)->getPermissionsTeamId()); diff --git a/tests/TeamHasRolesTest.php b/tests/TeamHasRolesTest.php index 387322f44..b07614d8e 100644 --- a/tests/TeamHasRolesTest.php +++ b/tests/TeamHasRolesTest.php @@ -59,7 +59,8 @@ public function it_can_assign_same_and_different_roles_on_same_user_different_te } /** @test */ - public function it_can_sync_or_remove_roles_without_detach_on_different_teams(){ + public function it_can_sync_or_remove_roles_without_detach_on_different_teams() + { app(Role::class)->create(['name' => 'testRole3', 'team_test_id' => 2]); $this->setPermissionsTeamId(1); From 8b304f2890be313b0b31cd5bb54af0cd2a89b469 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Thu, 28 Oct 2021 02:39:58 -0500 Subject: [PATCH 090/648] Add uuid compatibility on team_id (#1857) Co-authored-by: Erik Niebla --- src/PermissionRegistrar.php | 13 ++++++++++--- src/helpers.php | 14 ++++++++++++-- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index c3308b201..cdf157e0b 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -101,14 +101,21 @@ protected function getCacheStoreFromConfig(): \Illuminate\Contracts\Cache\Reposi /** * Set the team id for teams/groups support, this id is used when querying permissions/roles * - * @param int $id + * @param int|string|\Illuminate\Database\Eloquent\Model $id */ - public function setPermissionsTeamId(?int $id) + public function setPermissionsTeamId($id) { + if ($id instanceof \Illuminate\Database\Eloquent\Model) { + $id = $id->getKey(); + } $this->teamId = $id; } - public function getPermissionsTeamId(): ?int + /** + * + * @return int|string + */ + public function getPermissionsTeamId() { return $this->teamId; } diff --git a/src/helpers.php b/src/helpers.php index 2b80ea417..f4df0a7cc 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -21,11 +21,21 @@ function getModelForGuard(string $guard) if (! function_exists('setPermissionsTeamId')) { /** - * @param int $id + * @param int|string|\Illuminate\Database\Eloquent\Model $id * */ - function setPermissionsTeamId(int $id) + function setPermissionsTeamId($id) { app(\Spatie\Permission\PermissionRegistrar::class)->setPermissionsTeamId($id); } } + +if (! function_exists('getPermissionsTeamId')) { + /** + * @return int|string + */ + function getPermissionsTeamId() + { + app(\Spatie\Permission\PermissionRegistrar::class)->getPermissionsTeamId(); + } +} From f1bdffdb16a7579a5978e13815be05e9df31d3e9 Mon Sep 17 00:00:00 2001 From: Tim Schwartz Date: Thu, 28 Oct 2021 02:43:49 -0500 Subject: [PATCH 091/648] Adds setRoleClass method to PermissionRegistrar (#1867) --- src/PermissionRegistrar.php | 11 ++++++++++- tests/RoleTest.php | 27 +++++++++++++++++++++++++++ tests/RuntimeRole.php | 11 +++++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 tests/RuntimeRole.php diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index cdf157e0b..038da6c38 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -240,7 +240,8 @@ public function getPermissionClass(): Permission public function setPermissionClass($permissionClass) { $this->permissionClass = $permissionClass; - + config()->set('permission.models.permission', $permissionClass); + app()->bind(Permission::class, $permissionClass); return $this; } @@ -254,6 +255,14 @@ public function getRoleClass(): Role return app($this->roleClass); } + public function setRoleClass($roleClass) + { + $this->roleClass = $roleClass; + config()->set('permission.models.role', $roleClass); + app()->bind(Role::class, $roleClass); + return $this; + } + /** * Get the instance of the Cache Store. * diff --git a/tests/RoleTest.php b/tests/RoleTest.php index 89d51804a..3ea137902 100644 --- a/tests/RoleTest.php +++ b/tests/RoleTest.php @@ -8,6 +8,8 @@ use Spatie\Permission\Exceptions\RoleAlreadyExists; use Spatie\Permission\Exceptions\RoleDoesNotExist; use Spatie\Permission\Models\Permission; +use Spatie\Permission\Test\RuntimeRole; +use Spatie\Permission\PermissionRegistrar; class RoleTest extends TestCase { @@ -239,4 +241,29 @@ public function it_belongs_to_the_default_guard_by_default() $this->testUserRole->guard_name ); } + + /** @test */ + public function it_can_change_role_class_on_runtime() + { + $role = app(Role::class)->create(['name' => 'test-role-old']); + $this->assertNotInstanceOf(RuntimeRole::class, $role); + + $role->givePermissionTo('edit-articles'); + + app('config')->set('permission.models.role', RuntimeRole::class); + app()->bind(Role::class, RuntimeRole::class); + app(PermissionRegistrar::class)->setRoleClass(RuntimeRole::class); + + $permission = app(Permission::class)->findByName('edit-articles'); + $this->assertInstanceOf(RuntimeRole::class, $permission->roles[0]); + $this->assertSame('test-role-old', $permission->roles[0]->name); + + $role = app(Role::class)->create(['name' => 'test-role']); + $this->assertInstanceOf(RuntimeRole::class, $role); + + $this->testUser->assignRole('test-role'); + $this->assertTrue($this->testUser->hasRole('test-role')); + $this->assertInstanceOf(RuntimeRole::class, $this->testUser->roles[0]); + $this->assertSame('test-role', $this->testUser->roles[0]->name); + } } diff --git a/tests/RuntimeRole.php b/tests/RuntimeRole.php new file mode 100644 index 000000000..82f24a09f --- /dev/null +++ b/tests/RuntimeRole.php @@ -0,0 +1,11 @@ + Date: Thu, 28 Oct 2021 11:14:14 +0330 Subject: [PATCH 092/648] Load permissions for preventLazyLoading (#1884) --- src/Commands/Show.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Commands/Show.php b/src/Commands/Show.php index f7cc2b343..fbd61967a 100644 --- a/src/Commands/Show.php +++ b/src/Commands/Show.php @@ -35,6 +35,7 @@ public function handle() $this->info("Guard: $guard"); $roles = $roleClass::whereGuardName($guard) + ->with('permissions') ->when(config('permission.teams'), function ($q) use ($team_key) { $q->orderBy($team_key); }) From 5285f6b936819d1eef3c3b2d36397ba62e6a76cd Mon Sep 17 00:00:00 2001 From: freekmurze Date: Thu, 28 Oct 2021 07:45:03 +0000 Subject: [PATCH 093/648] Fix styling --- src/PermissionRegistrar.php | 2 ++ tests/RoleTest.php | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index 038da6c38..0326be646 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -242,6 +242,7 @@ public function setPermissionClass($permissionClass) $this->permissionClass = $permissionClass; config()->set('permission.models.permission', $permissionClass); app()->bind(Permission::class, $permissionClass); + return $this; } @@ -260,6 +261,7 @@ public function setRoleClass($roleClass) $this->roleClass = $roleClass; config()->set('permission.models.role', $roleClass); app()->bind(Role::class, $roleClass); + return $this; } diff --git a/tests/RoleTest.php b/tests/RoleTest.php index 3ea137902..61a958b8d 100644 --- a/tests/RoleTest.php +++ b/tests/RoleTest.php @@ -8,7 +8,6 @@ use Spatie\Permission\Exceptions\RoleAlreadyExists; use Spatie\Permission\Exceptions\RoleDoesNotExist; use Spatie\Permission\Models\Permission; -use Spatie\Permission\Test\RuntimeRole; use Spatie\Permission\PermissionRegistrar; class RoleTest extends TestCase From 2843185ab8cec3c08470024248e99ce55606f3ac Mon Sep 17 00:00:00 2001 From: erikn69 Date: Thu, 28 Oct 2021 02:46:03 -0500 Subject: [PATCH 094/648] Doc for `Super Admin` on teams (#1845) Co-authored-by: Erik Niebla --- docs/basic-usage/teams-permissions.md | 32 ++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/docs/basic-usage/teams-permissions.md b/docs/basic-usage/teams-permissions.md index b0688ba66..a9152ee71 100644 --- a/docs/basic-usage/teams-permissions.md +++ b/docs/basic-usage/teams-permissions.md @@ -53,7 +53,7 @@ NOTE: You must add your custom `Middleware` to `$middlewarePriority` on `app/Htt When creating a role you can pass the `team_id` as an optional parameter ```php -// with null team_id it creates a global role, global roles can be used from any team and they are unique +// with null team_id it creates a global role, global roles can be assigned to any team and they are unique Role::create(['name' => 'writer', 'team_id' => null]); // creates a role with team_id = 1, team roles can have the same name on different teams @@ -66,3 +66,33 @@ Role::create(['name' => 'reviewer']); ## Roles/Permissions Assignment & Removal The role/permission assignment and removal are the same, but they take the global `team_id` set on login for sync. + +## Defining a Super-Admin on Teams + +Global roles can be assigned to different teams, `team_id` as the primary key of the relationships is always required. If you want a "Super Admin" global role for a user, when you creates a new team you must assign it to your user. Example: + +```php +namespace App\Models; + +class YourTeamModel extends \Illuminate\Database\Eloquent\Model +{ + // ... + public static function boot() + { + parent::boot(); + + // here assign this team to a global user with global default role + self::created(function ($model) { + // get session team_id for restore it later + $session_team_id = getPermissionsTeamId(); + // set actual new team_id to package instance + setPermissionsTeamId($model); + // get the admin user and assign roles/permissions on new team model + User::find('your_user_id')->assignRole('Super Admin'); + // restore session team_id to package instance + setPermissionsTeamId($session_team_id); + } + } + // ... +} +``` From b7a2df8fa8804ccb194d3759f59b690ad314efe5 Mon Sep 17 00:00:00 2001 From: Freek Van der Herten Date: Thu, 28 Oct 2021 09:56:18 +0200 Subject: [PATCH 095/648] Update CHANGELOG.md --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e42e28728..c5cdc94b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to `laravel-permission` will be documented in this file +## 5.2.0 - 2021-10-28 + +- [V5] Fix detaching on all teams intstead of only current #1888 by @erikn69 in https://github.com/spatie/laravel-permission/pull/1890 +- [V5] Add uuid compatibility support on teams by @erikn69 in https://github.com/spatie/laravel-permission/pull/1857 +- Adds setRoleClass method to PermissionRegistrar by @timschwartz in https://github.com/spatie/laravel-permission/pull/1867 +- Load permissions for preventLazyLoading by @bahramsadin in https://github.com/spatie/laravel-permission/pull/1884 +- [V5] Doc for `Super Admin` on teams by @erikn69 in https://github.com/spatie/laravel-permission/pull/1845 + ## 5.1.1 - 2021-09-01 - Avoid Roles over-hydration #1834 From fb4f0278eec48be3e8fb019dd283ea18c25e30d5 Mon Sep 17 00:00:00 2001 From: Rizkhal Date: Fri, 29 Oct 2021 21:55:28 +0900 Subject: [PATCH 096/648] Update blade-directives.md (#1903) --- docs/basic-usage/blade-directives.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/basic-usage/blade-directives.md b/docs/basic-usage/blade-directives.md index aafa016b4..a3a4b8af1 100644 --- a/docs/basic-usage/blade-directives.md +++ b/docs/basic-usage/blade-directives.md @@ -92,7 +92,7 @@ Alternatively, `@unlessrole` gives the reverse for checking a singular role, lik You can also determine if a user has exactly all of a given list of roles: ```php -@hasexactroles('writer|admin'); +@hasexactroles('writer|admin') I am both a writer and an admin and nothing else! @else I do not have all of these roles or have more other roles... From b3a995c436e9cd616217153fd49531845bd7c219 Mon Sep 17 00:00:00 2001 From: Muhammad Umar Ulhaq <15181389+ulhaq@users.noreply.github.com> Date: Fri, 29 Oct 2021 15:02:34 +0200 Subject: [PATCH 097/648] Option for custom logic for checking permissions (#1891) * option for custom logic for checking permissions added * tests * docs for Custom Permission Check added --- config/permission.php | 7 ++++ .../advanced-usage/custom-permission-check.md | 29 +++++++++++++++ docs/advanced-usage/other.md | 2 +- docs/advanced-usage/phpstorm.md | 2 +- docs/advanced-usage/timestamps.md | 2 +- docs/advanced-usage/ui-options.md | 2 +- docs/advanced-usage/uuid.md | 2 +- src/PermissionServiceProvider.php | 6 ++-- tests/CustomGateTest.php | 35 +++++++++++++++++++ tests/TestCase.php | 1 + 10 files changed, 81 insertions(+), 7 deletions(-) create mode 100644 docs/advanced-usage/custom-permission-check.md create mode 100644 tests/CustomGateTest.php diff --git a/config/permission.php b/config/permission.php index c7029fa5b..5b6e184c3 100644 --- a/config/permission.php +++ b/config/permission.php @@ -96,6 +96,13 @@ 'team_foreign_key' => 'team_id', ], + /* + * When set to true, the method for checking permissions will be registered on the gate. + * Set this to false, if you want to implement custom logic for checking permissions. + */ + + 'register_permission_check_method' => true, + /* * When set to true the package implements teams using the 'team_foreign_key'. If you want * the migrations to register the 'team_foreign_key', you must set this to true diff --git a/docs/advanced-usage/custom-permission-check.md b/docs/advanced-usage/custom-permission-check.md new file mode 100644 index 000000000..f6bfb84d8 --- /dev/null +++ b/docs/advanced-usage/custom-permission-check.md @@ -0,0 +1,29 @@ +--- +title: Custom Permission Check +weight: 6 +--- + +By default, a method is registered on [Laravel's gate](https://laravel.com/docs/authorization). This method is responsible for checking if the user has the required permission or not. Whether a user has a permission or not is determined by checking the user's permissions stored in the database. + +However, in some cases, you might want to implement custom logic for checking if the user has a permission or not. + +Let's say that your application uses access tokens for authentication and when issuing the tokens, you add a custom claim containing all the permissions the user has. In this case, if you want to check whether the user has the required permission or not based on the permissions in your custom claim in the access token, then you need to implement your own logic for handling this. + +You could, for example, create a `before` method to handle this: + +**app/Providers/AuthServiceProvider.php** +```php +public function boot() +{ + ... + + Gate::before(function ($user, $ability) { + return $user->hasTokenPermission($ability) ?: null; + }); +} +``` +Here `hasTokenPermission` is a custom method you need to implement yourself. + +### Register Permission Check Method +By default, `register_permission_check_method` is set to `true`. +Only set this to false if you want to implement custom logic for checking permissions. \ No newline at end of file diff --git a/docs/advanced-usage/other.md b/docs/advanced-usage/other.md index 662dff4cf..5de498560 100644 --- a/docs/advanced-usage/other.md +++ b/docs/advanced-usage/other.md @@ -1,6 +1,6 @@ --- title: Other -weight: 8 +weight: 9 --- Schema Diagram: diff --git a/docs/advanced-usage/phpstorm.md b/docs/advanced-usage/phpstorm.md index 435680b72..205d6a0b3 100644 --- a/docs/advanced-usage/phpstorm.md +++ b/docs/advanced-usage/phpstorm.md @@ -1,6 +1,6 @@ --- title: PhpStorm Interaction -weight: 7 +weight: 8 --- # Extending PhpStorm diff --git a/docs/advanced-usage/timestamps.md b/docs/advanced-usage/timestamps.md index a19247658..7ab577ba4 100644 --- a/docs/advanced-usage/timestamps.md +++ b/docs/advanced-usage/timestamps.md @@ -1,6 +1,6 @@ --- title: Timestamps -weight: 8 +weight: 10 --- ### Excluding Timestamps from JSON diff --git a/docs/advanced-usage/ui-options.md b/docs/advanced-usage/ui-options.md index 347142464..787804ec4 100644 --- a/docs/advanced-usage/ui-options.md +++ b/docs/advanced-usage/ui-options.md @@ -1,6 +1,6 @@ --- title: UI Options -weight: 10 +weight: 11 --- ## Need a UI? diff --git a/docs/advanced-usage/uuid.md b/docs/advanced-usage/uuid.md index a37d1e437..bebbeef73 100644 --- a/docs/advanced-usage/uuid.md +++ b/docs/advanced-usage/uuid.md @@ -1,6 +1,6 @@ --- title: UUID -weight: 6 +weight: 7 --- If you're using UUIDs or GUIDs for your User models there are a few considerations to note. diff --git a/src/PermissionServiceProvider.php b/src/PermissionServiceProvider.php index c0ae0ed15..c185022e6 100644 --- a/src/PermissionServiceProvider.php +++ b/src/PermissionServiceProvider.php @@ -22,8 +22,10 @@ public function boot(PermissionRegistrar $permissionLoader) $this->registerModelBindings(); - $permissionLoader->clearClassPermissions(); - $permissionLoader->registerPermissions(); + if ($this->app->config['permission.register_permission_check_method']) { + $permissionLoader->clearClassPermissions(); + $permissionLoader->registerPermissions(); + } $this->app->singleton(PermissionRegistrar::class, function ($app) use ($permissionLoader) { return $permissionLoader; diff --git a/tests/CustomGateTest.php b/tests/CustomGateTest.php new file mode 100644 index 000000000..74b963105 --- /dev/null +++ b/tests/CustomGateTest.php @@ -0,0 +1,35 @@ +set('permission.register_permission_check_method', false); + } + + /** @test */ + public function it_doesnt_register_the_method_for_checking_permissions_on_the_gate() + { + $this->testUser->givePermissionTo('edit-articles'); + + $this->assertEmpty(app(Gate::class)->abilities()); + $this->assertFalse($this->testUser->can('edit-articles')); + } + + /** @test */ + public function it_can_authorize_using_custom_method_for_checking_permissions() + { + app(Gate::class)->define('edit-articles', function () { + return true; + }); + + $this->assertArrayHasKey('edit-articles', app(Gate::class)->abilities()); + $this->assertTrue($this->testUser->can('edit-articles')); + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php index 350d90188..72c527df8 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -79,6 +79,7 @@ protected function getPackageProviders($app) */ protected function getEnvironmentSetUp($app) { + $app['config']->set('permission.register_permission_check_method', true); $app['config']->set('permission.teams', $this->hasTeams); $app['config']->set('permission.testing', true); //fix sqlite $app['config']->set('permission.column_names.model_morph_key', 'model_test_id'); From 1c9304af746b42308a1d1da2989e12a1439571a9 Mon Sep 17 00:00:00 2001 From: Freek Van der Herten Date: Fri, 29 Oct 2021 15:03:02 +0200 Subject: [PATCH 098/648] Update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5cdc94b6..38f70fb79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to `laravel-permission` will be documented in this file +## 5.3.0 - 2021-10-29 + +- Option for custom logic for checking permissions (#1891) + ## 5.2.0 - 2021-10-28 - [V5] Fix detaching on all teams intstead of only current #1888 by @erikn69 in https://github.com/spatie/laravel-permission/pull/1890 From ee0a50ad174e7f8b14bd17dd2a79ab51ccbac3df Mon Sep 17 00:00:00 2001 From: erikn69 Date: Thu, 4 Nov 2021 06:03:16 -0500 Subject: [PATCH 099/648] Fix hints, support int on `scopePermission()` (#1863) (#1908) Co-authored-by: Erik Niebla Co-authored-by: Erik Niebla --- src/Traits/HasPermissions.php | 33 +++++++++++++-------------------- src/Traits/HasRoles.php | 8 ++++---- tests/HasPermissionsTest.php | 16 ++++++++++++++++ 3 files changed, 33 insertions(+), 24 deletions(-) diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index 3e0d4072b..bfe9a926b 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -16,6 +16,7 @@ trait HasPermissions { + /** @var string */ private $permissionClass; public static function bootHasPermissions() @@ -61,7 +62,7 @@ public function permissions(): BelongsToMany * Scope the model query to certain permissions only. * * @param \Illuminate\Database\Eloquent\Builder $query - * @param string|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection $permissions + * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection $permissions * * @return \Illuminate\Database\Eloquent\Builder */ @@ -86,9 +87,10 @@ public function scopePermission(Builder $query, $permissions): Builder } /** - * @param string|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection $permissions + * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection $permissions * * @return array + * @throws \Spatie\Permission\Exceptions\PermissionDoesNotExist */ protected function convertToPermissionModels($permissions): array { @@ -102,8 +104,9 @@ protected function convertToPermissionModels($permissions): array if ($permission instanceof Permission) { return $permission; } + $method = is_string($permission) ? 'findByName' : 'findById'; - return $this->getPermissionClass()->findByName($permission, $this->getDefaultGuardName()); + return $this->getPermissionClass()->{$method}($permission, $this->getDefaultGuardName()); }, $permissions); } @@ -184,15 +187,6 @@ protected function hasWildcardPermission($permission, $guardName = null): bool return false; } - /** - * @deprecated since 2.35.0 - * @alias of hasPermissionTo() - */ - public function hasUncachedPermissionTo($permission, $guardName = null): bool - { - return $this->hasPermissionTo($permission, $guardName); - } - /** * An alias to hasPermissionTo(), but avoids throwing an exception. * @@ -213,10 +207,9 @@ public function checkPermissionTo($permission, $guardName = null): bool /** * Determine if the model has any of the given permissions. * - * @param array ...$permissions + * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection ...$permissions * * @return bool - * @throws \Exception */ public function hasAnyPermission(...$permissions): bool { @@ -234,7 +227,7 @@ public function hasAnyPermission(...$permissions): bool /** * Determine if the model has all of the given permissions. * - * @param array ...$permissions + * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection ...$permissions * * @return bool * @throws \Exception @@ -335,7 +328,7 @@ protected function getPermissionsRelation() /** * Grant the given permission(s) to a role. * - * @param string|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection $permissions + * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection $permissions * * @return $this */ @@ -392,7 +385,7 @@ function ($object) use ($permissions, $model) { /** * Remove all current permissions and set the given ones. * - * @param string|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection $permissions + * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection $permissions * * @return $this */ @@ -429,7 +422,7 @@ public function getPermissionNames(): Collection } /** - * @param string|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection $permissions + * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection $permissions * * @return \Spatie\Permission\Contracts\Permission|\Spatie\Permission\Contracts\Permission[]|\Illuminate\Support\Collection */ @@ -487,7 +480,7 @@ public function forgetCachedPermissions() /** * Check if the model has All of the requested Direct permissions. - * @param array ...$permissions + * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection ...$permissions * @return bool */ public function hasAllDirectPermissions(...$permissions): bool @@ -505,7 +498,7 @@ public function hasAllDirectPermissions(...$permissions): bool /** * Check if the model has Any of the requested Direct permissions. - * @param array ...$permissions + * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection ...$permissions * @return bool */ public function hasAnyDirectPermission(...$permissions): bool diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index 24283ff66..e9b7b5145 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -13,6 +13,7 @@ trait HasRoles { use HasPermissions; + /** @var string */ private $roleClass; public static function bootHasRoles() @@ -86,9 +87,8 @@ public function scopeRole(Builder $query, $roles, $guard = null): Builder } $method = is_numeric($role) ? 'findById' : 'findByName'; - $guard = $guard ?: $this->getDefaultGuardName(); - return $this->getRoleClass()->{$method}($role, $guard); + return $this->getRoleClass()->{$method}($role, $guard ?: $this->getDefaultGuardName()); }, $roles); return $query->whereHas('roles', function (Builder $subQuery) use ($roles) { @@ -114,7 +114,7 @@ protected function getRolesRelation() /** * Assign the given role to the model. * - * @param array|string|int|\Spatie\Permission\Contracts\Role ...$roles + * @param array|string|int|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection ...$roles * * @return $this */ @@ -189,7 +189,7 @@ public function removeRole($role) /** * Remove all current roles and set the given ones. * - * @param array|\Spatie\Permission\Contracts\Role|string|int ...$roles + * @param array|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection|string|int ...$roles * * @return $this */ diff --git a/tests/HasPermissionsTest.php b/tests/HasPermissionsTest.php index 7507837b6..08a208533 100644 --- a/tests/HasPermissionsTest.php +++ b/tests/HasPermissionsTest.php @@ -65,6 +65,22 @@ public function it_can_scope_users_using_a_string() $this->assertEquals(1, $scopedUsers2->count()); } + /** @test */ + public function it_can_scope_users_using_a_int() + { + $user1 = User::create(['email' => 'user1@test.com']); + $user2 = User::create(['email' => 'user2@test.com']); + $user1->givePermissionTo([1, 2]); + $this->testUserRole->givePermissionTo(1); + $user2->assignRole('testRole'); + + $scopedUsers1 = User::permission(1)->get(); + $scopedUsers2 = User::permission([2])->get(); + + $this->assertEquals(2, $scopedUsers1->count()); + $this->assertEquals(1, $scopedUsers2->count()); + } + /** @test */ public function it_can_scope_users_using_an_array() { From 193e50e44dce7b289f475a7df8782c9be0467e39 Mon Sep 17 00:00:00 2001 From: Freek Van der Herten Date: Thu, 4 Nov 2021 12:06:46 +0100 Subject: [PATCH 100/648] Update CHANGELOG.md --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38f70fb79..91a4f20c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to `laravel-permission` will be documented in this file +## 5.3.1 - 2021-11-04 + +- Fix hints, support int on scopePermission (#1908) + ## 5.3.0 - 2021-10-29 - Option for custom logic for checking permissions (#1891) @@ -19,7 +23,7 @@ All notable changes to `laravel-permission` will be documented in this file ## 5.1.0 - 2021-08-31 - No longer flush cache on User role/perm assignment changes #1832 - NOTE: You should test your app to be sure that you don't accidentally have deep dependencies on cache resets happening automatically in these cases. + NOTE: You should test your app to be sure that you don't accidentally have deep dependencies on cache resets happening automatically in these cases. ALSO NOTE: If you have added custom code which depended on these flush operations, you may need to add your own cache-reset calls. ## 5.0.0 - 2021-08-31 From 31ede8fc58c15c1af1a5d83606805be12dbdccaa Mon Sep 17 00:00:00 2001 From: Adriaan Marain Date: Mon, 15 Nov 2021 13:28:47 +0100 Subject: [PATCH 101/648] Add changelog workflow (automated commit) --- .github/workflows/update-changelog.yml | 28 ++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/workflows/update-changelog.yml diff --git a/.github/workflows/update-changelog.yml b/.github/workflows/update-changelog.yml new file mode 100644 index 000000000..fa56639f2 --- /dev/null +++ b/.github/workflows/update-changelog.yml @@ -0,0 +1,28 @@ +name: "Update Changelog" + +on: + release: + types: [released] + +jobs: + update: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + with: + ref: main + + - name: Update Changelog + uses: stefanzweifel/changelog-updater-action@v1 + with: + latest-version: ${{ github.event.release.name }} + release-notes: ${{ github.event.release.body }} + + - name: Commit updated CHANGELOG + uses: stefanzweifel/git-auto-commit-action@v4 + with: + branch: main + commit_message: Update CHANGELOG + file_pattern: CHANGELOG.md From fb9e680990b83e6db363b708c4e8fe0d73cfb726 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Wed, 17 Nov 2021 05:44:26 -0500 Subject: [PATCH 102/648] Support for custom key names on Role,Permission (#1913) Co-authored-by: Erik Niebla --- src/Models/Permission.php | 4 +++- src/Models/Role.php | 4 +++- src/Traits/HasPermissions.php | 15 ++++++++++----- src/Traits/HasRoles.php | 19 ++++++++++++------- tests/HasPermissionsTest.php | 4 ++-- tests/HasRolesTest.php | 22 +++++++++++----------- 6 files changed, 41 insertions(+), 27 deletions(-) diff --git a/src/Models/Permission.php b/src/Models/Permission.php index d38c16d4b..2b253d6f6 100644 --- a/src/Models/Permission.php +++ b/src/Models/Permission.php @@ -18,13 +18,15 @@ class Permission extends Model implements PermissionContract use HasRoles; use RefreshesPermissionCache; - protected $guarded = ['id']; + protected $guarded = []; public function __construct(array $attributes = []) { $attributes['guard_name'] = $attributes['guard_name'] ?? config('auth.defaults.guard'); parent::__construct($attributes); + + $this->guarded[] = $this->primaryKey; } public function getTable() diff --git a/src/Models/Role.php b/src/Models/Role.php index a0fd14e92..2c6fcfd25 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -18,13 +18,15 @@ class Role extends Model implements RoleContract use HasPermissions; use RefreshesPermissionCache; - protected $guarded = ['id']; + protected $guarded = []; public function __construct(array $attributes = []) { $attributes['guard_name'] = $attributes['guard_name'] ?? config('auth.defaults.guard'); parent::__construct($attributes); + + $this->guarded[] = $this->primaryKey; } public function getTable() diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index bfe9a926b..e55e73e40 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -76,11 +76,15 @@ public function scopePermission(Builder $query, $permissions): Builder return $query->where(function (Builder $query) use ($permissions, $rolesWithPermissions) { $query->whereHas('permissions', function (Builder $subQuery) use ($permissions) { - $subQuery->whereIn(config('permission.table_names.permissions').'.id', \array_column($permissions, 'id')); + $permissionClass = $this->getPermissionClass(); + $key = (new $permissionClass)->getKeyName(); + $subQuery->whereIn(config('permission.table_names.permissions').".$key", \array_column($permissions, $key)); }); if (count($rolesWithPermissions) > 0) { $query->orWhereHas('roles', function (Builder $subQuery) use ($rolesWithPermissions) { - $subQuery->whereIn(config('permission.table_names.roles').'.id', \array_column($rolesWithPermissions, 'id')); + $roleClass = $this->getRoleClass(); + $key = (new $roleClass)->getKeyName(); + $subQuery->whereIn(config('permission.table_names.roles').".$key", \array_column($rolesWithPermissions, $key)); }); } }); @@ -281,7 +285,7 @@ public function hasDirectPermission($permission): bool throw new PermissionDoesNotExist(); } - return $this->permissions->contains('id', $permission->id); + return $this->permissions->contains($permission->getKeyName(), $permission->getKey()); } /** @@ -334,6 +338,7 @@ protected function getPermissionsRelation() */ public function givePermissionTo(...$permissions) { + $permissionClass = $this->getPermissionClass(); $permissions = collect($permissions) ->flatten() ->map(function ($permission) { @@ -350,11 +355,11 @@ public function givePermissionTo(...$permissions) $this->ensureModelSharesGuard($permission); }) ->map(function ($permission) { - return ['id' => $permission->id, 'values' => PermissionRegistrar::$teams && ! is_a($this, Role::class) ? + return [$permission->getKeyName() => $permission->getKey(), 'values' => PermissionRegistrar::$teams && ! is_a($this, Role::class) ? [PermissionRegistrar::$teamsKey => app(PermissionRegistrar::class)->getPermissionsTeamId()] : [], ]; }) - ->pluck('values', 'id')->toArray(); + ->pluck('values', (new $permissionClass)->getKeyName())->toArray(); $model = $this->getModel(); diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index e9b7b5145..fa1e0aea2 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -92,7 +92,9 @@ public function scopeRole(Builder $query, $roles, $guard = null): Builder }, $roles); return $query->whereHas('roles', function (Builder $subQuery) use ($roles) { - $subQuery->whereIn(config('permission.table_names.roles').'.id', \array_column($roles, 'id')); + $roleClass = $this->getRoleClass(); + $key = (new $roleClass)->getKeyName(); + $subQuery->whereIn(config('permission.table_names.roles').".$key", \array_column($roles, $key)); }); } @@ -120,6 +122,7 @@ protected function getRolesRelation() */ public function assignRole(...$roles) { + $roleClass = $this->getRoleClass(); $roles = collect($roles) ->flatten() ->map(function ($role) { @@ -136,11 +139,11 @@ public function assignRole(...$roles) $this->ensureModelSharesGuard($role); }) ->map(function ($role) { - return ['id' => $role->id, 'values' => PermissionRegistrar::$teams && ! is_a($this, Permission::class) ? + return [$role->getKeyName() => $role->getKey(), 'values' => PermissionRegistrar::$teams && ! is_a($this, Permission::class) ? [PermissionRegistrar::$teamsKey => app(PermissionRegistrar::class)->getPermissionsTeamId()] : [], ]; }) - ->pluck('values', 'id')->toArray(); + ->pluck('values', (new $roleClass)->getKeyName())->toArray(); $model = $this->getModel(); @@ -220,13 +223,15 @@ public function hasRole($roles, string $guard = null): bool } if (is_int($roles)) { + $roleClass = $this->getRoleClass(); + $key = (new $roleClass)->getKeyName(); return $guard - ? $this->roles->where('guard_name', $guard)->contains('id', $roles) - : $this->roles->contains('id', $roles); + ? $this->roles->where('guard_name', $guard)->contains($key, $roles) + : $this->roles->contains($key, $roles); } if ($roles instanceof Role) { - return $this->roles->contains('id', $roles->id); + return $this->roles->contains($roles->getKeyName(), $roles->getKey()); } if (is_array($roles)) { @@ -276,7 +281,7 @@ public function hasAllRoles($roles, string $guard = null): bool } if ($roles instanceof Role) { - return $this->roles->contains('id', $roles->id); + return $this->roles->contains($roles->getKeyName(), $roles->getKey()); } $roles = collect()->make($roles)->map(function ($role) { diff --git a/tests/HasPermissionsTest.php b/tests/HasPermissionsTest.php index 08a208533..11dd4b0f2 100644 --- a/tests/HasPermissionsTest.php +++ b/tests/HasPermissionsTest.php @@ -418,7 +418,7 @@ public function it_can_sync_multiple_permissions_by_id() { $this->testUser->givePermissionTo('edit-news'); - $ids = app(Permission::class)::whereIn('name', ['edit-articles', 'edit-blog'])->pluck('id'); + $ids = app(Permission::class)::whereIn('name', ['edit-articles', 'edit-blog'])->pluck($this->testUserPermission->getKeyName()); $this->testUser->syncPermissions($ids); @@ -434,7 +434,7 @@ public function sync_permission_ignores_null_inputs() { $this->testUser->givePermissionTo('edit-news'); - $ids = app(Permission::class)::whereIn('name', ['edit-articles', 'edit-blog'])->pluck('id'); + $ids = app(Permission::class)::whereIn('name', ['edit-articles', 'edit-blog'])->pluck($this->testUserPermission->getKeyName()); $ids->push(null); diff --git a/tests/HasRolesTest.php b/tests/HasRolesTest.php index daa556444..bd7415070 100644 --- a/tests/HasRolesTest.php +++ b/tests/HasRolesTest.php @@ -22,13 +22,13 @@ public function it_can_determine_that_the_user_does_not_have_a_role() $this->assertTrue($this->testUser->hasRole($role->name)); $this->assertTrue($this->testUser->hasRole($role->name, $role->guard_name)); $this->assertTrue($this->testUser->hasRole([$role->name, 'fakeRole'], $role->guard_name)); - $this->assertTrue($this->testUser->hasRole($role->id, $role->guard_name)); - $this->assertTrue($this->testUser->hasRole([$role->id, 'fakeRole'], $role->guard_name)); + $this->assertTrue($this->testUser->hasRole($role->getKey(), $role->guard_name)); + $this->assertTrue($this->testUser->hasRole([$role->getKey(), 'fakeRole'], $role->guard_name)); $this->assertFalse($this->testUser->hasRole($role->name, 'fakeGuard')); $this->assertFalse($this->testUser->hasRole([$role->name, 'fakeRole'], 'fakeGuard')); - $this->assertFalse($this->testUser->hasRole($role->id, 'fakeGuard')); - $this->assertFalse($this->testUser->hasRole([$role->id, 'fakeRole'], 'fakeGuard')); + $this->assertFalse($this->testUser->hasRole($role->getKey(), 'fakeGuard')); + $this->assertFalse($this->testUser->hasRole([$role->getKey(), 'fakeRole'], 'fakeGuard')); $role = app(Role::class)->findOrCreate('testRoleInWebGuard2', 'web'); $this->assertFalse($this->testUser->hasRole($role)); @@ -87,7 +87,7 @@ public function it_can_assign_a_role_using_an_object() /** @test */ public function it_can_assign_a_role_using_an_id() { - $this->testUser->assignRole($this->testUserRole->id); + $this->testUser->assignRole($this->testUserRole->getKey()); $this->assertTrue($this->testUser->hasRole($this->testUserRole)); } @@ -95,7 +95,7 @@ public function it_can_assign_a_role_using_an_id() /** @test */ public function it_can_assign_multiple_roles_at_once() { - $this->testUser->assignRole($this->testUserRole->id, 'testRole2'); + $this->testUser->assignRole($this->testUserRole->getKey(), 'testRole2'); $this->assertTrue($this->testUser->hasRole('testRole')); @@ -105,7 +105,7 @@ public function it_can_assign_multiple_roles_at_once() /** @test */ public function it_can_assign_multiple_roles_using_an_array() { - $this->testUser->assignRole([$this->testUserRole->id, 'testRole2']); + $this->testUser->assignRole([$this->testUserRole->getKey(), 'testRole2']); $this->assertTrue($this->testUser->hasRole('testRole')); @@ -115,7 +115,7 @@ public function it_can_assign_multiple_roles_using_an_array() /** @test */ public function it_does_not_remove_already_associated_roles_when_assigning_new_roles() { - $this->testUser->assignRole($this->testUserRole->id); + $this->testUser->assignRole($this->testUserRole->getKey()); $this->testUser->assignRole('testRole2'); @@ -125,9 +125,9 @@ public function it_does_not_remove_already_associated_roles_when_assigning_new_r /** @test */ public function it_does_not_throw_an_exception_when_assigning_a_role_that_is_already_assigned() { - $this->testUser->assignRole($this->testUserRole->id); + $this->testUser->assignRole($this->testUserRole->getKey()); - $this->testUser->assignRole($this->testUserRole->id); + $this->testUser->assignRole($this->testUserRole->getKey()); $this->assertTrue($this->testUser->fresh()->hasRole('testRole')); } @@ -352,7 +352,7 @@ public function it_can_scope_users_using_an_array_of_ids_and_names() $roleName = $this->testUserRole->name; - $otherRoleId = app(Role::class)->find(2)->id; + $otherRoleId = app(Role::class)->findByName('testRole2')->getKey(); $scopedUsers = User::role([$roleName, $otherRoleId])->get(); From 10d4c14dd66f82ce777ab76f5708bd14ff5a1ef8 Mon Sep 17 00:00:00 2001 From: freekmurze Date: Wed, 17 Nov 2021 10:45:32 +0000 Subject: [PATCH 103/648] Fix styling --- src/Traits/HasPermissions.php | 6 +++--- src/Traits/HasRoles.php | 7 ++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index e55e73e40..6baecb5dc 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -77,13 +77,13 @@ public function scopePermission(Builder $query, $permissions): Builder return $query->where(function (Builder $query) use ($permissions, $rolesWithPermissions) { $query->whereHas('permissions', function (Builder $subQuery) use ($permissions) { $permissionClass = $this->getPermissionClass(); - $key = (new $permissionClass)->getKeyName(); + $key = (new $permissionClass())->getKeyName(); $subQuery->whereIn(config('permission.table_names.permissions').".$key", \array_column($permissions, $key)); }); if (count($rolesWithPermissions) > 0) { $query->orWhereHas('roles', function (Builder $subQuery) use ($rolesWithPermissions) { $roleClass = $this->getRoleClass(); - $key = (new $roleClass)->getKeyName(); + $key = (new $roleClass())->getKeyName(); $subQuery->whereIn(config('permission.table_names.roles').".$key", \array_column($rolesWithPermissions, $key)); }); } @@ -359,7 +359,7 @@ public function givePermissionTo(...$permissions) [PermissionRegistrar::$teamsKey => app(PermissionRegistrar::class)->getPermissionsTeamId()] : [], ]; }) - ->pluck('values', (new $permissionClass)->getKeyName())->toArray(); + ->pluck('values', (new $permissionClass())->getKeyName())->toArray(); $model = $this->getModel(); diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index fa1e0aea2..54378b236 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -93,7 +93,7 @@ public function scopeRole(Builder $query, $roles, $guard = null): Builder return $query->whereHas('roles', function (Builder $subQuery) use ($roles) { $roleClass = $this->getRoleClass(); - $key = (new $roleClass)->getKeyName(); + $key = (new $roleClass())->getKeyName(); $subQuery->whereIn(config('permission.table_names.roles').".$key", \array_column($roles, $key)); }); } @@ -143,7 +143,7 @@ public function assignRole(...$roles) [PermissionRegistrar::$teamsKey => app(PermissionRegistrar::class)->getPermissionsTeamId()] : [], ]; }) - ->pluck('values', (new $roleClass)->getKeyName())->toArray(); + ->pluck('values', (new $roleClass())->getKeyName())->toArray(); $model = $this->getModel(); @@ -224,7 +224,8 @@ public function hasRole($roles, string $guard = null): bool if (is_int($roles)) { $roleClass = $this->getRoleClass(); - $key = (new $roleClass)->getKeyName(); + $key = (new $roleClass())->getKeyName(); + return $guard ? $this->roles->where('guard_name', $guard)->contains($key, $roles) : $this->roles->contains($key, $roles); From 7b0e0442b3c9e11d4c001f163bfd3967a103c676 Mon Sep 17 00:00:00 2001 From: freekmurze Date: Wed, 17 Nov 2021 10:48:30 +0000 Subject: [PATCH 104/648] Update CHANGELOG --- CHANGELOG.md | 221 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 184 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91a4f20c4..80a6bb6d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to `laravel-permission` will be documented in this file +## 5.3.2 - 2021-11-17 + +## What's Changed + +- [V5] Support for custom key names on Role,Permission by @erikn69 in https://github.com/spatie/laravel-permission/pull/1913 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/5.3.1...5.3.2 + ## 5.3.1 - 2021-11-04 - Fix hints, support int on scopePermission (#1908) @@ -19,40 +27,50 @@ All notable changes to `laravel-permission` will be documented in this file - [V5] Doc for `Super Admin` on teams by @erikn69 in https://github.com/spatie/laravel-permission/pull/1845 ## 5.1.1 - 2021-09-01 + - Avoid Roles over-hydration #1834 ## 5.1.0 - 2021-08-31 + - No longer flush cache on User role/perm assignment changes #1832 - NOTE: You should test your app to be sure that you don't accidentally have deep dependencies on cache resets happening automatically in these cases. - ALSO NOTE: If you have added custom code which depended on these flush operations, you may need to add your own cache-reset calls. +- NOTE: You should test your app to be sure that you don't accidentally have deep dependencies on cache resets happening automatically in these cases. +- ALSO NOTE: If you have added custom code which depended on these flush operations, you may need to add your own cache-reset calls. ## 5.0.0 - 2021-08-31 + - Change default-guard-lookup to prefer current user's guard (see BC note in #1817 ) - Teams/Groups feature (see docs, or PR #1804) - Customized pivots instead of `role_id`,`permission_id` #1823 ## 4.4.1 - 2021-09-01 + - Avoid Roles over-hydration #1834 ## 4.4.0 - 2021-08-28 + - Avoid BC break (removed interface change) on cache change added in 4.3.0 #1826 - Made cache even smaller #1826 - Avoid re-sync on non-persisted objects when firing Eloquent::saved #1819 ## 4.3.0 - 2021-08-17 + - Speed up permissions cache lookups, and make cache smaller #1799 ## 4.2.0 - 2021-06-04 + - Add hasExactRoles method #1696 ## 4.1.0 - 2021-06-01 + - Refactor to resolve guard only once during middleware - Refactor service provider by extracting some methods ## 4.0.1 - 2021-03-22 + - Added note in migration for field lengths on MySQL 8. (either shorten the columns to 125 or use InnoDB) ## 4.0.0 - 2021-01-27 + - Drop support on Laravel 5.8 #1615 - Fix bug when adding roles to a model that doesn't yet exist #1663 - Enforce unique constraints on database level #1261 @@ -63,121 +81,154 @@ This package now requires PHP 7.2.5 and Laravel 6.0 or higher. If you are on a PHP version below 7.2.5 or a Laravel version below 6.0 you can use an older version of this package. ## 3.18.0 - 2020-11-27 + - Allow PHP 8.0 ## 3.17.0 - 2020-09-16 + - Optional `$guard` parameter may be passed to `RoleMiddleware`, `PermissionMiddleware`, and `RoleOrPermissionMiddleware`. See #1565 ## 3.16.0 - 2020-08-18 + - Added Laravel 8 support ## 3.15.0 - 2020-08-15 + - Change `users` relationship type to BelongsToMany ## 3.14.0 - 2020-08-15 + - Declare table relations earlier to improve guarded/fillable detection accuracy (relates to Aug 2020 Laravel security patch) ## 3.13.0 - 2020-05-19 + - Provide migration error text to stop caching local config when installing packages. ## 3.12.0 - 2020-05-14 + - Add missing config setting for `display_role_in_exception` - Ensure artisan `permission:show` command uses configured models ## 3.11.0 - 2020-03-03 + - Allow guardName() as a function with priority over $guard_name property #1395 ## 3.10.1 - 2020-03-03 + - Update patch to handle intermittent error in #1370 ## 3.10.0 - 2020-03-02 + - Ugly patch to handle intermittent error: `Trying to access array offset on value of type null` in #1370 ## 3.9.0 - 2020-02-26 + - Add Wildcard Permissions feature #1381 (see PR or docs for details) ## 3.8.0 - 2020-02-18 + - Clear in-memory permissions on boot, for benefit of long running processes like Swoole. #1378 ## 3.7.2 - 2020-02-17 + - Refine test for Lumen dependency. Ref #1371, Fixes #1372. ## 3.7.1 - 2020-02-15 + - Internal refactoring of scopes to use whereIn instead of orWhere #1334, #1335 - Internal refactoring to flatten collection on splat #1341 ## 3.7.0 - 2020-02-15 + - Added methods to check any/all when querying direct permissions #1245 - Removed older Lumen dependencies #1371 ## 3.6.0 - 2020-01-17 + - Added Laravel 7.0 support - Allow splat operator for passing roles to `hasAnyRole()` ## 3.5.0 - 2020-01-07 + - Added missing `guardName` to Exception `PermissionDoesNotExist` #1316 ## 3.4.1 - 2019-12-28 + - Fix 3.4.0 for Lumen ## 3.4.0 - 2019-12-27 + - Make compatible with Swoole - ie: for long-running Laravel instances ## 3.3.1 - 2019-12-24 + - Expose Artisan commands to app layer, not just to console ## 3.3.0 - 2019-11-22 + - Remove duplicate and unreachable code - Remove checks for older Laravel versions ## 3.2.0 - 2019-10-16 + - Implementation of optional guard check for hasRoles and hasAllRoles - See #1236 ## 3.1.0 - 2019-10-16 + - Use bigIncrements/bigInteger in migration - See #1224 ## 3.0.0 - 2019-09-02 + - Update dependencies to allow for Laravel 6.0 - Drop support for Laravel 5.7 and older, and PHP 7.1 and older. (They can use v2 of this package until they upgrade.) -To be clear: v3 requires minimum Laravel 5.8 and PHP 7.2 - +- To be clear: v3 requires minimum Laravel 5.8 and PHP 7.2 ## 2.38.0 - 2019-09-02 + - Allow support for multiple role/permission models - Load roles relationship only when missing - Wrap helpers in function_exists() check ## 2.37.0 - 2019-04-09 + - Added `permission:show` CLI command to display a table of roles/permissions - `removeRole` now returns the model, consistent with other methods - model `$guarded` properties updated to `protected` - README updates ## 2.36.1 - 2019-03-05 + - reverts the changes made in 2.36.0 due to some reported breaks. ## 2.36.0 - 2019-03-04 + - improve performance by reducing another iteration in processing query results and returning earlier ## 2.35.0 - 2019-03-01 + - overhaul internal caching strategy for better performance and fix cache miss when permission names contained spaces - deprecated hasUncachedPermissionTo() (use hasPermissionTo() instead) - added getPermissionNames() method ## 2.34.0 - 2019-02-26 + - Add explicit pivotKeys to roles/permissions BelongsToMany relationships ## 2.33.0 - 2019-02-20 + - Laravel 5.8 compatibility ## 2.32.0 - 2019-02-13 + - Fix duplicate permissions being created through artisan command ## 2.31.0 - 2019-02-03 + - Add custom guard query to role scope - Remove use of array_wrap helper function due to future deprecation ## 2.30.0 - 2019-01-28 + - Change cache config time to DateInterval instead of integer This is in preparation for compatibility with Laravel 5.8's cache TTL change to seconds instead of minutes. @@ -190,321 +241,410 @@ https://laravel-news.com/cache-ttl-change-coming-to-laravel-5-8 https://github.com/laravel/framework/commit/fd6eb89b62ec09df1ffbee164831a827e83fa61d ## 2.29.0 - 2018-12-15 + - Fix bound `saved` event from firing on all subsequent models when calling assignRole or givePermissionTo on unsaved models. However, it is preferable to save the model first, and then add roles/permissions after saving. See #971. ## 2.28.2 - 2018-12-10 + - Use config settings for cache reset in migration stub ## 2.28.1 - 2018-12-07 + - Remove use of Cache facade, for Lumen compatibility ## 2.28.0 - 2018-11-30 -- Rename `getCacheKey` method in HasPermissions trait to `getPermissionCacheKey` for clearer specificity. + +- Rename `getCacheKey` method in HasPermissions trait to `getPermissionCacheKey` for clearer specificity. ## 2.27.0 - 2018-11-21 + - Add ability to specify a cache driver for roles/permissions caching ## 2.26.2 - 2018-11-20 + - Added the ability to reset the permissions cache via an Artisan command: -`php artisan permission:cache-reset` +- `php artisan permission:cache-reset` ## 2.26.1 - 2018-11-19 + - minor update to de-duplicate code overhead - numerous internal updates to cache tests infrastructure ## 2.26.0 - 2018-11-19 + - Substantial speed increase by caching the associations between models and permissions -### NOTES: ### +### NOTES: + The following changes are not "breaking", but worth making the updates to your app for consistency. 1. Config file: The `config/permission.php` file changed to move cache-related settings into a sub-array. **You should review the changes and merge the updates into your own config file.** Specifically the `expiration_time` value has moved into a sub-array entry, and the old top-level entry is no longer used. -See the original config file here: -https://github.com/spatie/laravel-permission/blob/main/config/permission.php +2. See the original config file here: +3. https://github.com/spatie/laravel-permission/blob/main/config/permission.php +4. +5. Cache Resets: If your `app` or `tests` are clearing the cache by specifying the cache key, **it is better to use the built-in forgetCachedPermissions() method** so that it properly handles tagged cache entries. Here is the recommended change: +6. -2. Cache Resets: If your `app` or `tests` are clearing the cache by specifying the cache key, **it is better to use the built-in forgetCachedPermissions() method** so that it properly handles tagged cache entries. Here is the recommended change: ```diff - app()['cache']->forget('spatie.permission.cache'); + $this->app->make(\Spatie\Permission\PermissionRegistrar::class)->forgetCachedPermissions(); -``` - -3. Also this is a good time to point out that now with v2.25.0 and v2.26.0 most permission-cache-reset scenarios may no longer be needed in your app, so it's worth reviewing those cases, as you may gain some app speed improvement by removing unnecessary cache resets. +``` +1. Also this is a good time to point out that now with v2.25.0 and v2.26.0 most permission-cache-reset scenarios may no longer be needed in your app, so it's worth reviewing those cases, as you may gain some app speed improvement by removing unnecessary cache resets. ## 2.25.0 - 2018-11-07 -- A model's `roles` and `permissions` relations (respectively) are now automatically reloaded after an Assign/Remove role or Grant/Revoke of permissions. This means there's no longer a need to call `->fresh()` on the model if the only reason is to reload the role/permission relations. (That said, you may want to call it for other reasons.) + +- A model's `roles` and `permissions` relations (respectively) are now automatically reloaded after an Assign/Remove role or Grant/Revoke of permissions. This means there's no longer a need to call `->fresh()` on the model if the only reason is to reload the role/permission relations. (That said, you may want to call it for other reasons.) - Added support for passing id to HasRole() ## 2.24.0 - 2018-11-06 + - Fix operator used on RoleOrPermissionMiddleware, and avoid throwing PermissionDoesNotExist if invalid permission passed - Auto-reload model role relation after using AssignRole - Avoid empty permission creation when using the CreateRole command ## 2.23.0 - 2018-10-15 + - Avoid unnecessary queries of user roles when fetching all permissions ## 2.22.1 - 2018-10-15 + - Fix Lumen issue with Route helper added in 2.22.0 ## 2.22.0 - 2018-10-11 + - Added `Route::role()` and `Route::permission()` middleware helper functions - Added new `role_or_permission` middleware to allow specifying "or" combinations ## 2.21.0 - 2018-09-29 + - Revert changes from 2.17.1 in order to support Lumen 5.7 ## 2.20.0 - 2018-09-19 -- It will sync roles/permissions to models that are not persisted, by registering a `saved` callback. -(It would previously throw an Integrity constraint violation QueryException on the pivot table insertion.) + +- It will sync roles/permissions to models that are not persisted, by registering a `saved` callback. +- (It would previously throw an Integrity constraint violation QueryException on the pivot table insertion.) ## 2.19.2 - 2018-09-19 + - add `@elserole` directive: - Usage: +- Usage: + ```php @role('roleA') // user hasRole 'roleA' @elserole('roleB') // user hasRole 'roleB' but not 'roleA' @endrole -``` +``` ## 2.19.1 - 2018-09-14 + - Spark-related fix to accommodate missing guard[providers] config ## 2.19.0 - 2018-09-10 + - Add ability to pass in IDs or mixed values to `role` scope - Add `@unlessrole`/`@endunlessrole` Blade directives ## 2.18.0 - 2018-09-06 + - Expanded CLI `permission:create-role` command to create optionally create-and-link permissions in one command. Also now no longer throws an error if the role already exists. ## 2.17.1 - 2018-08-28 -- Require laravel/framework instead of illuminate/* starting from ~5.4.0 + +- Require laravel/framework instead of illuminate/* starting from ~5.4.0 - Removed old dependency for illuminate/database@~5.3.0 (Laravel 5.3 is not supported) ## 2.17.0 - 2018-08-24 + - Laravel 5.7 compatibility ## 2.16.0 - 2018-08-20 + - Replace static Permission::class and Role::class with dynamic value (allows custom models more easily) - Added type checking in hasPermissionTo and hasDirectPermission ## 2.15.0 - 2018-08-15 + - Make assigning the same role or permission twice not throw an exception ## 2.14.0 - 2018-08-13 + - Allow using another key name than `model_id` by defining new `columns` array with `model_morph_key` key in config file. This improves UUID compatibility as discussed in #777. ## 2.13.0 - 2018-08-02 + - Fix issue with null values passed to syncPermissions & syncRoles ## 2.12.2 - 2018-06-13 + - added hasAllPermissions method ## 2.12.1 - 2018-04-23 -- Reverted 2.12.0. REVERTS: "Add ability to pass guard name to gate methods like can()". Requires reworking of guard handling if we're going to add this feature. + +- Reverted 2.12.0. REVERTS: "Add ability to pass guard name to gate methods like can()". Requires reworking of guard handling if we're going to add this feature. ## 2.12.0 - 2018-04-22 + - Add ability to pass guard name to gate methods like can() ## 2.11.0 - 2018-04-16 + - Improve speed of permission lookups with findByName, findById, findOrCreate ## 2.10.0 - 2018-04-15 + - changes the type-hinted Authenticatable to Authorizable in the PermissionRegistrar. -(Previously it was expecting models to implement the Authenticatable contract; but really that should have been Authorizable, since that's where the Gate functionality really is.) +- (Previously it was expecting models to implement the Authenticatable contract; but really that should have been Authorizable, since that's where the Gate functionality really is.) ## 2.9.2 - 2018-03-12 + - Now findOrCreate() exists for both Roles and Permissions - Internal code refactoring for future dev work ## 2.9.1 - 2018-02-23 + - Permissions now support passing integer id for sync, find, hasPermissionTo and hasDirectPermissionTo ## 2.9.0 - 2018-02-07 + - add compatibility with Laravel 5.6 - Allow assign/sync/remove Roles from Permission model ## 2.8.2 - 2018-02-07 + - Allow a collection containing a model to be passed to role/permission scopes ## 2.8.1 - 2018-02-03 + - Fix compatibility with Spark v2.0 to v5.0 ## 2.8.0 - 2018-01-25 + - Support getting guard_name from extended model when using static methods ## 2.7.9 - 2018-01-23 + Changes related to throwing UnauthorizedException: - - When UnauthorizedException is thrown, a property is added with the expected role/permission which triggered it - - A configuration option may be set to include the list of required roles/permissions in the message + +- When UnauthorizedException is thrown, a property is added with the expected role/permission which triggered it +- A configuration option may be set to include the list of required roles/permissions in the message ## 2.7.8 - 2018-01-02 -- REVERTED: Dynamic permission_id and role_id columns according to tables name -NOTE: This Dynamic field naming was a breaking change, so we've removed it for now. + +- REVERTED: Dynamic permission_id and role_id columns according to tables name +- NOTE: This Dynamic field naming was a breaking change, so we've removed it for now. BEST NOT TO USE v2.7.7 if you've changed tablenames in the config file. ## 2.7.7 - 2017-12-31 + - updated `HasPermissions::getStoredPermission` to allow a collection to be returned, and to fix query when passing multiple permissions -- Give and revoke multiple permissions -- Dynamic permission_id and role_id columns according to tables name -- Add findOrCreate function to Permission model +- Give and revoke multiple permissions +- Dynamic permission_id and role_id columns according to tables name +- Add findOrCreate function to Permission model - Improved Lumen support -- Allow guard name to be null for find role by id +- Allow guard name to be null for find role by id ## 2.7.6 - 2017-11-27 + - added Lumen support - updated `HasRole::assignRole` and `HasRole::syncRoles` to accept role id's in addition to role names as arguments ## 2.7.5 - 2017-10-26 + - fixed `Gate::before` for custom gate callbacks ## 2.7.4 - 2017-10-26 + - added cache clearing command in `up` migration for permission tables - use config_path helper for better Lumen support ## 2.7.3 - 2017-10-21 + - refactor middleware to throw custom `UnauthorizedException` (which raises an HttpException with 403 response) -The 403 response is backward compatible +- The 403 response is backward compatible ## 2.7.2 - 2017-10-18 -- refactor `PermissionRegistrar` to use `$gate->before()` + +- refactor `PermissionRegistrar` to use `$gate->before()` - removed `log_registration_exception` as it is no longer relevant ## 2.7.1 - 2017-10-12 + - fixed a bug where `Role`s and `Permission`s got detached when soft deleting a model ## 2.7.0 - 2017-09-27 + - add support for L5.3 ## 2.6.0 - 2017-09-10 + - add `permission` scope ## 2.5.4 - 2017-09-07 + - register the blade directives in the register method of the service provider ## 2.5.3 - 2017-09-07 + - register the blade directives in the boot method of the service provider ## 2.5.2 - 2017-09-05 + - let middleware use caching ## 2.5.1 - 2017-09-02 + - add getRoleNames() method to return a collection of assigned roles ## 2.5.0 - 2017-08-30 + - add compatibility with Laravel 5.5 ## 2.4.2 - 2017-08-11 + - automatically detach roles and permissions when a user gets deleted ## 2.4.1 - 2017-08-05 + - fix processing of pipe symbols in `@hasanyrole` and `@hasallroles` Blade directives ## 2.4.0 -2017-08-05 + - add `PermissionMiddleware` and `RoleMiddleware` ## 2.3.2 - 2017-07-28 + - allow `hasAnyPermission` to take an array of permissions ## 2.3.1 - 2017-07-27 + - fix commands not using custom models ## 2.3.0 - 2017-07-25 + - add `create-permission` and `create-role` commands ## 2.2.0 - 2017-07-01 + - `hasanyrole` and `hasallrole` can accept multiple roles ## 2.1.6 - 2017-06-06 + - fixed a bug where `hasPermissionTo` wouldn't use the right guard name ## 2.1.5 - 2017-05-17 + - fixed a bug that didn't allow you to assign a role or permission when using multiple guards ## 2.1.4 - 2017-05-10 + - add `model_type` to the primary key of tables that use a polymorphic relationship ## 2.1.3 - 2017-04-21 + - fixed a bug where the role()/permission() relation to user models would be saved incorrectly - added users() relation on Permission and Role ## 2.1.2 - 2017-04-20 + - fix a bug where the `role()`/`permission()` relation to user models would be saved incorrectly - add `users()` relation on `Permission` and `Role` ## 2.0.2 - 2017-04-13 + - check for duplicates when adding new roles and permissions ## 2.0.1 - 2017-04-11 + - fix the order of the `foreignKey` and `relatedKey` in the relations ## 2.0.0 - 2017-04-10 + - Requires minimum Laravel 5.4 - cache expiration is now configurable and set to one day by default - roles and permissions can now be assigned to any model through the `HasRoles` trait - removed deprecated `hasPermission` method - renamed config file from `laravel-permission` to `permission`. - ## 1.17.0 - 2018-08-24 + - added support for Laravel 5.7 ## 1.16.0 - 2018-02-07 + - added support for Laravel 5.6 ## 1.15 - 2017-12-08 + - allow `hasAnyPermission` to take an array of permissions ## 1.14.1 - 2017-10-26 + - fixed `Gate::before` for custom gate callbacks ## 1.14.0 - 2017-10-18 -- refactor `PermissionRegistrar` to use `$gate->before()` + +- refactor `PermissionRegistrar` to use `$gate->before()` - removed `log_registration_exception` as it is no longer relevant ## 1.13.0 - 2017-08-31 + - added compatibility for Laravel 5.5 ## 1.12.0 + - made foreign key name to users table configurable ## 1.11.1 + - `hasPermissionTo` uses the cache to avoid extra queries when it is called multiple times ## 1.11.0 + - add `getDirectPermissions`, `getPermissionsViaRoles`, `getAllPermissions` ## 1.10.0 - 2017-02-22 + - add `hasAnyPermission` ## 1.9.0 - 2017-02-20 + - add `log_registration_exception` in settings file -- fix for ambiguous column name `id` when using the role scope +- fix for ambiguous column name `id` when using the role scope ## 1.8.0 - 2017-02-09 + - `hasDirectPermission` method is now public ## 1.7.0 - 2016-01-23 + - added support for Laravel 5.4 ## 1.6.1 - 2016-01-19 + - make exception logging more verbose ## 1.6.0 - 2016-12-27 + - added `Role` scope ## 1.5.3 - 2016-12-15 + - moved some things to `boot` method in SP to solve some compatibility problems with other packages ## 1.5.2 - 2016-08-26 + - make compatible with L5.3 ## 1.5.1 - 2016-07-23 + - fixes `givePermissionTo` and `assignRole` in Laravel 5.1 ## 1.5.0 - 2016-07-23 + ** this version does not work in Laravel 5.1, please upgrade to version 1.5.1 of this package - allowed `givePermissonTo` to accept multiple permissions @@ -514,10 +654,12 @@ The 403 response is backward compatible - dropped support for PHP 5.5 and HHVM ## 1.4.0 - 2016-05-08 -- added `hasPermissionTo` function to the `Role` model + +- added `hasPermissionTo` function to the `Role` model ## 1.3.4 - 2016-02-27 -- `hasAnyRole` can now properly process an array + +- `hasAnyRole` can now properly process an array ## 1.3.3 - 2016-02-24 @@ -542,25 +684,30 @@ The 403 response is backward compatible ## 1.2.0 - 2015-10-28 ###Added + - support for custom models ## 1.1.0 - 2015-10-12 ### Added -- Blade directives + +- Blade directives - `hasAllRoles()`- and `hasAnyRole()`-functions ## 1.0.2 - 2015-10-11 ### Fixed + - Fix for running phpunit locally ## 1.0.1 - 2015-09-30 ### Fixed + - Fixed the inconsistent naming of the `hasPermission`-method. ## 1.0.0 - 2015-09-16 ### Added + - Everything, initial release From e54f376517f698e058c518f73703a0ee59b26521 Mon Sep 17 00:00:00 2001 From: Freek Van der Herten Date: Wed, 17 Nov 2021 12:47:22 +0100 Subject: [PATCH 105/648] Add support for PHP 8.1 (#1926) * wip * wip * wip * wip * drop support for PHP 7.2 * wip * wip * drop support for Laravel 6 * wip * wip * wip --- .github/workflows/run-tests-L7.yml | 14 +++----------- .github/workflows/run-tests-L8.yml | 10 ++-------- composer.json | 14 +++++++------- 3 files changed, 12 insertions(+), 26 deletions(-) diff --git a/.github/workflows/run-tests-L7.yml b/.github/workflows/run-tests-L7.yml index bde4ee667..4f347b351 100644 --- a/.github/workflows/run-tests-L7.yml +++ b/.github/workflows/run-tests-L7.yml @@ -9,14 +9,12 @@ jobs: strategy: fail-fast: false matrix: - php: [8.0, 7.4, 7.3, 7.2] - laravel: [7.*, 6.*] + php: [8.0, 7.4, 7.3] + laravel: [7.*] dependency-version: [prefer-lowest, prefer-stable] include: - laravel: 7.* - testbench: 5.* - - laravel: 6.* - testbench: 4.* + testbench: 5.20 name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }} @@ -24,12 +22,6 @@ jobs: - name: Checkout code uses: actions/checkout@v2 - - name: Cache dependencies - uses: actions/cache@v2 - with: - path: ~/.composer/cache/files - key: dependencies-laravel-${{ matrix.laravel }}-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }} - - name: Setup PHP uses: shivammathur/setup-php@v2 with: diff --git a/.github/workflows/run-tests-L8.yml b/.github/workflows/run-tests-L8.yml index de738eb00..14b15a76d 100644 --- a/.github/workflows/run-tests-L8.yml +++ b/.github/workflows/run-tests-L8.yml @@ -9,12 +9,12 @@ jobs: strategy: fail-fast: false matrix: - php: [8.0, 7.4, 7.3] + php: [8.1, 8.0, 7.4, 7.3] laravel: [8.*] dependency-version: [prefer-lowest, prefer-stable] include: - laravel: 8.* - testbench: 6.* + testbench: 6.23 name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }} @@ -22,12 +22,6 @@ jobs: - name: Checkout code uses: actions/checkout@v2 - - name: Cache dependencies - uses: actions/cache@v2 - with: - path: ~/.composer/cache/files - key: dependencies-laravel-${{ matrix.laravel }}-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }} - - name: Setup PHP uses: shivammathur/setup-php@v2 with: diff --git a/composer.json b/composer.json index 2810431cc..7382f27d7 100644 --- a/composer.json +++ b/composer.json @@ -22,15 +22,15 @@ } ], "require": { - "php" : "^7.2.5|^8.0", - "illuminate/auth": "^6.0|^7.0|^8.0", - "illuminate/container": "^6.0|^7.0|^8.0", - "illuminate/contracts": "^6.0|^7.0|^8.0", - "illuminate/database": "^6.0|^7.0|^8.0" + "php" : "^7.3|^8.0|^8.1", + "illuminate/auth": "^7.0|^8.0", + "illuminate/container": "^7.0|^8.0", + "illuminate/contracts": "^7.0|^8.0", + "illuminate/database": "^7.0|^8.0" }, "require-dev": { - "orchestra/testbench": "^4.0|^5.0|^6.0", - "phpunit/phpunit": "^8.0|^9.0", + "orchestra/testbench": "^5.0|^6.0", + "phpunit/phpunit": "^9.4", "predis/predis": "^1.1" }, "autoload": { From 08cc9bdbf89640cb1d64fb01eb9513c1cbb2549d Mon Sep 17 00:00:00 2001 From: freekmurze Date: Wed, 17 Nov 2021 11:48:05 +0000 Subject: [PATCH 106/648] Update CHANGELOG --- CHANGELOG.md | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80a6bb6d6..980ea5ecd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to `laravel-permission` will be documented in this file +## 5.4.0 - 2021-11-17 + +## What's Changed + +- Add support for PHP 8.1 by @freekmurze in https://github.com/spatie/laravel-permission/pull/1926 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/5.3.2...5.4.0 + ## 5.3.2 - 2021-11-17 ## What's Changed @@ -289,12 +297,13 @@ The following changes are not "breaking", but worth making the updates to your a - app()['cache']->forget('spatie.permission.cache'); + $this->app->make(\Spatie\Permission\PermissionRegistrar::class)->forgetCachedPermissions(); + ``` 1. Also this is a good time to point out that now with v2.25.0 and v2.26.0 most permission-cache-reset scenarios may no longer be needed in your app, so it's worth reviewing those cases, as you may gain some app speed improvement by removing unnecessary cache resets. ## 2.25.0 - 2018-11-07 -- A model's `roles` and `permissions` relations (respectively) are now automatically reloaded after an Assign/Remove role or Grant/Revoke of permissions. This means there's no longer a need to call `->fresh()` on the model if the only reason is to reload the role/permission relations. (That said, you may want to call it for other reasons.) +- A model's `roles` and `permissions` relations (respectively) are now automatically reloaded after an Assign/Remove role or Grant/Revoke of permissions. This means there's no longer a need to call `-&gt;fresh()` on the model if the only reason is to reload the role/permission relations. (That said, you may want to call it for other reasons.) - Added support for passing id to HasRole() ## 2.24.0 - 2018-11-06 @@ -337,6 +346,7 @@ The following changes are not "breaking", but worth making the updates to your a // user hasRole 'roleB' but not 'roleA' @endrole + ``` ## 2.19.1 - 2018-09-14 @@ -468,7 +478,7 @@ BEST NOT TO USE v2.7.7 if you've changed tablenames in the config file. ## 2.7.2 - 2017-10-18 -- refactor `PermissionRegistrar` to use `$gate->before()` +- refactor `PermissionRegistrar` to use `$gate-&gt;before()` - removed `log_registration_exception` as it is no longer relevant ## 2.7.1 - 2017-10-12 @@ -587,7 +597,7 @@ BEST NOT TO USE v2.7.7 if you've changed tablenames in the config file. ## 1.14.0 - 2017-10-18 -- refactor `PermissionRegistrar` to use `$gate->before()` +- refactor `PermissionRegistrar` to use `$gate-&gt;before()` - removed `log_registration_exception` as it is no longer relevant ## 1.13.0 - 2017-08-31 From a93244a9027be38584855042b3a223086b81444a Mon Sep 17 00:00:00 2001 From: Luke Downing Date: Fri, 17 Dec 2021 10:06:14 +0000 Subject: [PATCH 107/648] Adds documentation for `LazilyRefreshDatabase` (#1952) * Adds documentation for `LazilyRefreshDatabase` Howdy! When using `LazilyRefreshDatabase`, you ideally want to avoid seeding before each and every test, because that makes the trait useless. The approach described in this doc PR shows how to get around this, and also shows the user how to avoid a caveat with cached permissions when lazily refreshing the database. Thanks for all the hard work! Kind Regards, Luke * Update testing.md --- docs/advanced-usage/testing.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/advanced-usage/testing.md b/docs/advanced-usage/testing.md index bd9e64c50..d0cd3e7fe 100644 --- a/docs/advanced-usage/testing.md +++ b/docs/advanced-usage/testing.md @@ -20,6 +20,17 @@ In your tests simply add a `setUp()` instruction to re-register the permissions, } ``` +If you are using Laravel's `LazilyRefreshDatabase` trait, you most likely want to avoid seeding permissions before every test, because that would negate the use of the `LazilyRefreshDatabase` trait. To overcome this, you should wrap your seeder in an event listener for the `MigrationsEnded` event: + +```php +Event::listen(MigrationsEnded::class, function () { + $this->artisan('db:seed', ['--class' => RoleAndPermissionSeeder::class]); + $this->app->make(\Spatie\Permission\PermissionRegistrar::class)->forgetCachedPermissions(); +}); +``` + +Note that we call `PermissionRegistrar::forgetCachedPermissions` after seeding. This is to prevent a caching issue that can occur when the database is set up after permissions have already been registered and cached. + ## Factories Many applications do not require using factories to create fake roles/permissions for testing, because they use a Seeder to create specific roles and permissions that the application uses; thus tests are performed using the declared roles and permissions. From 51cef5b4a2cc593d5b1db22f7e568c08a7488b2c Mon Sep 17 00:00:00 2001 From: PaolaRuby <79208489+PaolaRuby@users.noreply.github.com> Date: Fri, 17 Dec 2021 05:06:33 -0500 Subject: [PATCH 108/648] Fix .gitattributes (#1945) --- .gitattributes | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitattributes b/.gitattributes index 7742c9ae4..5a0aa162e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,11 +2,15 @@ # https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html # Ignore all test and documentation with "export-ignore". +/.github export-ignore /.gitattributes export-ignore /.gitignore export-ignore /.travis.yml export-ignore /phpunit.xml.dist export-ignore /.scrutinizer.yml export-ignore +/art export-ignore +/docs export-ignore /tests export-ignore /.editorconfig export-ignore +/.php_cs.dist.php export-ignore /.styleci.yml export-ignore From 1fdb51702f5176469f2fc38d25c18fd373e11d84 Mon Sep 17 00:00:00 2001 From: Ali Ghasemzadeh Date: Fri, 17 Dec 2021 13:36:56 +0330 Subject: [PATCH 109/648] Livewire Role and Permissions. (#1928) Livewire Role and Permissions. --- docs/advanced-usage/ui-options.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/advanced-usage/ui-options.md b/docs/advanced-usage/ui-options.md index 787804ec4..e8029ff22 100644 --- a/docs/advanced-usage/ui-options.md +++ b/docs/advanced-usage/ui-options.md @@ -18,3 +18,6 @@ The package doesn't come with any screens out of the box, you should build that - [Laravel User Management for managing users, roles, permissions, departments and authorization](https://github.com/Mekaeil/LaravelUserManagement) by [Mekaeil](https://github.com/Mekaeil) - [Generating UI boilerplate using InfyOm](https://youtu.be/hlGu2pa1bdU) video tutorial by [Shailesh](https://github.com/shailesh-ladumor) + + +- [LiveWire Base Admin Panel](https://github.com/alighasemzadeh/bap) User management by [AliGhasemzadeh](https://github.com/alighasemzadeh) From 3f3d8742d2be29b35ed41a0c593f2e41a3f1dfe3 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Fri, 17 Dec 2021 05:15:20 -0500 Subject: [PATCH 110/648] Fix teams upgrade migration (#1959) Co-authored-by: Erik Niebla --- database/migrations/add_teams_fields.php.stub | 46 +++++++++++++------ 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/database/migrations/add_teams_fields.php.stub b/database/migrations/add_teams_fields.php.stub index 727104f17..55d2afc37 100644 --- a/database/migrations/add_teams_fields.php.stub +++ b/database/migrations/add_teams_fields.php.stub @@ -1,8 +1,10 @@ unsignedBigInteger($columnNames['team_foreign_key'])->nullable(); $table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index'); - } - }); + }); + } - Schema::table($tableNames['model_has_permissions'], function (Blueprint $table) use ($tableNames, $columnNames) { - if (! Schema::hasColumn($tableNames['model_has_permissions'], $columnNames['team_foreign_key'])) { + if (! Schema::hasColumn($tableNames['model_has_permissions'], $columnNames['team_foreign_key'])) { + Schema::table($tableNames['model_has_permissions'], function (Blueprint $table) use ($tableNames, $columnNames) { $table->unsignedBigInteger($columnNames['team_foreign_key'])->default('1');; $table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index'); + if (DB::getDriverName() !== 'sqlite') { + $table->dropForeign([PermissionRegistrar::$pivotPermission]); + } $table->dropPrimary(); - $table->primary([$columnNames['team_foreign_key'], 'permission_id', $columnNames['model_morph_key'], 'model_type'], + + $table->primary([$columnNames['team_foreign_key'], PermissionRegistrar::$pivotPermission, $columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_permission_model_type_primary'); - } - }); + if (DB::getDriverName() !== 'sqlite') { + $table->foreign(PermissionRegistrar::$pivotPermission) + ->references('id')->on($tableNames['permissions'])->onDelete('cascade'); + } + }); + } - Schema::table($tableNames['model_has_roles'], function (Blueprint $table) use ($tableNames, $columnNames) { - if (! Schema::hasColumn($tableNames['model_has_roles'], $columnNames['team_foreign_key'])) { + if (! Schema::hasColumn($tableNames['model_has_roles'], $columnNames['team_foreign_key'])) { + Schema::table($tableNames['model_has_roles'], function (Blueprint $table) use ($tableNames, $columnNames) { $table->unsignedBigInteger($columnNames['team_foreign_key'])->default('1');; $table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index'); + if (DB::getDriverName() !== 'sqlite') { + $table->dropForeign([PermissionRegistrar::$pivotRole]); + } $table->dropPrimary(); - $table->primary([$columnNames['team_foreign_key'], 'role_id', $columnNames['model_morph_key'], 'model_type'], + + $table->primary([$columnNames['team_foreign_key'], PermissionRegistrar::$pivotRole, $columnNames['model_morph_key'], 'model_type'], 'model_has_roles_role_model_type_primary'); - } - }); + if (DB::getDriverName() !== 'sqlite') { + $table->foreign(PermissionRegistrar::$pivotRole) + ->references('id')->on($tableNames['roles'])->onDelete('cascade'); + } + }); + } app('cache') ->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null) From 79f7dbf2b6b21d81209774f5726a0e9f4222dd92 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Fri, 17 Dec 2021 05:18:05 -0500 Subject: [PATCH 111/648] [V5] WherePivot instead of only Where on team relation pivot, better readability (#1944) * WherePivot instead of only Where on team relation pivot * Fix detach, sync after wherePivot on relations Co-authored-by: Erik Niebla --- src/Traits/HasPermissions.php | 65 +++++++++++------------------- src/Traits/HasRoles.php | 76 +++++++++++++---------------------- src/helpers.php | 2 +- 3 files changed, 53 insertions(+), 90 deletions(-) diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index 6baecb5dc..00e31ce74 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -44,18 +44,19 @@ public function getPermissionClass() */ public function permissions(): BelongsToMany { - return $this->morphToMany( + $relation = $this->morphToMany( config('permission.models.permission'), 'model', config('permission.table_names.model_has_permissions'), config('permission.column_names.model_morph_key'), PermissionRegistrar::$pivotPermission - ) - ->where(function ($q) { - $q->when(PermissionRegistrar::$teams, function ($q) { - $q->where(PermissionRegistrar::$teamsKey, app(PermissionRegistrar::class)->getPermissionsTeamId()); - }); - }); + ); + + if (! PermissionRegistrar::$teams) { + return $relation; + } + + return $relation->wherePivot(PermissionRegistrar::$teamsKey, getPermissionsTeamId()); } /** @@ -314,21 +315,6 @@ public function getAllPermissions(): Collection return $permissions->sort()->values(); } - /** - * Add teams pivot if teams are enabled - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany - */ - protected function getPermissionsRelation() - { - $relation = $this->permissions(); - if (PermissionRegistrar::$teams && ! is_a($this, Role::class)) { - $relation->wherePivot(PermissionRegistrar::$teamsKey, app(PermissionRegistrar::class)->getPermissionsTeamId()); - } - - return $relation; - } - /** * Grant the given permission(s) to a role. * @@ -338,33 +324,30 @@ protected function getPermissionsRelation() */ public function givePermissionTo(...$permissions) { - $permissionClass = $this->getPermissionClass(); $permissions = collect($permissions) ->flatten() - ->map(function ($permission) { + ->reduce(function ($array, $permission) { if (empty($permission)) { - return false; + return $array; + } + + $permission = $this->getStoredPermission($permission); + if (! $permission instanceof Permission) { + return $array; } - return $this->getStoredPermission($permission); - }) - ->filter(function ($permission) { - return $permission instanceof Permission; - }) - ->each(function ($permission) { $this->ensureModelSharesGuard($permission); - }) - ->map(function ($permission) { - return [$permission->getKeyName() => $permission->getKey(), 'values' => PermissionRegistrar::$teams && ! is_a($this, Role::class) ? - [PermissionRegistrar::$teamsKey => app(PermissionRegistrar::class)->getPermissionsTeamId()] : [], - ]; - }) - ->pluck('values', (new $permissionClass())->getKeyName())->toArray(); + + $array[$permission->getKey()] = PermissionRegistrar::$teams && ! is_a($this, Role::class) ? + [PermissionRegistrar::$teamsKey => getPermissionsTeamId()] : []; + + return $array; + }, []); $model = $this->getModel(); if ($model->exists) { - $this->getPermissionsRelation()->sync($permissions, false); + $this->permissions()->sync($permissions, false); $model->load('permissions'); } else { $class = \get_class($model); @@ -396,7 +379,7 @@ function ($object) use ($permissions, $model) { */ public function syncPermissions(...$permissions) { - $this->getPermissionsRelation()->detach(); + $this->permissions()->detach(); return $this->givePermissionTo($permissions); } @@ -410,7 +393,7 @@ public function syncPermissions(...$permissions) */ public function revokePermissionTo($permission) { - $this->getPermissionsRelation()->detach($this->getStoredPermission($permission)); + $this->permissions()->detach($this->getStoredPermission($permission)); if (is_a($this, get_class(app(PermissionRegistrar::class)->getRoleClass()))) { $this->forgetCachedPermissions(); diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index 54378b236..926381850 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -41,25 +41,23 @@ public function getRoleClass() */ public function roles(): BelongsToMany { - $model_has_roles = config('permission.table_names.model_has_roles'); - - return $this->morphToMany( + $relation = $this->morphToMany( config('permission.models.role'), 'model', - $model_has_roles, + config('permission.table_names.model_has_roles'), config('permission.column_names.model_morph_key'), PermissionRegistrar::$pivotRole - ) - ->where(function ($q) use ($model_has_roles) { - $q->when(PermissionRegistrar::$teams, function ($q) use ($model_has_roles) { - $teamId = app(PermissionRegistrar::class)->getPermissionsTeamId(); - $q->where($model_has_roles.'.'.PermissionRegistrar::$teamsKey, $teamId) - ->where(function ($q) use ($teamId) { - $teamField = config('permission.table_names.roles').'.'.PermissionRegistrar::$teamsKey; - $q->whereNull($teamField)->orWhere($teamField, $teamId); - }); + ); + + if (! PermissionRegistrar::$teams) { + return $relation; + } + + return $relation->wherePivot(PermissionRegistrar::$teamsKey, getPermissionsTeamId()) + ->where(function ($q) { + $teamField = config('permission.table_names.roles').'.'.PermissionRegistrar::$teamsKey; + $q->whereNull($teamField)->orWhere($teamField, getPermissionsTeamId()); }); - }); } /** @@ -98,21 +96,6 @@ public function scopeRole(Builder $query, $roles, $guard = null): Builder }); } - /** - * Add teams pivot if teams are enabled - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany - */ - protected function getRolesRelation() - { - $relation = $this->roles(); - if (PermissionRegistrar::$teams && ! is_a($this, Permission::class)) { - $relation->wherePivot(PermissionRegistrar::$teamsKey, app(PermissionRegistrar::class)->getPermissionsTeamId()); - } - - return $relation; - } - /** * Assign the given role to the model. * @@ -122,33 +105,30 @@ protected function getRolesRelation() */ public function assignRole(...$roles) { - $roleClass = $this->getRoleClass(); $roles = collect($roles) ->flatten() - ->map(function ($role) { + ->reduce(function ($array, $role) { if (empty($role)) { - return false; + return $array; + } + + $role = $this->getStoredRole($role); + if (! $role instanceof Role) { + return $array; } - return $this->getStoredRole($role); - }) - ->filter(function ($role) { - return $role instanceof Role; - }) - ->each(function ($role) { $this->ensureModelSharesGuard($role); - }) - ->map(function ($role) { - return [$role->getKeyName() => $role->getKey(), 'values' => PermissionRegistrar::$teams && ! is_a($this, Permission::class) ? - [PermissionRegistrar::$teamsKey => app(PermissionRegistrar::class)->getPermissionsTeamId()] : [], - ]; - }) - ->pluck('values', (new $roleClass())->getKeyName())->toArray(); + + $array[$role->getKey()] = PermissionRegistrar::$teams && ! is_a($this, Permission::class) ? + [PermissionRegistrar::$teamsKey => getPermissionsTeamId()] : []; + + return $array; + }, []); $model = $this->getModel(); if ($model->exists) { - $this->getRolesRelation()->sync($roles, false); + $this->roles()->sync($roles, false); $model->load('roles'); } else { $class = \get_class($model); @@ -178,7 +158,7 @@ function ($object) use ($roles, $model) { */ public function removeRole($role) { - $this->getRolesRelation()->detach($this->getStoredRole($role)); + $this->roles()->detach($this->getStoredRole($role)); $this->load('roles'); @@ -198,7 +178,7 @@ public function removeRole($role) */ public function syncRoles(...$roles) { - $this->getRolesRelation()->detach(); + $this->roles()->detach(); return $this->assignRole($roles); } diff --git a/src/helpers.php b/src/helpers.php index f4df0a7cc..25116d0ff 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -36,6 +36,6 @@ function setPermissionsTeamId($id) */ function getPermissionsTeamId() { - app(\Spatie\Permission\PermissionRegistrar::class)->getPermissionsTeamId(); + return app(\Spatie\Permission\PermissionRegistrar::class)->getPermissionsTeamId(); } } From 94443e59d64c90186c65000ac0859c8120f76379 Mon Sep 17 00:00:00 2001 From: Freek Van der Herten Date: Thu, 23 Dec 2021 22:16:13 +0100 Subject: [PATCH 112/648] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index df0e89b52..141fb33de 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ ## Documentation, Installation, and Usage Instructions -See the [DOCUMENTATION](https://docs.spatie.be/laravel-permission/) for detailed installation and usage instructions. +See the [documentation](https://spatie.be/docs/laravel-permission/) for detailed installation and usage instructions. ## What It Does This package allows you to manage user permissions and roles in a database. From 46fc8f201fa4bc771ba8be11d47cc00df3933a10 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Mon, 3 Jan 2022 06:23:42 -0500 Subject: [PATCH 113/648] Fix links (#1973) Co-authored-by: Erik Niebla --- docs/introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/introduction.md b/docs/introduction.md index 466402858..604fa8cac 100644 --- a/docs/introduction.md +++ b/docs/introduction.md @@ -17,7 +17,7 @@ $user->assignRole('writer'); $role->givePermissionTo('edit articles'); ``` -If you're using multiple guards we've got you covered as well. Every guard will have its own set of permissions and roles that can be assigned to the guard's users. Read about it in the [using multiple guards](https://docs.spatie.be/laravel-permission/v5/basic-usage/multiple-guards/) section. +If you're using multiple guards we've got you covered as well. Every guard will have its own set of permissions and roles that can be assigned to the guard's users. Read about it in the [using multiple guards](./basic-usage/multiple-guards/) section. Because all permissions will be registered on [Laravel's gate](https://laravel.com/docs/authorization), you can check if a user has a permission with Laravel's default `can` function: From 626b3ae8c8fc0670fb479e8d7ff9c2e4a210e9ed Mon Sep 17 00:00:00 2001 From: erikn69 Date: Mon, 3 Jan 2022 06:24:35 -0500 Subject: [PATCH 114/648] Fix #1966 `Duplicate entry 'roles_name_guard_name_unique'` (#1970) Co-authored-by: Erik Niebla --- database/migrations/add_teams_fields.php.stub | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/database/migrations/add_teams_fields.php.stub b/database/migrations/add_teams_fields.php.stub index 55d2afc37..6df2378bf 100644 --- a/database/migrations/add_teams_fields.php.stub +++ b/database/migrations/add_teams_fields.php.stub @@ -31,8 +31,11 @@ class AddTeamsFields extends Migration if (! Schema::hasColumn($tableNames['roles'], $columnNames['team_foreign_key'])) { Schema::table($tableNames['roles'], function (Blueprint $table) use ($columnNames) { - $table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable(); + $table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable()->after('id'); $table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index'); + + $table->dropUnique('roles_name_guard_name_unique'); + $table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']); }); } From 0da24dbdfe4611be1f56b4310e789b5ae4de4d1a Mon Sep 17 00:00:00 2001 From: Ahmed Kamel Date: Mon, 3 Jan 2022 13:24:50 +0200 Subject: [PATCH 115/648] fix use multiple guards (#1965) --- docs/basic-usage/basic-usage.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/basic-usage/basic-usage.md b/docs/basic-usage/basic-usage.md index f1508ea5a..28c30efd2 100644 --- a/docs/basic-usage/basic-usage.md +++ b/docs/basic-usage/basic-usage.md @@ -50,7 +50,7 @@ $role->revokePermissionTo($permission); $permission->removeRole($role); ``` -If you're using multiple guards the `guard_name` attribute needs to be set as well. Read about it in the [using multiple guards](../multiple-guards) section of the readme. +If you're using multiple guards the `guard_name` attribute needs to be set as well. Read about it in the [using multiple guards](./multiple-guards) section of the readme. The `HasRoles` trait adds Eloquent relationships to your models, which can be accessed directly or used as a base query: From 68d3ef3e179314fdac468eb230ca003b4c1fc3af Mon Sep 17 00:00:00 2001 From: erikn69 Date: Mon, 3 Jan 2022 06:26:22 -0500 Subject: [PATCH 116/648] Teams test fixes, global helper setPermissionsTeamId on tests (#1911) Co-authored-by: Erik Niebla --- docs/basic-usage/teams-permissions.md | 4 +- src/PermissionRegistrar.php | 4 +- tests/HasPermissionsTest.php | 17 +++---- tests/HasPermissionsWithCustomModelsTest.php | 5 +- tests/HasRolesTest.php | 15 +++--- tests/HasRolesWithCustomModelsTest.php | 15 ++++++ tests/TeamHasPermissionsTest.php | 53 +++++++++++++++----- tests/TeamHasRolesTest.php | 44 +++++++++++++--- tests/TestCase.php | 33 +++--------- 9 files changed, 120 insertions(+), 70 deletions(-) create mode 100644 tests/HasRolesWithCustomModelsTest.php diff --git a/docs/basic-usage/teams-permissions.md b/docs/basic-usage/teams-permissions.md index a9152ee71..9c57ffc15 100644 --- a/docs/basic-usage/teams-permissions.md +++ b/docs/basic-usage/teams-permissions.md @@ -34,12 +34,12 @@ class TeamsPermission{ public function handle($request, \Closure $next){ if(!empty(auth()->user())){ // session value set on login - app(\Spatie\Permission\PermissionRegistrar::class)->setPermissionsTeamId(session('team_id')); + setPermissionsTeamId(session('team_id')); } // other custom ways to get team_id /*if(!empty(auth('api')->user())){ // `getTeamIdFromToken()` example of custom method for getting the set team_id - app(\Spatie\Permission\PermissionRegistrar::class)->setPermissionsTeamId(auth('api')->user()->getTeamIdFromToken()); + setPermissionsTeamId(auth('api')->user()->getTeamIdFromToken()); }*/ return $next($request); diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index 0326be646..6d228440b 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -41,7 +41,7 @@ class PermissionRegistrar /** @var string */ public static $teamsKey; - /** @var int */ + /** @var int|string */ protected $teamId = null; /** @var string */ @@ -163,7 +163,7 @@ public function clearClassPermissions() */ private function loadPermissions() { - if ($this->permissions !== null) { + if ($this->permissions) { return; } diff --git a/tests/HasPermissionsTest.php b/tests/HasPermissionsTest.php index 11dd4b0f2..df793e451 100644 --- a/tests/HasPermissionsTest.php +++ b/tests/HasPermissionsTest.php @@ -2,6 +2,7 @@ namespace Spatie\Permission\Test; +use DB; use Spatie\Permission\Contracts\Permission; use Spatie\Permission\Contracts\Role; use Spatie\Permission\Exceptions\GuardDoesNotMatch; @@ -381,7 +382,7 @@ public function it_can_list_all_the_permissions_via_roles_of_user() $this->assertEquals( collect(['edit-articles', 'edit-news']), - $this->testUser->getPermissionsViaRoles()->pluck('name') + $this->testUser->getPermissionsViaRoles()->pluck('name')->sort()->values() ); } @@ -491,17 +492,16 @@ public function calling_givePermissionTo_before_saving_object_doesnt_interfere_w $user2 = new User(['email' => 'test2@user.com']); $user2->givePermissionTo('edit-articles'); - \DB::enableQueryLog(); + DB::enableQueryLog(); $user2->save(); - $querys = \DB::getQueryLog(); - \DB::disableQueryLog(); + DB::disableQueryLog(); $this->assertTrue($user->fresh()->hasPermissionTo('edit-news')); $this->assertFalse($user->fresh()->hasPermissionTo('edit-articles')); $this->assertTrue($user2->fresh()->hasPermissionTo('edit-articles')); $this->assertFalse($user2->fresh()->hasPermissionTo('edit-news')); - $this->assertSame(4, count($querys)); //avoid unnecessary sync + $this->assertSame(4, count(DB::getQueryLog())); //avoid unnecessary sync } /** @test */ @@ -514,17 +514,16 @@ public function calling_syncPermissions_before_saving_object_doesnt_interfere_wi $user2 = new User(['email' => 'test2@user.com']); $user2->syncPermissions('edit-articles'); - \DB::enableQueryLog(); + DB::enableQueryLog(); $user2->save(); - $querys = \DB::getQueryLog(); - \DB::disableQueryLog(); + DB::disableQueryLog(); $this->assertTrue($user->fresh()->hasPermissionTo('edit-news')); $this->assertFalse($user->fresh()->hasPermissionTo('edit-articles')); $this->assertTrue($user2->fresh()->hasPermissionTo('edit-articles')); $this->assertFalse($user2->fresh()->hasPermissionTo('edit-news')); - $this->assertSame(4, count($querys)); //avoid unnecessary sync + $this->assertSame(4, count(DB::getQueryLog())); //avoid unnecessary sync } /** @test */ diff --git a/tests/HasPermissionsWithCustomModelsTest.php b/tests/HasPermissionsWithCustomModelsTest.php index 92970121a..bdb237a5c 100644 --- a/tests/HasPermissionsWithCustomModelsTest.php +++ b/tests/HasPermissionsWithCustomModelsTest.php @@ -8,9 +8,8 @@ class HasPermissionsWithCustomModelsTest extends HasPermissionsTest protected $useCustomModels = true; /** @test */ - public function it_can_use_custom_models() + public function it_can_use_custom_model_permission() { - $this->assertSame(get_class($this->testUserPermission), \Spatie\Permission\Test\Permission::class); - $this->assertSame(get_class($this->testUserRole), \Spatie\Permission\Test\Role::class); + $this->assertSame(get_class($this->testUserPermission), Permission::class); } } diff --git a/tests/HasRolesTest.php b/tests/HasRolesTest.php index bd7415070..0031b58da 100644 --- a/tests/HasRolesTest.php +++ b/tests/HasRolesTest.php @@ -2,6 +2,7 @@ namespace Spatie\Permission\Test; +use DB; use Spatie\Permission\Contracts\Role; use Spatie\Permission\Exceptions\GuardDoesNotMatch; use Spatie\Permission\Exceptions\RoleDoesNotExist; @@ -246,17 +247,16 @@ public function calling_syncRoles_before_saving_object_doesnt_interfere_with_oth $user2 = new User(['email' => 'admin@user.com']); $user2->syncRoles('testRole2'); - \DB::enableQueryLog(); + DB::enableQueryLog(); $user2->save(); - $querys = \DB::getQueryLog(); - \DB::disableQueryLog(); + DB::disableQueryLog(); $this->assertTrue($user->fresh()->hasRole('testRole')); $this->assertFalse($user->fresh()->hasRole('testRole2')); $this->assertTrue($user2->fresh()->hasRole('testRole2')); $this->assertFalse($user2->fresh()->hasRole('testRole')); - $this->assertSame(4, count($querys)); //avoid unnecessary sync + $this->assertSame(4, count(DB::getQueryLog())); //avoid unnecessary sync } /** @test */ @@ -269,17 +269,16 @@ public function calling_assignRole_before_saving_object_doesnt_interfere_with_ot $admin_user = new User(['email' => 'admin@user.com']); $admin_user->assignRole('testRole2'); - \DB::enableQueryLog(); + DB::enableQueryLog(); $admin_user->save(); - $querys = \DB::getQueryLog(); - \DB::disableQueryLog(); + DB::disableQueryLog(); $this->assertTrue($user->fresh()->hasRole('testRole')); $this->assertFalse($user->fresh()->hasRole('testRole2')); $this->assertTrue($admin_user->fresh()->hasRole('testRole2')); $this->assertFalse($admin_user->fresh()->hasRole('testRole')); - $this->assertSame(4, count($querys)); //avoid unnecessary sync + $this->assertSame(4, count(DB::getQueryLog())); //avoid unnecessary sync } /** @test */ diff --git a/tests/HasRolesWithCustomModelsTest.php b/tests/HasRolesWithCustomModelsTest.php new file mode 100644 index 000000000..d39739205 --- /dev/null +++ b/tests/HasRolesWithCustomModelsTest.php @@ -0,0 +1,15 @@ +assertSame(get_class($this->testUserRole), Role::class); + } +} diff --git a/tests/TeamHasPermissionsTest.php b/tests/TeamHasPermissionsTest.php index 4fc345225..ccc3de93a 100644 --- a/tests/TeamHasPermissionsTest.php +++ b/tests/TeamHasPermissionsTest.php @@ -10,15 +10,15 @@ class TeamHasPermissionsTest extends HasPermissionsTest /** @test */ public function it_can_assign_same_and_different_permission_on_same_user_on_different_teams() { - $this->setPermissionsTeamId(1); + setPermissionsTeamId(1); $this->testUser->load('permissions'); $this->testUser->givePermissionTo('edit-articles', 'edit-news'); - $this->setPermissionsTeamId(2); + setPermissionsTeamId(2); $this->testUser->load('permissions'); $this->testUser->givePermissionTo('edit-articles', 'edit-blog'); - $this->setPermissionsTeamId(1); + setPermissionsTeamId(1); $this->testUser->load('permissions'); $this->assertEquals( collect(['edit-articles', 'edit-news']), @@ -27,7 +27,7 @@ public function it_can_assign_same_and_different_permission_on_same_user_on_diff $this->assertTrue($this->testUser->hasAllDirectPermissions(['edit-articles', 'edit-news'])); $this->assertFalse($this->testUser->hasAllDirectPermissions(['edit-articles', 'edit-blog'])); - $this->setPermissionsTeamId(2); + setPermissionsTeamId(2); $this->testUser->load('permissions'); $this->assertEquals( collect(['edit-articles', 'edit-blog']), @@ -42,17 +42,17 @@ public function it_can_list_all_the_coupled_permissions_both_directly_and_via_ro { $this->testUserRole->givePermissionTo('edit-articles'); - $this->setPermissionsTeamId(1); + setPermissionsTeamId(1); $this->testUser->load('permissions'); $this->testUser->assignRole('testRole'); $this->testUser->givePermissionTo('edit-news'); - $this->setPermissionsTeamId(2); + setPermissionsTeamId(2); $this->testUser->load('permissions'); $this->testUser->assignRole('testRole'); $this->testUser->givePermissionTo('edit-blog'); - $this->setPermissionsTeamId(1); + setPermissionsTeamId(1); $this->testUser->load('roles'); $this->testUser->load('permissions'); @@ -61,7 +61,7 @@ public function it_can_list_all_the_coupled_permissions_both_directly_and_via_ro $this->testUser->getAllPermissions()->pluck('name')->sort()->values() ); - $this->setPermissionsTeamId(2); + setPermissionsTeamId(2); $this->testUser->load('roles'); $this->testUser->load('permissions'); @@ -74,15 +74,15 @@ public function it_can_list_all_the_coupled_permissions_both_directly_and_via_ro /** @test */ public function it_can_sync_or_remove_permission_without_detach_on_different_teams() { - $this->setPermissionsTeamId(1); + setPermissionsTeamId(1); $this->testUser->load('permissions'); $this->testUser->syncPermissions('edit-articles', 'edit-news'); - $this->setPermissionsTeamId(2); + setPermissionsTeamId(2); $this->testUser->load('permissions'); $this->testUser->syncPermissions('edit-articles', 'edit-blog'); - $this->setPermissionsTeamId(1); + setPermissionsTeamId(1); $this->testUser->load('permissions'); $this->assertEquals( @@ -96,11 +96,40 @@ public function it_can_sync_or_remove_permission_without_detach_on_different_tea $this->testUser->getPermissionNames()->sort()->values() ); - $this->setPermissionsTeamId(2); + setPermissionsTeamId(2); $this->testUser->load('permissions'); $this->assertEquals( collect(['edit-articles', 'edit-blog']), $this->testUser->getPermissionNames()->sort()->values() ); } + + /** @test */ + public function it_can_scope_users_on_different_teams() + { + $user1 = User::create(['email' => 'user1@test.com']); + $user2 = User::create(['email' => 'user2@test.com']); + + setPermissionsTeamId(2); + $user1->givePermissionTo(['edit-articles', 'edit-news']); + $this->testUserRole->givePermissionTo('edit-articles'); + $user2->assignRole('testRole'); + + setPermissionsTeamId(1); + $user1->givePermissionTo(['edit-articles']); + + setPermissionsTeamId(2); + $scopedUsers1Team2 = User::permission(['edit-articles', 'edit-news'])->get(); + $scopedUsers2Team2 = User::permission('edit-news')->get(); + + $this->assertEquals(2, $scopedUsers1Team2->count()); + $this->assertEquals(1, $scopedUsers2Team2->count()); + + setPermissionsTeamId(1); + $scopedUsers1Team1 = User::permission(['edit-articles', 'edit-news'])->get(); + $scopedUsers2Team1 = User::permission('edit-news')->get(); + + $this->assertEquals(1, $scopedUsers1Team1->count()); + $this->assertEquals(0, $scopedUsers2Team1->count()); + } } diff --git a/tests/TeamHasRolesTest.php b/tests/TeamHasRolesTest.php index b07614d8e..05393194a 100644 --- a/tests/TeamHasRolesTest.php +++ b/tests/TeamHasRolesTest.php @@ -22,15 +22,15 @@ public function it_can_assign_same_and_different_roles_on_same_user_different_te $this->assertNotNull($testRole3Team1); $this->assertNotNull($testRole4NoTeam); - $this->setPermissionsTeamId(1); + setPermissionsTeamId(1); $this->testUser->load('roles'); $this->testUser->assignRole('testRole', 'testRole2'); - $this->setPermissionsTeamId(2); + setPermissionsTeamId(2); $this->testUser->load('roles'); $this->testUser->assignRole('testRole', 'testRole3'); - $this->setPermissionsTeamId(1); + setPermissionsTeamId(1); $this->testUser->load('roles'); $this->assertEquals( @@ -44,7 +44,7 @@ public function it_can_assign_same_and_different_roles_on_same_user_different_te $this->assertTrue($this->testUser->hasRole($testRole3Team1)); //testRole3 team=1 $this->assertTrue($this->testUser->hasRole($testRole4NoTeam)); // global role team=null - $this->setPermissionsTeamId(2); + setPermissionsTeamId(2); $this->testUser->load('roles'); $this->assertEquals( @@ -63,15 +63,15 @@ public function it_can_sync_or_remove_roles_without_detach_on_different_teams() { app(Role::class)->create(['name' => 'testRole3', 'team_test_id' => 2]); - $this->setPermissionsTeamId(1); + setPermissionsTeamId(1); $this->testUser->load('roles'); $this->testUser->syncRoles('testRole', 'testRole2'); - $this->setPermissionsTeamId(2); + setPermissionsTeamId(2); $this->testUser->load('roles'); $this->testUser->syncRoles('testRole', 'testRole3'); - $this->setPermissionsTeamId(1); + setPermissionsTeamId(1); $this->testUser->load('roles'); $this->assertEquals( @@ -85,7 +85,7 @@ public function it_can_sync_or_remove_roles_without_detach_on_different_teams() $this->testUser->getRoleNames()->sort()->values() ); - $this->setPermissionsTeamId(2); + setPermissionsTeamId(2); $this->testUser->load('roles'); $this->assertEquals( @@ -93,4 +93,32 @@ public function it_can_sync_or_remove_roles_without_detach_on_different_teams() $this->testUser->getRoleNames()->sort()->values() ); } + + /** @test */ + public function it_can_scope_users_on_different_teams() + { + $user1 = User::create(['email' => 'user1@test.com']); + $user2 = User::create(['email' => 'user2@test.com']); + + setPermissionsTeamId(2); + $user1->assignRole($this->testUserRole); + $user2->assignRole('testRole2'); + + setPermissionsTeamId(1); + $user1->assignRole('testRole'); + + setPermissionsTeamId(2); + $scopedUsers1Team1 = User::role($this->testUserRole)->get(); + $scopedUsers2Team1 = User::role(['testRole', 'testRole2'])->get(); + + $this->assertEquals(1, $scopedUsers1Team1->count()); + $this->assertEquals(2, $scopedUsers2Team1->count()); + + setPermissionsTeamId(1); + $scopedUsers1Team2 = User::role($this->testUserRole)->get(); + $scopedUsers2Team2 = User::role('testRole2')->get(); + + $this->assertEquals(1, $scopedUsers1Team2->count()); + $this->assertEquals(0, $scopedUsers2Team2->count()); + } } diff --git a/tests/TestCase.php b/tests/TestCase.php index 72c527df8..80a0c3c0b 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -46,17 +46,9 @@ public function setUp(): void // Note: this also flushes the cache from within the migration $this->setUpDatabase($this->app); if ($this->hasTeams) { - $this->setPermissionsTeamId(1); + setPermissionsTeamId(1); } - $this->testUser = User::first(); - $this->testUserRole = app(Role::class)->find(1); - $this->testUserPermission = app(Permission::class)->find(1); - - $this->testAdmin = Admin::first(); - $this->testAdminRole = app(Role::class)->find(3); - $this->testAdminPermission = app(Permission::class)->find(4); - $this->setUpRoutes(); } @@ -137,34 +129,23 @@ protected function setUpDatabase($app) (new \CreatePermissionTables())->up(); - User::create(['email' => 'test@user.com']); - Admin::create(['email' => 'admin@user.com']); - $app[Role::class]->create(['name' => 'testRole']); + $this->testUser = User::create(['email' => 'test@user.com']); + $this->testAdmin = Admin::create(['email' => 'admin@user.com']); + $this->testUserRole = $app[Role::class]->create(['name' => 'testRole']); $app[Role::class]->create(['name' => 'testRole2']); - $app[Role::class]->create(['name' => 'testAdminRole', 'guard_name' => 'admin']); - $app[Permission::class]->create(['name' => 'edit-articles']); + $this->testAdminRole = $app[Role::class]->create(['name' => 'testAdminRole', 'guard_name' => 'admin']); + $this->testUserPermission = $app[Permission::class]->create(['name' => 'edit-articles']); $app[Permission::class]->create(['name' => 'edit-news']); $app[Permission::class]->create(['name' => 'edit-blog']); - $app[Permission::class]->create(['name' => 'admin-permission', 'guard_name' => 'admin']); + $this->testAdminPermission = $app[Permission::class]->create(['name' => 'admin-permission', 'guard_name' => 'admin']); $app[Permission::class]->create(['name' => 'Edit News']); } - /** - * Reload the permissions. - */ protected function reloadPermissions() { app(PermissionRegistrar::class)->forgetCachedPermissions(); } - /** - * Change the team_id - */ - protected function setPermissionsTeamId(int $id) - { - app(PermissionRegistrar::class)->setPermissionsTeamId($id); - } - public function createCacheTable() { Schema::create('cache', function ($table) { From 10c7b8657e566bb8c01ec77c8cf1f4f192b073dc Mon Sep 17 00:00:00 2001 From: angeljqv <79208641+angeljqv@users.noreply.github.com> Date: Mon, 3 Jan 2022 06:27:49 -0500 Subject: [PATCH 117/648] Use global helpers (#1963) --- src/Commands/CreateRole.php | 6 +++--- src/Models/Role.php | 17 ++++++++++------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/Commands/CreateRole.php b/src/Commands/CreateRole.php index fdd76ad10..e4701f014 100644 --- a/src/Commands/CreateRole.php +++ b/src/Commands/CreateRole.php @@ -21,8 +21,8 @@ public function handle() { $roleClass = app(RoleContract::class); - $teamIdAux = app(PermissionRegistrar::class)->getPermissionsTeamId(); - app(PermissionRegistrar::class)->setPermissionsTeamId($this->option('team-id') ?: null); + $teamIdAux = getPermissionsTeamId(); + setPermissionsTeamId($this->option('team-id') ?: null); if (! PermissionRegistrar::$teams && $this->option('team-id')) { $this->warn("Teams feature disabled, argument --team-id has no effect. Either enable it in permissions config file or remove --team-id parameter"); @@ -31,7 +31,7 @@ public function handle() } $role = $roleClass::findOrCreate($this->argument('name'), $this->argument('guard')); - app(PermissionRegistrar::class)->setPermissionsTeamId($teamIdAux); + setPermissionsTeamId($teamIdAux); $teams_key = PermissionRegistrar::$teamsKey; if (PermissionRegistrar::$teams && $this->option('team-id') && is_null($role->$teams_key)) { diff --git a/src/Models/Role.php b/src/Models/Role.php index 2c6fcfd25..b1da140b7 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -43,7 +43,7 @@ public static function create(array $attributes = []) if (array_key_exists(PermissionRegistrar::$teamsKey, $attributes)) { $params[PermissionRegistrar::$teamsKey] = $attributes[PermissionRegistrar::$teamsKey]; } else { - $attributes[PermissionRegistrar::$teamsKey] = app(PermissionRegistrar::class)->getPermissionsTeamId(); + $attributes[PermissionRegistrar::$teamsKey] = getPermissionsTeamId(); } } if (static::findByParam($params)) { @@ -131,7 +131,7 @@ public static function findOrCreate(string $name, $guardName = null): RoleContra $role = static::findByParam(['name' => $name, 'guard_name' => $guardName]); if (! $role) { - return static::query()->create(['name' => $name, 'guard_name' => $guardName] + (PermissionRegistrar::$teams ? [PermissionRegistrar::$teamsKey => app(PermissionRegistrar::class)->getPermissionsTeamId()] : [])); + return static::query()->create(['name' => $name, 'guard_name' => $guardName] + (PermissionRegistrar::$teams ? [PermissionRegistrar::$teamsKey => getPermissionsTeamId()] : [])); } return $role; @@ -139,13 +139,16 @@ public static function findOrCreate(string $name, $guardName = null): RoleContra protected static function findByParam(array $params = []) { - $query = static::when(PermissionRegistrar::$teams, function ($q) use ($params) { - $q->where(function ($q) use ($params) { + $query = static::query(); + + if (PermissionRegistrar::$teams) { + $query->where(function ($q) use ($params) { $q->whereNull(PermissionRegistrar::$teamsKey) - ->orWhere(PermissionRegistrar::$teamsKey, $params[PermissionRegistrar::$teamsKey] ?? app(PermissionRegistrar::class)->getPermissionsTeamId()); + ->orWhere(PermissionRegistrar::$teamsKey, $params[PermissionRegistrar::$teamsKey] ?? getPermissionsTeamId()); }); - }); - unset($params[PermissionRegistrar::$teamsKey]); + unset($params[PermissionRegistrar::$teamsKey]); + } + foreach ($params as $key => $value) { $query->where($key, $value); } From 2017a9853320e2a235a8317ee20a161f1dc46616 Mon Sep 17 00:00:00 2001 From: angeljqv <79208641+angeljqv@users.noreply.github.com> Date: Mon, 3 Jan 2022 06:28:14 -0500 Subject: [PATCH 118/648] Replace is_array with Arr::wrap (#1962) --- src/PermissionServiceProvider.php | 13 +++---------- src/Traits/HasPermissions.php | 5 ++--- src/Traits/HasRoles.php | 7 ++----- 3 files changed, 7 insertions(+), 18 deletions(-) diff --git a/src/PermissionServiceProvider.php b/src/PermissionServiceProvider.php index c185022e6..db7bb6c9e 100644 --- a/src/PermissionServiceProvider.php +++ b/src/PermissionServiceProvider.php @@ -4,6 +4,7 @@ use Illuminate\Filesystem\Filesystem; use Illuminate\Routing\Route; +use Illuminate\Support\Arr; use Illuminate\Support\Collection; use Illuminate\Support\ServiceProvider; use Illuminate\View\Compilers\BladeCompiler; @@ -152,11 +153,7 @@ protected function registerMacroHelpers() } Route::macro('role', function ($roles = []) { - if (! is_array($roles)) { - $roles = [$roles]; - } - - $roles = implode('|', $roles); + $roles = implode('|', Arr::wrap($roles)); $this->middleware("role:$roles"); @@ -164,11 +161,7 @@ protected function registerMacroHelpers() }); Route::macro('permission', function ($permissions = []) { - if (! is_array($permissions)) { - $permissions = [$permissions]; - } - - $permissions = implode('|', $permissions); + $permissions = implode('|', Arr::wrap($permissions)); $this->middleware("permission:$permissions"); diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index 00e31ce74..ecf07414f 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -4,6 +4,7 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Relations\BelongsToMany; +use Illuminate\Support\Arr; use Illuminate\Support\Collection; use Spatie\Permission\Contracts\Permission; use Spatie\Permission\Contracts\Role; @@ -103,8 +104,6 @@ protected function convertToPermissionModels($permissions): array $permissions = $permissions->all(); } - $permissions = is_array($permissions) ? $permissions : [$permissions]; - return array_map(function ($permission) { if ($permission instanceof Permission) { return $permission; @@ -112,7 +111,7 @@ protected function convertToPermissionModels($permissions): array $method = is_string($permission) ? 'findByName' : 'findById'; return $this->getPermissionClass()->{$method}($permission, $this->getDefaultGuardName()); - }, $permissions); + }, Arr::wrap($permissions)); } /** diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index 926381850..f70f1daff 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -4,6 +4,7 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Relations\BelongsToMany; +use Illuminate\Support\Arr; use Illuminate\Support\Collection; use Spatie\Permission\Contracts\Permission; use Spatie\Permission\Contracts\Role; @@ -75,10 +76,6 @@ public function scopeRole(Builder $query, $roles, $guard = null): Builder $roles = $roles->all(); } - if (! is_array($roles)) { - $roles = [$roles]; - } - $roles = array_map(function ($role) use ($guard) { if ($role instanceof Role) { return $role; @@ -87,7 +84,7 @@ public function scopeRole(Builder $query, $roles, $guard = null): Builder $method = is_numeric($role) ? 'findById' : 'findByName'; return $this->getRoleClass()->{$method}($role, $guard ?: $this->getDefaultGuardName()); - }, $roles); + }, Arr::wrap($roles)); return $query->whereHas('roles', function (Builder $subQuery) use ($roles) { $roleClass = $this->getRoleClass(); From 3aab62f59fa23b8f88ff464fe46bf939aa976ae2 Mon Sep 17 00:00:00 2001 From: angeljqv <79208641+angeljqv@users.noreply.github.com> Date: Mon, 3 Jan 2022 06:28:35 -0500 Subject: [PATCH 119/648] Add CacheRepository getter (#1946) --- src/PermissionRegistrar.php | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index 6d228440b..a878bcb34 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -5,6 +5,8 @@ use Illuminate\Cache\CacheManager; use Illuminate\Contracts\Auth\Access\Authorizable; use Illuminate\Contracts\Auth\Access\Gate; +use Illuminate\Contracts\Cache\Repository; +use Illuminate\Contracts\Cache\Store; use Illuminate\Database\Eloquent\Collection; use Spatie\Permission\Contracts\Permission; use Spatie\Permission\Contracts\Role; @@ -79,7 +81,7 @@ public function initializeCache() $this->cache = $this->getCacheStoreFromConfig(); } - protected function getCacheStoreFromConfig(): \Illuminate\Contracts\Cache\Repository + protected function getCacheStoreFromConfig(): Repository { // the 'default' fallback here is from the permission.php config file, // where 'default' means to use config(cache.default) @@ -265,12 +267,12 @@ public function setRoleClass($roleClass) return $this; } - /** - * Get the instance of the Cache Store. - * - * @return \Illuminate\Contracts\Cache\Store - */ - public function getCacheStore(): \Illuminate\Contracts\Cache\Store + public function getCacheRepository(): Repository + { + return $this->cache; + } + + public function getCacheStore(): Store { return $this->cache->getStore(); } From 6f4764c0cafe1cf508b7b9801a81b2caa858150a Mon Sep 17 00:00:00 2001 From: freekmurze Date: Mon, 3 Jan 2022 11:29:06 +0000 Subject: [PATCH 120/648] Fix styling --- src/Models/Role.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Models/Role.php b/src/Models/Role.php index b1da140b7..58ff8c8a7 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -148,7 +148,7 @@ protected static function findByParam(array $params = []) }); unset($params[PermissionRegistrar::$teamsKey]); } - + foreach ($params as $key => $value) { $query->where($key, $value); } From 45a4af9bf7754eeb692f07041091229c3ea92234 Mon Sep 17 00:00:00 2001 From: freek Date: Tue, 11 Jan 2022 16:03:06 +0100 Subject: [PATCH 121/648] wip --- .github/workflows/run-tests-L8.yml | 9 ++++++++- composer.json | 10 +++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/.github/workflows/run-tests-L8.yml b/.github/workflows/run-tests-L8.yml index 14b15a76d..5843a98c9 100644 --- a/.github/workflows/run-tests-L8.yml +++ b/.github/workflows/run-tests-L8.yml @@ -10,11 +10,18 @@ jobs: fail-fast: false matrix: php: [8.1, 8.0, 7.4, 7.3] - laravel: [8.*] + laravel: [9.*, 8.*] dependency-version: [prefer-lowest, prefer-stable] include: + - laravel: 9.* + testbench: 7.* - laravel: 8.* testbench: 6.23 + exclude: + - laravel: 9.* + php: 7.4 + - laravel: 9.* + php: 7.3 name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }} diff --git a/composer.json b/composer.json index 7382f27d7..c1a09b24d 100644 --- a/composer.json +++ b/composer.json @@ -23,13 +23,13 @@ ], "require": { "php" : "^7.3|^8.0|^8.1", - "illuminate/auth": "^7.0|^8.0", - "illuminate/container": "^7.0|^8.0", - "illuminate/contracts": "^7.0|^8.0", - "illuminate/database": "^7.0|^8.0" + "illuminate/auth": "^7.0|^8.0|^9.0", + "illuminate/container": "^7.0|^8.0|^9.0", + "illuminate/contracts": "^7.0|^8.0|^9.0", + "illuminate/database": "^7.0|^8.0|^9.0" }, "require-dev": { - "orchestra/testbench": "^5.0|^6.0", + "orchestra/testbench": "^5.0|^6.0|^7.0", "phpunit/phpunit": "^9.4", "predis/predis": "^1.1" }, From 6a3ed627cee28a552b5176c172ae0abc5eb30925 Mon Sep 17 00:00:00 2001 From: freek Date: Tue, 11 Jan 2022 16:06:21 +0100 Subject: [PATCH 122/648] wip --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 980ea5ecd..e164c37a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to `laravel-permission` will be documented in this file +## 5.5.0 - 2021-01-11 + +- add support for Laravel 9 + ## 5.4.0 - 2021-11-17 ## What's Changed From 54acbaea6876a13169244c1b122c1c8aa17fd656 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szab=C3=B3=20Gerg=C5=91?= Date: Tue, 1 Mar 2022 20:26:37 +0100 Subject: [PATCH 123/648] Spelling correction (#2024) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e164c37a3..7a8680965 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ All notable changes to `laravel-permission` will be documented in this file -## 5.5.0 - 2021-01-11 +## 5.5.0 - 2022-01-11 - add support for Laravel 9 From 20dd894eb64e5b02de9dfa28aa13af0643b2e456 Mon Sep 17 00:00:00 2001 From: Samson Adesanoye Date: Tue, 1 Mar 2022 20:26:55 +0100 Subject: [PATCH 124/648] update broken link to laravel exception (#2023) --- docs/advanced-usage/exceptions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/advanced-usage/exceptions.md b/docs/advanced-usage/exceptions.md index b71939a3d..6134ed59f 100644 --- a/docs/advanced-usage/exceptions.md +++ b/docs/advanced-usage/exceptions.md @@ -7,7 +7,7 @@ If you need to override exceptions thrown by this package, you can simply use no An example is shown below for your convenience, but nothing here is specific to this package other than the name of the exception. -You can find all the exceptions added by this package in the code here: https://github.com/spatie/laravel-permission/tree/main/src/Exceptions +You can find all the exceptions added by this package in the code here: [https://github.com/spatie/laravel-permission/tree/main/src/Exceptions](https://github.com/spatie/laravel-permission/tree/main/src/Exceptions) **app/Exceptions/Handler.php** From 6c46b4e7dc5445ebb36a44b00ee08d0838f580b0 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Thu, 3 Mar 2022 12:57:16 -0500 Subject: [PATCH 125/648] Fix Blade Directives incompatibility with renderers (#2039) * Fix Blade Directives incompatibility with renderers * Fix styling Co-authored-by: erikn69 --- src/PermissionServiceProvider.php | 119 +++++++++++++++--------------- 1 file changed, 59 insertions(+), 60 deletions(-) diff --git a/src/PermissionServiceProvider.php b/src/PermissionServiceProvider.php index db7bb6c9e..924d576a4 100644 --- a/src/PermissionServiceProvider.php +++ b/src/PermissionServiceProvider.php @@ -7,7 +7,6 @@ use Illuminate\Support\Arr; use Illuminate\Support\Collection; use Illuminate\Support\ServiceProvider; -use Illuminate\View\Compilers\BladeCompiler; use Spatie\Permission\Contracts\Permission as PermissionContract; use Spatie\Permission\Contracts\Role as RoleContract; @@ -84,65 +83,65 @@ protected function registerModelBindings() protected function registerBladeExtensions() { - $this->app->afterResolving('blade.compiler', function (BladeCompiler $bladeCompiler) { - $bladeCompiler->directive('role', function ($arguments) { - list($role, $guard) = explode(',', $arguments.','); - - return "check() && auth({$guard})->user()->hasRole({$role})): ?>"; - }); - $bladeCompiler->directive('elserole', function ($arguments) { - list($role, $guard) = explode(',', $arguments.','); - - return "check() && auth({$guard})->user()->hasRole({$role})): ?>"; - }); - $bladeCompiler->directive('endrole', function () { - return ''; - }); - - $bladeCompiler->directive('hasrole', function ($arguments) { - list($role, $guard) = explode(',', $arguments.','); - - return "check() && auth({$guard})->user()->hasRole({$role})): ?>"; - }); - $bladeCompiler->directive('endhasrole', function () { - return ''; - }); - - $bladeCompiler->directive('hasanyrole', function ($arguments) { - list($roles, $guard) = explode(',', $arguments.','); - - return "check() && auth({$guard})->user()->hasAnyRole({$roles})): ?>"; - }); - $bladeCompiler->directive('endhasanyrole', function () { - return ''; - }); - - $bladeCompiler->directive('hasallroles', function ($arguments) { - list($roles, $guard) = explode(',', $arguments.','); - - return "check() && auth({$guard})->user()->hasAllRoles({$roles})): ?>"; - }); - $bladeCompiler->directive('endhasallroles', function () { - return ''; - }); - - $bladeCompiler->directive('unlessrole', function ($arguments) { - list($role, $guard) = explode(',', $arguments.','); - - return "check() || ! auth({$guard})->user()->hasRole({$role})): ?>"; - }); - $bladeCompiler->directive('endunlessrole', function () { - return ''; - }); - - $bladeCompiler->directive('hasexactroles', function ($arguments) { - list($roles, $guard) = explode(',', $arguments.','); - - return "check() && auth({$guard})->user()->hasExactRoles({$roles})): ?>"; - }); - $bladeCompiler->directive('endhasexactroles', function () { - return ''; - }); + $bladeCompiler = $this->app['blade.compiler']; + + $bladeCompiler->directive('role', function ($arguments) { + list($role, $guard) = explode(',', $arguments.','); + + return "check() && auth({$guard})->user()->hasRole({$role})): ?>"; + }); + $bladeCompiler->directive('elserole', function ($arguments) { + list($role, $guard) = explode(',', $arguments.','); + + return "check() && auth({$guard})->user()->hasRole({$role})): ?>"; + }); + $bladeCompiler->directive('endrole', function () { + return ''; + }); + + $bladeCompiler->directive('hasrole', function ($arguments) { + list($role, $guard) = explode(',', $arguments.','); + + return "check() && auth({$guard})->user()->hasRole({$role})): ?>"; + }); + $bladeCompiler->directive('endhasrole', function () { + return ''; + }); + + $bladeCompiler->directive('hasanyrole', function ($arguments) { + list($roles, $guard) = explode(',', $arguments.','); + + return "check() && auth({$guard})->user()->hasAnyRole({$roles})): ?>"; + }); + $bladeCompiler->directive('endhasanyrole', function () { + return ''; + }); + + $bladeCompiler->directive('hasallroles', function ($arguments) { + list($roles, $guard) = explode(',', $arguments.','); + + return "check() && auth({$guard})->user()->hasAllRoles({$roles})): ?>"; + }); + $bladeCompiler->directive('endhasallroles', function () { + return ''; + }); + + $bladeCompiler->directive('unlessrole', function ($arguments) { + list($role, $guard) = explode(',', $arguments.','); + + return "check() || ! auth({$guard})->user()->hasRole({$role})): ?>"; + }); + $bladeCompiler->directive('endunlessrole', function () { + return ''; + }); + + $bladeCompiler->directive('hasexactroles', function ($arguments) { + list($roles, $guard) = explode(',', $arguments.','); + + return "check() && auth({$guard})->user()->hasExactRoles({$roles})): ?>"; + }); + $bladeCompiler->directive('endhasexactroles', function () { + return ''; }); } From 677903c69460a1867da833979a0dfdffd163767d Mon Sep 17 00:00:00 2001 From: freekmurze Date: Thu, 3 Mar 2022 17:58:20 +0000 Subject: [PATCH 126/648] Update CHANGELOG --- CHANGELOG.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a8680965..0093dbf08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,21 @@ All notable changes to `laravel-permission` will be documented in this file +## 5.5.1 - 2022-03-03 + +## What's Changed + +- Spelling correction by @gergo85 in https://github.com/spatie/laravel-permission/pull/2024 +- update broken link to laravel exception by @kingzamzon in https://github.com/spatie/laravel-permission/pull/2023 +- Fix Blade Directives incompatibility with renderers by @erikn69 in https://github.com/spatie/laravel-permission/pull/2039 + +## New Contributors + +- @gergo85 made their first contribution in https://github.com/spatie/laravel-permission/pull/2024 +- @kingzamzon made their first contribution in https://github.com/spatie/laravel-permission/pull/2023 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/5.5.0...5.5.1 + ## 5.5.0 - 2022-01-11 - add support for Laravel 9 @@ -302,6 +317,7 @@ The following changes are not "breaking", but worth making the updates to your a + $this->app->make(\Spatie\Permission\PermissionRegistrar::class)->forgetCachedPermissions(); + ``` 1. Also this is a good time to point out that now with v2.25.0 and v2.26.0 most permission-cache-reset scenarios may no longer be needed in your app, so it's worth reviewing those cases, as you may gain some app speed improvement by removing unnecessary cache resets. @@ -351,6 +367,7 @@ The following changes are not "breaking", but worth making the updates to your a @endrole + ``` ## 2.19.1 - 2018-09-14 From eb838cd2c539d1c97f8c997e67479cb68c2d0e36 Mon Sep 17 00:00:00 2001 From: Cristian Tabacitu Date: Wed, 9 Mar 2022 12:21:20 +0200 Subject: [PATCH 127/648] [Fixes BIG bug] register blade directives after resolving blade compiler (#2048) * register blade directives after resolving blade compiler Fixes #2038, #2044, #2045 * use callAfterResolving instead of afterResolving --- src/PermissionServiceProvider.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/PermissionServiceProvider.php b/src/PermissionServiceProvider.php index 924d576a4..61fc1321b 100644 --- a/src/PermissionServiceProvider.php +++ b/src/PermissionServiceProvider.php @@ -7,6 +7,7 @@ use Illuminate\Support\Arr; use Illuminate\Support\Collection; use Illuminate\Support\ServiceProvider; +use Illuminate\View\Compilers\BladeCompiler; use Spatie\Permission\Contracts\Permission as PermissionContract; use Spatie\Permission\Contracts\Role as RoleContract; @@ -39,7 +40,9 @@ public function register() 'permission' ); - $this->registerBladeExtensions(); + $this->callAfterResolving('blade.compiler', function (BladeCompiler $bladeCompiler) { + $this->registerBladeExtensions($bladeCompiler); + }); } protected function offerPublishing() @@ -81,10 +84,8 @@ protected function registerModelBindings() $this->app->bind(RoleContract::class, $config['role']); } - protected function registerBladeExtensions() + protected function registerBladeExtensions($bladeCompiler) { - $bladeCompiler = $this->app['blade.compiler']; - $bladeCompiler->directive('role', function ($arguments) { list($role, $guard) = explode(',', $arguments.','); From 377e2308d2099f65207ea12e3b587d761b69c41f Mon Sep 17 00:00:00 2001 From: freekmurze Date: Wed, 9 Mar 2022 10:22:08 +0000 Subject: [PATCH 128/648] Update CHANGELOG --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0093dbf08..727ace7b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ All notable changes to `laravel-permission` will be documented in this file +## 5.5.2 - 2022-03-09 + +## What's Changed + +- [Fixes BIG bug] register blade directives after resolving blade compiler by @tabacitu in https://github.com/spatie/laravel-permission/pull/2048 + +## New Contributors + +- @tabacitu made their first contribution in https://github.com/spatie/laravel-permission/pull/2048 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/5.5.1...5.5.2 + ## 5.5.1 - 2022-03-03 ## What's Changed @@ -318,6 +330,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` 1. Also this is a good time to point out that now with v2.25.0 and v2.26.0 most permission-cache-reset scenarios may no longer be needed in your app, so it's worth reviewing those cases, as you may gain some app speed improvement by removing unnecessary cache resets. @@ -368,6 +381,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` ## 2.19.1 - 2018-09-14 From b731394245a0b88ed255b62bcad34b1fc163b625 Mon Sep 17 00:00:00 2001 From: Adriaan Marain Date: Wed, 9 Mar 2022 17:20:14 +0100 Subject: [PATCH 129/648] Add banner --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 141fb33de..b65b58048 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ + +[](https://supportukrainenow.org) +

Social Card of Laravel Permission

# Associate users with permissions and roles From 500afd4ce9770934e0c2e4a8fdfab4a18a56b41e Mon Sep 17 00:00:00 2001 From: Freek Van der Herten Date: Fri, 18 Mar 2022 16:40:42 +0100 Subject: [PATCH 130/648] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b65b58048..c03f87ec9 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ Please see [CONTRIBUTING](CONTRIBUTING.md) for details. ### Security -If you discover any security-related issues, please email [freek@spatie.be](mailto:freek@spatie.be) instead of using the issue tracker. +If you discover any security-related issues, please email [security@spatie.be](mailto:security@spatie.be) instead of using the issue tracker. ## Postcardware From 50c363888fb90c0404e945eab5db262ffcd0ff00 Mon Sep 17 00:00:00 2001 From: Adriaan Marain Date: Mon, 21 Mar 2022 13:33:29 +0100 Subject: [PATCH 131/648] Change copy --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c03f87ec9..d090b8b24 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recen ## Contributing -Please see [CONTRIBUTING](CONTRIBUTING.md) for details. +Please see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details. ### Security From 16e37de3cdf963677b50cd9ceec18968f3e6bd21 Mon Sep 17 00:00:00 2001 From: Adriaan Marain Date: Mon, 21 Mar 2022 13:51:34 +0100 Subject: [PATCH 132/648] Use organisation-wide community health files --- .github/SECURITY.md | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .github/SECURITY.md diff --git a/.github/SECURITY.md b/.github/SECURITY.md deleted file mode 100644 index ca9134343..000000000 --- a/.github/SECURITY.md +++ /dev/null @@ -1,3 +0,0 @@ -# Security Policy - -If you discover any security related issues, please email freek@spatie.be instead of using the issue tracker. From 15cb00fab6280c0e08955f9ec3f4c736a36f0173 Mon Sep 17 00:00:00 2001 From: Adriaan Marain Date: Mon, 21 Mar 2022 13:54:43 +0100 Subject: [PATCH 133/648] Use organisation-wide community health files --- CONTRIBUTING.md | 32 -------------------------------- 1 file changed, 32 deletions(-) delete mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 2ceb084d4..000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,32 +0,0 @@ -# Contributing - -Contributions are **welcome** and will be fully **credited**. - -We accept contributions via Pull Requests on [Github](https://github.com/spatie/laravel-permission). - - -## Pull Requests - -- **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](http://pear.php.net/package/PHP_CodeSniffer). - -- **Add tests!** - Your patch won't be accepted if it doesn't have tests. - -- **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date. - -- **Consider our release cycle** - We try to follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not an option. - -- **Create feature branches** - Don't ask us to pull from your master branch. - -- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. - -- **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](http://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting. - - -## Running Tests - -``` bash -$ phpunit -``` - - -**Happy coding**! From aec8d121ccb9cf2b96bb2792488f90648f9d4d65 Mon Sep 17 00:00:00 2001 From: angeljqv <79208641+angeljqv@users.noreply.github.com> Date: Thu, 5 May 2022 16:18:15 -0500 Subject: [PATCH 134/648] Update .gitattributes (#2065) --- .gitattributes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitattributes b/.gitattributes index 5a0aa162e..993e0d23c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -14,3 +14,5 @@ /.editorconfig export-ignore /.php_cs.dist.php export-ignore /.styleci.yml export-ignore +/CHANGELOG.md export-ignore +/CONTRIBUTING.md export-ignore From 77baa1b9024a0ef4acf5a0101b4ce6e5cda9dde9 Mon Sep 17 00:00:00 2001 From: Morgan Arnel <84181964+morganarnel@users.noreply.github.com> Date: Thu, 5 May 2022 22:19:11 +0100 Subject: [PATCH 135/648] Update add_teams_fields.php.stub (#2067) Double semicolon in teams_field migration ;) --- database/migrations/add_teams_fields.php.stub | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/migrations/add_teams_fields.php.stub b/database/migrations/add_teams_fields.php.stub index 6df2378bf..6abdf8d8f 100644 --- a/database/migrations/add_teams_fields.php.stub +++ b/database/migrations/add_teams_fields.php.stub @@ -41,7 +41,7 @@ class AddTeamsFields extends Migration if (! Schema::hasColumn($tableNames['model_has_permissions'], $columnNames['team_foreign_key'])) { Schema::table($tableNames['model_has_permissions'], function (Blueprint $table) use ($tableNames, $columnNames) { - $table->unsignedBigInteger($columnNames['team_foreign_key'])->default('1');; + $table->unsignedBigInteger($columnNames['team_foreign_key'])->default('1'); $table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index'); if (DB::getDriverName() !== 'sqlite') { From 96466b07c9a416fcc095a9097e87131e082e60af Mon Sep 17 00:00:00 2001 From: erikn69 Date: Thu, 5 May 2022 16:43:01 -0500 Subject: [PATCH 136/648] Allow revokePermissionTo to accept Permission[] (#2014) Co-authored-by: Erik Niebla --- src/Traits/HasPermissions.php | 6 +++++- tests/HasPermissionsTest.php | 28 ++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index ecf07414f..d116e8ac6 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -384,7 +384,7 @@ public function syncPermissions(...$permissions) } /** - * Revoke the given permission. + * Revoke the given permission(s). * * @param \Spatie\Permission\Contracts\Permission|\Spatie\Permission\Contracts\Permission[]|string|string[] $permission * @@ -426,6 +426,10 @@ protected function getStoredPermission($permissions) } if (is_array($permissions)) { + $permissions = array_map(function ($permission) use ($permissionClass) { + return is_a($permission, get_class($permissionClass)) ? $permission->name : $permission; + }, $permissions); + return $permissionClass ->whereIn('name', $permissions) ->whereIn('guard_name', $this->getGuardNames()) diff --git a/tests/HasPermissionsTest.php b/tests/HasPermissionsTest.php index df793e451..40151b67e 100644 --- a/tests/HasPermissionsTest.php +++ b/tests/HasPermissionsTest.php @@ -240,6 +240,34 @@ public function it_can_give_and_revoke_multiple_permissions() $this->assertEquals(0, $this->testUserRole->permissions()->count()); } + /** @test */ + public function it_can_give_and_revoke_permissions_models_array() + { + $models = [app(Permission::class)::where('name', 'edit-articles')->first(), app(Permission::class)::where('name', 'edit-news')->first()]; + + $this->testUserRole->givePermissionTo($models); + + $this->assertEquals(2, $this->testUserRole->permissions()->count()); + + $this->testUserRole->revokePermissionTo($models); + + $this->assertEquals(0, $this->testUserRole->permissions()->count()); + } + + /** @test */ + public function it_can_give_and_revoke_permissions_models_collection() + { + $models = app(Permission::class)::whereIn('name', ['edit-articles', 'edit-news'])->get(); + + $this->testUserRole->givePermissionTo($models); + + $this->assertEquals(2, $this->testUserRole->permissions()->count()); + + $this->testUserRole->revokePermissionTo($models); + + $this->assertEquals(0, $this->testUserRole->permissions()->count()); + } + /** @test */ public function it_can_determine_that_the_user_does_not_have_a_permission() { From 67c4df75d5692cfa2c91b317e57df9e670625d25 Mon Sep 17 00:00:00 2001 From: Faqih Muntashir <48067039+itsfaqih@users.noreply.github.com> Date: Fri, 6 May 2022 04:45:18 +0700 Subject: [PATCH 137/648] Improve typing in role's findById and findOrCreate method (#2022) --- src/Models/Role.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Models/Role.php b/src/Models/Role.php index 58ff8c8a7..76bec3095 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -103,6 +103,14 @@ public static function findByName(string $name, $guardName = null): RoleContract return $role; } + /** + * Find a role by its id (and optionally guardName). + * + * @param int $id + * @param string|null $guardName + * + * @return \Spatie\Permission\Contracts\Role|\Spatie\Permission\Models\Role + */ public static function findById(int $id, $guardName = null): RoleContract { $guardName = $guardName ?? Guard::getDefaultName(static::class); @@ -122,7 +130,7 @@ public static function findById(int $id, $guardName = null): RoleContract * @param string $name * @param string|null $guardName * - * @return \Spatie\Permission\Contracts\Role + * @return \Spatie\Permission\Contracts\Role|\Spatie\Permission\Models\Role */ public static function findOrCreate(string $name, $guardName = null): RoleContract { From 4e3ad0aed6e3a8a4b5df9424aa0a721902bc7841 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Thu, 5 May 2022 16:50:47 -0500 Subject: [PATCH 138/648] [V5] Cache loader improvements (#1912) * Cache loader improvements * Cache even smaller Co-authored-by: Erik Niebla --- src/PermissionRegistrar.php | 115 ++++++++++++++++++++++++++---------- 1 file changed, 83 insertions(+), 32 deletions(-) diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index a878bcb34..ce10bafa9 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -170,32 +170,22 @@ private function loadPermissions() } $this->permissions = $this->cache->remember(self::$cacheKey, self::$cacheExpirationTime, function () { - // make the cache smaller using an array with only required fields - return $this->getPermissionClass()->select('id', 'id as i', 'name as n', 'guard_name as g') - ->with('roles:id,id as i,name as n,guard_name as g')->get() - ->map(function ($permission) { - return $permission->only('i', 'n', 'g') + - ['r' => $permission->roles->map->only('i', 'n', 'g')->all()]; - })->all(); + return $this->getSerializedPermissionsForCache(); }); - if (is_array($this->permissions)) { - $this->permissions = $this->getPermissionClass()::hydrate( - collect($this->permissions)->map(function ($item) { - return ['id' => $item['i'] ?? $item['id'], 'name' => $item['n'] ?? $item['name'], 'guard_name' => $item['g'] ?? $item['guard_name']]; - })->all() - ) - ->each(function ($permission, $i) { - $roles = Collection::make($this->permissions[$i]['r'] ?? $this->permissions[$i]['roles'] ?? []) - ->map(function ($item) { - return $this->getHydratedRole($item); - }); - - $permission->setRelation('roles', $roles); - }); - - $this->cachedRoles = []; + // fallback for old cache method, must be removed on next mayor version + if (! isset($this->permissions['permissions'])) { + $this->forgetCachedPermissions(); + $this->loadPermissions(); + + return; } + + $this->hydrateRolesCache(); + + $this->permissions = $this->getHydratedPermissionCollection(); + + $this->cachedRoles = []; } /** @@ -277,21 +267,82 @@ public function getCacheStore(): Store return $this->cache->getStore(); } - private function getHydratedRole(array $item) + /* + * Make the cache smaller using an array with only required fields + */ + private function getSerializedPermissionsForCache() { - $roleId = $item['i'] ?? $item['id']; + $roleClass = $this->getRoleClass(); + $roleKey = (new $roleClass())->getKeyName(); + + $permissionClass = $this->getPermissionClass(); + $permissionKey = (new $permissionClass())->getKeyName(); + + $permissions = $permissionClass + ->select($permissionKey, "$permissionKey as i", 'name as n', 'guard_name as g') + ->with("roles:$roleKey,$roleKey as i,name as n,guard_name as g")->get() + ->map(function ($permission) { + return $permission->only('i', 'n', 'g') + $this->getSerializedRoleRelation($permission); + })->all(); + $roles = array_values($this->cachedRoles); + $this->cachedRoles = []; - if (isset($this->cachedRoles[$roleId])) { - return $this->cachedRoles[$roleId]; + return compact('permissions', 'roles'); + } + + private function getSerializedRoleRelation($permission) + { + if (! $permission->roles->count()) { + return []; } + return [ + 'r' => $permission->roles->map(function ($role) { + if (! isset($this->cachedRoles[$role->i])) { + $this->cachedRoles[$role->i] = $role->only('i', 'n', 'g'); + } + + return $role->i; + })->all(), + ]; + } + + private function getHydratedPermissionCollection() + { + $permissionClass = $this->getPermissionClass(); + $permissionInstance = new $permissionClass(); + + return Collection::make( + array_map(function ($item) use ($permissionInstance) { + return $permissionInstance + ->newFromBuilder([ + $permissionInstance->getKeyName() => $item['i'], + 'name' => $item['n'], + 'guard_name' => $item['g'], + ]) + ->setRelation('roles', $this->getHydratedRoleCollection($item['r'] ?? [])); + }, $this->permissions['permissions']) + ); + } + + private function getHydratedRoleCollection(array $roles) + { + return Collection::make(array_values( + array_intersect_key($this->cachedRoles, array_flip($roles)) + )); + } + + private function hydrateRolesCache() + { $roleClass = $this->getRoleClass(); $roleInstance = new $roleClass(); - return $this->cachedRoles[$roleId] = $roleInstance->newFromBuilder([ - 'id' => $roleId, - 'name' => $item['n'] ?? $item['name'], - 'guard_name' => $item['g'] ?? $item['guard_name'], - ]); + array_map(function ($item) use ($roleInstance) { + $this->cachedRoles[$item['i']] = $roleInstance->newFromBuilder([ + $roleInstance->getKeyName() => $item['i'], + 'name' => $item['n'], + 'guard_name' => $item['g'], + ]); + }, $this->permissions['roles']); } } From b71df07f3b5db4886657b4c17244d66ba6796c1a Mon Sep 17 00:00:00 2001 From: freekmurze Date: Thu, 5 May 2022 21:55:54 +0000 Subject: [PATCH 139/648] Update CHANGELOG --- CHANGELOG.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 727ace7b1..dcb503e1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,23 @@ All notable changes to `laravel-permission` will be documented in this file +## 5.5.3 - 2022-05-05 + +## What's Changed + +- Update .gitattributes by @angeljqv in https://github.com/spatie/laravel-permission/pull/2065 +- Remove double semicolon from add_teams_fields.php.stub by @morganarnel in https://github.com/spatie/laravel-permission/pull/2067 +- [V5] Allow revokePermissionTo to accept Permission[] by @erikn69 in https://github.com/spatie/laravel-permission/pull/2014 +- [V5] Improve typing in role's findById and findOrCreate method by @itsfaqih in https://github.com/spatie/laravel-permission/pull/2022 +- [V5] Cache loader improvements by @erikn69 in https://github.com/spatie/laravel-permission/pull/1912 + +## New Contributors + +- @morganarnel made their first contribution in https://github.com/spatie/laravel-permission/pull/2067 +- @itsfaqih made their first contribution in https://github.com/spatie/laravel-permission/pull/2022 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/5.5.2...5.5.3 + ## 5.5.2 - 2022-03-09 ## What's Changed @@ -331,6 +348,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` 1. Also this is a good time to point out that now with v2.25.0 and v2.26.0 most permission-cache-reset scenarios may no longer be needed in your app, so it's worth reviewing those cases, as you may gain some app speed improvement by removing unnecessary cache resets. @@ -382,6 +400,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` ## 2.19.1 - 2018-09-14 From 8236acb68d5596d857b81ced2a3ee5fcf773650a Mon Sep 17 00:00:00 2001 From: erikn69 Date: Mon, 16 May 2022 07:07:35 -0500 Subject: [PATCH 140/648] Support custom primary keys on models (#2092) --- src/Models/Permission.php | 2 +- src/Models/Role.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Models/Permission.php b/src/Models/Permission.php index 2b253d6f6..1834d72e2 100644 --- a/src/Models/Permission.php +++ b/src/Models/Permission.php @@ -108,7 +108,7 @@ public static function findByName(string $name, $guardName = null): PermissionCo public static function findById(int $id, $guardName = null): PermissionContract { $guardName = $guardName ?? Guard::getDefaultName(static::class); - $permission = static::getPermission(['id' => $id, 'guard_name' => $guardName]); + $permission = static::getPermission([(new static())->getKeyName() => $id, 'guard_name' => $guardName]); if (! $permission) { throw PermissionDoesNotExist::withId($id, $guardName); diff --git a/src/Models/Role.php b/src/Models/Role.php index 76bec3095..da6f269e0 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -115,7 +115,7 @@ public static function findById(int $id, $guardName = null): RoleContract { $guardName = $guardName ?? Guard::getDefaultName(static::class); - $role = static::findByParam(['id' => $id, 'guard_name' => $guardName]); + $role = static::findByParam([(new static())->getKeyName() => $id, 'guard_name' => $guardName]); if (! $role) { throw RoleDoesNotExist::withId($id); From 55f96fbd47f72d55aa92440fb65f5eafa83f7fcd Mon Sep 17 00:00:00 2001 From: Abhishek Paul Date: Mon, 16 May 2022 17:37:56 +0530 Subject: [PATCH 141/648] Fix UuidTrait (#2094) Boot method having protected it should be public and no need to call parent boot method --- docs/advanced-usage/uuid.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/advanced-usage/uuid.md b/docs/advanced-usage/uuid.md index bebbeef73..299f83c64 100644 --- a/docs/advanced-usage/uuid.md +++ b/docs/advanced-usage/uuid.md @@ -96,10 +96,8 @@ It is common to use a trait to handle the $keyType and $incrementing settings, a trait UuidTrait { - protected static function bootUuidTrait() + public static function bootUuidTrait() { - parent::boot(); - static::creating(function ($model) { $model->keyType = 'string'; $model->incrementing = false; From cb86fd87b43fcfc493c3f2b1de6fad100c078146 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Mon, 16 May 2022 07:09:59 -0500 Subject: [PATCH 142/648] Support custom fields on cache (#2091) --- src/PermissionRegistrar.php | 84 ++++++++++++++------ tests/HasPermissionsWithCustomModelsTest.php | 24 ++++++ 2 files changed, 83 insertions(+), 25 deletions(-) diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index ce10bafa9..38beec605 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -52,6 +52,12 @@ class PermissionRegistrar /** @var array */ private $cachedRoles = []; + /** @var array */ + private $alias = []; + + /** @var array */ + private $except = []; + /** * PermissionRegistrar constructor. * @@ -174,18 +180,20 @@ private function loadPermissions() }); // fallback for old cache method, must be removed on next mayor version - if (! isset($this->permissions['permissions'])) { + if (! isset($this->permissions['alias'])) { $this->forgetCachedPermissions(); $this->loadPermissions(); return; } + $this->alias = $this->permissions['alias']; + $this->hydrateRolesCache(); $this->permissions = $this->getHydratedPermissionCollection(); - $this->cachedRoles = []; + $this->cachedRoles = $this->alias = $this->except = []; } /** @@ -267,27 +275,55 @@ public function getCacheStore(): Store return $this->cache->getStore(); } + /** + * Changes array keys with alias + * + * @return array + */ + private function aliasedArray($model): array + { + return collect(is_array($model) ? $model : $model->getAttributes())->except($this->except) + ->keyBy(function ($value, $key) { + return $this->alias[$key] ?? $key; + })->all(); + } + + /** + * Array for cache alias + */ + private function aliasModelFields($newKeys = []): void + { + $i = 0; + $alphas = ! count($this->alias) ? range('a', 'h') : range('j', 'p'); + + foreach (array_keys($newKeys->getAttributes()) as $value) { + if (! isset($this->alias[$value])) { + $this->alias[$value] = $alphas[$i++] ?? $value; + } + } + + $this->alias = array_diff_key($this->alias, array_flip($this->except)); + } + /* * Make the cache smaller using an array with only required fields */ private function getSerializedPermissionsForCache() { - $roleClass = $this->getRoleClass(); - $roleKey = (new $roleClass())->getKeyName(); - - $permissionClass = $this->getPermissionClass(); - $permissionKey = (new $permissionClass())->getKeyName(); + $this->except = config('permission.cache.column_names_except', ['created_at','updated_at', 'deleted_at']); - $permissions = $permissionClass - ->select($permissionKey, "$permissionKey as i", 'name as n', 'guard_name as g') - ->with("roles:$roleKey,$roleKey as i,name as n,guard_name as g")->get() + $permissions = $this->getPermissionClass()->select()->with('roles')->get() ->map(function ($permission) { - return $permission->only('i', 'n', 'g') + $this->getSerializedRoleRelation($permission); + if (! $this->alias) { + $this->aliasModelFields($permission); + } + + return $this->aliasedArray($permission) + $this->getSerializedRoleRelation($permission); })->all(); $roles = array_values($this->cachedRoles); $this->cachedRoles = []; - return compact('permissions', 'roles'); + return ['alias' => array_flip($this->alias)] + compact('permissions', 'roles'); } private function getSerializedRoleRelation($permission) @@ -296,13 +332,18 @@ private function getSerializedRoleRelation($permission) return []; } + if (! isset($this->alias['roles'])) { + $this->alias['roles'] = 'r'; + $this->aliasModelFields($permission->roles[0]); + } + return [ 'r' => $permission->roles->map(function ($role) { - if (! isset($this->cachedRoles[$role->i])) { - $this->cachedRoles[$role->i] = $role->only('i', 'n', 'g'); + if (! isset($this->cachedRoles[$role->getKey()])) { + $this->cachedRoles[$role->getKey()] = $this->aliasedArray($role); } - return $role->i; + return $role->getKey(); })->all(), ]; } @@ -315,11 +356,7 @@ private function getHydratedPermissionCollection() return Collection::make( array_map(function ($item) use ($permissionInstance) { return $permissionInstance - ->newFromBuilder([ - $permissionInstance->getKeyName() => $item['i'], - 'name' => $item['n'], - 'guard_name' => $item['g'], - ]) + ->newFromBuilder($this->aliasedArray(array_diff_key($item, ['r' => 0]))) ->setRelation('roles', $this->getHydratedRoleCollection($item['r'] ?? [])); }, $this->permissions['permissions']) ); @@ -338,11 +375,8 @@ private function hydrateRolesCache() $roleInstance = new $roleClass(); array_map(function ($item) use ($roleInstance) { - $this->cachedRoles[$item['i']] = $roleInstance->newFromBuilder([ - $roleInstance->getKeyName() => $item['i'], - 'name' => $item['n'], - 'guard_name' => $item['g'], - ]); + $role = $roleInstance->newFromBuilder($this->aliasedArray($item)); + $this->cachedRoles[$role->getKey()] = $role; }, $this->permissions['roles']); } } diff --git a/tests/HasPermissionsWithCustomModelsTest.php b/tests/HasPermissionsWithCustomModelsTest.php index bdb237a5c..6b9fa34ca 100644 --- a/tests/HasPermissionsWithCustomModelsTest.php +++ b/tests/HasPermissionsWithCustomModelsTest.php @@ -2,6 +2,9 @@ namespace Spatie\Permission\Test; +use DB; +use Spatie\Permission\PermissionRegistrar; + class HasPermissionsWithCustomModelsTest extends HasPermissionsTest { /** @var bool */ @@ -12,4 +15,25 @@ public function it_can_use_custom_model_permission() { $this->assertSame(get_class($this->testUserPermission), Permission::class); } + + /** @test */ + public function it_can_use_custom_fields_from_cache() + { + DB::connection()->getSchemaBuilder()->table(config('permission.table_names.roles'), function ($table) { + $table->string('type')->default('R'); + }); + DB::connection()->getSchemaBuilder()->table(config('permission.table_names.permissions'), function ($table) { + $table->string('type')->default('P'); + }); + + $this->testUserRole->givePermissionTo($this->testUserPermission); + app(PermissionRegistrar::class)->getPermissions(); + + DB::enableQueryLog(); + $this->assertSame('P', Permission::findByName('edit-articles')->type); + $this->assertSame('R', Permission::findByName('edit-articles')->roles[0]->type); + DB::disableQueryLog(); + + $this->assertSame(0, count(DB::getQueryLog())); + } } From 8a69aaddbf91810b0175b05a1acb9b3410bff68e Mon Sep 17 00:00:00 2001 From: freekmurze Date: Mon, 16 May 2022 12:11:28 +0000 Subject: [PATCH 143/648] Update CHANGELOG --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dcb503e1f..755ea17f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,20 @@ All notable changes to `laravel-permission` will be documented in this file +## 5.5.4 - 2022-05-16 + +## What's Changed + +- Support custom primary key names on models by @erikn69 in https://github.com/spatie/laravel-permission/pull/2092 +- Fix UuidTrait on uuid doc page by @abhishekpaul in https://github.com/spatie/laravel-permission/pull/2094 +- Support custom fields on cache by @erikn69 in https://github.com/spatie/laravel-permission/pull/2091 + +## New Contributors + +- @abhishekpaul made their first contribution in https://github.com/spatie/laravel-permission/pull/2094 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/5.5.3...5.5.4 + ## 5.5.3 - 2022-05-05 ## What's Changed @@ -349,6 +363,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` 1. Also this is a good time to point out that now with v2.25.0 and v2.26.0 most permission-cache-reset scenarios may no longer be needed in your app, so it's worth reviewing those cases, as you may gain some app speed improvement by removing unnecessary cache resets. @@ -401,6 +416,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` ## 2.19.1 - 2018-09-14 From 411eecaef445aa37ae40d58468671ff8e9c45b56 Mon Sep 17 00:00:00 2001 From: Sergei Zhidkov Date: Tue, 24 May 2022 08:41:19 +0400 Subject: [PATCH 144/648] config() in Role and Permission models is deleted --- src/Models/Permission.php | 2 -- src/Models/Role.php | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/Models/Permission.php b/src/Models/Permission.php index 1834d72e2..9ef30ed17 100644 --- a/src/Models/Permission.php +++ b/src/Models/Permission.php @@ -22,8 +22,6 @@ class Permission extends Model implements PermissionContract public function __construct(array $attributes = []) { - $attributes['guard_name'] = $attributes['guard_name'] ?? config('auth.defaults.guard'); - parent::__construct($attributes); $this->guarded[] = $this->primaryKey; diff --git a/src/Models/Role.php b/src/Models/Role.php index da6f269e0..99d6479af 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -22,8 +22,6 @@ class Role extends Model implements RoleContract public function __construct(array $attributes = []) { - $attributes['guard_name'] = $attributes['guard_name'] ?? config('auth.defaults.guard'); - parent::__construct($attributes); $this->guarded[] = $this->primaryKey; From fc841f86190f558658c072c2f2107fdf67308c8f Mon Sep 17 00:00:00 2001 From: erikn69 Date: Mon, 30 May 2022 09:48:59 -0500 Subject: [PATCH 145/648] Custom primary keys tests (#2096) --- .gitignore | 2 +- .../create_permission_tables.php.stub | 12 ++--- tests/Permission.php | 4 +- tests/Role.php | 4 +- tests/TestCase.php | 44 +++++++++++++++++-- 5 files changed, 54 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index ba274b9c4..da5ec4b46 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,4 @@ tests/temp .idea .phpunit.result.cache .php-cs-fixer.cache - +tests/CreatePermissionCustomTables.php diff --git a/database/migrations/create_permission_tables.php.stub b/database/migrations/create_permission_tables.php.stub index f20ef752b..04c3278b9 100644 --- a/database/migrations/create_permission_tables.php.stub +++ b/database/migrations/create_permission_tables.php.stub @@ -26,7 +26,7 @@ class CreatePermissionTables extends Migration } Schema::create($tableNames['permissions'], function (Blueprint $table) { - $table->bigIncrements('id'); + $table->bigIncrements('id'); // permission id $table->string('name'); // For MySQL 8.0 use string('name', 125); $table->string('guard_name'); // For MySQL 8.0 use string('guard_name', 125); $table->timestamps(); @@ -35,7 +35,7 @@ class CreatePermissionTables extends Migration }); Schema::create($tableNames['roles'], function (Blueprint $table) use ($teams, $columnNames) { - $table->bigIncrements('id'); + $table->bigIncrements('id'); // role id if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing $table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable(); $table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index'); @@ -58,7 +58,7 @@ class CreatePermissionTables extends Migration $table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index'); $table->foreign(PermissionRegistrar::$pivotPermission) - ->references('id') + ->references('id') // permission id ->on($tableNames['permissions']) ->onDelete('cascade'); if ($teams) { @@ -82,7 +82,7 @@ class CreatePermissionTables extends Migration $table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index'); $table->foreign(PermissionRegistrar::$pivotRole) - ->references('id') + ->references('id') // role id ->on($tableNames['roles']) ->onDelete('cascade'); if ($teams) { @@ -102,12 +102,12 @@ class CreatePermissionTables extends Migration $table->unsignedBigInteger(PermissionRegistrar::$pivotRole); $table->foreign(PermissionRegistrar::$pivotPermission) - ->references('id') + ->references('id') // permission id ->on($tableNames['permissions']) ->onDelete('cascade'); $table->foreign(PermissionRegistrar::$pivotRole) - ->references('id') + ->references('id') // role id ->on($tableNames['roles']) ->onDelete('cascade'); diff --git a/tests/Permission.php b/tests/Permission.php index a621c53fc..d7529ed69 100644 --- a/tests/Permission.php +++ b/tests/Permission.php @@ -4,8 +4,10 @@ class Permission extends \Spatie\Permission\Models\Permission { + protected $primaryKey = 'permission_test_id'; + protected $visible = [ - 'id', + 'permission_test_id', 'name', ]; } diff --git a/tests/Role.php b/tests/Role.php index 1977b00c2..bbe13ece7 100644 --- a/tests/Role.php +++ b/tests/Role.php @@ -4,8 +4,10 @@ class Role extends \Spatie\Permission\Models\Role { + protected $primaryKey = 'role_test_id'; + protected $visible = [ - 'id', + 'role_test_id', 'name', ]; } diff --git a/tests/TestCase.php b/tests/TestCase.php index 80a0c3c0b..8f913c53e 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -39,10 +39,17 @@ abstract class TestCase extends Orchestra /** @var bool */ protected $hasTeams = false; + protected static $migration; + protected static $customMigration; + public function setUp(): void { parent::setUp(); + if (! self::$migration) { + $this->prepareMigration(); + } + // Note: this also flushes the cache from within the migration $this->setUpDatabase($this->app); if ($this->hasTeams) { @@ -125,9 +132,11 @@ protected function setUpDatabase($app) $this->createCacheTable(); } - include_once __DIR__.'/../database/migrations/create_permission_tables.php.stub'; - - (new \CreatePermissionTables())->up(); + if (! $this->useCustomModels) { + self::$migration->up(); + } else { + self::$customMigration->up(); + } $this->testUser = User::create(['email' => 'test@user.com']); $this->testAdmin = Admin::create(['email' => 'admin@user.com']); @@ -141,6 +150,35 @@ protected function setUpDatabase($app) $app[Permission::class]->create(['name' => 'Edit News']); } + private function prepareMigration() + { + $migration = str_replace( + [ + 'CreatePermissionTables', + '(\'id\'); // permission id', + '(\'id\'); // role id', + 'references(\'id\') // permission id', + 'references(\'id\') // role id', + ], + [ + 'CreatePermissionCustomTables', + '(\'permission_test_id\');', + '(\'role_test_id\');', + 'references(\'permission_test_id\')', + 'references(\'role_test_id\')', + ], + file_get_contents(__DIR__.'/../database/migrations/create_permission_tables.php.stub') + ); + + file_put_contents(__DIR__.'/CreatePermissionCustomTables.php', $migration); + + include_once __DIR__.'/../database/migrations/create_permission_tables.php.stub'; + self::$migration = new \CreatePermissionTables(); + + include_once __DIR__.'/CreatePermissionCustomTables.php'; + self::$customMigration = new \CreatePermissionCustomTables(); + } + protected function reloadPermissions() { app(PermissionRegistrar::class)->forgetCachedPermissions(); From f98e34ad47bfbc66ec8e3dd7f62e9f837e5d3daa Mon Sep 17 00:00:00 2001 From: Ayesh Karunaratne Date: Thu, 30 Jun 2022 04:39:24 +0530 Subject: [PATCH 146/648] [PHP 8.2] Fix `${var}` string interpolation deprecation (#2117) PHP 8.2 deprecates `"${var}"` string interpolation pattern. This fixes the only such occurrence in `spatie/laravel-permission` package. - [PHP 8.2: `${var}` string interpolation deprecated](https://php.watch/versions/8.2/${var}-string-interpolation-deprecated) - [RFC](https://wiki.php.net/rfc/deprecate_dollar_brace_string_interpolation) --- src/Commands/UpgradeForTeams.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Commands/UpgradeForTeams.php b/src/Commands/UpgradeForTeams.php index 39dd1376c..9923ca19d 100644 --- a/src/Commands/UpgradeForTeams.php +++ b/src/Commands/UpgradeForTeams.php @@ -122,6 +122,6 @@ protected function getMigrationPath($date = null) { $date = $date ?: date('Y_m_d_His'); - return database_path("migrations/${date}_{$this->migrationSuffix}"); + return database_path("migrations/{$date}_{$this->migrationSuffix}"); } } From 738333537f639ab4cabc7367235f93f73fc231b8 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Wed, 29 Jun 2022 18:09:46 -0500 Subject: [PATCH 147/648] Use `getKey`, `getKeyName` instead of `id` (#2116) --- src/Models/Role.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Models/Role.php b/src/Models/Role.php index da6f269e0..dfabc0b68 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -193,6 +193,6 @@ public function hasPermissionTo($permission): bool throw GuardDoesNotMatch::create($permission->guard_name, $this->getGuardNames()); } - return $this->permissions->contains('id', $permission->id); + return $this->permissions->contains($permission->getKeyName(), $permission->getKey()); } } From d26cb3d7c63bdc29c77a76c5007cf4813f18173d Mon Sep 17 00:00:00 2001 From: erikn69 Date: Wed, 29 Jun 2022 18:10:45 -0500 Subject: [PATCH 148/648] Use static instead of self for extending (#2111) --- src/WildcardPermission.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/WildcardPermission.php b/src/WildcardPermission.php index a1b20a707..884b1c17c 100644 --- a/src/WildcardPermission.php +++ b/src/WildcardPermission.php @@ -41,7 +41,7 @@ public function __construct(string $permission) public function implies($permission): bool { if (is_string($permission)) { - $permission = new self($permission); + $permission = new static($permission); } $otherParts = $permission->getParts(); @@ -52,7 +52,7 @@ public function implies($permission): bool return true; } - if (! $this->parts->get($i)->contains(self::WILDCARD_TOKEN) + if (! $this->parts->get($i)->contains(static::WILDCARD_TOKEN) && ! $this->containsAll($this->parts->get($i), $otherPart)) { return false; } @@ -61,7 +61,7 @@ public function implies($permission): bool } for ($i; $i < $this->parts->count(); $i++) { - if (! $this->parts->get($i)->contains(self::WILDCARD_TOKEN)) { + if (! $this->parts->get($i)->contains(static::WILDCARD_TOKEN)) { return false; } } @@ -105,10 +105,10 @@ protected function setParts(): void throw WildcardPermissionNotProperlyFormatted::create($this->permission); } - $parts = collect(explode(self::PART_DELIMITER, $this->permission)); + $parts = collect(explode(static::PART_DELIMITER, $this->permission)); $parts->each(function ($item, $key) { - $subParts = collect(explode(self::SUBPART_DELIMITER, $item)); + $subParts = collect(explode(static::SUBPART_DELIMITER, $item)); if ($subParts->isEmpty() || $subParts->contains('')) { throw WildcardPermissionNotProperlyFormatted::create($this->permission); From f2303a70be60919811ca8afc313e8244fda00974 Mon Sep 17 00:00:00 2001 From: angeljqv <79208641+angeljqv@users.noreply.github.com> Date: Wed, 29 Jun 2022 18:11:42 -0500 Subject: [PATCH 149/648] Clear roles array after hydrate from cache (#2099) --- src/PermissionRegistrar.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index 38beec605..e886c4961 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -378,5 +378,7 @@ private function hydrateRolesCache() $role = $roleInstance->newFromBuilder($this->aliasedArray($item)); $this->cachedRoles[$role->getKey()] = $role; }, $this->permissions['roles']); + + $this->permissions['roles'] = []; } } From dbc6c18ef4b2d097350a5faf82382564748e6c97 Mon Sep 17 00:00:00 2001 From: freekmurze Date: Wed, 29 Jun 2022 23:15:44 +0000 Subject: [PATCH 150/648] Update CHANGELOG --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 755ea17f1..37ae05db5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ All notable changes to `laravel-permission` will be documented in this file +## 5.5.5 - 2022-06-29 + +### What's Changed + +- Custom primary keys tests(Only tests) by @erikn69 in https://github.com/spatie/laravel-permission/pull/2096 +- [PHP 8.2] Fix `${var}` string interpolation deprecation by @Ayesh in https://github.com/spatie/laravel-permission/pull/2117 +- Use `getKey`, `getKeyName` instead of `id` by @erikn69 in https://github.com/spatie/laravel-permission/pull/2116 +- On WildcardPermission class use static instead of self for extending by @erikn69 in https://github.com/spatie/laravel-permission/pull/2111 +- Clear roles array after hydrate from cache by @angeljqv in https://github.com/spatie/laravel-permission/pull/2099 + +### New Contributors + +- @Ayesh made their first contribution in https://github.com/spatie/laravel-permission/pull/2117 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/5.5.4...5.5.5 + ## 5.5.4 - 2022-05-16 ## What's Changed @@ -364,6 +380,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` 1. Also this is a good time to point out that now with v2.25.0 and v2.26.0 most permission-cache-reset scenarios may no longer be needed in your app, so it's worth reviewing those cases, as you may gain some app speed improvement by removing unnecessary cache resets. @@ -417,6 +434,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` ## 2.19.1 - 2018-09-14 From 6e1b1122db35b8d779beb9be54a30b0e2e6c2f25 Mon Sep 17 00:00:00 2001 From: drdan18 Date: Thu, 14 Jul 2022 08:32:50 -0400 Subject: [PATCH 151/648] Update role-permission.md The explanation of the expected results of hasAllDirectPermissions was incorrect. --- docs/basic-usage/role-permissions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/basic-usage/role-permissions.md b/docs/basic-usage/role-permissions.md index c2c8fb764..113ab980a 100644 --- a/docs/basic-usage/role-permissions.md +++ b/docs/basic-usage/role-permissions.md @@ -143,7 +143,7 @@ $user->hasAllDirectPermissions(['edit articles', 'delete articles']); $user->hasAnyDirectPermission(['create articles', 'delete articles']); ``` By following the previous example, when we call `$user->hasAllDirectPermissions(['edit articles', 'delete articles'])` -it returns `true`, because the user has all these direct permissions. +it returns `false`, because the user does not have `edit articles` as a direct permission. When we call `$user->hasAnyDirectPermission('edit articles')`, it returns `true` because the user has one of the provided permissions. From 849f5b9432c9cc53b791670b9c9b91c5446cee85 Mon Sep 17 00:00:00 2001 From: Glen Solsberry Date: Tue, 6 Sep 2022 10:29:22 -0400 Subject: [PATCH 152/648] Update multiple-guards.md The verbiage provided is specific to a logged-in user, when in reality, it's for any loaded model. --- docs/basic-usage/multiple-guards.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/basic-usage/multiple-guards.md b/docs/basic-usage/multiple-guards.md index 564c232ea..27f453cb1 100644 --- a/docs/basic-usage/multiple-guards.md +++ b/docs/basic-usage/multiple-guards.md @@ -38,7 +38,7 @@ $user->hasPermissionTo('publish articles', 'admin'); > **Note**: When determining whether a role/permission is valid on a given model, it checks against the first matching guard in this order (it does NOT check role/permission for EACH possibility, just the first match): - first the guardName() method if it exists on the model; - then the `$guard_name` property if it exists on the model; -- then the first-defined guard/provider combination in the `auth.guards` config array that matches the logged-in user's guard; +- then the first-defined guard/provider combination in the `auth.guards` config array that matches the loaded model's guard; - then the `auth.defaults.guard` config (which is the user's guard if they are logged in, else the default in the file). From 0a11eec80b2882b068a1bf1d66e75d8a60d216e4 Mon Sep 17 00:00:00 2001 From: Miten Patel Date: Wed, 7 Sep 2022 09:48:20 +0530 Subject: [PATCH 153/648] Update teams-permissions.md --- docs/basic-usage/teams-permissions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/basic-usage/teams-permissions.md b/docs/basic-usage/teams-permissions.md index 9c57ffc15..7c2bf2f54 100644 --- a/docs/basic-usage/teams-permissions.md +++ b/docs/basic-usage/teams-permissions.md @@ -91,7 +91,7 @@ class YourTeamModel extends \Illuminate\Database\Eloquent\Model User::find('your_user_id')->assignRole('Super Admin'); // restore session team_id to package instance setPermissionsTeamId($session_team_id); - } + }); } // ... } From 54addd6e28c01caf5155310fc97b3441d611fd5e Mon Sep 17 00:00:00 2001 From: Androidacy Service Account Date: Fri, 16 Sep 2022 21:05:11 -0400 Subject: [PATCH 154/648] Add note about non-standard User models This may lead to some confusing results for users who didn't delve into the advanced usage section. Put a note in Prerequisites about the caveat. --- docs/prerequisites.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/prerequisites.md b/docs/prerequisites.md index 4508c6cdd..cac1e87ac 100644 --- a/docs/prerequisites.md +++ b/docs/prerequisites.md @@ -47,3 +47,7 @@ MySQL 8.0 limits index keys to 1000 characters. This package publishes a migrati Thus in your AppServiceProvider you will need to set `Schema::defaultStringLength(125)`. [See the Laravel Docs for instructions](https://laravel.com/docs/migrations#index-lengths-mysql-mariadb). +## Note for apps using UUIDs/GUIDs + +This package expects the primary key of your `User` model to be an auto-incrementing `int`. If it is not, you may need to modifiy the `create_permission_tables` migration and/or modify the default configuration. See [https://spatie.be/docs/laravel-permission/v5/advanced-usage/uuid](https://spatie.be/docs/laravel-permission/v5/advanced-usage/uuid) for more information. + From e7e78bb73f3b13ab37e8054086b7c0efc5862462 Mon Sep 17 00:00:00 2001 From: angeljqv <79208641+angeljqv@users.noreply.github.com> Date: Wed, 21 Sep 2022 09:50:02 -0500 Subject: [PATCH 155/648] Delegate permission collection filter to another method --- src/Traits/HasPermissions.php | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index d116e8ac6..3f89e802d 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -315,15 +315,15 @@ public function getAllPermissions(): Collection } /** - * Grant the given permission(s) to a role. + * Returns permissions ids as array keys * * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection $permissions * - * @return $this + * @return array */ - public function givePermissionTo(...$permissions) + public function collectPermissions(...$permissions) { - $permissions = collect($permissions) + return collect($permissions) ->flatten() ->reduce(function ($array, $permission) { if (empty($permission)) { @@ -342,6 +342,18 @@ public function givePermissionTo(...$permissions) return $array; }, []); + } + + /** + * Grant the given permission(s) to a role. + * + * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection $permissions + * + * @return $this + */ + public function givePermissionTo(...$permissions) + { + $permissions = $this->collectPermissions(...$permissions); $model = $this->getModel(); From a096b9d03cec1593376a83b7c036af7254e14f50 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Fri, 30 Sep 2022 09:51:31 -0500 Subject: [PATCH 156/648] Fix returning all roles instead of the assigned --- src/Traits/HasRoles.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index f70f1daff..6e4d7e5fe 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -189,6 +189,8 @@ public function syncRoles(...$roles) */ public function hasRole($roles, string $guard = null): bool { + $this->loadMissing('roles'); + if (is_string($roles) && false !== strpos($roles, '|')) { $roles = $this->convertPipeToArray($roles); } @@ -248,6 +250,8 @@ public function hasAnyRole(...$roles): bool */ public function hasAllRoles($roles, string $guard = null): bool { + $this->loadMissing('roles'); + if (is_string($roles) && false !== strpos($roles, '|')) { $roles = $this->convertPipeToArray($roles); } @@ -282,6 +286,8 @@ public function hasAllRoles($roles, string $guard = null): bool */ public function hasExactRoles($roles, string $guard = null): bool { + $this->loadMissing('roles'); + if (is_string($roles) && false !== strpos($roles, '|')) { $roles = $this->convertPipeToArray($roles); } @@ -311,6 +317,8 @@ public function getDirectPermissions(): Collection public function getRoleNames(): Collection { + $this->loadMissing('roles'); + return $this->roles->pluck('name'); } From dd47fdde95596252a8147b73e56c446663ab73cc Mon Sep 17 00:00:00 2001 From: Maarten Paauw Date: Wed, 5 Oct 2022 10:13:39 +0200 Subject: [PATCH 157/648] Make Writing Policies link clickable The link wasn't clickable at the documentation website. --- docs/basic-usage/role-permissions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/basic-usage/role-permissions.md b/docs/basic-usage/role-permissions.md index c2c8fb764..259a521c1 100644 --- a/docs/basic-usage/role-permissions.md +++ b/docs/basic-usage/role-permissions.md @@ -170,4 +170,4 @@ the second will be a collection with the `edit article` permission and the third ### NOTE about using permission names in policies -When calling `authorize()` for a policy method, if you have a permission named the same as one of those policy methods, your permission "name" will take precedence and not fire the policy. For this reason it may be wise to avoid naming your permissions the same as the methods in your policy. While you can define your own method names, you can read more about the defaults Laravel offers in Laravel's documentation at https://laravel.com/docs/authorization#writing-policies +When calling `authorize()` for a policy method, if you have a permission named the same as one of those policy methods, your permission "name" will take precedence and not fire the policy. For this reason it may be wise to avoid naming your permissions the same as the methods in your policy. While you can define your own method names, you can read more about the defaults Laravel offers in Laravel's documentation at [Writing Policies](https://laravel.com/docs/authorization#writing-policies). From 5d239d87ab001466274c3fd2fe04936c6a77e4b3 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Wed, 12 Oct 2022 11:46:10 -0400 Subject: [PATCH 158/648] Fix typo --- docs/prerequisites.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/prerequisites.md b/docs/prerequisites.md index cac1e87ac..069667cdb 100644 --- a/docs/prerequisites.md +++ b/docs/prerequisites.md @@ -49,5 +49,5 @@ Thus in your AppServiceProvider you will need to set `Schema::defaultStringLengt ## Note for apps using UUIDs/GUIDs -This package expects the primary key of your `User` model to be an auto-incrementing `int`. If it is not, you may need to modifiy the `create_permission_tables` migration and/or modify the default configuration. See [https://spatie.be/docs/laravel-permission/v5/advanced-usage/uuid](https://spatie.be/docs/laravel-permission/v5/advanced-usage/uuid) for more information. +This package expects the primary key of your `User` model to be an auto-incrementing `int`. If it is not, you may need to modify the `create_permission_tables` migration and/or modify the default configuration. See [https://spatie.be/docs/laravel-permission/v5/advanced-usage/uuid](https://spatie.be/docs/laravel-permission/v5/advanced-usage/uuid) for more information. From 309ec2d5eee1ea71f6bcdf77f6eddd4972f316a5 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Fri, 14 Oct 2022 11:08:06 -0500 Subject: [PATCH 159/648] Add ULIDs reference --- docs/prerequisites.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/prerequisites.md b/docs/prerequisites.md index 069667cdb..fc64ed7a8 100644 --- a/docs/prerequisites.md +++ b/docs/prerequisites.md @@ -47,7 +47,7 @@ MySQL 8.0 limits index keys to 1000 characters. This package publishes a migrati Thus in your AppServiceProvider you will need to set `Schema::defaultStringLength(125)`. [See the Laravel Docs for instructions](https://laravel.com/docs/migrations#index-lengths-mysql-mariadb). -## Note for apps using UUIDs/GUIDs +## Note for apps using UUIDs/ULIDs/GUIDs This package expects the primary key of your `User` model to be an auto-incrementing `int`. If it is not, you may need to modify the `create_permission_tables` migration and/or modify the default configuration. See [https://spatie.be/docs/laravel-permission/v5/advanced-usage/uuid](https://spatie.be/docs/laravel-permission/v5/advanced-usage/uuid) for more information. From fc9ea8586cc24b09f04f1a966cb7c943149b6f5e Mon Sep 17 00:00:00 2001 From: erikn69 Date: Fri, 14 Oct 2022 12:55:12 -0500 Subject: [PATCH 160/648] PHP 8.2 Build --- .github/workflows/run-tests-L8.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/run-tests-L8.yml b/.github/workflows/run-tests-L8.yml index 5843a98c9..11c4fcbae 100644 --- a/.github/workflows/run-tests-L8.yml +++ b/.github/workflows/run-tests-L8.yml @@ -9,7 +9,7 @@ jobs: strategy: fail-fast: false matrix: - php: [8.1, 8.0, 7.4, 7.3] + php: [8.2, 8.1, 8.0, 7.4, 7.3] laravel: [9.*, 8.*] dependency-version: [prefer-lowest, prefer-stable] include: @@ -38,7 +38,7 @@ jobs: - name: Install dependencies run: | - composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" "symfony/console:>=4.3.4" "mockery/mockery:^1.3.2" --no-interaction --no-update + composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" "symfony/console:>=4.3.4" "mockery/mockery:^1.3.2" "nesbot/carbon:>=2.62.1" --no-interaction --no-update composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction - name: Execute tests From ecf27c5f0d62210a38f9edef59352ec872b1d241 Mon Sep 17 00:00:00 2001 From: Eric Junker Date: Mon, 17 Oct 2022 11:35:00 -0500 Subject: [PATCH 161/648] Prevent MissingAttributeException for guard_name When new Laravel feature `Model::preventAccessingMissingAttributes()` is enabled you may get a `MissingAttributeException` for `guard_name`. This change uses `getAttributeValue()` to prevent the exception. Fixes #2215 --- src/Guard.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Guard.php b/src/Guard.php index 41d802c10..c0630392b 100644 --- a/src/Guard.php +++ b/src/Guard.php @@ -22,7 +22,7 @@ public static function getNames($model): Collection if (\method_exists($model, 'guardName')) { $guardName = $model->guardName(); } else { - $guardName = $model->guard_name ?? null; + $guardName = $model->getAttributeValue('guard_name'); } } From 5e8b0695f4e1ac8e9a09edb7b06a0aa92547e05e Mon Sep 17 00:00:00 2001 From: angeljqv <79208641+angeljqv@users.noreply.github.com> Date: Tue, 18 Oct 2022 20:37:09 -0500 Subject: [PATCH 162/648] Delegate permission filter to another method (#2183) * Delegate permission filter to another method --- src/Traits/HasPermissions.php | 45 +++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index 3f89e802d..7e89ec7a2 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -115,20 +115,15 @@ protected function convertToPermissionModels($permissions): array } /** - * Determine if the model may perform the given permission. + * Find a permission. * * @param string|int|\Spatie\Permission\Contracts\Permission $permission - * @param string|null $guardName * - * @return bool + * @return \Spatie\Permission\Contracts\Permission * @throws PermissionDoesNotExist */ - public function hasPermissionTo($permission, $guardName = null): bool + public function filterPermission($permission, $guardName = null) { - if (config('permission.enable_wildcard_permission', false)) { - return $this->hasWildcardPermission($permission, $guardName); - } - $permissionClass = $this->getPermissionClass(); if (is_string($permission)) { @@ -149,6 +144,26 @@ public function hasPermissionTo($permission, $guardName = null): bool throw new PermissionDoesNotExist(); } + return $permission; + } + + /** + * Determine if the model may perform the given permission. + * + * @param string|int|\Spatie\Permission\Contracts\Permission $permission + * @param string|null $guardName + * + * @return bool + * @throws PermissionDoesNotExist + */ + public function hasPermissionTo($permission, $guardName = null): bool + { + if (config('permission.enable_wildcard_permission', false)) { + return $this->hasWildcardPermission($permission, $guardName); + } + + $permission = $this->filterPermission($permission, $guardName); + return $this->hasDirectPermission($permission) || $this->hasPermissionViaRole($permission); } @@ -271,19 +286,7 @@ protected function hasPermissionViaRole(Permission $permission): bool */ public function hasDirectPermission($permission): bool { - $permissionClass = $this->getPermissionClass(); - - if (is_string($permission)) { - $permission = $permissionClass->findByName($permission, $this->getDefaultGuardName()); - } - - if (is_int($permission)) { - $permission = $permissionClass->findById($permission, $this->getDefaultGuardName()); - } - - if (! $permission instanceof Permission) { - throw new PermissionDoesNotExist(); - } + $permission = $this->filterPermission($permission); return $this->permissions->contains($permission->getKeyName(), $permission->getKey()); } From cca6226273787be4674aa23f552c8426495e6f1f Mon Sep 17 00:00:00 2001 From: drbyte Date: Wed, 19 Oct 2022 03:01:56 +0000 Subject: [PATCH 163/648] Update CHANGELOG --- CHANGELOG.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 37ae05db5..76c98176f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,30 @@ All notable changes to `laravel-permission` will be documented in this file +## 5.5.6 - 2022-10-19 + +Just a maintenance release. + +### What's Changed + +- Actions: add PHP 8.2 Build by @erikn69 in https://github.com/spatie/laravel-permission/pull/2214 +- Docs: Fix small syntax error in teams-permissions.md by @miten5 in https://github.com/spatie/laravel-permission/pull/2171 +- Docs: Update documentation for multiple guards by @gms8994 in https://github.com/spatie/laravel-permission/pull/2169 +- Docs: Make Writing Policies link clickable by @maartenpaauw in https://github.com/spatie/laravel-permission/pull/2202 +- Docs: Add note about non-standard User models by @androidacy-user in https://github.com/spatie/laravel-permission/pull/2179 +- Docs: Fix explanation of results for hasAllDirectPermissions in role-permission.md by @drdan18 in https://github.com/spatie/laravel-permission/pull/2139 +- Docs: Add ULIDs reference by @erikn69 in https://github.com/spatie/laravel-permission/pull/2213 + +### New Contributors + +- @miten5 made their first contribution in https://github.com/spatie/laravel-permission/pull/2171 +- @gms8994 made their first contribution in https://github.com/spatie/laravel-permission/pull/2169 +- @maartenpaauw made their first contribution in https://github.com/spatie/laravel-permission/pull/2202 +- @androidacy-user made their first contribution in https://github.com/spatie/laravel-permission/pull/2179 +- @drdan18 made their first contribution in https://github.com/spatie/laravel-permission/pull/2139 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/5.5.5...5.5.6 + ## 5.5.5 - 2022-06-29 ### What's Changed @@ -381,6 +405,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` 1. Also this is a good time to point out that now with v2.25.0 and v2.26.0 most permission-cache-reset scenarios may no longer be needed in your app, so it's worth reviewing those cases, as you may gain some app speed improvement by removing unnecessary cache resets. @@ -435,6 +460,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` ## 2.19.1 - 2018-09-14 From 5bd7b0c5a4765864e8fcbc36237b1ca5274255f6 Mon Sep 17 00:00:00 2001 From: drbyte Date: Wed, 19 Oct 2022 03:04:02 +0000 Subject: [PATCH 164/648] Update CHANGELOG --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76c98176f..17258ca8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,17 @@ All notable changes to `laravel-permission` will be documented in this file +## 5.5.7 - 2022-10-19 + +Optimize HasPermissions trait + +### What's Changed + +- Delegate permission collection filter to another method by @angeljqv in https://github.com/spatie/laravel-permission/pull/2182 +- Delegate permission filter to another method by @angeljqv in https://github.com/spatie/laravel-permission/pull/2183 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/5.5.6...5.5.7 + ## 5.5.6 - 2022-10-19 Just a maintenance release. @@ -406,6 +417,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` 1. Also this is a good time to point out that now with v2.25.0 and v2.26.0 most permission-cache-reset scenarios may no longer be needed in your app, so it's worth reviewing those cases, as you may gain some app speed improvement by removing unnecessary cache resets. @@ -461,6 +473,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` ## 2.19.1 - 2018-09-14 From 647d6e629eb7434ad57724c74fadd833456007f7 Mon Sep 17 00:00:00 2001 From: drbyte Date: Wed, 19 Oct 2022 03:05:52 +0000 Subject: [PATCH 165/648] Update CHANGELOG --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17258ca8e..cbd4c04c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ All notable changes to `laravel-permission` will be documented in this file +## 5.5.8 - 2022-10-19 + +`HasRoles` trait + +### What's Changed + +- Fix returning all roles instead of the assigned by @erikn69 in https://github.com/spatie/laravel-permission/pull/2194 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/5.5.7...5.5.8 + ## 5.5.7 - 2022-10-19 Optimize HasPermissions trait @@ -418,6 +428,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` 1. Also this is a good time to point out that now with v2.25.0 and v2.26.0 most permission-cache-reset scenarios may no longer be needed in your app, so it's worth reviewing those cases, as you may gain some app speed improvement by removing unnecessary cache resets. @@ -474,6 +485,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` ## 2.19.1 - 2018-09-14 From 2f266d80d6b0b5c5eeb29b190e45456a96769b86 Mon Sep 17 00:00:00 2001 From: drbyte Date: Wed, 19 Oct 2022 03:07:39 +0000 Subject: [PATCH 166/648] Update CHANGELOG --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cbd4c04c3..ccafa27ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,20 @@ All notable changes to `laravel-permission` will be documented in this file +## 5.5.9 - 2022-10-19 + +Compatibility Bugfix + +### What's Changed + +- Prevent `MissingAttributeException` for `guard_name` by @ejunker in https://github.com/spatie/laravel-permission/pull/2216 + +### New Contributors + +- @ejunker made their first contribution in https://github.com/spatie/laravel-permission/pull/2216 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/5.5.8...5.5.9 + ## 5.5.8 - 2022-10-19 `HasRoles` trait @@ -429,6 +443,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` 1. Also this is a good time to point out that now with v2.25.0 and v2.26.0 most permission-cache-reset scenarios may no longer be needed in your app, so it's worth reviewing those cases, as you may gain some app speed improvement by removing unnecessary cache resets. @@ -486,6 +501,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` ## 2.19.1 - 2018-09-14 From 9e742069966b3540f5642f54f8c1149d214ab504 Mon Sep 17 00:00:00 2001 From: drbyte Date: Wed, 19 Oct 2022 03:12:43 +0000 Subject: [PATCH 167/648] Update CHANGELOG --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ccafa27ab..eaed58a41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ All notable changes to `laravel-permission` will be documented in this file +## 5.5.10 - 2022-10-19 + +### What's Changed + +- Avoid calling the config helper in the role/perm model constructor by @adiafora in https://github.com/spatie/laravel-permission/pull/2098 as discussed in https://github.com/spatie/laravel-permission/issues/2097 regarding `DI` + +### New Contributors + +- @adiafora made their first contribution in https://github.com/spatie/laravel-permission/pull/2098 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/5.5.9...5.5.10 + ## 5.5.9 - 2022-10-19 Compatibility Bugfix @@ -444,6 +456,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` 1. Also this is a good time to point out that now with v2.25.0 and v2.26.0 most permission-cache-reset scenarios may no longer be needed in your app, so it's worth reviewing those cases, as you may gain some app speed improvement by removing unnecessary cache resets. @@ -502,6 +515,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` ## 2.19.1 - 2018-09-14 From b8e717b2248dfbc078e932e5487683a09f69ffbc Mon Sep 17 00:00:00 2001 From: erikn69 Date: Mon, 5 Sep 2022 11:35:59 -0500 Subject: [PATCH 168/648] Support static arrays on blade directives --- src/PermissionServiceProvider.php | 33 +++++++------------ tests/BladeTest.php | 31 +++++++++++++++-- .../views/guardHasAllRolesArray.blade.php | 5 +++ 3 files changed, 46 insertions(+), 23 deletions(-) create mode 100644 tests/resources/views/guardHasAllRolesArray.blade.php diff --git a/src/PermissionServiceProvider.php b/src/PermissionServiceProvider.php index 61fc1321b..e408064a9 100644 --- a/src/PermissionServiceProvider.php +++ b/src/PermissionServiceProvider.php @@ -84,62 +84,53 @@ protected function registerModelBindings() $this->app->bind(RoleContract::class, $config['role']); } + public static function bladeMethodWrapper($method, $role, $guard = null) + { + return auth($guard)->check() && auth($guard)->user()->{$method}($role); + } + protected function registerBladeExtensions($bladeCompiler) { $bladeCompiler->directive('role', function ($arguments) { - list($role, $guard) = explode(',', $arguments.','); - - return "check() && auth({$guard})->user()->hasRole({$role})): ?>"; + return ""; }); $bladeCompiler->directive('elserole', function ($arguments) { - list($role, $guard) = explode(',', $arguments.','); - - return "check() && auth({$guard})->user()->hasRole({$role})): ?>"; + return ""; }); $bladeCompiler->directive('endrole', function () { return ''; }); $bladeCompiler->directive('hasrole', function ($arguments) { - list($role, $guard) = explode(',', $arguments.','); - - return "check() && auth({$guard})->user()->hasRole({$role})): ?>"; + return ""; }); $bladeCompiler->directive('endhasrole', function () { return ''; }); $bladeCompiler->directive('hasanyrole', function ($arguments) { - list($roles, $guard) = explode(',', $arguments.','); - - return "check() && auth({$guard})->user()->hasAnyRole({$roles})): ?>"; + return ""; }); $bladeCompiler->directive('endhasanyrole', function () { return ''; }); $bladeCompiler->directive('hasallroles', function ($arguments) { - list($roles, $guard) = explode(',', $arguments.','); - - return "check() && auth({$guard})->user()->hasAllRoles({$roles})): ?>"; + return ""; }); $bladeCompiler->directive('endhasallroles', function () { return ''; }); $bladeCompiler->directive('unlessrole', function ($arguments) { - list($role, $guard) = explode(',', $arguments.','); - - return "check() || ! auth({$guard})->user()->hasRole({$role})): ?>"; + return ""; }); $bladeCompiler->directive('endunlessrole', function () { return ''; }); $bladeCompiler->directive('hasexactroles', function ($arguments) { - list($roles, $guard) = explode(',', $arguments.','); - - return "check() && auth({$guard})->user()->hasExactRoles({$roles})): ?>"; + return ""; }); $bladeCompiler->directive('endhasexactroles', function () { return ''; diff --git a/tests/BladeTest.php b/tests/BladeTest.php index 8558f5092..21d66437f 100644 --- a/tests/BladeTest.php +++ b/tests/BladeTest.php @@ -31,9 +31,9 @@ public function all_blade_directives_will_evaluate_false_when_there_is_nobody_lo $this->assertEquals('does not have permission', $this->renderView('can', ['permission' => $permission])); $this->assertEquals('does not have role', $this->renderView('role', compact('role', 'elserole'))); $this->assertEquals('does not have role', $this->renderView('hasRole', compact('role', 'elserole'))); - $this->assertEquals('does not have all of the given roles', $this->renderView('hasAllRoles', $roles)); + $this->assertEquals('does not have all of the given roles', $this->renderView('hasAllRoles', compact('roles'))); $this->assertEquals('does not have all of the given roles', $this->renderView('hasAllRoles', ['roles' => implode('|', $roles)])); - $this->assertEquals('does not have any of the given roles', $this->renderView('hasAnyRole', $roles)); + $this->assertEquals('does not have any of the given roles', $this->renderView('hasAnyRole', compact('roles'))); $this->assertEquals('does not have any of the given roles', $this->renderView('hasAnyRole', ['roles' => implode('|', $roles)])); } @@ -262,6 +262,33 @@ public function the_hasallroles_directive_will_evaluate_false_when_the_logged_in $this->assertEquals('does not have all of the given roles', $this->renderView('guardHasAllRolesPipe', compact('guard'))); } + /** @test */ + public function the_hasallroles_directive_will_evaluate_true_when_the_logged_in_user_does_have_all_required_roles_in_array() + { + $guard = 'admin'; + + $admin = $this->getSuperAdmin(); + + $admin->assignRole('moderator'); + + auth('admin')->setUser($admin); + + $this->assertEquals('does have all of the given roles', $this->renderView('guardHasAllRolesArray', compact('guard'))); + } + + /** @test */ + public function the_hasallroles_directive_will_evaluate_false_when_the_logged_in_user_doesnt_have_all_required_roles_in_array() + { + $guard = ''; + $user = $this->getMember(); + + $user->assignRole('writer'); + + auth()->setUser($user); + + $this->assertEquals('does not have all of the given roles', $this->renderView('guardHasAllRolesArray', compact('guard'))); + } + protected function getWriter() { $this->testUser->assignRole('writer'); diff --git a/tests/resources/views/guardHasAllRolesArray.blade.php b/tests/resources/views/guardHasAllRolesArray.blade.php new file mode 100644 index 000000000..6ba51c1d8 --- /dev/null +++ b/tests/resources/views/guardHasAllRolesArray.blade.php @@ -0,0 +1,5 @@ +@hasallroles(['super-admin', 'moderator'], $guard) +does have all of the given roles +@else +does not have all of the given roles +@endhasallroles From 1cb0a5ca72a04f3a2e34ecfe94340b30a6f92e82 Mon Sep 17 00:00:00 2001 From: drbyte Date: Wed, 19 Oct 2022 16:01:19 +0000 Subject: [PATCH 169/648] Update CHANGELOG --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index eaed58a41..01a4a7ac9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to `laravel-permission` will be documented in this file +## 5.5.11 - 2022-10-19 + +### What's Changed + +- Support static arrays on blade directives by @erikn69 in https://github.com/spatie/laravel-permission/pull/2168 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/5.5.10...5.5.11 + ## 5.5.10 - 2022-10-19 ### What's Changed @@ -457,6 +465,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` 1. Also this is a good time to point out that now with v2.25.0 and v2.26.0 most permission-cache-reset scenarios may no longer be needed in your app, so it's worth reviewing those cases, as you may gain some app speed improvement by removing unnecessary cache resets. @@ -516,6 +525,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` ## 2.19.1 - 2018-09-14 From e4493daedcab9aac91b9d1549ebcbad44f3f7326 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Wed, 19 Oct 2022 13:38:41 -0500 Subject: [PATCH 170/648] Fix undefined index guard_name --- src/Models/Permission.php | 2 +- src/Models/Role.php | 2 +- tests/PermissionTest.php | 14 ++++++++++++++ tests/RoleTest.php | 13 +++++++++++++ 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/Models/Permission.php b/src/Models/Permission.php index 9ef30ed17..066a37840 100644 --- a/src/Models/Permission.php +++ b/src/Models/Permission.php @@ -64,7 +64,7 @@ public function roles(): BelongsToMany public function users(): BelongsToMany { return $this->morphedByMany( - getModelForGuard($this->attributes['guard_name']), + getModelForGuard($this->attributes['guard_name'] ?? config('auth.defaults.guard')), 'model', config('permission.table_names.model_has_permissions'), PermissionRegistrar::$pivotPermission, diff --git a/src/Models/Role.php b/src/Models/Role.php index 731e99988..84f913618 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -70,7 +70,7 @@ public function permissions(): BelongsToMany public function users(): BelongsToMany { return $this->morphedByMany( - getModelForGuard($this->attributes['guard_name']), + getModelForGuard($this->attributes['guard_name'] ?? config('auth.defaults.guard')), 'model', config('permission.table_names.model_has_roles'), PermissionRegistrar::$pivotRole, diff --git a/tests/PermissionTest.php b/tests/PermissionTest.php index 35f02d0b0..b157d247d 100644 --- a/tests/PermissionTest.php +++ b/tests/PermissionTest.php @@ -7,6 +7,20 @@ class PermissionTest extends TestCase { + /** @test */ + public function it_get_user_models_using_with() + { + $this->testUser->givePermissionTo($this->testUserPermission); + + $permission = app(Permission::class)::with('users') + ->where($this->testUserPermission->getKeyName(), $this->testUserPermission->getKey()) + ->first(); + + $this->assertEquals($permission->getKey(), $this->testUserPermission->getKey()); + $this->assertCount(1, $permission->users); + $this->assertEquals($permission->users[0]->id, $this->testUser->id); + } + /** @test */ public function it_throws_an_exception_when_the_permission_already_exists() { diff --git a/tests/RoleTest.php b/tests/RoleTest.php index 61a958b8d..35e287d50 100644 --- a/tests/RoleTest.php +++ b/tests/RoleTest.php @@ -21,6 +21,19 @@ public function setUp(): void Permission::create(['name' => 'wrong-guard-permission', 'guard_name' => 'admin']); } + /** @test */ + public function it_get_user_models_using_with() + { + $this->testUser->assignRole($this->testUserRole); + + $role = app(Role::class)::with('users') + ->where($this->testUserRole->getKeyName(), $this->testUserRole->getKey())->first(); + + $this->assertEquals($role->getKey(), $this->testUserRole->getKey()); + $this->assertCount(1, $role->users); + $this->assertEquals($role->users[0]->id, $this->testUser->id); + } + /** @test */ public function it_has_user_models_of_the_right_class() { From 3cbed36eb0524f518bb2cbf1bf69e7df489c3424 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Wed, 19 Oct 2022 14:24:57 -0500 Subject: [PATCH 171/648] Fix detaching user models on teams feature #2220 --- src/Traits/HasPermissions.php | 3 +++ src/Traits/HasRoles.php | 3 +++ tests/TeamHasRolesTest.php | 26 ++++++++++++++++++++++++++ 3 files changed, 32 insertions(+) diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index 7e89ec7a2..8234dd666 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -27,7 +27,10 @@ public static function bootHasPermissions() return; } + $teams = PermissionRegistrar::$teams; + PermissionRegistrar::$teams = false; $model->permissions()->detach(); + PermissionRegistrar::$teams = $teams; }); } diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index 6e4d7e5fe..59faeec79 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -24,7 +24,10 @@ public static function bootHasRoles() return; } + $teams = PermissionRegistrar::$teams; + PermissionRegistrar::$teams = false; $model->roles()->detach(); + PermissionRegistrar::$teams = $teams; }); } diff --git a/tests/TeamHasRolesTest.php b/tests/TeamHasRolesTest.php index 05393194a..d3f35d899 100644 --- a/tests/TeamHasRolesTest.php +++ b/tests/TeamHasRolesTest.php @@ -9,6 +9,32 @@ class TeamHasRolesTest extends HasRolesTest /** @var bool */ protected $hasTeams = true; + /** @test */ + public function it_deletes_pivot_table_entries_when_deleting_models() + { + $user1 = User::create(['email' => 'user2@test.com']); + $user2 = User::create(['email' => 'user2@test.com']); + + setPermissionsTeamId(1); + $user1->assignRole('testRole'); + $user1->givePermissionTo('edit-articles'); + $user2->assignRole('testRole'); + $user2->givePermissionTo('edit-articles'); + setPermissionsTeamId(2); + $user1->givePermissionTo('edit-news'); + + $this->assertDatabaseHas('model_has_permissions', [config('permission.column_names.model_morph_key') => $user1->id]); + $this->assertDatabaseHas('model_has_roles', [config('permission.column_names.model_morph_key') => $user1->id]); + + $user1->delete(); + + setPermissionsTeamId(1); + $this->assertDatabaseMissing('model_has_permissions', [config('permission.column_names.model_morph_key') => $user1->id]); + $this->assertDatabaseMissing('model_has_roles', [config('permission.column_names.model_morph_key') => $user1->id]); + $this->assertDatabaseHas('model_has_permissions', [config('permission.column_names.model_morph_key') => $user2->id]); + $this->assertDatabaseHas('model_has_roles', [config('permission.column_names.model_morph_key') => $user2->id]); + } + /** @test */ public function it_can_assign_same_and_different_roles_on_same_user_different_teams() { From 9adc4b2189e216d1770ffaa8783e5c7cf2ff4174 Mon Sep 17 00:00:00 2001 From: drbyte Date: Wed, 19 Oct 2022 20:28:08 +0000 Subject: [PATCH 172/648] Update CHANGELOG --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 01a4a7ac9..100c68091 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ All notable changes to `laravel-permission` will be documented in this file +## 5.5.12 - 2022-10-19 + +Fix regression introduced in `5.5.10` + +### What's Changed + +- Fix undefined index guard_name by @erikn69 in https://github.com/spatie/laravel-permission/pull/2219 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/5.5.11...5.5.12 + ## 5.5.11 - 2022-10-19 ### What's Changed @@ -466,6 +476,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` 1. Also this is a good time to point out that now with v2.25.0 and v2.26.0 most permission-cache-reset scenarios may no longer be needed in your app, so it's worth reviewing those cases, as you may gain some app speed improvement by removing unnecessary cache resets. @@ -526,6 +537,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` ## 2.19.1 - 2018-09-14 From f1ab1775bf68e57f8eeca262ef7294559968a45b Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Wed, 19 Oct 2022 17:13:51 -0400 Subject: [PATCH 173/648] Add clarity about coding for permissions --- docs/best-practices/roles-vs-permissions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/best-practices/roles-vs-permissions.md b/docs/best-practices/roles-vs-permissions.md index 50be7b117..b7d99b01f 100644 --- a/docs/best-practices/roles-vs-permissions.md +++ b/docs/best-practices/roles-vs-permissions.md @@ -3,9 +3,9 @@ title: Roles vs Permissions weight: 1 --- -It is generally best to code your app around `permissions` only. That way you can always use the native Laravel `@can` and `can()` directives everywhere in your app. +It is generally best to code your app around testing against `permissions` only. (ie: when testing whether to grant access to something, in most cases it's wisest to check against a `permission`, not a `role`). That way you can always use the native Laravel `@can` and `can()` directives everywhere in your app. -Roles can still be used to group permissions for easy assignment, and you can still use the role-based helper methods if truly necessary. But most app-related logic can usually be best controlled using the `can` methods, which allows Laravel's Gate layer to do all the heavy lifting. +Roles can still be used to group permissions for easy assignment to a user/model, and you can still use the role-based helper methods if truly necessary. But most app-related logic can usually be best controlled using the `can` methods, which allows Laravel's Gate layer to do all the heavy lifting. Sometimes certain groups of `route` rules may make best sense to group them around a `role`, but still, whenever possible, there is less overhead used if you can check against a specific `permission` instead. eg: `users` have `roles`, and `roles` have `permissions`, and your app always checks for `permissions`, not `roles`. From 4807005e6c299f0e0f8165c18a4c23d0d357d8d4 Mon Sep 17 00:00:00 2001 From: Julio Motol Date: Fri, 21 Oct 2022 08:41:12 +0800 Subject: [PATCH 174/648] =?UTF-8?q?=F0=9F=90=9B=20Override=20`getAllPermis?= =?UTF-8?q?sions()`=20in=20`Role`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julio Motol --- src/Models/Role.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Models/Role.php b/src/Models/Role.php index bb4173d82..b26a9af38 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -4,6 +4,7 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsToMany; +use Illuminate\Support\Collection; use Spatie\Permission\Contracts\Role as RoleContract; use Spatie\Permission\Exceptions\GuardDoesNotMatch; use Spatie\Permission\Exceptions\RoleAlreadyExists; @@ -195,4 +196,12 @@ public function hasPermissionTo($permission): bool return $this->permissions->contains($permission->getKeyName(), $permission->getKey()); } + + /** + * Return all the permissions the model has, both directly and via roles. + */ + public function getAllPermissions(): Collection + { + return $this->permissions->sort()->values(); + } } From eaaeb69113c5878f9d452bc2fe4876363e415380 Mon Sep 17 00:00:00 2001 From: Yao Date: Fri, 21 Oct 2022 10:12:53 +0800 Subject: [PATCH 175/648] fix: A bad configuration was used in forRoles --- src/Exceptions/UnauthorizedException.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Exceptions/UnauthorizedException.php b/src/Exceptions/UnauthorizedException.php index 373f426cc..da6ab32b3 100644 --- a/src/Exceptions/UnauthorizedException.php +++ b/src/Exceptions/UnauthorizedException.php @@ -14,7 +14,7 @@ public static function forRoles(array $roles): self { $message = 'User does not have the right roles.'; - if (config('permission.display_permission_in_exception')) { + if (config('permission.display_role_in_exception')) { $permStr = implode(', ', $roles); $message = 'User does not have the right roles. Necessary roles are '.$permStr; } From 6a4747d79b2118581a109d8082db09ebadbff5fe Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 21 Oct 2022 00:46:41 -0400 Subject: [PATCH 176/648] Revert "Avoid calling the config helper in the role/perm model constructor" --- src/Models/Permission.php | 2 ++ src/Models/Role.php | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/Models/Permission.php b/src/Models/Permission.php index 066a37840..1a044e694 100644 --- a/src/Models/Permission.php +++ b/src/Models/Permission.php @@ -22,6 +22,8 @@ class Permission extends Model implements PermissionContract public function __construct(array $attributes = []) { + $attributes['guard_name'] = $attributes['guard_name'] ?? config('auth.defaults.guard'); + parent::__construct($attributes); $this->guarded[] = $this->primaryKey; diff --git a/src/Models/Role.php b/src/Models/Role.php index 84f913618..bb4173d82 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -22,6 +22,8 @@ class Role extends Model implements RoleContract public function __construct(array $attributes = []) { + $attributes['guard_name'] = $attributes['guard_name'] ?? config('auth.defaults.guard'); + parent::__construct($attributes); $this->guarded[] = $this->primaryKey; From 87850d72185278b65a33b58b6016dd3564d3a583 Mon Sep 17 00:00:00 2001 From: drbyte Date: Fri, 21 Oct 2022 04:50:01 +0000 Subject: [PATCH 177/648] Update CHANGELOG --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 100c68091..4e21a691d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ All notable changes to `laravel-permission` will be documented in this file +## 5.5.13 - 2022-10-21 + +### What's Changed + +- fix UnauthorizedException: Wrong configuration was used in forRoles by @Sy-Dante in https://github.com/spatie/laravel-permission/pull/2224 + +### New Contributors + +- @Sy-Dante made their first contribution in https://github.com/spatie/laravel-permission/pull/2224 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/5.5.12...5.5.13 + ## 5.5.12 - 2022-10-19 Fix regression introduced in `5.5.10` @@ -477,6 +489,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` 1. Also this is a good time to point out that now with v2.25.0 and v2.26.0 most permission-cache-reset scenarios may no longer be needed in your app, so it's worth reviewing those cases, as you may gain some app speed improvement by removing unnecessary cache resets. @@ -538,6 +551,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` ## 2.19.1 - 2018-09-14 From dc9c48cbff08dcc13fbe3b07d81fa6f0be3de6e3 Mon Sep 17 00:00:00 2001 From: drbyte Date: Fri, 21 Oct 2022 04:50:57 +0000 Subject: [PATCH 178/648] Update CHANGELOG --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e21a691d..acdc7ed3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ All notable changes to `laravel-permission` will be documented in this file +## 5.5.14 - 2022-10-21 + +FIXED BREAKING CHANGE. (Sorry about that!) + +### What's Changed + +- Revert "Avoid calling the config helper in the role/perm model constructor" by @drbyte in https://github.com/spatie/laravel-permission/pull/2225 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/5.5.13...5.5.14 + ## 5.5.13 - 2022-10-21 ### What's Changed @@ -490,6 +500,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` 1. Also this is a good time to point out that now with v2.25.0 and v2.26.0 most permission-cache-reset scenarios may no longer be needed in your app, so it's worth reviewing those cases, as you may gain some app speed improvement by removing unnecessary cache resets. @@ -552,6 +563,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` ## 2.19.1 - 2018-09-14 From 446c39f0658f6f0606eaf2dcb4f51b79aafdd913 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Fri, 21 Oct 2022 11:52:37 -0500 Subject: [PATCH 179/648] Add tests for display roles/permissions on UnauthorizedException --- src/Exceptions/UnauthorizedException.php | 9 ++--- tests/PermissionMiddlewareTest.php | 23 +++++++++++++ tests/RoleMiddlewareTest.php | 23 +++++++++++++ tests/RoleOrPermissionMiddlewareTest.php | 42 ++++++++++++++++++++++++ 4 files changed, 91 insertions(+), 6 deletions(-) diff --git a/src/Exceptions/UnauthorizedException.php b/src/Exceptions/UnauthorizedException.php index da6ab32b3..2a270faf8 100644 --- a/src/Exceptions/UnauthorizedException.php +++ b/src/Exceptions/UnauthorizedException.php @@ -15,8 +15,7 @@ public static function forRoles(array $roles): self $message = 'User does not have the right roles.'; if (config('permission.display_role_in_exception')) { - $permStr = implode(', ', $roles); - $message = 'User does not have the right roles. Necessary roles are '.$permStr; + $message .= ' Necessary roles are '.implode(', ', $roles); } $exception = new static(403, $message, null, []); @@ -30,8 +29,7 @@ public static function forPermissions(array $permissions): self $message = 'User does not have the right permissions.'; if (config('permission.display_permission_in_exception')) { - $permStr = implode(', ', $permissions); - $message = 'User does not have the right permissions. Necessary permissions are '.$permStr; + $message .= ' Necessary permissions are '.implode(', ', $permissions); } $exception = new static(403, $message, null, []); @@ -45,8 +43,7 @@ public static function forRolesOrPermissions(array $rolesOrPermissions): self $message = 'User does not have any of the necessary access rights.'; if (config('permission.display_permission_in_exception') && config('permission.display_role_in_exception')) { - $permStr = implode(', ', $rolesOrPermissions); - $message = 'User does not have the right permissions. Necessary permissions are '.$permStr; + $message .= ' Necessary roles or permissions are '.implode(', ', $rolesOrPermissions); } $exception = new static(403, $message, null, []); diff --git a/tests/PermissionMiddlewareTest.php b/tests/PermissionMiddlewareTest.php index fb49073d4..8d294fd48 100644 --- a/tests/PermissionMiddlewareTest.php +++ b/tests/PermissionMiddlewareTest.php @@ -5,6 +5,7 @@ use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Config; use InvalidArgumentException; use Spatie\Permission\Contracts\Permission; use Spatie\Permission\Exceptions\UnauthorizedException; @@ -147,6 +148,7 @@ public function the_required_permissions_can_be_fetched_from_the_exception() { Auth::login($this->testUser); + $message = null; $requiredPermissions = []; try { @@ -154,12 +156,33 @@ public function the_required_permissions_can_be_fetched_from_the_exception() return (new Response())->setContent(''); }, 'some-permission'); } catch (UnauthorizedException $e) { + $message = $e->getMessage(); $requiredPermissions = $e->getRequiredPermissions(); } + $this->assertEquals('User does not have the right permissions.', $message); $this->assertEquals(['some-permission'], $requiredPermissions); } + /** @test */ + public function the_required_permissions_can_be_displayed_in_the_exception() + { + Auth::login($this->testUser); + Config::set(['permission.display_permission_in_exception' => true]); + + $message = null; + + try { + $this->permissionMiddleware->handle(new Request(), function () { + return (new Response())->setContent(''); + }, 'some-permission'); + } catch (UnauthorizedException $e) { + $message = $e->getMessage(); + } + + $this->assertStringEndsWith('Necessary permissions are some-permission', $message); + } + /** @test */ public function use_not_existing_custom_guard_in_permission() { diff --git a/tests/RoleMiddlewareTest.php b/tests/RoleMiddlewareTest.php index 70fd27e86..e100af32c 100644 --- a/tests/RoleMiddlewareTest.php +++ b/tests/RoleMiddlewareTest.php @@ -5,6 +5,7 @@ use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Config; use InvalidArgumentException; use Spatie\Permission\Exceptions\UnauthorizedException; use Spatie\Permission\Middlewares\RoleMiddleware; @@ -113,6 +114,7 @@ public function the_required_roles_can_be_fetched_from_the_exception() { Auth::login($this->testUser); + $message = null; $requiredRoles = []; try { @@ -120,12 +122,33 @@ public function the_required_roles_can_be_fetched_from_the_exception() return (new Response())->setContent(''); }, 'some-role'); } catch (UnauthorizedException $e) { + $message = $e->getMessage(); $requiredRoles = $e->getRequiredRoles(); } + $this->assertEquals('User does not have the right roles.', $message); $this->assertEquals(['some-role'], $requiredRoles); } + /** @test */ + public function the_required_roles_can_be_displayed_in_the_exception() + { + Auth::login($this->testUser); + Config::set(['permission.display_role_in_exception' => true]); + + $message = null; + + try { + $this->roleMiddleware->handle(new Request(), function () { + return (new Response())->setContent(''); + }, 'some-role'); + } catch (UnauthorizedException $e) { + $message = $e->getMessage(); + } + + $this->assertStringEndsWith('Necessary roles are some-role', $message); + } + /** @test */ public function use_not_existing_custom_guard_in_role() { diff --git a/tests/RoleOrPermissionMiddlewareTest.php b/tests/RoleOrPermissionMiddlewareTest.php index 00e81b290..345f0209a 100644 --- a/tests/RoleOrPermissionMiddlewareTest.php +++ b/tests/RoleOrPermissionMiddlewareTest.php @@ -5,6 +5,7 @@ use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Config; use InvalidArgumentException; use Spatie\Permission\Exceptions\UnauthorizedException; use Spatie\Permission\Middlewares\RoleOrPermissionMiddleware; @@ -123,6 +124,47 @@ public function user_can_access_permission_or_role_with_guard_admin_while_login_ ); } + /** @test */ + public function the_required_permissions_or_roles_can_be_fetched_from_the_exception() + { + Auth::login($this->testUser); + + $message = null; + $requiredRolesOrPermissions = []; + + try { + $this->roleOrPermissionMiddleware->handle(new Request(), function () { + return (new Response())->setContent(''); + }, 'some-permission|some-role'); + } catch (UnauthorizedException $e) { + $message = $e->getMessage(); + $requiredRolesOrPermissions = $e->getRequiredPermissions(); + } + + $this->assertEquals('User does not have any of the necessary access rights.', $message); + $this->assertEquals(['some-permission', 'some-role'], $requiredRolesOrPermissions); + } + + /** @test */ + public function the_required_permissions_or_roles_can_be_displayed_in_the_exception() + { + Auth::login($this->testUser); + Config::set(['permission.display_permission_in_exception' => true]); + Config::set(['permission.display_role_in_exception' => true]); + + $message = null; + + try { + $this->roleOrPermissionMiddleware->handle(new Request(), function () { + return (new Response())->setContent(''); + }, 'some-permission|some-role'); + } catch (UnauthorizedException $e) { + $message = $e->getMessage(); + } + + $this->assertStringEndsWith('Necessary roles or permissions are some-permission, some-role', $message); + } + protected function runMiddleware($middleware, $name, $guard = null) { try { From cdf73026b686ad41323c48b5f79812d01c1b6b2a Mon Sep 17 00:00:00 2001 From: Maarten Paauw Date: Thu, 13 Oct 2022 19:15:59 +0200 Subject: [PATCH 180/648] Autocomplete all Blade directives via Laravel Idea plugin --- docs/advanced-usage/phpstorm.md | 43 +++++++++++++++----- ide.json | 72 +++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 10 deletions(-) create mode 100644 ide.json diff --git a/docs/advanced-usage/phpstorm.md b/docs/advanced-usage/phpstorm.md index 205d6a0b3..23e1a7574 100644 --- a/docs/advanced-usage/phpstorm.md +++ b/docs/advanced-usage/phpstorm.md @@ -5,6 +5,9 @@ weight: 8 # Extending PhpStorm +> **Note** +> When using Laravel Idea plugin all directives are automatically added. + You may wish to extend PhpStorm to support Blade Directives of this package. 1. In PhpStorm, open Preferences, and navigate to **Languages and Frameworks -> PHP -> Blade** @@ -16,11 +19,17 @@ You may wish to extend PhpStorm to support Blade Directives of this package. **role** - has parameter = YES -- Prefix: `check() && auth()->user()->hasRole(` -- Suffix: `)); ?>` +- Prefix: `` -- +**elserole** + +- has parameter = YES +- Prefix: `` + **endrole** - has parameter = NO @@ -32,8 +41,8 @@ You may wish to extend PhpStorm to support Blade Directives of this package. **hasrole** - has parameter = YES -- Prefix: `check() && auth()->user()->hasRole(` -- Suffix: `)); ?>` +- Prefix: `` -- @@ -48,8 +57,8 @@ You may wish to extend PhpStorm to support Blade Directives of this package. **hasanyrole** - has parameter = YES -- Prefix: `check() && auth()->user()->hasAnyRole(` -- Suffix: `)); ?>` +- Prefix: `` -- @@ -64,8 +73,8 @@ You may wish to extend PhpStorm to support Blade Directives of this package. **hasallroles** - has parameter = YES -- Prefix: `check() && auth()->user()->hasAllRoles(` -- Suffix: `)); ?>` +- Prefix: `` -- @@ -80,8 +89,8 @@ You may wish to extend PhpStorm to support Blade Directives of this package. **unlessrole** - has parameter = YES -- Prefix: `check() && !auth()->user()->hasRole(` -- Suffix: `)); ?>` +- Prefix: `` -- @@ -92,3 +101,17 @@ You may wish to extend PhpStorm to support Blade Directives of this package. - Suffix: blank -- + +**hasexactroles** + +- has parameter = YES +- Prefix: `` + +-- + +**endhasexactroles** + +- has parameter = NO +- Prefix: blank +- Suffix: blank diff --git a/ide.json b/ide.json new file mode 100644 index 000000000..7afcd2a45 --- /dev/null +++ b/ide.json @@ -0,0 +1,72 @@ +{ + "$schema": "https://laravel-ide.com/schema/laravel-ide-v2.json", + "blade": { + "directives": [ + { + "name": "role", + "prefix": "" + }, + { + "name": "elserole", + "prefix": "" + }, + { + "name": "endrole", + "prefix": "", + "suffix": "" + }, + { + "name": "hasrole", + "prefix": "" + }, + { + "name": "endhasrole", + "prefix": "", + "suffix": "" + }, + { + "name": "hasanyrole", + "prefix": "" + }, + { + "name": "endhasanyrole", + "prefix": "", + "suffix": "" + }, + { + "name": "hasallroles", + "prefix": "" + }, + { + "name": "endhasallroles", + "prefix": "", + "suffix": "" + }, + { + "name": "unlessrole", + "prefix": "" + }, + { + "name": "endunlessrole", + "prefix": "", + "suffix": "" + }, + { + "name": "hasexactroles", + "prefix": "" + }, + { + "name": "endhasexactroles", + "prefix": "", + "suffix": "" + } + ] + } +} From 074382c9639e06c4722f5a180a99a584d281a22f Mon Sep 17 00:00:00 2001 From: drbyte Date: Sun, 23 Oct 2022 02:35:44 +0000 Subject: [PATCH 181/648] Update CHANGELOG --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index acdc7ed3c..58b36b5ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,17 @@ All notable changes to `laravel-permission` will be documented in this file +## 5.5.15 - 2022-10-23 + +Autocomplete all Blade directives via Laravel Idea plugin + +### What's Changed + +- Autocomplete all Blade directives via Laravel Idea plugin by @maartenpaauw in https://github.com/spatie/laravel-permission/pull/2210 +- Add tests for display roles/permissions on UnauthorizedException by @erikn69 in https://github.com/spatie/laravel-permission/pull/2228 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/5.5.14...5.5.15 + ## 5.5.14 - 2022-10-21 FIXED BREAKING CHANGE. (Sorry about that!) @@ -501,6 +512,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` 1. Also this is a good time to point out that now with v2.25.0 and v2.26.0 most permission-cache-reset scenarios may no longer be needed in your app, so it's worth reviewing those cases, as you may gain some app speed improvement by removing unnecessary cache resets. @@ -564,6 +576,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` ## 2.19.1 - 2018-09-14 From e57a67b51a02cbfcd074ed68786897db1dbbf2fa Mon Sep 17 00:00:00 2001 From: Subhan Shamsoddini Date: Sun, 23 Oct 2022 06:19:28 +0330 Subject: [PATCH 182/648] optimize `for` loop in WildcardPermission (#2113) --- src/WildcardPermission.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/WildcardPermission.php b/src/WildcardPermission.php index 884b1c17c..f3bf631bd 100644 --- a/src/WildcardPermission.php +++ b/src/WildcardPermission.php @@ -47,8 +47,9 @@ public function implies($permission): bool $otherParts = $permission->getParts(); $i = 0; + $partsCount = $this->getParts()->count(); foreach ($otherParts as $otherPart) { - if ($this->getParts()->count() - 1 < $i) { + if ($partsCount - 1 < $i) { return true; } @@ -60,7 +61,7 @@ public function implies($permission): bool $i++; } - for ($i; $i < $this->parts->count(); $i++) { + for ($i; $i < $partsCount; $i++) { if (! $this->parts->get($i)->contains(static::WILDCARD_TOKEN)) { return false; } From e5d989cf4fb93e8b7b431ede417b96d6f776779e Mon Sep 17 00:00:00 2001 From: drbyte Date: Sun, 23 Oct 2022 02:49:55 +0000 Subject: [PATCH 183/648] Fix styling --- src/WildcardPermission.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/WildcardPermission.php b/src/WildcardPermission.php index f3bf631bd..73797a854 100644 --- a/src/WildcardPermission.php +++ b/src/WildcardPermission.php @@ -47,7 +47,7 @@ public function implies($permission): bool $otherParts = $permission->getParts(); $i = 0; - $partsCount = $this->getParts()->count(); + $partsCount = $this->getParts()->count(); foreach ($otherParts as $otherPart) { if ($partsCount - 1 < $i) { return true; From 15b3e2c7c8c6ed508ed873dc91173ae6a9ab985b Mon Sep 17 00:00:00 2001 From: drbyte Date: Sun, 23 Oct 2022 02:50:10 +0000 Subject: [PATCH 184/648] Update CHANGELOG --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58b36b5ea..d1c3f597d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ All notable changes to `laravel-permission` will be documented in this file +## 5.5.16 - 2022-10-23 + +### What's Changed + +- optimize `for` loop in WildcardPermission by @SubhanSh in https://github.com/spatie/laravel-permission/pull/2113 + +### New Contributors + +- @SubhanSh made their first contribution in https://github.com/spatie/laravel-permission/pull/2113 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/5.5.15...5.5.16 + ## 5.5.15 - 2022-10-23 Autocomplete all Blade directives via Laravel Idea plugin @@ -513,6 +525,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` 1. Also this is a good time to point out that now with v2.25.0 and v2.26.0 most permission-cache-reset scenarios may no longer be needed in your app, so it's worth reviewing those cases, as you may gain some app speed improvement by removing unnecessary cache resets. @@ -577,6 +590,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` ## 2.19.1 - 2018-09-14 From 2ec9befb33d2110d9d354bb65a41b3b962280eb1 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Sat, 22 Oct 2022 22:55:43 -0400 Subject: [PATCH 185/648] Remove stray whitespace --- CHANGELOG.md | 34 ---------------------------------- 1 file changed, 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1c3f597d..44b30be50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -509,23 +509,6 @@ The following changes are not "breaking", but worth making the updates to your a - app()['cache']->forget('spatie.permission.cache'); + $this->app->make(\Spatie\Permission\PermissionRegistrar::class)->forgetCachedPermissions(); - - - - - - - - - - - - - - - - - ``` 1. Also this is a good time to point out that now with v2.25.0 and v2.26.0 most permission-cache-reset scenarios may no longer be needed in your app, so it's worth reviewing those cases, as you may gain some app speed improvement by removing unnecessary cache resets. @@ -574,23 +557,6 @@ The following changes are not "breaking", but worth making the updates to your a // user hasRole 'roleB' but not 'roleA' @endrole - - - - - - - - - - - - - - - - - ``` ## 2.19.1 - 2018-09-14 From 1176b9f7a1121a7f18cdee7d2b07875c6e83ec59 Mon Sep 17 00:00:00 2001 From: Julio Motol Date: Wed, 26 Oct 2022 23:40:54 +0800 Subject: [PATCH 186/648] =?UTF-8?q?Revert=20"=F0=9F=90=9B=20Override=20`ge?= =?UTF-8?q?tAllPermissions()`=20in=20`Role`"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 4807005e6c299f0e0f8165c18a4c23d0d357d8d4. --- src/Models/Role.php | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/Models/Role.php b/src/Models/Role.php index b26a9af38..bb4173d82 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -4,7 +4,6 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsToMany; -use Illuminate\Support\Collection; use Spatie\Permission\Contracts\Role as RoleContract; use Spatie\Permission\Exceptions\GuardDoesNotMatch; use Spatie\Permission\Exceptions\RoleAlreadyExists; @@ -196,12 +195,4 @@ public function hasPermissionTo($permission): bool return $this->permissions->contains($permission->getKeyName(), $permission->getKey()); } - - /** - * Return all the permissions the model has, both directly and via roles. - */ - public function getAllPermissions(): Collection - { - return $this->permissions->sort()->values(); - } } From 4835ff7941c1f918eba95bed1ddc98fa6f1c6628 Mon Sep 17 00:00:00 2001 From: Julio Motol Date: Wed, 26 Oct 2022 23:41:57 +0800 Subject: [PATCH 187/648] =?UTF-8?q?=F0=9F=90=9B=20Check=20for=20role=20rel?= =?UTF-8?q?ationship=20method=20before=20merging=20permissions=20from=20ro?= =?UTF-8?q?les?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julio Motol --- src/Traits/HasPermissions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index 7e89ec7a2..69ccccd9d 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -310,7 +310,7 @@ public function getAllPermissions(): Collection /** @var Collection $permissions */ $permissions = $this->permissions; - if ($this->roles) { + if (method_exists($this, 'roles')) { $permissions = $permissions->merge($this->getPermissionsViaRoles()); } From c19742a031ccbaab2eae0363270b944ea26e17ad Mon Sep 17 00:00:00 2001 From: Ching Cheng Kang Date: Sat, 5 Nov 2022 15:41:49 +0800 Subject: [PATCH 188/648] Fix broken Link that link to freek's blog post --- docs/basic-usage/super-admin.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/basic-usage/super-admin.md b/docs/basic-usage/super-admin.md index d48ad254f..426bbc036 100644 --- a/docs/basic-usage/super-admin.md +++ b/docs/basic-usage/super-admin.md @@ -38,7 +38,7 @@ Jeffrey Way explains the concept of a super-admin (and a model owner, and model Alternatively you might want to move the Super Admin check to the `Gate::after` phase instead, particularly if your Super Admin shouldn't be allowed to do things your app doesn't want "anyone" to do, such as writing more than 1 review, or bypassing unsubscribe rules, etc. -The following code snippet is inspired from [Freek's blog article](https://murze.be/when-to-use-gateafter-in-laravel) where this topic is discussed further. +The following code snippet is inspired from [Freek's blog article](https://freek.dev/1325-when-to-use-gateafter-in-laravel) where this topic is discussed further. ```php // somewhere in a service provider From 61692e9a7e5f8427ed03a9f40d9f76a0a02b5b0f Mon Sep 17 00:00:00 2001 From: Jorin Vermeulen <4212335+xorinzor@users.noreply.github.com> Date: Sun, 6 Nov 2022 11:21:14 +0100 Subject: [PATCH 189/648] Update role-permissions.md the `syncPermissions` method on the `Role` object was missing from this page --- docs/basic-usage/role-permissions.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/basic-usage/role-permissions.md b/docs/basic-usage/role-permissions.md index 1a24fde8c..476610957 100644 --- a/docs/basic-usage/role-permissions.md +++ b/docs/basic-usage/role-permissions.md @@ -84,6 +84,12 @@ A permission can be revoked from a role: $role->revokePermissionTo('edit articles'); ``` +Or revoke & add new permissions in one go: + +```php +$role->syncPermissions(['edit articles', 'delete articles']); +``` + The `givePermissionTo` and `revokePermissionTo` functions can accept a string or a `Spatie\Permission\Models\Permission` object. From a1e4463d7a074565182915f472b10445d18c93e2 Mon Sep 17 00:00:00 2001 From: Mohammad ALTAWEEL Date: Fri, 18 Nov 2022 07:45:37 +0800 Subject: [PATCH 190/648] Don't throw an exception when checking permission --- src/Traits/HasPermissions.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index 7e89ec7a2..d55450bfc 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -249,14 +249,13 @@ public function hasAnyPermission(...$permissions): bool * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection ...$permissions * * @return bool - * @throws \Exception */ public function hasAllPermissions(...$permissions): bool { $permissions = collect($permissions)->flatten(); foreach ($permissions as $permission) { - if (! $this->hasPermissionTo($permission)) { + if (! $this->checkPermissionTo($permission)) { return false; } } @@ -486,6 +485,7 @@ public function forgetCachedPermissions() /** * Check if the model has All of the requested Direct permissions. + * * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection ...$permissions * @return bool */ @@ -504,6 +504,7 @@ public function hasAllDirectPermissions(...$permissions): bool /** * Check if the model has Any of the requested Direct permissions. + * * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection ...$permissions * @return bool */ From baa6d35a5d64f522e39f4652c00cd53c4219d3fa Mon Sep 17 00:00:00 2001 From: drbyte Date: Sat, 19 Nov 2022 02:10:48 +0000 Subject: [PATCH 191/648] Update CHANGELOG --- CHANGELOG.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44b30be50..d5ee15a1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,25 @@ All notable changes to `laravel-permission` will be documented in this file +## 5.6.0 - 2022-11-19 + +### What's Changed + +- No longer throws an exception when checking `hasAllPermissions()` if the permission name does not exist by @mtawil in https://github.com/spatie/laravel-permission/pull/2248 + +### Doc Updates + +- [Docs] Add syncPermissions() in role-permissions.md by @xorinzor in https://github.com/spatie/laravel-permission/pull/2235 +- [Docs] Fix broken Link that link to freek's blog post by @chengkangzai in https://github.com/spatie/laravel-permission/pull/2234 + +### New Contributors + +- @xorinzor made their first contribution in https://github.com/spatie/laravel-permission/pull/2235 +- @chengkangzai made their first contribution in https://github.com/spatie/laravel-permission/pull/2234 +- @mtawil made their first contribution in https://github.com/spatie/laravel-permission/pull/2248 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/5.5.16...5.6.0 + ## 5.5.16 - 2022-10-23 ### What's Changed @@ -509,6 +528,7 @@ The following changes are not "breaking", but worth making the updates to your a - app()['cache']->forget('spatie.permission.cache'); + $this->app->make(\Spatie\Permission\PermissionRegistrar::class)->forgetCachedPermissions(); + ``` 1. Also this is a good time to point out that now with v2.25.0 and v2.26.0 most permission-cache-reset scenarios may no longer be needed in your app, so it's worth reviewing those cases, as you may gain some app speed improvement by removing unnecessary cache resets. @@ -557,6 +577,7 @@ The following changes are not "breaking", but worth making the updates to your a // user hasRole 'roleB' but not 'roleA' @endrole + ``` ## 2.19.1 - 2018-09-14 From 4af34b00bc753aa953f57e1798873fb3a9324c1d Mon Sep 17 00:00:00 2001 From: drbyte Date: Wed, 23 Nov 2022 07:04:54 +0000 Subject: [PATCH 192/648] Update CHANGELOG --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d5ee15a1e..b12a5c516 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ All notable changes to `laravel-permission` will be documented in this file +## 5.7.0 - 2022-11-23 + +### What's Changed + +- [Bugfix] Avoid checking permissions-via-roles on `Role` model (ref `Model::preventAccessingMissingAttributes()`) by @juliomotol in https://github.com/spatie/laravel-permission/pull/2227 + +### New Contributors + +- @juliomotol made their first contribution in https://github.com/spatie/laravel-permission/pull/2227 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/5.6.0...5.7.0 + ## 5.6.0 - 2022-11-19 ### What's Changed @@ -529,6 +541,7 @@ The following changes are not "breaking", but worth making the updates to your a + $this->app->make(\Spatie\Permission\PermissionRegistrar::class)->forgetCachedPermissions(); + ``` 1. Also this is a good time to point out that now with v2.25.0 and v2.26.0 most permission-cache-reset scenarios may no longer be needed in your app, so it's worth reviewing those cases, as you may gain some app speed improvement by removing unnecessary cache resets. @@ -578,6 +591,7 @@ The following changes are not "breaking", but worth making the updates to your a @endrole + ``` ## 2.19.1 - 2018-09-14 From 70d5b0ca8588f6985c2ae585462244d5bddf9b92 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Wed, 23 Nov 2022 10:42:34 -0500 Subject: [PATCH 193/648] Custom wildcard verification support --- src/Contracts/Wildcard.php | 13 +++++ ...ildcardPermissionNotImplementsContract.php | 13 +++++ src/Traits/HasPermissions.php | 30 +++++++++++- src/WildcardPermission.php | 3 +- tests/WildcardHasPermissionsTest.php | 49 +++++++++++++++++++ tests/WildcardPermission.php | 17 +++++++ 6 files changed, 122 insertions(+), 3 deletions(-) create mode 100644 src/Contracts/Wildcard.php create mode 100644 src/Exceptions/WildcardPermissionNotImplementsContract.php create mode 100644 tests/WildcardPermission.php diff --git a/src/Contracts/Wildcard.php b/src/Contracts/Wildcard.php new file mode 100644 index 000000000..073a99dd1 --- /dev/null +++ b/src/Contracts/Wildcard.php @@ -0,0 +1,13 @@ +permissionClass; } + protected function getWildcardClass() + { + if (! is_null($this->wildcardClass)) { + return $this->wildcardClass; + } + + $this->wildcardClass = false; + + if (config('permission.enable_wildcard_permission', false)) { + $this->wildcardClass = config('permission.wildcard_permission', WildcardPermission::class); + + if (! is_subclass_of($this->wildcardClass, Wildcard::class)) { + throw WildcardPermissionNotImplementsContract::create(); + } + } + + return $this->wildcardClass; + } + /** * A model may have multiple direct permissions. */ @@ -158,7 +182,7 @@ public function filterPermission($permission, $guardName = null) */ public function hasPermissionTo($permission, $guardName = null): bool { - if (config('permission.enable_wildcard_permission', false)) { + if ($this->getWildcardClass()) { return $this->hasWildcardPermission($permission, $guardName); } @@ -191,12 +215,14 @@ protected function hasWildcardPermission($permission, $guardName = null): bool throw WildcardPermissionInvalidArgument::create(); } + $WildcardPermissionClass = $this->getWildcardClass(); + foreach ($this->getAllPermissions() as $userPermission) { if ($guardName !== $userPermission->guard_name) { continue; } - $userPermission = new WildcardPermission($userPermission->name); + $userPermission = new $WildcardPermissionClass($userPermission->name); if ($userPermission->implies($permission)) { return true; diff --git a/src/WildcardPermission.php b/src/WildcardPermission.php index 73797a854..3ff0ba81a 100644 --- a/src/WildcardPermission.php +++ b/src/WildcardPermission.php @@ -3,9 +3,10 @@ namespace Spatie\Permission; use Illuminate\Support\Collection; +use Spatie\Permission\Contracts\Wildcard; use Spatie\Permission\Exceptions\WildcardPermissionNotProperlyFormatted; -class WildcardPermission +class WildcardPermission implements Wildcard { /** @var string */ public const WILDCARD_TOKEN = '*'; diff --git a/tests/WildcardHasPermissionsTest.php b/tests/WildcardHasPermissionsTest.php index 5ae2e3c79..3a952c6c7 100644 --- a/tests/WildcardHasPermissionsTest.php +++ b/tests/WildcardHasPermissionsTest.php @@ -52,6 +52,55 @@ public function it_can_check_wildcard_permissions_via_roles() $this->assertFalse($user1->hasPermissionTo('projects.list')); } + /** @test */ + public function it_can_check_custom_wildcard_permission() + { + app('config')->set('permission.enable_wildcard_permission', true); + app('config')->set('permission.wildcard_permission', WildcardPermission::class); + + $user1 = User::create(['email' => 'user1@test.com']); + + $permission1 = Permission::create(['name' => 'articles:edit;view;create']); + $permission2 = Permission::create(['name' => 'news:@']); + $permission3 = Permission::create(['name' => 'posts:@']); + + $user1->givePermissionTo([$permission1, $permission2, $permission3]); + + $this->assertTrue($user1->hasPermissionTo('posts:create')); + $this->assertTrue($user1->hasPermissionTo('posts:create:123')); + $this->assertTrue($user1->hasPermissionTo('posts:@')); + $this->assertTrue($user1->hasPermissionTo('articles:view')); + $this->assertFalse($user1->hasPermissionTo('posts.*')); + $this->assertFalse($user1->hasPermissionTo('articles.view')); + $this->assertFalse($user1->hasPermissionTo('projects:view')); + } + + /** @test */ + public function it_can_check_custom_wildcard_permissions_via_roles() + { + app('config')->set('permission.enable_wildcard_permission', true); + app('config')->set('permission.wildcard_permission', WildcardPermission::class); + + $user1 = User::create(['email' => 'user1@test.com']); + + $user1->assignRole('testRole'); + + $permission1 = Permission::create(['name' => 'articles;projects:edit;view;create']); + $permission2 = Permission::create(['name' => 'news:@:456']); + $permission3 = Permission::create(['name' => 'posts']); + + $this->testUserRole->givePermissionTo([$permission1, $permission2, $permission3]); + + $this->assertTrue($user1->hasPermissionTo('posts:create')); + $this->assertTrue($user1->hasPermissionTo('news:create:456')); + $this->assertTrue($user1->hasPermissionTo('projects:create')); + $this->assertTrue($user1->hasPermissionTo('articles:view')); + $this->assertFalse($user1->hasPermissionTo('news.create.456')); + $this->assertFalse($user1->hasPermissionTo('projects.create')); + $this->assertFalse($user1->hasPermissionTo('articles:list')); + $this->assertFalse($user1->hasPermissionTo('projects:list')); + } + /** @test */ public function it_can_check_non_wildcard_permissions() { diff --git a/tests/WildcardPermission.php b/tests/WildcardPermission.php new file mode 100644 index 000000000..9b1dfe0d6 --- /dev/null +++ b/tests/WildcardPermission.php @@ -0,0 +1,17 @@ + Date: Wed, 23 Nov 2022 21:04:45 +0330 Subject: [PATCH 194/648] Name changed. Name changed. --- docs/advanced-usage/ui-options.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/advanced-usage/ui-options.md b/docs/advanced-usage/ui-options.md index e8029ff22..86151cfed 100644 --- a/docs/advanced-usage/ui-options.md +++ b/docs/advanced-usage/ui-options.md @@ -20,4 +20,4 @@ The package doesn't come with any screens out of the box, you should build that - [Generating UI boilerplate using InfyOm](https://youtu.be/hlGu2pa1bdU) video tutorial by [Shailesh](https://github.com/shailesh-ladumor) -- [LiveWire Base Admin Panel](https://github.com/alighasemzadeh/bap) User management by [AliGhasemzadeh](https://github.com/alighasemzadeh) +- [LiveWire Base Admin Panel](https://github.com/aliqasemzadeh/bap) User management by [AliQasemzadeh](https://github.com/aliqasemzadeh) From f2fc5a8fe848aff3e945b32c8f00a7529ff2eead Mon Sep 17 00:00:00 2001 From: Patrick Organ Date: Mon, 5 Dec 2022 04:37:00 -0500 Subject: [PATCH 195/648] Normalize composer.json (#2259) * wip * wip --- composer.json | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/composer.json b/composer.json index c1a09b24d..d4f66ca5c 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,7 @@ { "name": "spatie/laravel-permission", "description": "Permission handling for Laravel 6.0 and up", + "license": "MIT", "keywords": [ "spatie", "laravel", @@ -11,8 +12,6 @@ "rbac", "security" ], - "homepage": "https://github.com/spatie/laravel-permission", - "license": "MIT", "authors": [ { "name": "Freek Van der Herten", @@ -21,8 +20,9 @@ "role": "Developer" } ], + "homepage": "https://github.com/spatie/laravel-permission", "require": { - "php" : "^7.3|^8.0|^8.1", + "php": "^7.3|^8.0|^8.1", "illuminate/auth": "^7.0|^8.0|^9.0", "illuminate/container": "^7.0|^8.0|^9.0", "illuminate/contracts": "^7.0|^8.0|^9.0", @@ -33,6 +33,8 @@ "phpunit/phpunit": "^9.4", "predis/predis": "^1.1" }, + "minimum-stability": "dev", + "prefer-stable": true, "autoload": { "psr-4": { "Spatie\\Permission\\": "src" @@ -46,23 +48,21 @@ "Spatie\\Permission\\Test\\": "tests" } }, + "config": { + "sort-packages": true + }, "extra": { + "branch-alias": { + "dev-main": "5.x-dev", + "dev-master": "5.x-dev" + }, "laravel": { "providers": [ "Spatie\\Permission\\PermissionServiceProvider" ] - }, - "branch-alias": { - "dev-main": "5.x-dev", - "dev-master": "5.x-dev" } }, "scripts": { "test": "phpunit" - }, - "config": { - "sort-packages": true - }, - "minimum-stability": "dev", - "prefer-stable": true + } } From 4011bccc8d9f1575a1352bd648a42f3326d129d4 Mon Sep 17 00:00:00 2001 From: Patrick Organ Date: Mon, 5 Dec 2022 04:37:16 -0500 Subject: [PATCH 196/648] Add Dependabot Automation (#2257) * add dependabot configuration * add workflow to auto-merge dependabot PRs --- .github/dependabot.yml | 8 +++++ .github/workflows/dependabot-auto-merge.yml | 40 +++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/dependabot-auto-merge.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..a76dd83f9 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +version: 2 + +updates: + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml new file mode 100644 index 000000000..78e5cd252 --- /dev/null +++ b/.github/workflows/dependabot-auto-merge.yml @@ -0,0 +1,40 @@ +name: dependabot-auto-merge +on: pull_request_target + +permissions: + pull-requests: write + contents: write + +jobs: + dependabot: + runs-on: ubuntu-latest + if: ${{ github.actor == 'dependabot[bot]' }} + steps: + + - name: Dependabot metadata + id: metadata + uses: dependabot/fetch-metadata@v1.3.5 + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" + compat-lookup: true + + - name: Auto-merge Dependabot PRs for semver-minor updates + if: ${{steps.metadata.outputs.update-type == 'version-update:semver-minor'}} + run: gh pr merge --auto --merge "$PR_URL" + env: + PR_URL: ${{github.event.pull_request.html_url}} + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} + + - name: Auto-merge Dependabot PRs for semver-patch updates + if: ${{steps.metadata.outputs.update-type == 'version-update:semver-patch'}} + run: gh pr merge --auto --merge "$PR_URL" + env: + PR_URL: ${{github.event.pull_request.html_url}} + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} + + - name: Auto-merge Dependabot PRs for Action major versions when compatibility is higher than 90% + if: ${{steps.metadata.outputs.package-ecosystem == 'github_actions' && steps.metadata.outputs.update-type == 'version-update:semver-major' && steps.metadata.outputs.compatibility-score >= 90}} + run: gh pr merge --auto --merge "$PR_URL" + env: + PR_URL: ${{github.event.pull_request.html_url}} + GH_TOKEN: ${{secrets.GITHUB_TOKEN}} From 58946d5e28c82a1fb54a465c2819a38e1bbad092 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Dec 2022 09:37:45 +0000 Subject: [PATCH 197/648] Bump actions/checkout from 2 to 3 Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/php-cs-fixer.yml | 2 +- .github/workflows/run-tests-L7.yml | 2 +- .github/workflows/run-tests-L8.yml | 2 +- .github/workflows/update-changelog.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/php-cs-fixer.yml b/.github/workflows/php-cs-fixer.yml index b3c985609..d54c70741 100644 --- a/.github/workflows/php-cs-fixer.yml +++ b/.github/workflows/php-cs-fixer.yml @@ -8,7 +8,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Fix style uses: docker://oskarstark/php-cs-fixer-ga diff --git a/.github/workflows/run-tests-L7.yml b/.github/workflows/run-tests-L7.yml index 4f347b351..6849039e9 100644 --- a/.github/workflows/run-tests-L7.yml +++ b/.github/workflows/run-tests-L7.yml @@ -20,7 +20,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Setup PHP uses: shivammathur/setup-php@v2 diff --git a/.github/workflows/run-tests-L8.yml b/.github/workflows/run-tests-L8.yml index 11c4fcbae..d64ef9545 100644 --- a/.github/workflows/run-tests-L8.yml +++ b/.github/workflows/run-tests-L8.yml @@ -27,7 +27,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Setup PHP uses: shivammathur/setup-php@v2 diff --git a/.github/workflows/update-changelog.yml b/.github/workflows/update-changelog.yml index fa56639f2..b20f3b6fb 100644 --- a/.github/workflows/update-changelog.yml +++ b/.github/workflows/update-changelog.yml @@ -10,7 +10,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: ref: main From 50223e6aa9492a028a4f901b957fe463be1134df Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Dec 2022 09:37:48 +0000 Subject: [PATCH 198/648] Bump stefanzweifel/git-auto-commit-action from 2.3.0 to 4.16.0 Bumps [stefanzweifel/git-auto-commit-action](https://github.com/stefanzweifel/git-auto-commit-action) from 2.3.0 to 4.16.0. - [Release notes](https://github.com/stefanzweifel/git-auto-commit-action/releases) - [Changelog](https://github.com/stefanzweifel/git-auto-commit-action/blob/master/CHANGELOG.md) - [Commits](https://github.com/stefanzweifel/git-auto-commit-action/compare/v2.3.0...v4.16.0) --- updated-dependencies: - dependency-name: stefanzweifel/git-auto-commit-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/php-cs-fixer.yml | 2 +- .github/workflows/update-changelog.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/php-cs-fixer.yml b/.github/workflows/php-cs-fixer.yml index b3c985609..1db1528e5 100644 --- a/.github/workflows/php-cs-fixer.yml +++ b/.github/workflows/php-cs-fixer.yml @@ -21,7 +21,7 @@ jobs: id: extract_branch - name: Commit changes - uses: stefanzweifel/git-auto-commit-action@v2.3.0 + uses: stefanzweifel/git-auto-commit-action@v4.16.0 with: commit_message: Fix styling branch: ${{ steps.extract_branch.outputs.branch }} diff --git a/.github/workflows/update-changelog.yml b/.github/workflows/update-changelog.yml index fa56639f2..63b26c41d 100644 --- a/.github/workflows/update-changelog.yml +++ b/.github/workflows/update-changelog.yml @@ -21,7 +21,7 @@ jobs: release-notes: ${{ github.event.release.body }} - name: Commit updated CHANGELOG - uses: stefanzweifel/git-auto-commit-action@v4 + uses: stefanzweifel/git-auto-commit-action@v4.16.0 with: branch: main commit_message: Update CHANGELOG From 1bd0355baeccb6cf86b8d52885f4f2ab47f3978e Mon Sep 17 00:00:00 2001 From: Patrick Organ Date: Fri, 9 Dec 2022 08:00:42 -0500 Subject: [PATCH 199/648] Add Laravel Pint Support (#2269) * add pint workflow * remove php-cs-fixer workflow * remove php-cs-fixer config * Fix styling --- .../workflows/fix-php-code-style-issues.yml | 24 +++++++ .github/workflows/php-cs-fixer.yml | 29 --------- .php_cs.dist.php | 40 ------------ src/Commands/CreateRole.php | 4 +- src/Commands/Show.php | 2 +- src/Commands/UpgradeForTeams.php | 18 +++--- src/Contracts/Permission.php | 19 +++--- src/Contracts/Role.php | 18 +++--- src/Guard.php | 6 +- src/Models/Permission.php | 27 ++++---- src/Models/Role.php | 18 +++--- src/PermissionRegistrar.php | 14 ++--- src/Traits/HasPermissions.php | 63 ++++++++----------- src/Traits/HasRoles.php | 20 +++--- src/WildcardPermission.php | 10 ++- src/helpers.php | 6 +- tests/CacheTest.php | 3 + tests/HasPermissionsTest.php | 26 ++++---- tests/Permission.php | 4 +- tests/Role.php | 4 +- tests/RuntimeRole.php | 4 +- tests/TestCase.php | 12 ++-- tests/TestHelper.php | 5 +- tests/WildcardMiddlewareTest.php | 2 + 24 files changed, 150 insertions(+), 228 deletions(-) create mode 100644 .github/workflows/fix-php-code-style-issues.yml delete mode 100644 .github/workflows/php-cs-fixer.yml delete mode 100644 .php_cs.dist.php diff --git a/.github/workflows/fix-php-code-style-issues.yml b/.github/workflows/fix-php-code-style-issues.yml new file mode 100644 index 000000000..150750cbb --- /dev/null +++ b/.github/workflows/fix-php-code-style-issues.yml @@ -0,0 +1,24 @@ +name: Fix PHP code style issues + +on: + push: + paths: + - '**.php' + +jobs: + php-code-styling: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + with: + ref: ${{ github.head_ref }} + + - name: Fix PHP code style issues + uses: aglipanci/laravel-pint-action@1.0.0 + + - name: Commit changes + uses: stefanzweifel/git-auto-commit-action@v4 + with: + commit_message: Fix styling diff --git a/.github/workflows/php-cs-fixer.yml b/.github/workflows/php-cs-fixer.yml deleted file mode 100644 index dc1e82abf..000000000 --- a/.github/workflows/php-cs-fixer.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Check & fix styling - -on: [push] - -jobs: - style: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Fix style - uses: docker://oskarstark/php-cs-fixer-ga - with: - args: --config=.php_cs.dist.php --allow-risky=yes - - - name: Extract branch name - shell: bash - run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})" - id: extract_branch - - - name: Commit changes - uses: stefanzweifel/git-auto-commit-action@v4.16.0 - with: - commit_message: Fix styling - branch: ${{ steps.extract_branch.outputs.branch }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.php_cs.dist.php b/.php_cs.dist.php deleted file mode 100644 index e89f88628..000000000 --- a/.php_cs.dist.php +++ /dev/null @@ -1,40 +0,0 @@ -in([ - __DIR__ . '/src', - __DIR__ . '/tests', - ]) - ->name('*.php') - ->notName('*.blade.php') - ->ignoreDotFiles(true) - ->ignoreVCS(true); - -return (new PhpCsFixer\Config()) - ->setRules([ - '@PSR12' => true, - 'array_syntax' => ['syntax' => 'short'], - 'ordered_imports' => ['sort_algorithm' => 'alpha'], - 'no_unused_imports' => true, - 'not_operator_with_successor_space' => true, - 'trailing_comma_in_multiline' => true, - 'phpdoc_scalar' => true, - 'unary_operator_spaces' => true, - 'binary_operator_spaces' => true, - 'blank_line_before_statement' => [ - 'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try'], - ], - 'phpdoc_single_line_var_spacing' => true, - 'phpdoc_var_without_name' => true, - 'class_attributes_separation' => [ - 'elements' => [ - 'method' => 'one', - ], - ], - 'method_argument_space' => [ - 'on_multiline' => 'ensure_fully_multiline', - 'keep_multiple_spaces_after_comma' => true, - ], - 'single_trait_insert_per_statement' => true, - ]) - ->setFinder($finder); diff --git a/src/Commands/CreateRole.php b/src/Commands/CreateRole.php index e4701f014..f84385b09 100644 --- a/src/Commands/CreateRole.php +++ b/src/Commands/CreateRole.php @@ -25,7 +25,7 @@ public function handle() setPermissionsTeamId($this->option('team-id') ?: null); if (! PermissionRegistrar::$teams && $this->option('team-id')) { - $this->warn("Teams feature disabled, argument --team-id has no effect. Either enable it in permissions config file or remove --team-id parameter"); + $this->warn('Teams feature disabled, argument --team-id has no effect. Either enable it in permissions config file or remove --team-id parameter'); return; } @@ -44,7 +44,7 @@ public function handle() } /** - * @param array|null|string $string + * @param array|null|string $string */ protected function makePermissions($string = null) { diff --git a/src/Commands/Show.php b/src/Commands/Show.php index fbd61967a..bcbe07381 100644 --- a/src/Commands/Show.php +++ b/src/Commands/Show.php @@ -40,7 +40,7 @@ public function handle() $q->orderBy($team_key); }) ->orderBy('name')->get()->mapWithKeys(function ($role) use ($team_key) { - return [$role->name.'_'.($role->$team_key ?: '') => ['permissions' => $role->permissions->pluck('id'), $team_key => $role->$team_key ]]; + return [$role->name.'_'.($role->$team_key ?: '') => ['permissions' => $role->permissions->pluck('id'), $team_key => $role->$team_key]]; }); $permissions = $permissionClass::whereGuardName($guard)->orderBy('name')->pluck('name', 'id'); diff --git a/src/Commands/UpgradeForTeams.php b/src/Commands/UpgradeForTeams.php index 9923ca19d..210ca4407 100644 --- a/src/Commands/UpgradeForTeams.php +++ b/src/Commands/UpgradeForTeams.php @@ -23,7 +23,7 @@ public function handle() } $this->line(''); - $this->info("The teams feature setup is going to add a migration and a model"); + $this->info('The teams feature setup is going to add a migration and a model'); $existingMigrations = $this->alreadyExistingMigrations(); @@ -35,20 +35,20 @@ public function handle() $this->line(''); - if (! $this->confirm("Proceed with the migration creation?", "yes")) { + if (! $this->confirm('Proceed with the migration creation?', 'yes')) { return; } $this->line(''); - $this->line("Creating migration"); + $this->line('Creating migration'); if ($this->createMigration()) { - $this->info("Migration created successfully."); + $this->info('Migration created successfully.'); } else { $this->error( "Couldn't create migration.\n". - "Check the write permissions within the database/migrations directory." + 'Check the write permissions within the database/migrations directory.' ); } @@ -78,7 +78,7 @@ protected function createMigration() * Build a warning regarding possible duplication * due to already existing migrations. * - * @param array $existingMigrations + * @param array $existingMigrations * @return string */ protected function getExistingMigrationsWarning(array $existingMigrations) @@ -89,8 +89,8 @@ protected function getExistingMigrationsWarning(array $existingMigrations) $base = "Setup teams migration already exists.\nFollowing file was found: "; } - return $base . array_reduce($existingMigrations, function ($carry, $fileName) { - return $carry . "\n - " . $fileName; + return $base.array_reduce($existingMigrations, function ($carry, $fileName) { + return $carry."\n - ".$fileName; }); } @@ -115,7 +115,7 @@ protected function alreadyExistingMigrations() * The date parameter is optional for ability * to provide a custom value or a wildcard. * - * @param string|null $date + * @param string|null $date * @return string */ protected function getMigrationPath($date = null) diff --git a/src/Contracts/Permission.php b/src/Contracts/Permission.php index 344173901..91e0162a4 100644 --- a/src/Contracts/Permission.php +++ b/src/Contracts/Permission.php @@ -16,33 +16,30 @@ public function roles(): BelongsToMany; /** * Find a permission by its name. * - * @param string $name - * @param string|null $guardName + * @param string $name + * @param string|null $guardName + * @return Permission * * @throws \Spatie\Permission\Exceptions\PermissionDoesNotExist - * - * @return Permission */ public static function findByName(string $name, $guardName): self; /** * Find a permission by its id. * - * @param int $id - * @param string|null $guardName + * @param int $id + * @param string|null $guardName + * @return Permission * * @throws \Spatie\Permission\Exceptions\PermissionDoesNotExist - * - * @return Permission */ public static function findById(int $id, $guardName): self; /** * Find or Create a permission by its name and guard name. * - * @param string $name - * @param string|null $guardName - * + * @param string $name + * @param string|null $guardName * @return Permission */ public static function findOrCreate(string $name, $guardName): self; diff --git a/src/Contracts/Role.php b/src/Contracts/Role.php index 1292cdb42..28f3d4308 100644 --- a/src/Contracts/Role.php +++ b/src/Contracts/Role.php @@ -16,9 +16,8 @@ public function permissions(): BelongsToMany; /** * Find a role by its name and guard name. * - * @param string $name - * @param string|null $guardName - * + * @param string $name + * @param string|null $guardName * @return \Spatie\Permission\Contracts\Role * * @throws \Spatie\Permission\Exceptions\RoleDoesNotExist @@ -28,9 +27,8 @@ public static function findByName(string $name, $guardName): self; /** * Find a role by its id and guard name. * - * @param int $id - * @param string|null $guardName - * + * @param int $id + * @param string|null $guardName * @return \Spatie\Permission\Contracts\Role * * @throws \Spatie\Permission\Exceptions\RoleDoesNotExist @@ -40,9 +38,8 @@ public static function findById(int $id, $guardName): self; /** * Find or create a role by its name and guard name. * - * @param string $name - * @param string|null $guardName - * + * @param string $name + * @param string|null $guardName * @return \Spatie\Permission\Contracts\Role */ public static function findOrCreate(string $name, $guardName): self; @@ -50,8 +47,7 @@ public static function findOrCreate(string $name, $guardName): self; /** * Determine if the user may perform the given permission. * - * @param string|\Spatie\Permission\Contracts\Permission $permission - * + * @param string|\Spatie\Permission\Contracts\Permission $permission * @return bool */ public function hasPermissionTo($permission): bool; diff --git a/src/Guard.php b/src/Guard.php index c0630392b..395d4cfec 100644 --- a/src/Guard.php +++ b/src/Guard.php @@ -11,7 +11,7 @@ class Guard * Return a collection of guard names suitable for the $model, * as indicated by the presence of a $guard_name property or a guardName() method on the model. * - * @param string|Model $model model class object or name + * @param string|Model $model model class object or name * @return Collection */ public static function getNames($model): Collection @@ -46,7 +46,7 @@ public static function getNames($model): Collection * - keys() gives just the names of the matched guards * - return collection of guard names * - * @param string $class + * @param string $class * @return Collection */ protected static function getConfigAuthGuards(string $class): Collection @@ -68,7 +68,7 @@ protected static function getConfigAuthGuards(string $class): Collection /** * Lookup a guard name relevant for the $class model and the current user. * - * @param string|Model $class model class object or name + * @param string|Model $class model class object or name * @return string guard name */ public static function getDefaultName($class): string diff --git a/src/Models/Permission.php b/src/Models/Permission.php index 1a044e694..10f9dd174 100644 --- a/src/Models/Permission.php +++ b/src/Models/Permission.php @@ -77,12 +77,11 @@ public function users(): BelongsToMany /** * Find a permission by its name (and optionally guardName). * - * @param string $name - * @param string|null $guardName + * @param string $name + * @param string|null $guardName + * @return \Spatie\Permission\Contracts\Permission * * @throws \Spatie\Permission\Exceptions\PermissionDoesNotExist - * - * @return \Spatie\Permission\Contracts\Permission */ public static function findByName(string $name, $guardName = null): PermissionContract { @@ -98,12 +97,11 @@ public static function findByName(string $name, $guardName = null): PermissionCo /** * Find a permission by its id (and optionally guardName). * - * @param int $id - * @param string|null $guardName + * @param int $id + * @param string|null $guardName + * @return \Spatie\Permission\Contracts\Permission * * @throws \Spatie\Permission\Exceptions\PermissionDoesNotExist - * - * @return \Spatie\Permission\Contracts\Permission */ public static function findById(int $id, $guardName = null): PermissionContract { @@ -120,9 +118,8 @@ public static function findById(int $id, $guardName = null): PermissionContract /** * Find or create permission by its name (and optionally guardName). * - * @param string $name - * @param string|null $guardName - * + * @param string $name + * @param string|null $guardName * @return \Spatie\Permission\Contracts\Permission */ public static function findOrCreate(string $name, $guardName = null): PermissionContract @@ -140,9 +137,8 @@ public static function findOrCreate(string $name, $guardName = null): Permission /** * Get the current cached permissions. * - * @param array $params - * @param bool $onlyOne - * + * @param array $params + * @param bool $onlyOne * @return \Illuminate\Database\Eloquent\Collection */ protected static function getPermissions(array $params = [], bool $onlyOne = false): Collection @@ -155,8 +151,7 @@ protected static function getPermissions(array $params = [], bool $onlyOne = fal /** * Get the current cached first permission. * - * @param array $params - * + * @param array $params * @return \Spatie\Permission\Contracts\Permission */ protected static function getPermission(array $params = []): ?PermissionContract diff --git a/src/Models/Role.php b/src/Models/Role.php index bb4173d82..6628f9165 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -83,9 +83,8 @@ public function users(): BelongsToMany /** * Find a role by its name and guard name. * - * @param string $name - * @param string|null $guardName - * + * @param string $name + * @param string|null $guardName * @return \Spatie\Permission\Contracts\Role|\Spatie\Permission\Models\Role * * @throws \Spatie\Permission\Exceptions\RoleDoesNotExist @@ -106,9 +105,8 @@ public static function findByName(string $name, $guardName = null): RoleContract /** * Find a role by its id (and optionally guardName). * - * @param int $id - * @param string|null $guardName - * + * @param int $id + * @param string|null $guardName * @return \Spatie\Permission\Contracts\Role|\Spatie\Permission\Models\Role */ public static function findById(int $id, $guardName = null): RoleContract @@ -127,9 +125,8 @@ public static function findById(int $id, $guardName = null): RoleContract /** * Find or create role by its name (and optionally guardName). * - * @param string $name - * @param string|null $guardName - * + * @param string $name + * @param string|null $guardName * @return \Spatie\Permission\Contracts\Role|\Spatie\Permission\Models\Role */ public static function findOrCreate(string $name, $guardName = null): RoleContract @@ -167,8 +164,7 @@ protected static function findByParam(array $params = []) /** * Determine if the user may perform the given permission. * - * @param string|Permission $permission - * + * @param string|Permission $permission * @return bool * * @throws \Spatie\Permission\Exceptions\GuardDoesNotMatch diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index e886c4961..ca2bf66eb 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -61,7 +61,7 @@ class PermissionRegistrar /** * PermissionRegistrar constructor. * - * @param \Illuminate\Cache\CacheManager $cacheManager + * @param \Illuminate\Cache\CacheManager $cacheManager */ public function __construct(CacheManager $cacheManager) { @@ -109,7 +109,7 @@ protected function getCacheStoreFromConfig(): Repository /** * Set the team id for teams/groups support, this id is used when querying permissions/roles * - * @param int|string|\Illuminate\Database\Eloquent\Model $id + * @param int|string|\Illuminate\Database\Eloquent\Model $id */ public function setPermissionsTeamId($id) { @@ -120,7 +120,6 @@ public function setPermissionsTeamId($id) } /** - * * @return int|string */ public function getPermissionsTeamId() @@ -199,9 +198,8 @@ private function loadPermissions() /** * Get the permissions based on the passed params. * - * @param array $params - * @param bool $onlyOne - * + * @param array $params + * @param bool $onlyOne * @return \Illuminate\Database\Eloquent\Collection */ public function getPermissions(array $params = [], bool $onlyOne = false): Collection @@ -259,7 +257,7 @@ public function getRoleClass(): Role public function setRoleClass($roleClass) { $this->roleClass = $roleClass; - config()->set('permission.models.role', $roleClass); + config()->set('permission.models.role', $roleClass); app()->bind(Role::class, $roleClass); return $this; @@ -310,7 +308,7 @@ private function aliasModelFields($newKeys = []): void */ private function getSerializedPermissionsForCache() { - $this->except = config('permission.cache.column_names_except', ['created_at','updated_at', 'deleted_at']); + $this->except = config('permission.cache.column_names_except', ['created_at', 'updated_at', 'deleted_at']); $permissions = $this->getPermissionClass()->select()->with('roles')->get() ->map(function ($permission) { diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index 462493b63..c7e641768 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -63,9 +63,8 @@ public function permissions(): BelongsToMany /** * Scope the model query to certain permissions only. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection $permissions - * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection $permissions * @return \Illuminate\Database\Eloquent\Builder */ public function scopePermission(Builder $query, $permissions): Builder @@ -93,9 +92,9 @@ public function scopePermission(Builder $query, $permissions): Builder } /** - * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection $permissions - * + * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection $permissions * @return array + * * @throws \Spatie\Permission\Exceptions\PermissionDoesNotExist */ protected function convertToPermissionModels($permissions): array @@ -117,9 +116,9 @@ protected function convertToPermissionModels($permissions): array /** * Find a permission. * - * @param string|int|\Spatie\Permission\Contracts\Permission $permission - * + * @param string|int|\Spatie\Permission\Contracts\Permission $permission * @return \Spatie\Permission\Contracts\Permission + * * @throws PermissionDoesNotExist */ public function filterPermission($permission, $guardName = null) @@ -150,10 +149,10 @@ public function filterPermission($permission, $guardName = null) /** * Determine if the model may perform the given permission. * - * @param string|int|\Spatie\Permission\Contracts\Permission $permission - * @param string|null $guardName - * + * @param string|int|\Spatie\Permission\Contracts\Permission $permission + * @param string|null $guardName * @return bool + * * @throws PermissionDoesNotExist */ public function hasPermissionTo($permission, $guardName = null): bool @@ -170,9 +169,8 @@ public function hasPermissionTo($permission, $guardName = null): bool /** * Validates a wildcard permission against all permissions of a user. * - * @param string|int|\Spatie\Permission\Contracts\Permission $permission - * @param string|null $guardName - * + * @param string|int|\Spatie\Permission\Contracts\Permission $permission + * @param string|null $guardName * @return bool */ protected function hasWildcardPermission($permission, $guardName = null): bool @@ -209,9 +207,8 @@ protected function hasWildcardPermission($permission, $guardName = null): bool /** * An alias to hasPermissionTo(), but avoids throwing an exception. * - * @param string|int|\Spatie\Permission\Contracts\Permission $permission - * @param string|null $guardName - * + * @param string|int|\Spatie\Permission\Contracts\Permission $permission + * @param string|null $guardName * @return bool */ public function checkPermissionTo($permission, $guardName = null): bool @@ -226,8 +223,7 @@ public function checkPermissionTo($permission, $guardName = null): bool /** * Determine if the model has any of the given permissions. * - * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection ...$permissions - * + * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection ...$permissions * @return bool */ public function hasAnyPermission(...$permissions): bool @@ -246,8 +242,7 @@ public function hasAnyPermission(...$permissions): bool /** * Determine if the model has all of the given permissions. * - * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection ...$permissions - * + * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection ...$permissions * @return bool */ public function hasAllPermissions(...$permissions): bool @@ -266,8 +261,7 @@ public function hasAllPermissions(...$permissions): bool /** * Determine if the model has, via roles, the given permission. * - * @param \Spatie\Permission\Contracts\Permission $permission - * + * @param \Spatie\Permission\Contracts\Permission $permission * @return bool */ protected function hasPermissionViaRole(Permission $permission): bool @@ -278,9 +272,9 @@ protected function hasPermissionViaRole(Permission $permission): bool /** * Determine if the model has the given permission. * - * @param string|int|\Spatie\Permission\Contracts\Permission $permission - * + * @param string|int|\Spatie\Permission\Contracts\Permission $permission * @return bool + * * @throws PermissionDoesNotExist */ public function hasDirectPermission($permission): bool @@ -319,8 +313,7 @@ public function getAllPermissions(): Collection /** * Returns permissions ids as array keys * - * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection $permissions - * + * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection $permissions * @return array */ public function collectPermissions(...$permissions) @@ -349,8 +342,7 @@ public function collectPermissions(...$permissions) /** * Grant the given permission(s) to a role. * - * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection $permissions - * + * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection $permissions * @return $this */ public function givePermissionTo(...$permissions) @@ -386,8 +378,7 @@ function ($object) use ($permissions, $model) { /** * Remove all current permissions and set the given ones. * - * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection $permissions - * + * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection $permissions * @return $this */ public function syncPermissions(...$permissions) @@ -400,8 +391,7 @@ public function syncPermissions(...$permissions) /** * Revoke the given permission(s). * - * @param \Spatie\Permission\Contracts\Permission|\Spatie\Permission\Contracts\Permission[]|string|string[] $permission - * + * @param \Spatie\Permission\Contracts\Permission|\Spatie\Permission\Contracts\Permission[]|string|string[] $permission * @return $this */ public function revokePermissionTo($permission) @@ -423,8 +413,7 @@ public function getPermissionNames(): Collection } /** - * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection $permissions - * + * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection $permissions * @return \Spatie\Permission\Contracts\Permission|\Spatie\Permission\Contracts\Permission[]|\Illuminate\Support\Collection */ protected function getStoredPermission($permissions) @@ -454,7 +443,7 @@ protected function getStoredPermission($permissions) } /** - * @param \Spatie\Permission\Contracts\Permission|\Spatie\Permission\Contracts\Role $roleOrPermission + * @param \Spatie\Permission\Contracts\Permission|\Spatie\Permission\Contracts\Role $roleOrPermission * * @throws \Spatie\Permission\Exceptions\GuardDoesNotMatch */ @@ -486,7 +475,7 @@ public function forgetCachedPermissions() /** * Check if the model has All of the requested Direct permissions. * - * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection ...$permissions + * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection ...$permissions * @return bool */ public function hasAllDirectPermissions(...$permissions): bool @@ -505,7 +494,7 @@ public function hasAllDirectPermissions(...$permissions): bool /** * Check if the model has Any of the requested Direct permissions. * - * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection ...$permissions + * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection ...$permissions * @return bool */ public function hasAnyDirectPermission(...$permissions): bool diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index 6e4d7e5fe..39c02f68d 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -64,10 +64,9 @@ public function roles(): BelongsToMany /** * Scope the model query to certain roles only. * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param string|int|array|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection $roles - * @param string $guard - * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param string|int|array|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection $roles + * @param string $guard * @return \Illuminate\Database\Eloquent\Builder */ public function scopeRole(Builder $query, $roles, $guard = null): Builder @@ -96,8 +95,7 @@ public function scopeRole(Builder $query, $roles, $guard = null): Builder /** * Assign the given role to the model. * - * @param array|string|int|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection ...$roles - * + * @param array|string|int|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection ...$roles * @return $this */ public function assignRole(...$roles) @@ -151,7 +149,7 @@ function ($object) use ($roles, $model) { /** * Revoke the given role from the model. * - * @param string|int|\Spatie\Permission\Contracts\Role $role + * @param string|int|\Spatie\Permission\Contracts\Role $role */ public function removeRole($role) { @@ -170,7 +168,6 @@ public function removeRole($role) * Remove all current roles and set the given ones. * * @param array|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection|string|int ...$roles - * * @return $this */ public function syncRoles(...$roles) @@ -183,8 +180,8 @@ public function syncRoles(...$roles) /** * Determine if the model has (one of) the given role(s). * - * @param string|int|array|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection $roles - * @param string|null $guard + * @param string|int|array|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection $roles + * @param string|null $guard * @return bool */ public function hasRole($roles, string $guard = null): bool @@ -232,8 +229,7 @@ public function hasRole($roles, string $guard = null): bool * * Alias to hasRole() but without Guard controls * - * @param string|int|array|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection $roles - * + * @param string|int|array|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection $roles * @return bool */ public function hasAnyRole(...$roles): bool diff --git a/src/WildcardPermission.php b/src/WildcardPermission.php index 73797a854..ebac8ba40 100644 --- a/src/WildcardPermission.php +++ b/src/WildcardPermission.php @@ -23,7 +23,7 @@ class WildcardPermission protected $parts; /** - * @param string $permission + * @param string $permission */ public function __construct(string $permission) { @@ -34,8 +34,7 @@ public function __construct(string $permission) } /** - * @param string|WildcardPermission $permission - * + * @param string|WildcardPermission $permission * @return bool */ public function implies($permission): bool @@ -71,9 +70,8 @@ public function implies($permission): bool } /** - * @param Collection $part - * @param Collection $otherPart - * + * @param Collection $part + * @param Collection $otherPart * @return bool */ protected function containsAll(Collection $part, Collection $otherPart): bool diff --git a/src/helpers.php b/src/helpers.php index 25116d0ff..ac6fc3699 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -2,8 +2,7 @@ if (! function_exists('getModelForGuard')) { /** - * @param string $guard - * + * @param string $guard * @return string|null */ function getModelForGuard(string $guard) @@ -21,8 +20,7 @@ function getModelForGuard(string $guard) if (! function_exists('setPermissionsTeamId')) { /** - * @param int|string|\Illuminate\Database\Eloquent\Model $id - * + * @param int|string|\Illuminate\Database\Eloquent\Model $id */ function setPermissionsTeamId($id) { diff --git a/tests/CacheTest.php b/tests/CacheTest.php index 62123f792..a44f03a2b 100644 --- a/tests/CacheTest.php +++ b/tests/CacheTest.php @@ -12,8 +12,11 @@ class CacheTest extends TestCase { protected $cache_init_count = 0; + protected $cache_load_count = 0; + protected $cache_run_count = 2; // roles lookup, permissions lookup + protected $cache_relations_count = 1; protected $registrar; diff --git a/tests/HasPermissionsTest.php b/tests/HasPermissionsTest.php index 40151b67e..61bba126b 100644 --- a/tests/HasPermissionsTest.php +++ b/tests/HasPermissionsTest.php @@ -587,35 +587,35 @@ public function it_can_check_if_there_is_any_of_the_direct_permissions_given() public function it_can_check_permission_based_on_logged_in_user_guard() { $this->testUser->givePermissionTo(app(Permission::class)::create([ - 'name' => 'do_that', - 'guard_name' => 'api', - ])); + 'name' => 'do_that', + 'guard_name' => 'api', + ])); $response = $this->actingAs($this->testUser, 'api') ->json('GET', '/check-api-guard-permission'); $response->assertJson([ - 'status' => true, - ]); + 'status' => true, + ]); } /** @test */ public function it_can_reject_permission_based_on_logged_in_user_guard() { $unassignedPermission = app(Permission::class)::create([ - 'name' => 'do_that', - 'guard_name' => 'api', - ]); + 'name' => 'do_that', + 'guard_name' => 'api', + ]); $assignedPermission = app(Permission::class)::create([ - 'name' => 'do_that', - 'guard_name' => 'web', - ]); + 'name' => 'do_that', + 'guard_name' => 'web', + ]); $this->testUser->givePermissionTo($assignedPermission); $response = $this->withExceptionHandling() ->actingAs($this->testUser, 'api') ->json('GET', '/check-api-guard-permission'); $response->assertJson([ - 'status' => false, - ]); + 'status' => false, + ]); } } diff --git a/tests/Permission.php b/tests/Permission.php index d7529ed69..24d4938b8 100644 --- a/tests/Permission.php +++ b/tests/Permission.php @@ -7,7 +7,7 @@ class Permission extends \Spatie\Permission\Models\Permission protected $primaryKey = 'permission_test_id'; protected $visible = [ - 'permission_test_id', - 'name', + 'permission_test_id', + 'name', ]; } diff --git a/tests/Role.php b/tests/Role.php index bbe13ece7..ee2862f82 100644 --- a/tests/Role.php +++ b/tests/Role.php @@ -7,7 +7,7 @@ class Role extends \Spatie\Permission\Models\Role protected $primaryKey = 'role_test_id'; protected $visible = [ - 'role_test_id', - 'name', + 'role_test_id', + 'name', ]; } diff --git a/tests/RuntimeRole.php b/tests/RuntimeRole.php index 82f24a09f..b8cbac7da 100644 --- a/tests/RuntimeRole.php +++ b/tests/RuntimeRole.php @@ -5,7 +5,7 @@ class RuntimeRole extends \Spatie\Permission\Models\Role { protected $visible = [ - 'id', - 'name', + 'id', + 'name', ]; } diff --git a/tests/TestCase.php b/tests/TestCase.php index 8f913c53e..7fd2ebb57 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -40,6 +40,7 @@ abstract class TestCase extends Orchestra protected $hasTeams = false; protected static $migration; + protected static $customMigration; public function setUp(): void @@ -60,8 +61,7 @@ public function setUp(): void } /** - * @param \Illuminate\Foundation\Application $app - * + * @param \Illuminate\Foundation\Application $app * @return array */ protected function getPackageProviders($app) @@ -74,7 +74,7 @@ protected function getPackageProviders($app) /** * Set up the environment. * - * @param \Illuminate\Foundation\Application $app + * @param \Illuminate\Foundation\Application $app */ protected function getEnvironmentSetUp($app) { @@ -112,7 +112,7 @@ protected function getEnvironmentSetUp($app) /** * Set up the database. * - * @param \Illuminate\Foundation\Application $app + * @param \Illuminate\Foundation\Application $app */ protected function setUpDatabase($app) { @@ -200,8 +200,8 @@ public function setUpRoutes(): void { Route::middleware('auth:api')->get('/check-api-guard-permission', function (Request $request) { return [ - 'status' => $request->user()->hasPermissionTo('do_that'), - ]; + 'status' => $request->user()->hasPermissionTo('do_that'), + ]; }); } } diff --git a/tests/TestHelper.php b/tests/TestHelper.php index 2ad3b2a5e..c081d2f3d 100644 --- a/tests/TestHelper.php +++ b/tests/TestHelper.php @@ -9,9 +9,8 @@ class TestHelper { /** - * @param string $middleware - * @param object $parameter - * + * @param string $middleware + * @param object $parameter * @return int */ public function testMiddleware($middleware, $parameter) diff --git a/tests/WildcardMiddlewareTest.php b/tests/WildcardMiddlewareTest.php index 9fe52796d..e354800eb 100644 --- a/tests/WildcardMiddlewareTest.php +++ b/tests/WildcardMiddlewareTest.php @@ -14,7 +14,9 @@ class WildcardMiddlewareTest extends TestCase { protected $roleMiddleware; + protected $permissionMiddleware; + protected $roleOrPermissionMiddleware; public function setUp(): void From 567a199d75240835cca075e1ee836a7b4928db84 Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Thu, 27 Oct 2022 00:05:19 +0200 Subject: [PATCH 200/648] Hint model properties --- src/Models/Permission.php | 7 +++++++ src/Models/Role.php | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/src/Models/Permission.php b/src/Models/Permission.php index 1a044e694..0479b7430 100644 --- a/src/Models/Permission.php +++ b/src/Models/Permission.php @@ -13,6 +13,13 @@ use Spatie\Permission\Traits\HasRoles; use Spatie\Permission\Traits\RefreshesPermissionCache; +/** + * @property int $id + * @property string $name + * @property string $guard_name + * @property ?\Illuminate\Support\Carbon $created_at + * @property ?\Illuminate\Support\Carbon $updated_at + */ class Permission extends Model implements PermissionContract { use HasRoles; diff --git a/src/Models/Role.php b/src/Models/Role.php index bb4173d82..56c884826 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -13,6 +13,13 @@ use Spatie\Permission\Traits\HasPermissions; use Spatie\Permission\Traits\RefreshesPermissionCache; +/** + * @property int $id + * @property string $name + * @property string $guard_name + * @property ?\Illuminate\Support\Carbon $created_at + * @property ?\Illuminate\Support\Carbon $updated_at + */ class Role extends Model implements RoleContract { use HasPermissions; From fd2dfbca918d5c9ea640836ff3675773892969b2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Jan 2023 05:07:12 +0000 Subject: [PATCH 201/648] Bump aglipanci/laravel-pint-action from 1.0.0 to 2.1.0 Bumps [aglipanci/laravel-pint-action](https://github.com/aglipanci/laravel-pint-action) from 1.0.0 to 2.1.0. - [Release notes](https://github.com/aglipanci/laravel-pint-action/releases) - [Commits](https://github.com/aglipanci/laravel-pint-action/compare/1.0.0...2.1.0) --- updated-dependencies: - dependency-name: aglipanci/laravel-pint-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/fix-php-code-style-issues.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/fix-php-code-style-issues.yml b/.github/workflows/fix-php-code-style-issues.yml index 150750cbb..39a77e7b3 100644 --- a/.github/workflows/fix-php-code-style-issues.yml +++ b/.github/workflows/fix-php-code-style-issues.yml @@ -16,7 +16,7 @@ jobs: ref: ${{ github.head_ref }} - name: Fix PHP code style issues - uses: aglipanci/laravel-pint-action@1.0.0 + uses: aglipanci/laravel-pint-action@2.1.0 - name: Commit changes uses: stefanzweifel/git-auto-commit-action@v4 From d67b5c201aa35edceeba7efae433520cc3362aa0 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Thu, 12 Jan 2023 10:40:19 -0500 Subject: [PATCH 202/648] Laravel 10.x Support --- .github/workflows/run-tests-L8.yml | 10 +++++++++- composer.json | 10 +++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/.github/workflows/run-tests-L8.yml b/.github/workflows/run-tests-L8.yml index d64ef9545..a8e9ce842 100644 --- a/.github/workflows/run-tests-L8.yml +++ b/.github/workflows/run-tests-L8.yml @@ -10,14 +10,22 @@ jobs: fail-fast: false matrix: php: [8.2, 8.1, 8.0, 7.4, 7.3] - laravel: [9.*, 8.*] + laravel: [10.*, 9.*, 8.*] dependency-version: [prefer-lowest, prefer-stable] include: + - laravel: 10.* + testbench: 8.* - laravel: 9.* testbench: 7.* - laravel: 8.* testbench: 6.23 exclude: + - laravel: 10.* + php: 8.0 + - laravel: 10.* + php: 7.4 + - laravel: 10.* + php: 7.3 - laravel: 9.* php: 7.4 - laravel: 9.* diff --git a/composer.json b/composer.json index d4f66ca5c..e7d68ab6d 100644 --- a/composer.json +++ b/composer.json @@ -23,13 +23,13 @@ "homepage": "https://github.com/spatie/laravel-permission", "require": { "php": "^7.3|^8.0|^8.1", - "illuminate/auth": "^7.0|^8.0|^9.0", - "illuminate/container": "^7.0|^8.0|^9.0", - "illuminate/contracts": "^7.0|^8.0|^9.0", - "illuminate/database": "^7.0|^8.0|^9.0" + "illuminate/auth": "^7.0|^8.0|^9.0|^10.0", + "illuminate/container": "^7.0|^8.0|^9.0|^10.0", + "illuminate/contracts": "^7.0|^8.0|^9.0|^10.0", + "illuminate/database": "^7.0|^8.0|^9.0|^10.0" }, "require-dev": { - "orchestra/testbench": "^5.0|^6.0|^7.0", + "orchestra/testbench": "^5.0|^6.0|^7.0|^8.0", "phpunit/phpunit": "^9.4", "predis/predis": "^1.1" }, From 392feff3c27ff9347ec91f7b2893395c8260ffb9 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Fri, 13 Jan 2023 17:02:18 -0500 Subject: [PATCH 203/648] Fix tests badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d090b8b24..9a6408e69 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ [![Latest Version on Packagist](https://img.shields.io/packagist/v/spatie/laravel-permission.svg?style=flat-square)](https://packagist.org/packages/spatie/laravel-permission) -![](https://github.com/spatie/laravel-permission/workflows/Run%20Tests/badge.svg?branch=master) +[![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/spatie/laravel-permission/run-tests-L8.yml?branch=main&label=Tests)](https://github.com/spatie/laravel-permission/actions?query=workflow%3ATests+branch%3Amain) [![Total Downloads](https://img.shields.io/packagist/dt/spatie/laravel-permission.svg?style=flat-square)](https://packagist.org/packages/spatie/laravel-permission) ## Documentation, Installation, and Usage Instructions From ad69f772b0d5fe626fa6d780a54f29f934036990 Mon Sep 17 00:00:00 2001 From: drbyte Date: Sat, 14 Jan 2023 05:33:58 +0000 Subject: [PATCH 204/648] Update CHANGELOG --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b12a5c516..5d9188db3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ All notable changes to `laravel-permission` will be documented in this file +## 5.8.0 - 2023-01-14 + +### What's Changed + +- Laravel 10.x Support by @erikn69 in https://github.com/spatie/laravel-permission/pull/2298 + +#### Administrative + +- [Docs] Link updated to match name change of related tool repo by @aliqasemzadeh in https://github.com/spatie/laravel-permission/pull/2253 +- Fix tests badge by @erikn69 in https://github.com/spatie/laravel-permission/pull/2300 +- Add Laravel Pint Support by @patinthehat in https://github.com/spatie/laravel-permission/pull/2269 +- Normalize composer.json by @patinthehat in https://github.com/spatie/laravel-permission/pull/2259 +- Add Dependabot Automation by @patinthehat in https://github.com/spatie/laravel-permission/pull/2257 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/5.7.0...5.8.0 + ## 5.7.0 - 2022-11-23 ### What's Changed @@ -542,6 +558,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` 1. Also this is a good time to point out that now with v2.25.0 and v2.26.0 most permission-cache-reset scenarios may no longer be needed in your app, so it's worth reviewing those cases, as you may gain some app speed improvement by removing unnecessary cache resets. @@ -592,6 +609,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` ## 2.19.1 - 2018-09-14 From a6b22b9b63929b9b533be57a33320190e8a8b9e2 Mon Sep 17 00:00:00 2001 From: Navid Sedehi Date: Sat, 14 Jan 2023 18:36:45 +0330 Subject: [PATCH 205/648] update publish tag name --- src/PermissionServiceProvider.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PermissionServiceProvider.php b/src/PermissionServiceProvider.php index e408064a9..8162b2332 100644 --- a/src/PermissionServiceProvider.php +++ b/src/PermissionServiceProvider.php @@ -54,11 +54,11 @@ protected function offerPublishing() $this->publishes([ __DIR__.'/../config/permission.php' => config_path('permission.php'), - ], 'config'); + ], 'permission-config'); $this->publishes([ __DIR__.'/../database/migrations/create_permission_tables.php.stub' => $this->getMigrationFileName('create_permission_tables.php'), - ], 'migrations'); + ], 'permission-migrations'); } protected function registerCommands() From 97da4c029e4ff34627ab0fac8eb199d44afb9537 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Wed, 18 Jan 2023 01:38:59 -0500 Subject: [PATCH 206/648] [Docs] Clarify meaning of pipe operator in middleware rules Closes #2304 --- docs/basic-usage/middleware.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/docs/basic-usage/middleware.md b/docs/basic-usage/middleware.md index a10aa3f36..5d6ddae88 100644 --- a/docs/basic-usage/middleware.md +++ b/docs/basic-usage/middleware.md @@ -41,16 +41,12 @@ Route::group(['middleware' => ['role:super-admin','permission:publish articles'] // }); -Route::group(['middleware' => ['role_or_permission:super-admin|edit articles']], function () { - // -}); - Route::group(['middleware' => ['role_or_permission:publish articles']], function () { // }); ``` -Alternatively, you can separate multiple roles or permission with a `|` (pipe) character: +You can specify multiple roles or permissions with a `|` (pipe) character, which is treated as `OR`: ```php Route::group(['middleware' => ['role:super-admin|writer']], function () { From 3df2a29b5a8150f77189107272b8db419be59fe9 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Wed, 18 Jan 2023 02:03:25 -0500 Subject: [PATCH 207/648] [Docs] Add Laravel compatibility matrix --- docs/installation-laravel.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/installation-laravel.md b/docs/installation-laravel.md index c37db5d91..40bd0b032 100644 --- a/docs/installation-laravel.md +++ b/docs/installation-laravel.md @@ -3,9 +3,19 @@ title: Installation in Laravel weight: 4 --- +## Laravel Version Compatibility + This package can be used with Laravel 6.0 or higher. -(For Laravel 5.8, use v3.17.0) +Package | Laravel Version +--------|----------- + ^5.8 | 7,8,9,10 + ^5.7 | 7,8,9 +^5.4-^5.6 | 7,8 +5.0-5.3 | 6,7,8 + ^4 | 6,7,8 + ^3 | 5.8 + ## Installing From 2227bb5dae8bd2d51916a6093190c70c10ae4428 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Wed, 18 Jan 2023 02:08:13 -0500 Subject: [PATCH 208/648] [Docs] Tidy installation instructions --- docs/installation-laravel.md | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/docs/installation-laravel.md b/docs/installation-laravel.md index 40bd0b032..642ac5a3c 100644 --- a/docs/installation-laravel.md +++ b/docs/installation-laravel.md @@ -21,9 +21,9 @@ Package | Laravel Version 1. Consult the **Prerequisites** page for important considerations regarding your **User** models! -2. This package publishes a `config/permission.php` file. If you already have a file by that name, you must rename or remove it. +2. This package **publishes a `config/permission.php` file**. If you already have a file by that name, you must rename or remove it. -3. You can install the package via composer: +3. You can **install the package via composer**: composer require spatie/laravel-permission @@ -36,7 +36,7 @@ Package | Laravel Version ]; ``` -5. You should publish [the migration](https://github.com/spatie/laravel-permission/blob/main/database/migrations/create_permission_tables.php.stub) and the [`config/permission.php` config file](https://github.com/spatie/laravel-permission/blob/main/config/permission.php) with: +5. **You should publish** [the migration](https://github.com/spatie/laravel-permission/blob/main/database/migrations/create_permission_tables.php.stub) and the [`config/permission.php` config file](https://github.com/spatie/laravel-permission/blob/main/config/permission.php) with: ``` php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" @@ -45,17 +45,24 @@ Package | Laravel Version 6. NOTE: If you are using UUIDs, see the Advanced section of the docs on UUID steps, before you continue. It explains some changes you may want to make to the migrations and config file before continuing. It also mentions important considerations after extending this package's models for UUID capability. If you are going to use teams feature, you have to update your [`config/permission.php` config file](https://github.com/spatie/laravel-permission/blob/main/config/permission.php) and set `'teams' => true,`, if you want to use a custom foreign key for teams you must change `team_foreign_key`. -7. Clear your config cache. This package requires access to the `permission` config. Generally it's bad practice to do config-caching in a development environment. If you've been caching configurations locally, clear your config cache with either of these commands: +7. **Clear your config cache**. This package requires access to the `permission` config. Generally it's bad practice to do config-caching in a development environment. If you've been caching configurations locally, clear your config cache with either of these commands: php artisan optimize:clear # or php artisan config:clear -8. Run the migrations: After the config and migration have been published and configured, you can create the tables for this package by running: +8. **Run the migrations**: After the config and migration have been published and configured, you can create the tables for this package by running: php artisan migrate -9. Add the necessary trait to your User model: Consult the Basic Usage section of the docs for how to get started using the features of this package. +9. **Add the necessary trait to your User model**: + + // The User model requires this trait + use HasRoles; + + Consult the **Basic Usage** section of the docs to get started using the features of this package. + +. ### Default config file contents From d55b02cde202751e2026eb51b73b8e2ac0b3902d Mon Sep 17 00:00:00 2001 From: parallels999 <109294935+parallels999@users.noreply.github.com> Date: Thu, 19 Jan 2023 14:43:45 -0500 Subject: [PATCH 209/648] Shorten required php version (#2265) --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index e7d68ab6d..e3abef547 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ ], "homepage": "https://github.com/spatie/laravel-permission", "require": { - "php": "^7.3|^8.0|^8.1", + "php": "^7.3|^8.0", "illuminate/auth": "^7.0|^8.0|^9.0|^10.0", "illuminate/container": "^7.0|^8.0|^9.0|^10.0", "illuminate/contracts": "^7.0|^8.0|^9.0|^10.0", From 298736569d2f43cc799aa8267d3c6ade0667f770 Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 18 Jan 2023 15:48:59 +0100 Subject: [PATCH 210/648] fix: Lazily bind dependencies --- src/PermissionServiceProvider.php | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/PermissionServiceProvider.php b/src/PermissionServiceProvider.php index e408064a9..0867b8522 100644 --- a/src/PermissionServiceProvider.php +++ b/src/PermissionServiceProvider.php @@ -13,7 +13,7 @@ class PermissionServiceProvider extends ServiceProvider { - public function boot(PermissionRegistrar $permissionLoader) + public function boot() { $this->offerPublishing(); @@ -23,14 +23,14 @@ public function boot(PermissionRegistrar $permissionLoader) $this->registerModelBindings(); - if ($this->app->config['permission.register_permission_check_method']) { - $permissionLoader->clearClassPermissions(); - $permissionLoader->registerPermissions(); - } - - $this->app->singleton(PermissionRegistrar::class, function ($app) use ($permissionLoader) { - return $permissionLoader; + $this->callAfterResolving(PermissionRegistrar::class, function (PermissionRegistrar $permissionLoader) { + if ($this->app->config['permission.register_permission_check_method']) { + $permissionLoader->clearClassPermissions(); + $permissionLoader->registerPermissions(); + } }); + + $this->app->singleton(PermissionRegistrar::class); } public function register() @@ -74,14 +74,16 @@ protected function registerCommands() protected function registerModelBindings() { - $config = $this->app->config['permission.models']; + $this->app->bind(PermissionContract::class, function ($app) { + $config = $app->config['permission.models']; - if (! $config) { - return; - } + return $app->make($config['permission']); + }); + $this->app->bind(RoleContract::class, function ($app) { + $config = $app->config['permission.models']; - $this->app->bind(PermissionContract::class, $config['permission']); - $this->app->bind(RoleContract::class, $config['role']); + return $app->make($config['role']); + }); } public static function bladeMethodWrapper($method, $role, $guard = null) From 384e06f1bdb34717998e7273ed07ae95fc8296cf Mon Sep 17 00:00:00 2001 From: Freek Van der Herten Date: Mon, 23 Jan 2023 15:26:30 +0100 Subject: [PATCH 211/648] Update README.md --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 9a6408e69..9850ee4ee 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,3 @@ - -[](https://supportukrainenow.org) -

Social Card of Laravel Permission

# Associate users with permissions and roles From c14aced21d0a5542d446a1a21650e8e207962092 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Jan 2023 05:11:21 +0000 Subject: [PATCH 212/648] Bump dependabot/fetch-metadata from 1.3.5 to 1.3.6 Bumps [dependabot/fetch-metadata](https://github.com/dependabot/fetch-metadata) from 1.3.5 to 1.3.6. - [Release notes](https://github.com/dependabot/fetch-metadata/releases) - [Commits](https://github.com/dependabot/fetch-metadata/compare/v1.3.5...v1.3.6) --- updated-dependencies: - dependency-name: dependabot/fetch-metadata dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/dependabot-auto-merge.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml index 78e5cd252..f2e85e7d4 100644 --- a/.github/workflows/dependabot-auto-merge.yml +++ b/.github/workflows/dependabot-auto-merge.yml @@ -13,7 +13,7 @@ jobs: - name: Dependabot metadata id: metadata - uses: dependabot/fetch-metadata@v1.3.5 + uses: dependabot/fetch-metadata@v1.3.6 with: github-token: "${{ secrets.GITHUB_TOKEN }}" compat-lookup: true From 79ae998bad35f92c76bc027679669ab8a2dcb1fe Mon Sep 17 00:00:00 2001 From: Francisco Madeira Date: Fri, 3 Feb 2023 14:30:30 +0000 Subject: [PATCH 213/648] Extract query to `getPermissionsWithRoles` method. --- src/PermissionRegistrar.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index ca2bf66eb..8a7b4ca19 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -273,6 +273,11 @@ public function getCacheStore(): Store return $this->cache->getStore(); } + protected function getPermissionsWithRoles(): Collection + { + return $this->getPermissionClass()->select()->with('roles')->get(); + } + /** * Changes array keys with alias * @@ -310,7 +315,7 @@ private function getSerializedPermissionsForCache() { $this->except = config('permission.cache.column_names_except', ['created_at', 'updated_at', 'deleted_at']); - $permissions = $this->getPermissionClass()->select()->with('roles')->get() + $permissions = $this->getPermissionsWithRoles() ->map(function ($permission) { if (! $this->alias) { $this->aliasModelFields($permission); From 72e78cd07004e6caa5c8ffde5128bddf0c23a12a Mon Sep 17 00:00:00 2001 From: drbyte Date: Mon, 6 Feb 2023 17:08:47 +0000 Subject: [PATCH 214/648] Fix styling --- src/Contracts/Wildcard.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Contracts/Wildcard.php b/src/Contracts/Wildcard.php index 073a99dd1..4e9478bd5 100644 --- a/src/Contracts/Wildcard.php +++ b/src/Contracts/Wildcard.php @@ -5,8 +5,7 @@ interface Wildcard { /** - * @param string|Wildcard $permission - * + * @param string|Wildcard $permission * @return bool */ public function implies($permission): bool; From d5747753a80532b665efd09437004b75407a282b Mon Sep 17 00:00:00 2001 From: drbyte Date: Mon, 6 Feb 2023 17:16:02 +0000 Subject: [PATCH 215/648] Update CHANGELOG --- CHANGELOG.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d9188db3..efb20a419 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,28 @@ All notable changes to `laravel-permission` will be documented in this file +## 5.9.0 - 2023-02-06 + +### What's Changed + +- Add `permission-` prefix to publish tag names by @sedehi in https://github.com/spatie/laravel-permission/pull/2301 +- Fix detaching user models on teams feature #2220 by @erikn69 in https://github.com/spatie/laravel-permission/pull/2221 +- Hint model properties by @AJenbo in https://github.com/spatie/laravel-permission/pull/2230 +- Custom wildcard verification/separators support by @erikn69 in https://github.com/spatie/laravel-permission/pull/2252 +- fix: Lazily bind dependencies by @olivernybroe in https://github.com/spatie/laravel-permission/pull/2309 +- Extract query to `getPermissionsWithRoles` method. by @xiCO2k in https://github.com/spatie/laravel-permission/pull/2316 +- This will allow to extend the PermissionRegistrar class and change the query. + +### New Contributors + +- @sedehi made their first contribution in https://github.com/spatie/laravel-permission/pull/2301 +- @parallels999 made their first contribution in https://github.com/spatie/laravel-permission/pull/2265 +- @AJenbo made their first contribution in https://github.com/spatie/laravel-permission/pull/2230 +- @olivernybroe made their first contribution in https://github.com/spatie/laravel-permission/pull/2309 +- @xiCO2k made their first contribution in https://github.com/spatie/laravel-permission/pull/2316 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/5.8.0...5.9.0 + ## 5.8.0 - 2023-01-14 ### What's Changed @@ -559,6 +581,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` 1. Also this is a good time to point out that now with v2.25.0 and v2.26.0 most permission-cache-reset scenarios may no longer be needed in your app, so it's worth reviewing those cases, as you may gain some app speed improvement by removing unnecessary cache resets. @@ -610,6 +633,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` ## 2.19.1 - 2018-09-14 From 25f906619b03965da122f0c6000ef63a77507816 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Mon, 6 Feb 2023 16:34:04 -0500 Subject: [PATCH 216/648] Revert "fix: Lazily bind dependencies" --- src/PermissionServiceProvider.php | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/PermissionServiceProvider.php b/src/PermissionServiceProvider.php index 15683b66a..8162b2332 100644 --- a/src/PermissionServiceProvider.php +++ b/src/PermissionServiceProvider.php @@ -13,7 +13,7 @@ class PermissionServiceProvider extends ServiceProvider { - public function boot() + public function boot(PermissionRegistrar $permissionLoader) { $this->offerPublishing(); @@ -23,14 +23,14 @@ public function boot() $this->registerModelBindings(); - $this->callAfterResolving(PermissionRegistrar::class, function (PermissionRegistrar $permissionLoader) { - if ($this->app->config['permission.register_permission_check_method']) { - $permissionLoader->clearClassPermissions(); - $permissionLoader->registerPermissions(); - } - }); + if ($this->app->config['permission.register_permission_check_method']) { + $permissionLoader->clearClassPermissions(); + $permissionLoader->registerPermissions(); + } - $this->app->singleton(PermissionRegistrar::class); + $this->app->singleton(PermissionRegistrar::class, function ($app) use ($permissionLoader) { + return $permissionLoader; + }); } public function register() @@ -74,16 +74,14 @@ protected function registerCommands() protected function registerModelBindings() { - $this->app->bind(PermissionContract::class, function ($app) { - $config = $app->config['permission.models']; + $config = $this->app->config['permission.models']; - return $app->make($config['permission']); - }); - $this->app->bind(RoleContract::class, function ($app) { - $config = $app->config['permission.models']; + if (! $config) { + return; + } - return $app->make($config['role']); - }); + $this->app->bind(PermissionContract::class, $config['permission']); + $this->app->bind(RoleContract::class, $config['role']); } public static function bladeMethodWrapper($method, $role, $guard = null) From 6e0f9574d26d4d7ea16d63508db5b0a44424a862 Mon Sep 17 00:00:00 2001 From: drbyte Date: Mon, 6 Feb 2023 21:39:11 +0000 Subject: [PATCH 217/648] Update CHANGELOG --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index efb20a419..ebd548ee1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ All notable changes to `laravel-permission` will be documented in this file +## 5.9.1 - 2023-02-06 + +Apologies for the break caused by 5.9.0 ! + +### Reverted Lazy binding of dependencies. + +- Revert "fix: Lazily bind dependencies", originally #2309 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/5.9.0...5.9.1 + ## 5.9.0 - 2023-02-06 ### What's Changed @@ -582,6 +592,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` 1. Also this is a good time to point out that now with v2.25.0 and v2.26.0 most permission-cache-reset scenarios may no longer be needed in your app, so it's worth reviewing those cases, as you may gain some app speed improvement by removing unnecessary cache resets. @@ -634,6 +645,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` ## 2.19.1 - 2018-09-14 From f58e97a6a5b7554e0c786e3f7ee107b615f0aad0 Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 18 Jan 2023 15:48:59 +0100 Subject: [PATCH 218/648] fix: Lazily bind dependencies --- src/PermissionRegistrar.php | 4 ++-- src/PermissionServiceProvider.php | 35 ++++++++++++++++++------------- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index ca2bf66eb..cbff87357 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -133,9 +133,9 @@ public function getPermissionsTeamId() * * @return bool */ - public function registerPermissions(): bool + public function registerPermissions(Gate $gate): bool { - app(Gate::class)->before(function (Authorizable $user, string $ability) { + $gate->before(function (Authorizable $user, string $ability) { if (method_exists($user, 'checkPermissionTo')) { return $user->checkPermissionTo($ability) ?: null; } diff --git a/src/PermissionServiceProvider.php b/src/PermissionServiceProvider.php index e408064a9..3b0310673 100644 --- a/src/PermissionServiceProvider.php +++ b/src/PermissionServiceProvider.php @@ -2,6 +2,8 @@ namespace Spatie\Permission; +use Illuminate\Contracts\Auth\Access\Gate; +use Illuminate\Contracts\Foundation\Application; use Illuminate\Filesystem\Filesystem; use Illuminate\Routing\Route; use Illuminate\Support\Arr; @@ -13,7 +15,7 @@ class PermissionServiceProvider extends ServiceProvider { - public function boot(PermissionRegistrar $permissionLoader) + public function boot() { $this->offerPublishing(); @@ -23,14 +25,17 @@ public function boot(PermissionRegistrar $permissionLoader) $this->registerModelBindings(); - if ($this->app->config['permission.register_permission_check_method']) { - $permissionLoader->clearClassPermissions(); - $permissionLoader->registerPermissions(); - } - - $this->app->singleton(PermissionRegistrar::class, function ($app) use ($permissionLoader) { - return $permissionLoader; + $this->callAfterResolving(Gate::class, function (Gate $gate, Application $app) { + if ($this->app->config['permission.register_permission_check_method']) { + /** @var PermissionRegistrar $permissionLoader */ + $permissionLoader = $app->get(PermissionRegistrar::class); + $permissionLoader->clearClassPermissions(); + $permissionLoader->registerPermissions($gate); + } }); + + + $this->app->singleton(PermissionRegistrar::class); } public function register() @@ -74,14 +79,16 @@ protected function registerCommands() protected function registerModelBindings() { - $config = $this->app->config['permission.models']; + $this->app->bind(PermissionContract::class, function ($app) { + $config = $app->config['permission.models']; - if (! $config) { - return; - } + return $app->make($config['permission']); + }); + $this->app->bind(RoleContract::class, function ($app) { + $config = $app->config['permission.models']; - $this->app->bind(PermissionContract::class, $config['permission']); - $this->app->bind(RoleContract::class, $config['role']); + return $app->make($config['role']); + }); } public static function bladeMethodWrapper($method, $role, $guard = null) From 3859d92ef37184dafcefc16e8b403a0b5e57c552 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 7 Feb 2023 19:06:22 -0500 Subject: [PATCH 219/648] `master` now refers to v6 --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index e3abef547..afefa994d 100644 --- a/composer.json +++ b/composer.json @@ -53,8 +53,8 @@ }, "extra": { "branch-alias": { - "dev-main": "5.x-dev", - "dev-master": "5.x-dev" + "dev-main": "6.x-dev", + "dev-master": "6.x-dev" }, "laravel": { "providers": [ From b881a0777bdccd536d72ab36128e4f581417d575 Mon Sep 17 00:00:00 2001 From: xenaio <10925715+xenaio-daniil@users.noreply.github.com> Date: Wed, 8 Feb 2023 03:08:35 +0300 Subject: [PATCH 220/648] Fix Role::withCount if belongsToMany declared (#2280) * Fix Role::withCount if belongsToMany declared fixes spatie/laravel-permission#2277 --- src/Models/Role.php | 6 +- tests/RoleWithNesting.php | 39 ++++++++ tests/RoleWithNestingTest.php | 90 +++++++++++++++++++ .../roles_with_nesting_migration.php.stub | 40 +++++++++ 4 files changed, 170 insertions(+), 5 deletions(-) create mode 100644 tests/RoleWithNesting.php create mode 100644 tests/RoleWithNestingTest.php create mode 100644 tests/customMigrations/roles_with_nesting_migration.php.stub diff --git a/src/Models/Role.php b/src/Models/Role.php index 9c6de4771..104916dd2 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -34,11 +34,7 @@ public function __construct(array $attributes = []) parent::__construct($attributes); $this->guarded[] = $this->primaryKey; - } - - public function getTable() - { - return config('permission.table_names.roles', parent::getTable()); + $this->table = config('permission.table_names.roles', parent::getTable()); } public static function create(array $attributes = []) diff --git a/tests/RoleWithNesting.php b/tests/RoleWithNesting.php new file mode 100644 index 000000000..9a8c33a6a --- /dev/null +++ b/tests/RoleWithNesting.php @@ -0,0 +1,39 @@ +belongsToMany( + static::class, + static::HIERARCHY_TABLE, + 'child_id', + 'parent_id'); + } + + /** + * @return BelongsToMany + */ + public function children(): BelongsToMany + { + return $this->belongsToMany( + static::class, + static::HIERARCHY_TABLE, + 'parent_id', + 'child_id'); + } +} diff --git a/tests/RoleWithNestingTest.php b/tests/RoleWithNestingTest.php new file mode 100644 index 000000000..543f8e3a0 --- /dev/null +++ b/tests/RoleWithNestingTest.php @@ -0,0 +1,90 @@ +parent_roles = []; + $this->child_roles = []; + $this->parent_roles["has_no_children"] = RoleWithNesting::create(["name"=>"has_no_children"]); + $this->parent_roles["has_1_child"] = RoleWithNesting::create(["name"=>"has_1_child"]); + $this->parent_roles["has_3_children"] = RoleWithNesting::create(["name"=>"has_3_children"]); + + $this->child_roles["has_no_parents"] = RoleWithNesting::create(["name"=>"has_no_parents"]); + $this->child_roles["has_1_parent"] = RoleWithNesting::create(["name"=>"has_1_parent"]); + $this->child_roles["has_2_parents"] = RoleWithNesting::create(["name"=>"has_2_parents"]); + $this->child_roles["third_child"] = RoleWithNesting::create(["name"=>"third_child"]); + + $this->parent_roles["has_1_child"]->children()->attach($this->child_roles["has_2_parents"]); + $this->parent_roles["has_3_children"]->children()->attach($this->child_roles["has_2_parents"]); + $this->parent_roles["has_3_children"]->children()->attach($this->child_roles["has_1_parent"]); + $this->parent_roles["has_3_children"]->children()->attach($this->child_roles["third_child"]); + } + + public static function tearDownAfterClass(): void + { + parent::tearDownAfterClass(); + self::$migration = self::$old_migration; + } + + protected function getEnvironmentSetUp($app) + { + parent::getEnvironmentSetUp($app); + $app['config']->set('permission.models.role', RoleWithNesting::class); + $app['config']->set('permission.table_names.roles', "nesting_role"); + } + + protected static function getMigration() + { + require_once __DIR__."/customMigrations/roles_with_nesting_migration.php.stub"; + return new \CreatePermissionTablesWithNested(); + } + + /** @test + * @dataProvider roles_list + */ + public function it_returns_correct_withCount_of_nested_roles($role_group,$index,$relation,$expectedCount) + { + $role = $this->$role_group[$index]; + $count_field_name = sprintf("%s_count", $relation); + + $actualCount = intval(RoleWithNesting::query()->withCount($relation)->find($role->id)->$count_field_name); + + $this->assertSame( + $expectedCount, + $actualCount, + sprintf("%s expects %d %s, %d found",$role->name,$expectedCount,$relation,$actualCount) + ); + } + + public function roles_list(){ + return [ + ["parent_roles","has_no_children","children",0], + ["parent_roles","has_1_child","children",1], + ["parent_roles","has_3_children","children",3], + ["child_roles","has_no_parents","parents",0], + ["child_roles","has_1_parent","parents",1], + ["child_roles","has_2_parents","parents",2], + ]; + } +} diff --git a/tests/customMigrations/roles_with_nesting_migration.php.stub b/tests/customMigrations/roles_with_nesting_migration.php.stub new file mode 100644 index 000000000..81909275c --- /dev/null +++ b/tests/customMigrations/roles_with_nesting_migration.php.stub @@ -0,0 +1,40 @@ +id(); + $table->bigInteger("parent_id", false, true); + $table->bigInteger("child_id", false, true); + $table->foreign("parent_id")->references("id")->on($tableNames['roles']); + $table->foreign("child_id")->references("id")->on($tableNames['roles']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + parent::down(); + Schema::drop(\Spatie\Permission\Test\RoleWithNesting::HIERARCHY_TABLE); + } +} From 625a69d1a5cf8f7b7b921b271aad525577798cfd Mon Sep 17 00:00:00 2001 From: drbyte Date: Wed, 8 Feb 2023 00:09:03 +0000 Subject: [PATCH 221/648] Fix styling --- tests/RoleWithNestingTest.php | 58 +++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/tests/RoleWithNestingTest.php b/tests/RoleWithNestingTest.php index 543f8e3a0..4261dc03f 100644 --- a/tests/RoleWithNestingTest.php +++ b/tests/RoleWithNestingTest.php @@ -5,14 +5,16 @@ class RoleWithNestingTest extends TestCase { private static $old_migration; + /** * @var RoleWithNesting[] */ - protected $parent_roles=[]; + protected $parent_roles = []; + /** * @var RoleWithNesting[] */ - protected $child_roles=[]; + protected $child_roles = []; public static function setUpBeforeClass(): void { @@ -26,19 +28,19 @@ public function setUp(): void parent::setUp(); $this->parent_roles = []; $this->child_roles = []; - $this->parent_roles["has_no_children"] = RoleWithNesting::create(["name"=>"has_no_children"]); - $this->parent_roles["has_1_child"] = RoleWithNesting::create(["name"=>"has_1_child"]); - $this->parent_roles["has_3_children"] = RoleWithNesting::create(["name"=>"has_3_children"]); - - $this->child_roles["has_no_parents"] = RoleWithNesting::create(["name"=>"has_no_parents"]); - $this->child_roles["has_1_parent"] = RoleWithNesting::create(["name"=>"has_1_parent"]); - $this->child_roles["has_2_parents"] = RoleWithNesting::create(["name"=>"has_2_parents"]); - $this->child_roles["third_child"] = RoleWithNesting::create(["name"=>"third_child"]); - - $this->parent_roles["has_1_child"]->children()->attach($this->child_roles["has_2_parents"]); - $this->parent_roles["has_3_children"]->children()->attach($this->child_roles["has_2_parents"]); - $this->parent_roles["has_3_children"]->children()->attach($this->child_roles["has_1_parent"]); - $this->parent_roles["has_3_children"]->children()->attach($this->child_roles["third_child"]); + $this->parent_roles['has_no_children'] = RoleWithNesting::create(['name' => 'has_no_children']); + $this->parent_roles['has_1_child'] = RoleWithNesting::create(['name' => 'has_1_child']); + $this->parent_roles['has_3_children'] = RoleWithNesting::create(['name' => 'has_3_children']); + + $this->child_roles['has_no_parents'] = RoleWithNesting::create(['name' => 'has_no_parents']); + $this->child_roles['has_1_parent'] = RoleWithNesting::create(['name' => 'has_1_parent']); + $this->child_roles['has_2_parents'] = RoleWithNesting::create(['name' => 'has_2_parents']); + $this->child_roles['third_child'] = RoleWithNesting::create(['name' => 'third_child']); + + $this->parent_roles['has_1_child']->children()->attach($this->child_roles['has_2_parents']); + $this->parent_roles['has_3_children']->children()->attach($this->child_roles['has_2_parents']); + $this->parent_roles['has_3_children']->children()->attach($this->child_roles['has_1_parent']); + $this->parent_roles['has_3_children']->children()->attach($this->child_roles['third_child']); } public static function tearDownAfterClass(): void @@ -51,40 +53,42 @@ protected function getEnvironmentSetUp($app) { parent::getEnvironmentSetUp($app); $app['config']->set('permission.models.role', RoleWithNesting::class); - $app['config']->set('permission.table_names.roles', "nesting_role"); + $app['config']->set('permission.table_names.roles', 'nesting_role'); } protected static function getMigration() { - require_once __DIR__."/customMigrations/roles_with_nesting_migration.php.stub"; + require_once __DIR__.'/customMigrations/roles_with_nesting_migration.php.stub'; + return new \CreatePermissionTablesWithNested(); } /** @test * @dataProvider roles_list */ - public function it_returns_correct_withCount_of_nested_roles($role_group,$index,$relation,$expectedCount) + public function it_returns_correct_withCount_of_nested_roles($role_group, $index, $relation, $expectedCount) { $role = $this->$role_group[$index]; - $count_field_name = sprintf("%s_count", $relation); + $count_field_name = sprintf('%s_count', $relation); $actualCount = intval(RoleWithNesting::query()->withCount($relation)->find($role->id)->$count_field_name); $this->assertSame( $expectedCount, $actualCount, - sprintf("%s expects %d %s, %d found",$role->name,$expectedCount,$relation,$actualCount) + sprintf('%s expects %d %s, %d found', $role->name, $expectedCount, $relation, $actualCount) ); } - public function roles_list(){ + public function roles_list() + { return [ - ["parent_roles","has_no_children","children",0], - ["parent_roles","has_1_child","children",1], - ["parent_roles","has_3_children","children",3], - ["child_roles","has_no_parents","parents",0], - ["child_roles","has_1_parent","parents",1], - ["child_roles","has_2_parents","parents",2], + ['parent_roles', 'has_no_children', 'children', 0], + ['parent_roles', 'has_1_child', 'children', 1], + ['parent_roles', 'has_3_children', 'children', 3], + ['child_roles', 'has_no_parents', 'parents', 0], + ['child_roles', 'has_1_parent', 'parents', 1], + ['child_roles', 'has_2_parents', 'parents', 2], ]; } } From 991a000492611d2793a043dd70cbce18bf2dcca6 Mon Sep 17 00:00:00 2001 From: drbyte Date: Thu, 9 Feb 2023 02:41:41 +0000 Subject: [PATCH 222/648] Fix styling --- src/PermissionServiceProvider.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/PermissionServiceProvider.php b/src/PermissionServiceProvider.php index 08688b621..4abcd2cb8 100644 --- a/src/PermissionServiceProvider.php +++ b/src/PermissionServiceProvider.php @@ -34,7 +34,6 @@ public function boot() } }); - $this->app->singleton(PermissionRegistrar::class); } From cc8610e104a278144c475db9bee8037a33c298c5 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Mon, 6 Feb 2023 12:49:29 -0500 Subject: [PATCH 223/648] Full uuid support --- src/Models/Permission.php | 4 +-- src/Models/Role.php | 4 +-- src/Traits/HasPermissions.php | 10 ++++---- src/Traits/HasRoles.php | 8 +++--- tests/HasPermissionsWithCustomModelsTest.php | 27 ++++++++++++++++++++ tests/Permission.php | 20 +++++++++++++++ tests/Role.php | 20 +++++++++++++++ tests/TestCase.php | 6 +++++ 8 files changed, 86 insertions(+), 13 deletions(-) diff --git a/src/Models/Permission.php b/src/Models/Permission.php index 9bbf367e4..d0ea85122 100644 --- a/src/Models/Permission.php +++ b/src/Models/Permission.php @@ -104,13 +104,13 @@ public static function findByName(string $name, $guardName = null): PermissionCo /** * Find a permission by its id (and optionally guardName). * - * @param int $id + * @param int|string $id * @param string|null $guardName * @return \Spatie\Permission\Contracts\Permission * * @throws \Spatie\Permission\Exceptions\PermissionDoesNotExist */ - public static function findById(int $id, $guardName = null): PermissionContract + public static function findById($id, $guardName = null): PermissionContract { $guardName = $guardName ?? Guard::getDefaultName(static::class); $permission = static::getPermission([(new static())->getKeyName() => $id, 'guard_name' => $guardName]); diff --git a/src/Models/Role.php b/src/Models/Role.php index 104916dd2..aa1639e1d 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -108,11 +108,11 @@ public static function findByName(string $name, $guardName = null): RoleContract /** * Find a role by its id (and optionally guardName). * - * @param int $id + * @param int|string $id * @param string|null $guardName * @return \Spatie\Permission\Contracts\Role|\Spatie\Permission\Models\Role */ - public static function findById(int $id, $guardName = null): RoleContract + public static function findById($id, $guardName = null): RoleContract { $guardName = $guardName ?? Guard::getDefaultName(static::class); diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index 0d85b998f..217ea526e 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -134,7 +134,7 @@ protected function convertToPermissionModels($permissions): array if ($permission instanceof Permission) { return $permission; } - $method = is_string($permission) ? 'findByName' : 'findById'; + $method = is_string($permission) && ! \Str::isUuid($permission) ? 'findByName' : 'findById'; return $this->getPermissionClass()->{$method}($permission, $this->getDefaultGuardName()); }, Arr::wrap($permissions)); @@ -152,14 +152,14 @@ public function filterPermission($permission, $guardName = null) { $permissionClass = $this->getPermissionClass(); - if (is_string($permission)) { + if (is_string($permission) && ! \Str::isUuid($permission)) { $permission = $permissionClass->findByName( $permission, $guardName ?? $this->getDefaultGuardName() ); } - if (is_int($permission)) { + if (is_int($permission) || is_string($permission)) { $permission = $permissionClass->findById( $permission, $guardName ?? $this->getDefaultGuardName() @@ -204,7 +204,7 @@ protected function hasWildcardPermission($permission, $guardName = null): bool { $guardName = $guardName ?? $this->getDefaultGuardName(); - if (is_int($permission)) { + if (is_int($permission) || \Str::isUuid($permission)) { $permission = $this->getPermissionClass()->findById($permission, $guardName); } @@ -449,7 +449,7 @@ protected function getStoredPermission($permissions) { $permissionClass = $this->getPermissionClass(); - if (is_numeric($permissions)) { + if (is_numeric($permissions) || \Str::isUuid($permissions)) { return $permissionClass->findById($permissions, $this->getDefaultGuardName()); } diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index 8ab3732ad..35d008aa4 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -83,7 +83,7 @@ public function scopeRole(Builder $query, $roles, $guard = null): Builder return $role; } - $method = is_numeric($role) ? 'findById' : 'findByName'; + $method = is_numeric($role) || \Str::isUuid($role) ? 'findById' : 'findByName'; return $this->getRoleClass()->{$method}($role, $guard ?: $this->getDefaultGuardName()); }, Arr::wrap($roles)); @@ -195,13 +195,13 @@ public function hasRole($roles, string $guard = null): bool $roles = $this->convertPipeToArray($roles); } - if (is_string($roles)) { + if (is_string($roles) && ! \Str::isUuid($roles)) { return $guard ? $this->roles->where('guard_name', $guard)->contains('name', $roles) : $this->roles->contains('name', $roles); } - if (is_int($roles)) { + if (is_int($roles) || is_string($roles)) { $roleClass = $this->getRoleClass(); $key = (new $roleClass())->getKeyName(); @@ -325,7 +325,7 @@ protected function getStoredRole($role): Role { $roleClass = $this->getRoleClass(); - if (is_numeric($role)) { + if (is_numeric($role) || \Str::isUuid($role)) { return $roleClass->findById($role, $this->getDefaultGuardName()); } diff --git a/tests/HasPermissionsWithCustomModelsTest.php b/tests/HasPermissionsWithCustomModelsTest.php index 6b9fa34ca..b491c8e05 100644 --- a/tests/HasPermissionsWithCustomModelsTest.php +++ b/tests/HasPermissionsWithCustomModelsTest.php @@ -36,4 +36,31 @@ public function it_can_use_custom_fields_from_cache() $this->assertSame(0, count(DB::getQueryLog())); } + + /** @test */ + public function it_can_scope_users_using_a_int() + { + // Skipped because custom model uses uuid, + // replacement "it_can_scope_users_using_a_uuid" + $this->assertTrue(true); + } + + /** @test */ + public function it_can_scope_users_using_a_uuid() + { + $uuid1 = $this->testUserPermission->getKey(); + $uuid2 = app(Permission::class)::where('name', 'edit-news')->first()->getKey(); + + $user1 = User::create(['email' => 'user1@test.com']); + $user2 = User::create(['email' => 'user2@test.com']); + $user1->givePermissionTo([$uuid1, $uuid2]); + $this->testUserRole->givePermissionTo($uuid1); + $user2->assignRole('testRole'); + + $scopedUsers1 = User::permission($uuid1)->get(); + $scopedUsers2 = User::permission([$uuid2])->get(); + + $this->assertEquals(2, $scopedUsers1->count()); + $this->assertEquals(1, $scopedUsers2->count()); + } } diff --git a/tests/Permission.php b/tests/Permission.php index 24d4938b8..9d8f2cd91 100644 --- a/tests/Permission.php +++ b/tests/Permission.php @@ -10,4 +10,24 @@ class Permission extends \Spatie\Permission\Models\Permission 'permission_test_id', 'name', ]; + + protected static function boot() + { + parent::boot(); + static::creating(function ($model) { + if (empty($model->{$model->getKeyName()})) { + $model->{$model->getKeyName()} = \Str::uuid()->toString(); + } + }); + } + + public function getIncrementing() + { + return false; + } + + public function getKeyType() + { + return 'string'; + } } diff --git a/tests/Role.php b/tests/Role.php index ee2862f82..79f8cdbfd 100644 --- a/tests/Role.php +++ b/tests/Role.php @@ -10,4 +10,24 @@ class Role extends \Spatie\Permission\Models\Role 'role_test_id', 'name', ]; + + protected static function boot() + { + parent::boot(); + static::creating(function ($model) { + if (empty($model->{$model->getKeyName()})) { + $model->{$model->getKeyName()} = \Str::uuid()->toString(); + } + }); + } + + public function getIncrementing() + { + return false; + } + + public function getKeyType() + { + return 'string'; + } } diff --git a/tests/TestCase.php b/tests/TestCase.php index 7fd2ebb57..9481feb84 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -159,6 +159,9 @@ private function prepareMigration() '(\'id\'); // role id', 'references(\'id\') // permission id', 'references(\'id\') // role id', + 'bigIncrements', + 'unsignedBigInteger(PermissionRegistrar::$pivotRole)', + 'unsignedBigInteger(PermissionRegistrar::$pivotPermission)', ], [ 'CreatePermissionCustomTables', @@ -166,6 +169,9 @@ private function prepareMigration() '(\'role_test_id\');', 'references(\'permission_test_id\')', 'references(\'role_test_id\')', + 'uuid', + 'uuid(PermissionRegistrar::$pivotRole)->nullable(false)', + 'uuid(PermissionRegistrar::$pivotPermission)->nullable(false)', ], file_get_contents(__DIR__.'/../database/migrations/create_permission_tables.php.stub') ); From b2c858bc3cce4a7dfa13fd262a57ce258ceb4193 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Thu, 9 Feb 2023 10:12:19 -0500 Subject: [PATCH 224/648] Support ULIDs --- src/PermissionRegistrar.php | 21 +++++++++++++ src/Traits/HasPermissions.php | 8 ++--- src/Traits/HasRoles.php | 6 ++-- tests/PermissionRegistarTest.php | 51 ++++++++++++++++++++++++++++++++ 4 files changed, 79 insertions(+), 7 deletions(-) create mode 100644 tests/PermissionRegistarTest.php diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index 38f71e4bf..d771b2205 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -384,4 +384,25 @@ private function hydrateRolesCache() $this->permissions['roles'] = []; } + + public static function isUid($value) + { + if (! is_string($value) || empty(trim($value))) { + return false; + } + + // check if is UUID/GUID + $uid = preg_match('/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/iD', $value) > 0; + if ($uid) { + return true; + } + + // check if is ULID + $ulid = 26 == strlen($value) && 26 == strspn($value, '0123456789ABCDEFGHJKMNPQRSTVWXYZabcdefghjkmnpqrstvwxyz') && $value[0] <= '7'; + if ($ulid) { + return true; + } + + return false; + } } diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index 217ea526e..a29717d71 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -134,7 +134,7 @@ protected function convertToPermissionModels($permissions): array if ($permission instanceof Permission) { return $permission; } - $method = is_string($permission) && ! \Str::isUuid($permission) ? 'findByName' : 'findById'; + $method = is_string($permission) && ! PermissionRegistrar::isUid($permission) ? 'findByName' : 'findById'; return $this->getPermissionClass()->{$method}($permission, $this->getDefaultGuardName()); }, Arr::wrap($permissions)); @@ -152,7 +152,7 @@ public function filterPermission($permission, $guardName = null) { $permissionClass = $this->getPermissionClass(); - if (is_string($permission) && ! \Str::isUuid($permission)) { + if (is_string($permission) && ! PermissionRegistrar::isUid($permission)) { $permission = $permissionClass->findByName( $permission, $guardName ?? $this->getDefaultGuardName() @@ -204,7 +204,7 @@ protected function hasWildcardPermission($permission, $guardName = null): bool { $guardName = $guardName ?? $this->getDefaultGuardName(); - if (is_int($permission) || \Str::isUuid($permission)) { + if (is_int($permission) || PermissionRegistrar::isUid($permission)) { $permission = $this->getPermissionClass()->findById($permission, $guardName); } @@ -449,7 +449,7 @@ protected function getStoredPermission($permissions) { $permissionClass = $this->getPermissionClass(); - if (is_numeric($permissions) || \Str::isUuid($permissions)) { + if (is_numeric($permissions) || PermissionRegistrar::isUid($permissions)) { return $permissionClass->findById($permissions, $this->getDefaultGuardName()); } diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index 35d008aa4..42cfb4ac0 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -83,7 +83,7 @@ public function scopeRole(Builder $query, $roles, $guard = null): Builder return $role; } - $method = is_numeric($role) || \Str::isUuid($role) ? 'findById' : 'findByName'; + $method = is_numeric($role) || PermissionRegistrar::isUid($role) ? 'findById' : 'findByName'; return $this->getRoleClass()->{$method}($role, $guard ?: $this->getDefaultGuardName()); }, Arr::wrap($roles)); @@ -195,7 +195,7 @@ public function hasRole($roles, string $guard = null): bool $roles = $this->convertPipeToArray($roles); } - if (is_string($roles) && ! \Str::isUuid($roles)) { + if (is_string($roles) && ! PermissionRegistrar::isUid($roles)) { return $guard ? $this->roles->where('guard_name', $guard)->contains('name', $roles) : $this->roles->contains('name', $roles); @@ -325,7 +325,7 @@ protected function getStoredRole($role): Role { $roleClass = $this->getRoleClass(); - if (is_numeric($role) || \Str::isUuid($role)) { + if (is_numeric($role) || PermissionRegistrar::isUid($role)) { return $roleClass->findById($role, $this->getDefaultGuardName()); } diff --git a/tests/PermissionRegistarTest.php b/tests/PermissionRegistarTest.php new file mode 100644 index 000000000..b864b3e90 --- /dev/null +++ b/tests/PermissionRegistarTest.php @@ -0,0 +1,51 @@ +assertTrue(PermissionRegistrar::isUid($uid)); + } + + foreach ($not_uids as $not_uid) { + $this->assertFalse(PermissionRegistrar::isUid($not_uid)); + } + } +} From d70317fc8e223f5e90efa13f8d732acafdeb9879 Mon Sep 17 00:00:00 2001 From: Oliver Nybroe Date: Sat, 11 Feb 2023 00:21:42 +0100 Subject: [PATCH 225/648] refactor: Change static properties to non-static (#2324) * refactor: Change static properties to non-static * Use config helper on migrations --------- Co-authored-by: erikn69 --- database/migrations/add_teams_fields.php.stub | 19 +++++----- .../create_permission_tables.php.stub | 37 ++++++++++--------- src/Commands/CreateRole.php | 8 ++-- src/Models/Permission.php | 6 +-- src/Models/Role.php | 30 ++++++++------- src/PermissionRegistrar.php | 28 +++++++------- src/Traits/HasPermissions.php | 16 ++++---- src/Traits/HasRoles.php | 18 ++++----- 8 files changed, 84 insertions(+), 78 deletions(-) diff --git a/database/migrations/add_teams_fields.php.stub b/database/migrations/add_teams_fields.php.stub index 6abdf8d8f..be79ee047 100644 --- a/database/migrations/add_teams_fields.php.stub +++ b/database/migrations/add_teams_fields.php.stub @@ -4,7 +4,6 @@ use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; -use Spatie\Permission\PermissionRegistrar; class AddTeamsFields extends Migration { @@ -18,6 +17,8 @@ class AddTeamsFields extends Migration $teams = config('permission.teams'); $tableNames = config('permission.table_names'); $columnNames = config('permission.column_names'); + $pivotRole = $columnNames['role_pivot_key'] ?? 'role_id'; + $pivotPermission = $columnNames['permission_pivot_key'] ?? 'permission_id'; if (! $teams) { return; @@ -40,38 +41,38 @@ class AddTeamsFields extends Migration } if (! Schema::hasColumn($tableNames['model_has_permissions'], $columnNames['team_foreign_key'])) { - Schema::table($tableNames['model_has_permissions'], function (Blueprint $table) use ($tableNames, $columnNames) { + Schema::table($tableNames['model_has_permissions'], function (Blueprint $table) use ($tableNames, $columnNames, $pivotPermission) { $table->unsignedBigInteger($columnNames['team_foreign_key'])->default('1'); $table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index'); if (DB::getDriverName() !== 'sqlite') { - $table->dropForeign([PermissionRegistrar::$pivotPermission]); + $table->dropForeign([$pivotPermission]); } $table->dropPrimary(); - $table->primary([$columnNames['team_foreign_key'], PermissionRegistrar::$pivotPermission, $columnNames['model_morph_key'], 'model_type'], + $table->primary([$columnNames['team_foreign_key'], $pivotPermission, $columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_permission_model_type_primary'); if (DB::getDriverName() !== 'sqlite') { - $table->foreign(PermissionRegistrar::$pivotPermission) + $table->foreign($pivotPermission) ->references('id')->on($tableNames['permissions'])->onDelete('cascade'); } }); } if (! Schema::hasColumn($tableNames['model_has_roles'], $columnNames['team_foreign_key'])) { - Schema::table($tableNames['model_has_roles'], function (Blueprint $table) use ($tableNames, $columnNames) { + Schema::table($tableNames['model_has_roles'], function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole) { $table->unsignedBigInteger($columnNames['team_foreign_key'])->default('1');; $table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index'); if (DB::getDriverName() !== 'sqlite') { - $table->dropForeign([PermissionRegistrar::$pivotRole]); + $table->dropForeign([$pivotRole]); } $table->dropPrimary(); - $table->primary([$columnNames['team_foreign_key'], PermissionRegistrar::$pivotRole, $columnNames['model_morph_key'], 'model_type'], + $table->primary([$columnNames['team_foreign_key'], $pivotRole, $columnNames['model_morph_key'], 'model_type'], 'model_has_roles_role_model_type_primary'); if (DB::getDriverName() !== 'sqlite') { - $table->foreign(PermissionRegistrar::$pivotRole) + $table->foreign($pivotRole) ->references('id')->on($tableNames['roles'])->onDelete('cascade'); } }); diff --git a/database/migrations/create_permission_tables.php.stub b/database/migrations/create_permission_tables.php.stub index 04c3278b9..4874c05a8 100644 --- a/database/migrations/create_permission_tables.php.stub +++ b/database/migrations/create_permission_tables.php.stub @@ -3,7 +3,6 @@ use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; -use Spatie\Permission\PermissionRegistrar; class CreatePermissionTables extends Migration { @@ -14,9 +13,11 @@ class CreatePermissionTables extends Migration */ public function up() { + $teams = config('permission.teams'); $tableNames = config('permission.table_names'); $columnNames = config('permission.column_names'); - $teams = config('permission.teams'); + $pivotRole = $columnNames['role_pivot_key'] ?? 'role_id'; + $pivotPermission = $columnNames['permission_pivot_key'] ?? 'permission_id'; if (empty($tableNames)) { throw new \Exception('Error: config/permission.php not loaded. Run [php artisan config:clear] and try again.'); @@ -50,14 +51,14 @@ class CreatePermissionTables extends Migration } }); - Schema::create($tableNames['model_has_permissions'], function (Blueprint $table) use ($tableNames, $columnNames, $teams) { - $table->unsignedBigInteger(PermissionRegistrar::$pivotPermission); + Schema::create($tableNames['model_has_permissions'], function (Blueprint $table) use ($tableNames, $columnNames, $pivotPermission, $teams) { + $table->unsignedBigInteger($pivotPermission); $table->string('model_type'); $table->unsignedBigInteger($columnNames['model_morph_key']); $table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index'); - $table->foreign(PermissionRegistrar::$pivotPermission) + $table->foreign($pivotPermission) ->references('id') // permission id ->on($tableNames['permissions']) ->onDelete('cascade'); @@ -65,23 +66,23 @@ class CreatePermissionTables extends Migration $table->unsignedBigInteger($columnNames['team_foreign_key']); $table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index'); - $table->primary([$columnNames['team_foreign_key'], PermissionRegistrar::$pivotPermission, $columnNames['model_morph_key'], 'model_type'], + $table->primary([$columnNames['team_foreign_key'], $pivotPermission, $columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_permission_model_type_primary'); } else { - $table->primary([PermissionRegistrar::$pivotPermission, $columnNames['model_morph_key'], 'model_type'], + $table->primary([$pivotPermission, $columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_permission_model_type_primary'); } }); - Schema::create($tableNames['model_has_roles'], function (Blueprint $table) use ($tableNames, $columnNames, $teams) { - $table->unsignedBigInteger(PermissionRegistrar::$pivotRole); + Schema::create($tableNames['model_has_roles'], function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams) { + $table->unsignedBigInteger($pivotRole); $table->string('model_type'); $table->unsignedBigInteger($columnNames['model_morph_key']); $table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index'); - $table->foreign(PermissionRegistrar::$pivotRole) + $table->foreign($pivotRole) ->references('id') // role id ->on($tableNames['roles']) ->onDelete('cascade'); @@ -89,29 +90,29 @@ class CreatePermissionTables extends Migration $table->unsignedBigInteger($columnNames['team_foreign_key']); $table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index'); - $table->primary([$columnNames['team_foreign_key'], PermissionRegistrar::$pivotRole, $columnNames['model_morph_key'], 'model_type'], + $table->primary([$columnNames['team_foreign_key'], $pivotRole, $columnNames['model_morph_key'], 'model_type'], 'model_has_roles_role_model_type_primary'); } else { - $table->primary([PermissionRegistrar::$pivotRole, $columnNames['model_morph_key'], 'model_type'], + $table->primary([$pivotRole, $columnNames['model_morph_key'], 'model_type'], 'model_has_roles_role_model_type_primary'); } }); - Schema::create($tableNames['role_has_permissions'], function (Blueprint $table) use ($tableNames) { - $table->unsignedBigInteger(PermissionRegistrar::$pivotPermission); - $table->unsignedBigInteger(PermissionRegistrar::$pivotRole); + Schema::create($tableNames['role_has_permissions'], function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) { + $table->unsignedBigInteger($pivotPermission); + $table->unsignedBigInteger($pivotRole); - $table->foreign(PermissionRegistrar::$pivotPermission) + $table->foreign($pivotPermission) ->references('id') // permission id ->on($tableNames['permissions']) ->onDelete('cascade'); - $table->foreign(PermissionRegistrar::$pivotRole) + $table->foreign($pivotRole) ->references('id') // role id ->on($tableNames['roles']) ->onDelete('cascade'); - $table->primary([PermissionRegistrar::$pivotPermission, PermissionRegistrar::$pivotRole], 'role_has_permissions_permission_id_role_id_primary'); + $table->primary([$pivotPermission, $pivotRole], 'role_has_permissions_permission_id_role_id_primary'); }); app('cache') diff --git a/src/Commands/CreateRole.php b/src/Commands/CreateRole.php index f84385b09..7cb92aa35 100644 --- a/src/Commands/CreateRole.php +++ b/src/Commands/CreateRole.php @@ -17,14 +17,14 @@ class CreateRole extends Command protected $description = 'Create a role'; - public function handle() + public function handle(PermissionRegistrar $permissionRegistrar) { $roleClass = app(RoleContract::class); $teamIdAux = getPermissionsTeamId(); setPermissionsTeamId($this->option('team-id') ?: null); - if (! PermissionRegistrar::$teams && $this->option('team-id')) { + if (! $permissionRegistrar->teams && $this->option('team-id')) { $this->warn('Teams feature disabled, argument --team-id has no effect. Either enable it in permissions config file or remove --team-id parameter'); return; @@ -33,8 +33,8 @@ public function handle() $role = $roleClass::findOrCreate($this->argument('name'), $this->argument('guard')); setPermissionsTeamId($teamIdAux); - $teams_key = PermissionRegistrar::$teamsKey; - if (PermissionRegistrar::$teams && $this->option('team-id') && is_null($role->$teams_key)) { + $teams_key = $permissionRegistrar->teamsKey; + if ($permissionRegistrar->teams && $this->option('team-id') && is_null($role->$teams_key)) { $this->warn("Role `{$role->name}` already exists on the global team; argument --team-id has no effect"); } diff --git a/src/Models/Permission.php b/src/Models/Permission.php index 9bbf367e4..1fabdf162 100644 --- a/src/Models/Permission.php +++ b/src/Models/Permission.php @@ -62,8 +62,8 @@ public function roles(): BelongsToMany return $this->belongsToMany( config('permission.models.role'), config('permission.table_names.role_has_permissions'), - PermissionRegistrar::$pivotPermission, - PermissionRegistrar::$pivotRole + app(PermissionRegistrar::class)->pivotPermission, + app(PermissionRegistrar::class)->pivotRole ); } @@ -76,7 +76,7 @@ public function users(): BelongsToMany getModelForGuard($this->attributes['guard_name'] ?? config('auth.defaults.guard')), 'model', config('permission.table_names.model_has_permissions'), - PermissionRegistrar::$pivotPermission, + app(PermissionRegistrar::class)->pivotPermission, config('permission.column_names.model_morph_key') ); } diff --git a/src/Models/Role.php b/src/Models/Role.php index 104916dd2..f32766de3 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -42,11 +42,13 @@ public static function create(array $attributes = []) $attributes['guard_name'] = $attributes['guard_name'] ?? Guard::getDefaultName(static::class); $params = ['name' => $attributes['name'], 'guard_name' => $attributes['guard_name']]; - if (PermissionRegistrar::$teams) { - if (array_key_exists(PermissionRegistrar::$teamsKey, $attributes)) { - $params[PermissionRegistrar::$teamsKey] = $attributes[PermissionRegistrar::$teamsKey]; + if (app(PermissionRegistrar::class)->teams) { + $teamsKey = app(PermissionRegistrar::class)->teamsKey; + + if (array_key_exists($teamsKey, $attributes)) { + $params[$teamsKey] = $attributes[$teamsKey]; } else { - $attributes[PermissionRegistrar::$teamsKey] = getPermissionsTeamId(); + $attributes[$teamsKey] = getPermissionsTeamId(); } } if (static::findByParam($params)) { @@ -64,8 +66,8 @@ public function permissions(): BelongsToMany return $this->belongsToMany( config('permission.models.permission'), config('permission.table_names.role_has_permissions'), - PermissionRegistrar::$pivotRole, - PermissionRegistrar::$pivotPermission + app(PermissionRegistrar::class)->pivotRole, + app(PermissionRegistrar::class)->pivotPermission ); } @@ -78,7 +80,7 @@ public function users(): BelongsToMany getModelForGuard($this->attributes['guard_name'] ?? config('auth.defaults.guard')), 'model', config('permission.table_names.model_has_roles'), - PermissionRegistrar::$pivotRole, + app(PermissionRegistrar::class)->pivotRole, config('permission.column_names.model_morph_key') ); } @@ -139,7 +141,7 @@ public static function findOrCreate(string $name, $guardName = null): RoleContra $role = static::findByParam(['name' => $name, 'guard_name' => $guardName]); if (! $role) { - return static::query()->create(['name' => $name, 'guard_name' => $guardName] + (PermissionRegistrar::$teams ? [PermissionRegistrar::$teamsKey => getPermissionsTeamId()] : [])); + return static::query()->create(['name' => $name, 'guard_name' => $guardName] + (app(PermissionRegistrar::class)->teams ? [app(PermissionRegistrar::class)->teamsKey => getPermissionsTeamId()] : [])); } return $role; @@ -149,12 +151,14 @@ protected static function findByParam(array $params = []) { $query = static::query(); - if (PermissionRegistrar::$teams) { - $query->where(function ($q) use ($params) { - $q->whereNull(PermissionRegistrar::$teamsKey) - ->orWhere(PermissionRegistrar::$teamsKey, $params[PermissionRegistrar::$teamsKey] ?? getPermissionsTeamId()); + if (app(PermissionRegistrar::class)->teams) { + $teamsKey = app(PermissionRegistrar::class)->teamsKey; + + $query->where(function ($q) use ($params, $teamsKey) { + $q->whereNull($teamsKey) + ->orWhere($teamsKey, $params[$teamsKey] ?? getPermissionsTeamId()); }); - unset($params[PermissionRegistrar::$teamsKey]); + unset($params[$teamsKey]); } foreach ($params as $key => $value) { diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index 38f71e4bf..54a3a369f 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -29,25 +29,25 @@ class PermissionRegistrar protected $permissions; /** @var string */ - public static $pivotRole; + public $pivotRole; /** @var string */ - public static $pivotPermission; + public $pivotPermission; /** @var \DateInterval|int */ - public static $cacheExpirationTime; + public $cacheExpirationTime; /** @var bool */ - public static $teams; + public $teams; /** @var string */ - public static $teamsKey; + public $teamsKey; /** @var int|string */ protected $teamId = null; /** @var string */ - public static $cacheKey; + public $cacheKey; /** @var array */ private $cachedRoles = []; @@ -74,15 +74,15 @@ public function __construct(CacheManager $cacheManager) public function initializeCache() { - self::$cacheExpirationTime = config('permission.cache.expiration_time') ?: \DateInterval::createFromDateString('24 hours'); + $this->cacheExpirationTime = config('permission.cache.expiration_time') ?: \DateInterval::createFromDateString('24 hours'); - self::$teams = config('permission.teams', false); - self::$teamsKey = config('permission.column_names.team_foreign_key'); + $this->teams = config('permission.teams', false); + $this->teamsKey = config('permission.column_names.team_foreign_key'); - self::$cacheKey = config('permission.cache.key'); + $this->cacheKey = config('permission.cache.key'); - self::$pivotRole = config('permission.column_names.role_pivot_key') ?: 'role_id'; - self::$pivotPermission = config('permission.column_names.permission_pivot_key') ?: 'permission_id'; + $this->pivotRole = config('permission.column_names.role_pivot_key') ?: 'role_id'; + $this->pivotPermission = config('permission.column_names.permission_pivot_key') ?: 'permission_id'; $this->cache = $this->getCacheStoreFromConfig(); } @@ -151,7 +151,7 @@ public function forgetCachedPermissions() { $this->permissions = null; - return $this->cache->forget(self::$cacheKey); + return $this->cache->forget($this->cacheKey); } /** @@ -174,7 +174,7 @@ private function loadPermissions() return; } - $this->permissions = $this->cache->remember(self::$cacheKey, self::$cacheExpirationTime, function () { + $this->permissions = $this->cache->remember($this->cacheKey, $this->cacheExpirationTime, function () { return $this->getSerializedPermissionsForCache(); }); diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index 0d85b998f..2cf849d78 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -32,10 +32,10 @@ public static function bootHasPermissions() return; } - $teams = PermissionRegistrar::$teams; - PermissionRegistrar::$teams = false; + $teams = app(PermissionRegistrar::class)->teams; + app(PermissionRegistrar::class)->teams = false; $model->permissions()->detach(); - PermissionRegistrar::$teams = $teams; + app(PermissionRegistrar::class)->teams = $teams; }); } @@ -77,14 +77,14 @@ public function permissions(): BelongsToMany 'model', config('permission.table_names.model_has_permissions'), config('permission.column_names.model_morph_key'), - PermissionRegistrar::$pivotPermission + app(PermissionRegistrar::class)->pivotPermission ); - if (! PermissionRegistrar::$teams) { + if (! app(PermissionRegistrar::class)->teams) { return $relation; } - return $relation->wherePivot(PermissionRegistrar::$teamsKey, getPermissionsTeamId()); + return $relation->wherePivot(app(PermissionRegistrar::class)->teamsKey, getPermissionsTeamId()); } /** @@ -361,8 +361,8 @@ public function collectPermissions(...$permissions) $this->ensureModelSharesGuard($permission); - $array[$permission->getKey()] = PermissionRegistrar::$teams && ! is_a($this, Role::class) ? - [PermissionRegistrar::$teamsKey => getPermissionsTeamId()] : []; + $array[$permission->getKey()] = app(PermissionRegistrar::class)->teams && ! is_a($this, Role::class) ? + [app(PermissionRegistrar::class)->teamsKey => getPermissionsTeamId()] : []; return $array; }, []); diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index 8ab3732ad..eaa3c2b14 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -24,10 +24,10 @@ public static function bootHasRoles() return; } - $teams = PermissionRegistrar::$teams; - PermissionRegistrar::$teams = false; + $teams = app(PermissionRegistrar::class)->teams; + app(PermissionRegistrar::class)->teams = false; $model->roles()->detach(); - PermissionRegistrar::$teams = $teams; + app(PermissionRegistrar::class)->teams = $teams; }); } @@ -50,16 +50,16 @@ public function roles(): BelongsToMany 'model', config('permission.table_names.model_has_roles'), config('permission.column_names.model_morph_key'), - PermissionRegistrar::$pivotRole + app(PermissionRegistrar::class)->pivotRole ); - if (! PermissionRegistrar::$teams) { + if (! app(PermissionRegistrar::class)->teams) { return $relation; } - return $relation->wherePivot(PermissionRegistrar::$teamsKey, getPermissionsTeamId()) + return $relation->wherePivot(app(PermissionRegistrar::class)->teamsKey, getPermissionsTeamId()) ->where(function ($q) { - $teamField = config('permission.table_names.roles').'.'.PermissionRegistrar::$teamsKey; + $teamField = config('permission.table_names.roles').'.'. app(PermissionRegistrar::class)->teamsKey; $q->whereNull($teamField)->orWhere($teamField, getPermissionsTeamId()); }); } @@ -117,8 +117,8 @@ public function assignRole(...$roles) $this->ensureModelSharesGuard($role); - $array[$role->getKey()] = PermissionRegistrar::$teams && ! is_a($this, Permission::class) ? - [PermissionRegistrar::$teamsKey => getPermissionsTeamId()] : []; + $array[$role->getKey()] = app(PermissionRegistrar::class)->teams && ! is_a($this, Permission::class) ? + [app(PermissionRegistrar::class)->teamsKey => getPermissionsTeamId()] : []; return $array; }, []); From b1ff160a7a09057cbcdd1b56eda7813a0c4697f9 Mon Sep 17 00:00:00 2001 From: drbyte Date: Fri, 10 Feb 2023 23:22:22 +0000 Subject: [PATCH 226/648] Fix styling --- src/Traits/HasRoles.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index eaa3c2b14..de7d224a9 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -59,7 +59,7 @@ public function roles(): BelongsToMany return $relation->wherePivot(app(PermissionRegistrar::class)->teamsKey, getPermissionsTeamId()) ->where(function ($q) { - $teamField = config('permission.table_names.roles').'.'. app(PermissionRegistrar::class)->teamsKey; + $teamField = config('permission.table_names.roles').'.'.app(PermissionRegistrar::class)->teamsKey; $q->whereNull($teamField)->orWhere($teamField, getPermissionsTeamId()); }); } From 13e08f71efa1581b44be3d0389290db23ae10a3f Mon Sep 17 00:00:00 2001 From: erikn69 Date: Mon, 13 Feb 2023 08:56:21 -0500 Subject: [PATCH 227/648] Fix tests --- tests/TestCase.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/TestCase.php b/tests/TestCase.php index 9481feb84..b04d17839 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -160,8 +160,8 @@ private function prepareMigration() 'references(\'id\') // permission id', 'references(\'id\') // role id', 'bigIncrements', - 'unsignedBigInteger(PermissionRegistrar::$pivotRole)', - 'unsignedBigInteger(PermissionRegistrar::$pivotPermission)', + 'unsignedBigInteger($pivotRole)', + 'unsignedBigInteger($pivotPermission)', ], [ 'CreatePermissionCustomTables', @@ -170,8 +170,8 @@ private function prepareMigration() 'references(\'permission_test_id\')', 'references(\'role_test_id\')', 'uuid', - 'uuid(PermissionRegistrar::$pivotRole)->nullable(false)', - 'uuid(PermissionRegistrar::$pivotPermission)->nullable(false)', + 'uuid($pivotRole)->nullable(false)', + 'uuid($pivotPermission)->nullable(false)', ], file_get_contents(__DIR__.'/../database/migrations/create_permission_tables.php.stub') ); From 47a587a446c07f9040689e903c489f76eb7b850b Mon Sep 17 00:00:00 2001 From: "Mr. Alpaca" <85284773+JensvandeWiel@users.noreply.github.com> Date: Mon, 20 Feb 2023 15:16:18 +0100 Subject: [PATCH 228/648] Update middleware.md --- docs/basic-usage/middleware.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/basic-usage/middleware.md b/docs/basic-usage/middleware.md index 5d6ddae88..04391c683 100644 --- a/docs/basic-usage/middleware.md +++ b/docs/basic-usage/middleware.md @@ -17,6 +17,7 @@ Route::group(['middleware' => ['can:publish articles']], function () { This package comes with `RoleMiddleware`, `PermissionMiddleware` and `RoleOrPermissionMiddleware` middleware. You can add them inside your `app/Http/Kernel.php` file. +### Laravel 9 ```php protected $routeMiddleware = [ // ... @@ -25,6 +26,15 @@ protected $routeMiddleware = [ 'role_or_permission' => \Spatie\Permission\Middlewares\RoleOrPermissionMiddleware::class, ]; ``` +### Laravel 10 +```php +protected $middlewareAliases = [ + // ... + 'role' => \Spatie\Permission\Middlewares\RoleMiddleware::class, + 'permission' => \Spatie\Permission\Middlewares\PermissionMiddleware::class, + 'role_or_permission' => \Spatie\Permission\Middlewares\RoleOrPermissionMiddleware::class, +]; +``` Then you can protect your routes using middleware rules: From 490fa00b0c8286659a534d822f7e74c684c86d27 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Mon, 20 Feb 2023 15:01:00 -0500 Subject: [PATCH 229/648] [docs] Update middleware example for Laravel 10 --- docs/basic-usage/middleware.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/basic-usage/middleware.md b/docs/basic-usage/middleware.md index 04391c683..8a1971877 100644 --- a/docs/basic-usage/middleware.md +++ b/docs/basic-usage/middleware.md @@ -17,7 +17,9 @@ Route::group(['middleware' => ['can:publish articles']], function () { This package comes with `RoleMiddleware`, `PermissionMiddleware` and `RoleOrPermissionMiddleware` middleware. You can add them inside your `app/Http/Kernel.php` file. -### Laravel 9 +Note the differences between Laravel 10 and older versions of Laravel is the name of the `protected` property: + +### Laravel 9 (and older) ```php protected $routeMiddleware = [ // ... From 248e93f7f20b8b68e4182c0a32fb6f391f6b9c8b Mon Sep 17 00:00:00 2001 From: erikn69 Date: Wed, 1 Mar 2023 11:18:18 -0500 Subject: [PATCH 230/648] Avoid loss of all permissions/roles pivots on sync error --- src/Traits/HasPermissions.php | 7 ++++--- src/Traits/HasRoles.php | 22 +++++++++++++++++----- tests/HasPermissionsTest.php | 12 ++++++++++++ tests/HasRolesTest.php | 12 ++++++++++++ tests/RoleTest.php | 12 ++++++++++++ 5 files changed, 57 insertions(+), 8 deletions(-) diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index 7e2b91f53..ee6d07da1 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -343,9 +343,8 @@ public function getAllPermissions(): Collection * Returns permissions ids as array keys * * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection $permissions - * @return array */ - public function collectPermissions(...$permissions) + private function collectPermissions(...$permissions): array { return collect($permissions) ->flatten() @@ -376,7 +375,7 @@ public function collectPermissions(...$permissions) */ public function givePermissionTo(...$permissions) { - $permissions = $this->collectPermissions(...$permissions); + $permissions = $this->collectPermissions($permissions); $model = $this->getModel(); @@ -412,6 +411,8 @@ function ($object) use ($permissions, $model) { */ public function syncPermissions(...$permissions) { + $this->collectPermissions($permissions); + $this->permissions()->detach(); return $this->givePermissionTo($permissions); diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index 78787df0f..267528c2e 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -96,14 +96,13 @@ public function scopeRole(Builder $query, $roles, $guard = null): Builder } /** - * Assign the given role to the model. + * Returns roles ids as array keys * - * @param array|string|int|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection ...$roles - * @return $this + * @param array|string|int|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection $roles */ - public function assignRole(...$roles) + private function collectRoles(...$roles): array { - $roles = collect($roles) + return collect($roles) ->flatten() ->reduce(function ($array, $role) { if (empty($role)) { @@ -122,6 +121,17 @@ public function assignRole(...$roles) return $array; }, []); + } + + /** + * Assign the given role to the model. + * + * @param array|string|int|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection ...$roles + * @return $this + */ + public function assignRole(...$roles) + { + $roles = $this->collectRoles($roles); $model = $this->getModel(); @@ -175,6 +185,8 @@ public function removeRole($role) */ public function syncRoles(...$roles) { + $this->collectRoles($roles); + $this->roles()->detach(); return $this->assignRole($roles); diff --git a/tests/HasPermissionsTest.php b/tests/HasPermissionsTest.php index 61bba126b..a71867581 100644 --- a/tests/HasPermissionsTest.php +++ b/tests/HasPermissionsTest.php @@ -476,6 +476,18 @@ public function sync_permission_ignores_null_inputs() $this->assertFalse($this->testUser->hasDirectPermission('edit-news')); } + /** @test */ + public function sync_permission_error_does_not_detach_permissions() + { + $this->testUser->givePermissionTo('edit-news'); + + $this->expectException(PermissionDoesNotExist::class); + + $this->testUser->syncPermissions('edit-articles', 'permission-that-does-not-exist'); + + $this->assertTrue($this->testUser->fresh()->hasDirectPermission('edit-news')); + } + /** @test */ public function it_does_not_remove_already_associated_permissions_when_assigning_new_permissions() { diff --git a/tests/HasRolesTest.php b/tests/HasRolesTest.php index 0031b58da..1e7dcc8d5 100644 --- a/tests/HasRolesTest.php +++ b/tests/HasRolesTest.php @@ -227,6 +227,18 @@ public function it_will_remove_all_roles_when_an_empty_array_is_passed_to_sync_r $this->assertFalse($this->testUser->hasRole('testRole2')); } + /** @test */ + public function sync_roles_error_does_not_detach_roles() + { + $this->testUser->assignRole('testRole'); + + $this->expectException(RoleDoesNotExist::class); + + $this->testUser->syncRoles('testRole2', 'role-that-does-not-exist'); + + $this->assertTrue($this->testUser->fresh()->hasRole('testRole')); + } + /** @test */ public function it_will_sync_roles_to_a_model_that_is_not_persisted() { diff --git a/tests/RoleTest.php b/tests/RoleTest.php index 35e287d50..e76582f55 100644 --- a/tests/RoleTest.php +++ b/tests/RoleTest.php @@ -155,6 +155,18 @@ public function it_will_remove_all_permissions_when_passing_an_empty_array_to_sy $this->assertFalse($this->testUserRole->hasPermissionTo('edit-news')); } + /** @test */ + public function sync_permission_error_does_not_detach_permissions() + { + $this->testUserRole->givePermissionTo('edit-news'); + + $this->expectException(PermissionDoesNotExist::class); + + $this->testUserRole->syncPermissions('edit-articles', 'permission-that-does-not-exist'); + + $this->assertTrue($this->testUserRole->fresh()->hasDirectPermission('edit-news')); + } + /** @test */ public function it_can_revoke_a_permission() { From 7a1e128e2e5ffceb03e5dcd91c53c4a4db5aac8f Mon Sep 17 00:00:00 2001 From: drbyte Date: Thu, 16 Mar 2023 20:32:05 +0000 Subject: [PATCH 231/648] Fix styling --- src/Commands/UpgradeForTeams.php | 1 - src/Contracts/Permission.php | 8 -------- src/Contracts/Role.php | 6 ------ src/Contracts/Wildcard.php | 1 - src/Guard.php | 4 ---- src/Models/Permission.php | 10 ---------- src/Models/Role.php | 3 --- src/PermissionRegistrar.php | 14 -------------- src/PermissionServiceProvider.php | 2 -- src/Traits/HasPermissions.php | 14 -------------- src/Traits/HasRoles.php | 9 --------- src/WildcardPermission.php | 14 -------------- src/helpers.php | 1 - tests/RoleWithNesting.php | 3 --- 14 files changed, 90 deletions(-) diff --git a/src/Commands/UpgradeForTeams.php b/src/Commands/UpgradeForTeams.php index 210ca4407..70c790bc1 100644 --- a/src/Commands/UpgradeForTeams.php +++ b/src/Commands/UpgradeForTeams.php @@ -78,7 +78,6 @@ protected function createMigration() * Build a warning regarding possible duplication * due to already existing migrations. * - * @param array $existingMigrations * @return string */ protected function getExistingMigrationsWarning(array $existingMigrations) diff --git a/src/Contracts/Permission.php b/src/Contracts/Permission.php index 91e0162a4..fe151d077 100644 --- a/src/Contracts/Permission.php +++ b/src/Contracts/Permission.php @@ -8,17 +8,13 @@ interface Permission { /** * A permission can be applied to roles. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany */ public function roles(): BelongsToMany; /** * Find a permission by its name. * - * @param string $name * @param string|null $guardName - * @return Permission * * @throws \Spatie\Permission\Exceptions\PermissionDoesNotExist */ @@ -27,9 +23,7 @@ public static function findByName(string $name, $guardName): self; /** * Find a permission by its id. * - * @param int $id * @param string|null $guardName - * @return Permission * * @throws \Spatie\Permission\Exceptions\PermissionDoesNotExist */ @@ -38,9 +32,7 @@ public static function findById(int $id, $guardName): self; /** * Find or Create a permission by its name and guard name. * - * @param string $name * @param string|null $guardName - * @return Permission */ public static function findOrCreate(string $name, $guardName): self; } diff --git a/src/Contracts/Role.php b/src/Contracts/Role.php index 28f3d4308..94773e4fa 100644 --- a/src/Contracts/Role.php +++ b/src/Contracts/Role.php @@ -8,15 +8,12 @@ interface Role { /** * A role may be given various permissions. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany */ public function permissions(): BelongsToMany; /** * Find a role by its name and guard name. * - * @param string $name * @param string|null $guardName * @return \Spatie\Permission\Contracts\Role * @@ -27,7 +24,6 @@ public static function findByName(string $name, $guardName): self; /** * Find a role by its id and guard name. * - * @param int $id * @param string|null $guardName * @return \Spatie\Permission\Contracts\Role * @@ -38,7 +34,6 @@ public static function findById(int $id, $guardName): self; /** * Find or create a role by its name and guard name. * - * @param string $name * @param string|null $guardName * @return \Spatie\Permission\Contracts\Role */ @@ -48,7 +43,6 @@ public static function findOrCreate(string $name, $guardName): self; * Determine if the user may perform the given permission. * * @param string|\Spatie\Permission\Contracts\Permission $permission - * @return bool */ public function hasPermissionTo($permission): bool; } diff --git a/src/Contracts/Wildcard.php b/src/Contracts/Wildcard.php index 4e9478bd5..d2aabd8f8 100644 --- a/src/Contracts/Wildcard.php +++ b/src/Contracts/Wildcard.php @@ -6,7 +6,6 @@ interface Wildcard { /** * @param string|Wildcard $permission - * @return bool */ public function implies($permission): bool; } diff --git a/src/Guard.php b/src/Guard.php index 395d4cfec..376ffb897 100644 --- a/src/Guard.php +++ b/src/Guard.php @@ -12,7 +12,6 @@ class Guard * as indicated by the presence of a $guard_name property or a guardName() method on the model. * * @param string|Model $model model class object or name - * @return Collection */ public static function getNames($model): Collection { @@ -45,9 +44,6 @@ public static function getNames($model): Collection * - filter for provider models matching the model $class being checked (important for Lumen) * - keys() gives just the names of the matched guards * - return collection of guard names - * - * @param string $class - * @return Collection */ protected static function getConfigAuthGuards(string $class): Collection { diff --git a/src/Models/Permission.php b/src/Models/Permission.php index d5a7e39be..247c812dc 100644 --- a/src/Models/Permission.php +++ b/src/Models/Permission.php @@ -84,9 +84,7 @@ public function users(): BelongsToMany /** * Find a permission by its name (and optionally guardName). * - * @param string $name * @param string|null $guardName - * @return \Spatie\Permission\Contracts\Permission * * @throws \Spatie\Permission\Exceptions\PermissionDoesNotExist */ @@ -106,7 +104,6 @@ public static function findByName(string $name, $guardName = null): PermissionCo * * @param int|string $id * @param string|null $guardName - * @return \Spatie\Permission\Contracts\Permission * * @throws \Spatie\Permission\Exceptions\PermissionDoesNotExist */ @@ -125,9 +122,7 @@ public static function findById($id, $guardName = null): PermissionContract /** * Find or create permission by its name (and optionally guardName). * - * @param string $name * @param string|null $guardName - * @return \Spatie\Permission\Contracts\Permission */ public static function findOrCreate(string $name, $guardName = null): PermissionContract { @@ -143,10 +138,6 @@ public static function findOrCreate(string $name, $guardName = null): Permission /** * Get the current cached permissions. - * - * @param array $params - * @param bool $onlyOne - * @return \Illuminate\Database\Eloquent\Collection */ protected static function getPermissions(array $params = [], bool $onlyOne = false): Collection { @@ -158,7 +149,6 @@ protected static function getPermissions(array $params = [], bool $onlyOne = fal /** * Get the current cached first permission. * - * @param array $params * @return \Spatie\Permission\Contracts\Permission */ protected static function getPermission(array $params = []): ?PermissionContract diff --git a/src/Models/Role.php b/src/Models/Role.php index fe9f10117..a18d80930 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -88,7 +88,6 @@ public function users(): BelongsToMany /** * Find a role by its name and guard name. * - * @param string $name * @param string|null $guardName * @return \Spatie\Permission\Contracts\Role|\Spatie\Permission\Models\Role * @@ -130,7 +129,6 @@ public static function findById($id, $guardName = null): RoleContract /** * Find or create role by its name (and optionally guardName). * - * @param string $name * @param string|null $guardName * @return \Spatie\Permission\Contracts\Role|\Spatie\Permission\Models\Role */ @@ -172,7 +170,6 @@ protected static function findByParam(array $params = []) * Determine if the user may perform the given permission. * * @param string|Permission $permission - * @return bool * * @throws \Spatie\Permission\Exceptions\GuardDoesNotMatch */ diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index a3fc36f12..986477129 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -60,8 +60,6 @@ class PermissionRegistrar /** * PermissionRegistrar constructor. - * - * @param \Illuminate\Cache\CacheManager $cacheManager */ public function __construct(CacheManager $cacheManager) { @@ -130,8 +128,6 @@ public function getPermissionsTeamId() /** * Register the permission check method on the gate. * We resolve the Gate fresh here, for benefit of long-running instances. - * - * @return bool */ public function registerPermissions(Gate $gate): bool { @@ -197,10 +193,6 @@ private function loadPermissions() /** * Get the permissions based on the passed params. - * - * @param array $params - * @param bool $onlyOne - * @return \Illuminate\Database\Eloquent\Collection */ public function getPermissions(array $params = [], bool $onlyOne = false): Collection { @@ -227,8 +219,6 @@ public function getPermissions(array $params = [], bool $onlyOne = false): Colle /** * Get an instance of the permission class. - * - * @return \Spatie\Permission\Contracts\Permission */ public function getPermissionClass(): Permission { @@ -246,8 +236,6 @@ public function setPermissionClass($permissionClass) /** * Get an instance of the role class. - * - * @return \Spatie\Permission\Contracts\Role */ public function getRoleClass(): Role { @@ -280,8 +268,6 @@ protected function getPermissionsWithRoles(): Collection /** * Changes array keys with alias - * - * @return array */ private function aliasedArray($model): array { diff --git a/src/PermissionServiceProvider.php b/src/PermissionServiceProvider.php index 4abcd2cb8..bce255014 100644 --- a/src/PermissionServiceProvider.php +++ b/src/PermissionServiceProvider.php @@ -168,8 +168,6 @@ protected function registerMacroHelpers() /** * Returns existing migration file if found, else uses the current timestamp. - * - * @return string */ protected function getMigrationFileName($migrationFileName): string { diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index ee6d07da1..fbea0a7ec 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -90,9 +90,7 @@ public function permissions(): BelongsToMany /** * Scope the model query to certain permissions only. * - * @param \Illuminate\Database\Eloquent\Builder $query * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection $permissions - * @return \Illuminate\Database\Eloquent\Builder */ public function scopePermission(Builder $query, $permissions): Builder { @@ -120,7 +118,6 @@ public function scopePermission(Builder $query, $permissions): Builder /** * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection $permissions - * @return array * * @throws \Spatie\Permission\Exceptions\PermissionDoesNotExist */ @@ -178,7 +175,6 @@ public function filterPermission($permission, $guardName = null) * * @param string|int|\Spatie\Permission\Contracts\Permission $permission * @param string|null $guardName - * @return bool * * @throws PermissionDoesNotExist */ @@ -198,7 +194,6 @@ public function hasPermissionTo($permission, $guardName = null): bool * * @param string|int|\Spatie\Permission\Contracts\Permission $permission * @param string|null $guardName - * @return bool */ protected function hasWildcardPermission($permission, $guardName = null): bool { @@ -238,7 +233,6 @@ protected function hasWildcardPermission($permission, $guardName = null): bool * * @param string|int|\Spatie\Permission\Contracts\Permission $permission * @param string|null $guardName - * @return bool */ public function checkPermissionTo($permission, $guardName = null): bool { @@ -253,7 +247,6 @@ public function checkPermissionTo($permission, $guardName = null): bool * Determine if the model has any of the given permissions. * * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection ...$permissions - * @return bool */ public function hasAnyPermission(...$permissions): bool { @@ -272,7 +265,6 @@ public function hasAnyPermission(...$permissions): bool * Determine if the model has all of the given permissions. * * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection ...$permissions - * @return bool */ public function hasAllPermissions(...$permissions): bool { @@ -289,9 +281,6 @@ public function hasAllPermissions(...$permissions): bool /** * Determine if the model has, via roles, the given permission. - * - * @param \Spatie\Permission\Contracts\Permission $permission - * @return bool */ protected function hasPermissionViaRole(Permission $permission): bool { @@ -302,7 +291,6 @@ protected function hasPermissionViaRole(Permission $permission): bool * Determine if the model has the given permission. * * @param string|int|\Spatie\Permission\Contracts\Permission $permission - * @return bool * * @throws PermissionDoesNotExist */ @@ -506,7 +494,6 @@ public function forgetCachedPermissions() * Check if the model has All of the requested Direct permissions. * * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection ...$permissions - * @return bool */ public function hasAllDirectPermissions(...$permissions): bool { @@ -525,7 +512,6 @@ public function hasAllDirectPermissions(...$permissions): bool * Check if the model has Any of the requested Direct permissions. * * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection ...$permissions - * @return bool */ public function hasAnyDirectPermission(...$permissions): bool { diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index 267528c2e..fac948e06 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -67,10 +67,8 @@ public function roles(): BelongsToMany /** * Scope the model query to certain roles only. * - * @param \Illuminate\Database\Eloquent\Builder $query * @param string|int|array|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection $roles * @param string $guard - * @return \Illuminate\Database\Eloquent\Builder */ public function scopeRole(Builder $query, $roles, $guard = null): Builder { @@ -196,8 +194,6 @@ public function syncRoles(...$roles) * Determine if the model has (one of) the given role(s). * * @param string|int|array|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection $roles - * @param string|null $guard - * @return bool */ public function hasRole($roles, string $guard = null): bool { @@ -245,7 +241,6 @@ public function hasRole($roles, string $guard = null): bool * Alias to hasRole() but without Guard controls * * @param string|int|array|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection $roles - * @return bool */ public function hasAnyRole(...$roles): bool { @@ -256,8 +251,6 @@ public function hasAnyRole(...$roles): bool * Determine if the model has all of the given role(s). * * @param string|array|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection $roles - * @param string|null $guard - * @return bool */ public function hasAllRoles($roles, string $guard = null): bool { @@ -292,8 +285,6 @@ public function hasAllRoles($roles, string $guard = null): bool * Determine if the model has exactly all of the given role(s). * * @param string|array|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection $roles - * @param string|null $guard - * @return bool */ public function hasExactRoles($roles, string $guard = null): bool { diff --git a/src/WildcardPermission.php b/src/WildcardPermission.php index da1ce33bf..f8946a5c5 100644 --- a/src/WildcardPermission.php +++ b/src/WildcardPermission.php @@ -23,9 +23,6 @@ class WildcardPermission implements Wildcard /** @var Collection */ protected $parts; - /** - * @param string $permission - */ public function __construct(string $permission) { $this->permission = $permission; @@ -36,7 +33,6 @@ public function __construct(string $permission) /** * @param string|WildcardPermission $permission - * @return bool */ public function implies($permission): bool { @@ -70,11 +66,6 @@ public function implies($permission): bool return true; } - /** - * @param Collection $part - * @param Collection $otherPart - * @return bool - */ protected function containsAll(Collection $part, Collection $otherPart): bool { foreach ($otherPart->toArray() as $item) { @@ -86,9 +77,6 @@ protected function containsAll(Collection $part, Collection $otherPart): bool return true; } - /** - * @return Collection - */ public function getParts(): Collection { return $this->parts; @@ -96,8 +84,6 @@ public function getParts(): Collection /** * Sets the different parts and subparts from permission string. - * - * @return void */ protected function setParts(): void { diff --git a/src/helpers.php b/src/helpers.php index ac6fc3699..b29354655 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -2,7 +2,6 @@ if (! function_exists('getModelForGuard')) { /** - * @param string $guard * @return string|null */ function getModelForGuard(string $guard) diff --git a/tests/RoleWithNesting.php b/tests/RoleWithNesting.php index 9a8c33a6a..0be2b3076 100644 --- a/tests/RoleWithNesting.php +++ b/tests/RoleWithNesting.php @@ -25,9 +25,6 @@ public function parents() 'parent_id'); } - /** - * @return BelongsToMany - */ public function children(): BelongsToMany { return $this->belongsToMany( From 1c16bcd4a88d69ce2aa3147e9ce308bd6ad39a87 Mon Sep 17 00:00:00 2001 From: Faraz Samapoor Date: Sun, 19 Mar 2023 11:28:07 +0330 Subject: [PATCH 232/648] Rephrases teams-permissions.md to improve the readability. --- docs/basic-usage/teams-permissions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/basic-usage/teams-permissions.md b/docs/basic-usage/teams-permissions.md index 7c2bf2f54..9ff4b3364 100644 --- a/docs/basic-usage/teams-permissions.md +++ b/docs/basic-usage/teams-permissions.md @@ -23,8 +23,8 @@ Also, if you want to use a custom foreign key for teams you must change in the p ## Working with Teams Permissions -After implements on login a solution for select a team on authentication (for example set `team_id` of the current selected team on **session**: `session(['team_id' => $team->team_id]);` ), -we can set global `team_id` from anywhere, but works better if you create a `Middleware`, example: +After implementing a solution for selecting a team on the authentication process (for example, setting the `team_id` of the currently selected team on the **session**: `session(['team_id' => $team->team_id]);` ), +we can set global `team_id` from anywhere, but works better if you create a `Middleware`. Example: ```php namespace App\Http\Middleware; From f96456de92637fed4b716017c5991442cf185c84 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Sun, 19 Mar 2023 16:23:10 -0400 Subject: [PATCH 233/648] [Docs] Update teams-permissions.md for readability --- docs/basic-usage/teams-permissions.md | 31 +++++++++++++++++---------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/docs/basic-usage/teams-permissions.md b/docs/basic-usage/teams-permissions.md index 9ff4b3364..1908765a7 100644 --- a/docs/basic-usage/teams-permissions.md +++ b/docs/basic-usage/teams-permissions.md @@ -3,9 +3,14 @@ title: Teams permissions weight: 3 --- -NOTE: Those changes must be made before performing the migration. If you have already run the migration and want to upgrade your solution, you can run the artisan console command `php artisan permission:setup-teams`, to create a new migration file named [xxxx_xx_xx_xx_add_teams_fields.php](https://github.com/spatie/laravel-permission/blob/main/database/migrations/add_teams_fields.php.stub) and then run `php artisan migrate` to upgrade your database tables. +When enabled, teams permissions offers you flexible control for a variety of scenarios. The idea behind teams permissions is inspired by the default permission implementation of [Laratrust](https://laratrust.santigarcor.me/). -When enabled, teams permissions offers you a flexible control for a variety of scenarios. The idea behind teams permissions is inspired by the default permission implementation of [Laratrust](https://laratrust.santigarcor.me/). + +## Enabling Teams Permissions Feature + +NOTE: These configuration changes must be made **before** performing the migration when first installing the package. + +If you have already run the migration and want to upgrade your implementation, you can run the artisan console command `php artisan spatie-permission:setup-teams`, to create a new migration file named [xxxx_xx_xx_xx_add_teams_fields.php](https://github.com/spatie/laravel-permission/blob/main/database/migrations/add_teams_fields.php.stub) and then run `php artisan migrate` to upgrade your database tables. Teams permissions can be enabled in the permission config file: @@ -15,7 +20,7 @@ Teams permissions can be enabled in the permission config file: 'teams' => true, ``` -Also, if you want to use a custom foreign key for teams you must change in the permission config file: +Also, if you want to use a custom foreign key for teams you set it in the permission config file: ```php // config/permission.php 'team_foreign_key' => 'custom_team_id', @@ -24,7 +29,9 @@ Also, if you want to use a custom foreign key for teams you must change in the p ## Working with Teams Permissions After implementing a solution for selecting a team on the authentication process (for example, setting the `team_id` of the currently selected team on the **session**: `session(['team_id' => $team->team_id]);` ), -we can set global `team_id` from anywhere, but works better if you create a `Middleware`. Example: +we can set global `team_id` from anywhere, but works better if you create a `Middleware`. + +Example Team Middleware: ```php namespace App\Http\Middleware; @@ -46,17 +53,17 @@ class TeamsPermission{ } } ``` -NOTE: You must add your custom `Middleware` to `$middlewarePriority` on `app/Http/Kernel.php`. +NOTE: You must add your custom `Middleware` to `$middlewarePriority` in `app/Http/Kernel.php`. ## Roles Creating When creating a role you can pass the `team_id` as an optional parameter ```php -// with null team_id it creates a global role, global roles can be assigned to any team and they are unique +// with null team_id it creates a global role; global roles can be assigned to any team and they are unique Role::create(['name' => 'writer', 'team_id' => null]); -// creates a role with team_id = 1, team roles can have the same name on different teams +// creates a role with team_id = 1; team roles can have the same name on different teams Role::create(['name' => 'reader', 'team_id' => 1]); // creating a role without team_id makes the role take the default global team_id @@ -65,11 +72,13 @@ Role::create(['name' => 'reviewer']); ## Roles/Permissions Assignment & Removal -The role/permission assignment and removal are the same, but they take the global `team_id` set on login for sync. +The role/permission assignment and removal for teams are the same as without teams, but they take the global `team_id` set on login for sync. ## Defining a Super-Admin on Teams -Global roles can be assigned to different teams, `team_id` as the primary key of the relationships is always required. If you want a "Super Admin" global role for a user, when you creates a new team you must assign it to your user. Example: +Global roles can be assigned to different teams, and `team_id` as the primary key of the relationships is always required. + +If you want a "Super Admin" global role for a user, when you create a new team you must assign it to your user. Example: ```php namespace App\Models; @@ -83,13 +92,13 @@ class YourTeamModel extends \Illuminate\Database\Eloquent\Model // here assign this team to a global user with global default role self::created(function ($model) { - // get session team_id for restore it later + // temporary: get session team_id for restore at end $session_team_id = getPermissionsTeamId(); // set actual new team_id to package instance setPermissionsTeamId($model); // get the admin user and assign roles/permissions on new team model User::find('your_user_id')->assignRole('Super Admin'); - // restore session team_id to package instance + // restore session team_id to package instance using temporary value stored above setPermissionsTeamId($session_team_id); }); } From 21367074ab4177fdbf4c9fbc2a4ebeabb853266a Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Sun, 19 Mar 2023 16:29:45 -0400 Subject: [PATCH 234/648] [Docs] Update teams-permissions.md for readability --- docs/basic-usage/teams-permissions.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/basic-usage/teams-permissions.md b/docs/basic-usage/teams-permissions.md index 1908765a7..d62fa92c8 100644 --- a/docs/basic-usage/teams-permissions.md +++ b/docs/basic-usage/teams-permissions.md @@ -5,14 +5,12 @@ weight: 3 When enabled, teams permissions offers you flexible control for a variety of scenarios. The idea behind teams permissions is inspired by the default permission implementation of [Laratrust](https://laratrust.santigarcor.me/). - ## Enabling Teams Permissions Feature NOTE: These configuration changes must be made **before** performing the migration when first installing the package. If you have already run the migration and want to upgrade your implementation, you can run the artisan console command `php artisan spatie-permission:setup-teams`, to create a new migration file named [xxxx_xx_xx_xx_add_teams_fields.php](https://github.com/spatie/laravel-permission/blob/main/database/migrations/add_teams_fields.php.stub) and then run `php artisan migrate` to upgrade your database tables. - Teams permissions can be enabled in the permission config file: ```php @@ -28,7 +26,8 @@ Also, if you want to use a custom foreign key for teams you set it in the permis ## Working with Teams Permissions -After implementing a solution for selecting a team on the authentication process (for example, setting the `team_id` of the currently selected team on the **session**: `session(['team_id' => $team->team_id]);` ), +After implementing a solution for selecting a team on the authentication process +(for example, setting the `team_id` of the currently selected team on the **session**: `session(['team_id' => $team->team_id]);` ), we can set global `team_id` from anywhere, but works better if you create a `Middleware`. Example Team Middleware: @@ -72,11 +71,11 @@ Role::create(['name' => 'reviewer']); ## Roles/Permissions Assignment & Removal -The role/permission assignment and removal for teams are the same as without teams, but they take the global `team_id` set on login for sync. +The role/permission assignment and removal for teams are the same as without teams, but they take the global `team_id` which is set on login. ## Defining a Super-Admin on Teams -Global roles can be assigned to different teams, and `team_id` as the primary key of the relationships is always required. +Global roles can be assigned to different teams, and `team_id` (which is the primary key of the relationships) is always required. If you want a "Super Admin" global role for a user, when you create a new team you must assign it to your user. Example: From 33543616f673f30a1146f8f338b58ecb54180efc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Mar 2023 06:01:16 +0000 Subject: [PATCH 235/648] Bump aglipanci/laravel-pint-action from 2.1.0 to 2.2.0 Bumps [aglipanci/laravel-pint-action](https://github.com/aglipanci/laravel-pint-action) from 2.1.0 to 2.2.0. - [Release notes](https://github.com/aglipanci/laravel-pint-action/releases) - [Commits](https://github.com/aglipanci/laravel-pint-action/compare/2.1.0...2.2.0) --- updated-dependencies: - dependency-name: aglipanci/laravel-pint-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/fix-php-code-style-issues.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/fix-php-code-style-issues.yml b/.github/workflows/fix-php-code-style-issues.yml index 39a77e7b3..f76938275 100644 --- a/.github/workflows/fix-php-code-style-issues.yml +++ b/.github/workflows/fix-php-code-style-issues.yml @@ -16,7 +16,7 @@ jobs: ref: ${{ github.head_ref }} - name: Fix PHP code style issues - uses: aglipanci/laravel-pint-action@2.1.0 + uses: aglipanci/laravel-pint-action@2.2.0 - name: Commit changes uses: stefanzweifel/git-auto-commit-action@v4 From aebf4c942f10b175d5a3f3b881816fe0b3a5900d Mon Sep 17 00:00:00 2001 From: erikn69 Date: Mon, 20 Mar 2023 14:33:03 -0500 Subject: [PATCH 236/648] Fix nesting tests --- src/Models/Permission.php | 6 +- src/Models/Role.php | 2 +- tests/Role.php | 26 +++++++ tests/RoleWithNesting.php | 36 --------- tests/RoleWithNestingTest.php | 77 +++++++++---------- .../roles_with_nesting_migration.php.stub | 40 ---------- 6 files changed, 64 insertions(+), 123 deletions(-) delete mode 100644 tests/RoleWithNesting.php delete mode 100644 tests/customMigrations/roles_with_nesting_migration.php.stub diff --git a/src/Models/Permission.php b/src/Models/Permission.php index 247c812dc..e7b428c9d 100644 --- a/src/Models/Permission.php +++ b/src/Models/Permission.php @@ -34,11 +34,7 @@ public function __construct(array $attributes = []) parent::__construct($attributes); $this->guarded[] = $this->primaryKey; - } - - public function getTable() - { - return config('permission.table_names.permissions', parent::getTable()); + $this->table = config('permission.table_names.permissions') ?: parent::getTable(); } public static function create(array $attributes = []) diff --git a/src/Models/Role.php b/src/Models/Role.php index a18d80930..b8ad70353 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -34,7 +34,7 @@ public function __construct(array $attributes = []) parent::__construct($attributes); $this->guarded[] = $this->primaryKey; - $this->table = config('permission.table_names.roles', parent::getTable()); + $this->table = config('permission.table_names.roles') ?: parent::getTable(); } public static function create(array $attributes = []) diff --git a/tests/Role.php b/tests/Role.php index 79f8cdbfd..86bdd609f 100644 --- a/tests/Role.php +++ b/tests/Role.php @@ -11,6 +11,32 @@ class Role extends \Spatie\Permission\Models\Role 'name', ]; + const HIERARCHY_TABLE = 'roles_hierarchy'; + + /** + * @return BelongsToMany + */ + public function parents() + { + return $this->belongsToMany( + static::class, + static::HIERARCHY_TABLE, + 'child_id', + 'parent_id'); + } + + /** + * @return BelongsToMany + */ + public function children() + { + return $this->belongsToMany( + static::class, + static::HIERARCHY_TABLE, + 'parent_id', + 'child_id'); + } + protected static function boot() { parent::boot(); diff --git a/tests/RoleWithNesting.php b/tests/RoleWithNesting.php deleted file mode 100644 index 0be2b3076..000000000 --- a/tests/RoleWithNesting.php +++ /dev/null @@ -1,36 +0,0 @@ -belongsToMany( - static::class, - static::HIERARCHY_TABLE, - 'child_id', - 'parent_id'); - } - - public function children(): BelongsToMany - { - return $this->belongsToMany( - static::class, - static::HIERARCHY_TABLE, - 'parent_id', - 'child_id'); - } -} diff --git a/tests/RoleWithNestingTest.php b/tests/RoleWithNestingTest.php index 4261dc03f..6fcc82e73 100644 --- a/tests/RoleWithNestingTest.php +++ b/tests/RoleWithNestingTest.php @@ -4,63 +4,58 @@ class RoleWithNestingTest extends TestCase { - private static $old_migration; + /** @var bool */ + protected $useCustomModels = true; - /** - * @var RoleWithNesting[] - */ + /** @var Role[] */ protected $parent_roles = []; - /** - * @var RoleWithNesting[] - */ + /** @var Role[] */ protected $child_roles = []; - public static function setUpBeforeClass(): void - { - parent::setUpBeforeClass(); - self::$old_migration = self::$migration; - self::$migration = self::getMigration(); - } public function setUp(): void { parent::setUp(); - $this->parent_roles = []; - $this->child_roles = []; - $this->parent_roles['has_no_children'] = RoleWithNesting::create(['name' => 'has_no_children']); - $this->parent_roles['has_1_child'] = RoleWithNesting::create(['name' => 'has_1_child']); - $this->parent_roles['has_3_children'] = RoleWithNesting::create(['name' => 'has_3_children']); - $this->child_roles['has_no_parents'] = RoleWithNesting::create(['name' => 'has_no_parents']); - $this->child_roles['has_1_parent'] = RoleWithNesting::create(['name' => 'has_1_parent']); - $this->child_roles['has_2_parents'] = RoleWithNesting::create(['name' => 'has_2_parents']); - $this->child_roles['third_child'] = RoleWithNesting::create(['name' => 'third_child']); + $this->parent_roles = [ + 'has_no_children' => Role::create(['name' => 'has_no_children']), + 'has_1_child' => Role::create(['name' => 'has_1_child']), + 'has_3_children' => Role::create(['name' => 'has_3_children']), + ]; + $this->child_roles = [ + 'has_no_parents' => Role::create(['name' => 'has_no_parents']), + 'has_1_parent' => Role::create(['name' => 'has_1_parent']), + 'has_2_parents' => Role::create(['name' => 'has_2_parents']), + 'third_child' => Role::create(['name' => 'third_child']), + ]; $this->parent_roles['has_1_child']->children()->attach($this->child_roles['has_2_parents']); - $this->parent_roles['has_3_children']->children()->attach($this->child_roles['has_2_parents']); - $this->parent_roles['has_3_children']->children()->attach($this->child_roles['has_1_parent']); - $this->parent_roles['has_3_children']->children()->attach($this->child_roles['third_child']); + $this->parent_roles['has_3_children']->children()->attach([ + $this->child_roles['has_2_parents']->getKey(), + $this->child_roles['has_1_parent']->getKey(), + $this->child_roles['third_child']->getKey() + ]); } - public static function tearDownAfterClass(): void - { - parent::tearDownAfterClass(); - self::$migration = self::$old_migration; - } - - protected function getEnvironmentSetUp($app) + /** + * Set up the database. + * + * @param \Illuminate\Foundation\Application $app + */ + protected function setUpDatabase($app) { - parent::getEnvironmentSetUp($app); - $app['config']->set('permission.models.role', RoleWithNesting::class); - $app['config']->set('permission.table_names.roles', 'nesting_role'); - } + parent::setUpDatabase($app); - protected static function getMigration() - { - require_once __DIR__.'/customMigrations/roles_with_nesting_migration.php.stub'; + $tableRoles = $app['config']->get('permission.table_names.roles'); - return new \CreatePermissionTablesWithNested(); + $app['db']->connection()->getSchemaBuilder()->create(Role::HIERARCHY_TABLE, function ($table) use ($tableRoles) { + $table->id(); + $table->uuid("parent_id"); + $table->uuid("child_id"); + $table->foreign("parent_id")->references("role_test_id")->on($tableRoles); + $table->foreign("child_id")->references("role_test_id")->on($tableRoles); + }); } /** @test @@ -71,7 +66,7 @@ public function it_returns_correct_withCount_of_nested_roles($role_group, $index $role = $this->$role_group[$index]; $count_field_name = sprintf('%s_count', $relation); - $actualCount = intval(RoleWithNesting::query()->withCount($relation)->find($role->id)->$count_field_name); + $actualCount = intval(Role::withCount($relation)->find($role->getKey())->$count_field_name); $this->assertSame( $expectedCount, diff --git a/tests/customMigrations/roles_with_nesting_migration.php.stub b/tests/customMigrations/roles_with_nesting_migration.php.stub deleted file mode 100644 index 81909275c..000000000 --- a/tests/customMigrations/roles_with_nesting_migration.php.stub +++ /dev/null @@ -1,40 +0,0 @@ -id(); - $table->bigInteger("parent_id", false, true); - $table->bigInteger("child_id", false, true); - $table->foreign("parent_id")->references("id")->on($tableNames['roles']); - $table->foreign("child_id")->references("id")->on($tableNames['roles']); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - parent::down(); - Schema::drop(\Spatie\Permission\Test\RoleWithNesting::HIERARCHY_TABLE); - } -} From 090b129cce224d54ea3d62047adde2284bdd3d0f Mon Sep 17 00:00:00 2001 From: erikn69 Date: Tue, 21 Mar 2023 09:02:05 -0500 Subject: [PATCH 237/648] Fix delete permissions on Permissions Model --- src/Traits/HasPermissions.php | 4 ++ tests/HasPermissionsTest.php | 2 +- tests/HasPermissionsWithCustomModelsTest.php | 50 +++++++++++++++++++ tests/HasRolesTest.php | 2 +- tests/HasRolesWithCustomModelsTest.php | 51 ++++++++++++++++++++ tests/Permission.php | 4 ++ tests/Role.php | 4 ++ tests/TestCase.php | 13 ++++- 8 files changed, 126 insertions(+), 4 deletions(-) diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index fbea0a7ec..04a8b8abe 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -32,6 +32,10 @@ public static function bootHasPermissions() return; } + if (is_a($model, Permission::class)) { + return; + } + $teams = app(PermissionRegistrar::class)->teams; app(PermissionRegistrar::class)->teams = false; $model->permissions()->detach(); diff --git a/tests/HasPermissionsTest.php b/tests/HasPermissionsTest.php index a71867581..d6f584f75 100644 --- a/tests/HasPermissionsTest.php +++ b/tests/HasPermissionsTest.php @@ -217,7 +217,7 @@ public function it_throws_an_exception_when_trying_to_scope_a_permission_from_an } /** @test */ - public function it_doesnt_detach_permissions_when_soft_deleting() + public function it_doesnt_detach_permissions_when_user_soft_deleting() { $user = SoftDeletingUser::create(['email' => 'test@example.com']); $user->givePermissionTo(['edit-news']); diff --git a/tests/HasPermissionsWithCustomModelsTest.php b/tests/HasPermissionsWithCustomModelsTest.php index b491c8e05..28b7145bd 100644 --- a/tests/HasPermissionsWithCustomModelsTest.php +++ b/tests/HasPermissionsWithCustomModelsTest.php @@ -63,4 +63,54 @@ public function it_can_scope_users_using_a_uuid() $this->assertEquals(2, $scopedUsers1->count()); $this->assertEquals(1, $scopedUsers2->count()); } + + /** @test */ + public function it_doesnt_detach_roles_when_soft_deleting() + { + $this->testUserRole->givePermissionTo($this->testUserPermission); + + DB::enableQueryLog(); + $this->testUserPermission->delete(); + DB::disableQueryLog(); + + $this->assertSame(1, count(DB::getQueryLog())); + + $permission = Permission::onlyTrashed()->find($this->testUserPermission->getKey()); + + $this->assertEquals(1, DB::table(config('permission.table_names.role_has_permissions'))->where('permission_test_id', $this->testUserPermission->getKey())->count()); + } + + /** @test */ + public function it_doesnt_detach_users_when_soft_deleting() + { + $this->testUser->givePermissionTo($this->testUserPermission); + + DB::enableQueryLog(); + $this->testUserPermission->delete(); + DB::disableQueryLog(); + + $this->assertSame(1, count(DB::getQueryLog())); + + $permission = Permission::onlyTrashed()->find($this->testUserPermission->getKey()); + $permission->restore(); + + $this->assertEquals(1, DB::table(config('permission.table_names.model_has_permissions'))->where('permission_test_id', $this->testUserPermission->getKey())->count()); + } + + /** @test */ + public function it_does_detach_roles_when_force_deleting() + { + $this->testUserRole->givePermissionTo($this->testUserPermission); + + DB::enableQueryLog(); + $this->testUserPermission->forceDelete(); + DB::disableQueryLog(); + + $this->assertSame(2, count(DB::getQueryLog())); //avoid detach permissions on permissions + + $permission = Permission::withTrashed()->find($this->testUserPermission->getKey()); + + $this->assertNull($permission); + $this->assertEquals(0, DB::table(config('permission.table_names.role_has_permissions'))->where('permission_test_id', $this->testUserPermission->getKey())->count()); + } } diff --git a/tests/HasRolesTest.php b/tests/HasRolesTest.php index 1e7dcc8d5..43f7f6217 100644 --- a/tests/HasRolesTest.php +++ b/tests/HasRolesTest.php @@ -584,7 +584,7 @@ public function it_can_retrieve_role_names() } /** @test */ - public function it_does_not_detach_roles_when_soft_deleting() + public function it_does_not_detach_roles_when_user_soft_deleting() { $user = SoftDeletingUser::create(['email' => 'test@example.com']); $user->assignRole('testRole'); diff --git a/tests/HasRolesWithCustomModelsTest.php b/tests/HasRolesWithCustomModelsTest.php index d39739205..fe6ea443d 100644 --- a/tests/HasRolesWithCustomModelsTest.php +++ b/tests/HasRolesWithCustomModelsTest.php @@ -2,6 +2,8 @@ namespace Spatie\Permission\Test; +use DB; + class HasRolesWithCustomModelsTest extends HasRolesTest { /** @var bool */ @@ -12,4 +14,53 @@ public function it_can_use_custom_model_role() { $this->assertSame(get_class($this->testUserRole), Role::class); } + + /** @test */ + public function it_doesnt_detach_permissions_when_soft_deleting() + { + $this->testUserRole->givePermissionTo($this->testUserPermission); + + DB::enableQueryLog(); + $this->testUserRole->delete(); + DB::disableQueryLog(); + + $this->assertSame(1, count(DB::getQueryLog())); + + $role = Role::onlyTrashed()->find($this->testUserRole->getKey()); + + $this->assertEquals(1, DB::table(config('permission.table_names.role_has_permissions'))->where('role_test_id', $this->testUserRole->getKey())->count()); + } + + /** @test */ + public function it_doesnt_detach_users_when_soft_deleting() + { + $this->testUser->assignRole($this->testUserRole); + + DB::enableQueryLog(); + $this->testUserRole->delete(); + DB::disableQueryLog(); + + $this->assertSame(1, count(DB::getQueryLog())); + + $role = Role::onlyTrashed()->find($this->testUserRole->getKey()); + + $this->assertEquals(1, DB::table(config('permission.table_names.model_has_roles'))->where('role_test_id', $this->testUserRole->getKey())->count()); + } + + /** @test */ + public function it_does_detach_permissions_when_force_deleting() + { + $this->testUserRole->givePermissionTo($this->testUserPermission); + + DB::enableQueryLog(); + $this->testUserRole->forceDelete(); + DB::disableQueryLog(); + + $this->assertSame(2, count(DB::getQueryLog())); + + $role = Role::withTrashed()->find($this->testUserRole->getKey()); + + $this->assertNull($role); + $this->assertEquals(0, DB::table(config('permission.table_names.role_has_permissions'))->where('role_test_id', $this->testUserRole->getKey())->count()); + } } diff --git a/tests/Permission.php b/tests/Permission.php index 9d8f2cd91..be35d20d5 100644 --- a/tests/Permission.php +++ b/tests/Permission.php @@ -2,8 +2,12 @@ namespace Spatie\Permission\Test; +use Illuminate\Database\Eloquent\SoftDeletes; + class Permission extends \Spatie\Permission\Models\Permission { + use SoftDeletes; + protected $primaryKey = 'permission_test_id'; protected $visible = [ diff --git a/tests/Role.php b/tests/Role.php index 79f8cdbfd..fc09476f2 100644 --- a/tests/Role.php +++ b/tests/Role.php @@ -2,8 +2,12 @@ namespace Spatie\Permission\Test; +use Illuminate\Database\Eloquent\SoftDeletes; + class Role extends \Spatie\Permission\Models\Role { + use SoftDeletes; + protected $primaryKey = 'role_test_id'; protected $visible = [ diff --git a/tests/TestCase.php b/tests/TestCase.php index b04d17839..67fc537dd 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -116,13 +116,15 @@ protected function getEnvironmentSetUp($app) */ protected function setUpDatabase($app) { - $app['db']->connection()->getSchemaBuilder()->create('users', function (Blueprint $table) { + $schema = $app['db']->connection()->getSchemaBuilder(); + + $schema->create('users', function (Blueprint $table) { $table->increments('id'); $table->string('email'); $table->softDeletes(); }); - $app['db']->connection()->getSchemaBuilder()->create('admins', function (Blueprint $table) { + $schema->create('admins', function (Blueprint $table) { $table->increments('id'); $table->string('email'); }); @@ -136,6 +138,13 @@ protected function setUpDatabase($app) self::$migration->up(); } else { self::$customMigration->up(); + + $schema->table(config('permission.table_names.roles'), function (Blueprint $table) { + $table->softDeletes(); + }); + $schema->table(config('permission.table_names.permissions'), function (Blueprint $table) { + $table->softDeletes(); + }); } $this->testUser = User::create(['email' => 'test@user.com']); From c7e0eb2851bcc0bdb42fd82d25ff8236aec463b8 Mon Sep 17 00:00:00 2001 From: drbyte Date: Wed, 22 Mar 2023 03:00:16 +0000 Subject: [PATCH 238/648] Update CHANGELOG --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ebd548ee1..ec4980c00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to `laravel-permission` will be documented in this file +## 5.10.0 - 2023-03-22 + +### What's Changed + +- Fix delete permissions on Permissions Model by @erikn69 in https://github.com/spatie/laravel-permission/pull/2366 + ## 5.9.1 - 2023-02-06 Apologies for the break caused by 5.9.0 ! @@ -593,6 +599,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` 1. Also this is a good time to point out that now with v2.25.0 and v2.26.0 most permission-cache-reset scenarios may no longer be needed in your app, so it's worth reviewing those cases, as you may gain some app speed improvement by removing unnecessary cache resets. @@ -646,6 +653,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` ## 2.19.1 - 2018-09-14 From 25cb3a14faf5311e9344bd7b2e3d9a09bf6fd893 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Wed, 22 Mar 2023 08:53:00 -0500 Subject: [PATCH 239/648] Detach users on role/permission physical deletion --- src/Traits/HasPermissions.php | 13 +++++++------ src/Traits/HasRoles.php | 5 ++++- tests/HasPermissionsWithCustomModelsTest.php | 18 ++++++++++-------- tests/HasRolesWithCustomModelsTest.php | 17 ++++++++++------- 4 files changed, 31 insertions(+), 22 deletions(-) diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index 04a8b8abe..432723fbf 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -32,20 +32,21 @@ public static function bootHasPermissions() return; } - if (is_a($model, Permission::class)) { - return; - } - $teams = app(PermissionRegistrar::class)->teams; app(PermissionRegistrar::class)->teams = false; - $model->permissions()->detach(); + if (! is_a($model, Permission::class)) { + $model->permissions()->detach(); + } + if (is_a($model, Role::class)) { + $model->users()->detach(); + } app(PermissionRegistrar::class)->teams = $teams; }); } public function getPermissionClass() { - if (! isset($this->permissionClass)) { + if (! $this->permissionClass) { $this->permissionClass = app(PermissionRegistrar::class)->getPermissionClass(); } diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index fac948e06..7982c781c 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -27,13 +27,16 @@ public static function bootHasRoles() $teams = app(PermissionRegistrar::class)->teams; app(PermissionRegistrar::class)->teams = false; $model->roles()->detach(); + if (is_a($model, Permission::class)) { + $model->users()->detach(); + } app(PermissionRegistrar::class)->teams = $teams; }); } public function getRoleClass() { - if (! isset($this->roleClass)) { + if (! $this->roleClass) { $this->roleClass = app(PermissionRegistrar::class)->getRoleClass(); } diff --git a/tests/HasPermissionsWithCustomModelsTest.php b/tests/HasPermissionsWithCustomModelsTest.php index 28b7145bd..57f3310a6 100644 --- a/tests/HasPermissionsWithCustomModelsTest.php +++ b/tests/HasPermissionsWithCustomModelsTest.php @@ -77,7 +77,7 @@ public function it_doesnt_detach_roles_when_soft_deleting() $permission = Permission::onlyTrashed()->find($this->testUserPermission->getKey()); - $this->assertEquals(1, DB::table(config('permission.table_names.role_has_permissions'))->where('permission_test_id', $this->testUserPermission->getKey())->count()); + $this->assertEquals(1, DB::table(config('permission.table_names.role_has_permissions'))->where('permission_test_id', $permission->getKey())->count()); } /** @test */ @@ -92,25 +92,27 @@ public function it_doesnt_detach_users_when_soft_deleting() $this->assertSame(1, count(DB::getQueryLog())); $permission = Permission::onlyTrashed()->find($this->testUserPermission->getKey()); - $permission->restore(); - $this->assertEquals(1, DB::table(config('permission.table_names.model_has_permissions'))->where('permission_test_id', $this->testUserPermission->getKey())->count()); + $this->assertEquals(1, DB::table(config('permission.table_names.model_has_permissions'))->where('permission_test_id', $permission->getKey())->count()); } /** @test */ - public function it_does_detach_roles_when_force_deleting() + public function it_does_detach_roles_and_users_when_force_deleting() { - $this->testUserRole->givePermissionTo($this->testUserPermission); + $permission_id = $this->testUserPermission->getKey(); + $this->testUserRole->givePermissionTo($permission_id); + $this->testUser->givePermissionTo($permission_id); DB::enableQueryLog(); $this->testUserPermission->forceDelete(); DB::disableQueryLog(); - $this->assertSame(2, count(DB::getQueryLog())); //avoid detach permissions on permissions + $this->assertSame(3, count(DB::getQueryLog())); //avoid detach permissions on permissions - $permission = Permission::withTrashed()->find($this->testUserPermission->getKey()); + $permission = Permission::withTrashed()->find($permission_id); $this->assertNull($permission); - $this->assertEquals(0, DB::table(config('permission.table_names.role_has_permissions'))->where('permission_test_id', $this->testUserPermission->getKey())->count()); + $this->assertEquals(0, DB::table(config('permission.table_names.role_has_permissions'))->where('permission_test_id', $permission_id)->count()); + $this->assertEquals(0, DB::table(config('permission.table_names.model_has_permissions'))->where('permission_test_id', $permission_id)->count()); } } diff --git a/tests/HasRolesWithCustomModelsTest.php b/tests/HasRolesWithCustomModelsTest.php index fe6ea443d..3d7db9bb0 100644 --- a/tests/HasRolesWithCustomModelsTest.php +++ b/tests/HasRolesWithCustomModelsTest.php @@ -28,7 +28,7 @@ public function it_doesnt_detach_permissions_when_soft_deleting() $role = Role::onlyTrashed()->find($this->testUserRole->getKey()); - $this->assertEquals(1, DB::table(config('permission.table_names.role_has_permissions'))->where('role_test_id', $this->testUserRole->getKey())->count()); + $this->assertEquals(1, DB::table(config('permission.table_names.role_has_permissions'))->where('role_test_id', $role->getKey())->count()); } /** @test */ @@ -44,23 +44,26 @@ public function it_doesnt_detach_users_when_soft_deleting() $role = Role::onlyTrashed()->find($this->testUserRole->getKey()); - $this->assertEquals(1, DB::table(config('permission.table_names.model_has_roles'))->where('role_test_id', $this->testUserRole->getKey())->count()); + $this->assertEquals(1, DB::table(config('permission.table_names.model_has_roles'))->where('role_test_id', $role->getKey())->count()); } /** @test */ - public function it_does_detach_permissions_when_force_deleting() + public function it_does_detach_permissions_and_users_when_force_deleting() { - $this->testUserRole->givePermissionTo($this->testUserPermission); + $role_id = $this->testUserRole->getKey(); + $this->testUserPermission->assignRole($role_id); + $this->testUser->assignRole($role_id); DB::enableQueryLog(); $this->testUserRole->forceDelete(); DB::disableQueryLog(); - $this->assertSame(2, count(DB::getQueryLog())); + $this->assertSame(3, count(DB::getQueryLog())); - $role = Role::withTrashed()->find($this->testUserRole->getKey()); + $role = Role::withTrashed()->find($role_id); $this->assertNull($role); - $this->assertEquals(0, DB::table(config('permission.table_names.role_has_permissions'))->where('role_test_id', $this->testUserRole->getKey())->count()); + $this->assertEquals(0, DB::table(config('permission.table_names.role_has_permissions'))->where('role_test_id', $role_id)->count()); + $this->assertEquals(0, DB::table(config('permission.table_names.model_has_roles'))->where('role_test_id', $role_id)->count()); } } From ae8bb2108f048ab40b30a7403397962b7d0a255f Mon Sep 17 00:00:00 2001 From: erikn69 Date: Tue, 21 Mar 2023 12:57:46 -0500 Subject: [PATCH 240/648] Change clearClassPermissions to clearPermissionsCollection --- src/PermissionRegistrar.php | 13 +++++++++++-- src/PermissionServiceProvider.php | 2 +- tests/PermissionRegistarTest.php | 15 +++++++++++++++ 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index 986477129..ade4413bf 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -151,15 +151,24 @@ public function forgetCachedPermissions() } /** - * Clear class permissions. + * Clear already loaded permissions collection. * This is only intended to be called by the PermissionServiceProvider on boot, * so that long-running instances like Swoole don't keep old data in memory. */ - public function clearClassPermissions() + public function clearPermissionsCollection(): void { $this->permissions = null; } + /** + * @deprecated + * @alias of clearPermissionsCollection() + */ + public function clearClassPermissions() + { + $this->clearPermissionsCollection(); + } + /** * Load permissions from cache * This get cache and turns array into \Illuminate\Database\Eloquent\Collection diff --git a/src/PermissionServiceProvider.php b/src/PermissionServiceProvider.php index bce255014..e80b292a2 100644 --- a/src/PermissionServiceProvider.php +++ b/src/PermissionServiceProvider.php @@ -29,7 +29,7 @@ public function boot() if ($this->app->config['permission.register_permission_check_method']) { /** @var PermissionRegistrar $permissionLoader */ $permissionLoader = $app->get(PermissionRegistrar::class); - $permissionLoader->clearClassPermissions(); + $permissionLoader->clearPermissionsCollection(); $permissionLoader->registerPermissions($gate); } }); diff --git a/tests/PermissionRegistarTest.php b/tests/PermissionRegistarTest.php index b864b3e90..ac73bbaf8 100644 --- a/tests/PermissionRegistarTest.php +++ b/tests/PermissionRegistarTest.php @@ -6,6 +6,21 @@ class PermissionRegistarTest extends TestCase { + /** @test */ + public function it_can_clear_loaded_permissions_collection() { + $reflectedClass = new \ReflectionClass(app(PermissionRegistrar::class)); + $reflectedProperty = $reflectedClass->getProperty('permissions'); + $reflectedProperty->setAccessible(true); + + app(PermissionRegistrar::class)->getPermissions(); + + $this->assertNotNull($reflectedProperty->getValue(app(PermissionRegistrar::class))); + + app(PermissionRegistrar::class)->clearPermissionsCollection(); + + $this->assertNull($reflectedProperty->getValue(app(PermissionRegistrar::class))); + } + /** @test */ public function it_can_check_uids() { From cbfe2dff35d6d10736f66fd3cb3c9d5ee05db5ee Mon Sep 17 00:00:00 2001 From: drbyte Date: Wed, 22 Mar 2023 20:35:48 +0000 Subject: [PATCH 241/648] Fix styling --- src/PermissionRegistrar.php | 1 + tests/PermissionRegistarTest.php | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index ade4413bf..aa5a6dcdc 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -162,6 +162,7 @@ public function clearPermissionsCollection(): void /** * @deprecated + * * @alias of clearPermissionsCollection() */ public function clearClassPermissions() diff --git a/tests/PermissionRegistarTest.php b/tests/PermissionRegistarTest.php index ac73bbaf8..4d07a9e82 100644 --- a/tests/PermissionRegistarTest.php +++ b/tests/PermissionRegistarTest.php @@ -7,7 +7,8 @@ class PermissionRegistarTest extends TestCase { /** @test */ - public function it_can_clear_loaded_permissions_collection() { + public function it_can_clear_loaded_permissions_collection() + { $reflectedClass = new \ReflectionClass(app(PermissionRegistrar::class)); $reflectedProperty = $reflectedClass->getProperty('permissions'); $reflectedProperty->setAccessible(true); From 746c0e9dedda969497de6c5f959bc3937a87217e Mon Sep 17 00:00:00 2001 From: drbyte Date: Wed, 22 Mar 2023 20:43:25 +0000 Subject: [PATCH 242/648] Fix styling --- tests/RoleWithNestingTest.php | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/tests/RoleWithNestingTest.php b/tests/RoleWithNestingTest.php index 6fcc82e73..4872a0876 100644 --- a/tests/RoleWithNestingTest.php +++ b/tests/RoleWithNestingTest.php @@ -13,28 +13,27 @@ class RoleWithNestingTest extends TestCase /** @var Role[] */ protected $child_roles = []; - public function setUp(): void { parent::setUp(); $this->parent_roles = [ - 'has_no_children' => Role::create(['name' => 'has_no_children']), - 'has_1_child' => Role::create(['name' => 'has_1_child']), - 'has_3_children' => Role::create(['name' => 'has_3_children']), + 'has_no_children' => Role::create(['name' => 'has_no_children']), + 'has_1_child' => Role::create(['name' => 'has_1_child']), + 'has_3_children' => Role::create(['name' => 'has_3_children']), ]; $this->child_roles = [ - 'has_no_parents' => Role::create(['name' => 'has_no_parents']), - 'has_1_parent' => Role::create(['name' => 'has_1_parent']), - 'has_2_parents' => Role::create(['name' => 'has_2_parents']), - 'third_child' => Role::create(['name' => 'third_child']), + 'has_no_parents' => Role::create(['name' => 'has_no_parents']), + 'has_1_parent' => Role::create(['name' => 'has_1_parent']), + 'has_2_parents' => Role::create(['name' => 'has_2_parents']), + 'third_child' => Role::create(['name' => 'third_child']), ]; $this->parent_roles['has_1_child']->children()->attach($this->child_roles['has_2_parents']); $this->parent_roles['has_3_children']->children()->attach([ $this->child_roles['has_2_parents']->getKey(), $this->child_roles['has_1_parent']->getKey(), - $this->child_roles['third_child']->getKey() + $this->child_roles['third_child']->getKey(), ]); } @@ -51,10 +50,10 @@ protected function setUpDatabase($app) $app['db']->connection()->getSchemaBuilder()->create(Role::HIERARCHY_TABLE, function ($table) use ($tableRoles) { $table->id(); - $table->uuid("parent_id"); - $table->uuid("child_id"); - $table->foreign("parent_id")->references("role_test_id")->on($tableRoles); - $table->foreign("child_id")->references("role_test_id")->on($tableRoles); + $table->uuid('parent_id'); + $table->uuid('child_id'); + $table->foreign('parent_id')->references('role_test_id')->on($tableRoles); + $table->foreign('child_id')->references('role_test_id')->on($tableRoles); }); } From bfc3e3bc5600d000d05ebd55ba6cea2f75cbb621 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Wed, 22 Mar 2023 15:53:39 -0500 Subject: [PATCH 243/648] Use anonymous migrations --- database/migrations/add_teams_fields.php.stub | 8 ++++---- .../migrations/create_permission_tables.php.stub | 8 ++++---- tests/CommandTest.php | 6 +++--- tests/TestCase.php | 12 ++++-------- 4 files changed, 15 insertions(+), 19 deletions(-) diff --git a/database/migrations/add_teams_fields.php.stub b/database/migrations/add_teams_fields.php.stub index be79ee047..9fb25ac09 100644 --- a/database/migrations/add_teams_fields.php.stub +++ b/database/migrations/add_teams_fields.php.stub @@ -5,14 +5,14 @@ use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; -class AddTeamsFields extends Migration +return new class extends Migration { /** * Run the migrations. * * @return void */ - public function up() + public function up(): void { $teams = config('permission.teams'); $tableNames = config('permission.table_names'); @@ -88,8 +88,8 @@ class AddTeamsFields extends Migration * * @return void */ - public function down() + public function down(): void { } -} +}; diff --git a/database/migrations/create_permission_tables.php.stub b/database/migrations/create_permission_tables.php.stub index 4874c05a8..5a7301abe 100644 --- a/database/migrations/create_permission_tables.php.stub +++ b/database/migrations/create_permission_tables.php.stub @@ -4,14 +4,14 @@ use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; -class CreatePermissionTables extends Migration +return new class extends Migration { /** * Run the migrations. * * @return void */ - public function up() + public function up(): void { $teams = config('permission.teams'); $tableNames = config('permission.table_names'); @@ -125,7 +125,7 @@ class CreatePermissionTables extends Migration * * @return void */ - public function down() + public function down(): void { $tableNames = config('permission.table_names'); @@ -139,4 +139,4 @@ class CreatePermissionTables extends Migration Schema::drop($tableNames['roles']); Schema::drop($tableNames['permissions']); } -} +}; diff --git a/tests/CommandTest.php b/tests/CommandTest.php index be1541f88..f1088f0e7 100644 --- a/tests/CommandTest.php +++ b/tests/CommandTest.php @@ -143,9 +143,9 @@ public function it_can_setup_teams_upgrade() $matchingFiles = glob(database_path('migrations/*_add_teams_fields.php')); $this->assertTrue(count($matchingFiles) > 0); - include_once $matchingFiles[count($matchingFiles) - 1]; - (new \AddTeamsFields())->up(); - (new \AddTeamsFields())->up(); //test upgrade teams migration fresh + $AddTeamsFields = require($matchingFiles[count($matchingFiles) - 1]); + $AddTeamsFields->up(); + $AddTeamsFields->up(); //test upgrade teams migration fresh Role::create(['name' => 'new-role', 'team_test_id' => 1]); $role = Role::where('name', 'new-role')->first(); diff --git a/tests/TestCase.php b/tests/TestCase.php index 67fc537dd..0c86ff007 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -163,7 +163,6 @@ private function prepareMigration() { $migration = str_replace( [ - 'CreatePermissionTables', '(\'id\'); // permission id', '(\'id\'); // role id', 'references(\'id\') // permission id', @@ -173,7 +172,6 @@ private function prepareMigration() 'unsignedBigInteger($pivotPermission)', ], [ - 'CreatePermissionCustomTables', '(\'permission_test_id\');', '(\'role_test_id\');', 'references(\'permission_test_id\')', @@ -186,12 +184,10 @@ private function prepareMigration() ); file_put_contents(__DIR__.'/CreatePermissionCustomTables.php', $migration); - - include_once __DIR__.'/../database/migrations/create_permission_tables.php.stub'; - self::$migration = new \CreatePermissionTables(); - - include_once __DIR__.'/CreatePermissionCustomTables.php'; - self::$customMigration = new \CreatePermissionCustomTables(); + + self::$migration = require(__DIR__.'/../database/migrations/create_permission_tables.php.stub'); + + self::$customMigration = require(__DIR__.'/CreatePermissionCustomTables.php'); } protected function reloadPermissions() From b55c2c80cf4a95bad00cecf8de5959dc32633ca7 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Wed, 22 Mar 2023 20:25:08 -0400 Subject: [PATCH 244/648] Refactor Tests - update namespace from `Test` to `Tests` - namespace models into `Tests\TestModels` - update PHPUnit setUp() fixture (protected, not public) Thanks to @AyoobMH - ref PR #2240 Co-authored-by: AyoobMH --- composer.json | 2 +- tests/BladeTest.php | 6 +-- tests/CacheTest.php | 5 +- tests/CommandTest.php | 2 +- tests/CustomGateTest.php | 2 +- tests/GateTest.php | 2 +- tests/HasPermissionsTest.php | 4 +- tests/HasPermissionsWithCustomModelsTest.php | 6 ++- tests/HasRolesTest.php | 7 ++- tests/HasRolesWithCustomModelsTest.php | 5 +- tests/MultipleGuardsTest.php | 3 +- tests/PermissionMiddlewareTest.php | 15 +----- tests/PermissionRegistarTest.php | 2 +- tests/PermissionTest.php | 3 +- tests/RoleMiddlewareTest.php | 15 +----- tests/RoleOrPermissionMiddlewareTest.php | 15 +----- tests/RoleTest.php | 7 ++- tests/RoleWithNestingTest.php | 6 ++- tests/RouteTest.php | 21 +-------- tests/TeamHasPermissionsTest.php | 4 +- tests/TeamHasRolesTest.php | 3 +- tests/TestCase.php | 46 ++++++++++++++++--- tests/TestHelper.php | 2 +- tests/{ => TestModels}/Admin.php | 2 +- tests/{ => TestModels}/Manager.php | 2 +- tests/{ => TestModels}/Permission.php | 2 +- tests/{ => TestModels}/Role.php | 2 +- tests/{ => TestModels}/RuntimeRole.php | 2 +- tests/{ => TestModels}/SoftDeletingUser.php | 2 +- tests/{ => TestModels}/User.php | 2 +- tests/{ => TestModels}/WildcardPermission.php | 2 +- tests/WildcardHasPermissionsTest.php | 4 +- tests/WildcardMiddlewareTest.php | 15 +----- tests/WildcardRoleTest.php | 4 +- tests/WildcardRouteTest.php | 21 +-------- 35 files changed, 108 insertions(+), 135 deletions(-) rename tests/{ => TestModels}/Admin.php (92%) rename tests/{ => TestModels}/Manager.php (95%) rename tests/{ => TestModels}/Permission.php (93%) rename tests/{ => TestModels}/Role.php (96%) rename tests/{ => TestModels}/RuntimeRole.php (74%) rename tests/{ => TestModels}/SoftDeletingUser.php (76%) rename tests/{ => TestModels}/User.php (92%) rename tests/{ => TestModels}/WildcardPermission.php (87%) diff --git a/composer.json b/composer.json index afefa994d..759ef75a5 100644 --- a/composer.json +++ b/composer.json @@ -45,7 +45,7 @@ }, "autoload-dev": { "psr-4": { - "Spatie\\Permission\\Test\\": "tests" + "Spatie\\Permission\\Tests\\": "tests" } }, "config": { diff --git a/tests/BladeTest.php b/tests/BladeTest.php index 21d66437f..02a899083 100644 --- a/tests/BladeTest.php +++ b/tests/BladeTest.php @@ -1,13 +1,13 @@ runMiddleware($this->permissionMiddleware, 'admin-permission', 'admin') ); } - - protected function runMiddleware($middleware, $permission, $guard = null) - { - try { - return $middleware->handle(new Request(), function () { - return (new Response())->setContent(''); - }, $permission, $guard)->status(); - } catch (UnauthorizedException $e) { - return $e->getStatusCode(); - } - } } diff --git a/tests/PermissionRegistarTest.php b/tests/PermissionRegistarTest.php index 4d07a9e82..1e52362b5 100644 --- a/tests/PermissionRegistarTest.php +++ b/tests/PermissionRegistarTest.php @@ -1,6 +1,6 @@ runMiddleware($this->roleMiddleware, 'testAdminRole', 'admin') ); } - - protected function runMiddleware($middleware, $roleName, $guard = null) - { - try { - return $middleware->handle(new Request(), function () { - return (new Response())->setContent(''); - }, $roleName, $guard)->status(); - } catch (UnauthorizedException $e) { - return $e->getStatusCode(); - } - } } diff --git a/tests/RoleOrPermissionMiddlewareTest.php b/tests/RoleOrPermissionMiddlewareTest.php index 345f0209a..a6e8147e2 100644 --- a/tests/RoleOrPermissionMiddlewareTest.php +++ b/tests/RoleOrPermissionMiddlewareTest.php @@ -1,6 +1,6 @@ assertStringEndsWith('Necessary roles or permissions are some-permission, some-role', $message); } - - protected function runMiddleware($middleware, $name, $guard = null) - { - try { - return $middleware->handle(new Request(), function () { - return (new Response())->setContent(''); - }, $name, $guard)->status(); - } catch (UnauthorizedException $e) { - return $e->getStatusCode(); - } - } } diff --git a/tests/RoleTest.php b/tests/RoleTest.php index e76582f55..7cff50d8c 100644 --- a/tests/RoleTest.php +++ b/tests/RoleTest.php @@ -1,6 +1,6 @@ getLastRouteMiddlewareFromRouter($router) ); } - - protected function getLastRouteMiddlewareFromRouter($router) - { - return last($router->getRoutes()->get())->middleware(); - } - - protected function getRouter() - { - return app('router'); - } - - protected function getRouteResponse() - { - return function () { - return (new Response())->setContent(''); - }; - } } diff --git a/tests/TeamHasPermissionsTest.php b/tests/TeamHasPermissionsTest.php index ccc3de93a..54fe106f0 100644 --- a/tests/TeamHasPermissionsTest.php +++ b/tests/TeamHasPermissionsTest.php @@ -1,6 +1,8 @@ set('auth.guards.admin', ['driver' => 'session', 'provider' => 'admins']); $app['config']->set('auth.providers.admins', ['driver' => 'eloquent', 'model' => Admin::class]); if ($this->useCustomModels) { - $app['config']->set('permission.models.permission', \Spatie\Permission\Test\Permission::class); - $app['config']->set('permission.models.role', \Spatie\Permission\Test\Role::class); + $app['config']->set('permission.models.permission', \Spatie\Permission\Tests\TestModels\Permission::class); + $app['config']->set('permission.models.role', \Spatie\Permission\Tests\TestModels\Role::class); } // Use test User model for users provider $app['config']->set('auth.providers.users.model', User::class); @@ -219,4 +223,34 @@ public function setUpRoutes(): void ]; }); } + + + ////// TEST HELPERS + public function runMiddleware($middleware, $permission, $guard = null) + { + try { + return $middleware->handle(new Request(), function () { + return (new Response())->setContent(''); + }, $permission, $guard)->status(); + } catch (UnauthorizedException $e) { + return $e->getStatusCode(); + } + } + + public function getLastRouteMiddlewareFromRouter($router) + { + return last($router->getRoutes()->get())->middleware(); + } + + public function getRouter() + { + return app('router'); + } + + public function getRouteResponse() + { + return function () { + return (new Response())->setContent(''); + }; + } } diff --git a/tests/TestHelper.php b/tests/TestHelper.php index c081d2f3d..5b8efa5cd 100644 --- a/tests/TestHelper.php +++ b/tests/TestHelper.php @@ -1,6 +1,6 @@ assertEquals(['permission.some'], $requiredPermissions); } - - protected function runMiddleware($middleware, $parameter) - { - try { - return $middleware->handle(new Request(), function () { - return (new Response())->setContent(''); - }, $parameter)->status(); - } catch (UnauthorizedException $e) { - return $e->getStatusCode(); - } - } } diff --git a/tests/WildcardRoleTest.php b/tests/WildcardRoleTest.php index 6d69c9d2c..da674c6f6 100644 --- a/tests/WildcardRoleTest.php +++ b/tests/WildcardRoleTest.php @@ -1,12 +1,12 @@ getLastRouteMiddlewareFromRouter($router) ); } - - protected function getLastRouteMiddlewareFromRouter($router) - { - return last($router->getRoutes()->get())->middleware(); - } - - protected function getRouter() - { - return app('router'); - } - - protected function getRouteResponse() - { - return function () { - return (new Response())->setContent(''); - }; - } } From 58b8a011f449f297f7872635a9fa22fb6d23cc71 Mon Sep 17 00:00:00 2001 From: drbyte Date: Thu, 23 Mar 2023 00:25:47 +0000 Subject: [PATCH 245/648] Fix styling --- tests/RoleTest.php | 2 +- tests/TestCase.php | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/tests/RoleTest.php b/tests/RoleTest.php index 7cff50d8c..68d7c1aee 100644 --- a/tests/RoleTest.php +++ b/tests/RoleTest.php @@ -10,8 +10,8 @@ use Spatie\Permission\Models\Permission; use Spatie\Permission\PermissionRegistrar; use Spatie\Permission\Tests\TestModels\Admin; -use Spatie\Permission\Tests\TestModels\User; use Spatie\Permission\Tests\TestModels\RuntimeRole; +use Spatie\Permission\Tests\TestModels\User; class RoleTest extends TestCase { diff --git a/tests/TestCase.php b/tests/TestCase.php index c28ac0355..bc06463f3 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -11,9 +11,9 @@ use Orchestra\Testbench\TestCase as Orchestra; use Spatie\Permission\Contracts\Permission; use Spatie\Permission\Contracts\Role; +use Spatie\Permission\Exceptions\UnauthorizedException; use Spatie\Permission\PermissionRegistrar; use Spatie\Permission\PermissionServiceProvider; -use Spatie\Permission\Exceptions\UnauthorizedException; use Spatie\Permission\Tests\TestModels\Admin; use Spatie\Permission\Tests\TestModels\User; @@ -224,7 +224,6 @@ public function setUpRoutes(): void }); } - ////// TEST HELPERS public function runMiddleware($middleware, $permission, $guard = null) { @@ -238,9 +237,9 @@ public function runMiddleware($middleware, $permission, $guard = null) } public function getLastRouteMiddlewareFromRouter($router) - { - return last($router->getRoutes()->get())->middleware(); - } + { + return last($router->getRoutes()->get())->middleware(); + } public function getRouter() { From 8a0c4b4707c05ea05d63a5777b487effd4daf836 Mon Sep 17 00:00:00 2001 From: drbyte Date: Fri, 24 Mar 2023 04:01:47 +0000 Subject: [PATCH 246/648] Fix styling --- tests/CommandTest.php | 2 +- tests/TestCase.php | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/CommandTest.php b/tests/CommandTest.php index 0da59ee37..1ded858fe 100644 --- a/tests/CommandTest.php +++ b/tests/CommandTest.php @@ -143,7 +143,7 @@ public function it_can_setup_teams_upgrade() $matchingFiles = glob(database_path('migrations/*_add_teams_fields.php')); $this->assertTrue(count($matchingFiles) > 0); - $AddTeamsFields = require($matchingFiles[count($matchingFiles) - 1]); + $AddTeamsFields = require $matchingFiles[count($matchingFiles) - 1]; $AddTeamsFields->up(); $AddTeamsFields->up(); //test upgrade teams migration fresh diff --git a/tests/TestCase.php b/tests/TestCase.php index 0b7d6cfd4..a708ac196 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -188,10 +188,10 @@ private function prepareMigration() ); file_put_contents(__DIR__.'/CreatePermissionCustomTables.php', $migration); - - self::$migration = require(__DIR__.'/../database/migrations/create_permission_tables.php.stub'); - - self::$customMigration = require(__DIR__.'/CreatePermissionCustomTables.php'); + + self::$migration = require __DIR__.'/../database/migrations/create_permission_tables.php.stub'; + + self::$customMigration = require __DIR__.'/CreatePermissionCustomTables.php'; } protected function reloadPermissions() From c3122123b86e69bf370de97fac8e7bce069aca3e Mon Sep 17 00:00:00 2001 From: erikn69 Date: Tue, 21 Mar 2023 11:23:57 -0500 Subject: [PATCH 247/648] Return string on getPermissionClass(), getRoleClass() --- src/Models/Role.php | 6 ++-- src/PermissionRegistrar.php | 16 +++------- src/Traits/HasPermissions.php | 29 ++++++++--------- src/Traits/HasRoles.php | 14 ++++----- tests/PermissionRegistarTest.php | 53 ++++++++++++++++++++++++++++++++ 5 files changed, 78 insertions(+), 40 deletions(-) diff --git a/src/Models/Role.php b/src/Models/Role.php index b8ad70353..feb11738e 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -179,14 +179,12 @@ public function hasPermissionTo($permission): bool return $this->hasWildcardPermission($permission, $this->getDefaultGuardName()); } - $permissionClass = $this->getPermissionClass(); - if (is_string($permission)) { - $permission = $permissionClass->findByName($permission, $this->getDefaultGuardName()); + $permission = $this->getPermissionClass()::findByName($permission, $this->getDefaultGuardName()); } if (is_int($permission)) { - $permission = $permissionClass->findById($permission, $this->getDefaultGuardName()); + $permission = $this->getPermissionClass()::findById($permission, $this->getDefaultGuardName()); } if (! $this->getGuardNames()->contains($permission->guard_name)) { diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index aa5a6dcdc..2b64eed6b 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -227,12 +227,9 @@ public function getPermissions(array $params = [], bool $onlyOne = false): Colle return $permissions; } - /** - * Get an instance of the permission class. - */ - public function getPermissionClass(): Permission + public function getPermissionClass(): string { - return app($this->permissionClass); + return $this->permissionClass; } public function setPermissionClass($permissionClass) @@ -244,12 +241,9 @@ public function setPermissionClass($permissionClass) return $this; } - /** - * Get an instance of the role class. - */ - public function getRoleClass(): Role + public function getRoleClass(): string { - return app($this->roleClass); + return $this->roleClass; } public function setRoleClass($roleClass) @@ -273,7 +267,7 @@ public function getCacheStore(): Store protected function getPermissionsWithRoles(): Collection { - return $this->getPermissionClass()->select()->with('roles')->get(); + return $this->permissionClass::select()->with('roles')->get(); } /** diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index 432723fbf..4160a3346 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -44,7 +44,7 @@ public static function bootHasPermissions() }); } - public function getPermissionClass() + public function getPermissionClass(): string { if (! $this->permissionClass) { $this->permissionClass = app(PermissionRegistrar::class)->getPermissionClass(); @@ -138,7 +138,7 @@ protected function convertToPermissionModels($permissions): array } $method = is_string($permission) && ! PermissionRegistrar::isUid($permission) ? 'findByName' : 'findById'; - return $this->getPermissionClass()->{$method}($permission, $this->getDefaultGuardName()); + return $this->getPermissionClass()::{$method}($permission, $this->getDefaultGuardName()); }, Arr::wrap($permissions)); } @@ -152,17 +152,15 @@ protected function convertToPermissionModels($permissions): array */ public function filterPermission($permission, $guardName = null) { - $permissionClass = $this->getPermissionClass(); - if (is_string($permission) && ! PermissionRegistrar::isUid($permission)) { - $permission = $permissionClass->findByName( + $permission = $this->getPermissionClass()::findByName( $permission, $guardName ?? $this->getDefaultGuardName() ); } if (is_int($permission) || is_string($permission)) { - $permission = $permissionClass->findById( + $permission = $this->getPermissionClass()::findById( $permission, $guardName ?? $this->getDefaultGuardName() ); @@ -205,7 +203,7 @@ protected function hasWildcardPermission($permission, $guardName = null): bool $guardName = $guardName ?? $this->getDefaultGuardName(); if (is_int($permission) || PermissionRegistrar::isUid($permission)) { - $permission = $this->getPermissionClass()->findById($permission, $guardName); + $permission = $this->getPermissionClass()::findById($permission, $guardName); } if ($permission instanceof Permission) { @@ -389,7 +387,7 @@ function ($object) use ($permissions, $model) { ); } - if (is_a($this, get_class(app(PermissionRegistrar::class)->getRoleClass()))) { + if (is_a($this, Role::class)) { $this->forgetCachedPermissions(); } @@ -421,7 +419,7 @@ public function revokePermissionTo($permission) { $this->permissions()->detach($this->getStoredPermission($permission)); - if (is_a($this, get_class(app(PermissionRegistrar::class)->getRoleClass()))) { + if (is_a($this, Role::class)) { $this->forgetCachedPermissions(); } @@ -441,23 +439,20 @@ public function getPermissionNames(): Collection */ protected function getStoredPermission($permissions) { - $permissionClass = $this->getPermissionClass(); - if (is_numeric($permissions) || PermissionRegistrar::isUid($permissions)) { - return $permissionClass->findById($permissions, $this->getDefaultGuardName()); + return $this->getPermissionClass()::findById($permissions, $this->getDefaultGuardName()); } if (is_string($permissions)) { - return $permissionClass->findByName($permissions, $this->getDefaultGuardName()); + return $this->getPermissionClass()::findByName($permissions, $this->getDefaultGuardName()); } if (is_array($permissions)) { - $permissions = array_map(function ($permission) use ($permissionClass) { - return is_a($permission, get_class($permissionClass)) ? $permission->name : $permission; + $permissions = array_map(function ($permission) { + return is_a($permission, Permission::class) ? $permission->name : $permission; }, $permissions); - return $permissionClass - ->whereIn('name', $permissions) + return $this->getPermissionClass()::whereIn('name', $permissions) ->whereIn('guard_name', $this->getGuardNames()) ->get(); } diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index 7982c781c..99ec852f3 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -34,7 +34,7 @@ public static function bootHasRoles() }); } - public function getRoleClass() + public function getRoleClass(): string { if (! $this->roleClass) { $this->roleClass = app(PermissionRegistrar::class)->getRoleClass(); @@ -86,7 +86,7 @@ public function scopeRole(Builder $query, $roles, $guard = null): Builder $method = is_numeric($role) || PermissionRegistrar::isUid($role) ? 'findById' : 'findByName'; - return $this->getRoleClass()->{$method}($role, $guard ?: $this->getDefaultGuardName()); + return $this->getRoleClass()::{$method}($role, $guard ?: $this->getDefaultGuardName()); }, Arr::wrap($roles)); return $query->whereHas('roles', function (Builder $subQuery) use ($roles) { @@ -153,7 +153,7 @@ function ($object) use ($roles, $model) { ); } - if (is_a($this, get_class($this->getPermissionClass()))) { + if (is_a($this, Permission::class)) { $this->forgetCachedPermissions(); } @@ -171,7 +171,7 @@ public function removeRole($role) $this->load('roles'); - if (is_a($this, get_class($this->getPermissionClass()))) { + if (is_a($this, Permission::class)) { $this->forgetCachedPermissions(); } @@ -329,14 +329,12 @@ public function getRoleNames(): Collection protected function getStoredRole($role): Role { - $roleClass = $this->getRoleClass(); - if (is_numeric($role) || PermissionRegistrar::isUid($role)) { - return $roleClass->findById($role, $this->getDefaultGuardName()); + return $this->getRoleClass()::findById($role, $this->getDefaultGuardName()); } if (is_string($role)) { - return $roleClass->findByName($role, $this->getDefaultGuardName()); + return $this->getRoleClass()::findByName($role, $this->getDefaultGuardName()); } return $role; diff --git a/tests/PermissionRegistarTest.php b/tests/PermissionRegistarTest.php index 1e52362b5..24d027b5e 100644 --- a/tests/PermissionRegistarTest.php +++ b/tests/PermissionRegistarTest.php @@ -2,7 +2,13 @@ namespace Spatie\Permission\Tests; +use Spatie\Permission\Contracts\Permission as PermissionContract; +use Spatie\Permission\Contracts\Role as RoleContract; +use Spatie\Permission\Models\Permission as SpatiePermission; +use Spatie\Permission\Models\Role as SpatieRole; use Spatie\Permission\PermissionRegistrar; +use Spatie\Permission\Tests\TestModels\Permission as TestPermission; +use Spatie\Permission\Tests\TestModels\Role as TestRole; class PermissionRegistarTest extends TestCase { @@ -64,4 +70,51 @@ public function it_can_check_uids() $this->assertFalse(PermissionRegistrar::isUid($not_uid)); } } + + /** @test */ + public function it_can_get_permission_class() { + $this->assertSame(SpatiePermission::class, app(PermissionRegistrar::class)->getPermissionClass()); + $this->assertSame(SpatiePermission::class, get_class(app(PermissionContract::class))); + } + + /** @test */ + public function it_can_change_permission_class() { + $this->assertSame(SpatiePermission::class, config('permission.models.permission')); + $this->assertSame(SpatiePermission::class, app(PermissionRegistrar::class)->getPermissionClass()); + $this->assertSame(SpatiePermission::class, get_class(app(PermissionContract::class))); + + app(PermissionRegistrar::class)->setPermissionClass(TestPermission::class); + + $this->assertSame(TestPermission::class, config('permission.models.permission')); + $this->assertSame(TestPermission::class, app(PermissionRegistrar::class)->getPermissionClass()); + $this->assertSame(TestPermission::class, get_class(app(PermissionContract::class))); + } + + /** @test */ + public function it_can_get_role_class() { + $this->assertSame(SpatieRole::class, app(PermissionRegistrar::class)->getRoleClass()); + $this->assertSame(SpatieRole::class, get_class(app(RoleContract::class))); + } + + /** @test */ + public function it_can_change_role_class() { + $this->assertSame(SpatieRole::class, config('permission.models.role')); + $this->assertSame(SpatieRole::class, app(PermissionRegistrar::class)->getRoleClass()); + $this->assertSame(SpatieRole::class, get_class(app(RoleContract::class))); + + app(PermissionRegistrar::class)->setRoleClass(TestRole::class); + + $this->assertSame(TestRole::class, config('permission.models.role')); + $this->assertSame(TestRole::class, app(PermissionRegistrar::class)->getRoleClass()); + $this->assertSame(TestRole::class, get_class(app(RoleContract::class))); + } + + /** @test */ + public function it_can_change_team_id() { + $team_id = '00000000-0000-0000-0000-000000000000'; + + app(PermissionRegistrar::class)->setPermissionsTeamId($team_id); + + $this->assertSame($team_id, app(PermissionRegistrar::class)->getPermissionsTeamId()); + } } From 74f646081f9b387b4cb32cbaff12e732445b23af Mon Sep 17 00:00:00 2001 From: drbyte Date: Fri, 24 Mar 2023 18:47:46 +0000 Subject: [PATCH 248/648] Fix styling --- tests/PermissionRegistarTest.php | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/tests/PermissionRegistarTest.php b/tests/PermissionRegistarTest.php index 24d027b5e..d5ed03547 100644 --- a/tests/PermissionRegistarTest.php +++ b/tests/PermissionRegistarTest.php @@ -72,17 +72,19 @@ public function it_can_check_uids() } /** @test */ - public function it_can_get_permission_class() { + public function it_can_get_permission_class() + { $this->assertSame(SpatiePermission::class, app(PermissionRegistrar::class)->getPermissionClass()); $this->assertSame(SpatiePermission::class, get_class(app(PermissionContract::class))); } /** @test */ - public function it_can_change_permission_class() { + public function it_can_change_permission_class() + { $this->assertSame(SpatiePermission::class, config('permission.models.permission')); $this->assertSame(SpatiePermission::class, app(PermissionRegistrar::class)->getPermissionClass()); $this->assertSame(SpatiePermission::class, get_class(app(PermissionContract::class))); - + app(PermissionRegistrar::class)->setPermissionClass(TestPermission::class); $this->assertSame(TestPermission::class, config('permission.models.permission')); @@ -91,13 +93,15 @@ public function it_can_change_permission_class() { } /** @test */ - public function it_can_get_role_class() { + public function it_can_get_role_class() + { $this->assertSame(SpatieRole::class, app(PermissionRegistrar::class)->getRoleClass()); $this->assertSame(SpatieRole::class, get_class(app(RoleContract::class))); } /** @test */ - public function it_can_change_role_class() { + public function it_can_change_role_class() + { $this->assertSame(SpatieRole::class, config('permission.models.role')); $this->assertSame(SpatieRole::class, app(PermissionRegistrar::class)->getRoleClass()); $this->assertSame(SpatieRole::class, get_class(app(RoleContract::class))); @@ -110,7 +114,8 @@ public function it_can_change_role_class() { } /** @test */ - public function it_can_change_team_id() { + public function it_can_change_team_id() + { $team_id = '00000000-0000-0000-0000-000000000000'; app(PermissionRegistrar::class)->setPermissionsTeamId($team_id); From 87fd227c522775bbe0a123f49135d0e214448345 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Sat, 25 Mar 2023 21:43:33 -0400 Subject: [PATCH 249/648] Specify version 6 as supporting L8-L10 --- .github/workflows/run-tests-L7.yml | 38 ------------------------------ composer.json | 12 +++++----- docs/installation-laravel.md | 3 ++- 3 files changed, 8 insertions(+), 45 deletions(-) delete mode 100644 .github/workflows/run-tests-L7.yml diff --git a/.github/workflows/run-tests-L7.yml b/.github/workflows/run-tests-L7.yml deleted file mode 100644 index 6849039e9..000000000 --- a/.github/workflows/run-tests-L7.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: "Run Tests - Older" - -on: [push, pull_request] - -jobs: - test: - - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - php: [8.0, 7.4, 7.3] - laravel: [7.*] - dependency-version: [prefer-lowest, prefer-stable] - include: - - laravel: 7.* - testbench: 5.20 - - name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }} - - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php }} - extensions: curl, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, iconv - coverage: none - - - name: Install dependencies - run: | - composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" "symfony/console:>=4.3.4" "mockery/mockery:^1.3.2" --no-interaction --no-update - composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction - - - name: Execute tests - run: vendor/bin/phpunit diff --git a/composer.json b/composer.json index 759ef75a5..d05f7dfcb 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "spatie/laravel-permission", - "description": "Permission handling for Laravel 6.0 and up", + "description": "Permission handling for Laravel 8.0 and up", "license": "MIT", "keywords": [ "spatie", @@ -23,13 +23,13 @@ "homepage": "https://github.com/spatie/laravel-permission", "require": { "php": "^7.3|^8.0", - "illuminate/auth": "^7.0|^8.0|^9.0|^10.0", - "illuminate/container": "^7.0|^8.0|^9.0|^10.0", - "illuminate/contracts": "^7.0|^8.0|^9.0|^10.0", - "illuminate/database": "^7.0|^8.0|^9.0|^10.0" + "illuminate/auth": "^8.0|^9.0|^10.0", + "illuminate/container": "^8.0|^9.0|^10.0", + "illuminate/contracts": "^8.0|^9.0|^10.0", + "illuminate/database": "^8.0|^9.0|^10.0" }, "require-dev": { - "orchestra/testbench": "^5.0|^6.0|^7.0|^8.0", + "orchestra/testbench": "^6.0|^7.0|^8.0", "phpunit/phpunit": "^9.4", "predis/predis": "^1.1" }, diff --git a/docs/installation-laravel.md b/docs/installation-laravel.md index 642ac5a3c..5e2914937 100644 --- a/docs/installation-laravel.md +++ b/docs/installation-laravel.md @@ -9,10 +9,11 @@ This package can be used with Laravel 6.0 or higher. Package | Laravel Version --------|----------- + ^6.0 | 8,9,10 ^5.8 | 7,8,9,10 ^5.7 | 7,8,9 ^5.4-^5.6 | 7,8 -5.0-5.3 | 6,7,8 + 5.0-5.3 | 6,7,8 ^4 | 6,7,8 ^3 | 5.8 From 3507a3a6727c5403a592e5a5983c5fa4f873d268 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Sat, 25 Mar 2023 21:45:13 -0400 Subject: [PATCH 250/648] [Docs] Add more details to Upgrade Instructions --- docs/upgrading.md | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/docs/upgrading.md b/docs/upgrading.md index 7b9afaaf7..60436ebc6 100644 --- a/docs/upgrading.md +++ b/docs/upgrading.md @@ -3,11 +3,23 @@ title: Upgrading weight: 6 --- -### Upgrading from v2 to v3 -There are no special requirements for upgrading from v2 to v3, other than changing `^2.xx` (xx can vary) to `^3.0` in your `composer.json` and running `composer update`. Of course, your app must meet the minimum requirements as well. +ALL upgrades of this package should follow these steps: + +1. Upgrading between major versions of this package always require the usual Composer steps: + > 1. Update your `composer.json` to specify the new major version, such as `^5.0` + > 2. Then run `composer update`. + +2. Compare the `migration` file stubs in the NEW version of this package against the migrations you've already run inside your app. If necessary, create a new migration (by hand) to apply any new changes. + +3. If you have made any custom Models from this package into your own app, compare the old and new models and apply any relevant updates to your custom models. + +4. If you have overridden any methods from this package's Traits, compare the old and new traits, and apply any relevant updates to your overridden methods. + +5. Apply any version-specific special updates as outlined below... + ### Upgrading from v1 to v2 -If you're upgrading from v1 to v2, there's no built-in automatic migration/conversion of your data. +If you're upgrading from v1 to v2, there's no built-in automatic migration/conversion of your data to the new structure. You will need to carefully adapt your code and your data manually. Tip: @fabricecw prepared [a gist which may make your data migration easier](https://gist.github.com/fabricecw/58ee93dd4f99e78724d8acbb851658a4). From addbd4f9e2a0933526ec75cfd77a3ef5ef1aae36 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Sat, 25 Mar 2023 21:45:40 -0400 Subject: [PATCH 251/648] [Docs] Add v6 upgrade instructions --- docs/upgrading.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/upgrading.md b/docs/upgrading.md index 60436ebc6..c77468ea7 100644 --- a/docs/upgrading.md +++ b/docs/upgrading.md @@ -6,7 +6,7 @@ weight: 6 ALL upgrades of this package should follow these steps: 1. Upgrading between major versions of this package always require the usual Composer steps: - > 1. Update your `composer.json` to specify the new major version, such as `^5.0` + > 1. Update your `composer.json` to specify the new major version, such as `^6.0` > 2. Then run `composer update`. 2. Compare the `migration` file stubs in the NEW version of this package against the migrations you've already run inside your app. If necessary, create a new migration (by hand) to apply any new changes. @@ -17,6 +17,12 @@ ALL upgrades of this package should follow these steps: 5. Apply any version-specific special updates as outlined below... +### Upgrading to v6 +If you have overridden the `getPermissionClass()` or `getRoleClass()` methods or have custom Models, you will need to revisit those customizations. See PR #2368 for details. +eg: if you have a custom model you will need to make changes, including accessing the model using `$this->permissionClass::` syntax (eg: using `::` instead of `->`) in all the overridden methods that make use of the models. +Be sure to compare your custom models with originals to see what else may have changed. + +Also note that the migrations have been updated to anonymous-class syntax that was introduced in Laravel 8. You may optionally update your original migration files accordingly. ### Upgrading from v1 to v2 If you're upgrading from v1 to v2, there's no built-in automatic migration/conversion of your data to the new structure. From 905c1768646468c002776de1ad0879d4069e3491 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Tue, 28 Mar 2023 17:40:42 -0500 Subject: [PATCH 252/648] Only offer publishing when running in console --- src/PermissionServiceProvider.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/PermissionServiceProvider.php b/src/PermissionServiceProvider.php index e80b292a2..f52eff86b 100644 --- a/src/PermissionServiceProvider.php +++ b/src/PermissionServiceProvider.php @@ -56,6 +56,10 @@ protected function offerPublishing() return; } + if (! $this->app->runningInConsole()) { + return; + } + $this->publishes([ __DIR__.'/../config/permission.php' => config_path('permission.php'), ], 'permission-config'); From 045f8de97b4993a0bd4e67e0a39d7fa2c3bbc899 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Thu, 30 Mar 2023 09:28:36 -0500 Subject: [PATCH 253/648] fix Role->hasPermissionTo overwrite accorde has permission trait --- src/Models/Role.php | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/Models/Role.php b/src/Models/Role.php index feb11738e..850b77df8 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -173,22 +173,16 @@ protected static function findByParam(array $params = []) * * @throws \Spatie\Permission\Exceptions\GuardDoesNotMatch */ - public function hasPermissionTo($permission): bool + public function hasPermissionTo($permission, $guardName = null): bool { - if (config('permission.enable_wildcard_permission', false)) { - return $this->hasWildcardPermission($permission, $this->getDefaultGuardName()); - } - - if (is_string($permission)) { - $permission = $this->getPermissionClass()::findByName($permission, $this->getDefaultGuardName()); - } - - if (is_int($permission)) { - $permission = $this->getPermissionClass()::findById($permission, $this->getDefaultGuardName()); + if ($this->getWildcardClass()) { + return $this->hasWildcardPermission($permission, $guardName); } + + $permission = $this->filterPermission($permission, $guardName); if (! $this->getGuardNames()->contains($permission->guard_name)) { - throw GuardDoesNotMatch::create($permission->guard_name, $this->getGuardNames()); + throw GuardDoesNotMatch::create($permission->guard_name, $guardName ?? $this->getGuardNames()); } return $this->permissions->contains($permission->getKeyName(), $permission->getKey()); From 29ec9ec3ee5b7181c6303f018eac48a1fc8e6bda Mon Sep 17 00:00:00 2001 From: erikn69 Date: Thu, 30 Mar 2023 10:08:30 -0500 Subject: [PATCH 254/648] getPermissionsViaRoles, hasPermissionViaRole must be used only by authenticable --- src/Traits/HasPermissions.php | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index 4160a3346..a41c061c8 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -22,7 +22,7 @@ trait HasPermissions /** @var string */ private $permissionClass; - /** @var string */ + /** @var string|false|null */ private $wildcardClass; public static function bootHasPermissions() @@ -61,7 +61,7 @@ protected function getWildcardClass() $this->wildcardClass = false; - if (config('permission.enable_wildcard_permission', false)) { + if (config('permission.enable_wildcard_permission')) { $this->wildcardClass = config('permission.wildcard_permission', WildcardPermission::class); if (! is_subclass_of($this->wildcardClass, Wildcard::class)) { @@ -101,7 +101,7 @@ public function scopePermission(Builder $query, $permissions): Builder { $permissions = $this->convertToPermissionModels($permissions); - $rolesWithPermissions = array_unique(array_reduce($permissions, function ($result, $permission) { + $rolesWithPermissions = is_a($this, Role::class) ? []: array_unique(array_reduce($permissions, function ($result, $permission) { return array_merge($result, $permission->roles->all()); }, [])); @@ -111,7 +111,7 @@ public function scopePermission(Builder $query, $permissions): Builder $key = (new $permissionClass())->getKeyName(); $subQuery->whereIn(config('permission.table_names.permissions').".$key", \array_column($permissions, $key)); }); - if (count($rolesWithPermissions) > 0) { + if (count($rolesWithPermissions) > 0 && ! is_a($this, Role::class)) { $query->orWhereHas('roles', function (Builder $subQuery) use ($rolesWithPermissions) { $roleClass = $this->getRoleClass(); $key = (new $roleClass())->getKeyName(); @@ -287,6 +287,10 @@ public function hasAllPermissions(...$permissions): bool */ protected function hasPermissionViaRole(Permission $permission): bool { + if (is_a($this, Role::class)) { + return false; + } + return $this->hasRole($permission->roles); } @@ -309,6 +313,10 @@ public function hasDirectPermission($permission): bool */ public function getPermissionsViaRoles(): Collection { + if (is_a($this, Role::class) || is_a($this, Permission::class)) { + return collect(); + } + return $this->loadMissing('roles', 'roles.permissions') ->roles->flatMap(function ($role) { return $role->permissions; From 509a73de2a45f0d56bc16069eac088c5b626f52e Mon Sep 17 00:00:00 2001 From: erikn69 Date: Thu, 30 Mar 2023 10:53:53 -0500 Subject: [PATCH 255/648] minor nitpicks --- src/Commands/Show.php | 6 +++--- src/Commands/UpgradeForTeams.php | 2 +- src/PermissionServiceProvider.php | 8 +++++--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Commands/Show.php b/src/Commands/Show.php index bcbe07381..c80d12c1b 100644 --- a/src/Commands/Show.php +++ b/src/Commands/Show.php @@ -58,15 +58,15 @@ public function handle() } $this->table( - array_merge([ - config('permission.teams') ? $teams->prepend('')->toArray() : [], + array_merge( + isset($teams) ? $teams->prepend(new TableCell(''))->toArray() : [], $roles->keys()->map(function ($val) { $name = explode('_', $val); return $name[0]; }) ->prepend('')->toArray(), - ]), + ), $body->toArray(), $style ); diff --git a/src/Commands/UpgradeForTeams.php b/src/Commands/UpgradeForTeams.php index 70c790bc1..f42218cc7 100644 --- a/src/Commands/UpgradeForTeams.php +++ b/src/Commands/UpgradeForTeams.php @@ -35,7 +35,7 @@ public function handle() $this->line(''); - if (! $this->confirm('Proceed with the migration creation?', 'yes')) { + if (! $this->confirm('Proceed with the migration creation?', true)) { return; } diff --git a/src/PermissionServiceProvider.php b/src/PermissionServiceProvider.php index f52eff86b..fdf0670df 100644 --- a/src/PermissionServiceProvider.php +++ b/src/PermissionServiceProvider.php @@ -26,7 +26,7 @@ public function boot() $this->registerModelBindings(); $this->callAfterResolving(Gate::class, function (Gate $gate, Application $app) { - if ($this->app->config['permission.register_permission_check_method']) { + if ($this->app['config']->get('permission.register_permission_check_method')) { /** @var PermissionRegistrar $permissionLoader */ $permissionLoader = $app->get(PermissionRegistrar::class); $permissionLoader->clearPermissionsCollection(); @@ -156,6 +156,7 @@ protected function registerMacroHelpers() Route::macro('role', function ($roles = []) { $roles = implode('|', Arr::wrap($roles)); + /** @var \Illuminate\Routing\Route $this */ $this->middleware("role:$roles"); return $this; @@ -164,6 +165,7 @@ protected function registerMacroHelpers() Route::macro('permission', function ($permissions = []) { $permissions = implode('|', Arr::wrap($permissions)); + /** @var \Illuminate\Routing\Route $this */ $this->middleware("permission:$permissions"); return $this; @@ -173,13 +175,13 @@ protected function registerMacroHelpers() /** * Returns existing migration file if found, else uses the current timestamp. */ - protected function getMigrationFileName($migrationFileName): string + protected function getMigrationFileName(string $migrationFileName): string { $timestamp = date('Y_m_d_His'); $filesystem = $this->app->make(Filesystem::class); - return Collection::make($this->app->databasePath().DIRECTORY_SEPARATOR.'migrations'.DIRECTORY_SEPARATOR) + return Collection::make([$this->app->databasePath().DIRECTORY_SEPARATOR.'migrations'.DIRECTORY_SEPARATOR]) ->flatMap(function ($path) use ($filesystem, $migrationFileName) { return $filesystem->glob($path.'*_'.$migrationFileName); }) From 6dff984bee0c38cfb0103f48251569a003617214 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Thu, 30 Mar 2023 14:44:52 -0400 Subject: [PATCH 256/648] [Docs] Mention changelog in upgrade instructions --- docs/upgrading.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/upgrading.md b/docs/upgrading.md index c77468ea7..24a2cfc7e 100644 --- a/docs/upgrading.md +++ b/docs/upgrading.md @@ -17,6 +17,9 @@ ALL upgrades of this package should follow these steps: 5. Apply any version-specific special updates as outlined below... +6. Review the changelog, which details all the changes: https://github.com/spatie/laravel-permission/blob/main/CHANGELOG.md + + ### Upgrading to v6 If you have overridden the `getPermissionClass()` or `getRoleClass()` methods or have custom Models, you will need to revisit those customizations. See PR #2368 for details. eg: if you have a custom model you will need to make changes, including accessing the model using `$this->permissionClass::` syntax (eg: using `::` instead of `->`) in all the overridden methods that make use of the models. From b6274744d8b046ce8865d5f8a135fcd3ff93105a Mon Sep 17 00:00:00 2001 From: drbyte Date: Thu, 30 Mar 2023 19:25:34 +0000 Subject: [PATCH 257/648] Fix styling --- src/Models/Role.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Models/Role.php b/src/Models/Role.php index 850b77df8..7da997548 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -178,7 +178,7 @@ public function hasPermissionTo($permission, $guardName = null): bool if ($this->getWildcardClass()) { return $this->hasWildcardPermission($permission, $guardName); } - + $permission = $this->filterPermission($permission, $guardName); if (! $this->getGuardNames()->contains($permission->guard_name)) { From 56c068b09e48cb4da599d4d3c42b89c5b8ef74e0 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Thu, 30 Mar 2023 15:28:01 -0400 Subject: [PATCH 258/648] [Docs] more v6 upgrade info --- docs/upgrading.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/upgrading.md b/docs/upgrading.md index 24a2cfc7e..9ff46d95d 100644 --- a/docs/upgrading.md +++ b/docs/upgrading.md @@ -21,11 +21,13 @@ ALL upgrades of this package should follow these steps: ### Upgrading to v6 -If you have overridden the `getPermissionClass()` or `getRoleClass()` methods or have custom Models, you will need to revisit those customizations. See PR #2368 for details. +1. If you have overridden the `getPermissionClass()` or `getRoleClass()` methods or have custom Models, you will need to revisit those customizations. See PR #2368 for details. eg: if you have a custom model you will need to make changes, including accessing the model using `$this->permissionClass::` syntax (eg: using `::` instead of `->`) in all the overridden methods that make use of the models. Be sure to compare your custom models with originals to see what else may have changed. -Also note that the migrations have been updated to anonymous-class syntax that was introduced in Laravel 8. You may optionally update your original migration files accordingly. +2. If you have a custom Role model and (in the rare case that you might) have overridden the `hasPermissionTo()` method in it, you will need to update its method signature to `hasPermissionTo($permission, $guardName = null):bool`. See PR #2380. + +3. Also note that the migrations have been updated to anonymous-class syntax that was introduced in Laravel 8. You may optionally update your original migration files accordingly. ### Upgrading from v1 to v2 If you're upgrading from v1 to v2, there's no built-in automatic migration/conversion of your data to the new structure. From f893ff06490a8d1f6fdaa0316cbfb6c86848d91f Mon Sep 17 00:00:00 2001 From: drbyte Date: Thu, 30 Mar 2023 19:30:33 +0000 Subject: [PATCH 259/648] Fix styling --- src/Traits/HasPermissions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index a41c061c8..c1b602bea 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -101,7 +101,7 @@ public function scopePermission(Builder $query, $permissions): Builder { $permissions = $this->convertToPermissionModels($permissions); - $rolesWithPermissions = is_a($this, Role::class) ? []: array_unique(array_reduce($permissions, function ($result, $permission) { + $rolesWithPermissions = is_a($this, Role::class) ? [] : array_unique(array_reduce($permissions, function ($result, $permission) { return array_merge($result, $permission->roles->all()); }, [])); From d76450519421f5b41b3246cd2d001e6fa25112eb Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Thu, 30 Mar 2023 16:33:02 -0400 Subject: [PATCH 260/648] [Docs] MySQL 8 note in migrations --- docs/installation-laravel.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/installation-laravel.md b/docs/installation-laravel.md index 5e2914937..5b410cb5c 100644 --- a/docs/installation-laravel.md +++ b/docs/installation-laravel.md @@ -46,17 +46,19 @@ Package | Laravel Version 6. NOTE: If you are using UUIDs, see the Advanced section of the docs on UUID steps, before you continue. It explains some changes you may want to make to the migrations and config file before continuing. It also mentions important considerations after extending this package's models for UUID capability. If you are going to use teams feature, you have to update your [`config/permission.php` config file](https://github.com/spatie/laravel-permission/blob/main/config/permission.php) and set `'teams' => true,`, if you want to use a custom foreign key for teams you must change `team_foreign_key`. -7. **Clear your config cache**. This package requires access to the `permission` config. Generally it's bad practice to do config-caching in a development environment. If you've been caching configurations locally, clear your config cache with either of these commands: +7. NOTE: If you are using MySQL 8, look at the migration files for notes about MySQL 8 to set/limit the index key length, and edit accordingly. + +8. **Clear your config cache**. This package requires access to the `permission` config. Generally it's bad practice to do config-caching in a development environment. If you've been caching configurations locally, clear your config cache with either of these commands: php artisan optimize:clear # or php artisan config:clear -8. **Run the migrations**: After the config and migration have been published and configured, you can create the tables for this package by running: +9. **Run the migrations**: After the config and migration have been published and configured, you can create the tables for this package by running: php artisan migrate -9. **Add the necessary trait to your User model**: +10. **Add the necessary trait to your User model**: // The User model requires this trait use HasRoles; From 938422360b8ace598e3155dd57353eba0f5ef216 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Thu, 30 Mar 2023 09:45:15 -0500 Subject: [PATCH 261/648] fix BadMethodCallException: undefined methods hasAnyRole, hasAnyPermissions --- src/Exceptions/UnauthorizedException.php | 8 +++++ src/Middlewares/PermissionMiddleware.php | 17 +++++++---- src/Middlewares/RoleMiddleware.php | 8 ++++- .../RoleOrPermissionMiddleware.php | 8 ++++- tests/PermissionMiddlewareTest.php | 30 +++++++++++++++++++ tests/RoleMiddlewareTest.php | 15 ++++++++++ tests/RoleOrPermissionMiddlewareTest.php | 30 +++++++++++++++++++ tests/TestModels/UserWithoutHasRoles.php | 21 +++++++++++++ 8 files changed, 129 insertions(+), 8 deletions(-) create mode 100644 tests/TestModels/UserWithoutHasRoles.php diff --git a/src/Exceptions/UnauthorizedException.php b/src/Exceptions/UnauthorizedException.php index 2a270faf8..249898a75 100644 --- a/src/Exceptions/UnauthorizedException.php +++ b/src/Exceptions/UnauthorizedException.php @@ -2,6 +2,7 @@ namespace Spatie\Permission\Exceptions; +use Illuminate\Contracts\Auth\Access\Authorizable; use Symfony\Component\HttpKernel\Exception\HttpException; class UnauthorizedException extends HttpException @@ -52,6 +53,13 @@ public static function forRolesOrPermissions(array $rolesOrPermissions): self return $exception; } + public static function missingTraitHasRoles(Authorizable $user): self + { + $class = get_class($user); + + return new static(403, "Authorizable class `{$class}` must use Spatie\Permission\Traits\HasRoles trait.", null, []); + } + public static function notLoggedIn(): self { return new static(403, 'User is not logged in.', null, []); diff --git a/src/Middlewares/PermissionMiddleware.php b/src/Middlewares/PermissionMiddleware.php index 73dec2ef8..49793f5ce 100644 --- a/src/Middlewares/PermissionMiddleware.php +++ b/src/Middlewares/PermissionMiddleware.php @@ -3,28 +3,33 @@ namespace Spatie\Permission\Middlewares; use Closure; +use Illuminate\Support\Facades\Auth; use Spatie\Permission\Exceptions\UnauthorizedException; class PermissionMiddleware { public function handle($request, Closure $next, $permission, $guard = null) { - $authGuard = app('auth')->guard($guard); + $authGuard = Auth::guard($guard); if ($authGuard->guest()) { throw UnauthorizedException::notLoggedIn(); } + $user = $authGuard->user(); + + if (! method_exists($user, 'hasAnyPermission')) { + throw UnauthorizedException::missingTraitHasRoles($user); + } + $permissions = is_array($permission) ? $permission : explode('|', $permission); - foreach ($permissions as $permission) { - if ($authGuard->user()->can($permission)) { - return $next($request); - } + if (! $user->canAny($permissions)) { + throw UnauthorizedException::forPermissions($permissions); } - throw UnauthorizedException::forPermissions($permissions); + return $next($request); } } diff --git a/src/Middlewares/RoleMiddleware.php b/src/Middlewares/RoleMiddleware.php index 34e91e241..f3e972a9b 100644 --- a/src/Middlewares/RoleMiddleware.php +++ b/src/Middlewares/RoleMiddleware.php @@ -16,11 +16,17 @@ public function handle($request, Closure $next, $role, $guard = null) throw UnauthorizedException::notLoggedIn(); } + $user = $authGuard->user(); + + if (! method_exists($user, 'hasAnyRole')) { + throw UnauthorizedException::missingTraitHasRoles($user); + } + $roles = is_array($role) ? $role : explode('|', $role); - if (! $authGuard->user()->hasAnyRole($roles)) { + if (! $user->hasAnyRole($roles) && ! $user->can('')) { throw UnauthorizedException::forRoles($roles); } diff --git a/src/Middlewares/RoleOrPermissionMiddleware.php b/src/Middlewares/RoleOrPermissionMiddleware.php index b9149f2f6..5a9aaa341 100644 --- a/src/Middlewares/RoleOrPermissionMiddleware.php +++ b/src/Middlewares/RoleOrPermissionMiddleware.php @@ -15,11 +15,17 @@ public function handle($request, Closure $next, $roleOrPermission, $guard = null throw UnauthorizedException::notLoggedIn(); } + $user = $authGuard->user(); + + if (! method_exists($user, 'hasAnyRole') || ! method_exists($user, 'hasAnyPermission')) { + throw UnauthorizedException::missingTraitHasRoles($user); + } + $rolesOrPermissions = is_array($roleOrPermission) ? $roleOrPermission : explode('|', $roleOrPermission); - if (! $authGuard->user()->hasAnyRole($rolesOrPermissions) && ! $authGuard->user()->hasAnyPermission($rolesOrPermissions)) { + if (! $user->canAny($rolesOrPermissions) && ! $user->hasAnyRole($rolesOrPermissions)) { throw UnauthorizedException::forRolesOrPermissions($rolesOrPermissions); } diff --git a/tests/PermissionMiddlewareTest.php b/tests/PermissionMiddlewareTest.php index 0b5f82943..9d9659107 100644 --- a/tests/PermissionMiddlewareTest.php +++ b/tests/PermissionMiddlewareTest.php @@ -6,10 +6,12 @@ use Illuminate\Http\Response; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Config; +use Illuminate\Support\Facades\Gate; use InvalidArgumentException; use Spatie\Permission\Contracts\Permission; use Spatie\Permission\Exceptions\UnauthorizedException; use Spatie\Permission\Middlewares\PermissionMiddleware; +use Spatie\Permission\Tests\TestModels\UserWithoutHasRoles; class PermissionMiddlewareTest extends TestCase { @@ -69,6 +71,21 @@ public function a_user_cannot_access_a_route_protected_by_the_permission_middlew ); } + /** @test */ + public function a_super_admin_user_can_access_a_route_protected_by_permission_middleware() + { + Auth::login($this->testUser); + + Gate::before(function ($user, $ability) { + return $user->getKey() === $this->testUser->getKey() ? true : null; + }); + + $this->assertEquals( + 200, + $this->runMiddleware($this->permissionMiddleware, 'edit-articles') + ); + } + /** @test */ public function a_user_can_access_a_route_protected_by_permission_middleware_if_have_this_permission() { @@ -100,6 +117,19 @@ public function a_user_can_access_a_route_protected_by_this_permission_middlewar ); } + /** @test */ + public function a_user_cannot_access_a_route_protected_by_the_permission_middleware_if_have_not_has_roles_trait() + { + $userWithoutHasRoles = UserWithoutHasRoles::create(['email' => 'test_not_has_roles@user.com']); + + Auth::login($userWithoutHasRoles); + + $this->assertEquals( + 403, + $this->runMiddleware($this->permissionMiddleware, 'edit-news') + ); + } + /** @test */ public function a_user_cannot_access_a_route_protected_by_the_permission_middleware_if_have_a_different_permission() { diff --git a/tests/RoleMiddlewareTest.php b/tests/RoleMiddlewareTest.php index 1109e5a4e..de9f6e844 100644 --- a/tests/RoleMiddlewareTest.php +++ b/tests/RoleMiddlewareTest.php @@ -6,9 +6,11 @@ use Illuminate\Http\Response; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Config; +use Illuminate\Support\Facades\Gate; use InvalidArgumentException; use Spatie\Permission\Exceptions\UnauthorizedException; use Spatie\Permission\Middlewares\RoleMiddleware; +use Spatie\Permission\Tests\TestModels\UserWithoutHasRoles; class RoleMiddlewareTest extends TestCase { @@ -74,6 +76,19 @@ public function a_user_can_access_a_route_protected_by_this_role_middleware_if_h ); } + /** @test */ + public function a_user_cannot_access_a_route_protected_by_the_role_middleware_if_have_not_has_roles_trait() + { + $userWithoutHasRoles = UserWithoutHasRoles::create(['email' => 'test_not_has_roles@user.com']); + + Auth::login($userWithoutHasRoles); + + $this->assertEquals( + 403, + $this->runMiddleware($this->roleMiddleware, 'testRole') + ); + } + /** @test */ public function a_user_cannot_access_a_route_protected_by_the_role_middleware_if_have_a_different_role() { diff --git a/tests/RoleOrPermissionMiddlewareTest.php b/tests/RoleOrPermissionMiddlewareTest.php index a6e8147e2..20a4139be 100644 --- a/tests/RoleOrPermissionMiddlewareTest.php +++ b/tests/RoleOrPermissionMiddlewareTest.php @@ -6,9 +6,11 @@ use Illuminate\Http\Response; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Config; +use Illuminate\Support\Facades\Gate; use InvalidArgumentException; use Spatie\Permission\Exceptions\UnauthorizedException; use Spatie\Permission\Middlewares\RoleOrPermissionMiddleware; +use Spatie\Permission\Tests\TestModels\UserWithoutHasRoles; class RoleOrPermissionMiddlewareTest extends TestCase { @@ -64,6 +66,34 @@ public function a_user_can_access_a_route_protected_by_permission_or_role_middle ); } + /** @test */ + public function a_super_admin_user_can_access_a_route_protected_by_permission_or_role_middleware() + { + Auth::login($this->testUser); + + Gate::before(function ($user, $ability) { + return $user->getKey() === $this->testUser->getKey() ? true : null; + }); + + $this->assertEquals( + 200, + $this->runMiddleware($this->roleOrPermissionMiddleware, 'testRole|edit-articles') + ); + } + + /** @test */ + public function a_user_can_not_access_a_route_protected_by_permission_or_role_middleware_if_have_not_has_roles_trait() + { + $userWithoutHasRoles = UserWithoutHasRoles::create(['email' => 'test_not_has_roles@user.com']); + + Auth::login($userWithoutHasRoles); + + $this->assertEquals( + 403, + $this->runMiddleware($this->roleOrPermissionMiddleware, 'testRole|edit-articles') + ); + } + /** @test */ public function a_user_can_not_access_a_route_protected_by_permission_or_role_middleware_if_have_not_this_permission_and_role() { diff --git a/tests/TestModels/UserWithoutHasRoles.php b/tests/TestModels/UserWithoutHasRoles.php new file mode 100644 index 000000000..dd9a29445 --- /dev/null +++ b/tests/TestModels/UserWithoutHasRoles.php @@ -0,0 +1,21 @@ + Date: Thu, 30 Mar 2023 21:19:40 +0000 Subject: [PATCH 262/648] Fix styling --- tests/PermissionMiddlewareTest.php | 2 +- tests/RoleMiddlewareTest.php | 1 - tests/RoleOrPermissionMiddlewareTest.php | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/PermissionMiddlewareTest.php b/tests/PermissionMiddlewareTest.php index 9d9659107..39a57c48b 100644 --- a/tests/PermissionMiddlewareTest.php +++ b/tests/PermissionMiddlewareTest.php @@ -77,7 +77,7 @@ public function a_super_admin_user_can_access_a_route_protected_by_permission_mi Auth::login($this->testUser); Gate::before(function ($user, $ability) { - return $user->getKey() === $this->testUser->getKey() ? true : null; + return $user->getKey() === $this->testUser->getKey() ? true : null; }); $this->assertEquals( diff --git a/tests/RoleMiddlewareTest.php b/tests/RoleMiddlewareTest.php index de9f6e844..7d532a004 100644 --- a/tests/RoleMiddlewareTest.php +++ b/tests/RoleMiddlewareTest.php @@ -6,7 +6,6 @@ use Illuminate\Http\Response; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Config; -use Illuminate\Support\Facades\Gate; use InvalidArgumentException; use Spatie\Permission\Exceptions\UnauthorizedException; use Spatie\Permission\Middlewares\RoleMiddleware; diff --git a/tests/RoleOrPermissionMiddlewareTest.php b/tests/RoleOrPermissionMiddlewareTest.php index 20a4139be..e9f628932 100644 --- a/tests/RoleOrPermissionMiddlewareTest.php +++ b/tests/RoleOrPermissionMiddlewareTest.php @@ -72,7 +72,7 @@ public function a_super_admin_user_can_access_a_route_protected_by_permission_or Auth::login($this->testUser); Gate::before(function ($user, $ability) { - return $user->getKey() === $this->testUser->getKey() ? true : null; + return $user->getKey() === $this->testUser->getKey() ? true : null; }); $this->assertEquals( From 5ed128b611c3b02176768ac3b3375979913678b0 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Thu, 30 Mar 2023 17:23:16 -0400 Subject: [PATCH 263/648] [Docs] v6 RoleOrPermissionMiddleware upgrade note --- docs/upgrading.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/upgrading.md b/docs/upgrading.md index 9ff46d95d..4f1e1b481 100644 --- a/docs/upgrading.md +++ b/docs/upgrading.md @@ -27,7 +27,9 @@ Be sure to compare your custom models with originals to see what else may have c 2. If you have a custom Role model and (in the rare case that you might) have overridden the `hasPermissionTo()` method in it, you will need to update its method signature to `hasPermissionTo($permission, $guardName = null):bool`. See PR #2380. -3. Also note that the migrations have been updated to anonymous-class syntax that was introduced in Laravel 8. You may optionally update your original migration files accordingly. +3. Also note that the migrations have been updated to anonymous-class syntax that was introduced in Laravel 8. You may optionally update your original migration files accordingly. + +4. NOTE: For consistency with the `PermissionMiddleware`, the `RoleOrPermissionMiddleware` has switched from only checking permissions provided by this package to using `canAny()` to check against any abilities registered by your application. This may have the effect of granting those other abilities (such as Super Admin) when using the `RoleOrPermissionMiddleware`, which previously would have failed silently. ### Upgrading from v1 to v2 If you're upgrading from v1 to v2, there's no built-in automatic migration/conversion of your data to the new structure. From 38d0074a2f185f685415d7f016e452b79e4383c6 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Mon, 27 Mar 2023 16:31:56 -0500 Subject: [PATCH 264/648] Add PHPStan workflow with fixes --- .gitattributes | 2 ++ .github/workflows/phpstan.yml | 31 ++++++++++++++++++++++ composer.json | 4 ++- phpstan-baseline.neon | 2 ++ phpstan.neon.dist | 17 ++++++++++++ src/Contracts/Permission.php | 6 +++++ src/Contracts/Role.php | 6 +++++ src/Middlewares/RoleMiddleware.php | 2 +- src/Models/Permission.php | 9 +++---- src/Models/Role.php | 19 +++++++------- src/PermissionRegistrar.php | 6 ++--- src/PermissionServiceProvider.php | 10 +++---- src/Traits/HasPermissions.php | 42 +++++++++++++++--------------- src/Traits/HasRoles.php | 18 ++++++------- 14 files changed, 118 insertions(+), 56 deletions(-) create mode 100644 .github/workflows/phpstan.yml create mode 100644 phpstan-baseline.neon create mode 100644 phpstan.neon.dist diff --git a/.gitattributes b/.gitattributes index 993e0d23c..47d2cd035 100644 --- a/.gitattributes +++ b/.gitattributes @@ -13,6 +13,8 @@ /tests export-ignore /.editorconfig export-ignore /.php_cs.dist.php export-ignore +/phpstan* export-ignore /.styleci.yml export-ignore /CHANGELOG.md export-ignore /CONTRIBUTING.md export-ignore + diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml new file mode 100644 index 000000000..acd8b906e --- /dev/null +++ b/.github/workflows/phpstan.yml @@ -0,0 +1,31 @@ +name: PHPStan + +on: + push: + paths: + - '**.php' + - 'phpstan.neon.dist' + +jobs: + phpstan: + name: phpstan + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.1 + coverage: none + + - name: Install composer dependencies + uses: ramsey/composer-install@v2 + + - name: Install larastan + run: | + composer require "nunomaduro/larastan" --no-interaction --no-update + composer update --prefer-dist --no-interaction + + - name: Run PHPStan + run: ./vendor/bin/phpstan --error-format=github diff --git a/composer.json b/composer.json index d05f7dfcb..8a01fd579 100644 --- a/composer.json +++ b/composer.json @@ -63,6 +63,8 @@ } }, "scripts": { - "test": "phpunit" + "test": "phpunit", + "format": "php-cs-fixer fix --allow-risky=yes", + "analyse": "phpstan analyse" } } diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 000000000..364905f71 --- /dev/null +++ b/phpstan-baseline.neon @@ -0,0 +1,2 @@ +parameters: + ignoreErrors: diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 000000000..b367f0713 --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,17 @@ +includes: + - ./vendor/nunomaduro/larastan/extension.neon + - phpstan-baseline.neon + +parameters: + level: 5 + paths: + - src + - config + - database/migrations/create_permission_tables.php.stub + - database/migrations/add_teams_fields.php.stub + tmpDir: build/phpstan + checkOctaneCompatibility: true + checkMissingIterableValueType: false + + ignoreErrors: + - '#Unsafe usage of new static#' diff --git a/src/Contracts/Permission.php b/src/Contracts/Permission.php index fe151d077..e54f84d21 100644 --- a/src/Contracts/Permission.php +++ b/src/Contracts/Permission.php @@ -4,6 +4,12 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany; +/** + * @property int $id + * @property string $name + * @property string $guard_name + * @mixin \Spatie\Permission\Models\Permission + */ interface Permission { /** diff --git a/src/Contracts/Role.php b/src/Contracts/Role.php index 94773e4fa..9e2281428 100644 --- a/src/Contracts/Role.php +++ b/src/Contracts/Role.php @@ -4,6 +4,12 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany; +/** + * @property int $id + * @property string $name + * @property string $guard_name + * @mixin \Spatie\Permission\Models\Role + */ interface Role { /** diff --git a/src/Middlewares/RoleMiddleware.php b/src/Middlewares/RoleMiddleware.php index f3e972a9b..4e81f0073 100644 --- a/src/Middlewares/RoleMiddleware.php +++ b/src/Middlewares/RoleMiddleware.php @@ -26,7 +26,7 @@ public function handle($request, Closure $next, $role, $guard = null) ? $role : explode('|', $role); - if (! $user->hasAnyRole($roles) && ! $user->can('')) { + if (! $user->hasAnyRole($roles)) { throw UnauthorizedException::forRoles($roles); } diff --git a/src/Models/Permission.php b/src/Models/Permission.php index e7b428c9d..631597fac 100644 --- a/src/Models/Permission.php +++ b/src/Models/Permission.php @@ -14,9 +14,6 @@ use Spatie\Permission\Traits\RefreshesPermissionCache; /** - * @property int $id - * @property string $name - * @property string $guard_name * @property ?\Illuminate\Support\Carbon $created_at * @property ?\Illuminate\Support\Carbon $updated_at */ @@ -82,7 +79,7 @@ public function users(): BelongsToMany * * @param string|null $guardName * - * @throws \Spatie\Permission\Exceptions\PermissionDoesNotExist + * @throws PermissionDoesNotExist */ public static function findByName(string $name, $guardName = null): PermissionContract { @@ -101,7 +98,7 @@ public static function findByName(string $name, $guardName = null): PermissionCo * @param int|string $id * @param string|null $guardName * - * @throws \Spatie\Permission\Exceptions\PermissionDoesNotExist + * @throws PermissionDoesNotExist */ public static function findById($id, $guardName = null): PermissionContract { @@ -145,7 +142,7 @@ protected static function getPermissions(array $params = [], bool $onlyOne = fal /** * Get the current cached first permission. * - * @return \Spatie\Permission\Contracts\Permission + * @return PermissionContract */ protected static function getPermission(array $params = []): ?PermissionContract { diff --git a/src/Models/Role.php b/src/Models/Role.php index 7da997548..f948b0aa3 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -6,6 +6,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Spatie\Permission\Contracts\Role as RoleContract; use Spatie\Permission\Exceptions\GuardDoesNotMatch; +use Spatie\Permission\Exceptions\PermissionDoesNotExist; use Spatie\Permission\Exceptions\RoleAlreadyExists; use Spatie\Permission\Exceptions\RoleDoesNotExist; use Spatie\Permission\Guard; @@ -14,9 +15,6 @@ use Spatie\Permission\Traits\RefreshesPermissionCache; /** - * @property int $id - * @property string $name - * @property string $guard_name * @property ?\Illuminate\Support\Carbon $created_at * @property ?\Illuminate\Support\Carbon $updated_at */ @@ -89,9 +87,9 @@ public function users(): BelongsToMany * Find a role by its name and guard name. * * @param string|null $guardName - * @return \Spatie\Permission\Contracts\Role|\Spatie\Permission\Models\Role + * @return RoleContract|Role * - * @throws \Spatie\Permission\Exceptions\RoleDoesNotExist + * @throws RoleDoesNotExist */ public static function findByName(string $name, $guardName = null): RoleContract { @@ -111,7 +109,7 @@ public static function findByName(string $name, $guardName = null): RoleContract * * @param int|string $id * @param string|null $guardName - * @return \Spatie\Permission\Contracts\Role|\Spatie\Permission\Models\Role + * @return RoleContract|Role */ public static function findById($id, $guardName = null): RoleContract { @@ -130,7 +128,7 @@ public static function findById($id, $guardName = null): RoleContract * Find or create role by its name (and optionally guardName). * * @param string|null $guardName - * @return \Spatie\Permission\Contracts\Role|\Spatie\Permission\Models\Role + * @return RoleContract|Role */ public static function findOrCreate(string $name, $guardName = null): RoleContract { @@ -167,11 +165,12 @@ protected static function findByParam(array $params = []) } /** - * Determine if the user may perform the given permission. + * Determine if the role may perform the given permission. * - * @param string|Permission $permission + * @param string|int|Permission $permission + * @param string|null $guardName * - * @throws \Spatie\Permission\Exceptions\GuardDoesNotMatch + * @throws PermissionDoesNotExist|GuardDoesNotMatch */ public function hasPermissionTo($permission, $guardName = null): bool { diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index 2b64eed6b..2032be779 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -13,10 +13,10 @@ class PermissionRegistrar { - /** @var \Illuminate\Contracts\Cache\Repository */ + /** @var Repository */ protected $cache; - /** @var \Illuminate\Cache\CacheManager */ + /** @var CacheManager */ protected $cacheManager; /** @var string */ @@ -25,7 +25,7 @@ class PermissionRegistrar /** @var string */ protected $roleClass; - /** @var \Illuminate\Database\Eloquent\Collection */ + /** @var Collection|null */ protected $permissions; /** @var string */ diff --git a/src/PermissionServiceProvider.php b/src/PermissionServiceProvider.php index fdf0670df..4cab16979 100644 --- a/src/PermissionServiceProvider.php +++ b/src/PermissionServiceProvider.php @@ -51,12 +51,12 @@ public function register() protected function offerPublishing() { - if (! function_exists('config_path')) { - // function not available and 'publish' not relevant in Lumen + if (! $this->app->runningInConsole()) { return; } - if (! $this->app->runningInConsole()) { + if (! function_exists('config_path')) { + // function not available and 'publish' not relevant in Lumen return; } @@ -156,7 +156,7 @@ protected function registerMacroHelpers() Route::macro('role', function ($roles = []) { $roles = implode('|', Arr::wrap($roles)); - /** @var \Illuminate\Routing\Route $this */ + /** @var Route $this */ $this->middleware("role:$roles"); return $this; @@ -165,7 +165,7 @@ protected function registerMacroHelpers() Route::macro('permission', function ($permissions = []) { $permissions = implode('|', Arr::wrap($permissions)); - /** @var \Illuminate\Routing\Route $this */ + /** @var Route $this */ $this->middleware("permission:$permissions"); return $this; diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index c1b602bea..160be5f93 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -95,7 +95,7 @@ public function permissions(): BelongsToMany /** * Scope the model query to certain permissions only. * - * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection $permissions + * @param string|int|array|Permission|Collection $permissions */ public function scopePermission(Builder $query, $permissions): Builder { @@ -122,9 +122,9 @@ public function scopePermission(Builder $query, $permissions): Builder } /** - * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection $permissions + * @param string|int|array|Permission|Collection $permissions * - * @throws \Spatie\Permission\Exceptions\PermissionDoesNotExist + * @throws PermissionDoesNotExist */ protected function convertToPermissionModels($permissions): array { @@ -145,8 +145,8 @@ protected function convertToPermissionModels($permissions): array /** * Find a permission. * - * @param string|int|\Spatie\Permission\Contracts\Permission $permission - * @return \Spatie\Permission\Contracts\Permission + * @param string|int|Permission $permission + * @return Permission * * @throws PermissionDoesNotExist */ @@ -176,7 +176,7 @@ public function filterPermission($permission, $guardName = null) /** * Determine if the model may perform the given permission. * - * @param string|int|\Spatie\Permission\Contracts\Permission $permission + * @param string|int|Permission $permission * @param string|null $guardName * * @throws PermissionDoesNotExist @@ -195,7 +195,7 @@ public function hasPermissionTo($permission, $guardName = null): bool /** * Validates a wildcard permission against all permissions of a user. * - * @param string|int|\Spatie\Permission\Contracts\Permission $permission + * @param string|int|Permission $permission * @param string|null $guardName */ protected function hasWildcardPermission($permission, $guardName = null): bool @@ -234,7 +234,7 @@ protected function hasWildcardPermission($permission, $guardName = null): bool /** * An alias to hasPermissionTo(), but avoids throwing an exception. * - * @param string|int|\Spatie\Permission\Contracts\Permission $permission + * @param string|int|Permission $permission * @param string|null $guardName */ public function checkPermissionTo($permission, $guardName = null): bool @@ -249,7 +249,7 @@ public function checkPermissionTo($permission, $guardName = null): bool /** * Determine if the model has any of the given permissions. * - * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection ...$permissions + * @param string|int|array|Permission|Collection ...$permissions */ public function hasAnyPermission(...$permissions): bool { @@ -267,7 +267,7 @@ public function hasAnyPermission(...$permissions): bool /** * Determine if the model has all of the given permissions. * - * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection ...$permissions + * @param string|int|array|Permission|Collection ...$permissions */ public function hasAllPermissions(...$permissions): bool { @@ -297,7 +297,7 @@ protected function hasPermissionViaRole(Permission $permission): bool /** * Determine if the model has the given permission. * - * @param string|int|\Spatie\Permission\Contracts\Permission $permission + * @param string|int|Permission $permission * * @throws PermissionDoesNotExist */ @@ -341,7 +341,7 @@ public function getAllPermissions(): Collection /** * Returns permissions ids as array keys * - * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection $permissions + * @param string|int|array|Permission|Collection $permissions */ private function collectPermissions(...$permissions): array { @@ -369,7 +369,7 @@ private function collectPermissions(...$permissions): array /** * Grant the given permission(s) to a role. * - * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection $permissions + * @param string|int|array|Permission|Collection $permissions * @return $this */ public function givePermissionTo(...$permissions) @@ -405,7 +405,7 @@ function ($object) use ($permissions, $model) { /** * Remove all current permissions and set the given ones. * - * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection $permissions + * @param string|int|array|Permission|Collection $permissions * @return $this */ public function syncPermissions(...$permissions) @@ -420,7 +420,7 @@ public function syncPermissions(...$permissions) /** * Revoke the given permission(s). * - * @param \Spatie\Permission\Contracts\Permission|\Spatie\Permission\Contracts\Permission[]|string|string[] $permission + * @param Permission|Permission[]|string|string[] $permission * @return $this */ public function revokePermissionTo($permission) @@ -442,8 +442,8 @@ public function getPermissionNames(): Collection } /** - * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection $permissions - * @return \Spatie\Permission\Contracts\Permission|\Spatie\Permission\Contracts\Permission[]|\Illuminate\Support\Collection + * @param string|int|array|Permission|Collection $permissions + * @return Permission|Permission[]|Collection */ protected function getStoredPermission($permissions) { @@ -469,9 +469,9 @@ protected function getStoredPermission($permissions) } /** - * @param \Spatie\Permission\Contracts\Permission|\Spatie\Permission\Contracts\Role $roleOrPermission + * @param Permission|Role $roleOrPermission * - * @throws \Spatie\Permission\Exceptions\GuardDoesNotMatch + * @throws GuardDoesNotMatch */ protected function ensureModelSharesGuard($roleOrPermission) { @@ -501,7 +501,7 @@ public function forgetCachedPermissions() /** * Check if the model has All of the requested Direct permissions. * - * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection ...$permissions + * @param string|int|array|Permission|Collection ...$permissions */ public function hasAllDirectPermissions(...$permissions): bool { @@ -519,7 +519,7 @@ public function hasAllDirectPermissions(...$permissions): bool /** * Check if the model has Any of the requested Direct permissions. * - * @param string|int|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection ...$permissions + * @param string|int|array|Permission|Collection ...$permissions */ public function hasAnyDirectPermission(...$permissions): bool { diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index 99ec852f3..0feca7e52 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -70,7 +70,7 @@ public function roles(): BelongsToMany /** * Scope the model query to certain roles only. * - * @param string|int|array|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection $roles + * @param string|int|array|Role|Collection $roles * @param string $guard */ public function scopeRole(Builder $query, $roles, $guard = null): Builder @@ -99,7 +99,7 @@ public function scopeRole(Builder $query, $roles, $guard = null): Builder /** * Returns roles ids as array keys * - * @param array|string|int|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection $roles + * @param array|string|int|Role|Collection $roles */ private function collectRoles(...$roles): array { @@ -127,7 +127,7 @@ private function collectRoles(...$roles): array /** * Assign the given role to the model. * - * @param array|string|int|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection ...$roles + * @param array|string|int|Role|Collection ...$roles * @return $this */ public function assignRole(...$roles) @@ -163,7 +163,7 @@ function ($object) use ($roles, $model) { /** * Revoke the given role from the model. * - * @param string|int|\Spatie\Permission\Contracts\Role $role + * @param string|int|Role $role */ public function removeRole($role) { @@ -181,7 +181,7 @@ public function removeRole($role) /** * Remove all current roles and set the given ones. * - * @param array|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection|string|int ...$roles + * @param array|Role|Collection|string|int ...$roles * @return $this */ public function syncRoles(...$roles) @@ -196,7 +196,7 @@ public function syncRoles(...$roles) /** * Determine if the model has (one of) the given role(s). * - * @param string|int|array|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection $roles + * @param string|int|array|Role|Collection $roles */ public function hasRole($roles, string $guard = null): bool { @@ -243,7 +243,7 @@ public function hasRole($roles, string $guard = null): bool * * Alias to hasRole() but without Guard controls * - * @param string|int|array|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection $roles + * @param string|int|array|Role|Collection $roles */ public function hasAnyRole(...$roles): bool { @@ -253,7 +253,7 @@ public function hasAnyRole(...$roles): bool /** * Determine if the model has all of the given role(s). * - * @param string|array|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection $roles + * @param string|array|Role|Collection $roles */ public function hasAllRoles($roles, string $guard = null): bool { @@ -287,7 +287,7 @@ public function hasAllRoles($roles, string $guard = null): bool /** * Determine if the model has exactly all of the given role(s). * - * @param string|array|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection $roles + * @param string|array|Role|Collection $roles */ public function hasExactRoles($roles, string $guard = null): bool { From 5319e3accf9d0aa36ad60d423cbe222739872b35 Mon Sep 17 00:00:00 2001 From: drbyte Date: Thu, 30 Mar 2023 21:56:06 +0000 Subject: [PATCH 265/648] Fix styling --- src/Contracts/Permission.php | 1 + src/Contracts/Role.php | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Contracts/Permission.php b/src/Contracts/Permission.php index e54f84d21..8b3d14e18 100644 --- a/src/Contracts/Permission.php +++ b/src/Contracts/Permission.php @@ -8,6 +8,7 @@ * @property int $id * @property string $name * @property string $guard_name + * * @mixin \Spatie\Permission\Models\Permission */ interface Permission diff --git a/src/Contracts/Role.php b/src/Contracts/Role.php index 9e2281428..04009de37 100644 --- a/src/Contracts/Role.php +++ b/src/Contracts/Role.php @@ -8,6 +8,7 @@ * @property int $id * @property string $name * @property string $guard_name + * * @mixin \Spatie\Permission\Models\Role */ interface Role From bc019441d3afb47da24ea35d44bf8692f5d84430 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Thu, 30 Mar 2023 22:45:05 -0400 Subject: [PATCH 266/648] [docs] tidying --- docs/installation-laravel.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/installation-laravel.md b/docs/installation-laravel.md index 5b410cb5c..da36f7869 100644 --- a/docs/installation-laravel.md +++ b/docs/installation-laravel.md @@ -7,15 +7,15 @@ weight: 4 This package can be used with Laravel 6.0 or higher. -Package | Laravel Version ---------|----------- - ^6.0 | 8,9,10 - ^5.8 | 7,8,9,10 - ^5.7 | 7,8,9 -^5.4-^5.6 | 7,8 - 5.0-5.3 | 6,7,8 - ^4 | 6,7,8 - ^3 | 5.8 +Package Version | Laravel Version +----------------|----------- + ^6.0 | 8,9,10 + ^5.8 | 7,8,9,10 + ^5.7 | 7,8,9 + ^5.4-^5.6 | 7,8 + 5.0-5.3 | 6,7,8 + ^4 | 6,7,8 + ^3 | 5.8 ## Installing From be8dcac8932b44e787563000e0e175b406b8e319 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Thu, 30 Mar 2023 22:46:33 -0400 Subject: [PATCH 267/648] [docs] tidying --- docs/installation-laravel.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/installation-laravel.md b/docs/installation-laravel.md index da36f7869..bbf7c429a 100644 --- a/docs/installation-laravel.md +++ b/docs/installation-laravel.md @@ -10,9 +10,9 @@ This package can be used with Laravel 6.0 or higher. Package Version | Laravel Version ----------------|----------- ^6.0 | 8,9,10 - ^5.8 | 7,8,9,10 + ^5.8 | 7,8,9,10 ^5.7 | 7,8,9 - ^5.4-^5.6 | 7,8 + ^5.4-^5.6 | 7,8 5.0-5.3 | 6,7,8 ^4 | 6,7,8 ^3 | 5.8 @@ -43,8 +43,8 @@ Package Version | Laravel Version php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" ``` -6. NOTE: If you are using UUIDs, see the Advanced section of the docs on UUID steps, before you continue. It explains some changes you may want to make to the migrations and config file before continuing. It also mentions important considerations after extending this package's models for UUID capability. - If you are going to use teams feature, you have to update your [`config/permission.php` config file](https://github.com/spatie/laravel-permission/blob/main/config/permission.php) and set `'teams' => true,`, if you want to use a custom foreign key for teams you must change `team_foreign_key`. +6. NOTE: **If you are using UUIDs**, see the Advanced section of the docs on UUID steps, before you continue. It explains some changes you may want to make to the migrations and config file before continuing. It also mentions important considerations after extending this package's models for UUID capability. + **If you are going to use the TEAMS features**, you must update your [`config/permission.php` config file](https://github.com/spatie/laravel-permission/blob/main/config/permission.php) and set `'teams' => true,`; and in your database if you want to use a custom foreign key for teams you must change `team_foreign_key`. 7. NOTE: If you are using MySQL 8, look at the migration files for notes about MySQL 8 to set/limit the index key length, and edit accordingly. From 38bf2fc0075638eb8ad2ef4aedfaecfef8176073 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Thu, 30 Mar 2023 22:47:39 -0400 Subject: [PATCH 268/648] [docs] Tidying --- docs/installation-laravel.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/installation-laravel.md b/docs/installation-laravel.md index bbf7c429a..bd77f7f68 100644 --- a/docs/installation-laravel.md +++ b/docs/installation-laravel.md @@ -9,13 +9,13 @@ This package can be used with Laravel 6.0 or higher. Package Version | Laravel Version ----------------|----------- - ^6.0 | 8,9,10 - ^5.8 | 7,8,9,10 - ^5.7 | 7,8,9 - ^5.4-^5.6 | 7,8 + ^6.0 | 8,9,10 + ^5.8 | 7,8,9,10 + ^5.7 | 7,8,9 + ^5.4-^5.6 | 7,8 5.0-5.3 | 6,7,8 - ^4 | 6,7,8 - ^3 | 5.8 + ^4 | 6,7,8 + ^3 | 5.8 ## Installing From f85eb66a79251c8582a28a40f0f7b959f6460582 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Thu, 30 Mar 2023 22:48:16 -0400 Subject: [PATCH 269/648] [docs] tidying --- docs/installation-laravel.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/installation-laravel.md b/docs/installation-laravel.md index bd77f7f68..65e20c29c 100644 --- a/docs/installation-laravel.md +++ b/docs/installation-laravel.md @@ -44,6 +44,7 @@ Package Version | Laravel Version ``` 6. NOTE: **If you are using UUIDs**, see the Advanced section of the docs on UUID steps, before you continue. It explains some changes you may want to make to the migrations and config file before continuing. It also mentions important considerations after extending this package's models for UUID capability. + **If you are going to use the TEAMS features**, you must update your [`config/permission.php` config file](https://github.com/spatie/laravel-permission/blob/main/config/permission.php) and set `'teams' => true,`; and in your database if you want to use a custom foreign key for teams you must change `team_foreign_key`. 7. NOTE: If you are using MySQL 8, look at the migration files for notes about MySQL 8 to set/limit the index key length, and edit accordingly. From 23a6b12a6bbbb2da2b3dfce956e47dfce1cbd39f Mon Sep 17 00:00:00 2001 From: erikn69 Date: Thu, 30 Mar 2023 16:37:02 -0500 Subject: [PATCH 270/648] [tests] tidying --- tests/TestModels/Admin.php | 17 +---------------- tests/TestModels/Manager.php | 19 +------------------ tests/TestModels/User.php | 15 +-------------- 3 files changed, 3 insertions(+), 48 deletions(-) diff --git a/tests/TestModels/Admin.php b/tests/TestModels/Admin.php index dc4cf75e3..5b6334a98 100644 --- a/tests/TestModels/Admin.php +++ b/tests/TestModels/Admin.php @@ -2,22 +2,7 @@ namespace Spatie\Permission\Tests\TestModels; -use Illuminate\Auth\Authenticatable; -use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract; -use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract; -use Illuminate\Database\Eloquent\Model; -use Illuminate\Foundation\Auth\Access\Authorizable; -use Spatie\Permission\Traits\HasRoles; - -class Admin extends Model implements AuthorizableContract, AuthenticatableContract +class Admin extends User { - use HasRoles; - use Authorizable; - use Authenticatable; - - protected $fillable = ['email']; - - public $timestamps = false; - protected $table = 'admins'; } diff --git a/tests/TestModels/Manager.php b/tests/TestModels/Manager.php index c75b0d007..3405d924e 100644 --- a/tests/TestModels/Manager.php +++ b/tests/TestModels/Manager.php @@ -2,25 +2,8 @@ namespace Spatie\Permission\Tests\TestModels; -use Illuminate\Auth\Authenticatable; -use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract; -use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract; -use Illuminate\Database\Eloquent\Model; -use Illuminate\Foundation\Auth\Access\Authorizable; -use Spatie\Permission\Traits\HasRoles; - -class Manager extends Model implements AuthorizableContract, AuthenticatableContract +class Manager extends User { - use HasRoles; - use Authorizable; - use Authenticatable; - - protected $fillable = ['email']; - - public $timestamps = false; - - protected $table = 'users'; - // this function is added here to support the unit tests verifying it works // When present, it takes precedence over the $guard_name property. public function guardName() diff --git a/tests/TestModels/User.php b/tests/TestModels/User.php index 8398d34b9..288f5f5f2 100644 --- a/tests/TestModels/User.php +++ b/tests/TestModels/User.php @@ -2,22 +2,9 @@ namespace Spatie\Permission\Tests\TestModels; -use Illuminate\Auth\Authenticatable; -use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract; -use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract; -use Illuminate\Database\Eloquent\Model; -use Illuminate\Foundation\Auth\Access\Authorizable; use Spatie\Permission\Traits\HasRoles; -class User extends Model implements AuthorizableContract, AuthenticatableContract +class User extends UserWithoutHasRoles { use HasRoles; - use Authorizable; - use Authenticatable; - - protected $fillable = ['email']; - - public $timestamps = false; - - protected $table = 'users'; } From 20c10e31ea68b21ecab0ad01234aebc615c48ea7 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Sat, 1 Apr 2023 19:32:55 -0400 Subject: [PATCH 271/648] [docs] tidying --- docs/upgrading.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/upgrading.md b/docs/upgrading.md index 4f1e1b481..7089bfc7b 100644 --- a/docs/upgrading.md +++ b/docs/upgrading.md @@ -18,6 +18,7 @@ ALL upgrades of this package should follow these steps: 5. Apply any version-specific special updates as outlined below... 6. Review the changelog, which details all the changes: https://github.com/spatie/laravel-permission/blob/main/CHANGELOG.md +and/or consult the [Release Notes](https://github.com/spatie/laravel-permission/releases) ### Upgrading to v6 From 95527721299e1dfe79d1c5b39d9a124ed2ecfd4f Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Wed, 5 Apr 2023 19:22:11 -0400 Subject: [PATCH 272/648] [docs] v6 note about no longer manually calling `registerPermissions()` --- docs/upgrading.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/upgrading.md b/docs/upgrading.md index 7089bfc7b..1c6c0ebc3 100644 --- a/docs/upgrading.md +++ b/docs/upgrading.md @@ -32,6 +32,9 @@ Be sure to compare your custom models with originals to see what else may have c 4. NOTE: For consistency with the `PermissionMiddleware`, the `RoleOrPermissionMiddleware` has switched from only checking permissions provided by this package to using `canAny()` to check against any abilities registered by your application. This may have the effect of granting those other abilities (such as Super Admin) when using the `RoleOrPermissionMiddleware`, which previously would have failed silently. +5. Test suites. If you have tests which manually clear the permission cache and re-register permissions, you no longer need to call `\Spatie\Permission\PermissionRegistrar::class)->registerPermissions();`. Such calls can be deleted from your tests. + + ### Upgrading from v1 to v2 If you're upgrading from v1 to v2, there's no built-in automatic migration/conversion of your data to the new structure. You will need to carefully adapt your code and your data manually. From f9b6573e3962cf06082d3dfde4db6c0dfbef6c90 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 7 Apr 2023 16:37:57 -0400 Subject: [PATCH 273/648] [docs] fix typo --- docs/advanced-usage/cache.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/advanced-usage/cache.md b/docs/advanced-usage/cache.md index 3b1f8160e..5a87370b9 100644 --- a/docs/advanced-usage/cache.md +++ b/docs/advanced-usage/cache.md @@ -53,7 +53,7 @@ If you wish to alter the expiration time you may do so in the `config/permission ### Cache Key The default cache key is `spatie.permission.cache`. -We recommend not changing the cache "key" name. Usually changing it is a bad idea. More likely setting the cache `prefix` is better, as mentioned above. +We recommend not changing the cache "key" name. Usually changing it is a bad idea. More likely setting the cache `prefix` is better, as mentioned below. ### Cache Identifier / Prefix From 0431448f9f9f2741c11a39d75f437be310f0ab19 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 7 Apr 2023 16:54:09 -0400 Subject: [PATCH 274/648] [Docs] Minor updates to testing docs --- docs/advanced-usage/testing.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/docs/advanced-usage/testing.md b/docs/advanced-usage/testing.md index d0cd3e7fe..ac205980a 100644 --- a/docs/advanced-usage/testing.md +++ b/docs/advanced-usage/testing.md @@ -10,16 +10,18 @@ In your application's tests, if you are not seeding roles and permissions as par In your tests simply add a `setUp()` instruction to re-register the permissions, like this: ```php - public function setUp(): void + protected function setUp(): void { // first include all the normal setUp operations parent::setUp(); - // now re-register all the roles and permissions (clears cache and reloads relations) - $this->app->make(\Spatie\Permission\PermissionRegistrar::class)->registerPermissions(); + // now de-register all the roles and permissions by clearing the permission cache + $this->app->make(\Spatie\Permission\PermissionRegistrar::class)->forgetCachedPermissions(); } ``` +## Clear Cache When Using Seeders + If you are using Laravel's `LazilyRefreshDatabase` trait, you most likely want to avoid seeding permissions before every test, because that would negate the use of the `LazilyRefreshDatabase` trait. To overcome this, you should wrap your seeder in an event listener for the `MigrationsEnded` event: ```php @@ -29,7 +31,7 @@ Event::listen(MigrationsEnded::class, function () { }); ``` -Note that we call `PermissionRegistrar::forgetCachedPermissions` after seeding. This is to prevent a caching issue that can occur when the database is set up after permissions have already been registered and cached. +Note that `PermissionRegistrar::forgetCachedPermissions()` is called AFTER seeding. This is to prevent a caching issue that can occur when the database is set up after permissions have already been registered and cached. ## Factories @@ -37,7 +39,4 @@ Many applications do not require using factories to create fake roles/permission However, if your application allows users to define their own roles and permissions you may wish to use Model Factories to generate roles and permissions as part of your test suite. -With Laravel 7 you can simply create a model factory using the artisan command, and then call the `factory()` helper function to invoke it as needed. - -With Laravel 8 if you want to use the class-based Model Factory features you will need to `extend` this package's `Role` and/or `Permission` model into your app's namespace, add the `HasFactory` trait to it, and define a model factory for it. Then you can use that factory in your seeders like any other factory related to your app's models. - +When using Laravel's class-based Model Factory features you will need to `extend` this package's `Role` and/or `Permission` model into your app's namespace, add the `HasFactory` trait to it, and define a model factory for it. Then you can use that factory in your seeders like any other factory related to your application's models. From 56baec6e4a9bbc6eb4dc51d9ecdd9aac8049ccfa Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 7 Apr 2023 17:10:29 -0400 Subject: [PATCH 275/648] [Docs] small comments on testing --- docs/advanced-usage/testing.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/docs/advanced-usage/testing.md b/docs/advanced-usage/testing.md index ac205980a..f67ecd4a2 100644 --- a/docs/advanced-usage/testing.md +++ b/docs/advanced-usage/testing.md @@ -33,7 +33,21 @@ Event::listen(MigrationsEnded::class, function () { Note that `PermissionRegistrar::forgetCachedPermissions()` is called AFTER seeding. This is to prevent a caching issue that can occur when the database is set up after permissions have already been registered and cached. -## Factories + +## Bypassing Cache When Testing + +The caching infrastructure for this package is "always on", but when running your test suite you may wish to reduce its impact. + +Two things you might wish to explore include: + +- Change the cache driver to `array`. **Very often you will have already done this in your `phpunit.xml` configuration.** + +- Shorten cache lifetime to 1 second, by setting the config (not necessary if cache driver is set to `array`) in your test suite TestCase: + + `'permission.cache.expiration_time' = \DateInterval::createFromDateString('1 seconds')` + + +## Testing Using Factories Many applications do not require using factories to create fake roles/permissions for testing, because they use a Seeder to create specific roles and permissions that the application uses; thus tests are performed using the declared roles and permissions. From 50dc0aaf33f419b04c68b8955812baad596b2bce Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 7 Apr 2023 19:15:54 -0400 Subject: [PATCH 276/648] [Docs] Reviewed Demo app instructions NOTE: Tested and confirmed that these instructions are still valid with Laravel 8-10 --- docs/basic-usage/new-app.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/basic-usage/new-app.md b/docs/basic-usage/new-app.md index 449955d9d..b3f112d0b 100644 --- a/docs/basic-usage/new-app.md +++ b/docs/basic-usage/new-app.md @@ -9,7 +9,7 @@ If you want to just try out the features of this package you can get started wit The examples on this page are primarily added for assistance in creating a quick demo app for troubleshooting purposes, to post the repo on github for convenient sharing to collaborate or get support. -If you're new to Laravel or to any of the concepts mentioned here, you can learn more in the [Laravel documentation](https://laravel.com/docs/) and in the free videos at Laracasts such as with the [Laravel From Scratch series](https://laracasts.com/series/laravel-6-from-scratch/). +If you're new to Laravel or to any of the concepts mentioned here, you can learn more in the [Laravel documentation](https://laravel.com/docs/) and in the free videos at Laracasts such as with the [Laravel From Scratch series](https://laracasts.com/series/laravel-8-from-scratch/). ### Initial setup: From a444757340087a4f5220cf2bc097b077036577f6 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 7 Apr 2023 19:19:11 -0400 Subject: [PATCH 277/648] [Docs] refer to version matrix --- docs/prerequisites.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/prerequisites.md b/docs/prerequisites.md index fc64ed7a8..359276e01 100644 --- a/docs/prerequisites.md +++ b/docs/prerequisites.md @@ -5,7 +5,7 @@ weight: 3 ## Laravel Version -This package can be used in Laravel 6 or higher. +This package can be used in Laravel 6 or higher. Check the "Installing on Laravel" page for package versions compatible with various Laravel versions. ## User Model / Contract/Interface From 99443d5ed05dd4d5cc711bb64a08d3eaa8404ef8 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 7 Apr 2023 19:26:19 -0400 Subject: [PATCH 278/648] [Docs] update cross-link --- docs/prerequisites.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/prerequisites.md b/docs/prerequisites.md index 359276e01..b59d4cc2d 100644 --- a/docs/prerequisites.md +++ b/docs/prerequisites.md @@ -49,5 +49,5 @@ Thus in your AppServiceProvider you will need to set `Schema::defaultStringLengt ## Note for apps using UUIDs/ULIDs/GUIDs -This package expects the primary key of your `User` model to be an auto-incrementing `int`. If it is not, you may need to modify the `create_permission_tables` migration and/or modify the default configuration. See [https://spatie.be/docs/laravel-permission/v5/advanced-usage/uuid](https://spatie.be/docs/laravel-permission/v5/advanced-usage/uuid) for more information. +This package expects the primary key of your `User` model to be an auto-incrementing `int`. If it is not, you may need to modify the `create_permission_tables` migration and/or modify the default configuration. See [https://spatie.be/docs/laravel-permission/advanced-usage/uuid](https://spatie.be/docs/laravel-permission/advanced-usage/uuid) for more information. From 564104b5b19f052a3f8ce1ec7f2ec777a7d37299 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 7 Apr 2023 19:32:07 -0400 Subject: [PATCH 279/648] [Docs] fix link --- docs/installation-lumen.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation-lumen.md b/docs/installation-lumen.md index ff3d98455..dba274fd7 100644 --- a/docs/installation-lumen.md +++ b/docs/installation-lumen.md @@ -68,7 +68,7 @@ NOTE: Remember that Laravel's authorization layer requires that your `User` mode ### User Table NOTE: If you are working with a fresh install of Lumen, then you probably also need a migration file for your Users table. You can create your own, or you can copy a basic one from Laravel: -[https://github.com/laravel/laravel/blob/main/database/migrations/2014_10_12_000000_create_users_table.php](https://github.com/laravel/laravel/blob/main/database/migrations/2014_10_12_000000_create_users_table.php) +[https://github.com/laravel/laravel/blob/master/database/migrations/2014_10_12_000000_create_users_table.php](https://github.com/laravel/laravel/blob/master/database/migrations/2014_10_12_000000_create_users_table.php) (You will need to run `php artisan migrate` after adding this file.) From 73acbe7e9af379202b6ba49675977aaf859ec093 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 7 Apr 2023 19:34:21 -0400 Subject: [PATCH 280/648] [Docs] formatting --- docs/upgrading.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/upgrading.md b/docs/upgrading.md index 1c6c0ebc3..a543f6a56 100644 --- a/docs/upgrading.md +++ b/docs/upgrading.md @@ -3,11 +3,13 @@ title: Upgrading weight: 6 --- +# Upgrade Essentials + ALL upgrades of this package should follow these steps: 1. Upgrading between major versions of this package always require the usual Composer steps: - > 1. Update your `composer.json` to specify the new major version, such as `^6.0` - > 2. Then run `composer update`. + - Update your `composer.json` to specify the new major version, such as `^6.0` + - Then run `composer update`. 2. Compare the `migration` file stubs in the NEW version of this package against the migrations you've already run inside your app. If necessary, create a new migration (by hand) to apply any new changes. @@ -21,7 +23,7 @@ ALL upgrades of this package should follow these steps: and/or consult the [Release Notes](https://github.com/spatie/laravel-permission/releases) -### Upgrading to v6 +# Upgrading to v6 1. If you have overridden the `getPermissionClass()` or `getRoleClass()` methods or have custom Models, you will need to revisit those customizations. See PR #2368 for details. eg: if you have a custom model you will need to make changes, including accessing the model using `$this->permissionClass::` syntax (eg: using `::` instead of `->`) in all the overridden methods that make use of the models. Be sure to compare your custom models with originals to see what else may have changed. @@ -35,7 +37,7 @@ Be sure to compare your custom models with originals to see what else may have c 5. Test suites. If you have tests which manually clear the permission cache and re-register permissions, you no longer need to call `\Spatie\Permission\PermissionRegistrar::class)->registerPermissions();`. Such calls can be deleted from your tests. -### Upgrading from v1 to v2 +# Upgrading from v1 to v2 If you're upgrading from v1 to v2, there's no built-in automatic migration/conversion of your data to the new structure. You will need to carefully adapt your code and your data manually. From 1a11080a304ae147960083d3bf638a2a5ef6153d Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 7 Apr 2023 19:35:31 -0400 Subject: [PATCH 281/648] [Docs] formatting --- docs/upgrading.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/upgrading.md b/docs/upgrading.md index a543f6a56..837937440 100644 --- a/docs/upgrading.md +++ b/docs/upgrading.md @@ -3,7 +3,7 @@ title: Upgrading weight: 6 --- -# Upgrade Essentials +## Upgrade Essentials ALL upgrades of this package should follow these steps: @@ -23,7 +23,7 @@ ALL upgrades of this package should follow these steps: and/or consult the [Release Notes](https://github.com/spatie/laravel-permission/releases) -# Upgrading to v6 +## Upgrading to v6 1. If you have overridden the `getPermissionClass()` or `getRoleClass()` methods or have custom Models, you will need to revisit those customizations. See PR #2368 for details. eg: if you have a custom model you will need to make changes, including accessing the model using `$this->permissionClass::` syntax (eg: using `::` instead of `->`) in all the overridden methods that make use of the models. Be sure to compare your custom models with originals to see what else may have changed. @@ -37,7 +37,7 @@ Be sure to compare your custom models with originals to see what else may have c 5. Test suites. If you have tests which manually clear the permission cache and re-register permissions, you no longer need to call `\Spatie\Permission\PermissionRegistrar::class)->registerPermissions();`. Such calls can be deleted from your tests. -# Upgrading from v1 to v2 +## Upgrading from v1 to v2 If you're upgrading from v1 to v2, there's no built-in automatic migration/conversion of your data to the new structure. You will need to carefully adapt your code and your data manually. From 8d52451c88bc3dd4d90f231c2fb21eb634816fd7 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 7 Apr 2023 19:38:18 -0400 Subject: [PATCH 282/648] [Docs] minor updates --- docs/basic-usage/basic-usage.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/basic-usage/basic-usage.md b/docs/basic-usage/basic-usage.md index 28c30efd2..2db919ac1 100644 --- a/docs/basic-usage/basic-usage.md +++ b/docs/basic-usage/basic-usage.md @@ -29,28 +29,28 @@ $permission = Permission::create(['name' => 'edit articles']); ``` -A permission can be assigned to a role using 1 of these methods: +A permission can be assigned to a role using either of these methods: ```php $role->givePermissionTo($permission); $permission->assignRole($role); ``` -Multiple permissions can be synced to a role using 1 of these methods: +Multiple permissions can be synced to a role using either of these methods: ```php $role->syncPermissions($permissions); $permission->syncRoles($roles); ``` -A permission can be removed from a role using 1 of these methods: +A permission can be removed from a role using either of these methods: ```php $role->revokePermissionTo($permission); $permission->removeRole($role); ``` -If you're using multiple guards the `guard_name` attribute needs to be set as well. Read about it in the [using multiple guards](./multiple-guards) section of the readme. +If you're using multiple guards then the `guard_name` attribute must be set as well. Read about it in the [using multiple guards](./multiple-guards) section of the readme. The `HasRoles` trait adds Eloquent relationships to your models, which can be accessed directly or used as a base query: From 15539f1bd78c49698bdb9b3cd27462b92d07c747 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 7 Apr 2023 20:44:14 -0400 Subject: [PATCH 283/648] [Docs] Rewrite permissions-vs-roles explanation --- docs/best-practices/roles-vs-permissions.md | 29 ++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/docs/best-practices/roles-vs-permissions.md b/docs/best-practices/roles-vs-permissions.md index b7d99b01f..17bc60a91 100644 --- a/docs/best-practices/roles-vs-permissions.md +++ b/docs/best-practices/roles-vs-permissions.md @@ -3,9 +3,32 @@ title: Roles vs Permissions weight: 1 --- -It is generally best to code your app around testing against `permissions` only. (ie: when testing whether to grant access to something, in most cases it's wisest to check against a `permission`, not a `role`). That way you can always use the native Laravel `@can` and `can()` directives everywhere in your app. +Best-Practice for thinking about Roles vs Permissions is this: -Roles can still be used to group permissions for easy assignment to a user/model, and you can still use the role-based helper methods if truly necessary. But most app-related logic can usually be best controlled using the `can` methods, which allows Laravel's Gate layer to do all the heavy lifting. Sometimes certain groups of `route` rules may make best sense to group them around a `role`, but still, whenever possible, there is less overhead used if you can check against a specific `permission` instead. +**Roles** are best to only assign to **Users** in order to "**group**" people by "**sets of permissions**". + +**Permissions** are best assigned **to roles**. +The more granular/detailed your permission-names (such as separate permissions like "view document" and "edit document"), the easier it is to control access in your application. + +**Users** should *rarely* be given "direct" permissions. Best if Users inherit permissions via the Roles that they're assigned to. + +When designed this way, all the sections of your application can check for specific permissions needed to access certain features or perform certain actions AND this way you can always **use the native Laravel `@can` and `can()` directives everywhere** in your app, which allows Laravel's Gate layer to do all the heavy lifting. + +Example: it's safer to have your Views test `$user->can('view member addresses')` or `$user->can('edit document')`, INSTEAD of testing for `$user->hasRole('Editor')`. It's easier to control displaying a "section" of content vs edit/delete buttons if you have "view document" and "edit document" permissions defined. And then Writer role would get both "view" and "edit" assigned to it. And then the user would get the Writer role. + +This also allows you to treat permission names as static (only editable by developers), and then your application (almost) never needs to know anything about role names, so you could (almost) change role names at will. + +Summary: +- **users** have `roles` +- **roles** have `permissions` +- app always checks for `permissions` (as much as possible), not `roles` +- **views** check permission-names +- **policies** check permission-names +- **model policies** check permission-names +- **controller methods** check permission-names +- **middleware** check permission names, or sometimes role-names +- **routes** check permission-names, or maybe role-names if you need to code that way. + +Sometimes certain groups of `route` rules may make best sense to group them around a `role`, but still, whenever possible, there is less overhead used if you can check against a specific `permission` instead. -eg: `users` have `roles`, and `roles` have `permissions`, and your app always checks for `permissions`, not `roles`. From 3216513db86fffb7c4cdb9ae3f912ca3a786fd56 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 7 Apr 2023 20:48:24 -0400 Subject: [PATCH 284/648] [Docs] formatting --- docs/basic-usage/direct-permissions.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/basic-usage/direct-permissions.md b/docs/basic-usage/direct-permissions.md index cd6a49faa..c508ff9d5 100644 --- a/docs/basic-usage/direct-permissions.md +++ b/docs/basic-usage/direct-permissions.md @@ -3,6 +3,16 @@ title: Direct Permissions weight: 2 --- +## Best Practice + +It's better to assign permissions to Roles, and then assign Roles to Users. + +See https://spatie.be/docs/laravel-permission/best-practices/roles-vs-permissions for a deeper explanation. + +HOWEVER, If you have reason to directly assign individual permissions to specific users (instead of to roles assigned to those users), you can do that as described below: + +## Direct Permissions to Users + A permission can be given to any user: ```php @@ -59,8 +69,7 @@ You may also pass integers to lookup by permission id $user->hasAnyPermission(['edit articles', 1, 5]); ``` -Saved permissions will be registered with the `Illuminate\Auth\Access\Gate` class for the default guard. So you can -check if a user has a permission with Laravel's default `can` function: +Like all permissions assigned via roles, you can check if a user has a permission by using Laravel's default `can` function: ```php $user->can('edit articles'); From b04fcc451453d1f44559ca4e6669af1b7d287041 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 7 Apr 2023 20:51:29 -0400 Subject: [PATCH 285/648] [docs] tidying --- docs/best-practices/roles-vs-permissions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/best-practices/roles-vs-permissions.md b/docs/best-practices/roles-vs-permissions.md index 17bc60a91..ad94432aa 100644 --- a/docs/best-practices/roles-vs-permissions.md +++ b/docs/best-practices/roles-vs-permissions.md @@ -14,7 +14,7 @@ The more granular/detailed your permission-names (such as separate permissions l When designed this way, all the sections of your application can check for specific permissions needed to access certain features or perform certain actions AND this way you can always **use the native Laravel `@can` and `can()` directives everywhere** in your app, which allows Laravel's Gate layer to do all the heavy lifting. -Example: it's safer to have your Views test `$user->can('view member addresses')` or `$user->can('edit document')`, INSTEAD of testing for `$user->hasRole('Editor')`. It's easier to control displaying a "section" of content vs edit/delete buttons if you have "view document" and "edit document" permissions defined. And then Writer role would get both "view" and "edit" assigned to it. And then the user would get the Writer role. +Example: it's safer to have your Views test `@can('view member addresses')` or `@can('edit document')`, INSTEAD of testing for `$user->hasRole('Editor')`. It's easier to control displaying a "section" of content vs edit/delete buttons if you have "view document" and "edit document" permissions defined. And then Writer role would get both "view" and "edit" assigned to it. And then the user would get the Writer role. This also allows you to treat permission names as static (only editable by developers), and then your application (almost) never needs to know anything about role names, so you could (almost) change role names at will. From 7c72cd6ee6a7ae241bdf1ffae0aaa46180a7ba09 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 7 Apr 2023 20:57:26 -0400 Subject: [PATCH 286/648] [Docs] formatting --- docs/basic-usage/middleware.md | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/docs/basic-usage/middleware.md b/docs/basic-usage/middleware.md index 8a1971877..976ae816b 100644 --- a/docs/basic-usage/middleware.md +++ b/docs/basic-usage/middleware.md @@ -17,19 +17,12 @@ Route::group(['middleware' => ['can:publish articles']], function () { This package comes with `RoleMiddleware`, `PermissionMiddleware` and `RoleOrPermissionMiddleware` middleware. You can add them inside your `app/Http/Kernel.php` file. -Note the differences between Laravel 10 and older versions of Laravel is the name of the `protected` property: +Note the property name difference between Laravel 10 and older versions of Laravel: -### Laravel 9 (and older) -```php -protected $routeMiddleware = [ - // ... - 'role' => \Spatie\Permission\Middlewares\RoleMiddleware::class, - 'permission' => \Spatie\Permission\Middlewares\PermissionMiddleware::class, - 'role_or_permission' => \Spatie\Permission\Middlewares\RoleOrPermissionMiddleware::class, -]; -``` -### Laravel 10 ```php +// Laravel 9 uses $routeMiddleware = [ +//protected $routeMiddleware = [ +// Laravel 10+ uses $middlewareAliases = [ protected $middlewareAliases = [ // ... 'role' => \Spatie\Permission\Middlewares\RoleMiddleware::class, @@ -38,6 +31,8 @@ protected $middlewareAliases = [ ]; ``` +## Middleware via Routes + Then you can protect your routes using middleware rules: ```php @@ -74,6 +69,8 @@ Route::group(['middleware' => ['role_or_permission:super-admin|edit articles']], }); ``` +## Middleware with Controllers + You can protect your controllers similarly, by setting desired middleware in the constructor: ```php @@ -89,3 +86,5 @@ public function __construct() $this->middleware(['role_or_permission:super-admin|edit articles']); } ``` + +(You can use Laravel's Model Policy feature with your controller methods. See the Model Policies section of these docs.) From 7fe6044869d57d0d3f2987f69ac4a363ae896341 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 7 Apr 2023 21:02:01 -0400 Subject: [PATCH 287/648] [docs] wip --- docs/advanced-usage/seeding.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/advanced-usage/seeding.md b/docs/advanced-usage/seeding.md index fcff50ce7..d27bc2a1e 100644 --- a/docs/advanced-usage/seeding.md +++ b/docs/advanced-usage/seeding.md @@ -96,3 +96,5 @@ foreach ($permissionIdsByRole as $role => $permissionIds) { ); } ``` + +**CAUTION**: ANY TIME YOU DIRECTLY RUN DB QUERIES you are bypassing cache-control features. So you will need to manually flush the package cache AFTER running direct DB queries, even in a seeder. From 56c99ff8c4198a87d247e7fd32058f6caf1c6d16 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 7 Apr 2023 21:07:53 -0400 Subject: [PATCH 288/648] [docs] formatting --- docs/best-practices/performance.md | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/docs/best-practices/performance.md b/docs/best-practices/performance.md index f2d302e68..81c40be78 100644 --- a/docs/best-practices/performance.md +++ b/docs/best-practices/performance.md @@ -3,6 +3,8 @@ title: Performance Tips weight: 10 --- +On small apps, most of the following will be moot, and unnecessary. + Often we think in terms of "roles have permissions" so we lookup a Role, and call `$role->givePermissionTo()` to indicate what users with that role are allowed to do. This is perfectly fine! @@ -12,12 +14,17 @@ you may find that things are more performant if you lookup the permission and as The end result is the same, but sometimes it runs quite a lot faster. Also, because of the way this package enforces some protections for you, on large databases you may find -that instead of creating permissions with `Permission::create([attributes])` it might be faster to -`$permission = Permission::make([attributes]); $permission->saveOrFail();` - -On small apps, most of the above will be moot, and unnecessary. +that instead of creating permissions with: +```php +Permission::create([attributes]); +``` +it might be faster (more performant) to use: +```php +$permission = Permission::make([attributes]); +$permission->saveOrFail(); +``` As always, if you choose to bypass the provided object methods for adding/removing/syncing roles and permissions by manipulating Role and Permission objects directly in the database, -you will need to manually reset the cache with the PermissionRegistrar's method for that, +**you will need to manually reset the package cache** with the PermissionRegistrar's method for that, as described in the Cache section of the docs. From fa4beb13a9ba73b3f9c1bc844891631ec8216613 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 7 Apr 2023 21:11:53 -0400 Subject: [PATCH 289/648] [Docs] formatting --- docs/advanced-usage/timestamps.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/advanced-usage/timestamps.md b/docs/advanced-usage/timestamps.md index 7ab577ba4..ca0b709fc 100644 --- a/docs/advanced-usage/timestamps.md +++ b/docs/advanced-usage/timestamps.md @@ -3,7 +3,7 @@ title: Timestamps weight: 10 --- -### Excluding Timestamps from JSON +## Excluding Timestamps from JSON If you want to exclude timestamps from JSON output of role/permission pivots, you can extend the Role and Permission models into your own App namespace and mark the pivot as hidden: @@ -11,10 +11,9 @@ If you want to exclude timestamps from JSON output of role/permission pivots, yo protected $hidden = ['pivot']; ``` -### Adding Timestamps to Pivots +## Adding Timestamps to Pivots If you want to add timestamps to your pivot tables, you can do it with a few steps: - update the tables by calling `$table->timestamps();` in a migration - - extend the Permission and Role models and add `->withTimestamps();` to the BelongsToMany relationshps for `roles()` and `permissions()` - - update your User models (wherever you use the HasRoles or HasPermissions traits) by adding `->withTimestamps();` to the BelongsToMany relationshps for `roles()` and `permissions()` - + - extend the `Permission` and `Role` models and add `->withTimestamps();` to the `BelongsToMany` relationshps for `roles()` and `permissions()` + - update your `User` models (wherever you use the `HasRoles` or `HasPermissions` traits) by adding `->withTimestamps();` to the `BelongsToMany` relationships for `roles()` and `permissions()` From bc9fff8cb46b13782b07331bf2ab1d13652f1c58 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 7 Apr 2023 21:19:06 -0400 Subject: [PATCH 290/648] [Docs] formatting --- docs/advanced-usage/custom-permission-check.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/advanced-usage/custom-permission-check.md b/docs/advanced-usage/custom-permission-check.md index f6bfb84d8..3003adda0 100644 --- a/docs/advanced-usage/custom-permission-check.md +++ b/docs/advanced-usage/custom-permission-check.md @@ -3,13 +3,13 @@ title: Custom Permission Check weight: 6 --- -By default, a method is registered on [Laravel's gate](https://laravel.com/docs/authorization). This method is responsible for checking if the user has the required permission or not. Whether a user has a permission or not is determined by checking the user's permissions stored in the database. +By default, this package registers a `Gate::before()` method call on [Laravel's gate](https://laravel.com/docs/authorization). This method is responsible for checking if the user has the required permission or not, for calls to `can()` helpers and most `model policies`. Whether a user has a permission or not is determined by checking the user's permissions stored in the database. However, in some cases, you might want to implement custom logic for checking if the user has a permission or not. Let's say that your application uses access tokens for authentication and when issuing the tokens, you add a custom claim containing all the permissions the user has. In this case, if you want to check whether the user has the required permission or not based on the permissions in your custom claim in the access token, then you need to implement your own logic for handling this. -You could, for example, create a `before` method to handle this: +You could, for example, create a `Gate::before()` method call to handle this: **app/Providers/AuthServiceProvider.php** ```php @@ -22,8 +22,9 @@ public function boot() }); } ``` -Here `hasTokenPermission` is a custom method you need to implement yourself. +Here `hasTokenPermission` is a **custom method you need to implement yourself**. ### Register Permission Check Method -By default, `register_permission_check_method` is set to `true`. -Only set this to false if you want to implement custom logic for checking permissions. \ No newline at end of file +By default, `register_permission_check_method` is set to `true`, which means this package operates using the default behavior described earlier. + +Only set this to false if you want to bypass the default operation and implement your own custom logic for checking permissions. From 02a40fecaa97ce4bcd199a31b053718ab6ae0f75 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 11 Apr 2023 02:43:19 -0400 Subject: [PATCH 291/648] Add BackedEnum support Fixes #1991 Note: Requires PHP 8.1+ /cc @ceejayoz /cc @edalzell --- docs/basic-usage/enums.md | 103 +++++++++++++++++++ src/Models/Role.php | 2 +- src/Traits/HasPermissions.php | 56 +++++++--- src/Traits/HasRoles.php | 30 +++++- tests/GateTest.php | 23 +++++ tests/HasPermissionsTest.php | 48 +++++++++ tests/HasRolesTest.php | 42 ++++++++ tests/TestModels/TestRolePermissionsEnum.php | 56 ++++++++++ tests/WildcardHasPermissionsTest.php | 41 ++++++++ 9 files changed, 381 insertions(+), 20 deletions(-) create mode 100644 docs/basic-usage/enums.md create mode 100644 tests/TestModels/TestRolePermissionsEnum.php diff --git a/docs/basic-usage/enums.md b/docs/basic-usage/enums.md new file mode 100644 index 000000000..b343efbf0 --- /dev/null +++ b/docs/basic-usage/enums.md @@ -0,0 +1,103 @@ +--- +title: Enums +weight: 3 +--- + +# Enum Prerequisites + +Must be using PHP 8.1 or higher. + +If you are using PHP 8.1+ you can implement Enums as native types. + +Internally, Enums implicitly implement `\BackedEnum`, which is how this package recognizes that you're passing an Enum. + + +# Code Requirements + +You can create your Enum object for use with Roles and/or Permissions. You will probably create separate Enums for Roles and for Permissions, although if your application needs are simple you might choose a single Enum for both. + +Usually the list of application Roles is much shorter than the list of Permissions, so having separate objects for them can make them easier to manage. + +Here is an example Enum for Roles. You would do similarly for Permissions. + +```php +namespace App\Enums; + +enum RolesEnum: string +{ + // case NAMEINAPP = 'name-in-database'; + + case WRITER = 'writer'; + case EDITOR = 'editor'; + case USERMANAGER = 'user-manager'; + + // extra helper to allow for greater customization of displayed values, without disclosing the name/value data directly + public function label(): string + { + return match ($this) { + static::WRITER => 'Writers', + static::EDITOR => 'Editors', + static::USERMANAGER => 'User Managers', + }; + } +} +``` + +# Using Enum names and values + +## Creating Roles/Permissions + +When creating roles/permissions, you cannot pass a Enum name directly, because Eloquent expects a string for the name. + +You must manually convert the name to its value in order to pass the correct string to Eloquent for the role/permission name. + +eg: use `RolesEnum::WRITER->value` when specifying the role/permission name + +```php + $role = app(Role::class)->findOrCreate(RolesEnum::WRITER->value, 'web'); +``` +Same with creating Permissions. + +## Authorizing using Enums + +In your application code, when checking for authorization using features of this package, you can use `MyEnum::NAME` directly in most cases, without passing `->value` to convert to a string. + +There will be times where you will need to manually fallback to adding `->value` (eg: `MyEnum::NAME->value`) when using features that aren't aware of Enum support. This will occur when you need to pass `string` values instead of an `Enum`, such as when interacting with Laravel's Gate via the `can()` methods/helpers (eg: `can`, `canAny`, etc). + +Examples: +```php +// the following are identical because `hasPermissionTo` is aware of `BackedEnum` support: +$user->hasPermissionTo(PermissionsEnum::VIEWPOSTS); +$user->hasPermissionTo(PermissionsEnum::VIEWPOSTS->value); + +// when calling Gate features, such as Model Policies, etc +$user->can(PermissionsEnum::VIEWPOSTS->value); +$model->can(PermissionsEnum::VIEWPOSTS->value); + +// Blade directives: +@can(PermissionsEnum::VIEWPOSTS->value) +``` + + +# Package methods supporting BackedEnums: +The following methods of this package support passing `BackedEnum` parameters directly: + +```php + $user->assignRole(RolesEnum::WRITER); + $user->removeRole(RolesEnum::EDITOR); + + $role->givePermissionTo(PermissionsEnum::EDITPOSTS); + $role->revokePermissionTo(PermissionsEnum::EDITPOSTS); + + $user->givePermissionTo(PermissionsEnum::EDITPOSTS); + $user->revokePermissionTo(PermissionsEnum::EDITPOSTS); + + $user->hasPermissionTo(PermissionsEnum::EDITPOSTS); + $user->hasAnyPermission([PermissionsEnum::EDITPOSTS, PermissionsEnum::VIEWPOSTS]); + $user->hasDirectPermission(PermissionsEnum::EDITPOSTS); + + $user->hasRole(RolesEnum::WRITER); + $user->hasAllRoles([RolesEnum::WRITER, RolesEnum::EDITOR]); + $user->hasExactRoles([RolesEnum::WRITER, RolesEnum::EDITOR, RolesEnum::MANAGER]); + +``` diff --git a/src/Models/Role.php b/src/Models/Role.php index f948b0aa3..1b68e99d9 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -167,7 +167,7 @@ protected static function findByParam(array $params = []) /** * Determine if the role may perform the given permission. * - * @param string|int|Permission $permission + * @param string|int|Permission|\BackedEnum $permission * @param string|null $guardName * * @throws PermissionDoesNotExist|GuardDoesNotMatch diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index 160be5f93..ac561a4b6 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -95,7 +95,7 @@ public function permissions(): BelongsToMany /** * Scope the model query to certain permissions only. * - * @param string|int|array|Permission|Collection $permissions + * @param string|int|array|Permission|Collection|\BackedEnum $permissions */ public function scopePermission(Builder $query, $permissions): Builder { @@ -122,7 +122,7 @@ public function scopePermission(Builder $query, $permissions): Builder } /** - * @param string|int|array|Permission|Collection $permissions + * @param string|int|array|Permission|Collection|\BackedEnum $permissions * * @throws PermissionDoesNotExist */ @@ -136,6 +136,11 @@ protected function convertToPermissionModels($permissions): array if ($permission instanceof Permission) { return $permission; } + + if ($permission instanceof \BackedEnum) { + $permission = $permission->value; + } + $method = is_string($permission) && ! PermissionRegistrar::isUid($permission) ? 'findByName' : 'findById'; return $this->getPermissionClass()::{$method}($permission, $this->getDefaultGuardName()); @@ -145,7 +150,7 @@ protected function convertToPermissionModels($permissions): array /** * Find a permission. * - * @param string|int|Permission $permission + * @param string|int|Permission|\BackedEnum $permission * @return Permission * * @throws PermissionDoesNotExist @@ -159,6 +164,13 @@ public function filterPermission($permission, $guardName = null) ); } + if ($permission instanceof \BackedEnum) { + $permission = $this->getPermissionClass()::findByName( + $permission->value, + $guardName ?? $this->getDefaultGuardName() + ); + } + if (is_int($permission) || is_string($permission)) { $permission = $this->getPermissionClass()::findById( $permission, @@ -176,7 +188,7 @@ public function filterPermission($permission, $guardName = null) /** * Determine if the model may perform the given permission. * - * @param string|int|Permission $permission + * @param string|int|Permission|\BackedEnum $permission * @param string|null $guardName * * @throws PermissionDoesNotExist @@ -195,7 +207,7 @@ public function hasPermissionTo($permission, $guardName = null): bool /** * Validates a wildcard permission against all permissions of a user. * - * @param string|int|Permission $permission + * @param string|int|Permission|\BackedEnum $permission * @param string|null $guardName */ protected function hasWildcardPermission($permission, $guardName = null): bool @@ -210,6 +222,10 @@ protected function hasWildcardPermission($permission, $guardName = null): bool $permission = $permission->name; } + if ($permission instanceof \BackedEnum) { + $permission = $permission->value; + } + if (! is_string($permission)) { throw WildcardPermissionInvalidArgument::create(); } @@ -234,7 +250,7 @@ protected function hasWildcardPermission($permission, $guardName = null): bool /** * An alias to hasPermissionTo(), but avoids throwing an exception. * - * @param string|int|Permission $permission + * @param string|int|Permission|\BackedEnum $permission * @param string|null $guardName */ public function checkPermissionTo($permission, $guardName = null): bool @@ -249,7 +265,7 @@ public function checkPermissionTo($permission, $guardName = null): bool /** * Determine if the model has any of the given permissions. * - * @param string|int|array|Permission|Collection ...$permissions + * @param string|int|array|Permission|Collection|\BackedEnum ...$permissions */ public function hasAnyPermission(...$permissions): bool { @@ -267,7 +283,7 @@ public function hasAnyPermission(...$permissions): bool /** * Determine if the model has all of the given permissions. * - * @param string|int|array|Permission|Collection ...$permissions + * @param string|int|array|Permission|Collection|\BackedEnum ...$permissions */ public function hasAllPermissions(...$permissions): bool { @@ -297,7 +313,7 @@ protected function hasPermissionViaRole(Permission $permission): bool /** * Determine if the model has the given permission. * - * @param string|int|Permission $permission + * @param string|int|Permission|\BackedEnum $permission * * @throws PermissionDoesNotExist */ @@ -341,7 +357,7 @@ public function getAllPermissions(): Collection /** * Returns permissions ids as array keys * - * @param string|int|array|Permission|Collection $permissions + * @param string|int|array|Permission|Collection|\BackedEnum $permissions */ private function collectPermissions(...$permissions): array { @@ -369,7 +385,7 @@ private function collectPermissions(...$permissions): array /** * Grant the given permission(s) to a role. * - * @param string|int|array|Permission|Collection $permissions + * @param string|int|array|Permission|Collection|\BackedEnum $permissions * @return $this */ public function givePermissionTo(...$permissions) @@ -405,7 +421,7 @@ function ($object) use ($permissions, $model) { /** * Remove all current permissions and set the given ones. * - * @param string|int|array|Permission|Collection $permissions + * @param string|int|array|Permission|Collection|\BackedEnum $permissions * @return $this */ public function syncPermissions(...$permissions) @@ -420,7 +436,7 @@ public function syncPermissions(...$permissions) /** * Revoke the given permission(s). * - * @param Permission|Permission[]|string|string[] $permission + * @param Permission|Permission[]|string|string[]|\BackedEnum $permission * @return $this */ public function revokePermissionTo($permission) @@ -442,7 +458,7 @@ public function getPermissionNames(): Collection } /** - * @param string|int|array|Permission|Collection $permissions + * @param string|int|array|Permission|Collection|\BackedEnum $permissions * @return Permission|Permission[]|Collection */ protected function getStoredPermission($permissions) @@ -455,8 +471,16 @@ protected function getStoredPermission($permissions) return $this->getPermissionClass()::findByName($permissions, $this->getDefaultGuardName()); } + if ($permissions instanceof \BackedEnum) { + return $this->getPermissionClass()::findByName($permissions->value, $this->getDefaultGuardName()); + } + if (is_array($permissions)) { $permissions = array_map(function ($permission) { + if ($permission instanceof \BackedEnum) { + return $permission->value; + } + return is_a($permission, Permission::class) ? $permission->name : $permission; }, $permissions); @@ -501,7 +525,7 @@ public function forgetCachedPermissions() /** * Check if the model has All of the requested Direct permissions. * - * @param string|int|array|Permission|Collection ...$permissions + * @param string|int|array|Permission|Collection|\BackedEnum ...$permissions */ public function hasAllDirectPermissions(...$permissions): bool { @@ -519,7 +543,7 @@ public function hasAllDirectPermissions(...$permissions): bool /** * Check if the model has Any of the requested Direct permissions. * - * @param string|int|array|Permission|Collection ...$permissions + * @param string|int|array|Permission|Collection|\BackedEnum ...$permissions */ public function hasAnyDirectPermission(...$permissions): bool { diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index 0feca7e52..5d6c70c4d 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -196,7 +196,7 @@ public function syncRoles(...$roles) /** * Determine if the model has (one of) the given role(s). * - * @param string|int|array|Role|Collection $roles + * @param string|int|array|Role|Collection|\BackedEnum $roles */ public function hasRole($roles, string $guard = null): bool { @@ -212,6 +212,12 @@ public function hasRole($roles, string $guard = null): bool : $this->roles->contains('name', $roles); } + if ($roles instanceof \BackedEnum) { + return $guard + ? $this->roles->where('guard_name', $guard)->contains('name', $roles->value) + : $this->roles->contains('name', $roles->value); + } + if (is_int($roles) || is_string($roles)) { $roleClass = $this->getRoleClass(); $key = (new $roleClass())->getKeyName(); @@ -235,7 +241,11 @@ public function hasRole($roles, string $guard = null): bool return false; } - return $roles->intersect($guard ? $this->roles->where('guard_name', $guard) : $this->roles)->isNotEmpty(); + if ($roles instanceof Collection) { + return $roles->intersect($guard ? $this->roles->where('guard_name', $guard) : $this->roles)->isNotEmpty(); + } + + throw new \TypeError('Unsupported type for $roles parameter to hasRole().'); } /** @@ -253,7 +263,7 @@ public function hasAnyRole(...$roles): bool /** * Determine if the model has all of the given role(s). * - * @param string|array|Role|Collection $roles + * @param string|array|Role|Collection|\BackedEnum $roles */ public function hasAllRoles($roles, string $guard = null): bool { @@ -269,11 +279,21 @@ public function hasAllRoles($roles, string $guard = null): bool : $this->roles->contains('name', $roles); } + if ($roles instanceof \BackedEnum) { + return $guard + ? $this->roles->where('guard_name', $guard)->contains('name', $roles->value) + : $this->roles->contains('name', $roles->value); + } + if ($roles instanceof Role) { return $this->roles->contains($roles->getKeyName(), $roles->getKey()); } $roles = collect()->make($roles)->map(function ($role) { + if ($role instanceof \BackedEnum) { + return $role->value; + } + return $role instanceof Role ? $role->name : $role; }); @@ -337,6 +357,10 @@ protected function getStoredRole($role): Role return $this->getRoleClass()::findByName($role, $this->getDefaultGuardName()); } + if ($role instanceof \BackedEnum) { + return $this->getRoleClass()::findByName($role->value, $this->getDefaultGuardName()); + } + return $role; } diff --git a/tests/GateTest.php b/tests/GateTest.php index dafbd6ad9..93359834a 100644 --- a/tests/GateTest.php +++ b/tests/GateTest.php @@ -3,6 +3,7 @@ namespace Spatie\Permission\Tests; use Illuminate\Contracts\Auth\Access\Gate; +use Spatie\Permission\Contracts\Permission; class GateTest extends TestCase { @@ -36,6 +37,28 @@ public function it_can_determine_if_a_user_has_a_direct_permission() $this->assertFalse($this->testUser->can('admin-permission')); } + /** + * @test + * + * @requires PHP >= 8.1 + */ + public function it_can_determine_if_a_user_has_a_direct_permission_using_enums() + { + $enum = TestModels\TestRolePermissionsEnum::VIEWARTICLES; + + $permission = app(Permission::class)->findOrCreate($enum->value, 'web'); + + $this->assertFalse($this->testUser->can($enum->value)); + $this->assertFalse($this->testUser->canAny([$enum->value, 'some other permission'])); + + $this->testUser->givePermissionTo($enum); + + $this->assertTrue($this->testUser->hasPermissionTo($enum)); + + $this->assertTrue($this->testUser->can($enum->value)); + $this->assertTrue($this->testUser->canAny([$enum->value, 'some other permission'])); + } + /** @test */ public function it_can_determine_if_a_user_has_a_permission_through_roles() { diff --git a/tests/HasPermissionsTest.php b/tests/HasPermissionsTest.php index 30c4839e6..5871ed46c 100644 --- a/tests/HasPermissionsTest.php +++ b/tests/HasPermissionsTest.php @@ -52,6 +52,54 @@ public function it_can_revoke_a_permission_from_a_user() $this->assertFalse($this->testUser->hasPermissionTo($this->testUserPermission)); } + /** + * @test + * + * @requires PHP >= 8.1 + */ + public function it_can_assign_and_remove_a_permission_using_enums() + { + $enum = TestModels\TestRolePermissionsEnum::VIEWARTICLES; + + $permission = app(Permission::class)->findOrCreate($enum->value, 'web'); + + $this->testUser->givePermissionTo($enum); + + $this->assertTrue($this->testUser->hasPermissionTo($enum)); + $this->assertTrue($this->testUser->hasAnyPermission($enum)); + $this->assertTrue($this->testUser->hasDirectPermission($enum)); + + $this->testUser->revokePermissionTo($enum); + + $this->assertFalse($this->testUser->hasPermissionTo($enum)); + $this->assertFalse($this->testUser->hasAnyPermission($enum)); + $this->assertFalse($this->testUser->hasDirectPermission($enum)); + } + + /** + * @test + * + * @requires PHP >= 8.1 + */ + public function it_can_scope_users_using_enums() + { + $enum1 = TestModels\TestRolePermissionsEnum::VIEWARTICLES; + $enum2 = TestModels\TestRolePermissionsEnum::EDITARTICLES; + $permission1 = app(Permission::class)->findOrCreate($enum1->value, 'web'); + $permission2 = app(Permission::class)->findOrCreate($enum2->value, 'web'); + $user1 = User::create(['email' => 'user1@test.com']); + $user2 = User::create(['email' => 'user2@test.com']); + $user1->givePermissionTo([$enum1, $enum2]); + $this->testUserRole->givePermissionTo($enum2); + $user2->assignRole('testRole'); + + $scopedUsers1 = User::permission($enum2)->get(); + $scopedUsers2 = User::permission([$enum1])->get(); + + $this->assertEquals(2, $scopedUsers1->count()); + $this->assertEquals(1, $scopedUsers2->count()); + } + /** @test */ public function it_can_scope_users_using_a_string() { diff --git a/tests/HasRolesTest.php b/tests/HasRolesTest.php index e1cbd0fa1..2d0c63ecc 100644 --- a/tests/HasRolesTest.php +++ b/tests/HasRolesTest.php @@ -38,6 +38,38 @@ public function it_can_determine_that_the_user_does_not_have_a_role() $this->assertFalse($this->testUser->hasRole($role)); } + /** + * @test + * + * @requires PHP >= 8.1 + */ + public function it_can_assign_and_remove_a_role_using_enums() + { + $enum1 = TestModels\TestRolePermissionsEnum::USERMANAGER; + $enum2 = TestModels\TestRolePermissionsEnum::WRITER; + + app(Role::class)->findOrCreate($enum1->value, 'web'); + app(Role::class)->findOrCreate($enum2->value, 'web'); + + $this->assertFalse($this->testUser->hasRole($enum1)); + $this->assertFalse($this->testUser->hasRole($enum2)); + + $this->testUser->assignRole($enum1); + $this->testUser->assignRole($enum2); + + $this->assertTrue($this->testUser->hasRole($enum1)); + $this->assertTrue($this->testUser->hasRole($enum2)); + + $this->assertTrue($this->testUser->hasAllRoles([$enum1, $enum2])); + $this->assertFalse($this->testUser->hasAllRoles([$enum1, $enum2, 'not exist'])); + + $this->assertTrue($this->testUser->hasExactRoles([$enum2, $enum1])); + + $this->testUser->removeRole($enum1); + + $this->assertFalse($this->testUser->hasRole($enum1)); + } + /** @test */ public function it_can_assign_and_remove_a_role() { @@ -575,6 +607,16 @@ public function it_returns_false_instead_of_an_exception_when_checking_against_a $this->assertFalse($this->testUser->hasAnyRole('This Role Does Not Even Exist', $this->testAdminRole)); } + /** @test */ + public function it_throws_an_exception_if_an_unsupported_type_is_passed_to_hasRoles() + { + $this->expectException(\TypeError::class); + + $this->testUser->hasRole(new class + { + }); + } + /** @test */ public function it_can_retrieve_role_names() { diff --git a/tests/TestModels/TestRolePermissionsEnum.php b/tests/TestModels/TestRolePermissionsEnum.php new file mode 100644 index 000000000..d5c73ebc1 --- /dev/null +++ b/tests/TestModels/TestRolePermissionsEnum.php @@ -0,0 +1,56 @@ +value when specifying the role/permission name + * + * In your application code, when checking for authorization, you can use MyEnum::NAME in most cases. + * You can always manually fallback to MyEnum::NAME->value when using features that aren't aware of Enum support. + * + * TestRolePermissionsEnum::USERMANAGER->name = 'USERMANAGER' + * TestRolePermissionsEnum::USERMANAGER->value = 'User Manager' <-- This is the role-name checked by this package + * TestRolePermissionsEnum::USERMANAGER->label() = 'Manage Users' + */ +enum TestRolePermissionsEnum: string +{ + // case NAME = 'value'; + // case NAMEINAPP = 'name-in-database'; + + case WRITER = 'writer'; + case EDITOR = 'editor'; + case USERMANAGER = 'user-manager'; + case ADMIN = 'administrator'; + + case VIEWARTICLES = 'view articles'; + case EDITARTICLES = 'edit articles'; + + case WildcardArticlesCreator = 'articles.edit,view,create'; + case WildcardNewsEverything = 'news.*'; + case WildcardPostsEverything = 'posts.*'; + + case WildcardPostsCreate = 'posts.create'; + case WildcardArticlesView = 'articles.view'; + case WildcardProjectsView = 'projects.view'; + + // extra helper to allow for greater customization of displayed values, without disclosing the name/value data directly + public function label(): string + { + return match ($this) { + static::WRITER => 'Writers', + static::EDITOR => 'Editors', + static::USERMANAGER => 'User Managers', + static::ADMIN => 'Admins', + + static::VIEWARTICLES => 'View Articles', + static::EDITARTICLES => 'Edit Articles', + }; + } +} diff --git a/tests/WildcardHasPermissionsTest.php b/tests/WildcardHasPermissionsTest.php index 4472bb706..873f35a96 100644 --- a/tests/WildcardHasPermissionsTest.php +++ b/tests/WildcardHasPermissionsTest.php @@ -31,6 +31,47 @@ public function it_can_check_wildcard_permission() $this->assertFalse($user1->hasPermissionTo('projects.view')); } + /** + * @test + * + * @requires PHP >= 8.1 + */ + public function it_can_assign_wildcard_permissions_using_enums() + { + app('config')->set('permission.enable_wildcard_permission', true); + + $user1 = User::create(['email' => 'user1@test.com']); + + $articlesCreator = TestModels\TestRolePermissionsEnum::WildcardArticlesCreator; + $newsEverything = TestModels\TestRolePermissionsEnum::WildcardNewsEverything; + $postsEverything = TestModels\TestRolePermissionsEnum::WildcardPostsEverything; + $postsCreate = TestModels\TestRolePermissionsEnum::WildcardPostsCreate; + + $permission1 = app(Permission::class)->findOrCreate($articlesCreator->value, 'web'); + $permission2 = app(Permission::class)->findOrCreate($newsEverything->value, 'web'); + $permission3 = app(Permission::class)->findOrCreate($postsEverything->value, 'web'); + + $user1->givePermissionTo([$permission1, $permission2, $permission3]); + + $this->assertTrue($user1->hasPermissionTo($postsCreate)); + $this->assertTrue($user1->hasPermissionTo($postsCreate->value.'.123')); + $this->assertTrue($user1->hasPermissionTo($postsEverything)); + + $this->assertTrue($user1->hasPermissionTo(TestModels\TestRolePermissionsEnum::WildcardArticlesView)); + $this->assertTrue($user1->hasAnyPermission(TestModels\TestRolePermissionsEnum::WildcardArticlesView)); + + $this->assertFalse($user1->hasPermissionTo(TestModels\TestRolePermissionsEnum::WildcardProjectsView)); + + $user1->revokePermissionTo([$permission1, $permission2, $permission3]); + + $this->assertFalse($user1->hasPermissionTo(TestModels\TestRolePermissionsEnum::WildcardPostsCreate)); + $this->assertFalse($user1->hasPermissionTo($postsCreate->value.'.123')); + $this->assertFalse($user1->hasPermissionTo(TestModels\TestRolePermissionsEnum::WildcardPostsEverything)); + + $this->assertFalse($user1->hasPermissionTo(TestModels\TestRolePermissionsEnum::WildcardArticlesView)); + $this->assertFalse($user1->hasAnyPermission(TestModels\TestRolePermissionsEnum::WildcardArticlesView)); + } + /** @test */ public function it_can_check_wildcard_permissions_via_roles() { From 017717cfb011d80cb9817bbd17bf832cd1110575 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Tue, 11 Apr 2023 16:16:35 -0500 Subject: [PATCH 292/648] BackedEnum tidying --- src/Traits/HasPermissions.php | 19 ++++++++----------- src/Traits/HasRoles.php | 28 ++++++++++++---------------- 2 files changed, 20 insertions(+), 27 deletions(-) diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index ac561a4b6..52d833d6a 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -157,16 +157,13 @@ protected function convertToPermissionModels($permissions): array */ public function filterPermission($permission, $guardName = null) { - if (is_string($permission) && ! PermissionRegistrar::isUid($permission)) { - $permission = $this->getPermissionClass()::findByName( - $permission, - $guardName ?? $this->getDefaultGuardName() - ); + if ($permission instanceof \BackedEnum) { + $permission = $permission->value; } - if ($permission instanceof \BackedEnum) { + if (is_string($permission) && ! PermissionRegistrar::isUid($permission)) { $permission = $this->getPermissionClass()::findByName( - $permission->value, + $permission, $guardName ?? $this->getDefaultGuardName() ); } @@ -463,6 +460,10 @@ public function getPermissionNames(): Collection */ protected function getStoredPermission($permissions) { + if ($permissions instanceof \BackedEnum) { + $permissions = $permissions->value; + } + if (is_numeric($permissions) || PermissionRegistrar::isUid($permissions)) { return $this->getPermissionClass()::findById($permissions, $this->getDefaultGuardName()); } @@ -471,10 +472,6 @@ protected function getStoredPermission($permissions) return $this->getPermissionClass()::findByName($permissions, $this->getDefaultGuardName()); } - if ($permissions instanceof \BackedEnum) { - return $this->getPermissionClass()::findByName($permissions->value, $this->getDefaultGuardName()); - } - if (is_array($permissions)) { $permissions = array_map(function ($permission) { if ($permission instanceof \BackedEnum) { diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index 5d6c70c4d..08c21a8c9 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -206,18 +206,16 @@ public function hasRole($roles, string $guard = null): bool $roles = $this->convertPipeToArray($roles); } + if ($roles instanceof \BackedEnum) { + $roles = $roles->value; + } + if (is_string($roles) && ! PermissionRegistrar::isUid($roles)) { return $guard ? $this->roles->where('guard_name', $guard)->contains('name', $roles) : $this->roles->contains('name', $roles); } - if ($roles instanceof \BackedEnum) { - return $guard - ? $this->roles->where('guard_name', $guard)->contains('name', $roles->value) - : $this->roles->contains('name', $roles->value); - } - if (is_int($roles) || is_string($roles)) { $roleClass = $this->getRoleClass(); $key = (new $roleClass())->getKeyName(); @@ -269,6 +267,10 @@ public function hasAllRoles($roles, string $guard = null): bool { $this->loadMissing('roles'); + if ($roles instanceof \BackedEnum) { + $roles = $roles->value; + } + if (is_string($roles) && false !== strpos($roles, '|')) { $roles = $this->convertPipeToArray($roles); } @@ -279,12 +281,6 @@ public function hasAllRoles($roles, string $guard = null): bool : $this->roles->contains('name', $roles); } - if ($roles instanceof \BackedEnum) { - return $guard - ? $this->roles->where('guard_name', $guard)->contains('name', $roles->value) - : $this->roles->contains('name', $roles->value); - } - if ($roles instanceof Role) { return $this->roles->contains($roles->getKeyName(), $roles->getKey()); } @@ -349,6 +345,10 @@ public function getRoleNames(): Collection protected function getStoredRole($role): Role { + if ($role instanceof \BackedEnum) { + $role = $role->value; + } + if (is_numeric($role) || PermissionRegistrar::isUid($role)) { return $this->getRoleClass()::findById($role, $this->getDefaultGuardName()); } @@ -357,10 +357,6 @@ protected function getStoredRole($role): Role return $this->getRoleClass()::findByName($role, $this->getDefaultGuardName()); } - if ($role instanceof \BackedEnum) { - return $this->getRoleClass()::findByName($role->value, $this->getDefaultGuardName()); - } - return $role; } From e0b009782f9c5becd99ec7dae17b3f91c9236b45 Mon Sep 17 00:00:00 2001 From: Ebuka Ifezue Date: Wed, 12 Apr 2023 08:13:15 +0000 Subject: [PATCH 293/648] Fix wrong model reference --- docs/advanced-usage/extending.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/advanced-usage/extending.md b/docs/advanced-usage/extending.md index 5fd067925..e177ba38b 100644 --- a/docs/advanced-usage/extending.md +++ b/docs/advanced-usage/extending.md @@ -6,7 +6,7 @@ weight: 4 ## Extending User Models Laravel's authorization features are available in models which implement the `Illuminate\Foundation\Auth\Access\Authorizable` trait. -By default Laravel does this in `\App\User` by extending `Illuminate\Foundation\Auth\User`, in which the trait and `Illuminate\Contracts\Auth\Access\Authorizable` contract are declared. +By default Laravel does this in `\App\Models\User` by extending `Illuminate\Foundation\Auth\User`, in which the trait and `Illuminate\Contracts\Auth\Access\Authorizable` contract are declared. If you are creating your own User models and wish Authorization features to be available, you need to implement `Illuminate\Contracts\Auth\Access\Authorizable` in one of those ways as well. From 6e0916dcbecf18e2e8372a8193695032a5dcfe2c Mon Sep 17 00:00:00 2001 From: erikn69 Date: Wed, 12 Apr 2023 08:43:51 -0500 Subject: [PATCH 294/648] Fix permission:show roles with underscores --- src/Commands/Show.php | 3 ++- tests/CommandTest.php | 26 +++++++++++++++----------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/Commands/Show.php b/src/Commands/Show.php index c80d12c1b..af243ba4b 100644 --- a/src/Commands/Show.php +++ b/src/Commands/Show.php @@ -62,8 +62,9 @@ public function handle() isset($teams) ? $teams->prepend(new TableCell(''))->toArray() : [], $roles->keys()->map(function ($val) { $name = explode('_', $val); + array_pop($name); - return $name[0]; + return implode('_', $name); }) ->prepend('')->toArray(), ), diff --git a/tests/CommandTest.php b/tests/CommandTest.php index 1ded858fe..923cad4e0 100644 --- a/tests/CommandTest.php +++ b/tests/CommandTest.php @@ -87,6 +87,9 @@ public function it_can_create_a_permission_without_duplication() /** @test */ public function it_can_show_permission_tables() { + Role::where('name', 'testRole2')->delete(); + Role::create(['name' => 'testRole_2']); + Artisan::call('permission:show'); $output = Artisan::output(); @@ -94,14 +97,13 @@ public function it_can_show_permission_tables() $this->assertTrue(strpos($output, 'Guard: web') !== false); $this->assertTrue(strpos($output, 'Guard: admin') !== false); + // | | testRole | testRole_2 | + // | edit-articles | · | · | if (method_exists($this, 'assertMatchesRegularExpression')) { - // | | testRole | testRole2 | - $this->assertMatchesRegularExpression('/\|\s+\|\s+testRole\s+\|\s+testRole2\s+\|/', $output); - - // | edit-articles | · | · | + $this->assertMatchesRegularExpression('/\|\s+\|\s+testRole\s+\|\s+testRole_2\s+\|/', $output); $this->assertMatchesRegularExpression('/\|\s+edit-articles\s+\|\s+·\s+\|\s+·\s+\|/', $output); } else { // phpUnit 9/8 - $this->assertRegExp('/\|\s+\|\s+testRole\s+\|\s+testRole2\s+\|/', $output); + $this->assertRegExp('/\|\s+\|\s+testRole\s+\|\s+testRole_2\s+\|/', $output); $this->assertRegExp('/\|\s+edit-articles\s+\|\s+·\s+\|\s+·\s+\|/', $output); } @@ -164,20 +166,22 @@ public function it_can_show_roles_by_teams() config()->set('permission.teams', true); app(\Spatie\Permission\PermissionRegistrar::class)->initializeCache(); - Role::create(['name' => 'testRoleTeam', 'team_test_id' => 1]); - Role::create(['name' => 'testRoleTeam', 'team_test_id' => 2]); // same name different team + Role::where('name', 'testRole2')->delete(); + Role::create(['name' => 'testRole_2']); + Role::create(['name' => 'testRole_Team', 'team_test_id' => 1]); + Role::create(['name' => 'testRole_Team', 'team_test_id' => 2]); // same name different team Artisan::call('permission:show'); $output = Artisan::output(); - // | | Team ID: NULL | Team ID: 1 | Team ID: 2 | - // | | testRole | testRole2 | testRoleTeam | testRoleTeam | + // | | Team ID: NULL | Team ID: 1 | Team ID: 2 | + // | | testRole | testRole_2 | testRole_Team | testRole_Team | if (method_exists($this, 'assertMatchesRegularExpression')) { $this->assertMatchesRegularExpression('/\|\s+\|\s+Team ID: NULL\s+\|\s+Team ID: 1\s+\|\s+Team ID: 2\s+\|/', $output); - $this->assertMatchesRegularExpression('/\|\s+\|\s+testRole\s+\|\s+testRole2\s+\|\s+testRoleTeam\s+\|\s+testRoleTeam\s+\|/', $output); + $this->assertMatchesRegularExpression('/\|\s+\|\s+testRole\s+\|\s+testRole_2\s+\|\s+testRole_Team\s+\|\s+testRole_Team\s+\|/', $output); } else { // phpUnit 9/8 $this->assertRegExp('/\|\s+\|\s+Team ID: NULL\s+\|\s+Team ID: 1\s+\|\s+Team ID: 2\s+\|/', $output); - $this->assertRegExp('/\|\s+\|\s+testRole\s+\|\s+testRole2\s+\|\s+testRoleTeam\s+\|\s+testRoleTeam\s+\|/', $output); + $this->assertRegExp('/\|\s+\|\s+testRole\s+\|\s+testRole_2\s+\|\s+testRole_Team\s+\|\s+testRole_Team\s+\|/', $output); } } } From 55c87477b57581b8b2b38ecbd9402e079e39f372 Mon Sep 17 00:00:00 2001 From: drbyte Date: Wed, 12 Apr 2023 19:10:38 +0000 Subject: [PATCH 295/648] Update CHANGELOG --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec4980c00..d07abe830 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to `laravel-permission` will be documented in this file +## 5.10.1 - 2023-04-12 + +### What's Changed + +- [V5] Fix artisan command `permission:show` output of roles with underscores by @erikn69 in https://github.com/spatie/laravel-permission/pull/2396 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/5.10.0...5.10.1 + ## 5.10.0 - 2023-03-22 ### What's Changed @@ -600,6 +608,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` 1. Also this is a good time to point out that now with v2.25.0 and v2.26.0 most permission-cache-reset scenarios may no longer be needed in your app, so it's worth reviewing those cases, as you may gain some app speed improvement by removing unnecessary cache resets. @@ -654,6 +663,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` ## 2.19.1 - 2018-09-14 From 6dd55adb2284d9935ec1fd6c43ca3a6b1e65ead2 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Wed, 12 Apr 2023 14:04:04 -0500 Subject: [PATCH 296/648] Phpstan fixes --- src/Models/Permission.php | 3 +-- src/PermissionRegistrar.php | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Models/Permission.php b/src/Models/Permission.php index 631597fac..f5edcb90f 100644 --- a/src/Models/Permission.php +++ b/src/Models/Permission.php @@ -141,11 +141,10 @@ protected static function getPermissions(array $params = [], bool $onlyOne = fal /** * Get the current cached first permission. - * - * @return PermissionContract */ protected static function getPermission(array $params = []): ?PermissionContract { + /** @var PermissionContract|null */ return static::getPermissions($params, true)->first(); } } diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index 2032be779..694865126 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -25,7 +25,7 @@ class PermissionRegistrar /** @var string */ protected $roleClass; - /** @var Collection|null */ + /** @var Collection|array|null */ protected $permissions; /** @var string */ From 7924d99d5e62b17f62acbcb4fd643caaf327e67b Mon Sep 17 00:00:00 2001 From: angeljqv <79208641+angeljqv@users.noreply.github.com> Date: Wed, 5 Apr 2023 15:46:35 -0500 Subject: [PATCH 297/648] Drop PHP 7.3 support --- .github/workflows/run-tests-L8.yml | 6 +----- composer.json | 5 ++--- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/.github/workflows/run-tests-L8.yml b/.github/workflows/run-tests-L8.yml index a8e9ce842..dd9d0c446 100644 --- a/.github/workflows/run-tests-L8.yml +++ b/.github/workflows/run-tests-L8.yml @@ -9,7 +9,7 @@ jobs: strategy: fail-fast: false matrix: - php: [8.2, 8.1, 8.0, 7.4, 7.3] + php: [8.2, 8.1, 8.0, 7.4] laravel: [10.*, 9.*, 8.*] dependency-version: [prefer-lowest, prefer-stable] include: @@ -24,12 +24,8 @@ jobs: php: 8.0 - laravel: 10.* php: 7.4 - - laravel: 10.* - php: 7.3 - laravel: 9.* php: 7.4 - - laravel: 9.* - php: 7.3 name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }} diff --git a/composer.json b/composer.json index 8a01fd579..9ea458849 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ ], "homepage": "https://github.com/spatie/laravel-permission", "require": { - "php": "^7.3|^8.0", + "php": "^7.4|^8.0", "illuminate/auth": "^8.0|^9.0|^10.0", "illuminate/container": "^8.0|^9.0|^10.0", "illuminate/contracts": "^8.0|^9.0|^10.0", @@ -30,8 +30,7 @@ }, "require-dev": { "orchestra/testbench": "^6.0|^7.0|^8.0", - "phpunit/phpunit": "^9.4", - "predis/predis": "^1.1" + "phpunit/phpunit": "^9.4" }, "minimum-stability": "dev", "prefer-stable": true, From 7a27aebcefc3bbcbb6febfd63cc5839c02f9ba36 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Wed, 12 Apr 2023 18:06:02 -0400 Subject: [PATCH 298/648] Add code for testing alternate cache stores --- tests/TestCase.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/TestCase.php b/tests/TestCase.php index a708ac196..b45718f0c 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -111,6 +111,11 @@ protected function getEnvironmentSetUp($app) $app['config']->set('auth.providers.users.model', User::class); $app['config']->set('cache.prefix', 'spatie_tests---'); + + // FOR MANUAL TESTING OF ALTERNATE CACHE STORES: + // $app['config']->set('cache.default', 'array'); + //Laravel supports: array, database, file + //requires extensions: apc, memcached, redis, dynamodb, octane } /** From 5e66ed149bfbb30bb9f7d9741e271c4a28db6946 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Wed, 12 Apr 2023 18:36:39 -0400 Subject: [PATCH 299/648] [Docs] Tidy display order --- docs/basic-usage/artisan.md | 2 +- docs/basic-usage/blade-directives.md | 2 +- docs/basic-usage/enums.md | 4 ++-- docs/basic-usage/middleware.md | 2 +- docs/basic-usage/multiple-guards.md | 2 +- docs/basic-usage/super-admin.md | 2 +- docs/basic-usage/teams-permissions.md | 2 +- docs/basic-usage/wildcard-permissions.md | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/basic-usage/artisan.md b/docs/basic-usage/artisan.md index 8cf340d1b..2091560de 100644 --- a/docs/basic-usage/artisan.md +++ b/docs/basic-usage/artisan.md @@ -1,6 +1,6 @@ --- title: Using artisan commands -weight: 7 +weight: 10 --- ## Creating roles and permissions with Artisan Commands diff --git a/docs/basic-usage/blade-directives.md b/docs/basic-usage/blade-directives.md index a3a4b8af1..e2c9f396a 100644 --- a/docs/basic-usage/blade-directives.md +++ b/docs/basic-usage/blade-directives.md @@ -1,6 +1,6 @@ --- title: Blade directives -weight: 4 +weight: 7 --- ## Permissions diff --git a/docs/basic-usage/enums.md b/docs/basic-usage/enums.md index b343efbf0..3f3b4ec99 100644 --- a/docs/basic-usage/enums.md +++ b/docs/basic-usage/enums.md @@ -1,11 +1,11 @@ --- title: Enums -weight: 3 +weight: 4 --- # Enum Prerequisites -Must be using PHP 8.1 or higher. +Requires PHP 8.1 or higher. If you are using PHP 8.1+ you can implement Enums as native types. diff --git a/docs/basic-usage/middleware.md b/docs/basic-usage/middleware.md index 976ae816b..6d2564728 100644 --- a/docs/basic-usage/middleware.md +++ b/docs/basic-usage/middleware.md @@ -1,6 +1,6 @@ --- title: Using a middleware -weight: 7 +weight: 11 --- ## Default Middleware diff --git a/docs/basic-usage/multiple-guards.md b/docs/basic-usage/multiple-guards.md index 27f453cb1..ef2848c9b 100644 --- a/docs/basic-usage/multiple-guards.md +++ b/docs/basic-usage/multiple-guards.md @@ -1,6 +1,6 @@ --- title: Using multiple guards -weight: 6 +weight: 9 --- When using the default Laravel auth configuration all of the core methods of this package will work out of the box, no extra configuration required. diff --git a/docs/basic-usage/super-admin.md b/docs/basic-usage/super-admin.md index 426bbc036..344a9a41e 100644 --- a/docs/basic-usage/super-admin.md +++ b/docs/basic-usage/super-admin.md @@ -1,6 +1,6 @@ --- title: Defining a Super-Admin -weight: 5 +weight: 8 --- We strongly recommend that a Super-Admin be handled by setting a global `Gate::before` or `Gate::after` rule which checks for the desired role. diff --git a/docs/basic-usage/teams-permissions.md b/docs/basic-usage/teams-permissions.md index d62fa92c8..3b2c49528 100644 --- a/docs/basic-usage/teams-permissions.md +++ b/docs/basic-usage/teams-permissions.md @@ -1,6 +1,6 @@ --- title: Teams permissions -weight: 3 +weight: 5 --- When enabled, teams permissions offers you flexible control for a variety of scenarios. The idea behind teams permissions is inspired by the default permission implementation of [Laratrust](https://laratrust.santigarcor.me/). diff --git a/docs/basic-usage/wildcard-permissions.md b/docs/basic-usage/wildcard-permissions.md index 31662e844..f13a58fa8 100644 --- a/docs/basic-usage/wildcard-permissions.md +++ b/docs/basic-usage/wildcard-permissions.md @@ -1,6 +1,6 @@ --- title: Wildcard permissions -weight: 3 +weight: 6 --- Wildcard permissions can be enabled in the permission config file: From 02ad48771f0722bbbff32aa7b5ec382f5b6dccd5 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Wed, 12 Apr 2023 17:04:04 -0500 Subject: [PATCH 300/648] Php 7.4 arrow function sintax --- database/migrations/add_teams_fields.php.stub | 4 - .../create_permission_tables.php.stub | 4 - src/Commands/Show.php | 26 +++--- src/Commands/UpgradeForTeams.php | 4 +- src/Guard.php | 14 +-- src/Models/Role.php | 6 +- src/PermissionRegistrar.php | 31 +++---- src/PermissionServiceProvider.php | 92 ++++++------------- src/Traits/HasPermissions.php | 15 ++- src/Traits/HasRoles.php | 8 +- src/helpers.php | 10 +- 11 files changed, 76 insertions(+), 138 deletions(-) diff --git a/database/migrations/add_teams_fields.php.stub b/database/migrations/add_teams_fields.php.stub index 9fb25ac09..b8935dd09 100644 --- a/database/migrations/add_teams_fields.php.stub +++ b/database/migrations/add_teams_fields.php.stub @@ -9,8 +9,6 @@ return new class extends Migration { /** * Run the migrations. - * - * @return void */ public function up(): void { @@ -85,8 +83,6 @@ return new class extends Migration /** * Reverse the migrations. - * - * @return void */ public function down(): void { diff --git a/database/migrations/create_permission_tables.php.stub b/database/migrations/create_permission_tables.php.stub index 5a7301abe..b865d480c 100644 --- a/database/migrations/create_permission_tables.php.stub +++ b/database/migrations/create_permission_tables.php.stub @@ -8,8 +8,6 @@ return new class extends Migration { /** * Run the migrations. - * - * @return void */ public function up(): void { @@ -122,8 +120,6 @@ return new class extends Migration /** * Reverse the migrations. - * - * @return void */ public function down(): void { diff --git a/src/Commands/Show.php b/src/Commands/Show.php index af243ba4b..8af347df3 100644 --- a/src/Commands/Show.php +++ b/src/Commands/Show.php @@ -36,25 +36,23 @@ public function handle() $roles = $roleClass::whereGuardName($guard) ->with('permissions') - ->when(config('permission.teams'), function ($q) use ($team_key) { - $q->orderBy($team_key); - }) - ->orderBy('name')->get()->mapWithKeys(function ($role) use ($team_key) { - return [$role->name.'_'.($role->$team_key ?: '') => ['permissions' => $role->permissions->pluck('id'), $team_key => $role->$team_key]]; - }); + ->when(config('permission.teams'), fn ($q) => $q->orderBy($team_key)) + ->orderBy('name')->get()->mapWithKeys(fn ($role) => + [$role->name.'_'.($role->$team_key ?: '') => ['permissions' => $role->permissions->pluck('id'), $team_key => $role->$team_key]] + ); $permissions = $permissionClass::whereGuardName($guard)->orderBy('name')->pluck('name', 'id'); - $body = $permissions->map(function ($permission, $id) use ($roles) { - return $roles->map(function (array $role_data) use ($id) { - return $role_data['permissions']->contains($id) ? ' ✔' : ' ·'; - })->prepend($permission); - }); + $body = $permissions->map(fn ($permission, $id) => + $roles->map(fn (array $role_data) => + $role_data['permissions']->contains($id) ? ' ✔' : ' ·' + )->prepend($permission) + ); if (config('permission.teams')) { - $teams = $roles->groupBy($team_key)->values()->map(function ($group, $id) { - return new TableCell('Team ID: '.($id ?: 'NULL'), ['colspan' => $group->count()]); - }); + $teams = $roles->groupBy($team_key)->values()->map(fn ($group, $id) => + new TableCell('Team ID: '.($id ?: 'NULL'), ['colspan' => $group->count()]) + ); } $this->table( diff --git a/src/Commands/UpgradeForTeams.php b/src/Commands/UpgradeForTeams.php index f42218cc7..1e1626b08 100644 --- a/src/Commands/UpgradeForTeams.php +++ b/src/Commands/UpgradeForTeams.php @@ -103,9 +103,7 @@ protected function alreadyExistingMigrations() { $matchingFiles = glob($this->getMigrationPath('*')); - return array_map(function ($path) { - return basename($path); - }, $matchingFiles); + return array_map(fn ($path) => basename($path), $matchingFiles); } /** diff --git a/src/Guard.php b/src/Guard.php index 376ffb897..83ffd4a8c 100644 --- a/src/Guard.php +++ b/src/Guard.php @@ -48,16 +48,10 @@ public static function getNames($model): Collection protected static function getConfigAuthGuards(string $class): Collection { return collect(config('auth.guards')) - ->map(function ($guard) { - if (! isset($guard['provider'])) { - return null; - } - - return config("auth.providers.{$guard['provider']}.model"); - }) - ->filter(function ($model) use ($class) { - return $class === $model; - }) + ->map(fn ($guard) => + isset($guard['provider']) ? config("auth.providers.{$guard['provider']}.model") : null + ) + ->filter(fn ($model) => $class === $model) ->keys(); } diff --git a/src/Models/Role.php b/src/Models/Role.php index 1b68e99d9..b52919593 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -150,10 +150,10 @@ protected static function findByParam(array $params = []) if (app(PermissionRegistrar::class)->teams) { $teamsKey = app(PermissionRegistrar::class)->teamsKey; - $query->where(function ($q) use ($params, $teamsKey) { + $query->where(fn ($q) => $q->whereNull($teamsKey) - ->orWhere($teamsKey, $params[$teamsKey] ?? getPermissionsTeamId()); - }); + ->orWhere($teamsKey, $params[$teamsKey] ?? getPermissionsTeamId()) + ); unset($params[$teamsKey]); } diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index 694865126..184d92d71 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -70,7 +70,7 @@ public function __construct(CacheManager $cacheManager) $this->initializeCache(); } - public function initializeCache() + public function initializeCache(): void { $this->cacheExpirationTime = config('permission.cache.expiration_time') ?: \DateInterval::createFromDateString('24 hours'); @@ -109,7 +109,7 @@ protected function getCacheStoreFromConfig(): Repository * * @param int|string|\Illuminate\Database\Eloquent\Model $id */ - public function setPermissionsTeamId($id) + public function setPermissionsTeamId($id): void { if ($id instanceof \Illuminate\Database\Eloquent\Model) { $id = $id->getKey(); @@ -174,7 +174,7 @@ public function clearClassPermissions() * Load permissions from cache * This get cache and turns array into \Illuminate\Database\Eloquent\Collection */ - private function loadPermissions() + private function loadPermissions(): void { if ($this->permissions) { return; @@ -276,9 +276,8 @@ protected function getPermissionsWithRoles(): Collection private function aliasedArray($model): array { return collect(is_array($model) ? $model : $model->getAttributes())->except($this->except) - ->keyBy(function ($value, $key) { - return $this->alias[$key] ?? $key; - })->all(); + ->keyBy(fn ($value, $key) => $this->alias[$key] ?? $key) + ->all(); } /** @@ -301,7 +300,7 @@ private function aliasModelFields($newKeys = []): void /* * Make the cache smaller using an array with only required fields */ - private function getSerializedPermissionsForCache() + private function getSerializedPermissionsForCache(): array { $this->except = config('permission.cache.column_names_except', ['created_at', 'updated_at', 'deleted_at']); @@ -319,7 +318,7 @@ private function getSerializedPermissionsForCache() return ['alias' => array_flip($this->alias)] + compact('permissions', 'roles'); } - private function getSerializedRoleRelation($permission) + private function getSerializedRoleRelation($permission): array { if (! $permission->roles->count()) { return []; @@ -341,28 +340,28 @@ private function getSerializedRoleRelation($permission) ]; } - private function getHydratedPermissionCollection() + private function getHydratedPermissionCollection(): Collection { $permissionClass = $this->getPermissionClass(); $permissionInstance = new $permissionClass(); return Collection::make( - array_map(function ($item) use ($permissionInstance) { - return $permissionInstance + array_map(fn ($item) => + $permissionInstance ->newFromBuilder($this->aliasedArray(array_diff_key($item, ['r' => 0]))) - ->setRelation('roles', $this->getHydratedRoleCollection($item['r'] ?? [])); - }, $this->permissions['permissions']) + ->setRelation('roles', $this->getHydratedRoleCollection($item['r'] ?? [])) + , $this->permissions['permissions']) ); } - private function getHydratedRoleCollection(array $roles) + private function getHydratedRoleCollection(array $roles): Collection { return Collection::make(array_values( array_intersect_key($this->cachedRoles, array_flip($roles)) )); } - private function hydrateRolesCache() + private function hydrateRolesCache(): void { $roleClass = $this->getRoleClass(); $roleInstance = new $roleClass(); @@ -375,7 +374,7 @@ private function hydrateRolesCache() $this->permissions['roles'] = []; } - public static function isUid($value) + public static function isUid($value): bool { if (! is_string($value) || empty(trim($value))) { return false; diff --git a/src/PermissionServiceProvider.php b/src/PermissionServiceProvider.php index 4cab16979..ec1dfb01b 100644 --- a/src/PermissionServiceProvider.php +++ b/src/PermissionServiceProvider.php @@ -44,9 +44,9 @@ public function register() 'permission' ); - $this->callAfterResolving('blade.compiler', function (BladeCompiler $bladeCompiler) { - $this->registerBladeExtensions($bladeCompiler); - }); + $this->callAfterResolving('blade.compiler', fn (BladeCompiler $bladeCompiler) => + $this->registerBladeExtensions($bladeCompiler) + ); } protected function offerPublishing() @@ -82,16 +82,12 @@ protected function registerCommands() protected function registerModelBindings() { - $this->app->bind(PermissionContract::class, function ($app) { - $config = $app->config['permission.models']; - - return $app->make($config['permission']); - }); - $this->app->bind(RoleContract::class, function ($app) { - $config = $app->config['permission.models']; - - return $app->make($config['role']); - }); + $this->app->bind(PermissionContract::class, fn ($app) => + $app->make($app->config['permission.models.permission']) + ); + $this->app->bind(RoleContract::class, fn ($app) => + $app->make($app->config['permission.models.role']) + ); } public static function bladeMethodWrapper($method, $role, $guard = null) @@ -101,50 +97,26 @@ public static function bladeMethodWrapper($method, $role, $guard = null) protected function registerBladeExtensions($bladeCompiler) { - $bladeCompiler->directive('role', function ($arguments) { - return ""; - }); - $bladeCompiler->directive('elserole', function ($arguments) { - return ""; - }); - $bladeCompiler->directive('endrole', function () { - return ''; - }); + $bladeMethodWrapper = '\\Spatie\\Permission\\PermissionServiceProvider::bladeMethodWrapper'; - $bladeCompiler->directive('hasrole', function ($arguments) { - return ""; - }); - $bladeCompiler->directive('endhasrole', function () { - return ''; - }); + $bladeCompiler->directive('role', fn ($args) => ""); + $bladeCompiler->directive('elserole', fn ($args) => ""); + $bladeCompiler->directive('endrole', fn () => ''); - $bladeCompiler->directive('hasanyrole', function ($arguments) { - return ""; - }); - $bladeCompiler->directive('endhasanyrole', function () { - return ''; - }); + $bladeCompiler->directive('hasrole', fn ($args) => ""); + $bladeCompiler->directive('endhasrole', fn () => ''); - $bladeCompiler->directive('hasallroles', function ($arguments) { - return ""; - }); - $bladeCompiler->directive('endhasallroles', function () { - return ''; - }); + $bladeCompiler->directive('hasanyrole', fn ($args) => ""); + $bladeCompiler->directive('endhasanyrole', fn () => ''); - $bladeCompiler->directive('unlessrole', function ($arguments) { - return ""; - }); - $bladeCompiler->directive('endunlessrole', function () { - return ''; - }); + $bladeCompiler->directive('hasallroles', fn ($args) => ""); + $bladeCompiler->directive('endhasallroles', fn () => '' ); - $bladeCompiler->directive('hasexactroles', function ($arguments) { - return ""; - }); - $bladeCompiler->directive('endhasexactroles', function () { - return ''; - }); + $bladeCompiler->directive('unlessrole', fn ($args) => ""); + $bladeCompiler->directive('endunlessrole', fn () => ''); + + $bladeCompiler->directive('hasexactroles', fn ($args) => ""); + $bladeCompiler->directive('endhasexactroles', fn () => ''); } protected function registerMacroHelpers() @@ -154,21 +126,13 @@ protected function registerMacroHelpers() } Route::macro('role', function ($roles = []) { - $roles = implode('|', Arr::wrap($roles)); - /** @var Route $this */ - $this->middleware("role:$roles"); - - return $this; + return $this->middleware("role:" . implode('|', Arr::wrap($roles))); }); Route::macro('permission', function ($permissions = []) { - $permissions = implode('|', Arr::wrap($permissions)); - /** @var Route $this */ - $this->middleware("permission:$permissions"); - - return $this; + return $this->middleware("permission:" . implode('|', Arr::wrap($permissions))); }); } @@ -182,9 +146,7 @@ protected function getMigrationFileName(string $migrationFileName): string $filesystem = $this->app->make(Filesystem::class); return Collection::make([$this->app->databasePath().DIRECTORY_SEPARATOR.'migrations'.DIRECTORY_SEPARATOR]) - ->flatMap(function ($path) use ($filesystem, $migrationFileName) { - return $filesystem->glob($path.'*_'.$migrationFileName); - }) + ->flatMap(fn ($path) => $filesystem->glob($path.'*_'.$migrationFileName)) ->push($this->app->databasePath()."/migrations/{$timestamp}_{$migrationFileName}") ->first(); } diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index 52d833d6a..413b81386 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -22,7 +22,7 @@ trait HasPermissions /** @var string */ private $permissionClass; - /** @var string|false|null */ + /** @var string|null */ private $wildcardClass; public static function bootHasPermissions() @@ -59,7 +59,7 @@ protected function getWildcardClass() return $this->wildcardClass; } - $this->wildcardClass = false; + $this->wildcardClass = ''; if (config('permission.enable_wildcard_permission')) { $this->wildcardClass = config('permission.wildcard_permission', WildcardPermission::class); @@ -101,9 +101,9 @@ public function scopePermission(Builder $query, $permissions): Builder { $permissions = $this->convertToPermissionModels($permissions); - $rolesWithPermissions = is_a($this, Role::class) ? [] : array_unique(array_reduce($permissions, function ($result, $permission) { - return array_merge($result, $permission->roles->all()); - }, [])); + $rolesWithPermissions = is_a($this, Role::class) ? [] : array_unique(array_reduce($permissions, fn ($result, $permission) => + array_merge($result, $permission->roles->all()) + , [])); return $query->where(function (Builder $query) use ($permissions, $rolesWithPermissions) { $query->whereHas('permissions', function (Builder $subQuery) use ($permissions) { @@ -331,9 +331,8 @@ public function getPermissionsViaRoles(): Collection } return $this->loadMissing('roles', 'roles.permissions') - ->roles->flatMap(function ($role) { - return $role->permissions; - })->sort()->values(); + ->roles->flatMap(fn ($role) => $role->permissions) + ->sort()->values(); } /** diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index 08c21a8c9..932e6ca25 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -321,9 +321,9 @@ public function hasExactRoles($roles, string $guard = null): bool $roles = [$roles->name]; } - $roles = collect()->make($roles)->map(function ($role) { - return $role instanceof Role ? $role->name : $role; - }); + $roles = collect()->make($roles)->map(fn ($role) => + $role instanceof Role ? $role->name : $role + ); return $this->roles->count() == $roles->count() && $this->hasAllRoles($roles, $guard); } @@ -365,7 +365,7 @@ protected function convertPipeToArray(string $pipeString) $pipeString = trim($pipeString); if (strlen($pipeString) <= 2) { - return $pipeString; + return [str_replace('|', '', $pipeString)]; } $quoteCharacter = substr($pipeString, 0, 1); diff --git a/src/helpers.php b/src/helpers.php index b29354655..120384bed 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -7,13 +7,9 @@ function getModelForGuard(string $guard) { return collect(config('auth.guards')) - ->map(function ($guard) { - if (! isset($guard['provider'])) { - return; - } - - return config("auth.providers.{$guard['provider']}.model"); - })->get($guard); + ->map(fn ($guard) => + isset($guard['provider']) ? config("auth.providers.{$guard['provider']}.model") : null + )->get($guard); } } From e5fd4dda64369e9c8d4d2965c2ef7656db0e8b65 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Wed, 12 Apr 2023 21:10:32 -0400 Subject: [PATCH 301/648] [Docs] Update headings syntax to show in docs website index --- docs/advanced-usage/cache.md | 7 ++++--- docs/advanced-usage/custom-permission-check.md | 9 +++++---- docs/advanced-usage/exceptions.md | 2 +- docs/advanced-usage/extending.md | 2 +- docs/advanced-usage/other.md | 2 +- docs/advanced-usage/phpstorm.md | 2 +- docs/advanced-usage/uuid.md | 12 ++++++------ docs/basic-usage/blade-directives.md | 2 +- docs/basic-usage/enums.md | 12 +++++------- docs/basic-usage/multiple-guards.md | 8 ++++---- docs/basic-usage/role-permissions.md | 4 ++-- docs/basic-usage/wildcard-permissions.md | 16 ++++++++++------ docs/installation-laravel.md | 2 +- docs/installation-lumen.md | 8 +++++--- 14 files changed, 47 insertions(+), 41 deletions(-) diff --git a/docs/advanced-usage/cache.md b/docs/advanced-usage/cache.md index 5a87370b9..fc6b5e59a 100644 --- a/docs/advanced-usage/cache.md +++ b/docs/advanced-usage/cache.md @@ -5,7 +5,7 @@ weight: 5 Role and Permission data are cached to speed up performance. -### Automatic Cache Refresh Using Built-In Functions +## Automatic Cache Refresh Using Built-In Functions When you **use the built-in functions** for manipulating roles and permissions, the cache is automatically reset for you, and relations are automatically reloaded for the current model record: @@ -31,7 +31,7 @@ $user->removeRole('writer'); $user->syncRoles(params); ``` -### Manual cache reset +## Manual cache reset To manually reset the cache for this package, you can run the following in your app code: ```php app()->make(\Spatie\Permission\PermissionRegistrar::class)->forgetCachedPermissions(); @@ -43,6 +43,7 @@ php artisan permission:cache-reset ``` (This command is effectively an alias for `artisan cache:forget spatie.permission.cache` but respects the package config as well.) +## Cache Configuration Settings ### Cache Expiration Time @@ -71,7 +72,7 @@ You can configure the package to use any of the Cache Stores you've configured i In `config/permission.php` set `cache.store` to the name of any one of the `config/cache.php` stores you've defined. -#### Disabling Cache +## Disabling Cache Setting `'cache.store' => 'array'` in `config/permission.php` will effectively disable caching by this package between requests (it will only cache in-memory until the current request is completed processing, never persisting it). diff --git a/docs/advanced-usage/custom-permission-check.md b/docs/advanced-usage/custom-permission-check.md index 3003adda0..54e9b3035 100644 --- a/docs/advanced-usage/custom-permission-check.md +++ b/docs/advanced-usage/custom-permission-check.md @@ -3,8 +3,13 @@ title: Custom Permission Check weight: 6 --- +## Default Permission Check Functionality By default, this package registers a `Gate::before()` method call on [Laravel's gate](https://laravel.com/docs/authorization). This method is responsible for checking if the user has the required permission or not, for calls to `can()` helpers and most `model policies`. Whether a user has a permission or not is determined by checking the user's permissions stored in the database. +In the permission config file, `register_permission_check_method` is set to `true`, which means this package operates using the default behavior described above. Only set this to `false` if you want to bypass the default operation and implement your own custom logic for checking permissions, as described below. + +## Using Custom Permission Check Functionality + However, in some cases, you might want to implement custom logic for checking if the user has a permission or not. Let's say that your application uses access tokens for authentication and when issuing the tokens, you add a custom claim containing all the permissions the user has. In this case, if you want to check whether the user has the required permission or not based on the permissions in your custom claim in the access token, then you need to implement your own logic for handling this. @@ -24,7 +29,3 @@ public function boot() ``` Here `hasTokenPermission` is a **custom method you need to implement yourself**. -### Register Permission Check Method -By default, `register_permission_check_method` is set to `true`, which means this package operates using the default behavior described earlier. - -Only set this to false if you want to bypass the default operation and implement your own custom logic for checking permissions. diff --git a/docs/advanced-usage/exceptions.md b/docs/advanced-usage/exceptions.md index 6134ed59f..291f85feb 100644 --- a/docs/advanced-usage/exceptions.md +++ b/docs/advanced-usage/exceptions.md @@ -3,7 +3,7 @@ title: Exceptions weight: 3 --- -If you need to override exceptions thrown by this package, you can simply use normal [Laravel practices for handling exceptions](https://laravel.com/docs/8.x/errors#rendering-exceptions). +If you need to override exceptions thrown by this package, you can simply use normal [Laravel practices for handling exceptions](https://laravel.com/docs/10.x/errors#rendering-exceptions). An example is shown below for your convenience, but nothing here is specific to this package other than the name of the exception. diff --git a/docs/advanced-usage/extending.md b/docs/advanced-usage/extending.md index e177ba38b..ded3621ec 100644 --- a/docs/advanced-usage/extending.md +++ b/docs/advanced-usage/extending.md @@ -10,7 +10,7 @@ By default Laravel does this in `\App\Models\User` by extending `Illuminate\Foun If you are creating your own User models and wish Authorization features to be available, you need to implement `Illuminate\Contracts\Auth\Access\Authorizable` in one of those ways as well. -#### Child User Models +## Child User Models Due to the nature of polymorphism and Eloquent's hard-coded mapping of model names in the database, setting relationships for child models that inherit permissions of the parent can be difficult (even near impossible depending on app requirements, especially when attempting to do inverse mappings). However, one thing you might consider if you need the child model to never have its own permissions/roles but to only use its parent's permissions/roles, is to [override the `getMorphClass` method on the model](https://github.com/laravel/framework/issues/17830#issuecomment-345619085). diff --git a/docs/advanced-usage/other.md b/docs/advanced-usage/other.md index 5de498560..7fd4b2bcc 100644 --- a/docs/advanced-usage/other.md +++ b/docs/advanced-usage/other.md @@ -3,6 +3,6 @@ title: Other weight: 9 --- -Schema Diagram: +**Schema Diagram:** You can find a schema diagram at [https://drawsql.app/templates/laravel-permission](https://drawsql.app/templates/laravel-permission) diff --git a/docs/advanced-usage/phpstorm.md b/docs/advanced-usage/phpstorm.md index 23e1a7574..070e7973b 100644 --- a/docs/advanced-usage/phpstorm.md +++ b/docs/advanced-usage/phpstorm.md @@ -3,7 +3,7 @@ title: PhpStorm Interaction weight: 8 --- -# Extending PhpStorm +## Extending PhpStorm > **Note** > When using Laravel Idea plugin all directives are automatically added. diff --git a/docs/advanced-usage/uuid.md b/docs/advanced-usage/uuid.md index 299f83c64..517cf7eb5 100644 --- a/docs/advanced-usage/uuid.md +++ b/docs/advanced-usage/uuid.md @@ -10,7 +10,7 @@ If you're using UUIDs or GUIDs for your User models there are a few consideratio Since each UUID implementation approach is different, some of these may or may not benefit you. As always, your implementation may vary. -### Migrations +## Migrations You will probably want to update the `create_permission_tables.php` migration: If your User models are using `uuid` instead of `unsignedBigInteger` then you'll need to reflect the change in the migration provided by this package. Something like this would be typical, for both `model_has_permissions` and `model_has_roles` tables: @@ -61,7 +61,7 @@ OPTIONAL: If you also want the roles and permissions to use a UUID for their `id ``` -### Configuration (OPTIONAL) +## Configuration (OPTIONAL) You might want to change the pivot table field name from `model_id` to `model_uuid`, just for semantic purposes. For this, in the configuration file edit `column_names.model_morph_key`: @@ -79,7 +79,7 @@ For this, in the configuration file edit `column_names.model_morph_key`: ], - If you extend the models into your app, be sure to list those models in your configuration file. See the Extending section of the documentation and the Models section below. -### Models +## Models If you want all the role/permission objects to have a UUID instead of an integer, you will need to Extend the default Role and Permission models into your own namespace in order to set some specific properties. (See the Extending section of the docs, where it explains requirements of Extending, as well as the configuration settings you need to update.) - You likely want to set `protected $keyType = 'string';` so Laravel handles joins as strings and doesn't cast to integer. @@ -119,7 +119,7 @@ It is common to use a trait to handle the $keyType and $incrementing settings, a ``` -### User Models +## User Models > Troubleshooting tip: In the ***Prerequisites*** section of the docs we remind you that your User model must implement the `Illuminate\Contracts\Auth\Access\Authorizable` contract so that the Gate features are made available to the User object. In the default User model provided with Laravel, this is done by extending another model (aliased to `Authenticatable`), which extends the base Eloquent model. However, your app's UUID implementation may need to override that in order to set some of the properties mentioned in the Models section above. @@ -127,7 +127,7 @@ However, your app's UUID implementation may need to override that in order to se If you are running into difficulties, you may want to double-check whether your User model is doing UUIDs consistent with other parts of your app. -## REMINDER: +# REMINDER: > THIS IS NOT A FULL LESSON ON HOW TO IMPLEMENT UUIDs IN YOUR APP. @@ -135,7 +135,7 @@ Again, since each UUID implementation approach is different, some of these may o -### Packages +## Packages There are many packages offering UUID features for Eloquent models. You may want to explore whether these are of value to you in your study of implementing UUID in your applications: https://github.com/JamesHemery/laravel-uuid diff --git a/docs/basic-usage/blade-directives.md b/docs/basic-usage/blade-directives.md index e2c9f396a..51924866b 100644 --- a/docs/basic-usage/blade-directives.md +++ b/docs/basic-usage/blade-directives.md @@ -31,7 +31,7 @@ If you actually need to test for Roles, this package offers some Blade directive Optionally you can pass in the `guard` that the check will be performed on as a second argument. -#### Blade and Roles +## Blade and Roles Check for a specific role: ```php @role('writer') diff --git a/docs/basic-usage/enums.md b/docs/basic-usage/enums.md index 3f3b4ec99..dffce53f9 100644 --- a/docs/basic-usage/enums.md +++ b/docs/basic-usage/enums.md @@ -3,7 +3,7 @@ title: Enums weight: 4 --- -# Enum Prerequisites +## Enum Prerequisites Requires PHP 8.1 or higher. @@ -12,7 +12,7 @@ If you are using PHP 8.1+ you can implement Enums as native types. Internally, Enums implicitly implement `\BackedEnum`, which is how this package recognizes that you're passing an Enum. -# Code Requirements +## Code Requirements You can create your Enum object for use with Roles and/or Permissions. You will probably create separate Enums for Roles and for Permissions, although if your application needs are simple you might choose a single Enum for both. @@ -43,9 +43,7 @@ enum RolesEnum: string } ``` -# Using Enum names and values - -## Creating Roles/Permissions +## Creating Roles/Permissions using Enums When creating roles/permissions, you cannot pass a Enum name directly, because Eloquent expects a string for the name. @@ -58,7 +56,7 @@ eg: use `RolesEnum::WRITER->value` when specifying the role/permission name ``` Same with creating Permissions. -## Authorizing using Enums +### Authorizing using Enums In your application code, when checking for authorization using features of this package, you can use `MyEnum::NAME` directly in most cases, without passing `->value` to convert to a string. @@ -79,7 +77,7 @@ $model->can(PermissionsEnum::VIEWPOSTS->value); ``` -# Package methods supporting BackedEnums: +## Package methods supporting BackedEnums: The following methods of this package support passing `BackedEnum` parameters directly: ```php diff --git a/docs/basic-usage/multiple-guards.md b/docs/basic-usage/multiple-guards.md index ef2848c9b..08e44d1d2 100644 --- a/docs/basic-usage/multiple-guards.md +++ b/docs/basic-usage/multiple-guards.md @@ -7,14 +7,14 @@ When using the default Laravel auth configuration all of the core methods of thi However, when using multiple guards they will act like namespaces for your permissions and roles. Meaning every guard has its own set of permissions and roles that can be assigned to their user model. -### The Downside To Multiple Guards +## The Downside To Multiple Guards Note that this package requires you to register a permission name for each guard you want to authenticate with. So, "edit-article" would have to be created multiple times for each guard your app uses. An exception will be thrown if you try to authenticate against a non-existing permission+guard combination. Same for roles. > **Tip**: If your app uses only a single guard, but is not `web` (Laravel's default, which shows "first" in the auth config file) then change the order of your listed guards in your `config/auth.php` to list your primary guard as the default and as the first in the list of defined guards. While you're editing that file, best to remove any guards you don't use, too. -### Using permissions and roles with multiple guards +## Using permissions and roles with multiple guards When creating new permissions and roles, if no guard is specified, then the **first** defined guard in `auth.guards` config array will be used. @@ -42,12 +42,12 @@ $user->hasPermissionTo('publish articles', 'admin'); - then the `auth.defaults.guard` config (which is the user's guard if they are logged in, else the default in the file). -### Assigning permissions and roles to guard users +## Assigning permissions and roles to guard users You can use the same core methods to assign permissions and roles to users; just make sure the `guard_name` on the permission or role matches the guard of the user, otherwise a `GuardDoesNotMatch` or `Role/PermissionDoesNotExist` exception will be thrown. -### Using blade directives with multiple guards +## Using blade directives with multiple guards You can use all of the blade directives offered by this package by passing in the guard you wish to use as the second argument to the directive: diff --git a/docs/basic-usage/role-permissions.md b/docs/basic-usage/role-permissions.md index 476610957..ddfd0ce57 100644 --- a/docs/basic-usage/role-permissions.md +++ b/docs/basic-usage/role-permissions.md @@ -97,7 +97,7 @@ string or a `Spatie\Permission\Models\Permission` object. **NOTE: Permissions are inherited from roles automatically.** -### What Permissions Does A Role Have? +## What Permissions Does A Role Have? The `permissions` property on any given role returns a collection with all the related permission objects. This collection can respond to usual Eloquent Collection operations, such as count, sort, etc. @@ -174,6 +174,6 @@ the second will be a collection with the `edit article` permission and the third -### NOTE about using permission names in policies +## NOTE about using permission names in policies When calling `authorize()` for a policy method, if you have a permission named the same as one of those policy methods, your permission "name" will take precedence and not fire the policy. For this reason it may be wise to avoid naming your permissions the same as the methods in your policy. While you can define your own method names, you can read more about the defaults Laravel offers in Laravel's documentation at [Writing Policies](https://laravel.com/docs/authorization#writing-policies). diff --git a/docs/basic-usage/wildcard-permissions.md b/docs/basic-usage/wildcard-permissions.md index f13a58fa8..f1afc046d 100644 --- a/docs/basic-usage/wildcard-permissions.md +++ b/docs/basic-usage/wildcard-permissions.md @@ -3,6 +3,12 @@ title: Wildcard permissions weight: 6 --- +When enabled, wildcard permissions offers you a flexible representation for a variety of permission schemes. The idea + behind wildcard permissions is inspired by the default permission implementation of + [Apache Shiro](https://shiro.apache.org/permissions.html). + +## Enabling Wildcard Feature + Wildcard permissions can be enabled in the permission config file: ```php @@ -10,9 +16,7 @@ Wildcard permissions can be enabled in the permission config file: 'enable_wildcard_permission' => true, ``` -When enabled, wildcard permissions offers you a flexible representation for a variety of permission schemes. The idea - behind wildcard permissions is inspired by the default permission implementation of - [Apache Shiro](https://shiro.apache.org/permissions.html). +## Wildcard Syntax A wildcard permission string is made of one or more parts separated by dots (.). @@ -29,7 +33,7 @@ this is the common use-case, representing {resource}.{action}.{target}. > NOTE: You must create any wildcard permission patterns (eg: `posts.create.*`) before you can assign them or check for them. -### Using Wildcards +## Using Wildcards > ALERT: The `*` means "ALL". It does **not** mean "ANY". @@ -53,14 +57,14 @@ $user->can('posts.edit'); $user->can('posts.delete'); ``` -### Meaning of the `*` Asterisk +## Meaning of the `*` Asterisk The `*` means "ALL". It does **not** mean "ANY". Thus `can('post.*')` will only pass if the user has been assigned `post.*` explicitly. -### Subparts +## Subparts Besides the use of parts and wildcards, subparts can also be used. Subparts are divided with commas (,). This is a powerful feature that lets you create complex permission schemes. diff --git a/docs/installation-laravel.md b/docs/installation-laravel.md index 65e20c29c..e1e3d5792 100644 --- a/docs/installation-laravel.md +++ b/docs/installation-laravel.md @@ -69,7 +69,7 @@ Package Version | Laravel Version . -### Default config file contents +## Default config file contents You can view the default config file contents at: diff --git a/docs/installation-lumen.md b/docs/installation-lumen.md index dba274fd7..70268977d 100644 --- a/docs/installation-lumen.md +++ b/docs/installation-lumen.md @@ -7,6 +7,8 @@ NOTE: Lumen is **not** officially supported by this package. However, the follow Lumen installation instructions can be found in the [Lumen documentation](https://lumen.laravel.com/docs/main). +## Installing + Install the permissions package via Composer: ``` bash @@ -61,15 +63,15 @@ php artisan migrate ``` --- -### User Model +## User Model NOTE: Remember that Laravel's authorization layer requires that your `User` model implement the `Illuminate\Contracts\Auth\Access\Authorizable` contract. In Lumen you will then also need to use the `Laravel\Lumen\Auth\Authorizable` trait. --- -### User Table +## User Table NOTE: If you are working with a fresh install of Lumen, then you probably also need a migration file for your Users table. You can create your own, or you can copy a basic one from Laravel: [https://github.com/laravel/laravel/blob/master/database/migrations/2014_10_12_000000_create_users_table.php](https://github.com/laravel/laravel/blob/master/database/migrations/2014_10_12_000000_create_users_table.php) (You will need to run `php artisan migrate` after adding this file.) -Remember to update your UserFactory.php to match the fields in the migration you create/copy. +Remember to update your `UserFactory.php` to match the fields in the migration you create/copy. From 33b016a3fc652dc1abb0fbdd5cad03c6e92294f5 Mon Sep 17 00:00:00 2001 From: drbyte Date: Thu, 13 Apr 2023 01:38:40 +0000 Subject: [PATCH 302/648] Fix styling --- src/Commands/Show.php | 10 +++------- src/Guard.php | 3 +-- src/Models/Role.php | 3 +-- src/PermissionRegistrar.php | 6 ++---- src/PermissionServiceProvider.php | 15 ++++++--------- src/Traits/HasPermissions.php | 4 +--- src/Traits/HasRoles.php | 3 +-- src/helpers.php | 3 +-- 8 files changed, 16 insertions(+), 31 deletions(-) diff --git a/src/Commands/Show.php b/src/Commands/Show.php index 8af347df3..8b41f9985 100644 --- a/src/Commands/Show.php +++ b/src/Commands/Show.php @@ -37,21 +37,17 @@ public function handle() $roles = $roleClass::whereGuardName($guard) ->with('permissions') ->when(config('permission.teams'), fn ($q) => $q->orderBy($team_key)) - ->orderBy('name')->get()->mapWithKeys(fn ($role) => - [$role->name.'_'.($role->$team_key ?: '') => ['permissions' => $role->permissions->pluck('id'), $team_key => $role->$team_key]] + ->orderBy('name')->get()->mapWithKeys(fn ($role) => [$role->name.'_'.($role->$team_key ?: '') => ['permissions' => $role->permissions->pluck('id'), $team_key => $role->$team_key]] ); $permissions = $permissionClass::whereGuardName($guard)->orderBy('name')->pluck('name', 'id'); - $body = $permissions->map(fn ($permission, $id) => - $roles->map(fn (array $role_data) => - $role_data['permissions']->contains($id) ? ' ✔' : ' ·' + $body = $permissions->map(fn ($permission, $id) => $roles->map(fn (array $role_data) => $role_data['permissions']->contains($id) ? ' ✔' : ' ·' )->prepend($permission) ); if (config('permission.teams')) { - $teams = $roles->groupBy($team_key)->values()->map(fn ($group, $id) => - new TableCell('Team ID: '.($id ?: 'NULL'), ['colspan' => $group->count()]) + $teams = $roles->groupBy($team_key)->values()->map(fn ($group, $id) => new TableCell('Team ID: '.($id ?: 'NULL'), ['colspan' => $group->count()]) ); } diff --git a/src/Guard.php b/src/Guard.php index 83ffd4a8c..c481c0640 100644 --- a/src/Guard.php +++ b/src/Guard.php @@ -48,8 +48,7 @@ public static function getNames($model): Collection protected static function getConfigAuthGuards(string $class): Collection { return collect(config('auth.guards')) - ->map(fn ($guard) => - isset($guard['provider']) ? config("auth.providers.{$guard['provider']}.model") : null + ->map(fn ($guard) => isset($guard['provider']) ? config("auth.providers.{$guard['provider']}.model") : null ) ->filter(fn ($model) => $class === $model) ->keys(); diff --git a/src/Models/Role.php b/src/Models/Role.php index b52919593..472a93847 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -150,8 +150,7 @@ protected static function findByParam(array $params = []) if (app(PermissionRegistrar::class)->teams) { $teamsKey = app(PermissionRegistrar::class)->teamsKey; - $query->where(fn ($q) => - $q->whereNull($teamsKey) + $query->where(fn ($q) => $q->whereNull($teamsKey) ->orWhere($teamsKey, $params[$teamsKey] ?? getPermissionsTeamId()) ); unset($params[$teamsKey]); diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index 184d92d71..e95880154 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -346,11 +346,9 @@ private function getHydratedPermissionCollection(): Collection $permissionInstance = new $permissionClass(); return Collection::make( - array_map(fn ($item) => - $permissionInstance + array_map(fn ($item) => $permissionInstance ->newFromBuilder($this->aliasedArray(array_diff_key($item, ['r' => 0]))) - ->setRelation('roles', $this->getHydratedRoleCollection($item['r'] ?? [])) - , $this->permissions['permissions']) + ->setRelation('roles', $this->getHydratedRoleCollection($item['r'] ?? [])), $this->permissions['permissions']) ); } diff --git a/src/PermissionServiceProvider.php b/src/PermissionServiceProvider.php index ec1dfb01b..16e8f3287 100644 --- a/src/PermissionServiceProvider.php +++ b/src/PermissionServiceProvider.php @@ -44,8 +44,7 @@ public function register() 'permission' ); - $this->callAfterResolving('blade.compiler', fn (BladeCompiler $bladeCompiler) => - $this->registerBladeExtensions($bladeCompiler) + $this->callAfterResolving('blade.compiler', fn (BladeCompiler $bladeCompiler) => $this->registerBladeExtensions($bladeCompiler) ); } @@ -82,11 +81,9 @@ protected function registerCommands() protected function registerModelBindings() { - $this->app->bind(PermissionContract::class, fn ($app) => - $app->make($app->config['permission.models.permission']) + $this->app->bind(PermissionContract::class, fn ($app) => $app->make($app->config['permission.models.permission']) ); - $this->app->bind(RoleContract::class, fn ($app) => - $app->make($app->config['permission.models.role']) + $this->app->bind(RoleContract::class, fn ($app) => $app->make($app->config['permission.models.role']) ); } @@ -110,7 +107,7 @@ protected function registerBladeExtensions($bladeCompiler) $bladeCompiler->directive('endhasanyrole', fn () => ''); $bladeCompiler->directive('hasallroles', fn ($args) => ""); - $bladeCompiler->directive('endhasallroles', fn () => '' ); + $bladeCompiler->directive('endhasallroles', fn () => ''); $bladeCompiler->directive('unlessrole', fn ($args) => ""); $bladeCompiler->directive('endunlessrole', fn () => ''); @@ -127,12 +124,12 @@ protected function registerMacroHelpers() Route::macro('role', function ($roles = []) { /** @var Route $this */ - return $this->middleware("role:" . implode('|', Arr::wrap($roles))); + return $this->middleware('role:'.implode('|', Arr::wrap($roles))); }); Route::macro('permission', function ($permissions = []) { /** @var Route $this */ - return $this->middleware("permission:" . implode('|', Arr::wrap($permissions))); + return $this->middleware('permission:'.implode('|', Arr::wrap($permissions))); }); } diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index 413b81386..53b7f8dbc 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -101,9 +101,7 @@ public function scopePermission(Builder $query, $permissions): Builder { $permissions = $this->convertToPermissionModels($permissions); - $rolesWithPermissions = is_a($this, Role::class) ? [] : array_unique(array_reduce($permissions, fn ($result, $permission) => - array_merge($result, $permission->roles->all()) - , [])); + $rolesWithPermissions = is_a($this, Role::class) ? [] : array_unique(array_reduce($permissions, fn ($result, $permission) => array_merge($result, $permission->roles->all()), [])); return $query->where(function (Builder $query) use ($permissions, $rolesWithPermissions) { $query->whereHas('permissions', function (Builder $subQuery) use ($permissions) { diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index 932e6ca25..8fce2d4f0 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -321,8 +321,7 @@ public function hasExactRoles($roles, string $guard = null): bool $roles = [$roles->name]; } - $roles = collect()->make($roles)->map(fn ($role) => - $role instanceof Role ? $role->name : $role + $roles = collect()->make($roles)->map(fn ($role) => $role instanceof Role ? $role->name : $role ); return $this->roles->count() == $roles->count() && $this->hasAllRoles($roles, $guard); diff --git a/src/helpers.php b/src/helpers.php index 120384bed..0f765fa4e 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -7,8 +7,7 @@ function getModelForGuard(string $guard) { return collect(config('auth.guards')) - ->map(fn ($guard) => - isset($guard['provider']) ? config("auth.providers.{$guard['provider']}.model") : null + ->map(fn ($guard) => isset($guard['provider']) ? config("auth.providers.{$guard['provider']}.model") : null )->get($guard); } } From cd37572ea5ad81a077a857aa7aa3c764779f7782 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Thu, 13 Apr 2023 10:28:13 -0500 Subject: [PATCH 303/648] Php 7.4 typed properties --- phpstan-baseline.neon | 5 +++ src/Commands/Show.php | 11 ++++--- src/Commands/UpgradeForTeams.php | 4 +-- src/Guard.php | 3 +- src/PermissionRegistrar.php | 53 +++++++++++++------------------- src/Traits/HasPermissions.php | 26 ++++++++-------- src/Traits/HasRoles.php | 21 ++++++------- 7 files changed, 58 insertions(+), 65 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 364905f71..a1b0ec7db 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,2 +1,7 @@ parameters: ignoreErrors: + - + # PHPStan can't understand what's going on in context of Role class using Builder `when` + message: "#^Call to an undefined method Spatie\\\\Permission\\\\Models\\\\Role::getRoleClass\\(\\).$#" + count: 1 + path: src\Traits\HasPermissions.php diff --git a/src/Commands/Show.php b/src/Commands/Show.php index 8b41f9985..d4f39b003 100644 --- a/src/Commands/Show.php +++ b/src/Commands/Show.php @@ -37,17 +37,20 @@ public function handle() $roles = $roleClass::whereGuardName($guard) ->with('permissions') ->when(config('permission.teams'), fn ($q) => $q->orderBy($team_key)) - ->orderBy('name')->get()->mapWithKeys(fn ($role) => [$role->name.'_'.($role->$team_key ?: '') => ['permissions' => $role->permissions->pluck('id'), $team_key => $role->$team_key]] - ); + ->orderBy('name')->get()->mapWithKeys(fn ($role) => [ + $role->name.'_'.($role->$team_key ?: '') => ['permissions' => $role->permissions->pluck('id'), $team_key => $role->$team_key], + ]); $permissions = $permissionClass::whereGuardName($guard)->orderBy('name')->pluck('name', 'id'); - $body = $permissions->map(fn ($permission, $id) => $roles->map(fn (array $role_data) => $role_data['permissions']->contains($id) ? ' ✔' : ' ·' + $body = $permissions->map(fn ($permission, $id) => $roles->map( + fn (array $role_data) => $role_data['permissions']->contains($id) ? ' ✔' : ' ·' )->prepend($permission) ); if (config('permission.teams')) { - $teams = $roles->groupBy($team_key)->values()->map(fn ($group, $id) => new TableCell('Team ID: '.($id ?: 'NULL'), ['colspan' => $group->count()]) + $teams = $roles->groupBy($team_key)->values()->map( + fn ($group, $id) => new TableCell('Team ID: '.($id ?: 'NULL'), ['colspan' => $group->count()]) ); } diff --git a/src/Commands/UpgradeForTeams.php b/src/Commands/UpgradeForTeams.php index 1e1626b08..8d7f9004d 100644 --- a/src/Commands/UpgradeForTeams.php +++ b/src/Commands/UpgradeForTeams.php @@ -88,9 +88,7 @@ protected function getExistingMigrationsWarning(array $existingMigrations) $base = "Setup teams migration already exists.\nFollowing file was found: "; } - return $base.array_reduce($existingMigrations, function ($carry, $fileName) { - return $carry."\n - ".$fileName; - }); + return $base.array_reduce($existingMigrations, fn ($carry, $fileName) => $carry."\n - ".$fileName); } /** diff --git a/src/Guard.php b/src/Guard.php index c481c0640..b5904cc4d 100644 --- a/src/Guard.php +++ b/src/Guard.php @@ -48,8 +48,7 @@ public static function getNames($model): Collection protected static function getConfigAuthGuards(string $class): Collection { return collect(config('auth.guards')) - ->map(fn ($guard) => isset($guard['provider']) ? config("auth.providers.{$guard['provider']}.model") : null - ) + ->map(fn ($guard) => isset($guard['provider']) ? config("auth.providers.{$guard['provider']}.model") : null) ->filter(fn ($model) => $class === $model) ->keys(); } diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index e95880154..6e1574f63 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -13,50 +13,38 @@ class PermissionRegistrar { - /** @var Repository */ - protected $cache; + protected Repository $cache; - /** @var CacheManager */ - protected $cacheManager; + protected CacheManager $cacheManager; - /** @var string */ - protected $permissionClass; + protected string $permissionClass; - /** @var string */ - protected $roleClass; + protected string $roleClass; /** @var Collection|array|null */ protected $permissions; - /** @var string */ - public $pivotRole; + public string $pivotRole; - /** @var string */ - public $pivotPermission; + public string $pivotPermission; /** @var \DateInterval|int */ public $cacheExpirationTime; - /** @var bool */ - public $teams; + public bool $teams; - /** @var string */ - public $teamsKey; + public string $teamsKey; /** @var int|string */ protected $teamId = null; - /** @var string */ - public $cacheKey; + public string $cacheKey; - /** @var array */ - private $cachedRoles = []; + private array $cachedRoles = []; - /** @var array */ - private $alias = []; + private array $alias = []; - /** @var array */ - private $except = []; + private array $except = []; /** * PermissionRegistrar constructor. @@ -180,9 +168,9 @@ private function loadPermissions(): void return; } - $this->permissions = $this->cache->remember($this->cacheKey, $this->cacheExpirationTime, function () { - return $this->getSerializedPermissionsForCache(); - }); + $this->permissions = $this->cache->remember( + $this->cacheKey, $this->cacheExpirationTime, fn () => $this->getSerializedPermissionsForCache() + ); // fallback for old cache method, must be removed on next mayor version if (! isset($this->permissions['alias'])) { @@ -345,11 +333,12 @@ private function getHydratedPermissionCollection(): Collection $permissionClass = $this->getPermissionClass(); $permissionInstance = new $permissionClass(); - return Collection::make( - array_map(fn ($item) => $permissionInstance - ->newFromBuilder($this->aliasedArray(array_diff_key($item, ['r' => 0]))) - ->setRelation('roles', $this->getHydratedRoleCollection($item['r'] ?? [])), $this->permissions['permissions']) - ); + return Collection::make(array_map( + fn ($item) => $permissionInstance + ->newFromBuilder($this->aliasedArray(array_diff_key($item, ['r' => 0]))) + ->setRelation('roles', $this->getHydratedRoleCollection($item['r'] ?? [])), + $this->permissions['permissions'] + )); } private function getHydratedRoleCollection(array $roles): Collection diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index 53b7f8dbc..fcd461fe1 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -19,11 +19,9 @@ trait HasPermissions { - /** @var string */ - private $permissionClass; + private ?string $permissionClass = null; - /** @var string|null */ - private $wildcardClass; + private ?string $wildcardClass = null; public static function bootHasPermissions() { @@ -101,22 +99,24 @@ public function scopePermission(Builder $query, $permissions): Builder { $permissions = $this->convertToPermissionModels($permissions); - $rolesWithPermissions = is_a($this, Role::class) ? [] : array_unique(array_reduce($permissions, fn ($result, $permission) => array_merge($result, $permission->roles->all()), [])); + $rolesWithPermissions = is_a($this, Role::class) ? [] : array_unique( + array_reduce($permissions, fn ($result, $permission) => array_merge($result, $permission->roles->all()), []) + ); - return $query->where(function (Builder $query) use ($permissions, $rolesWithPermissions) { - $query->whereHas('permissions', function (Builder $subQuery) use ($permissions) { + return $query->where(fn (Builder $query) => $query + ->whereHas('permissions', function (Builder $subQuery) use ($permissions) { $permissionClass = $this->getPermissionClass(); $key = (new $permissionClass())->getKeyName(); $subQuery->whereIn(config('permission.table_names.permissions').".$key", \array_column($permissions, $key)); - }); - if (count($rolesWithPermissions) > 0 && ! is_a($this, Role::class)) { - $query->orWhereHas('roles', function (Builder $subQuery) use ($rolesWithPermissions) { + }) + ->when(count($rolesWithPermissions) > 0, fn ($subQuery) => $subQuery + ->orWhereHas('roles', function (Builder $subQuery) use ($rolesWithPermissions) { $roleClass = $this->getRoleClass(); $key = (new $roleClass())->getKeyName(); $subQuery->whereIn(config('permission.table_names.roles').".$key", \array_column($rolesWithPermissions, $key)); - }); - } - }); + }) + ) + ); } /** diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index 8fce2d4f0..9a4d9187e 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -14,8 +14,7 @@ trait HasRoles { use HasPermissions; - /** @var string */ - private $roleClass; + private ?string $roleClass = null; public static function bootHasRoles() { @@ -60,11 +59,10 @@ public function roles(): BelongsToMany return $relation; } + $teamField = config('permission.table_names.roles').'.'.app(PermissionRegistrar::class)->teamsKey; + return $relation->wherePivot(app(PermissionRegistrar::class)->teamsKey, getPermissionsTeamId()) - ->where(function ($q) { - $teamField = config('permission.table_names.roles').'.'.app(PermissionRegistrar::class)->teamsKey; - $q->whereNull($teamField)->orWhere($teamField, getPermissionsTeamId()); - }); + ->where(fn ($q) => $q->whereNull($teamField)->orWhere($teamField, getPermissionsTeamId())); } /** @@ -89,11 +87,12 @@ public function scopeRole(Builder $query, $roles, $guard = null): Builder return $this->getRoleClass()::{$method}($role, $guard ?: $this->getDefaultGuardName()); }, Arr::wrap($roles)); - return $query->whereHas('roles', function (Builder $subQuery) use ($roles) { - $roleClass = $this->getRoleClass(); - $key = (new $roleClass())->getKeyName(); - $subQuery->whereIn(config('permission.table_names.roles').".$key", \array_column($roles, $key)); - }); + $roleClass = $this->getRoleClass(); + $key = (new $roleClass())->getKeyName(); + + return $query->whereHas('roles', fn (Builder $subQuery) => $subQuery + ->whereIn(config('permission.table_names.roles').".$key", \array_column($roles, $key)) + ); } /** From cb1963a863bbd56791f26e1e6b54b92203a1d3b6 Mon Sep 17 00:00:00 2001 From: angeljqv <79208641+angeljqv@users.noreply.github.com> Date: Fri, 14 Apr 2023 10:46:01 -0500 Subject: [PATCH 304/648] don't add commands in web interface context --- src/PermissionServiceProvider.php | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/PermissionServiceProvider.php b/src/PermissionServiceProvider.php index 16e8f3287..7d7975b47 100644 --- a/src/PermissionServiceProvider.php +++ b/src/PermissionServiceProvider.php @@ -48,7 +48,7 @@ public function register() ); } - protected function offerPublishing() + protected function offerPublishing(): void { if (! $this->app->runningInConsole()) { return; @@ -68,10 +68,17 @@ protected function offerPublishing() ], 'permission-migrations'); } - protected function registerCommands() + protected function registerCommands(): void { $this->commands([ Commands\CacheReset::class, + ]); + + if (! $this->app->runningInConsole()) { + return; + } + + $this->commands([ Commands\CreateRole::class, Commands\CreatePermission::class, Commands\Show::class, @@ -79,7 +86,7 @@ protected function registerCommands() ]); } - protected function registerModelBindings() + protected function registerModelBindings(): void { $this->app->bind(PermissionContract::class, fn ($app) => $app->make($app->config['permission.models.permission']) ); @@ -87,12 +94,12 @@ protected function registerModelBindings() ); } - public static function bladeMethodWrapper($method, $role, $guard = null) + public static function bladeMethodWrapper($method, $role, $guard = null): bool { return auth($guard)->check() && auth($guard)->user()->{$method}($role); } - protected function registerBladeExtensions($bladeCompiler) + protected function registerBladeExtensions($bladeCompiler): void { $bladeMethodWrapper = '\\Spatie\\Permission\\PermissionServiceProvider::bladeMethodWrapper'; @@ -116,7 +123,7 @@ protected function registerBladeExtensions($bladeCompiler) $bladeCompiler->directive('endhasexactroles', fn () => ''); } - protected function registerMacroHelpers() + protected function registerMacroHelpers(): void { if (! method_exists(Route::class, 'macro')) { // Lumen return; From 0a4e6bfc7d90cd2f713366043dd284da92efb539 Mon Sep 17 00:00:00 2001 From: angeljqv <79208641+angeljqv@users.noreply.github.com> Date: Fri, 14 Apr 2023 09:41:16 -0500 Subject: [PATCH 305/648] code reformatting --- src/PermissionServiceProvider.php | 6 ++--- src/Traits/HasPermissions.php | 43 ++++++++++++++++--------------- src/Traits/HasRoles.php | 18 ++++++------- src/helpers.php | 4 +-- 4 files changed, 35 insertions(+), 36 deletions(-) diff --git a/src/PermissionServiceProvider.php b/src/PermissionServiceProvider.php index 16e8f3287..262309430 100644 --- a/src/PermissionServiceProvider.php +++ b/src/PermissionServiceProvider.php @@ -81,10 +81,8 @@ protected function registerCommands() protected function registerModelBindings() { - $this->app->bind(PermissionContract::class, fn ($app) => $app->make($app->config['permission.models.permission']) - ); - $this->app->bind(RoleContract::class, fn ($app) => $app->make($app->config['permission.models.role']) - ); + $this->app->bind(PermissionContract::class, fn ($app) => $app->make($app->config['permission.models.permission'])); + $this->app->bind(RoleContract::class, fn ($app) => $app->make($app->config['permission.models.role'])); } public static function bladeMethodWrapper($method, $role, $guard = null) diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index fcd461fe1..cf07d0cdf 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -99,22 +99,23 @@ public function scopePermission(Builder $query, $permissions): Builder { $permissions = $this->convertToPermissionModels($permissions); + $permissionClass = $this->getPermissionClass(); + $permissionKey = (new $permissionClass())->getKeyName(); + $roleClass = $this->getRoleClass(); + $roleKey = (new $roleClass())->getKeyName(); + $rolesWithPermissions = is_a($this, Role::class) ? [] : array_unique( array_reduce($permissions, fn ($result, $permission) => array_merge($result, $permission->roles->all()), []) ); return $query->where(fn (Builder $query) => $query - ->whereHas('permissions', function (Builder $subQuery) use ($permissions) { - $permissionClass = $this->getPermissionClass(); - $key = (new $permissionClass())->getKeyName(); - $subQuery->whereIn(config('permission.table_names.permissions').".$key", \array_column($permissions, $key)); - }) - ->when(count($rolesWithPermissions) > 0, fn ($subQuery) => $subQuery - ->orWhereHas('roles', function (Builder $subQuery) use ($rolesWithPermissions) { - $roleClass = $this->getRoleClass(); - $key = (new $roleClass())->getKeyName(); - $subQuery->whereIn(config('permission.table_names.roles').".$key", \array_column($rolesWithPermissions, $key)); - }) + ->whereHas('permissions', fn (Builder $subQuery) => $subQuery + ->whereIn(config('permission.table_names.permissions').".$permissionKey", \array_column($permissions, $permissionKey)) + ) + ->when(count($rolesWithPermissions), fn ($whenQuery) => $whenQuery + ->orWhereHas('roles', fn (Builder $subQuery) => $subQuery + ->whereIn(config('permission.table_names.roles').".$roleKey", \array_column($rolesWithPermissions, $roleKey)) + ) ) ); } @@ -139,7 +140,7 @@ protected function convertToPermissionModels($permissions): array $permission = $permission->value; } - $method = is_string($permission) && ! PermissionRegistrar::isUid($permission) ? 'findByName' : 'findById'; + $method = is_int($permission) || PermissionRegistrar::isUid($permission) ? 'findById' : 'findByName'; return $this->getPermissionClass()::{$method}($permission, $this->getDefaultGuardName()); }, Arr::wrap($permissions)); @@ -159,15 +160,15 @@ public function filterPermission($permission, $guardName = null) $permission = $permission->value; } - if (is_string($permission) && ! PermissionRegistrar::isUid($permission)) { - $permission = $this->getPermissionClass()::findByName( + if (is_int($permission) || PermissionRegistrar::isUid($permission)) { + $permission = $this->getPermissionClass()::findById( $permission, $guardName ?? $this->getDefaultGuardName() ); } - if (is_int($permission) || is_string($permission)) { - $permission = $this->getPermissionClass()::findById( + if (is_string($permission)) { + $permission = $this->getPermissionClass()::findByName( $permission, $guardName ?? $this->getDefaultGuardName() ); @@ -209,6 +210,10 @@ protected function hasWildcardPermission($permission, $guardName = null): bool { $guardName = $guardName ?? $this->getDefaultGuardName(); + if ($permission instanceof \BackedEnum) { + $permission = $permission->value; + } + if (is_int($permission) || PermissionRegistrar::isUid($permission)) { $permission = $this->getPermissionClass()::findById($permission, $guardName); } @@ -217,10 +222,6 @@ protected function hasWildcardPermission($permission, $guardName = null): bool $permission = $permission->name; } - if ($permission instanceof \BackedEnum) { - $permission = $permission->value; - } - if (! is_string($permission)) { throw WildcardPermissionInvalidArgument::create(); } @@ -461,7 +462,7 @@ protected function getStoredPermission($permissions) $permissions = $permissions->value; } - if (is_numeric($permissions) || PermissionRegistrar::isUid($permissions)) { + if (is_int($permissions) || PermissionRegistrar::isUid($permissions)) { return $this->getPermissionClass()::findById($permissions, $this->getDefaultGuardName()); } diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index 9a4d9187e..f99697235 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -82,7 +82,7 @@ public function scopeRole(Builder $query, $roles, $guard = null): Builder return $role; } - $method = is_numeric($role) || PermissionRegistrar::isUid($role) ? 'findById' : 'findByName'; + $method = is_int($role) || PermissionRegistrar::isUid($role) ? 'findById' : 'findByName'; return $this->getRoleClass()::{$method}($role, $guard ?: $this->getDefaultGuardName()); }, Arr::wrap($roles)); @@ -209,13 +209,7 @@ public function hasRole($roles, string $guard = null): bool $roles = $roles->value; } - if (is_string($roles) && ! PermissionRegistrar::isUid($roles)) { - return $guard - ? $this->roles->where('guard_name', $guard)->contains('name', $roles) - : $this->roles->contains('name', $roles); - } - - if (is_int($roles) || is_string($roles)) { + if (is_int($roles) || PermissionRegistrar::isUid($roles)) { $roleClass = $this->getRoleClass(); $key = (new $roleClass())->getKeyName(); @@ -224,6 +218,12 @@ public function hasRole($roles, string $guard = null): bool : $this->roles->contains($key, $roles); } + if (is_string($roles)) { + return $guard + ? $this->roles->where('guard_name', $guard)->contains('name', $roles) + : $this->roles->contains('name', $roles); + } + if ($roles instanceof Role) { return $this->roles->contains($roles->getKeyName(), $roles->getKey()); } @@ -347,7 +347,7 @@ protected function getStoredRole($role): Role $role = $role->value; } - if (is_numeric($role) || PermissionRegistrar::isUid($role)) { + if (is_int($role) || PermissionRegistrar::isUid($role)) { return $this->getRoleClass()::findById($role, $this->getDefaultGuardName()); } diff --git a/src/helpers.php b/src/helpers.php index 0f765fa4e..d1aae1ee2 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -7,8 +7,8 @@ function getModelForGuard(string $guard) { return collect(config('auth.guards')) - ->map(fn ($guard) => isset($guard['provider']) ? config("auth.providers.{$guard['provider']}.model") : null - )->get($guard); + ->map(fn ($guard) => isset($guard['provider']) ? config("auth.providers.{$guard['provider']}.model") : null) + ->get($guard); } } From 07af475984d644d66761a77fc234d6f349f8266b Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 18 Apr 2023 12:02:09 -0400 Subject: [PATCH 306/648] Update README.md minor rearrangements --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 9850ee4ee..25ff759bd 100644 --- a/README.md +++ b/README.md @@ -49,13 +49,7 @@ We invest a lot of resources into creating [best in class open source packages]( We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received postcards on [our virtual postcard wall](https://spatie.be/open-source/postcards). -### Testing - -``` bash -composer test -``` - -### Changelog +## Changelog Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently. @@ -63,6 +57,12 @@ Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recen Please see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details. +### Testing + +``` bash +composer test +``` + ### Security If you discover any security-related issues, please email [security@spatie.be](mailto:security@spatie.be) instead of using the issue tracker. @@ -92,8 +92,8 @@ And a special thanks to [Caneco](https://twitter.com/caneco) for the logo ✨ ## Alternatives - [Povilas Korop](https://twitter.com/@povilaskorop) did an excellent job listing the alternatives [in an article on Laravel News](https://laravel-news.com/two-best-roles-permissions-packages). In that same article, he compares laravel-permission to [Joseph Silber](https://github.com/JosephSilber)'s [Bouncer]((https://github.com/JosephSilber/bouncer)), which in our book is also an excellent package. -- [ultraware/roles](https://github.com/ultraware/roles) takes a slightly different approach to its features. - [santigarcor/laratrust](https://github.com/santigarcor/laratrust) implements team support +- [ultraware/roles](https://github.com/ultraware/roles) (archived) takes a slightly different approach to its features. - [zizaco/entrust](https://github.com/zizaco/entrust) offers some wildcard pattern matching ## License From 32ff0612863a347fce1dfd88e6d66e995728fa18 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Apr 2023 06:01:17 +0000 Subject: [PATCH 307/648] Bump dependabot/fetch-metadata from 1.3.6 to 1.4.0 Bumps [dependabot/fetch-metadata](https://github.com/dependabot/fetch-metadata) from 1.3.6 to 1.4.0. - [Release notes](https://github.com/dependabot/fetch-metadata/releases) - [Commits](https://github.com/dependabot/fetch-metadata/compare/v1.3.6...v1.4.0) --- updated-dependencies: - dependency-name: dependabot/fetch-metadata dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/dependabot-auto-merge.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml index f2e85e7d4..4c8e4c314 100644 --- a/.github/workflows/dependabot-auto-merge.yml +++ b/.github/workflows/dependabot-auto-merge.yml @@ -13,7 +13,7 @@ jobs: - name: Dependabot metadata id: metadata - uses: dependabot/fetch-metadata@v1.3.6 + uses: dependabot/fetch-metadata@v1.4.0 with: github-token: "${{ secrets.GITHUB_TOKEN }}" compat-lookup: true From d595dfff2b4a580872c969895964903a2a11f109 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Wed, 26 Apr 2023 10:55:19 -0500 Subject: [PATCH 308/648] Fix call to an undefined method Role::getRoleClass --- phpstan-baseline.neon | 5 ----- src/Traits/HasPermissions.php | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index a1b0ec7db..364905f71 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,7 +1,2 @@ parameters: ignoreErrors: - - - # PHPStan can't understand what's going on in context of Role class using Builder `when` - message: "#^Call to an undefined method Spatie\\\\Permission\\\\Models\\\\Role::getRoleClass\\(\\).$#" - count: 1 - path: src\Traits\HasPermissions.php diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index cf07d0cdf..c0b1a1b6f 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -101,7 +101,7 @@ public function scopePermission(Builder $query, $permissions): Builder $permissionClass = $this->getPermissionClass(); $permissionKey = (new $permissionClass())->getKeyName(); - $roleClass = $this->getRoleClass(); + $roleClass = is_a($this, Role::class) ? static::class : $this->getRoleClass(); $roleKey = (new $roleClass())->getKeyName(); $rolesWithPermissions = is_a($this, Role::class) ? [] : array_unique( From ec9079aadfe20051a9ccf0a15b6c9ed453b7bca6 Mon Sep 17 00:00:00 2001 From: drbyte Date: Wed, 26 Apr 2023 16:39:50 +0000 Subject: [PATCH 309/648] Fix styling --- src/Commands/Show.php | 6 +++--- src/Models/Role.php | 2 +- tests/HasPermissionsTest.php | 6 +++--- tests/RouteTest.php | 14 +++++++------- tests/WildcardRouteTest.php | 10 +++++----- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/Commands/Show.php b/src/Commands/Show.php index d4f39b003..0c08bd28a 100644 --- a/src/Commands/Show.php +++ b/src/Commands/Show.php @@ -44,8 +44,8 @@ public function handle() $permissions = $permissionClass::whereGuardName($guard)->orderBy('name')->pluck('name', 'id'); $body = $permissions->map(fn ($permission, $id) => $roles->map( - fn (array $role_data) => $role_data['permissions']->contains($id) ? ' ✔' : ' ·' - )->prepend($permission) + fn (array $role_data) => $role_data['permissions']->contains($id) ? ' ✔' : ' ·' + )->prepend($permission) ); if (config('permission.teams')) { @@ -63,7 +63,7 @@ public function handle() return implode('_', $name); }) - ->prepend('')->toArray(), + ->prepend('')->toArray(), ), $body->toArray(), $style diff --git a/src/Models/Role.php b/src/Models/Role.php index 472a93847..7199504d1 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -151,7 +151,7 @@ protected static function findByParam(array $params = []) $teamsKey = app(PermissionRegistrar::class)->teamsKey; $query->where(fn ($q) => $q->whereNull($teamsKey) - ->orWhere($teamsKey, $params[$teamsKey] ?? getPermissionsTeamId()) + ->orWhere($teamsKey, $params[$teamsKey] ?? getPermissionsTeamId()) ); unset($params[$teamsKey]); } diff --git a/tests/HasPermissionsTest.php b/tests/HasPermissionsTest.php index 5871ed46c..fdc4c94e5 100644 --- a/tests/HasPermissionsTest.php +++ b/tests/HasPermissionsTest.php @@ -653,7 +653,7 @@ public function it_can_check_permission_based_on_logged_in_user_guard() 'guard_name' => 'api', ])); $response = $this->actingAs($this->testUser, 'api') - ->json('GET', '/check-api-guard-permission'); + ->json('GET', '/check-api-guard-permission'); $response->assertJson([ 'status' => true, ]); @@ -674,8 +674,8 @@ public function it_can_reject_permission_based_on_logged_in_user_guard() $this->testUser->givePermissionTo($assignedPermission); $response = $this->withExceptionHandling() - ->actingAs($this->testUser, 'api') - ->json('GET', '/check-api-guard-permission'); + ->actingAs($this->testUser, 'api') + ->json('GET', '/check-api-guard-permission'); $response->assertJson([ 'status' => false, ]); diff --git a/tests/RouteTest.php b/tests/RouteTest.php index db5a6cf6b..e4843f0cb 100644 --- a/tests/RouteTest.php +++ b/tests/RouteTest.php @@ -10,8 +10,8 @@ public function test_role_function() $router = $this->getRouter(); $router->get('role-test', $this->getRouteResponse()) - ->name('role.test') - ->role('superadmin'); + ->name('role.test') + ->role('superadmin'); $this->assertEquals(['role:superadmin'], $this->getLastRouteMiddlewareFromRouter($router)); } @@ -22,8 +22,8 @@ public function test_permission_function() $router = $this->getRouter(); $router->get('permission-test', $this->getRouteResponse()) - ->name('permission.test') - ->permission(['edit articles', 'save articles']); + ->name('permission.test') + ->permission(['edit articles', 'save articles']); $this->assertEquals(['permission:edit articles|save articles'], $this->getLastRouteMiddlewareFromRouter($router)); } @@ -34,9 +34,9 @@ public function test_role_and_permission_function_together() $router = $this->getRouter(); $router->get('role-permission-test', $this->getRouteResponse()) - ->name('role-permission.test') - ->role('superadmin|admin') - ->permission('create user|edit user'); + ->name('role-permission.test') + ->role('superadmin|admin') + ->permission('create user|edit user'); $this->assertEquals( [ diff --git a/tests/WildcardRouteTest.php b/tests/WildcardRouteTest.php index 7292a6cb2..f56dfcb6c 100644 --- a/tests/WildcardRouteTest.php +++ b/tests/WildcardRouteTest.php @@ -12,8 +12,8 @@ public function test_permission_function() $router = $this->getRouter(); $router->get('permission-test', $this->getRouteResponse()) - ->name('permission.test') - ->permission(['articles.edit', 'articles.save']); + ->name('permission.test') + ->permission(['articles.edit', 'articles.save']); $this->assertEquals(['permission:articles.edit|articles.save'], $this->getLastRouteMiddlewareFromRouter($router)); } @@ -26,9 +26,9 @@ public function test_role_and_permission_function_together() $router = $this->getRouter(); $router->get('role-permission-test', $this->getRouteResponse()) - ->name('role-permission.test') - ->role('superadmin|admin') - ->permission('user.create|user.edit'); + ->name('role-permission.test') + ->role('superadmin|admin') + ->permission('user.create|user.edit'); $this->assertEquals( [ From 82159260ab2c38aeac34b4341d7f779aac9a1cf6 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Wed, 26 Apr 2023 15:42:11 -0500 Subject: [PATCH 310/648] Add database cache reset count to tests (#2415) --- tests/HasPermissionsWithCustomModelsTest.php | 18 +++++++++++++++--- tests/HasRolesWithCustomModelsTest.php | 18 +++++++++++++++--- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/tests/HasPermissionsWithCustomModelsTest.php b/tests/HasPermissionsWithCustomModelsTest.php index 90b6abc4d..7f5ccde75 100644 --- a/tests/HasPermissionsWithCustomModelsTest.php +++ b/tests/HasPermissionsWithCustomModelsTest.php @@ -12,6 +12,18 @@ class HasPermissionsWithCustomModelsTest extends HasPermissionsTest /** @var bool */ protected $useCustomModels = true; + /** @var int */ + protected $resetDatabaseQuery = 0; + + protected function getEnvironmentSetUp($app) + { + parent::getEnvironmentSetUp($app); + + if ($app['config']->get('cache.default') == 'database') { + $this->resetDatabaseQuery = 1; + } + } + /** @test */ public function it_can_use_custom_model_permission() { @@ -75,7 +87,7 @@ public function it_doesnt_detach_roles_when_soft_deleting() $this->testUserPermission->delete(); DB::disableQueryLog(); - $this->assertSame(1, count(DB::getQueryLog())); + $this->assertSame(1 + $this->resetDatabaseQuery, count(DB::getQueryLog())); $permission = Permission::onlyTrashed()->find($this->testUserPermission->getKey()); @@ -91,7 +103,7 @@ public function it_doesnt_detach_users_when_soft_deleting() $this->testUserPermission->delete(); DB::disableQueryLog(); - $this->assertSame(1, count(DB::getQueryLog())); + $this->assertSame(1 + $this->resetDatabaseQuery, count(DB::getQueryLog())); $permission = Permission::onlyTrashed()->find($this->testUserPermission->getKey()); @@ -109,7 +121,7 @@ public function it_does_detach_roles_and_users_when_force_deleting() $this->testUserPermission->forceDelete(); DB::disableQueryLog(); - $this->assertSame(3, count(DB::getQueryLog())); //avoid detach permissions on permissions + $this->assertSame(3 + $this->resetDatabaseQuery, count(DB::getQueryLog())); //avoid detach permissions on permissions $permission = Permission::withTrashed()->find($permission_id); diff --git a/tests/HasRolesWithCustomModelsTest.php b/tests/HasRolesWithCustomModelsTest.php index bb3ddce32..659f04db7 100644 --- a/tests/HasRolesWithCustomModelsTest.php +++ b/tests/HasRolesWithCustomModelsTest.php @@ -10,6 +10,18 @@ class HasRolesWithCustomModelsTest extends HasRolesTest /** @var bool */ protected $useCustomModels = true; + /** @var int */ + protected $resetDatabaseQuery = 0; + + protected function getEnvironmentSetUp($app) + { + parent::getEnvironmentSetUp($app); + + if ($app['config']->get('cache.default') == 'database') { + $this->resetDatabaseQuery = 1; + } + } + /** @test */ public function it_can_use_custom_model_role() { @@ -25,7 +37,7 @@ public function it_doesnt_detach_permissions_when_soft_deleting() $this->testUserRole->delete(); DB::disableQueryLog(); - $this->assertSame(1, count(DB::getQueryLog())); + $this->assertSame(1 + $this->resetDatabaseQuery, count(DB::getQueryLog())); $role = Role::onlyTrashed()->find($this->testUserRole->getKey()); @@ -41,7 +53,7 @@ public function it_doesnt_detach_users_when_soft_deleting() $this->testUserRole->delete(); DB::disableQueryLog(); - $this->assertSame(1, count(DB::getQueryLog())); + $this->assertSame(1 + $this->resetDatabaseQuery, count(DB::getQueryLog())); $role = Role::onlyTrashed()->find($this->testUserRole->getKey()); @@ -59,7 +71,7 @@ public function it_does_detach_permissions_and_users_when_force_deleting() $this->testUserRole->forceDelete(); DB::disableQueryLog(); - $this->assertSame(3, count(DB::getQueryLog())); + $this->assertSame(3 + $this->resetDatabaseQuery, count(DB::getQueryLog())); $role = Role::withTrashed()->find($role_id); From f9446b85263246907c76a810944c8aa8668b98ee Mon Sep 17 00:00:00 2001 From: erikn69 Date: Wed, 26 Apr 2023 15:43:26 -0500 Subject: [PATCH 311/648] Remove force loading model relationships (#2412) --- src/Traits/HasPermissions.php | 6 +++--- src/Traits/HasRoles.php | 6 +++--- tests/CacheTest.php | 11 ++++++----- tests/HasPermissionsTest.php | 4 ++-- tests/HasRolesTest.php | 4 ++-- tests/TeamHasPermissionsTest.php | 12 ++---------- tests/TeamHasRolesTest.php | 4 ---- 7 files changed, 18 insertions(+), 29 deletions(-) diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index c0b1a1b6f..174c254fe 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -391,7 +391,7 @@ public function givePermissionTo(...$permissions) if ($model->exists) { $this->permissions()->sync($permissions, false); - $model->load('permissions'); + $model->unsetRelation('permissions'); } else { $class = \get_class($model); @@ -401,7 +401,7 @@ function ($object) use ($permissions, $model) { return; } $model->permissions()->sync($permissions, false); - $model->load('permissions'); + $model->unsetRelation('permissions'); } ); } @@ -442,7 +442,7 @@ public function revokePermissionTo($permission) $this->forgetCachedPermissions(); } - $this->load('permissions'); + $this->unsetRelation('permissions'); return $this; } diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index f99697235..419f5189a 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -137,7 +137,7 @@ public function assignRole(...$roles) if ($model->exists) { $this->roles()->sync($roles, false); - $model->load('roles'); + $model->unsetRelation('roles'); } else { $class = \get_class($model); @@ -147,7 +147,7 @@ function ($object) use ($roles, $model) { return; } $model->roles()->sync($roles, false); - $model->load('roles'); + $model->unsetRelation('roles'); } ); } @@ -168,7 +168,7 @@ public function removeRole($role) { $this->roles()->detach($this->getStoredRole($role)); - $this->load('roles'); + $this->unsetRelation('roles'); if (is_a($this, Permission::class)) { $this->forgetCachedPermissions(); diff --git a/tests/CacheTest.php b/tests/CacheTest.php index 2205e298c..ba8ca6a1e 100644 --- a/tests/CacheTest.php +++ b/tests/CacheTest.php @@ -18,8 +18,6 @@ class CacheTest extends TestCase protected $cache_run_count = 2; // roles lookup, permissions lookup - protected $cache_relations_count = 1; - protected $registrar; protected function setUp(): void @@ -199,10 +197,11 @@ public function has_permission_to_should_use_the_cache() { $this->testUserRole->givePermissionTo(['edit-articles', 'edit-news', 'Edit News']); $this->testUser->assignRole('testRole'); + $this->testUser->loadMissing('roles', 'permissions'); // load relations $this->resetQueryCount(); $this->assertTrue($this->testUser->hasPermissionTo('edit-articles')); - $this->assertQueryCount($this->cache_init_count + $this->cache_load_count + $this->cache_run_count + $this->cache_relations_count); + $this->assertQueryCount($this->cache_init_count + $this->cache_load_count + $this->cache_run_count); $this->resetQueryCount(); $this->assertTrue($this->testUser->hasPermissionTo('edit-news')); @@ -224,10 +223,11 @@ public function the_cache_should_differentiate_by_guard_name() $this->testUserRole->givePermissionTo(['edit-articles', 'web']); $this->testUser->assignRole('testRole'); + $this->testUser->loadMissing('roles', 'permissions'); // load relations $this->resetQueryCount(); $this->assertTrue($this->testUser->hasPermissionTo('edit-articles', 'web')); - $this->assertQueryCount($this->cache_init_count + $this->cache_load_count + $this->cache_run_count + $this->cache_relations_count); + $this->assertQueryCount($this->cache_init_count + $this->cache_load_count + $this->cache_run_count); $this->resetQueryCount(); $this->assertFalse($this->testUser->hasPermissionTo('edit-articles', 'admin')); @@ -239,6 +239,7 @@ public function get_all_permissions_should_use_the_cache() { $this->testUserRole->givePermissionTo($expected = ['edit-articles', 'edit-news']); $this->testUser->assignRole('testRole'); + $this->testUser->loadMissing('roles.permissions', 'permissions'); // load relations $this->resetQueryCount(); $this->registrar->getPermissions(); @@ -248,7 +249,7 @@ public function get_all_permissions_should_use_the_cache() $actual = $this->testUser->getAllPermissions()->pluck('name')->sort()->values(); $this->assertEquals($actual, collect($expected)); - $this->assertQueryCount(2); + $this->assertQueryCount(0); } /** @test */ diff --git a/tests/HasPermissionsTest.php b/tests/HasPermissionsTest.php index fdc4c94e5..c876294f9 100644 --- a/tests/HasPermissionsTest.php +++ b/tests/HasPermissionsTest.php @@ -591,7 +591,7 @@ public function calling_givePermissionTo_before_saving_object_doesnt_interfere_w $this->assertTrue($user2->fresh()->hasPermissionTo('edit-articles')); $this->assertFalse($user2->fresh()->hasPermissionTo('edit-news')); - $this->assertSame(4, count(DB::getQueryLog())); //avoid unnecessary sync + $this->assertSame(3, count(DB::getQueryLog())); //avoid unnecessary sync } /** @test */ @@ -613,7 +613,7 @@ public function calling_syncPermissions_before_saving_object_doesnt_interfere_wi $this->assertTrue($user2->fresh()->hasPermissionTo('edit-articles')); $this->assertFalse($user2->fresh()->hasPermissionTo('edit-news')); - $this->assertSame(4, count(DB::getQueryLog())); //avoid unnecessary sync + $this->assertSame(3, count(DB::getQueryLog())); //avoid unnecessary sync } /** @test */ diff --git a/tests/HasRolesTest.php b/tests/HasRolesTest.php index 2d0c63ecc..b984aeb4d 100644 --- a/tests/HasRolesTest.php +++ b/tests/HasRolesTest.php @@ -303,7 +303,7 @@ public function calling_syncRoles_before_saving_object_doesnt_interfere_with_oth $this->assertTrue($user2->fresh()->hasRole('testRole2')); $this->assertFalse($user2->fresh()->hasRole('testRole')); - $this->assertSame(4, count(DB::getQueryLog())); //avoid unnecessary sync + $this->assertSame(3, count(DB::getQueryLog())); //avoid unnecessary sync } /** @test */ @@ -325,7 +325,7 @@ public function calling_assignRole_before_saving_object_doesnt_interfere_with_ot $this->assertTrue($admin_user->fresh()->hasRole('testRole2')); $this->assertFalse($admin_user->fresh()->hasRole('testRole')); - $this->assertSame(4, count(DB::getQueryLog())); //avoid unnecessary sync + $this->assertSame(3, count(DB::getQueryLog())); //avoid unnecessary sync } /** @test */ diff --git a/tests/TeamHasPermissionsTest.php b/tests/TeamHasPermissionsTest.php index 54fe106f0..5c9826b15 100644 --- a/tests/TeamHasPermissionsTest.php +++ b/tests/TeamHasPermissionsTest.php @@ -13,11 +13,9 @@ class TeamHasPermissionsTest extends HasPermissionsTest public function it_can_assign_same_and_different_permission_on_same_user_on_different_teams() { setPermissionsTeamId(1); - $this->testUser->load('permissions'); $this->testUser->givePermissionTo('edit-articles', 'edit-news'); setPermissionsTeamId(2); - $this->testUser->load('permissions'); $this->testUser->givePermissionTo('edit-articles', 'edit-blog'); setPermissionsTeamId(1); @@ -45,18 +43,15 @@ public function it_can_list_all_the_coupled_permissions_both_directly_and_via_ro $this->testUserRole->givePermissionTo('edit-articles'); setPermissionsTeamId(1); - $this->testUser->load('permissions'); $this->testUser->assignRole('testRole'); $this->testUser->givePermissionTo('edit-news'); setPermissionsTeamId(2); - $this->testUser->load('permissions'); $this->testUser->assignRole('testRole'); $this->testUser->givePermissionTo('edit-blog'); setPermissionsTeamId(1); - $this->testUser->load('roles'); - $this->testUser->load('permissions'); + $this->testUser->load('roles', 'permissions'); $this->assertEquals( collect(['edit-articles', 'edit-news']), @@ -64,8 +59,7 @@ public function it_can_list_all_the_coupled_permissions_both_directly_and_via_ro ); setPermissionsTeamId(2); - $this->testUser->load('roles'); - $this->testUser->load('permissions'); + $this->testUser->load('roles', 'permissions'); $this->assertEquals( collect(['edit-articles', 'edit-blog']), @@ -77,11 +71,9 @@ public function it_can_list_all_the_coupled_permissions_both_directly_and_via_ro public function it_can_sync_or_remove_permission_without_detach_on_different_teams() { setPermissionsTeamId(1); - $this->testUser->load('permissions'); $this->testUser->syncPermissions('edit-articles', 'edit-news'); setPermissionsTeamId(2); - $this->testUser->load('permissions'); $this->testUser->syncPermissions('edit-articles', 'edit-blog'); setPermissionsTeamId(1); diff --git a/tests/TeamHasRolesTest.php b/tests/TeamHasRolesTest.php index ca40292ea..e67010009 100644 --- a/tests/TeamHasRolesTest.php +++ b/tests/TeamHasRolesTest.php @@ -50,11 +50,9 @@ public function it_can_assign_same_and_different_roles_on_same_user_different_te $this->assertNotNull($testRole4NoTeam); setPermissionsTeamId(1); - $this->testUser->load('roles'); $this->testUser->assignRole('testRole', 'testRole2'); setPermissionsTeamId(2); - $this->testUser->load('roles'); $this->testUser->assignRole('testRole', 'testRole3'); setPermissionsTeamId(1); @@ -91,11 +89,9 @@ public function it_can_sync_or_remove_roles_without_detach_on_different_teams() app(Role::class)->create(['name' => 'testRole3', 'team_test_id' => 2]); setPermissionsTeamId(1); - $this->testUser->load('roles'); $this->testUser->syncRoles('testRole', 'testRole2'); setPermissionsTeamId(2); - $this->testUser->load('roles'); $this->testUser->syncRoles('testRole', 'testRole3'); setPermissionsTeamId(1); From 9f18371600277317260c14d296e01732710bc0b2 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Thu, 27 Apr 2023 12:33:47 -0500 Subject: [PATCH 312/648] Alternate cache driver on tests (#2416) --- .github/workflows/test-cache-drivers.yml | 67 ++++++++++++++++++++++++ tests/TestCase.php | 1 + 2 files changed, 68 insertions(+) create mode 100644 .github/workflows/test-cache-drivers.yml diff --git a/.github/workflows/test-cache-drivers.yml b/.github/workflows/test-cache-drivers.yml new file mode 100644 index 000000000..ab49f2cf2 --- /dev/null +++ b/.github/workflows/test-cache-drivers.yml @@ -0,0 +1,67 @@ +name: "Run Tests - Cache Drivers" + +on: [push, pull_request] + +jobs: + cache: + + runs-on: ubuntu-latest + + services: + redis: + image: redis + ports: + - 6379/tcp + options: --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=3 + + strategy: + fail-fast: false + + name: Cache Drivers + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.2 + extensions: curl, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, iconv, memcache + coverage: none + + - name: Install dependencies + run: | + composer require "predis/predis" --no-interaction --no-update + composer update --prefer-stable --prefer-dist --no-interaction + + - name: Execute tests - memcached cache driver + run: | + vendor/bin/phpunit + env: + CACHE_DRIVER: memcached + + - name: Execute tests - redis cache driver + run: | + vendor/bin/phpunit + env: + CACHE_DRIVER: redis + REDIS_PORT: ${{ job.services.redis.ports['6379'] }} + + - name: Execute tests - database cache driver + run: | + vendor/bin/phpunit + env: + CACHE_DRIVER: database + + - name: Execute tests - file cache driver + run: | + vendor/bin/phpunit + env: + CACHE_DRIVER: file + + - name: Execute tests - array cache driver + run: | + vendor/bin/phpunit + env: + CACHE_DRIVER: array diff --git a/tests/TestCase.php b/tests/TestCase.php index b45718f0c..456964ec1 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -111,6 +111,7 @@ protected function getEnvironmentSetUp($app) $app['config']->set('auth.providers.users.model', User::class); $app['config']->set('cache.prefix', 'spatie_tests---'); + $app['config']->set('cache.default', getenv('CACHE_DRIVER') ?: 'array'); // FOR MANUAL TESTING OF ALTERNATE CACHE STORES: // $app['config']->set('cache.default', 'array'); From 2bc6a501d1a0ed448e78a02d5104edefaa97c092 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hasan=20Ek=C5=9Fi?= Date: Fri, 28 Apr 2023 12:49:40 +0300 Subject: [PATCH 313/648] Fix command name (#2418) --- docs/basic-usage/teams-permissions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/basic-usage/teams-permissions.md b/docs/basic-usage/teams-permissions.md index 3b2c49528..a97136e8c 100644 --- a/docs/basic-usage/teams-permissions.md +++ b/docs/basic-usage/teams-permissions.md @@ -9,7 +9,7 @@ When enabled, teams permissions offers you flexible control for a variety of sce NOTE: These configuration changes must be made **before** performing the migration when first installing the package. -If you have already run the migration and want to upgrade your implementation, you can run the artisan console command `php artisan spatie-permission:setup-teams`, to create a new migration file named [xxxx_xx_xx_xx_add_teams_fields.php](https://github.com/spatie/laravel-permission/blob/main/database/migrations/add_teams_fields.php.stub) and then run `php artisan migrate` to upgrade your database tables. +If you have already run the migration and want to upgrade your implementation, you can run the artisan console command `php artisan permission:setup-teams`, to create a new migration file named [xxxx_xx_xx_xx_add_teams_fields.php](https://github.com/spatie/laravel-permission/blob/main/database/migrations/add_teams_fields.php.stub) and then run `php artisan migrate` to upgrade your database tables. Teams permissions can be enabled in the permission config file: From 61cacc007c64aa3364709568c8f9e8c332db15cf Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 28 Apr 2023 18:04:56 -0400 Subject: [PATCH 314/648] [Docs] Update v6 upgrade instructions --- docs/upgrading.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/upgrading.md b/docs/upgrading.md index 837937440..7a9e463d1 100644 --- a/docs/upgrading.md +++ b/docs/upgrading.md @@ -30,11 +30,17 @@ Be sure to compare your custom models with originals to see what else may have c 2. If you have a custom Role model and (in the rare case that you might) have overridden the `hasPermissionTo()` method in it, you will need to update its method signature to `hasPermissionTo($permission, $guardName = null):bool`. See PR #2380. -3. Also note that the migrations have been updated to anonymous-class syntax that was introduced in Laravel 8. You may optionally update your original migration files accordingly. +3. Migrations. If you have old migrations you might get the following error: + + Error: Access to undeclared static property Spatie\Permission\PermissionRegistrar::$pivotPermission + +To fix this, update your migration file associated with this package. + +Also note that the migrations have been updated to anonymous-class syntax that was introduced in Laravel 8. 4. NOTE: For consistency with the `PermissionMiddleware`, the `RoleOrPermissionMiddleware` has switched from only checking permissions provided by this package to using `canAny()` to check against any abilities registered by your application. This may have the effect of granting those other abilities (such as Super Admin) when using the `RoleOrPermissionMiddleware`, which previously would have failed silently. -5. Test suites. If you have tests which manually clear the permission cache and re-register permissions, you no longer need to call `\Spatie\Permission\PermissionRegistrar::class)->registerPermissions();`. Such calls can be deleted from your tests. +5. Test suites. If you have tests which manually clear the permission cache and re-register permissions, you no longer need to call `\Spatie\Permission\PermissionRegistrar::class)->registerPermissions();`. Such calls MUST be deleted from your tests. ## Upgrading from v1 to v2 From 88cf4f98af7a93b010269249fea847a2e8ad9e85 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 28 Apr 2023 18:32:33 -0400 Subject: [PATCH 315/648] [Docs] note: requires v6 --- docs/basic-usage/enums.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/basic-usage/enums.md b/docs/basic-usage/enums.md index dffce53f9..2de63642b 100644 --- a/docs/basic-usage/enums.md +++ b/docs/basic-usage/enums.md @@ -5,6 +5,8 @@ weight: 4 ## Enum Prerequisites +Requires `version 6` of this package. + Requires PHP 8.1 or higher. If you are using PHP 8.1+ you can implement Enums as native types. From 1f04549fe7153a62ceb60d1cca49bdb36a5dcaba Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 28 Apr 2023 19:16:41 -0400 Subject: [PATCH 316/648] [Docs] Update v6 upgrade instructions --- docs/upgrading.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/upgrading.md b/docs/upgrading.md index 7a9e463d1..2b3c892e3 100644 --- a/docs/upgrading.md +++ b/docs/upgrading.md @@ -32,11 +32,11 @@ Be sure to compare your custom models with originals to see what else may have c 3. Migrations. If you have old migrations you might get the following error: - Error: Access to undeclared static property Spatie\Permission\PermissionRegistrar::$pivotPermission + `Error: Access to undeclared static property Spatie\Permission\PermissionRegistrar::$pivotPermission` -To fix this, update your migration file associated with this package. + To fix this, update your migration file associated with this package. -Also note that the migrations have been updated to anonymous-class syntax that was introduced in Laravel 8. + Also note that the migrations have been updated to anonymous-class syntax that was introduced in Laravel 8. 4. NOTE: For consistency with the `PermissionMiddleware`, the `RoleOrPermissionMiddleware` has switched from only checking permissions provided by this package to using `canAny()` to check against any abilities registered by your application. This may have the effect of granting those other abilities (such as Super Admin) when using the `RoleOrPermissionMiddleware`, which previously would have failed silently. From 47867f02e10bbb201012bab47f69858e7794e140 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 28 Apr 2023 21:21:06 -0400 Subject: [PATCH 317/648] [Docs] Update v6 upgrade instructions --- docs/upgrading.md | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/docs/upgrading.md b/docs/upgrading.md index 2b3c892e3..9642d8326 100644 --- a/docs/upgrading.md +++ b/docs/upgrading.md @@ -32,18 +32,33 @@ Be sure to compare your custom models with originals to see what else may have c 3. Migrations. If you have old migrations you might get the following error: - `Error: Access to undeclared static property Spatie\Permission\PermissionRegistrar::$pivotPermission` + `Error: Access to undeclared static property Spatie\Permission\PermissionRegistrar::$pivotPermission` To fix this, update your migration file associated with this package. Also note that the migrations have been updated to anonymous-class syntax that was introduced in Laravel 8. -4. NOTE: For consistency with the `PermissionMiddleware`, the `RoleOrPermissionMiddleware` has switched from only checking permissions provided by this package to using `canAny()` to check against any abilities registered by your application. This may have the effect of granting those other abilities (such as Super Admin) when using the `RoleOrPermissionMiddleware`, which previously would have failed silently. +4. NOTE: For consistency with `PermissionMiddleware`, the `RoleOrPermissionMiddleware` has switched from only checking permissions provided by this package to using `canAny()` to check against any abilities registered by your application. This may have the effect of granting those other abilities (such as Super Admin) when using the `RoleOrPermissionMiddleware`, which previously would have failed silently. 5. Test suites. If you have tests which manually clear the permission cache and re-register permissions, you no longer need to call `\Spatie\Permission\PermissionRegistrar::class)->registerPermissions();`. Such calls MUST be deleted from your tests. +## Upgrading from v4 to v5 + +Follow the instructions described in "Essentials" above. + +## Upgrading from v3 to v4 + +Update `composer.json` as described in "Essentials" above. + +## Upgrading from v2 to v3 + +Update `composer.json` as described in "Essentials" above. + + ## Upgrading from v1 to v2 +There were significant database and code changes between v1 to v2. + If you're upgrading from v1 to v2, there's no built-in automatic migration/conversion of your data to the new structure. You will need to carefully adapt your code and your data manually. From 2a9fd80679e45b603ecae5a56109dce46c28bf01 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 28 Apr 2023 21:32:52 -0400 Subject: [PATCH 318/648] [Docs] Update v6 upgrade instructions --- docs/upgrading.md | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/docs/upgrading.md b/docs/upgrading.md index 9642d8326..de7e487eb 100644 --- a/docs/upgrading.md +++ b/docs/upgrading.md @@ -7,19 +7,21 @@ weight: 6 ALL upgrades of this package should follow these steps: -1. Upgrading between major versions of this package always require the usual Composer steps: +1. Composer. Upgrading between major versions of this package always require the usual Composer steps: - Update your `composer.json` to specify the new major version, such as `^6.0` - Then run `composer update`. -2. Compare the `migration` file stubs in the NEW version of this package against the migrations you've already run inside your app. If necessary, create a new migration (by hand) to apply any new changes. +2. Migrations. Compare the `migration` file stubs in the NEW version of this package against the migrations you've already run inside your app. If necessary, create a new migration (by hand) to apply any new database changes. -3. If you have made any custom Models from this package into your own app, compare the old and new models and apply any relevant updates to your custom models. +3. Config file. Incorporate any changes to the permission.php config file, updating your existing file. (It may be easiest to make a backup copy of your existing file, re-publish it from this package, and then re-make your customizations to it.) -4. If you have overridden any methods from this package's Traits, compare the old and new traits, and apply any relevant updates to your overridden methods. +3. Models. If you have made any custom Models from this package into your own app, compare the old and new models and apply any relevant updates to your custom models. + +4. Custom Methods/Traits. If you have overridden any methods from this package's Traits, compare the old and new traits, and apply any relevant updates to your overridden methods. 5. Apply any version-specific special updates as outlined below... -6. Review the changelog, which details all the changes: https://github.com/spatie/laravel-permission/blob/main/CHANGELOG.md +6. Review the changelog, which details all the changes: [CHANGELOG](https://github.com/spatie/laravel-permission/blob/main/CHANGELOG.md) and/or consult the [Release Notes](https://github.com/spatie/laravel-permission/releases) @@ -30,13 +32,13 @@ Be sure to compare your custom models with originals to see what else may have c 2. If you have a custom Role model and (in the rare case that you might) have overridden the `hasPermissionTo()` method in it, you will need to update its method signature to `hasPermissionTo($permission, $guardName = null):bool`. See PR #2380. -3. Migrations. If you have old migrations you might get the following error: - - `Error: Access to undeclared static property Spatie\Permission\PermissionRegistrar::$pivotPermission` +3. Migrations. Migrations have changed in 2 ways: + - The migrations have been updated to anonymous-class syntax that was introduced in Laravel 8. + - Some structural coding changes in the registrar class changed the way we extracted configuration settings in the migration files. + - THEREFORE: you will need to upgrade your migrations, especially if you get the following error: - To fix this, update your migration file associated with this package. + `Error: Access to undeclared static property Spatie\Permission\PermissionRegistrar::$pivotPermission` - Also note that the migrations have been updated to anonymous-class syntax that was introduced in Laravel 8. 4. NOTE: For consistency with `PermissionMiddleware`, the `RoleOrPermissionMiddleware` has switched from only checking permissions provided by this package to using `canAny()` to check against any abilities registered by your application. This may have the effect of granting those other abilities (such as Super Admin) when using the `RoleOrPermissionMiddleware`, which previously would have failed silently. From c17c0e20c8808e5bb0d4052f04faadd8403330d2 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 28 Apr 2023 21:39:30 -0400 Subject: [PATCH 319/648] [Docs] Update v6 upgrade instructions --- docs/upgrading.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/upgrading.md b/docs/upgrading.md index de7e487eb..3ae0b6728 100644 --- a/docs/upgrading.md +++ b/docs/upgrading.md @@ -26,20 +26,21 @@ and/or consult the [Release Notes](https://github.com/spatie/laravel-permission/ ## Upgrading to v6 +There are a few breaking-changes when upgrading to v6. + 1. If you have overridden the `getPermissionClass()` or `getRoleClass()` methods or have custom Models, you will need to revisit those customizations. See PR #2368 for details. eg: if you have a custom model you will need to make changes, including accessing the model using `$this->permissionClass::` syntax (eg: using `::` instead of `->`) in all the overridden methods that make use of the models. Be sure to compare your custom models with originals to see what else may have changed. 2. If you have a custom Role model and (in the rare case that you might) have overridden the `hasPermissionTo()` method in it, you will need to update its method signature to `hasPermissionTo($permission, $guardName = null):bool`. See PR #2380. -3. Migrations. Migrations have changed in 2 ways: - - The migrations have been updated to anonymous-class syntax that was introduced in Laravel 8. - - Some structural coding changes in the registrar class changed the way we extracted configuration settings in the migration files. - - THEREFORE: you will need to upgrade your migrations, especially if you get the following error: +3. Migrations have changed in a few ways: + 1. The migrations have been updated to anonymous-class syntax that was introduced in Laravel 8. + 2. Some structural coding changes in the registrar class changed the way we extracted configuration settings in the migration files. + 3. THEREFORE: you will need to upgrade your migrations, especially if you get the following error: `Error: Access to undeclared static property Spatie\Permission\PermissionRegistrar::$pivotPermission` - 4. NOTE: For consistency with `PermissionMiddleware`, the `RoleOrPermissionMiddleware` has switched from only checking permissions provided by this package to using `canAny()` to check against any abilities registered by your application. This may have the effect of granting those other abilities (such as Super Admin) when using the `RoleOrPermissionMiddleware`, which previously would have failed silently. 5. Test suites. If you have tests which manually clear the permission cache and re-register permissions, you no longer need to call `\Spatie\Permission\PermissionRegistrar::class)->registerPermissions();`. Such calls MUST be deleted from your tests. From 38a9942497d8f24b2dc2e1f178256efba9a665cf Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 28 Apr 2023 21:43:26 -0400 Subject: [PATCH 320/648] [Docs] Update v6 upgrade instructions --- docs/upgrading.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/docs/upgrading.md b/docs/upgrading.md index 3ae0b6728..5c74d4dc9 100644 --- a/docs/upgrading.md +++ b/docs/upgrading.md @@ -34,10 +34,7 @@ Be sure to compare your custom models with originals to see what else may have c 2. If you have a custom Role model and (in the rare case that you might) have overridden the `hasPermissionTo()` method in it, you will need to update its method signature to `hasPermissionTo($permission, $guardName = null):bool`. See PR #2380. -3. Migrations have changed in a few ways: - 1. The migrations have been updated to anonymous-class syntax that was introduced in Laravel 8. - 2. Some structural coding changes in the registrar class changed the way we extracted configuration settings in the migration files. - 3. THEREFORE: you will need to upgrade your migrations, especially if you get the following error: +3. Migrations will need to be upgraded. (They have been updated to anonymous-class syntax that was introduced in Laravel 8, AND some structural coding changes in the registrar class changed the way we extracted configuration settings in the migration files.) If you had not customized it from the original then replacing the contents of the file should be straightforward. Usually the only customization is if you've switched to UUIDs or customized MySQL index name lengths. If you get the following error, it means your migration file needs upgrading: `Error: Access to undeclared static property Spatie\Permission\PermissionRegistrar::$pivotPermission` From 10f04e72ba74e3c1e1300459b6761614213af9fe Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 28 Apr 2023 21:46:24 -0400 Subject: [PATCH 321/648] [Docs] Update v6 upgrade instructions --- docs/upgrading.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/upgrading.md b/docs/upgrading.md index 5c74d4dc9..28d431eb8 100644 --- a/docs/upgrading.md +++ b/docs/upgrading.md @@ -25,8 +25,8 @@ ALL upgrades of this package should follow these steps: and/or consult the [Release Notes](https://github.com/spatie/laravel-permission/releases) -## Upgrading to v6 -There are a few breaking-changes when upgrading to v6. +## Upgrading from v5 to v6 +There are a few breaking-changes when upgrading to v6, but most of them won't affect you unless you have been customizing things. 1. If you have overridden the `getPermissionClass()` or `getRoleClass()` methods or have custom Models, you will need to revisit those customizations. See PR #2368 for details. eg: if you have a custom model you will need to make changes, including accessing the model using `$this->permissionClass::` syntax (eg: using `::` instead of `->`) in all the overridden methods that make use of the models. @@ -34,9 +34,8 @@ Be sure to compare your custom models with originals to see what else may have c 2. If you have a custom Role model and (in the rare case that you might) have overridden the `hasPermissionTo()` method in it, you will need to update its method signature to `hasPermissionTo($permission, $guardName = null):bool`. See PR #2380. -3. Migrations will need to be upgraded. (They have been updated to anonymous-class syntax that was introduced in Laravel 8, AND some structural coding changes in the registrar class changed the way we extracted configuration settings in the migration files.) If you had not customized it from the original then replacing the contents of the file should be straightforward. Usually the only customization is if you've switched to UUIDs or customized MySQL index name lengths. If you get the following error, it means your migration file needs upgrading: - - `Error: Access to undeclared static property Spatie\Permission\PermissionRegistrar::$pivotPermission` +3. Migrations will need to be upgraded. (They have been updated to anonymous-class syntax that was introduced in Laravel 8, AND some structural coding changes in the registrar class changed the way we extracted configuration settings in the migration files.) If you had not customized it from the original then replacing the contents of the file should be straightforward. Usually the only customization is if you've switched to UUIDs or customized MySQL index name lengths. +If you get the following error, it means your migration file needs upgrading: `Error: Access to undeclared static property Spatie\Permission\PermissionRegistrar::$pivotPermission` 4. NOTE: For consistency with `PermissionMiddleware`, the `RoleOrPermissionMiddleware` has switched from only checking permissions provided by this package to using `canAny()` to check against any abilities registered by your application. This may have the effect of granting those other abilities (such as Super Admin) when using the `RoleOrPermissionMiddleware`, which previously would have failed silently. From 76079fd81f585129a7fbcc4ad3418027f658230e Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Mon, 1 May 2023 15:24:03 -0400 Subject: [PATCH 322/648] Code formatting --- src/PermissionServiceProvider.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/PermissionServiceProvider.php b/src/PermissionServiceProvider.php index 49580a4de..639e7faa7 100644 --- a/src/PermissionServiceProvider.php +++ b/src/PermissionServiceProvider.php @@ -44,8 +44,7 @@ public function register() 'permission' ); - $this->callAfterResolving('blade.compiler', fn (BladeCompiler $bladeCompiler) => $this->registerBladeExtensions($bladeCompiler) - ); + $this->callAfterResolving('blade.compiler', fn (BladeCompiler $bladeCompiler) => $this->registerBladeExtensions($bladeCompiler)); } protected function offerPublishing(): void From 2df70da68a84e8530abfc99c286286a522a4f347 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Tue, 2 May 2023 16:09:26 -0500 Subject: [PATCH 323/648] [V6] Use attach instead of sync on traits (#2420) * Use attach instead of sync on traits * Test touch on syncRoles, syncPermissions --- src/Traits/HasPermissions.php | 13 +++++++----- src/Traits/HasRoles.php | 13 +++++++----- tests/HasPermissionsTest.php | 16 +++++++++++++-- tests/HasPermissionsWithCustomModelsTest.php | 21 ++++++++++++++++++++ tests/HasRolesTest.php | 16 +++++++++++++-- tests/HasRolesWithCustomModelsTest.php | 21 ++++++++++++++++++++ tests/TestModels/Admin.php | 2 ++ 7 files changed, 88 insertions(+), 14 deletions(-) diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index 174c254fe..8d94ebcd3 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -370,8 +370,7 @@ private function collectPermissions(...$permissions): array $this->ensureModelSharesGuard($permission); - $array[$permission->getKey()] = app(PermissionRegistrar::class)->teams && ! is_a($this, Role::class) ? - [app(PermissionRegistrar::class)->teamsKey => getPermissionsTeamId()] : []; + $array[] = $permission->getKey(); return $array; }, []); @@ -388,19 +387,23 @@ public function givePermissionTo(...$permissions) $permissions = $this->collectPermissions($permissions); $model = $this->getModel(); + $teamPivot = app(PermissionRegistrar::class)->teams && ! is_a($this, Role::class) ? + [app(PermissionRegistrar::class)->teamsKey => getPermissionsTeamId()] : []; if ($model->exists) { - $this->permissions()->sync($permissions, false); + $currentPermissions = $this->permissions()->get()->map(fn ($permission) => $permission->getKey())->toArray(); + + $this->permissions()->attach(array_diff($permissions, $currentPermissions), $teamPivot); $model->unsetRelation('permissions'); } else { $class = \get_class($model); $class::saved( - function ($object) use ($permissions, $model) { + function ($object) use ($permissions, $model, $teamPivot) { if ($model->getKey() != $object->getKey()) { return; } - $model->permissions()->sync($permissions, false); + $model->permissions()->attach($permissions, $teamPivot); $model->unsetRelation('permissions'); } ); diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index 419f5189a..cad1aebd6 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -116,8 +116,7 @@ private function collectRoles(...$roles): array $this->ensureModelSharesGuard($role); - $array[$role->getKey()] = app(PermissionRegistrar::class)->teams && ! is_a($this, Permission::class) ? - [app(PermissionRegistrar::class)->teamsKey => getPermissionsTeamId()] : []; + $array[] = $role->getKey(); return $array; }, []); @@ -134,19 +133,23 @@ public function assignRole(...$roles) $roles = $this->collectRoles($roles); $model = $this->getModel(); + $teamPivot = app(PermissionRegistrar::class)->teams && ! is_a($this, Permission::class) ? + [app(PermissionRegistrar::class)->teamsKey => getPermissionsTeamId()] : []; if ($model->exists) { - $this->roles()->sync($roles, false); + $currentRoles = $this->roles()->get()->map(fn ($role) => $role->getKey())->toArray(); + + $this->roles()->attach(array_diff($roles, $currentRoles), $teamPivot); $model->unsetRelation('roles'); } else { $class = \get_class($model); $class::saved( - function ($object) use ($roles, $model) { + function ($object) use ($roles, $model, $teamPivot) { if ($model->getKey() != $object->getKey()) { return; } - $model->roles()->sync($roles, false); + $model->roles()->attach($roles, $teamPivot); $model->unsetRelation('roles'); } ); diff --git a/tests/HasPermissionsTest.php b/tests/HasPermissionsTest.php index c876294f9..c062325e1 100644 --- a/tests/HasPermissionsTest.php +++ b/tests/HasPermissionsTest.php @@ -572,6 +572,18 @@ public function it_can_sync_permissions_to_a_model_that_is_not_persisted() $this->assertTrue($user->fresh()->hasPermissionTo('edit-articles')); } + /** @test */ + public function it_does_not_run_unnecessary_sqls_when_assigning_new_permissions() + { + $permission2 = app(Permission::class)->where('name', ['edit-news'])->first(); + + DB::enableQueryLog(); + $this->testUser->syncPermissions($this->testUserPermission, $permission2); + DB::disableQueryLog(); + + $this->assertSame(3, count(DB::getQueryLog())); //avoid unnecessary sqls + } + /** @test */ public function calling_givePermissionTo_before_saving_object_doesnt_interfere_with_other_objects() { @@ -591,7 +603,7 @@ public function calling_givePermissionTo_before_saving_object_doesnt_interfere_w $this->assertTrue($user2->fresh()->hasPermissionTo('edit-articles')); $this->assertFalse($user2->fresh()->hasPermissionTo('edit-news')); - $this->assertSame(3, count(DB::getQueryLog())); //avoid unnecessary sync + $this->assertSame(2, count(DB::getQueryLog())); //avoid unnecessary sync } /** @test */ @@ -613,7 +625,7 @@ public function calling_syncPermissions_before_saving_object_doesnt_interfere_wi $this->assertTrue($user2->fresh()->hasPermissionTo('edit-articles')); $this->assertFalse($user2->fresh()->hasPermissionTo('edit-news')); - $this->assertSame(3, count(DB::getQueryLog())); //avoid unnecessary sync + $this->assertSame(2, count(DB::getQueryLog())); //avoid unnecessary sync } /** @test */ diff --git a/tests/HasPermissionsWithCustomModelsTest.php b/tests/HasPermissionsWithCustomModelsTest.php index 7f5ccde75..60aa481e2 100644 --- a/tests/HasPermissionsWithCustomModelsTest.php +++ b/tests/HasPermissionsWithCustomModelsTest.php @@ -2,8 +2,10 @@ namespace Spatie\Permission\Tests; +use Illuminate\Support\Carbon; use Illuminate\Support\Facades\DB; use Spatie\Permission\PermissionRegistrar; +use Spatie\Permission\Tests\TestModels\Admin; use Spatie\Permission\Tests\TestModels\Permission; use Spatie\Permission\Tests\TestModels\User; @@ -129,4 +131,23 @@ public function it_does_detach_roles_and_users_when_force_deleting() $this->assertEquals(0, DB::table(config('permission.table_names.role_has_permissions'))->where('permission_test_id', $permission_id)->count()); $this->assertEquals(0, DB::table(config('permission.table_names.model_has_permissions'))->where('permission_test_id', $permission_id)->count()); } + + /** @test */ + public function it_should_touch_when_assigning_new_permissions() + { + Carbon::setTestNow('2021-07-19 10:13:14'); + + $user = Admin::create(['email' => 'user1@test.com']); + $permission1 = Permission::create(['name' => 'edit-news', 'guard_name' => 'admin']); + $permission2 = Permission::create(['name' => 'edit-blog', 'guard_name' => 'admin']); + + $this->assertSame('2021-07-19 10:13:14', $permission1->updated_at->format('Y-m-d H:i:s')); + + Carbon::setTestNow('2021-07-20 19:13:14'); + + $user->syncPermissions([$permission1->getKey(), $permission2->getKey()]); + + $this->assertSame('2021-07-20 19:13:14', $permission1->refresh()->updated_at->format('Y-m-d H:i:s')); + $this->assertSame('2021-07-20 19:13:14', $permission2->refresh()->updated_at->format('Y-m-d H:i:s')); + } } diff --git a/tests/HasRolesTest.php b/tests/HasRolesTest.php index b984aeb4d..ed04757ed 100644 --- a/tests/HasRolesTest.php +++ b/tests/HasRolesTest.php @@ -284,6 +284,18 @@ public function it_will_sync_roles_to_a_model_that_is_not_persisted() $this->assertTrue($user->hasRole($this->testUserRole)); } + /** @test */ + public function it_does_not_run_unnecessary_sqls_when_assigning_new_roles() + { + $role2 = app(Role::class)->where('name', ['testRole2'])->first(); + + DB::enableQueryLog(); + $this->testUser->syncRoles($this->testUserRole, $role2); + DB::disableQueryLog(); + + $this->assertSame(3, count(DB::getQueryLog())); //avoid unnecessary sqls + } + /** @test */ public function calling_syncRoles_before_saving_object_doesnt_interfere_with_other_objects() { @@ -303,7 +315,7 @@ public function calling_syncRoles_before_saving_object_doesnt_interfere_with_oth $this->assertTrue($user2->fresh()->hasRole('testRole2')); $this->assertFalse($user2->fresh()->hasRole('testRole')); - $this->assertSame(3, count(DB::getQueryLog())); //avoid unnecessary sync + $this->assertSame(2, count(DB::getQueryLog())); //avoid unnecessary sync } /** @test */ @@ -325,7 +337,7 @@ public function calling_assignRole_before_saving_object_doesnt_interfere_with_ot $this->assertTrue($admin_user->fresh()->hasRole('testRole2')); $this->assertFalse($admin_user->fresh()->hasRole('testRole')); - $this->assertSame(3, count(DB::getQueryLog())); //avoid unnecessary sync + $this->assertSame(2, count(DB::getQueryLog())); //avoid unnecessary sync } /** @test */ diff --git a/tests/HasRolesWithCustomModelsTest.php b/tests/HasRolesWithCustomModelsTest.php index 659f04db7..b306676af 100644 --- a/tests/HasRolesWithCustomModelsTest.php +++ b/tests/HasRolesWithCustomModelsTest.php @@ -2,7 +2,9 @@ namespace Spatie\Permission\Tests; +use Illuminate\Support\Carbon; use Illuminate\Support\Facades\DB; +use Spatie\Permission\Tests\TestModels\Admin; use Spatie\Permission\Tests\TestModels\Role; class HasRolesWithCustomModelsTest extends HasRolesTest @@ -79,4 +81,23 @@ public function it_does_detach_permissions_and_users_when_force_deleting() $this->assertEquals(0, DB::table(config('permission.table_names.role_has_permissions'))->where('role_test_id', $role_id)->count()); $this->assertEquals(0, DB::table(config('permission.table_names.model_has_roles'))->where('role_test_id', $role_id)->count()); } + + /** @test */ + public function it_should_touch_when_assigning_new_roles() + { + Carbon::setTestNow('2021-07-19 10:13:14'); + + $user = Admin::create(['email' => 'user1@test.com']); + $role1 = app(Role::class)->create(['name' => 'testRoleInWebGuard', 'guard_name' => 'admin']); + $role2 = app(Role::class)->create(['name' => 'testRoleInWebGuard1', 'guard_name' => 'admin']); + + $this->assertSame('2021-07-19 10:13:14', $role1->updated_at->format('Y-m-d H:i:s')); + + Carbon::setTestNow('2021-07-20 19:13:14'); + + $user->syncRoles([$role1->getKey(), $role2->getKey()]); + + $this->assertSame('2021-07-20 19:13:14', $role1->refresh()->updated_at->format('Y-m-d H:i:s')); + $this->assertSame('2021-07-20 19:13:14', $role2->refresh()->updated_at->format('Y-m-d H:i:s')); + } } diff --git a/tests/TestModels/Admin.php b/tests/TestModels/Admin.php index 5b6334a98..c2aa1c85f 100644 --- a/tests/TestModels/Admin.php +++ b/tests/TestModels/Admin.php @@ -5,4 +5,6 @@ class Admin extends User { protected $table = 'admins'; + + protected $touches = ['roles', 'permissions']; } From e64d2f9fd510dc10174d66d170f1eb09efa45530 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Tue, 2 May 2023 17:49:48 -0500 Subject: [PATCH 324/648] fewer sqls in syncRoles, syncPermissions (#2423) --- src/Traits/HasPermissions.php | 10 ++++++---- src/Traits/HasRoles.php | 10 ++++++---- tests/HasPermissionsTest.php | 2 +- tests/HasRolesTest.php | 6 +++++- 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index 8d94ebcd3..6c821ffdc 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -391,7 +391,7 @@ public function givePermissionTo(...$permissions) [app(PermissionRegistrar::class)->teamsKey => getPermissionsTeamId()] : []; if ($model->exists) { - $currentPermissions = $this->permissions()->get()->map(fn ($permission) => $permission->getKey())->toArray(); + $currentPermissions = $this->permissions->map(fn ($permission) => $permission->getKey())->toArray(); $this->permissions()->attach(array_diff($permissions, $currentPermissions), $teamPivot); $model->unsetRelation('permissions'); @@ -424,9 +424,11 @@ function ($object) use ($permissions, $model, $teamPivot) { */ public function syncPermissions(...$permissions) { - $this->collectPermissions($permissions); - - $this->permissions()->detach(); + if ($this->getModel()->exists) { + $this->collectPermissions($permissions); + $this->permissions()->detach(); + $this->setRelation('permissions', collect()); + } return $this->givePermissionTo($permissions); } diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index cad1aebd6..ab637a795 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -137,7 +137,7 @@ public function assignRole(...$roles) [app(PermissionRegistrar::class)->teamsKey => getPermissionsTeamId()] : []; if ($model->exists) { - $currentRoles = $this->roles()->get()->map(fn ($role) => $role->getKey())->toArray(); + $currentRoles = $this->roles->map(fn ($role) => $role->getKey())->toArray(); $this->roles()->attach(array_diff($roles, $currentRoles), $teamPivot); $model->unsetRelation('roles'); @@ -188,9 +188,11 @@ public function removeRole($role) */ public function syncRoles(...$roles) { - $this->collectRoles($roles); - - $this->roles()->detach(); + if ($this->getModel()->exists) { + $this->collectRoles($roles); + $this->roles()->detach(); + $this->setRelation('roles', collect()); + } return $this->assignRole($roles); } diff --git a/tests/HasPermissionsTest.php b/tests/HasPermissionsTest.php index c062325e1..e5c9a4c85 100644 --- a/tests/HasPermissionsTest.php +++ b/tests/HasPermissionsTest.php @@ -581,7 +581,7 @@ public function it_does_not_run_unnecessary_sqls_when_assigning_new_permissions( $this->testUser->syncPermissions($this->testUserPermission, $permission2); DB::disableQueryLog(); - $this->assertSame(3, count(DB::getQueryLog())); //avoid unnecessary sqls + $this->assertSame(2, count(DB::getQueryLog())); //avoid unnecessary sqls } /** @test */ diff --git a/tests/HasRolesTest.php b/tests/HasRolesTest.php index ed04757ed..1f86ff47d 100644 --- a/tests/HasRolesTest.php +++ b/tests/HasRolesTest.php @@ -282,6 +282,10 @@ public function it_will_sync_roles_to_a_model_that_is_not_persisted() $user->save(); $this->assertTrue($user->hasRole($this->testUserRole)); + + $user->syncRoles([$this->testUserRole]); + $this->assertTrue($user->hasRole($this->testUserRole)); + $this->assertTrue($user->fresh()->hasRole($this->testUserRole)); } /** @test */ @@ -293,7 +297,7 @@ public function it_does_not_run_unnecessary_sqls_when_assigning_new_roles() $this->testUser->syncRoles($this->testUserRole, $role2); DB::disableQueryLog(); - $this->assertSame(3, count(DB::getQueryLog())); //avoid unnecessary sqls + $this->assertSame(2, count(DB::getQueryLog())); //avoid unnecessary sqls } /** @test */ From 587ba9cd2c91c4cb4f15f862d9538585f84831dc Mon Sep 17 00:00:00 2001 From: Jesper Noordsij <45041769+jnoordsij@users.noreply.github.com> Date: Sun, 7 May 2023 20:21:11 +0200 Subject: [PATCH 325/648] Add middleware using static method (#2424) * Allow static creation of middlewares with using method * Add tests for creation of middleware with static using method * Add example about static middleware methods to docs --- docs/basic-usage/middleware.md | 30 ++++++++++++++++++- src/Middlewares/PermissionMiddleware.php | 15 ++++++++++ src/Middlewares/RoleMiddleware.php | 15 ++++++++++ .../RoleOrPermissionMiddleware.php | 15 ++++++++++ tests/PermissionMiddlewareTest.php | 17 +++++++++++ tests/RoleMiddlewareTest.php | 17 +++++++++++ tests/RoleOrPermissionMiddlewareTest.php | 17 +++++++++++ tests/TestCase.php | 22 +++++++------- 8 files changed, 136 insertions(+), 12 deletions(-) diff --git a/docs/basic-usage/middleware.md b/docs/basic-usage/middleware.md index 6d2564728..48a5ceee1 100644 --- a/docs/basic-usage/middleware.md +++ b/docs/basic-usage/middleware.md @@ -13,9 +13,18 @@ Route::group(['middleware' => ['can:publish articles']], function () { }); ``` +In Laravel v10.9 and up, you can also call this middleware with a static method. + +```php +Route::group(['middleware' => [\Illuminate\Auth\Middleware\Authorize::using('publish articles')]], function () { + // +}); +``` + ## Package Middleware -This package comes with `RoleMiddleware`, `PermissionMiddleware` and `RoleOrPermissionMiddleware` middleware. You can add them inside your `app/Http/Kernel.php` file. +This package comes with `RoleMiddleware`, `PermissionMiddleware` and `RoleOrPermissionMiddleware` middleware. +You can add them inside your `app/Http/Kernel.php` file to be able to use them through aliases. Note the property name difference between Laravel 10 and older versions of Laravel: @@ -88,3 +97,22 @@ public function __construct() ``` (You can use Laravel's Model Policy feature with your controller methods. See the Model Policies section of these docs.) + +## Use middleware static methods + +All of the middlewares can also be applied by calling the static `using` method, +which accepts either a `|`-separated string or an array as input. + +```php +Route::group(['middleware' => [\Spatie\Permission\Middlewares\RoleMiddleware::using('super-admin')]], function () { + // +}); + +Route::group(['middleware' => [\Spatie\Permission\Middlewares\PermissionMiddleware::using('publish articles|edit articles')]], function () { + // +}); + +Route::group(['middleware' => [\Spatie\Permission\Middlewares\RoleOrPermissionMiddleware::using(['super-admin', 'edit articles'])]], function () { + // +}); +``` diff --git a/src/Middlewares/PermissionMiddleware.php b/src/Middlewares/PermissionMiddleware.php index 49793f5ce..ca37cb672 100644 --- a/src/Middlewares/PermissionMiddleware.php +++ b/src/Middlewares/PermissionMiddleware.php @@ -32,4 +32,19 @@ public function handle($request, Closure $next, $permission, $guard = null) return $next($request); } + + /** + * Specify the permission and guard for the middleware. + * + * @param array|string $permission + * @param string|null $guard + * @return string + */ + public static function using($permission, $guard = null) + { + $permissionString = is_string($permission) ? $permission : implode('|', $permission); + $args = is_null($guard) ? $permissionString : "$permissionString,$guard"; + + return static::class.':'.$args; + } } diff --git a/src/Middlewares/RoleMiddleware.php b/src/Middlewares/RoleMiddleware.php index 4e81f0073..ae9232cd5 100644 --- a/src/Middlewares/RoleMiddleware.php +++ b/src/Middlewares/RoleMiddleware.php @@ -32,4 +32,19 @@ public function handle($request, Closure $next, $role, $guard = null) return $next($request); } + + /** + * Specify the role and guard for the middleware. + * + * @param array|string $role + * @param string|null $guard + * @return string + */ + public static function using($role, $guard = null) + { + $roleString = is_string($role) ? $role : implode('|', $role); + $args = is_null($guard) ? $roleString : "$roleString,$guard"; + + return static::class.':'.$args; + } } diff --git a/src/Middlewares/RoleOrPermissionMiddleware.php b/src/Middlewares/RoleOrPermissionMiddleware.php index 5a9aaa341..52675a43f 100644 --- a/src/Middlewares/RoleOrPermissionMiddleware.php +++ b/src/Middlewares/RoleOrPermissionMiddleware.php @@ -31,4 +31,19 @@ public function handle($request, Closure $next, $roleOrPermission, $guard = null return $next($request); } + + /** + * Specify the role or permission and guard for the middleware. + * + * @param array|string $roleOrPermission + * @param string|null $guard + * @return string + */ + public static function using($roleOrPermission, $guard = null) + { + $roleOrPermissionString = is_string($roleOrPermission) ? $roleOrPermission : implode('|', $roleOrPermission); + $args = is_null($guard) ? $roleOrPermissionString : "$roleOrPermissionString,$guard"; + + return static::class.':'.$args; + } } diff --git a/tests/PermissionMiddlewareTest.php b/tests/PermissionMiddlewareTest.php index 39a57c48b..d8831c309 100644 --- a/tests/PermissionMiddlewareTest.php +++ b/tests/PermissionMiddlewareTest.php @@ -254,4 +254,21 @@ public function user_can_access_permission_with_guard_admin_while_login_using_ad $this->runMiddleware($this->permissionMiddleware, 'admin-permission', 'admin') ); } + + /** @test */ + public function the_middleware_can_be_created_with_static_using_method() + { + $this->assertSame( + 'Spatie\Permission\Middlewares\PermissionMiddleware:edit-articles', + PermissionMiddleware::using('edit-articles') + ); + $this->assertEquals( + 'Spatie\Permission\Middlewares\PermissionMiddleware:edit-articles,my-guard', + PermissionMiddleware::using('edit-articles', 'my-guard') + ); + $this->assertEquals( + 'Spatie\Permission\Middlewares\PermissionMiddleware:edit-articles|edit-news', + PermissionMiddleware::using(['edit-articles', 'edit-news']) + ); + } } diff --git a/tests/RoleMiddlewareTest.php b/tests/RoleMiddlewareTest.php index 7d532a004..a28c2de07 100644 --- a/tests/RoleMiddlewareTest.php +++ b/tests/RoleMiddlewareTest.php @@ -204,4 +204,21 @@ public function user_can_access_role_with_guard_admin_while_login_using_admin_gu $this->runMiddleware($this->roleMiddleware, 'testAdminRole', 'admin') ); } + + /** @test */ + public function the_middleware_can_be_created_with_static_using_method() + { + $this->assertSame( + 'Spatie\Permission\Middlewares\RoleMiddleware:testAdminRole', + RoleMiddleware::using('testAdminRole') + ); + $this->assertEquals( + 'Spatie\Permission\Middlewares\RoleMiddleware:testAdminRole,my-guard', + RoleMiddleware::using('testAdminRole', 'my-guard') + ); + $this->assertEquals( + 'Spatie\Permission\Middlewares\RoleMiddleware:testAdminRole|anotherRole', + RoleMiddleware::using(['testAdminRole', 'anotherRole']) + ); + } } diff --git a/tests/RoleOrPermissionMiddlewareTest.php b/tests/RoleOrPermissionMiddlewareTest.php index e9f628932..24367e51f 100644 --- a/tests/RoleOrPermissionMiddlewareTest.php +++ b/tests/RoleOrPermissionMiddlewareTest.php @@ -194,4 +194,21 @@ public function the_required_permissions_or_roles_can_be_displayed_in_the_except $this->assertStringEndsWith('Necessary roles or permissions are some-permission, some-role', $message); } + + /** @test */ + public function the_middleware_can_be_created_with_static_using_method() + { + $this->assertSame( + 'Spatie\Permission\Middlewares\RoleOrPermissionMiddleware:edit-articles', + RoleOrPermissionMiddleware::using('edit-articles') + ); + $this->assertEquals( + 'Spatie\Permission\Middlewares\RoleOrPermissionMiddleware:edit-articles,my-guard', + RoleOrPermissionMiddleware::using('edit-articles', 'my-guard') + ); + $this->assertEquals( + 'Spatie\Permission\Middlewares\RoleOrPermissionMiddleware:edit-articles|testAdminRole', + RoleOrPermissionMiddleware::using(['edit-articles', 'testAdminRole']) + ); + } } diff --git a/tests/TestCase.php b/tests/TestCase.php index 456964ec1..3fe775b08 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -243,15 +243,15 @@ public function getLastRouteMiddlewareFromRouter($router) return last($router->getRoutes()->get())->middleware(); } - public function getRouter() - { - return app('router'); - } - - public function getRouteResponse() - { - return function () { - return (new Response())->setContent(''); - }; - } + public function getRouter() + { + return app('router'); + } + + public function getRouteResponse() + { + return function () { + return (new Response())->setContent(''); + }; + } } From 563794f2309fee43f8d56d993bd0085655a0889b Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Sun, 7 May 2023 18:43:39 -0400 Subject: [PATCH 326/648] type declaration --- src/WildcardPermission.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/WildcardPermission.php b/src/WildcardPermission.php index f8946a5c5..50a741dd8 100644 --- a/src/WildcardPermission.php +++ b/src/WildcardPermission.php @@ -11,10 +11,10 @@ class WildcardPermission implements Wildcard /** @var string */ public const WILDCARD_TOKEN = '*'; - /** @var string */ + /** @var non-empty-string */ public const PART_DELIMITER = '.'; - /** @var string */ + /** @var non-empty-string */ public const SUBPART_DELIMITER = ','; /** @var string */ From 3d40339fb96b8997be94c6d850cbbbe1553e416d Mon Sep 17 00:00:00 2001 From: erikn69 Date: Fri, 26 May 2023 21:58:34 -0500 Subject: [PATCH 327/648] Update PHPDocs for IDE autocompletion (#2437) --- src/Models/Permission.php | 10 ++++++++++ src/Models/Role.php | 12 +++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/Models/Permission.php b/src/Models/Permission.php index f5edcb90f..21ce630ea 100644 --- a/src/Models/Permission.php +++ b/src/Models/Permission.php @@ -34,6 +34,11 @@ public function __construct(array $attributes = []) $this->table = config('permission.table_names.permissions') ?: parent::getTable(); } + /** + * @return PermissionContract|Permission + * + * @throws PermissionAlreadyExists + */ public static function create(array $attributes = []) { $attributes['guard_name'] = $attributes['guard_name'] ?? Guard::getDefaultName(static::class); @@ -78,6 +83,7 @@ public function users(): BelongsToMany * Find a permission by its name (and optionally guardName). * * @param string|null $guardName + * @return PermissionContract|Permission * * @throws PermissionDoesNotExist */ @@ -97,6 +103,7 @@ public static function findByName(string $name, $guardName = null): PermissionCo * * @param int|string $id * @param string|null $guardName + * @return PermissionContract|Permission * * @throws PermissionDoesNotExist */ @@ -116,6 +123,7 @@ public static function findById($id, $guardName = null): PermissionContract * Find or create permission by its name (and optionally guardName). * * @param string|null $guardName + * @return PermissionContract|Permission */ public static function findOrCreate(string $name, $guardName = null): PermissionContract { @@ -141,6 +149,8 @@ protected static function getPermissions(array $params = [], bool $onlyOne = fal /** * Get the current cached first permission. + * + * @return PermissionContract|Permission|null */ protected static function getPermission(array $params = []): ?PermissionContract { diff --git a/src/Models/Role.php b/src/Models/Role.php index 7199504d1..025147864 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -35,6 +35,11 @@ public function __construct(array $attributes = []) $this->table = config('permission.table_names.roles') ?: parent::getTable(); } + /** + * @return RoleContract|Role + * + * @throws RoleAlreadyExists + */ public static function create(array $attributes = []) { $attributes['guard_name'] = $attributes['guard_name'] ?? Guard::getDefaultName(static::class); @@ -143,7 +148,12 @@ public static function findOrCreate(string $name, $guardName = null): RoleContra return $role; } - protected static function findByParam(array $params = []) + /** + * Finds a role based on an array of parameters. + * + * @return RoleContract|Role|null + */ + protected static function findByParam(array $params = []): ?RoleContract { $query = static::query(); From def2e7382650ba7dd78d5aa2b253ff934831e398 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 May 2023 05:59:44 +0000 Subject: [PATCH 328/648] Bump aglipanci/laravel-pint-action from 2.2.0 to 2.3.0 Bumps [aglipanci/laravel-pint-action](https://github.com/aglipanci/laravel-pint-action) from 2.2.0 to 2.3.0. - [Release notes](https://github.com/aglipanci/laravel-pint-action/releases) - [Commits](https://github.com/aglipanci/laravel-pint-action/compare/2.2.0...2.3.0) --- updated-dependencies: - dependency-name: aglipanci/laravel-pint-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/fix-php-code-style-issues.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/fix-php-code-style-issues.yml b/.github/workflows/fix-php-code-style-issues.yml index f76938275..49d14b77e 100644 --- a/.github/workflows/fix-php-code-style-issues.yml +++ b/.github/workflows/fix-php-code-style-issues.yml @@ -16,7 +16,7 @@ jobs: ref: ${{ github.head_ref }} - name: Fix PHP code style issues - uses: aglipanci/laravel-pint-action@2.2.0 + uses: aglipanci/laravel-pint-action@2.3.0 - name: Commit changes uses: stefanzweifel/git-auto-commit-action@v4 From badd36c57d77b695d9aec2a3435dc17804139a10 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 May 2023 05:59:49 +0000 Subject: [PATCH 329/648] Bump dependabot/fetch-metadata from 1.4.0 to 1.5.1 Bumps [dependabot/fetch-metadata](https://github.com/dependabot/fetch-metadata) from 1.4.0 to 1.5.1. - [Release notes](https://github.com/dependabot/fetch-metadata/releases) - [Commits](https://github.com/dependabot/fetch-metadata/compare/v1.4.0...v1.5.1) --- updated-dependencies: - dependency-name: dependabot/fetch-metadata dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/dependabot-auto-merge.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml index 4c8e4c314..3f60ce0f6 100644 --- a/.github/workflows/dependabot-auto-merge.yml +++ b/.github/workflows/dependabot-auto-merge.yml @@ -13,7 +13,7 @@ jobs: - name: Dependabot metadata id: metadata - uses: dependabot/fetch-metadata@v1.4.0 + uses: dependabot/fetch-metadata@v1.5.1 with: github-token: "${{ secrets.GITHUB_TOKEN }}" compat-lookup: true From 6e1d309e6c6f0c109a66190be5f296927fae0bdb Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Thu, 15 Jun 2023 13:34:22 -0400 Subject: [PATCH 330/648] Note the requirement for foreign-key support --- docs/prerequisites.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/prerequisites.md b/docs/prerequisites.md index b59d4cc2d..beb6c87a8 100644 --- a/docs/prerequisites.md +++ b/docs/prerequisites.md @@ -7,6 +7,10 @@ weight: 3 This package can be used in Laravel 6 or higher. Check the "Installing on Laravel" page for package versions compatible with various Laravel versions. +## Database with foreign-key relationship capability + +This package depends on cascading delete rules to enforce database integrity, so foreign-key support is required by your database engine. + ## User Model / Contract/Interface This package uses Laravel's Gate layer to provide Authorization capabilities. From c382b0dda7906e7c3abbda226bfb7f66fb1a39c9 Mon Sep 17 00:00:00 2001 From: Dan Harrin Date: Sat, 17 Jun 2023 18:12:47 +0100 Subject: [PATCH 331/648] fix: Wildcard permissions algorithm performance (#2445) * New wildcard algorithm * Fix issue with multipart wildcard captures * Refactor to use existing WildcardPermission class --- src/Contracts/Wildcard.php | 11 +-- src/PermissionRegistrar.php | 26 +++++++ src/Traits/HasPermissions.php | 38 ++++++----- src/WildcardPermission.php | 125 ++++++++++++++++++---------------- 4 files changed, 122 insertions(+), 78 deletions(-) diff --git a/src/Contracts/Wildcard.php b/src/Contracts/Wildcard.php index d2aabd8f8..68f15be44 100644 --- a/src/Contracts/Wildcard.php +++ b/src/Contracts/Wildcard.php @@ -2,10 +2,13 @@ namespace Spatie\Permission\Contracts; +use Illuminate\Database\Eloquent\Model; + interface Wildcard { - /** - * @param string|Wildcard $permission - */ - public function implies($permission): bool; + public function __construct(Model $record); + + public function getIndex(): array; + + public function implies(string $permission, string $guardName, array $index): bool; } diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index 6e1574f63..be1a0aa12 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -8,8 +8,11 @@ use Illuminate\Contracts\Cache\Repository; use Illuminate\Contracts\Cache\Store; use Illuminate\Database\Eloquent\Collection; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Str; use Spatie\Permission\Contracts\Permission; use Spatie\Permission\Contracts\Role; +use Spatie\Permission\Exceptions\WildcardPermissionNotProperlyFormatted; class PermissionRegistrar { @@ -46,6 +49,8 @@ class PermissionRegistrar private array $except = []; + private array $wildcardPermissionsIndex = []; + /** * PermissionRegistrar constructor. */ @@ -134,10 +139,22 @@ public function registerPermissions(Gate $gate): bool public function forgetCachedPermissions() { $this->permissions = null; + $this->forgetWildcardPermissionIndex(); return $this->cache->forget($this->cacheKey); } + public function forgetWildcardPermissionIndex(?Model $record = null): void + { + if ($record) { + unset($this->wildcardPermissionsIndex[get_class($record)][$record->getKey()]); + + return; + } + + $this->wildcardPermissionsIndex = []; + } + /** * Clear already loaded permissions collection. * This is only intended to be called by the PermissionServiceProvider on boot, @@ -148,6 +165,15 @@ public function clearPermissionsCollection(): void $this->permissions = null; } + public function getWildcardPermissionIndex(Model $record): array + { + if (isset($this->wildcardPermissionsIndex[get_class($record)][$record->getKey()])) { + return $this->wildcardPermissionsIndex[get_class($record)][$record->getKey()]; + } + + return $this->wildcardPermissionsIndex[get_class($record)][$record->getKey()] = app($record->getWildcardClass(), ['record' => $record])->getIndex(); + } + /** * @deprecated * diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index 6c821ffdc..b20953246 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -6,6 +6,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Support\Arr; use Illuminate\Support\Collection; +use Illuminate\Support\Str; use Spatie\Permission\Contracts\Permission; use Spatie\Permission\Contracts\Role; use Spatie\Permission\Contracts\Wildcard; @@ -13,9 +14,11 @@ use Spatie\Permission\Exceptions\PermissionDoesNotExist; use Spatie\Permission\Exceptions\WildcardPermissionInvalidArgument; use Spatie\Permission\Exceptions\WildcardPermissionNotImplementsContract; +use Spatie\Permission\Exceptions\WildcardPermissionNotProperlyFormatted; use Spatie\Permission\Guard; use Spatie\Permission\PermissionRegistrar; use Spatie\Permission\WildcardPermission; +use Spatie\Permission\WildcardPermissionIndexChecker; trait HasPermissions { @@ -23,6 +26,8 @@ trait HasPermissions private ?string $wildcardClass = null; + private array $wildcardPermissionsIndex; + public static function bootHasPermissions() { static::deleting(function ($model) { @@ -51,7 +56,7 @@ public function getPermissionClass(): string return $this->permissionClass; } - protected function getWildcardClass() + public function getWildcardClass() { if (! is_null($this->wildcardClass)) { return $this->wildcardClass; @@ -226,21 +231,11 @@ protected function hasWildcardPermission($permission, $guardName = null): bool throw WildcardPermissionInvalidArgument::create(); } - $WildcardPermissionClass = $this->getWildcardClass(); - - foreach ($this->getAllPermissions() as $userPermission) { - if ($guardName !== $userPermission->guard_name) { - continue; - } - - $userPermission = new $WildcardPermissionClass($userPermission->name); - - if ($userPermission->implies($permission)) { - return true; - } - } - - return false; + return app($this->getWildcardClass(), ['record' => $this])->implies( + $permission, + $guardName, + app(PermissionRegistrar::class)->getWildcardPermissionIndex($this), + ); } /** @@ -413,9 +408,18 @@ function ($object) use ($permissions, $model, $teamPivot) { $this->forgetCachedPermissions(); } + $this->forgetWildcardPermissionIndex(); + return $this; } + public function forgetWildcardPermissionIndex(): void + { + app(PermissionRegistrar::class)->forgetWildcardPermissionIndex( + is_a($this, Role::class) ? null : $this, + ); + } + /** * Remove all current permissions and set the given ones. * @@ -447,6 +451,8 @@ public function revokePermissionTo($permission) $this->forgetCachedPermissions(); } + $this->forgetWildcardPermissionIndex(); + $this->unsetRelation('permissions'); return $this; diff --git a/src/WildcardPermission.php b/src/WildcardPermission.php index 50a741dd8..2c14f868c 100644 --- a/src/WildcardPermission.php +++ b/src/WildcardPermission.php @@ -2,7 +2,9 @@ namespace Spatie\Permission; +use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Collection; +use Illuminate\Support\Str; use Spatie\Permission\Contracts\Wildcard; use Spatie\Permission\Exceptions\WildcardPermissionNotProperlyFormatted; @@ -17,94 +19,101 @@ class WildcardPermission implements Wildcard /** @var non-empty-string */ public const SUBPART_DELIMITER = ','; - /** @var string */ - protected $permission; + protected Model $record; - /** @var Collection */ - protected $parts; + public function __construct(Model $record) + { + $this->record = $record; + } - public function __construct(string $permission) + public function getIndex(): array { - $this->permission = $permission; - $this->parts = collect(); + $index = []; + + foreach ($this->record->getAllPermissions() as $permission) { + $index[$permission->guard_name] = $this->buildIndex( + $index[$permission->guard_name] ?? [], + explode(static::PART_DELIMITER, $permission->name), + $permission->name, + ); + } - $this->setParts(); + return $index; } - /** - * @param string|WildcardPermission $permission - */ - public function implies($permission): bool + protected function buildIndex(array $index, array $parts, string $permission): array { - if (is_string($permission)) { - $permission = new static($permission); - } + if (empty($parts)) { + $index[null] = true; - $otherParts = $permission->getParts(); + return $index; + } - $i = 0; - $partsCount = $this->getParts()->count(); - foreach ($otherParts as $otherPart) { - if ($partsCount - 1 < $i) { - return true; - } + $part = array_shift($parts); - if (! $this->parts->get($i)->contains(static::WILDCARD_TOKEN) - && ! $this->containsAll($this->parts->get($i), $otherPart)) { - return false; - } + if (blank($part)) { + throw WildcardPermissionNotProperlyFormatted::create($permission); + } - $i++; + if (! Str::contains($part, static::SUBPART_DELIMITER)) { + $index[$part] = $this->buildIndex( + $index[$part] ?? [], + $parts, + $permission, + ); } - for ($i; $i < $partsCount; $i++) { - if (! $this->parts->get($i)->contains(static::WILDCARD_TOKEN)) { - return false; + $subParts = explode(static::SUBPART_DELIMITER, $part); + + foreach ($subParts as $subPart) { + if (blank($subPart)) { + throw WildcardPermissionNotProperlyFormatted::create($permission); } + + $index[$subPart] = $this->buildIndex( + $index[$subPart] ?? [], + $parts, + $permission, + ); } - return true; + return $index; } - protected function containsAll(Collection $part, Collection $otherPart): bool + public function implies(string $permission, string $guardName, array $index): bool { - foreach ($otherPart->toArray() as $item) { - if (! $part->contains($item)) { - return false; - } + if (! array_key_exists($guardName, $index)) { + return false; } - return true; - } + $permission = explode(static::PART_DELIMITER, $permission); - public function getParts(): Collection - { - return $this->parts; + return $this->checkIndex($permission, $index[$guardName]); } - /** - * Sets the different parts and subparts from permission string. - */ - protected function setParts(): void + protected function checkIndex(array $permission, array $index): bool { - if (empty($this->permission) || $this->permission == null) { - throw WildcardPermissionNotProperlyFormatted::create($this->permission); + if (array_key_exists(strval(null), $index)) { + return true; } - $parts = collect(explode(static::PART_DELIMITER, $this->permission)); - - $parts->each(function ($item, $key) { - $subParts = collect(explode(static::SUBPART_DELIMITER, $item)); + if (empty($permission)) { + return false; + } - if ($subParts->isEmpty() || $subParts->contains('')) { - throw WildcardPermissionNotProperlyFormatted::create($this->permission); - } + $firstPermission = array_shift($permission); - $this->parts->add($subParts); - }); + if ( + array_key_exists($firstPermission, $index) && + $this->checkIndex($permission, $index[$firstPermission]) + ) { + return true; + } - if ($this->parts->isEmpty()) { - throw WildcardPermissionNotProperlyFormatted::create($this->permission); + if (array_key_exists(static::WILDCARD_TOKEN, $index)) { + return $this->checkIndex($permission, $index[static::WILDCARD_TOKEN]); } + + return false; } } From c73bac8103d72355ed0c12b101e8a4f59ef70fa3 Mon Sep 17 00:00:00 2001 From: drbyte Date: Sat, 17 Jun 2023 17:13:21 +0000 Subject: [PATCH 332/648] Fix styling --- src/PermissionRegistrar.php | 2 -- src/Traits/HasPermissions.php | 3 --- src/WildcardPermission.php | 1 - 3 files changed, 6 deletions(-) diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index be1a0aa12..d94530a2a 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -9,10 +9,8 @@ use Illuminate\Contracts\Cache\Store; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Model; -use Illuminate\Support\Str; use Spatie\Permission\Contracts\Permission; use Spatie\Permission\Contracts\Role; -use Spatie\Permission\Exceptions\WildcardPermissionNotProperlyFormatted; class PermissionRegistrar { diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index b20953246..6b6c8c91e 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -6,7 +6,6 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Support\Arr; use Illuminate\Support\Collection; -use Illuminate\Support\Str; use Spatie\Permission\Contracts\Permission; use Spatie\Permission\Contracts\Role; use Spatie\Permission\Contracts\Wildcard; @@ -14,11 +13,9 @@ use Spatie\Permission\Exceptions\PermissionDoesNotExist; use Spatie\Permission\Exceptions\WildcardPermissionInvalidArgument; use Spatie\Permission\Exceptions\WildcardPermissionNotImplementsContract; -use Spatie\Permission\Exceptions\WildcardPermissionNotProperlyFormatted; use Spatie\Permission\Guard; use Spatie\Permission\PermissionRegistrar; use Spatie\Permission\WildcardPermission; -use Spatie\Permission\WildcardPermissionIndexChecker; trait HasPermissions { diff --git a/src/WildcardPermission.php b/src/WildcardPermission.php index 2c14f868c..a4287187f 100644 --- a/src/WildcardPermission.php +++ b/src/WildcardPermission.php @@ -3,7 +3,6 @@ namespace Spatie\Permission; use Illuminate\Database\Eloquent\Model; -use Illuminate\Support\Collection; use Illuminate\Support\Str; use Spatie\Permission\Contracts\Wildcard; use Spatie\Permission\Exceptions\WildcardPermissionNotProperlyFormatted; From 7e16464d67899872e235a8b7ef6d51352389ca20 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Sat, 17 Jun 2023 14:45:03 -0400 Subject: [PATCH 333/648] Update foreign-key relationship details --- docs/prerequisites.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/prerequisites.md b/docs/prerequisites.md index beb6c87a8..fd50f9929 100644 --- a/docs/prerequisites.md +++ b/docs/prerequisites.md @@ -7,10 +7,6 @@ weight: 3 This package can be used in Laravel 6 or higher. Check the "Installing on Laravel" page for package versions compatible with various Laravel versions. -## Database with foreign-key relationship capability - -This package depends on cascading delete rules to enforce database integrity, so foreign-key support is required by your database engine. - ## User Model / Contract/Interface This package uses Laravel's Gate layer to provide Authorization capabilities. @@ -55,3 +51,8 @@ Thus in your AppServiceProvider you will need to set `Schema::defaultStringLengt This package expects the primary key of your `User` model to be an auto-incrementing `int`. If it is not, you may need to modify the `create_permission_tables` migration and/or modify the default configuration. See [https://spatie.be/docs/laravel-permission/advanced-usage/uuid](https://spatie.be/docs/laravel-permission/advanced-usage/uuid) for more information. +## Database foreign-key relationship support + +To enforce database integrity, this package uses foreign-key relationships with cascading deletes. This prevents data mismatch situations if database records are manipulated outside of this package. If your database engine does not support foreign-key relationships, then you will have to alter the migration files accordingly. + +This package does its own detaching of pivot records when deletes are called using provided package methods, so if your database does not support foreign keys then as long as you only use method calls provided by this package for managing related records, there should not be data integrity issues. From adb5923ef5168614f8e16947233e4225a701ac13 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Sat, 17 Jun 2023 18:09:21 -0400 Subject: [PATCH 334/648] Code formatting (rearrange order) --- src/PermissionRegistrar.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index d94530a2a..ffb12d87e 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -153,16 +153,6 @@ public function forgetWildcardPermissionIndex(?Model $record = null): void $this->wildcardPermissionsIndex = []; } - /** - * Clear already loaded permissions collection. - * This is only intended to be called by the PermissionServiceProvider on boot, - * so that long-running instances like Swoole don't keep old data in memory. - */ - public function clearPermissionsCollection(): void - { - $this->permissions = null; - } - public function getWildcardPermissionIndex(Model $record): array { if (isset($this->wildcardPermissionsIndex[get_class($record)][$record->getKey()])) { @@ -172,6 +162,16 @@ public function getWildcardPermissionIndex(Model $record): array return $this->wildcardPermissionsIndex[get_class($record)][$record->getKey()] = app($record->getWildcardClass(), ['record' => $record])->getIndex(); } + /** + * Clear already-loaded permissions collection. + * This is only intended to be called by the PermissionServiceProvider on boot, + * so that long-running instances like Octane or Swoole don't keep old data in memory. + */ + public function clearPermissionsCollection(): void + { + $this->permissions = null; + } + /** * @deprecated * From c92aa332982fb0c93351402b3c7d0e5b71e97487 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Sat, 17 Jun 2023 19:07:54 -0400 Subject: [PATCH 335/648] Suppress phpstan errors in Wildcard implementation Ref #2445 See comments in PR 2445 --- phpstan.neon.dist | 3 +++ 1 file changed, 3 insertions(+) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index b367f0713..f4b16051f 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -15,3 +15,6 @@ parameters: ignoreErrors: - '#Unsafe usage of new static#' + # wildcard permissions: + - '#Call to an undefined method Illuminate\\Database\\Eloquent\\Model::getWildcardClass#' + - '#Call to an undefined method Illuminate\\Database\\Eloquent\\Model::getAllPermissions#' From f7a90059ce703f390632d6925d5ab48c235a9ae1 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Sat, 17 Jun 2023 19:28:38 -0400 Subject: [PATCH 336/648] [Docs] More v6 notes --- docs/upgrading.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/upgrading.md b/docs/upgrading.md index 28d431eb8..fd5e7b6e5 100644 --- a/docs/upgrading.md +++ b/docs/upgrading.md @@ -35,11 +35,13 @@ Be sure to compare your custom models with originals to see what else may have c 2. If you have a custom Role model and (in the rare case that you might) have overridden the `hasPermissionTo()` method in it, you will need to update its method signature to `hasPermissionTo($permission, $guardName = null):bool`. See PR #2380. 3. Migrations will need to be upgraded. (They have been updated to anonymous-class syntax that was introduced in Laravel 8, AND some structural coding changes in the registrar class changed the way we extracted configuration settings in the migration files.) If you had not customized it from the original then replacing the contents of the file should be straightforward. Usually the only customization is if you've switched to UUIDs or customized MySQL index name lengths. -If you get the following error, it means your migration file needs upgrading: `Error: Access to undeclared static property Spatie\Permission\PermissionRegistrar::$pivotPermission` +**If you get the following error, it means your migration file needs upgrading: `Error: Access to undeclared static property Spatie\Permission\PermissionRegistrar::$pivotPermission`** 4. NOTE: For consistency with `PermissionMiddleware`, the `RoleOrPermissionMiddleware` has switched from only checking permissions provided by this package to using `canAny()` to check against any abilities registered by your application. This may have the effect of granting those other abilities (such as Super Admin) when using the `RoleOrPermissionMiddleware`, which previously would have failed silently. -5. Test suites. If you have tests which manually clear the permission cache and re-register permissions, you no longer need to call `\Spatie\Permission\PermissionRegistrar::class)->registerPermissions();`. Such calls MUST be deleted from your tests. +5. In the unlikely event that you have customized the Wildcard Permissions feature by extending the `WildcardPermission` model, please note that the public interface has changed and you will need to update your extended model with the new method signatures. + +6. Test suites. If you have tests which manually clear the permission cache and re-register permissions, you no longer need to call `\Spatie\Permission\PermissionRegistrar::class)->registerPermissions();`. Such calls MUST be deleted from your tests. ## Upgrading from v4 to v5 From 4475f05f2e1cb3fae51b2fb066815789b6110a4d Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Sat, 17 Jun 2023 21:38:44 -0400 Subject: [PATCH 337/648] [Docs] Update upgrade docs --- docs/upgrading.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/upgrading.md b/docs/upgrading.md index fd5e7b6e5..f44ec1172 100644 --- a/docs/upgrading.md +++ b/docs/upgrading.md @@ -15,13 +15,13 @@ ALL upgrades of this package should follow these steps: 3. Config file. Incorporate any changes to the permission.php config file, updating your existing file. (It may be easiest to make a backup copy of your existing file, re-publish it from this package, and then re-make your customizations to it.) -3. Models. If you have made any custom Models from this package into your own app, compare the old and new models and apply any relevant updates to your custom models. +4. Models. If you have made any custom Models from this package into your own app, compare the old and new models and apply any relevant updates to your custom models. -4. Custom Methods/Traits. If you have overridden any methods from this package's Traits, compare the old and new traits, and apply any relevant updates to your overridden methods. +5. Custom Methods/Traits. If you have overridden any methods from this package's Traits, compare the old and new traits, and apply any relevant updates to your overridden methods. -5. Apply any version-specific special updates as outlined below... +6. Apply any version-specific special updates as outlined below... -6. Review the changelog, which details all the changes: [CHANGELOG](https://github.com/spatie/laravel-permission/blob/main/CHANGELOG.md) +7. Review the changelog, which details all the changes: [CHANGELOG](https://github.com/spatie/laravel-permission/blob/main/CHANGELOG.md) and/or consult the [Release Notes](https://github.com/spatie/laravel-permission/releases) From cf670092d9e69e7538b2b0f1ef05f5cbf6af3386 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Mon, 26 Jun 2023 13:41:46 -0400 Subject: [PATCH 338/648] Example for adding 'description' fields --- docs/advanced-usage/extending.md | 47 +++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/docs/advanced-usage/extending.md b/docs/advanced-usage/extending.md index ded3621ec..3a86d0b52 100644 --- a/docs/advanced-usage/extending.md +++ b/docs/advanced-usage/extending.md @@ -3,6 +3,43 @@ title: Extending weight: 4 --- +## Adding fields to your models +You can add your own migrations to make changes to the role/permission tables, as you would for adding/changing fields in any other tables in your Laravel project. + +Following that, you can add any necessary logic for interacting with those fields into your custom/extended Models. + +Here is an example of adding a 'description' field to your Permissions and Roles tables: + +```sh +php artisan make:migration add_description_to_permissions_tables +``` +And in the migration file: +```php +public function up() +{ + Schema::table('permissions', function (Blueprint $table) { + $table->string('description')->nullable(); + }); + Schema::table('roles', function (Blueprint $table) { + $table->string('description')->nullable(); + }); +} +``` + +Semi-Related article: [Adding Extra Fields To Pivot Table](https://quickadminpanel.com/blog/laravel-belongstomany-add-extra-fields-to-pivot-table/) (video) + +## Adding a description to roles and permissions +A common question is "how do I add a description for my roles or permissions?". + +By default, a 'description' field is not included in this package, to keep the model memory usage low, because not every app has a need for displayed descriptions. + +But you are free to add it yourself if you wish. You can use the example above. + +### Multiple Language Descriptions + +If you need your 'description' to support multiple languages, simply use Laravel's built-in language features. You might prefer to rename the 'description' field in these migration examples from 'description' to 'description_key' for clarity. + + ## Extending User Models Laravel's authorization features are available in models which implement the `Illuminate\Foundation\Auth\Access\Authorizable` trait. @@ -58,13 +95,3 @@ In the rare case that you have need to REPLACE the existing `Role` or `Permissio - Your `Permission` model needs to implement the `Spatie\Permission\Contracts\Permission` contract - You need to update `config/permission.php` to specify your namespaced model - -## Adding fields to your models -You can add your own migrations to make changes to the role/permission tables, as you would for adding/changing fields in any other tables in your Laravel project. - -Following that, you can add any necessary logic for interacting with those fields into your custom/extended Models. - -Related article: [Adding Extra Fields To Pivot Table](https://quickadminpanel.com/blog/laravel-belongstomany-add-extra-fields-to-pivot-table/) (video) - - - From d8f46bea1d43607829d229e75e661da084089d85 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Thu, 29 Jun 2023 17:54:57 -0400 Subject: [PATCH 339/648] [Docs] Formatting Traded backticks for square-brackets so auto-generated index renders full section titles (otherwise it was truncating them, leading to confusion) --- docs/prerequisites.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/prerequisites.md b/docs/prerequisites.md index fd50f9929..78773b379 100644 --- a/docs/prerequisites.md +++ b/docs/prerequisites.md @@ -29,13 +29,13 @@ class User extends Authenticatable } ``` -## Must not have a `role` or `roles` property, nor a `roles()` method +## Must not have a [role] or [roles] property, nor a [roles()] method -Additionally, your `User` model/object MUST NOT have a `role` or `roles` property (or field in the database), nor a `roles()` method on it. Those will interfere with the properties and methods added by the `HasRoles` trait provided by this package, thus causing unexpected outcomes when this package's methods are used to inspect roles and permissions. +Your `User` model/object MUST NOT have a `role` or `roles` property (or field in the database by that name), nor a `roles()` method on it. Those will interfere with the properties and methods added by the `HasRoles` trait provided by this package, thus causing unexpected outcomes when this package's methods are used to inspect roles and permissions. -## Must not have a `permission` or `permissions` property, nor a `permissions()` method +## Must not have a [permission] or [permissions] property, nor a [permissions()] method -Similarly, your `User` model/object MUST NOT have a `permission` or `permissions` property (or field in the database), nor a `permissions()` method on it. Those will interfere with the properties and methods added by the `HasPermissions` trait provided by this package (which is invoked via the `HasRoles` trait). +Your `User` model/object MUST NOT have a `permission` or `permissions` property (or field in the database by that name), nor a `permissions()` method on it. Those will interfere with the properties and methods added by the `HasPermissions` trait provided by this package (which is invoked via the `HasRoles` trait). ## Config file From c0c5e2ed4d19734f39c9e0f2066343e6ff3bec2e Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Sun, 2 Jul 2023 21:02:53 -0400 Subject: [PATCH 340/648] [Docs] Add headings --- docs/basic-usage/basic-usage.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/docs/basic-usage/basic-usage.md b/docs/basic-usage/basic-usage.md index 2db919ac1..56bdd04d8 100644 --- a/docs/basic-usage/basic-usage.md +++ b/docs/basic-usage/basic-usage.md @@ -3,6 +3,7 @@ title: Basic Usage weight: 1 --- +## Add The Trait First, add the `Spatie\Permission\Traits\HasRoles` trait to your `User` model(s): ```php @@ -17,6 +18,7 @@ class User extends Authenticatable } ``` +## Create A Permission This package allows for users to be associated with permissions and roles. Every role is associated with multiple permissions. A `Role` and a `Permission` are regular Eloquent models. They require a `name` and can be created like this: @@ -28,7 +30,7 @@ $role = Role::create(['name' => 'writer']); $permission = Permission::create(['name' => 'edit articles']); ``` - +## Assign A Permission To A Role A permission can be assigned to a role using either of these methods: ```php @@ -36,6 +38,7 @@ $role->givePermissionTo($permission); $permission->assignRole($role); ``` +## Sync Permissions To A Role Multiple permissions can be synced to a role using either of these methods: ```php @@ -43,6 +46,7 @@ $role->syncPermissions($permissions); $permission->syncRoles($roles); ``` +## Remove Permission From A Role A permission can be removed from a role using either of these methods: ```php @@ -50,8 +54,10 @@ $role->revokePermissionTo($permission); $permission->removeRole($role); ``` +## Guard Name If you're using multiple guards then the `guard_name` attribute must be set as well. Read about it in the [using multiple guards](./multiple-guards) section of the readme. +## Get Permissions For A User The `HasRoles` trait adds Eloquent relationships to your models, which can be accessed directly or used as a base query: ```php @@ -68,6 +74,7 @@ $permissions = $user->getAllPermissions(); $roles = $user->getRoleNames(); // Returns a collection ``` +## Scopes The `HasRoles` trait also adds a `role` scope to your models to scope the query to certain roles or permissions: ```php @@ -85,7 +92,7 @@ $users = User::permission('edit articles')->get(); // Returns only users with th The scope can accept a string, a `\Spatie\Permission\Models\Permission` object or an `\Illuminate\Support\Collection` object. -### Eloquent +## Eloquent Calls Since Role and Permission models are extended from Eloquent models, basic Eloquent calls can be used as well: ```php @@ -96,3 +103,10 @@ $users_without_any_roles = User::doesntHave('roles')->get(); $all_roles_except_a_and_b = Role::whereNotIn('name', ['role A', 'role B'])->get(); ``` +## Counting Users Having A Role +One way to count all users who have a certain role is by filtering the collection of all Users with their Roles: +```php +$superAdminCount = User::with('roles')->get()->filter( + fn ($user) => $user->roles->where('name', 'Super Admin')->toArray() +)->count(); +``` From e7d8cfb7da3263c6c8d70089536538ae69bf6681 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Jul 2023 05:37:40 +0000 Subject: [PATCH 341/648] Bump dependabot/fetch-metadata from 1.5.1 to 1.6.0 Bumps [dependabot/fetch-metadata](https://github.com/dependabot/fetch-metadata) from 1.5.1 to 1.6.0. - [Release notes](https://github.com/dependabot/fetch-metadata/releases) - [Commits](https://github.com/dependabot/fetch-metadata/compare/v1.5.1...v1.6.0) --- updated-dependencies: - dependency-name: dependabot/fetch-metadata dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/dependabot-auto-merge.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml index 3f60ce0f6..60183c521 100644 --- a/.github/workflows/dependabot-auto-merge.yml +++ b/.github/workflows/dependabot-auto-merge.yml @@ -13,7 +13,7 @@ jobs: - name: Dependabot metadata id: metadata - uses: dependabot/fetch-metadata@v1.5.1 + uses: dependabot/fetch-metadata@v1.6.0 with: github-token: "${{ secrets.GITHUB_TOKEN }}" compat-lookup: true From 816d83729bd4d4c0039e63553d57e333bad488a6 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Tue, 4 Jul 2023 08:37:50 -0500 Subject: [PATCH 342/648] Fix Eloquent Strictness on `permission:show` Command (#2458) --- src/Commands/Show.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Commands/Show.php b/src/Commands/Show.php index 0c08bd28a..418daaa65 100644 --- a/src/Commands/Show.php +++ b/src/Commands/Show.php @@ -20,6 +20,7 @@ public function handle() { $permissionClass = app(PermissionContract::class); $roleClass = app(RoleContract::class); + $teamsEnabled = config('permission.teams'); $team_key = config('permission.column_names.team_foreign_key'); $style = $this->argument('style') ?? 'default'; @@ -36,9 +37,12 @@ public function handle() $roles = $roleClass::whereGuardName($guard) ->with('permissions') - ->when(config('permission.teams'), fn ($q) => $q->orderBy($team_key)) + ->when($teamsEnabled, fn ($q) => $q->orderBy($team_key)) ->orderBy('name')->get()->mapWithKeys(fn ($role) => [ - $role->name.'_'.($role->$team_key ?: '') => ['permissions' => $role->permissions->pluck('id'), $team_key => $role->$team_key], + $role->name.'_'.($teamsEnabled ? ($role->$team_key ?: '') : '') => [ + 'permissions' => $role->permissions->pluck('id'), + $team_key => $teamsEnabled ? $role->$team_key : null, + ], ]); $permissions = $permissionClass::whereGuardName($guard)->orderBy('name')->pluck('name', 'id'); @@ -48,7 +52,7 @@ public function handle() )->prepend($permission) ); - if (config('permission.teams')) { + if ($teamsEnabled) { $teams = $roles->groupBy($team_key)->values()->map( fn ($group, $id) => new TableCell('Team ID: '.($id ?: 'NULL'), ['colspan' => $group->count()]) ); From bb1faba77a3b581331690bee0ee1deb6b7dae9ca Mon Sep 17 00:00:00 2001 From: drbyte Date: Tue, 4 Jul 2023 13:38:17 +0000 Subject: [PATCH 343/648] Fix styling --- tests/HasRolesTest.php | 18 +++++++++--------- tests/TestModels/TestRolePermissionsEnum.php | 12 ++++++------ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/tests/HasRolesTest.php b/tests/HasRolesTest.php index 1f86ff47d..49a775ee3 100644 --- a/tests/HasRolesTest.php +++ b/tests/HasRolesTest.php @@ -623,15 +623,15 @@ public function it_returns_false_instead_of_an_exception_when_checking_against_a $this->assertFalse($this->testUser->hasAnyRole('This Role Does Not Even Exist', $this->testAdminRole)); } - /** @test */ - public function it_throws_an_exception_if_an_unsupported_type_is_passed_to_hasRoles() - { - $this->expectException(\TypeError::class); - - $this->testUser->hasRole(new class - { - }); - } + /** @test */ + public function it_throws_an_exception_if_an_unsupported_type_is_passed_to_hasRoles() + { + $this->expectException(\TypeError::class); + + $this->testUser->hasRole(new class + { + }); + } /** @test */ public function it_can_retrieve_role_names() diff --git a/tests/TestModels/TestRolePermissionsEnum.php b/tests/TestModels/TestRolePermissionsEnum.php index d5c73ebc1..858b7f937 100644 --- a/tests/TestModels/TestRolePermissionsEnum.php +++ b/tests/TestModels/TestRolePermissionsEnum.php @@ -44,13 +44,13 @@ enum TestRolePermissionsEnum: string public function label(): string { return match ($this) { - static::WRITER => 'Writers', - static::EDITOR => 'Editors', - static::USERMANAGER => 'User Managers', - static::ADMIN => 'Admins', + self::WRITER => 'Writers', + self::EDITOR => 'Editors', + self::USERMANAGER => 'User Managers', + self::ADMIN => 'Admins', - static::VIEWARTICLES => 'View Articles', - static::EDITARTICLES => 'Edit Articles', + self::VIEWARTICLES => 'View Articles', + self::EDITARTICLES => 'Edit Articles', }; } } From b70c71d42fb5c39e74411d0b3e3bdb2073ebd412 Mon Sep 17 00:00:00 2001 From: drbyte Date: Tue, 4 Jul 2023 13:51:45 +0000 Subject: [PATCH 344/648] Update CHANGELOG --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d07abe830..1a9a38279 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to `laravel-permission` will be documented in this file +## 5.10.2 - 2023-07-04 + +### What's Changed + +- Fix Eloquent Strictness on `permission:show` Command by @erikn69 in https://github.com/spatie/laravel-permission/pull/2457 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/5.10.1...5.10.2 + ## 5.10.1 - 2023-04-12 ### What's Changed @@ -609,6 +617,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` 1. Also this is a good time to point out that now with v2.25.0 and v2.26.0 most permission-cache-reset scenarios may no longer be needed in your app, so it's worth reviewing those cases, as you may gain some app speed improvement by removing unnecessary cache resets. @@ -664,6 +673,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` ## 2.19.1 - 2018-09-14 From c22493f98c3a198ea8f1951afcddc3cc28cb01d5 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Wed, 12 Jul 2023 15:06:05 -0400 Subject: [PATCH 345/648] Tidying some tests --- tests/HasRolesTest.php | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/tests/HasRolesTest.php b/tests/HasRolesTest.php index 49a775ee3..8dafa0e7b 100644 --- a/tests/HasRolesTest.php +++ b/tests/HasRolesTest.php @@ -395,7 +395,6 @@ public function it_can_scope_users_using_an_array() $user2->assignRole('testRole2'); $scopedUsers1 = User::role([$this->testUserRole])->get(); - $scopedUsers2 = User::role(['testRole', 'testRole2'])->get(); $this->assertEquals(1, $scopedUsers1->count()); @@ -407,16 +406,13 @@ public function it_can_scope_users_using_an_array_of_ids_and_names() { $user1 = User::create(['email' => 'user1@test.com']); $user2 = User::create(['email' => 'user2@test.com']); - $user1->assignRole($this->testUserRole); - $user2->assignRole('testRole2'); - $roleName = $this->testUserRole->name; - - $otherRoleId = app(Role::class)->findByName('testRole2')->getKey(); + $firstAssignedRoleName = $this->testUserRole->name; + $secondAssignedRoleId = app(Role::class)->findByName('testRole2')->getKey(); - $scopedUsers = User::role([$roleName, $otherRoleId])->get(); + $scopedUsers = User::role([$firstAssignedRoleName, $secondAssignedRoleId])->get(); $this->assertEquals(2, $scopedUsers->count()); } @@ -465,9 +461,9 @@ public function it_can_scope_against_a_specific_guard() $this->assertEquals(1, $scopedUsers1->count()); - $user3 = Admin::create(['email' => 'user1@test.com']); - $user4 = Admin::create(['email' => 'user1@test.com']); - $user5 = Admin::create(['email' => 'user2@test.com']); + $user3 = Admin::create(['email' => 'user3@test.com']); + $user4 = Admin::create(['email' => 'user4@test.com']); + $user5 = Admin::create(['email' => 'user5@test.com']); $testAdminRole2 = app(Role::class)->create(['name' => 'testAdminRole2', 'guard_name' => 'admin']); $user3->assignRole($this->testAdminRole); $user4->assignRole($this->testAdminRole); From 01bcb2fdae6ae7aaba9ce95769efdd010c5ae4a1 Mon Sep 17 00:00:00 2001 From: drbyte Date: Wed, 12 Jul 2023 19:06:42 +0000 Subject: [PATCH 346/648] Fix styling --- src/PermissionRegistrar.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index ffb12d87e..bc434e571 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -142,7 +142,7 @@ public function forgetCachedPermissions() return $this->cache->forget($this->cacheKey); } - public function forgetWildcardPermissionIndex(?Model $record = null): void + public function forgetWildcardPermissionIndex(Model $record = null): void { if ($record) { unset($this->wildcardPermissionsIndex[get_class($record)][$record->getKey()]); From bee67cbdc9708ca55b98dc48a1d1adba17e22530 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Thu, 13 Jul 2023 01:45:18 -0400 Subject: [PATCH 347/648] Update descriptions in default config file --- config/permission.php | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/config/permission.php b/config/permission.php index 5b6e184c3..c23acb9ce 100644 --- a/config/permission.php +++ b/config/permission.php @@ -98,39 +98,42 @@ /* * When set to true, the method for checking permissions will be registered on the gate. - * Set this to false, if you want to implement custom logic for checking permissions. + * Set this to false if you want to implement custom logic for checking permissions. */ 'register_permission_check_method' => true, /* - * When set to true the package implements teams using the 'team_foreign_key'. If you want - * the migrations to register the 'team_foreign_key', you must set this to true - * before doing the migration. If you already did the migration then you must make a new - * migration to also add 'team_foreign_key' to 'roles', 'model_has_roles', and - * 'model_has_permissions'(view the latest version of package's migration file) + * Teams Feature. + * When set to true the package implements teams using the 'team_foreign_key'. + * If you want the migrations to register the 'team_foreign_key', you must + * set this to true before doing the migration. + * If you already did the migration then you must make a new migration to also + * add 'team_foreign_key' to 'roles', 'model_has_roles', and 'model_has_permissions' + * (view the latest version of this package's migration file) */ 'teams' => false, /* - * When set to true, the required permission names are added to the exception - * message. This could be considered an information leak in some contexts, so - * the default setting is false here for optimum safety. + * When set to true, the required permission names are added to exception messages. + * This could be considered an information leak in some contexts, so the default + * setting is false here for optimum safety. */ 'display_permission_in_exception' => false, /* - * When set to true, the required role names are added to the exception - * message. This could be considered an information leak in some contexts, so - * the default setting is false here for optimum safety. + * When set to true, the required role names are added to exception messages. + * This could be considered an information leak in some contexts, so the default + * setting is false here for optimum safety. */ 'display_role_in_exception' => false, /* * By default wildcard permission lookups are disabled. + * See documentation to understand supported syntax. */ 'enable_wildcard_permission' => false, From ed0de82322489520a426bcdd647e9df41e60393f Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Thu, 13 Jul 2023 22:55:46 -0400 Subject: [PATCH 348/648] Add withoutRole and withoutPermission scopes (#2463) This allows the inverse of the prior scopes to now allow finding users that do-not-have the specified role or permission Ref: #1037 --- docs/basic-usage/basic-usage.md | 10 ++- src/Traits/HasPermissions.php | 31 +++++++ src/Traits/HasRoles.php | 30 +++++++ tests/HasPermissionsTest.php | 61 ++++++++++++-- tests/HasRolesTest.php | 138 ++++++++++++++++++++++++++++++++ tests/TeamHasRolesTest.php | 4 + 6 files changed, 264 insertions(+), 10 deletions(-) diff --git a/docs/basic-usage/basic-usage.md b/docs/basic-usage/basic-usage.md index 56bdd04d8..ea0d9e172 100644 --- a/docs/basic-usage/basic-usage.md +++ b/docs/basic-usage/basic-usage.md @@ -75,18 +75,20 @@ $roles = $user->getRoleNames(); // Returns a collection ``` ## Scopes -The `HasRoles` trait also adds a `role` scope to your models to scope the query to certain roles or permissions: +The `HasRoles` trait also adds `role` and `withoutRole` scopes to your models to scope the query to certain roles or permissions: ```php $users = User::role('writer')->get(); // Returns only users with the role 'writer' +$nonEditors = User::withoutRole('editor')->get(); // Returns only users without the role 'editor' ``` -The `role` scope can accept a string, a `\Spatie\Permission\Models\Role` object or an `\Illuminate\Support\Collection` object. +The `role` and `withoutRole` scopes can accept a string, a `\Spatie\Permission\Models\Role` object or an `\Illuminate\Support\Collection` object. -The same trait also adds a scope to only get users that have a certain permission. +The same trait also adds scopes to only get users that have or don't have a certain permission. ```php $users = User::permission('edit articles')->get(); // Returns only users with the permission 'edit articles' (inherited or directly) +$usersWhoCannotEditArticles = User::withoutPermission('edit articles')->get(); // Returns all users without the permission 'edit articles' (inherited or directly) ``` The scope can accept a string, a `\Spatie\Permission\Models\Permission` object or an `\Illuminate\Support\Collection` object. @@ -97,7 +99,7 @@ Since Role and Permission models are extended from Eloquent models, basic Eloque ```php $all_users_with_all_their_roles = User::with('roles')->get(); -$all_users_with_all_direct_permissions = User::with('permissions')->get(); +$all_users_with_all_their_direct_permissions = User::with('permissions')->get(); $all_roles_in_database = Role::all()->pluck('name'); $users_without_any_roles = User::doesntHave('roles')->get(); $all_roles_except_a_and_b = Role::whereNotIn('name', ['role A', 'role B'])->get(); diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index 6b6c8c91e..1404bb501 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -122,6 +122,37 @@ public function scopePermission(Builder $query, $permissions): Builder ); } + /** + * Scope the model query to only those without certain permissions, + * whether indirectly by role or by direct permission. + * + * @param string|int|array|Permission|Collection|\BackedEnum $permissions + */ + public function scopeWithoutPermission(Builder $query, $permissions, $debug = false): Builder + { + $permissions = $this->convertToPermissionModels($permissions); + + $permissionClass = $this->getPermissionClass(); + $permissionKey = (new $permissionClass())->getKeyName(); + $roleClass = is_a($this, Role::class) ? static::class : $this->getRoleClass(); + $roleKey = (new $roleClass())->getKeyName(); + + $rolesWithPermissions = is_a($this, Role::class) ? [] : array_unique( + array_reduce($permissions, fn ($result, $permission) => array_merge($result, $permission->roles->all()), []) + ); + + return $query->where(fn (Builder $query) => $query + ->whereDoesntHave('permissions', fn (Builder $subQuery) => $subQuery + ->whereIn(config('permission.table_names.permissions').".$permissionKey", \array_column($permissions, $permissionKey)) + ) + ->when(count($rolesWithPermissions), fn ($whenQuery) => $whenQuery + ->whereDoesntHave('roles', fn (Builder $subQuery) => $subQuery + ->whereIn(config('permission.table_names.roles').".$roleKey", \array_column($rolesWithPermissions, $roleKey)) + ) + ) + ); + } + /** * @param string|int|array|Permission|Collection|\BackedEnum $permissions * diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index ab637a795..8e9664cd7 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -95,6 +95,36 @@ public function scopeRole(Builder $query, $roles, $guard = null): Builder ); } + /** + * Scope the model query to only those without certain roles. + * + * @param string|int|array|Role|Collection $roles + * @param string $guard + */ + public function scopeWithoutRole(Builder $query, $roles, $guard = null): Builder + { + if ($roles instanceof Collection) { + $roles = $roles->all(); + } + + $roles = array_map(function ($role) use ($guard) { + if ($role instanceof Role) { + return $role; + } + + $method = is_int($role) || PermissionRegistrar::isUid($role) ? 'findById' : 'findByName'; + + return $this->getRoleClass()::{$method}($role, $guard ?: $this->getDefaultGuardName()); + }, Arr::wrap($roles)); + + $roleClass = $this->getRoleClass(); + $key = (new $roleClass())->getKeyName(); + + return $query->whereHas('roles', fn (Builder $subQuery) => $subQuery + ->whereNotIn(config('permission.table_names.roles').".$key", \array_column($roles, $key)) + ); + } + /** * Returns roles ids as array keys * diff --git a/tests/HasPermissionsTest.php b/tests/HasPermissionsTest.php index e5c9a4c85..0eeb961a9 100644 --- a/tests/HasPermissionsTest.php +++ b/tests/HasPermissionsTest.php @@ -87,123 +87,160 @@ public function it_can_scope_users_using_enums() $enum2 = TestModels\TestRolePermissionsEnum::EDITARTICLES; $permission1 = app(Permission::class)->findOrCreate($enum1->value, 'web'); $permission2 = app(Permission::class)->findOrCreate($enum2->value, 'web'); + + User::all()->each(fn ($item) => $item->delete()); $user1 = User::create(['email' => 'user1@test.com']); $user2 = User::create(['email' => 'user2@test.com']); + $user3 = User::create(['email' => 'user3@test.com']); $user1->givePermissionTo([$enum1, $enum2]); $this->testUserRole->givePermissionTo($enum2); $user2->assignRole('testRole'); $scopedUsers1 = User::permission($enum2)->get(); $scopedUsers2 = User::permission([$enum1])->get(); + $scopedUsers3 = User::withoutPermission([$enum1])->get(); + $scopedUsers4 = User::withoutPermission([$enum2])->get(); $this->assertEquals(2, $scopedUsers1->count()); $this->assertEquals(1, $scopedUsers2->count()); + $this->assertEquals(2, $scopedUsers3->count()); + $this->assertEquals(1, $scopedUsers4->count()); } /** @test */ public function it_can_scope_users_using_a_string() { + User::all()->each(fn ($item) => $item->delete()); $user1 = User::create(['email' => 'user1@test.com']); $user2 = User::create(['email' => 'user2@test.com']); + $user3 = User::create(['email' => 'user3@test.com']); $user1->givePermissionTo(['edit-articles', 'edit-news']); $this->testUserRole->givePermissionTo('edit-articles'); $user2->assignRole('testRole'); $scopedUsers1 = User::permission('edit-articles')->get(); $scopedUsers2 = User::permission(['edit-news'])->get(); + $scopedUsers3 = User::withoutPermission('edit-news')->get(); $this->assertEquals(2, $scopedUsers1->count()); $this->assertEquals(1, $scopedUsers2->count()); + $this->assertEquals(2, $scopedUsers3->count()); } /** @test */ public function it_can_scope_users_using_a_int() { + User::all()->each(fn ($item) => $item->delete()); $user1 = User::create(['email' => 'user1@test.com']); $user2 = User::create(['email' => 'user2@test.com']); + $user3 = User::create(['email' => 'user3@test.com']); $user1->givePermissionTo([1, 2]); $this->testUserRole->givePermissionTo(1); $user2->assignRole('testRole'); $scopedUsers1 = User::permission(1)->get(); $scopedUsers2 = User::permission([2])->get(); + $scopedUsers3 = User::withoutPermission([2])->get(); $this->assertEquals(2, $scopedUsers1->count()); $this->assertEquals(1, $scopedUsers2->count()); + $this->assertEquals(2, $scopedUsers3->count()); } /** @test */ public function it_can_scope_users_using_an_array() { + User::all()->each(fn ($item) => $item->delete()); $user1 = User::create(['email' => 'user1@test.com']); $user2 = User::create(['email' => 'user2@test.com']); + $user3 = User::create(['email' => 'user3@test.com']); $user1->givePermissionTo(['edit-articles', 'edit-news']); $this->testUserRole->givePermissionTo('edit-articles'); $user2->assignRole('testRole'); + $user3->assignRole('testRole2'); $scopedUsers1 = User::permission(['edit-articles', 'edit-news'])->get(); $scopedUsers2 = User::permission(['edit-news'])->get(); + $scopedUsers3 = User::withoutPermission(['edit-news'])->get(); $this->assertEquals(2, $scopedUsers1->count()); $this->assertEquals(1, $scopedUsers2->count()); + $this->assertEquals(2, $scopedUsers3->count()); } /** @test */ public function it_can_scope_users_using_a_collection() { + User::all()->each(fn ($item) => $item->delete()); $user1 = User::create(['email' => 'user1@test.com']); $user2 = User::create(['email' => 'user2@test.com']); + $user3 = User::create(['email' => 'user3@test.com']); $user1->givePermissionTo(['edit-articles', 'edit-news']); $this->testUserRole->givePermissionTo('edit-articles'); $user2->assignRole('testRole'); + $user3->assignRole('testRole2'); $scopedUsers1 = User::permission(collect(['edit-articles', 'edit-news']))->get(); $scopedUsers2 = User::permission(collect(['edit-news']))->get(); + $scopedUsers3 = User::withoutPermission(collect(['edit-news']))->get(); $this->assertEquals(2, $scopedUsers1->count()); $this->assertEquals(1, $scopedUsers2->count()); + $this->assertEquals(2, $scopedUsers3->count()); } /** @test */ public function it_can_scope_users_using_an_object() { + User::all()->each(fn ($item) => $item->delete()); $user1 = User::create(['email' => 'user1@test.com']); $user1->givePermissionTo($this->testUserPermission->name); $scopedUsers1 = User::permission($this->testUserPermission)->get(); $scopedUsers2 = User::permission([$this->testUserPermission])->get(); $scopedUsers3 = User::permission(collect([$this->testUserPermission]))->get(); + $scopedUsers4 = User::withoutPermission(collect([$this->testUserPermission]))->get(); $this->assertEquals(1, $scopedUsers1->count()); $this->assertEquals(1, $scopedUsers2->count()); $this->assertEquals(1, $scopedUsers3->count()); + $this->assertEquals(0, $scopedUsers4->count()); } /** @test */ - public function it_can_scope_users_without_permissions_only_role() + public function it_can_scope_users_without_direct_permissions_only_role() { + User::all()->each(fn ($item) => $item->delete()); $user1 = User::create(['email' => 'user1@test.com']); $user2 = User::create(['email' => 'user2@test.com']); + $user3 = User::create(['email' => 'user3@test.com']); $this->testUserRole->givePermissionTo('edit-articles'); $user1->assignRole('testRole'); $user2->assignRole('testRole'); + $user3->assignRole('testRole2'); - $scopedUsers = User::permission('edit-articles')->get(); + $scopedUsers1 = User::permission('edit-articles')->get(); + $scopedUsers2 = User::withoutPermission('edit-articles')->get(); - $this->assertEquals(2, $scopedUsers->count()); + $this->assertEquals(2, $scopedUsers1->count()); + $this->assertEquals(1, $scopedUsers2->count()); } /** @test */ - public function it_can_scope_users_without_permissions_only_permission() + public function it_can_scope_users_with_only_direct_permission() { + User::all()->each(fn ($item) => $item->delete()); $user1 = User::create(['email' => 'user1@test.com']); $user2 = User::create(['email' => 'user2@test.com']); + $user3 = User::create(['email' => 'user3@test.com']); $user1->givePermissionTo(['edit-news']); $user2->givePermissionTo(['edit-articles', 'edit-news']); - $scopedUsers = User::permission('edit-news')->get(); + $scopedUsers1 = User::permission('edit-news')->get(); + $scopedUsers2 = User::withoutPermission('edit-news')->get(); - $this->assertEquals(2, $scopedUsers->count()); + $this->assertEquals(2, $scopedUsers1->count()); + $this->assertEquals(1, $scopedUsers2->count()); } /** @test */ @@ -252,6 +289,10 @@ public function it_throws_an_exception_when_trying_to_scope_a_non_existing_permi $this->expectException(PermissionDoesNotExist::class); User::permission('not defined permission')->get(); + + $this->expectException(PermissionDoesNotExist::class); + + User::withoutPermission('not defined permission')->get(); } /** @test */ @@ -261,9 +302,17 @@ public function it_throws_an_exception_when_trying_to_scope_a_permission_from_an User::permission('testAdminPermission')->get(); + $this->expectException(PermissionDoesNotExist::class); + + User::withoutPermission('testAdminPermission')->get(); + $this->expectException(GuardDoesNotMatch::class); User::permission($this->testAdminPermission)->get(); + + $this->expectException(GuardDoesNotMatch::class); + + User::withoutPermission($this->testAdminPermission)->get(); } /** @test */ diff --git a/tests/HasRolesTest.php b/tests/HasRolesTest.php index 8dafa0e7b..870177fd6 100644 --- a/tests/HasRolesTest.php +++ b/tests/HasRolesTest.php @@ -386,6 +386,21 @@ public function it_can_scope_users_using_a_string() $this->assertEquals(1, $scopedUsers->count()); } + /** @test */ + public function it_can_withoutscope_users_using_a_string() + { + $user1 = User::create(['email' => 'user1@test.com']); + $user2 = User::create(['email' => 'user2@test.com']); + $user3 = User::create(['email' => 'user3@test.com']); + $user1->assignRole('testRole'); + $user2->assignRole('testRole2'); + $user3->assignRole('testRole2'); + + $scopedUsers = User::withoutRole('testRole2')->get(); + + $this->assertEquals(1, $scopedUsers->count()); + } + /** @test */ public function it_can_scope_users_using_an_array() { @@ -401,6 +416,23 @@ public function it_can_scope_users_using_an_array() $this->assertEquals(2, $scopedUsers2->count()); } + /** @test */ + public function it_can_withoutscope_users_using_an_array() + { + $user1 = User::create(['email' => 'user1@test.com']); + $user2 = User::create(['email' => 'user2@test.com']); + $user3 = User::create(['email' => 'user3@test.com']); + $user1->assignRole($this->testUserRole); + $user2->assignRole('testRole2'); + $user3->assignRole('testRole2'); + + $scopedUsers1 = User::withoutRole([$this->testUserRole])->get(); + $scopedUsers2 = User::withoutRole([$this->testUserRole->name, 'testRole2'])->get(); + + $this->assertEquals(2, $scopedUsers1->count()); + $this->assertEquals(0, $scopedUsers2->count()); + } + /** @test */ public function it_can_scope_users_using_an_array_of_ids_and_names() { @@ -417,6 +449,26 @@ public function it_can_scope_users_using_an_array_of_ids_and_names() $this->assertEquals(2, $scopedUsers->count()); } + /** @test */ + public function it_can_withoutscope_users_using_an_array_of_ids_and_names() + { + app(Role::class)->create(['name' => 'testRole3']); + + $user1 = User::create(['email' => 'user1@test.com']); + $user2 = User::create(['email' => 'user2@test.com']); + $user3 = User::create(['email' => 'user3@test.com']); + $user1->assignRole($this->testUserRole); + $user2->assignRole('testRole2'); + $user3->assignRole('testRole2'); + + $firstAssignedRoleName = $this->testUserRole->name; + $unassignedRoleId = app(Role::class)->findByName('testRole3')->getKey(); + + $scopedUsers = User::withoutRole([$firstAssignedRoleName, $unassignedRoleId])->get(); + + $this->assertEquals(2, $scopedUsers->count()); + } + /** @test */ public function it_can_scope_users_using_a_collection() { @@ -432,6 +484,25 @@ public function it_can_scope_users_using_a_collection() $this->assertEquals(2, $scopedUsers2->count()); } + /** @test */ + public function it_can_withoutscope_users_using_a_collection() + { + app(Role::class)->create(['name' => 'testRole3']); + + $user1 = User::create(['email' => 'user1@test.com']); + $user2 = User::create(['email' => 'user2@test.com']); + $user3 = User::create(['email' => 'user3@test.com']); + $user1->assignRole($this->testUserRole); + $user2->assignRole('testRole'); + $user3->assignRole('testRole2'); + + $scopedUsers1 = User::withoutRole([$this->testUserRole])->get(); + $scopedUsers2 = User::withoutRole(collect(['testRole', 'testRole3']))->get(); + + $this->assertEquals(1, $scopedUsers1->count()); + $this->assertEquals(1, $scopedUsers2->count()); + } + /** @test */ public function it_can_scope_users_using_an_object() { @@ -449,6 +520,25 @@ public function it_can_scope_users_using_an_object() $this->assertEquals(1, $scopedUsers3->count()); } + /** @test */ + public function it_can_withoutscope_users_using_an_object() + { + $user1 = User::create(['email' => 'user1@test.com']); + $user2 = User::create(['email' => 'user2@test.com']); + $user3 = User::create(['email' => 'user3@test.com']); + $user1->assignRole($this->testUserRole); + $user2->assignRole('testRole2'); + $user3->assignRole('testRole2'); + + $scopedUsers1 = User::withoutRole($this->testUserRole)->get(); + $scopedUsers2 = User::withoutRole([$this->testUserRole])->get(); + $scopedUsers3 = User::withoutRole(collect([$this->testUserRole]))->get(); + + $this->assertEquals(2, $scopedUsers1->count()); + $this->assertEquals(2, $scopedUsers2->count()); + $this->assertEquals(2, $scopedUsers3->count()); + } + /** @test */ public function it_can_scope_against_a_specific_guard() { @@ -475,6 +565,34 @@ public function it_can_scope_against_a_specific_guard() $this->assertEquals(1, $scopedUsers3->count()); } + /** @test */ + public function it_can_withoutscope_against_a_specific_guard() + { + $user1 = User::create(['email' => 'user1@test.com']); + $user2 = User::create(['email' => 'user2@test.com']); + $user3 = User::create(['email' => 'user3@test.com']); + $user1->assignRole('testRole'); + $user2->assignRole('testRole2'); + $user3->assignRole('testRole2'); + + $scopedUsers1 = User::withoutRole('testRole', 'web')->get(); + + $this->assertEquals(2, $scopedUsers1->count()); + + $user4 = Admin::create(['email' => 'user4@test.com']); + $user5 = Admin::create(['email' => 'user5@test.com']); + $user6 = Admin::create(['email' => 'user6@test.com']); + $testAdminRole2 = app(Role::class)->create(['name' => 'testAdminRole2', 'guard_name' => 'admin']); + $user4->assignRole($this->testAdminRole); + $user5->assignRole($this->testAdminRole); + $user6->assignRole($testAdminRole2); + $scopedUsers2 = Admin::withoutRole('testAdminRole', 'admin')->get(); + $scopedUsers3 = Admin::withoutRole('testAdminRole2', 'admin')->get(); + + $this->assertEquals(1, $scopedUsers2->count()); + $this->assertEquals(2, $scopedUsers3->count()); + } + /** @test */ public function it_throws_an_exception_when_trying_to_scope_a_role_from_another_guard() { @@ -487,6 +605,18 @@ public function it_throws_an_exception_when_trying_to_scope_a_role_from_another_ User::role($this->testAdminRole)->get(); } + /** @test */ + public function it_throws_an_exception_when_trying_to_call_withoutscope_on_a_role_from_another_guard() + { + $this->expectException(RoleDoesNotExist::class); + + User::withoutRole('testAdminRole')->get(); + + $this->expectException(GuardDoesNotMatch::class); + + User::withoutRole($this->testAdminRole)->get(); + } + /** @test */ public function it_throws_an_exception_when_trying_to_scope_a_non_existing_role() { @@ -495,6 +625,14 @@ public function it_throws_an_exception_when_trying_to_scope_a_non_existing_role( User::role('role not defined')->get(); } + /** @test */ + public function it_throws_an_exception_when_trying_to_use_withoutscope_on_a_non_existing_role() + { + $this->expectException(RoleDoesNotExist::class); + + User::withoutRole('role not defined')->get(); + } + /** @test */ public function it_can_determine_that_a_user_has_one_of_the_given_roles() { diff --git a/tests/TeamHasRolesTest.php b/tests/TeamHasRolesTest.php index e67010009..31ba19f53 100644 --- a/tests/TeamHasRolesTest.php +++ b/tests/TeamHasRolesTest.php @@ -133,15 +133,19 @@ public function it_can_scope_users_on_different_teams() setPermissionsTeamId(2); $scopedUsers1Team1 = User::role($this->testUserRole)->get(); $scopedUsers2Team1 = User::role(['testRole', 'testRole2'])->get(); + $scopedUsers3Team1 = User::withoutRole('testRole')->get(); $this->assertEquals(1, $scopedUsers1Team1->count()); $this->assertEquals(2, $scopedUsers2Team1->count()); + $this->assertEquals(1, $scopedUsers3Team1->count()); setPermissionsTeamId(1); $scopedUsers1Team2 = User::role($this->testUserRole)->get(); $scopedUsers2Team2 = User::role('testRole2')->get(); + $scopedUsers3Team2 = User::withoutRole('testRole')->get(); $this->assertEquals(1, $scopedUsers1Team2->count()); $this->assertEquals(0, $scopedUsers2Team2->count()); + $this->assertEquals(0, $scopedUsers3Team2->count()); } } From bf4b198e6033234460028c982862c60787c40f72 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Sun, 16 Jul 2023 16:33:19 -0400 Subject: [PATCH 349/648] Fix withoutRole scope added in #2463 Ref #2463 Ref #1037 --- src/Traits/HasRoles.php | 4 ++-- tests/HasRolesTest.php | 7 +++++++ tests/TeamHasRolesTest.php | 3 ++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index 8e9664cd7..273205a51 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -120,8 +120,8 @@ public function scopeWithoutRole(Builder $query, $roles, $guard = null): Builder $roleClass = $this->getRoleClass(); $key = (new $roleClass())->getKeyName(); - return $query->whereHas('roles', fn (Builder $subQuery) => $subQuery - ->whereNotIn(config('permission.table_names.roles').".$key", \array_column($roles, $key)) + return $query->whereDoesntHave('roles', fn (Builder $subQuery) => $subQuery + ->whereIn(config('permission.table_names.roles').".$key", \array_column($roles, $key)) ); } diff --git a/tests/HasRolesTest.php b/tests/HasRolesTest.php index 870177fd6..1e0c38c77 100644 --- a/tests/HasRolesTest.php +++ b/tests/HasRolesTest.php @@ -389,6 +389,7 @@ public function it_can_scope_users_using_a_string() /** @test */ public function it_can_withoutscope_users_using_a_string() { + User::all()->each(fn ($item) => $item->delete()); $user1 = User::create(['email' => 'user1@test.com']); $user2 = User::create(['email' => 'user2@test.com']); $user3 = User::create(['email' => 'user3@test.com']); @@ -419,6 +420,7 @@ public function it_can_scope_users_using_an_array() /** @test */ public function it_can_withoutscope_users_using_an_array() { + User::all()->each(fn ($item) => $item->delete()); $user1 = User::create(['email' => 'user1@test.com']); $user2 = User::create(['email' => 'user2@test.com']); $user3 = User::create(['email' => 'user3@test.com']); @@ -454,6 +456,7 @@ public function it_can_withoutscope_users_using_an_array_of_ids_and_names() { app(Role::class)->create(['name' => 'testRole3']); + User::all()->each(fn ($item) => $item->delete()); $user1 = User::create(['email' => 'user1@test.com']); $user2 = User::create(['email' => 'user2@test.com']); $user3 = User::create(['email' => 'user3@test.com']); @@ -489,6 +492,7 @@ public function it_can_withoutscope_users_using_a_collection() { app(Role::class)->create(['name' => 'testRole3']); + User::all()->each(fn ($item) => $item->delete()); $user1 = User::create(['email' => 'user1@test.com']); $user2 = User::create(['email' => 'user2@test.com']); $user3 = User::create(['email' => 'user3@test.com']); @@ -523,6 +527,7 @@ public function it_can_scope_users_using_an_object() /** @test */ public function it_can_withoutscope_users_using_an_object() { + User::all()->each(fn ($item) => $item->delete()); $user1 = User::create(['email' => 'user1@test.com']); $user2 = User::create(['email' => 'user2@test.com']); $user3 = User::create(['email' => 'user3@test.com']); @@ -568,6 +573,7 @@ public function it_can_scope_against_a_specific_guard() /** @test */ public function it_can_withoutscope_against_a_specific_guard() { + User::all()->each(fn ($item) => $item->delete()); $user1 = User::create(['email' => 'user1@test.com']); $user2 = User::create(['email' => 'user2@test.com']); $user3 = User::create(['email' => 'user3@test.com']); @@ -579,6 +585,7 @@ public function it_can_withoutscope_against_a_specific_guard() $this->assertEquals(2, $scopedUsers1->count()); + Admin::all()->each(fn ($item) => $item->delete()); $user4 = Admin::create(['email' => 'user4@test.com']); $user5 = Admin::create(['email' => 'user5@test.com']); $user6 = Admin::create(['email' => 'user6@test.com']); diff --git a/tests/TeamHasRolesTest.php b/tests/TeamHasRolesTest.php index 31ba19f53..16d8cdd6d 100644 --- a/tests/TeamHasRolesTest.php +++ b/tests/TeamHasRolesTest.php @@ -120,6 +120,7 @@ public function it_can_sync_or_remove_roles_without_detach_on_different_teams() /** @test */ public function it_can_scope_users_on_different_teams() { + User::all()->each(fn ($item) => $item->delete()); $user1 = User::create(['email' => 'user1@test.com']); $user2 = User::create(['email' => 'user2@test.com']); @@ -146,6 +147,6 @@ public function it_can_scope_users_on_different_teams() $this->assertEquals(1, $scopedUsers1Team2->count()); $this->assertEquals(0, $scopedUsers2Team2->count()); - $this->assertEquals(0, $scopedUsers3Team2->count()); + $this->assertEquals(1, $scopedUsers3Team2->count()); } } From 299fe4beb7e6f77079c9642e92bbd887d8736b6f Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Sun, 16 Jul 2023 16:34:46 -0400 Subject: [PATCH 350/648] Add Enum support for Role and WithoutRole scopes Ref #2391 --- src/Traits/HasRoles.php | 14 +++++++++++--- tests/HasRolesTest.php | 31 +++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index 273205a51..829afde22 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -68,7 +68,7 @@ public function roles(): BelongsToMany /** * Scope the model query to certain roles only. * - * @param string|int|array|Role|Collection $roles + * @param string|int|array|Role|Collection|\BackedEnum $roles * @param string $guard */ public function scopeRole(Builder $query, $roles, $guard = null): Builder @@ -82,6 +82,10 @@ public function scopeRole(Builder $query, $roles, $guard = null): Builder return $role; } + if ($role instanceof \BackedEnum) { + $role = $role->value; + } + $method = is_int($role) || PermissionRegistrar::isUid($role) ? 'findById' : 'findByName'; return $this->getRoleClass()::{$method}($role, $guard ?: $this->getDefaultGuardName()); @@ -98,7 +102,7 @@ public function scopeRole(Builder $query, $roles, $guard = null): Builder /** * Scope the model query to only those without certain roles. * - * @param string|int|array|Role|Collection $roles + * @param string|int|array|Role|Collection|\BackedEnum $roles * @param string $guard */ public function scopeWithoutRole(Builder $query, $roles, $guard = null): Builder @@ -112,6 +116,10 @@ public function scopeWithoutRole(Builder $query, $roles, $guard = null): Builder return $role; } + if ($role instanceof \BackedEnum) { + $role = $role->value; + } + $method = is_int($role) || PermissionRegistrar::isUid($role) ? 'findById' : 'findByName'; return $this->getRoleClass()::{$method}($role, $guard ?: $this->getDefaultGuardName()); @@ -128,7 +136,7 @@ public function scopeWithoutRole(Builder $query, $roles, $guard = null): Builder /** * Returns roles ids as array keys * - * @param array|string|int|Role|Collection $roles + * @param array|string|int|Role|Collection|\BackedEnum $roles */ private function collectRoles(...$roles): array { diff --git a/tests/HasRolesTest.php b/tests/HasRolesTest.php index 1e0c38c77..ec7bccc72 100644 --- a/tests/HasRolesTest.php +++ b/tests/HasRolesTest.php @@ -70,6 +70,37 @@ public function it_can_assign_and_remove_a_role_using_enums() $this->assertFalse($this->testUser->hasRole($enum1)); } + /** + * @test + * + * @requires PHP >= 8.1 + */ + public function it_can_scope_a_role_using_enums() + { + $enum1 = TestModels\TestRolePermissionsEnum::USERMANAGER; + $enum2 = TestModels\TestRolePermissionsEnum::WRITER; + $role1 = app(Role::class)->findOrCreate($enum1->value, 'web'); + $role2 = app(Role::class)->findOrCreate($enum2->value, 'web'); + + User::all()->each(fn ($item) => $item->delete()); + $user1 = User::create(['email' => 'user1@test.com']); + $user2 = User::create(['email' => 'user2@test.com']); + $user3 = User::create(['email' => 'user3@test.com']); + + // assign only one user to a role + $user2->assignRole($enum1); + $this->assertTrue($user2->hasRole($enum1)); + $this->assertFalse($user2->hasRole($enum2)); + + $scopedUsers1 = User::role($enum1)->get(); + $scopedUsers2 = User::role($enum2)->get(); + $scopedUsers3 = User::withoutRole($enum2)->get(); + + $this->assertEquals(1, $scopedUsers1->count()); + $this->assertEquals(0, $scopedUsers2->count()); + $this->assertEquals(3, $scopedUsers3->count()); + } + /** @test */ public function it_can_assign_and_remove_a_role() { From 484a79d5da0adaf422e8fa0a62c277e06d935962 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Sun, 23 Jul 2023 14:48:20 -0400 Subject: [PATCH 351/648] Fix Show command Co-authored-by: erikn69 --- src/Commands/Show.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Commands/Show.php b/src/Commands/Show.php index 418daaa65..e20a371ed 100644 --- a/src/Commands/Show.php +++ b/src/Commands/Show.php @@ -67,7 +67,7 @@ public function handle() return implode('_', $name); }) - ->prepend('')->toArray(), + ->prepend(new TableCell(''))->toArray(), ), $body->toArray(), $style From 2dc2787dd8b0b6ccba2e40a7620a0a1653f39ec3 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Sun, 23 Jul 2023 14:58:49 -0400 Subject: [PATCH 352/648] Document the config override for Wildcard class name --- config/permission.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/config/permission.php b/config/permission.php index c23acb9ce..b4407316b 100644 --- a/config/permission.php +++ b/config/permission.php @@ -138,6 +138,12 @@ 'enable_wildcard_permission' => false, + /* + * The class to use for interpreting wildcard permissions. + * If you need to modify delimiters, override the class and specify its name here. + */ + // 'permission.wildcard_permission' => Spatie\Permission\WildcardPermission::class, + 'cache' => [ /* From 04a9592eb5a954511b1a044a3775ee076728102d Mon Sep 17 00:00:00 2001 From: SuperDJ <6484766+SuperDJ@users.noreply.github.com> Date: Wed, 26 Jul 2023 22:29:25 +0200 Subject: [PATCH 353/648] Adding the ability to use package with service-to-service Passport client (#2467) * Adding the ability to use package with service-to-service Passport clients Co-authored-by: SuperDJ Co-authored-by: erikn69 --- .github/workflows/run-tests-L8.yml | 4 + composer.json | 1 + config/permission.php | 7 + docs/basic-usage/passport.md | 53 +++++++ phpunit.xml.dist | 2 + src/Guard.php | 32 ++++ src/Middlewares/PermissionMiddleware.php | 12 +- src/Middlewares/RoleMiddleware.php | 12 +- .../RoleOrPermissionMiddleware.php | 13 +- tests/PermissionMiddlewareTest.php | 140 ++++++++++++++++++ tests/RoleMiddlewareTest.php | 123 +++++++++++++++ tests/RoleOrPermissionMiddlewareTest.php | 79 ++++++++++ tests/TestCase.php | 51 ++++++- tests/TestModels/Client.php | 21 +++ 14 files changed, 538 insertions(+), 12 deletions(-) create mode 100644 docs/basic-usage/passport.md create mode 100644 tests/TestModels/Client.php diff --git a/.github/workflows/run-tests-L8.yml b/.github/workflows/run-tests-L8.yml index dd9d0c446..4d8f8e774 100644 --- a/.github/workflows/run-tests-L8.yml +++ b/.github/workflows/run-tests-L8.yml @@ -40,6 +40,10 @@ jobs: extensions: curl, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, iconv coverage: none + - name: Install dependencies (remove passport) + run: composer remove --dev laravel/passport --no-interaction --no-update + if: matrix.laravel == '8.*' + - name: Install dependencies run: | composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" "symfony/console:>=4.3.4" "mockery/mockery:^1.3.2" "nesbot/carbon:>=2.62.1" --no-interaction --no-update diff --git a/composer.json b/composer.json index 9ea458849..4e31db50e 100644 --- a/composer.json +++ b/composer.json @@ -29,6 +29,7 @@ "illuminate/database": "^8.0|^9.0|^10.0" }, "require-dev": { + "laravel/passport": "^11.0", "orchestra/testbench": "^6.0|^7.0|^8.0", "phpunit/phpunit": "^9.4" }, diff --git a/config/permission.php b/config/permission.php index b4407316b..b98b24148 100644 --- a/config/permission.php +++ b/config/permission.php @@ -115,6 +115,13 @@ 'teams' => false, + /* + * Passport Client Credentials Grant + * When set to true the package will use Passports Client to check permissions + */ + + 'use_passport_client_credentials' => false, + /* * When set to true, the required permission names are added to exception messages. * This could be considered an information leak in some contexts, so the default diff --git a/docs/basic-usage/passport.md b/docs/basic-usage/passport.md new file mode 100644 index 000000000..a5a007009 --- /dev/null +++ b/docs/basic-usage/passport.md @@ -0,0 +1,53 @@ +--- +title: Passport Client Credentials Grant usage +weight: 12 +--- + +**NOTE** currently this only works for Laravel 9 and Passport 11 and newer. + +## Install Passport +First of all make sure to have Passport installed as described in the [Laravel documentation](https://laravel.com/docs/master/passport). + +## Extend the Client model +After installing the Passport package we need to extend Passports Client model. +The extended Client model should look like something as shown below. + +```php +use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract; +use Illuminate\Foundation\Auth\Access\Authorizable; +use Laravel\Passport\Client as BaseClient; +use Spatie\Permission\Traits\HasRoles; + +class Client extends BaseClient implements AuthorizableContract +{ + use HasRoles; + use Authorizable; + + public $guard_name = 'api'; + + // or + + public function guardName() + { + return 'api' + } +} +``` + +You need to extend the Client model to make it possible to add the required traits and properties/ methods. +The extended Client should either provide a `$guard_name` property or a `guardName()` method. +They should return a string that matches the [configured](https://laravel.com/docs/master/passport#installation) guard name for the passport driver. + +## Middleware +All middlewares provided by this package work with the Client. + +Do make sure that you only wrap your routes in the [`client`](https://laravel.com/docs/master/passport#via-middleware) middleware and not the `auth:api` middleware as well. +Wrapping routes in the `auth:api` middleware currently does not work for the Client Credentials Grant. + +## Config +Finally, update the config file as well. Setting `use_passport_client_credentials` to `true` will make sure that the right checks are performed. + +```php +// config/permission.php +'use_passport_client_credentials' => true, +``` diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 6cd82acfb..1e08d1014 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -12,5 +12,7 @@ + + diff --git a/src/Guard.php b/src/Guard.php index b5904cc4d..dd5023148 100644 --- a/src/Guard.php +++ b/src/Guard.php @@ -2,8 +2,10 @@ namespace Spatie\Permission; +use Illuminate\Contracts\Auth\Access\Authorizable; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Auth; class Guard { @@ -72,4 +74,34 @@ public static function getDefaultName($class): string return $possible_guards->first() ?: $default; } + + /** + * Lookup a passport guard + */ + public static function getPassportClient($guard): ?Authorizable + { + $guards = collect(config('auth.guards'))->where('driver', 'passport'); + + if (! $guards->count()) { + return null; + } + + $authGuard = Auth::guard($guards->keys()[0]); + + if (! \method_exists($authGuard, 'client')) { + return null; + } + + $client = $authGuard->client(); + + if (! $guard || ! $client) { + return $client; + } + + if (self::getNames($client)->contains($guard)) { + return $client; + } + + return null; + } } diff --git a/src/Middlewares/PermissionMiddleware.php b/src/Middlewares/PermissionMiddleware.php index ca37cb672..7d303261c 100644 --- a/src/Middlewares/PermissionMiddleware.php +++ b/src/Middlewares/PermissionMiddleware.php @@ -5,6 +5,7 @@ use Closure; use Illuminate\Support\Facades\Auth; use Spatie\Permission\Exceptions\UnauthorizedException; +use Spatie\Permission\Guard; class PermissionMiddleware { @@ -12,11 +13,16 @@ public function handle($request, Closure $next, $permission, $guard = null) { $authGuard = Auth::guard($guard); - if ($authGuard->guest()) { - throw UnauthorizedException::notLoggedIn(); + $user = $authGuard->user(); + + // For machine-to-machine Passport clients + if (! $user && $request->bearerToken() && config('permission.use_passport_client_credentials')) { + $user = Guard::getPassportClient($guard); } - $user = $authGuard->user(); + if (! $user) { + throw UnauthorizedException::notLoggedIn(); + } if (! method_exists($user, 'hasAnyPermission')) { throw UnauthorizedException::missingTraitHasRoles($user); diff --git a/src/Middlewares/RoleMiddleware.php b/src/Middlewares/RoleMiddleware.php index ae9232cd5..c98ab9d30 100644 --- a/src/Middlewares/RoleMiddleware.php +++ b/src/Middlewares/RoleMiddleware.php @@ -5,6 +5,7 @@ use Closure; use Illuminate\Support\Facades\Auth; use Spatie\Permission\Exceptions\UnauthorizedException; +use Spatie\Permission\Guard; class RoleMiddleware { @@ -12,11 +13,16 @@ public function handle($request, Closure $next, $role, $guard = null) { $authGuard = Auth::guard($guard); - if ($authGuard->guest()) { - throw UnauthorizedException::notLoggedIn(); + $user = $authGuard->user(); + + // For machine-to-machine Passport clients + if (! $user && $request->bearerToken() && config('permission.use_passport_client_credentials')) { + $user = Guard::getPassportClient($guard); } - $user = $authGuard->user(); + if (! $user) { + throw UnauthorizedException::notLoggedIn(); + } if (! method_exists($user, 'hasAnyRole')) { throw UnauthorizedException::missingTraitHasRoles($user); diff --git a/src/Middlewares/RoleOrPermissionMiddleware.php b/src/Middlewares/RoleOrPermissionMiddleware.php index 52675a43f..4a4abb562 100644 --- a/src/Middlewares/RoleOrPermissionMiddleware.php +++ b/src/Middlewares/RoleOrPermissionMiddleware.php @@ -5,18 +5,25 @@ use Closure; use Illuminate\Support\Facades\Auth; use Spatie\Permission\Exceptions\UnauthorizedException; +use Spatie\Permission\Guard; class RoleOrPermissionMiddleware { public function handle($request, Closure $next, $roleOrPermission, $guard = null) { $authGuard = Auth::guard($guard); - if ($authGuard->guest()) { - throw UnauthorizedException::notLoggedIn(); - } $user = $authGuard->user(); + // For machine-to-machine Passport clients + if (! $user && $request->bearerToken() && config('permission.use_passport_client_credentials')) { + $user = Guard::getPassportClient($guard); + } + + if (! $user) { + throw UnauthorizedException::notLoggedIn(); + } + if (! method_exists($user, 'hasAnyRole') || ! method_exists($user, 'hasAnyPermission')) { throw UnauthorizedException::missingTraitHasRoles($user); } diff --git a/tests/PermissionMiddlewareTest.php b/tests/PermissionMiddlewareTest.php index d8831c309..7059bd3e9 100644 --- a/tests/PermissionMiddlewareTest.php +++ b/tests/PermissionMiddlewareTest.php @@ -8,6 +8,7 @@ use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\Gate; use InvalidArgumentException; +use Laravel\Passport\Passport; use Spatie\Permission\Contracts\Permission; use Spatie\Permission\Exceptions\UnauthorizedException; use Spatie\Permission\Middlewares\PermissionMiddleware; @@ -17,6 +18,8 @@ class PermissionMiddlewareTest extends TestCase { protected $permissionMiddleware; + protected $usePassport = true; + protected function setUp(): void { parent::setUp(); @@ -71,6 +74,32 @@ public function a_user_cannot_access_a_route_protected_by_the_permission_middlew ); } + /** @test */ + public function a_client_cannot_access_a_route_protected_by_the_permission_middleware_of_a_different_guard(): void + { + if ($this->getLaravelVersion() < 9) { + $this->markTestSkipped('requires laravel >= 9'); + } + + // These permissions are created fresh here in reverse order of guard being applied, so they are not "found first" in the db lookup when matching + app(Permission::class)->create(['name' => 'admin-permission2', 'guard_name' => 'web']); + $p1 = app(Permission::class)->create(['name' => 'admin-permission2', 'guard_name' => 'api']); + + Passport::actingAsClient($this->testClient, ['*']); + + $this->testClient->givePermissionTo($p1); + + $this->assertEquals( + 200, + $this->runMiddleware($this->permissionMiddleware, 'admin-permission2', 'api', true) + ); + + $this->assertEquals( + 403, + $this->runMiddleware($this->permissionMiddleware, 'edit-articles2', 'web', true) + ); + } + /** @test */ public function a_super_admin_user_can_access_a_route_protected_by_permission_middleware() { @@ -99,6 +128,23 @@ public function a_user_can_access_a_route_protected_by_permission_middleware_if_ ); } + /** @test */ + public function a_client_can_access_a_route_protected_by_permission_middleware_if_have_this_permission(): void + { + if ($this->getLaravelVersion() < 9) { + $this->markTestSkipped('requires laravel >= 9'); + } + + Passport::actingAsClient($this->testClient, ['*'], 'api'); + + $this->testClient->givePermissionTo('edit-posts'); + + $this->assertEquals( + 200, + $this->runMiddleware($this->permissionMiddleware, 'edit-posts', null, true) + ); + } + /** @test */ public function a_user_can_access_a_route_protected_by_this_permission_middleware_if_have_one_of_the_permissions() { @@ -117,6 +163,28 @@ public function a_user_can_access_a_route_protected_by_this_permission_middlewar ); } + /** @test */ + public function a_client_can_access_a_route_protected_by_this_permission_middleware_if_have_one_of_the_permissions(): void + { + if ($this->getLaravelVersion() < 9) { + $this->markTestSkipped('requires laravel >= 9'); + } + + Passport::actingAsClient($this->testClient, ['*']); + + $this->testClient->givePermissionTo('edit-posts'); + + $this->assertEquals( + 200, + $this->runMiddleware($this->permissionMiddleware, 'edit-news|edit-posts', null, true) + ); + + $this->assertEquals( + 200, + $this->runMiddleware($this->permissionMiddleware, ['edit-news', 'edit-posts'], null, true) + ); + } + /** @test */ public function a_user_cannot_access_a_route_protected_by_the_permission_middleware_if_have_not_has_roles_trait() { @@ -143,6 +211,23 @@ public function a_user_cannot_access_a_route_protected_by_the_permission_middlew ); } + /** @test */ + public function a_client_cannot_access_a_route_protected_by_the_permission_middleware_if_have_a_different_permission(): void + { + if ($this->getLaravelVersion() < 9) { + $this->markTestSkipped('requires laravel >= 9'); + } + + Passport::actingAsClient($this->testClient, ['*']); + + $this->testClient->givePermissionTo('edit-posts'); + + $this->assertEquals( + 403, + $this->runMiddleware($this->permissionMiddleware, 'edit-news', null, true) + ); + } + /** @test */ public function a_user_cannot_access_a_route_protected_by_permission_middleware_if_have_not_permissions() { @@ -154,6 +239,21 @@ public function a_user_cannot_access_a_route_protected_by_permission_middleware_ ); } + /** @test */ + public function a_client_cannot_access_a_route_protected_by_permission_middleware_if_have_not_permissions(): void + { + if ($this->getLaravelVersion() < 9) { + $this->markTestSkipped('requires laravel >= 9'); + } + + Passport::actingAsClient($this->testClient, ['*']); + + $this->assertEquals( + 403, + $this->runMiddleware($this->permissionMiddleware, 'edit-articles|edit-posts', null, true) + ); + } + /** @test */ public function a_user_can_access_a_route_protected_by_permission_middleware_if_has_permission_via_role() { @@ -173,6 +273,29 @@ public function a_user_can_access_a_route_protected_by_permission_middleware_if_ ); } + /** @test */ + public function a_client_can_access_a_route_protected_by_permission_middleware_if_has_permission_via_role(): void + { + if ($this->getLaravelVersion() < 9) { + $this->markTestSkipped('requires laravel >= 9'); + } + + Passport::actingAsClient($this->testClient, ['*']); + + $this->assertEquals( + 403, + $this->runMiddleware($this->permissionMiddleware, 'edit-articles', null, true) + ); + + $this->testClientRole->givePermissionTo('edit-posts'); + $this->testClient->assignRole('clientRole'); + + $this->assertEquals( + 200, + $this->runMiddleware($this->permissionMiddleware, 'edit-posts', null, true) + ); + } + /** @test */ public function the_required_permissions_can_be_fetched_from_the_exception() { @@ -242,6 +365,23 @@ public function user_can_not_access_permission_with_guard_admin_while_login_usin ); } + /** @test */ + public function client_can_not_access_permission_with_guard_admin_while_login_using_default_guard(): void + { + if ($this->getLaravelVersion() < 9) { + $this->markTestSkipped('requires laravel >= 9'); + } + + Passport::actingAsClient($this->testClient, ['*']); + + $this->testClient->givePermissionTo('edit-posts'); + + $this->assertEquals( + 403, + $this->runMiddleware($this->permissionMiddleware, 'edit-posts', 'admin', true) + ); + } + /** @test */ public function user_can_access_permission_with_guard_admin_while_login_using_admin_guard() { diff --git a/tests/RoleMiddlewareTest.php b/tests/RoleMiddlewareTest.php index a28c2de07..25f411ee1 100644 --- a/tests/RoleMiddlewareTest.php +++ b/tests/RoleMiddlewareTest.php @@ -7,6 +7,7 @@ use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Config; use InvalidArgumentException; +use Laravel\Passport\Passport; use Spatie\Permission\Exceptions\UnauthorizedException; use Spatie\Permission\Middlewares\RoleMiddleware; use Spatie\Permission\Tests\TestModels\UserWithoutHasRoles; @@ -15,6 +16,8 @@ class RoleMiddlewareTest extends TestCase { protected $roleMiddleware; + protected $usePassport = true; + protected function setUp(): void { parent::setUp(); @@ -44,6 +47,23 @@ public function a_user_cannot_access_a_route_protected_by_role_middleware_of_ano ); } + /** @test */ + public function a_client_cannot_access_a_route_protected_by_role_middleware_of_another_guard(): void + { + if ($this->getLaravelVersion() < 9) { + $this->markTestSkipped('requires laravel >= 9'); + } + + Passport::actingAsClient($this->testClient, ['*']); + + $this->testClient->assignRole('clientRole'); + + $this->assertEquals( + 403, + $this->runMiddleware($this->roleMiddleware, 'testAdminRole', null, true) + ); + } + /** @test */ public function a_user_can_access_a_route_protected_by_role_middleware_if_have_this_role() { @@ -57,6 +77,23 @@ public function a_user_can_access_a_route_protected_by_role_middleware_if_have_t ); } + /** @test */ + public function a_client_can_access_a_route_protected_by_role_middleware_if_have_this_role(): void + { + if ($this->getLaravelVersion() < 9) { + $this->markTestSkipped('requires laravel >= 9'); + } + + Passport::actingAsClient($this->testClient, ['*']); + + $this->testClient->assignRole('clientRole'); + + $this->assertEquals( + 200, + $this->runMiddleware($this->roleMiddleware, 'clientRole', null, true) + ); + } + /** @test */ public function a_user_can_access_a_route_protected_by_this_role_middleware_if_have_one_of_the_roles() { @@ -75,6 +112,28 @@ public function a_user_can_access_a_route_protected_by_this_role_middleware_if_h ); } + /** @test */ + public function a_client_can_access_a_route_protected_by_this_role_middleware_if_have_one_of_the_roles(): void + { + if ($this->getLaravelVersion() < 9) { + $this->markTestSkipped('requires laravel >= 9'); + } + + Passport::actingAsClient($this->testClient, ['*']); + + $this->testClient->assignRole('clientRole'); + + $this->assertEquals( + 200, + $this->runMiddleware($this->roleMiddleware, 'clientRole|testRole2', null, true) + ); + + $this->assertEquals( + 200, + $this->runMiddleware($this->roleMiddleware, ['testRole2', 'clientRole'], null, true) + ); + } + /** @test */ public function a_user_cannot_access_a_route_protected_by_the_role_middleware_if_have_not_has_roles_trait() { @@ -101,6 +160,23 @@ public function a_user_cannot_access_a_route_protected_by_the_role_middleware_if ); } + /** @test */ + public function a_client_cannot_access_a_route_protected_by_the_role_middleware_if_have_a_different_role(): void + { + if ($this->getLaravelVersion() < 9) { + $this->markTestSkipped('requires laravel >= 9'); + } + + Passport::actingAsClient($this->testClient, ['*']); + + $this->testClient->assignRole(['clientRole']); + + $this->assertEquals( + 403, + $this->runMiddleware($this->roleMiddleware, 'clientRole2', null, true) + ); + } + /** @test */ public function a_user_cannot_access_a_route_protected_by_role_middleware_if_have_not_roles() { @@ -112,6 +188,21 @@ public function a_user_cannot_access_a_route_protected_by_role_middleware_if_hav ); } + /** @test */ + public function a_client_cannot_access_a_route_protected_by_role_middleware_if_have_not_roles(): void + { + if ($this->getLaravelVersion() < 9) { + $this->markTestSkipped('requires laravel >= 9'); + } + + Passport::actingAsClient($this->testClient, ['*']); + + $this->assertEquals( + 403, + $this->runMiddleware($this->roleMiddleware, 'testRole|testRole2', null, true) + ); + } + /** @test */ public function a_user_cannot_access_a_route_protected_by_role_middleware_if_role_is_undefined() { @@ -123,6 +214,21 @@ public function a_user_cannot_access_a_route_protected_by_role_middleware_if_rol ); } + /** @test */ + public function a_client_cannot_access_a_route_protected_by_role_middleware_if_role_is_undefined(): void + { + if ($this->getLaravelVersion() < 9) { + $this->markTestSkipped('requires laravel >= 9'); + } + + Passport::actingAsClient($this->testClient, ['*']); + + $this->assertEquals( + 403, + $this->runMiddleware($this->roleMiddleware, '', null, true) + ); + } + /** @test */ public function the_required_roles_can_be_fetched_from_the_exception() { @@ -192,6 +298,23 @@ public function user_can_not_access_role_with_guard_admin_while_login_using_defa ); } + /** @test */ + public function client_can_not_access_role_with_guard_admin_while_login_using_default_guard(): void + { + if ($this->getLaravelVersion() < 9) { + $this->markTestSkipped('requires laravel >= 9'); + } + + Passport::actingAsClient($this->testClient, ['*']); + + $this->testClient->assignRole('clientRole'); + + $this->assertEquals( + 403, + $this->runMiddleware($this->roleMiddleware, 'clientRole', 'admin', true) + ); + } + /** @test */ public function user_can_access_role_with_guard_admin_while_login_using_admin_guard() { diff --git a/tests/RoleOrPermissionMiddlewareTest.php b/tests/RoleOrPermissionMiddlewareTest.php index 24367e51f..a3de1054d 100644 --- a/tests/RoleOrPermissionMiddlewareTest.php +++ b/tests/RoleOrPermissionMiddlewareTest.php @@ -8,6 +8,7 @@ use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\Gate; use InvalidArgumentException; +use Laravel\Passport\Passport; use Spatie\Permission\Exceptions\UnauthorizedException; use Spatie\Permission\Middlewares\RoleOrPermissionMiddleware; use Spatie\Permission\Tests\TestModels\UserWithoutHasRoles; @@ -16,6 +17,8 @@ class RoleOrPermissionMiddlewareTest extends TestCase { protected $roleOrPermissionMiddleware; + protected $usePassport = true; + protected function setUp(): void { parent::setUp(); @@ -66,6 +69,44 @@ public function a_user_can_access_a_route_protected_by_permission_or_role_middle ); } + /** @test */ + public function a_client_can_access_a_route_protected_by_permission_or_role_middleware_if_has_this_permission_or_role(): void + { + if ($this->getLaravelVersion() < 9) { + $this->markTestSkipped('requires laravel >= 9'); + } + + Passport::actingAsClient($this->testClient, ['*']); + + $this->testClient->assignRole('clientRole'); + $this->testClient->givePermissionTo('edit-posts'); + + $this->assertEquals( + 200, + $this->runMiddleware($this->roleOrPermissionMiddleware, 'clientRole|edit-news|edit-posts', null, true) + ); + + $this->testClient->removeRole('clientRole'); + + $this->assertEquals( + 200, + $this->runMiddleware($this->roleOrPermissionMiddleware, 'clientRole|edit-posts', null, true) + ); + + $this->testClient->revokePermissionTo('edit-posts'); + $this->testClient->assignRole('clientRole'); + + $this->assertEquals( + 200, + $this->runMiddleware($this->roleOrPermissionMiddleware, 'clientRole|edit-posts', null, true) + ); + + $this->assertEquals( + 200, + $this->runMiddleware($this->roleOrPermissionMiddleware, ['clientRole', 'edit-posts'], null, true) + ); + } + /** @test */ public function a_super_admin_user_can_access_a_route_protected_by_permission_or_role_middleware() { @@ -110,6 +151,26 @@ public function a_user_can_not_access_a_route_protected_by_permission_or_role_mi ); } + /** @test */ + public function a_client_can_not_access_a_route_protected_by_permission_or_role_middleware_if_have_not_this_permission_and_role(): void + { + if ($this->getLaravelVersion() < 9) { + $this->markTestSkipped('requires laravel >= 9'); + } + + Passport::actingAsClient($this->testClient, ['*']); + + $this->assertEquals( + 403, + $this->runMiddleware($this->roleOrPermissionMiddleware, 'clientRole|edit-posts', null, true) + ); + + $this->assertEquals( + 403, + $this->runMiddleware($this->roleOrPermissionMiddleware, 'missingRole|missingPermission', null, true) + ); + } + /** @test */ public function use_not_existing_custom_guard_in_role_or_permission() { @@ -140,6 +201,24 @@ public function user_can_not_access_permission_or_role_with_guard_admin_while_lo ); } + /** @test */ + public function client_can_not_access_permission_or_role_with_guard_admin_while_login_using_default_guard(): void + { + if ($this->getLaravelVersion() < 9) { + $this->markTestSkipped('requires laravel >= 9'); + } + + Passport::actingAsClient($this->testClient, ['*']); + + $this->testClient->assignRole('clientRole'); + $this->testClient->givePermissionTo('edit-posts'); + + $this->assertEquals( + 403, + $this->runMiddleware($this->roleOrPermissionMiddleware, 'edit-posts|clientRole', 'admin', true) + ); + } + /** @test */ public function user_can_access_permission_or_role_with_guard_admin_while_login_using_admin_guard() { diff --git a/tests/TestCase.php b/tests/TestCase.php index 3fe775b08..302169f65 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -8,6 +8,7 @@ use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Schema; +use Laravel\Passport\PassportServiceProvider; use Orchestra\Testbench\TestCase as Orchestra; use Spatie\Permission\Contracts\Permission; use Spatie\Permission\Contracts\Role; @@ -15,6 +16,7 @@ use Spatie\Permission\PermissionRegistrar; use Spatie\Permission\PermissionServiceProvider; use Spatie\Permission\Tests\TestModels\Admin; +use Spatie\Permission\Tests\TestModels\Client; use Spatie\Permission\Tests\TestModels\User; abstract class TestCase extends Orchestra @@ -47,6 +49,15 @@ abstract class TestCase extends Orchestra protected static $customMigration; + /** @var bool */ + protected $usePassport = false; + + protected Client $testClient; + + protected \Spatie\Permission\Models\Permission $testClientPermission; + + protected \Spatie\Permission\Models\Role $testClientRole; + protected function setUp(): void { parent::setUp(); @@ -61,6 +72,10 @@ protected function setUp(): void setPermissionsTeamId(1); } + if ($this->usePassport) { + $this->setUpPassport($this->app); + } + $this->setUpRoutes(); } @@ -70,8 +85,11 @@ protected function setUp(): void */ protected function getPackageProviders($app) { - return [ + return $this->getLaravelVersion() < 9 ? [ + PermissionServiceProvider::class, + ] : [ PermissionServiceProvider::class, + PassportServiceProvider::class, ]; } @@ -169,6 +187,23 @@ protected function setUpDatabase($app) $app[Permission::class]->create(['name' => 'Edit News']); } + protected function setUpPassport($app): void + { + if ($this->getLaravelVersion() < 9) { + return; + } + + $app['config']->set('permission.use_passport_client_credentials', true); + $app['config']->set('auth.guards.api', ['driver' => 'passport', 'provider' => 'users']); + + $this->artisan('migrate'); + $this->artisan('passport:install'); + + $this->testClient = Client::create(['name' => 'Test', 'redirect' => 'https://example.com/', 'personal_access_client' => 0, 'password_client' => 0, 'revoked' => 0]); + $this->testClientRole = $app[Role::class]->create(['name' => 'clientRole', 'guard_name' => 'api']); + $this->testClientPermission = $app[Permission::class]->create(['name' => 'edit-posts', 'guard_name' => 'api']); + } + private function prepareMigration() { $migration = str_replace( @@ -227,10 +262,15 @@ public function setUpRoutes(): void } ////// TEST HELPERS - public function runMiddleware($middleware, $permission, $guard = null) + public function runMiddleware($middleware, $permission, $guard = null, bool $client = false) { + $request = new Request; + if ($client) { + $request->headers->set('Authorization', 'Bearer '.str()->random(30)); + } + try { - return $middleware->handle(new Request(), function () { + return $middleware->handle($request, function () { return (new Response())->setContent(''); }, $permission, $guard)->status(); } catch (UnauthorizedException $e) { @@ -254,4 +294,9 @@ public function getRouteResponse() return (new Response())->setContent(''); }; } + + protected function getLaravelVersion() + { + return (float) app()->version(); + } } diff --git a/tests/TestModels/Client.php b/tests/TestModels/Client.php new file mode 100644 index 000000000..a920bddd0 --- /dev/null +++ b/tests/TestModels/Client.php @@ -0,0 +1,21 @@ + Date: Wed, 26 Jul 2023 19:46:31 -0400 Subject: [PATCH 354/648] Document unsetRelations() requirements when switching teams @erikn69 does this need additional clarification? --- docs/basic-usage/teams-permissions.md | 31 +++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/docs/basic-usage/teams-permissions.md b/docs/basic-usage/teams-permissions.md index a97136e8c..d60657377 100644 --- a/docs/basic-usage/teams-permissions.md +++ b/docs/basic-usage/teams-permissions.md @@ -73,6 +73,37 @@ Role::create(['name' => 'reviewer']); The role/permission assignment and removal for teams are the same as without teams, but they take the global `team_id` which is set on login. +## Changing The Active Team ID + +While your middleware will set a user's `team_id` upon login, you may later need to set it to another team for various reasons. The two most common reasons are these: + +### Switching Teams After Login +If your application allows the user to switch between various teams which they belong to, you can activate the roles/permissions for that team by calling `setPermissionsTeamId($new_team_id)` and unsetting relations as described below. + +### Administrating Team Details +You may have created a User-Manager page where you can view the roles/permissions of users on certain teams. For managing that user in each team they belong to, you must also use `setPermissionsTeamId($new_team_id)` to cause lookups to relate to that new team, and unset prior relations as described below. + +### Querying Roles/Permissions for Other Teams +Whenever you switch the active `team_id` using `setPermissionsTeamId()`, you need to `unset` the user's/model's `roles` and `permissions` relations before querying what roles/permissions that user has (`$user->roles`, etc) and before calling any authorization functions (`can()`, `hasPermissionTo()`, `hasRole()`, etc). + +Example: +```php +// set active global team_id +setPermissionsTeamId($new_team_id); + +// $user = Auth::user(); + +// unset cached model relations so new team relations will get reloaded +$user->unsetRelation('roles','permissions'); + +// Now you can check: +$roles = $user->roles; +$hasRole = $user->hasRole('my_role'); +$user->hasPermissionTo('foo'); +$user->can('bar'); +// etc +``` + ## Defining a Super-Admin on Teams Global roles can be assigned to different teams, and `team_id` (which is the primary key of the relationships) is always required. From 2c0f7677451e8ab709d1eaeed6449f43c8babde0 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Mon, 31 Jul 2023 14:37:13 -0400 Subject: [PATCH 355/648] [Docs] For WithoutModelEvents add note to flush cache after seeding --- docs/advanced-usage/seeding.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/advanced-usage/seeding.md b/docs/advanced-usage/seeding.md index d27bc2a1e..c9abac299 100644 --- a/docs/advanced-usage/seeding.md +++ b/docs/advanced-usage/seeding.md @@ -3,9 +3,11 @@ title: Database Seeding weight: 2 --- -## Flush cache before seeding +## Flush cache before/after seeding -You may discover that it is best to flush this package's cache before seeding, to avoid cache conflict errors. +You may discover that it is best to flush this package's cache **BEFORE seeding, to avoid cache conflict errors**. + +And if you use the `WithoutModelEvents` trait in your seeders, flush it **AFTER seeding as well**. ```php // reset cached roles and permissions From 2cc68fdce917d7f7f6c37ec91157225afe9db902 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Mon, 31 Jul 2023 14:40:00 -0400 Subject: [PATCH 356/648] [docs] another note about flushing cache in seeders --- docs/advanced-usage/seeding.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/advanced-usage/seeding.md b/docs/advanced-usage/seeding.md index c9abac299..0f40e7f27 100644 --- a/docs/advanced-usage/seeding.md +++ b/docs/advanced-usage/seeding.md @@ -14,7 +14,7 @@ And if you use the `WithoutModelEvents` trait in your seeders, flush it **AFTER app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions(); ``` -You can do this in the `SetUp()` method of your test suite (see the Testing page in the docs). +You can optionally flush the cache before seeding by using the `SetUp()` method of your test suite (see the Testing page in the docs). Or it can be done directly in a seeder class, as shown below. @@ -97,6 +97,8 @@ foreach ($permissionIdsByRole as $role => $permissionIds) { ])->toArray() ); } + +// and also add the command to flush the cache again now after doing all these inserts ``` **CAUTION**: ANY TIME YOU DIRECTLY RUN DB QUERIES you are bypassing cache-control features. So you will need to manually flush the package cache AFTER running direct DB queries, even in a seeder. From 0c2422b4b295a0f936a12d118317091ca9267159 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 15 Aug 2023 17:11:51 -0400 Subject: [PATCH 357/648] [Docs] Show a way to use a single guard for all roles/permissions --- docs/basic-usage/multiple-guards.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/basic-usage/multiple-guards.md b/docs/basic-usage/multiple-guards.md index 08e44d1d2..9a1911906 100644 --- a/docs/basic-usage/multiple-guards.md +++ b/docs/basic-usage/multiple-guards.md @@ -12,6 +12,16 @@ However, when using multiple guards they will act like namespaces for your permi Note that this package requires you to register a permission name for each guard you want to authenticate with. So, "edit-article" would have to be created multiple times for each guard your app uses. An exception will be thrown if you try to authenticate against a non-existing permission+guard combination. Same for roles. > **Tip**: If your app uses only a single guard, but is not `web` (Laravel's default, which shows "first" in the auth config file) then change the order of your listed guards in your `config/auth.php` to list your primary guard as the default and as the first in the list of defined guards. While you're editing that file, best to remove any guards you don't use, too. +> +> OR you could use the suggestion below to force the use of a single guard: + +## Forcing Use Of A Single Guard + +If your app structure does NOT differentiate between guards when it comes to roles/permissions, (ie: if ALL your roles/permissions are the SAME for ALL guards), you can override the `getDefaultGuardName` function by adding it to your User model, and specifying your desired guard_name. Then you only need to create roles/permissions for that single guard_name, not duplicating them. The example here sets it to `web`, but use whatever your application's default is: + +```php + protected function getDefaultGuardName(): string { return 'web'; } +```` ## Using permissions and roles with multiple guards From feb301470b7ca09e77cca8d2dec662a89d2770a8 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Wed, 16 Aug 2023 19:42:19 -0500 Subject: [PATCH 358/648] [V6] Register OctaneReloadPermissions listener for Laravel Octane (#2403) * Register OctaneReloadPermissions listener for Laravel Octane --------- Co-authored-by: Chris Brown --- config/permission.php | 8 ++++++++ src/Listeners/OctaneReloadPermissions.php | 13 +++++++++++++ src/PermissionServiceProvider.php | 19 +++++++++++++++++++ 3 files changed, 40 insertions(+) create mode 100644 src/Listeners/OctaneReloadPermissions.php diff --git a/config/permission.php b/config/permission.php index b98b24148..0507baec5 100644 --- a/config/permission.php +++ b/config/permission.php @@ -103,6 +103,14 @@ 'register_permission_check_method' => true, + /* + * When set to true, the Spatie\Permission\Listeners\OctaneReloadPermissions listener will be registered + * on the Laravel\Octane\Events\OperationTerminated event, this will refresh permissions on every + * TickTerminated, TaskTerminated and RequestTerminated + * NOTE: This should not be needed in most cases, but an Octane/Vapor combination benefited from it. + */ + 'register_octane_reset_listener' => false, + /* * Teams Feature. * When set to true the package implements teams using the 'team_foreign_key'. diff --git a/src/Listeners/OctaneReloadPermissions.php b/src/Listeners/OctaneReloadPermissions.php new file mode 100644 index 000000000..6f4544e5e --- /dev/null +++ b/src/Listeners/OctaneReloadPermissions.php @@ -0,0 +1,13 @@ +sandbox->make(PermissionRegistrar::class)->clearPermissionsCollection(); + } +} diff --git a/src/PermissionServiceProvider.php b/src/PermissionServiceProvider.php index 639e7faa7..f2643cef0 100644 --- a/src/PermissionServiceProvider.php +++ b/src/PermissionServiceProvider.php @@ -3,6 +3,7 @@ namespace Spatie\Permission; use Illuminate\Contracts\Auth\Access\Gate; +use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Contracts\Foundation\Application; use Illuminate\Filesystem\Filesystem; use Illuminate\Routing\Route; @@ -12,6 +13,7 @@ use Illuminate\View\Compilers\BladeCompiler; use Spatie\Permission\Contracts\Permission as PermissionContract; use Spatie\Permission\Contracts\Role as RoleContract; +use Spatie\Permission\Listeners\OctaneReloadPermissions; class PermissionServiceProvider extends ServiceProvider { @@ -25,6 +27,8 @@ public function boot() $this->registerModelBindings(); + $this->registerOctaneListener(); + $this->callAfterResolving(Gate::class, function (Gate $gate, Application $app) { if ($this->app['config']->get('permission.register_permission_check_method')) { /** @var PermissionRegistrar $permissionLoader */ @@ -85,6 +89,21 @@ protected function registerCommands(): void ]); } + protected function registerOctaneListener(): void + { + if ($this->app->runningInConsole() || ! $this->app['config']->get('permission.register_octane_reset_listener')) { + return; + } + + if (! $this->app['config']->get('octane.listeners')) { + return; + } + + $dispatcher = $this->app[Dispatcher::class]; + // @phpstan-ignore-next-line + $dispatcher->listen(\Laravel\Octane\Events\OperationTerminated::class, OctaneReloadPermissions::class); + } + protected function registerModelBindings(): void { $this->app->bind(PermissionContract::class, fn ($app) => $app->make($app->config['permission.models.permission'])); From 7920916de2d2c13745e190181ce5570830991ceb Mon Sep 17 00:00:00 2001 From: Denis Kovtunenko Date: Sat, 19 Aug 2023 08:04:31 +0700 Subject: [PATCH 359/648] Update uuid.md (#2438) * Update uuid.md Resolve problems with Laravel 10. Ex version of uuid.md doesn`t correct working. In this manual -- all good --------- Co-authored-by: Chris Brown --- docs/advanced-usage/uuid.md | 167 +++++++++++++++++++++++++++--------- 1 file changed, 125 insertions(+), 42 deletions(-) diff --git a/docs/advanced-usage/uuid.md b/docs/advanced-usage/uuid.md index 517cf7eb5..1f54e2e1e 100644 --- a/docs/advanced-usage/uuid.md +++ b/docs/advanced-usage/uuid.md @@ -3,61 +3,67 @@ title: UUID weight: 7 --- -If you're using UUIDs or GUIDs for your User models there are a few considerations to note. +If you're using UUIDs for your User models there are a few considerations to note. > THIS IS NOT A FULL LESSON ON HOW TO IMPLEMENT UUIDs IN YOUR APP. Since each UUID implementation approach is different, some of these may or may not benefit you. As always, your implementation may vary. - ## Migrations -You will probably want to update the `create_permission_tables.php` migration: +You will need to update the `create_permission_tables.php` migration: -If your User models are using `uuid` instead of `unsignedBigInteger` then you'll need to reflect the change in the migration provided by this package. Something like this would be typical, for both `model_has_permissions` and `model_has_roles` tables: +**User Models using UUIDs** +If your User models are using `uuid` instead of `unsignedBigInteger` then you'll need to reflect the change in the migration provided by this package. Something like the following would be typical, for **both** `model_has_permissions` and `model_has_roles` tables: ```diff +// note: this is done in two places in the default migration file, so edit both places: - $table->unsignedBigInteger($columnNames['model_morph_key']) + $table->uuid($columnNames['model_morph_key']) ``` -OPTIONAL: If you also want the roles and permissions to use a UUID for their `id` value, then you'll need to also change the id fields accordingly, and manually set the primary key. LEAVE THE FIELD NAME AS `id` unless you also change it in dozens of other places. +**Roles and Permissions using UUIDS** +If you also want the roles and permissions to use a UUID for their `id` value, then you'll need to change the id fields accordingly, and manually set the primary key. ```diff Schema::create($tableNames['permissions'], function (Blueprint $table) { -- $table->bigIncrements('id'); -+ $table->uuid('id'); - $table->string('name'); - $table->string('guard_name'); - $table->timestamps(); - -+ $table->primary('id'); +- $table->bigIncrements('id'); // permission id ++ $table->uuid('id')->primary()->unique(); // permission id +//... }); Schema::create($tableNames['roles'], function (Blueprint $table) { -- $table->bigIncrements('id'); -+ $table->uuid('id'); - $table->string('name'); - $table->string('guard_name'); - $table->timestamps(); - -+ $table->primary('id'); +- $table->bigIncrements('id'); // role id ++ $table->uuid('id')->primary()->unique(); // role id +//... }); Schema::create($tableNames['model_has_permissions'], function (Blueprint $table) use ($tableNames, $columnNames) { -- $table->bigIncrements('permission_id'); -+ $table->uuid('permission_id'); - ... +- $table->unsignedBigInteger(PermissionRegistrar::$pivotPermission); ++ $table->uuid(PermissionRegistrar::$pivotPermission); + $table->string('model_type'); +//... + $table->foreign(PermissionRegistrar::$pivotPermission) +- ->references('id') // permission id ++ ->references('uuid') // permission id + ->on($tableNames['permissions']) + ->onDelete('cascade'); +//... Schema::create($tableNames['model_has_roles'], function (Blueprint $table) use ($tableNames, $columnNames) { -- $table->bigIncrements('role_id'); -+ $table->uuid('role_id'); - ... +- $table->unsignedBigInteger(PermissionRegistrar::$pivotRole); ++ $table->uuid(PermissionRegistrar::$pivotRole); +//... + $table->foreign(PermissionRegistrar::$pivotRole) +- ->references('id') // role id ++ ->references('uuid') // role id + ->on($tableNames['roles']) + ->onDelete('cascade');//... Schema::create($tableNames['role_has_permissions'], function (Blueprint $table) use ($tableNames) { -- $table->bigIncrements('permission_id'); -- $table->bigIncrements('role_id'); -+ $table->uuid('permission_id'); -+ $table->uuid('role_id'); +- $table->unsignedBigInteger(PermissionRegistrar::$pivotPermission); +- $table->unsignedBigInteger(PermissionRegistrar::$pivotRole); ++ $table->uuid(PermissionRegistrar::$pivotPermission); ++ $table->uuid(PermissionRegistrar::$pivotRole); ``` @@ -65,26 +71,103 @@ OPTIONAL: If you also want the roles and permissions to use a UUID for their `id You might want to change the pivot table field name from `model_id` to `model_uuid`, just for semantic purposes. For this, in the configuration file edit `column_names.model_morph_key`: -- OPTIONAL: Change to `model_uuid` instead of the default `model_id`. (The default of `model_id` is shown in this snippet below. Change it to match your needs.) - +- OPTIONAL: Change to `model_uuid` instead of the default `model_id`. +```diff 'column_names' => [ - /* - * Change this if you want to name the related model primary key other than - * `model_id`. - * - * For example, this would be nice if your primary keys are all UUIDs. In - * that case, name this `model_uuid`. - */ - 'model_morph_key' => 'model_id', + /* + * Change this if you want to name the related pivots other than defaults + */ + 'role_pivot_key' => null, //default 'role_id', + 'permission_pivot_key' => null, //default 'permission_id', + + /* + * Change this if you want to name the related model primary key other than + * `model_id`. + * + * For example, this would be nice if your primary keys are all UUIDs. In + * that case, name this `model_uuid`. + */ +- 'model_morph_key' => 'model_id', ++ 'model_morph_key' => 'model_uuid', ], +``` - If you extend the models into your app, be sure to list those models in your configuration file. See the Extending section of the documentation and the Models section below. ## Models If you want all the role/permission objects to have a UUID instead of an integer, you will need to Extend the default Role and Permission models into your own namespace in order to set some specific properties. (See the Extending section of the docs, where it explains requirements of Extending, as well as the configuration settings you need to update.) -- You likely want to set `protected $keyType = 'string';` so Laravel handles joins as strings and doesn't cast to integer. -- OPTIONAL: If you changed the field name in your migrations, you must set `protected $primaryKey = 'uuid';` to match. -- Usually for UUID you will also set `public $incrementing = false;`. Remove it if it causes problems for you. +Examples: + +Create new models, which extend the Role and Permission models of this package, and add the HasUuids trait (available since Laravel 9): +```bash +php artisan make:model Role +php artisan make:model Permission +``` + +`App\Model\Role.php` +```php + [ + + /* + * When using the "HasPermissions" trait from this package, we need to know which + * Eloquent model should be used to retrieve your permissions. Of course, it + * is often just the "Permission" model but you may use whatever you like. + * + * The model you want to use as a Permission model needs to implement the + * `Spatie\Permission\Contracts\Permission` contract. + */ + +- 'permission' => Spatie\Permission\Models\Permission::class ++ 'permission' => App\Models\Permission::class, + + /* + * When using the "HasRoles" trait from this package, we need to know which + * Eloquent model should be used to retrieve your roles. Of course, it + * is often just the "Role" model but you may use whatever you like. + * + * The model you want to use as a Role model needs to implement the + * `Spatie\Permission\Contracts\Role` contract. + */ + +- 'role' => Spatie\Permission\Models\Role::class, ++ 'role' => App\Models\Role::class, + + ], +``` + It is common to use a trait to handle the $keyType and $incrementing settings, as well as add a boot event trigger to ensure new records are assigned a uuid. You would `use` this trait in your User and extended Role/Permission models. An example `UuidTrait` is shown here for inspiration. Adjust to suit your needs. From 188bcda43c2aad39b9287770e5418d0523fcfa80 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 18 Aug 2023 21:12:05 -0400 Subject: [PATCH 360/648] [docs] encourage HasUuids trait instead of bespoke code --- docs/advanced-usage/uuid.md | 74 ++++--------------------------------- 1 file changed, 7 insertions(+), 67 deletions(-) diff --git a/docs/advanced-usage/uuid.md b/docs/advanced-usage/uuid.md index 1f54e2e1e..81fe7d2fe 100644 --- a/docs/advanced-usage/uuid.md +++ b/docs/advanced-usage/uuid.md @@ -27,13 +27,13 @@ If you also want the roles and permissions to use a UUID for their `id` value, t ```diff Schema::create($tableNames['permissions'], function (Blueprint $table) { - $table->bigIncrements('id'); // permission id -+ $table->uuid('id')->primary()->unique(); // permission id ++ $table->uuid('uuid')->primary()->unique(); // permission id //... }); Schema::create($tableNames['roles'], function (Blueprint $table) { - $table->bigIncrements('id'); // role id -+ $table->uuid('id')->primary()->unique(); // role id ++ $table->uuid('uuid')->primary()->unique(); // role id //... }); @@ -98,7 +98,7 @@ If you want all the role/permission objects to have a UUID instead of an integer Examples: -Create new models, which extend the Role and Permission models of this package, and add the HasUuids trait (available since Laravel 9): +Create new models, which extend the Role and Permission models of this package, and add Laravel's `HasUuids` trait (available since Laravel 9): ```bash php artisan make:model Role php artisan make:model Permission @@ -107,7 +107,6 @@ php artisan make:model Permission `App\Model\Role.php` ```php keyType = 'string'; - $model->incrementing = false; - - $model->{$model->getKeyName()} = $model->{$model->getKeyName()} ?: (string) Str::orderedUuid(); - }); - } - - public function getIncrementing() - { - return false; - } - - public function getKeyType() - { - return 'string'; - } - } -``` - - -## User Models -> Troubleshooting tip: In the ***Prerequisites*** section of the docs we remind you that your User model must implement the `Illuminate\Contracts\Auth\Access\Authorizable` contract so that the Gate features are made available to the User object. -In the default User model provided with Laravel, this is done by extending another model (aliased to `Authenticatable`), which extends the base Eloquent model. -However, your app's UUID implementation may need to override that in order to set some of the properties mentioned in the Models section above. - -If you are running into difficulties, you may want to double-check whether your User model is doing UUIDs consistent with other parts of your app. - - -# REMINDER: - -> THIS IS NOT A FULL LESSON ON HOW TO IMPLEMENT UUIDs IN YOUR APP. - -Again, since each UUID implementation approach is different, some of these may or may not benefit you. As always, your implementation may vary. - - - -## Packages -There are many packages offering UUID features for Eloquent models. You may want to explore whether these are of value to you in your study of implementing UUID in your applications: - -https://github.com/JamesHemery/laravel-uuid -https://github.com/jamesmills/eloquent-uuid -https://github.com/goldspecdigital/laravel-eloquent-uuid -https://github.com/michaeldyrynda/laravel-model-uuid - -Remember: always make sure you understand what a package is doing before you use it! If it's doing "more than what you need" then you're adding more complexity to your application, as well as more things to test and support! From 04b3230c157f296ac3580612682d70b4c8969fdc Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 18 Aug 2023 21:14:37 -0400 Subject: [PATCH 361/648] [docs] minor edits to uuid --- docs/advanced-usage/uuid.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/advanced-usage/uuid.md b/docs/advanced-usage/uuid.md index 81fe7d2fe..2209159d9 100644 --- a/docs/advanced-usage/uuid.md +++ b/docs/advanced-usage/uuid.md @@ -10,7 +10,7 @@ If you're using UUIDs for your User models there are a few considerations to not Since each UUID implementation approach is different, some of these may or may not benefit you. As always, your implementation may vary. ## Migrations -You will need to update the `create_permission_tables.php` migration: +You will need to update the `create_permission_tables.php` migration after creating it with `php artisan vendor:publish`. After making your edits, be sure to run the migration! **User Models using UUIDs** If your User models are using `uuid` instead of `unsignedBigInteger` then you'll need to reflect the change in the migration provided by this package. Something like the following would be typical, for **both** `model_has_permissions` and `model_has_roles` tables: From 8eb76af0115208052bff3233402e2ccd46d62775 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Mon, 21 Aug 2023 11:21:08 -0400 Subject: [PATCH 362/648] [Docs] Simplify instructions --- docs/installation-laravel.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/installation-laravel.md b/docs/installation-laravel.md index e1e3d5792..54894042d 100644 --- a/docs/installation-laravel.md +++ b/docs/installation-laravel.md @@ -43,28 +43,30 @@ Package Version | Laravel Version php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" ``` -6. NOTE: **If you are using UUIDs**, see the Advanced section of the docs on UUID steps, before you continue. It explains some changes you may want to make to the migrations and config file before continuing. It also mentions important considerations after extending this package's models for UUID capability. +6. BEFORE RUNNING MIGRATIONS - **If you are going to use the TEAMS features**, you must update your [`config/permission.php` config file](https://github.com/spatie/laravel-permission/blob/main/config/permission.php) and set `'teams' => true,`; and in your database if you want to use a custom foreign key for teams you must change `team_foreign_key`. + - **If you are using UUIDs**, see the Advanced section of the docs on UUID steps, before you continue. It explains some changes you may want to make to the migrations and config file before continuing. It also mentions important considerations after extending this package's models for UUID capability. -7. NOTE: If you are using MySQL 8, look at the migration files for notes about MySQL 8 to set/limit the index key length, and edit accordingly. + - **If you are going to use the TEAMS features**, you must update your [`config/permission.php` config file](https://github.com/spatie/laravel-permission/blob/main/config/permission.php) and set `'teams' => true,`; and in your database if you want to use a custom foreign key for teams you must change `team_foreign_key`. -8. **Clear your config cache**. This package requires access to the `permission` config. Generally it's bad practice to do config-caching in a development environment. If you've been caching configurations locally, clear your config cache with either of these commands: + - **If you are using MySQL 8**, look at the migration files for notes about MySQL 8 to set/limit the index key length, and edit accordingly. + +7. **Clear your config cache**. This package requires access to the `permission` config settings in order to run migrations. If you've been caching configurations locally, clear your config cache with either of these commands: php artisan optimize:clear # or php artisan config:clear -9. **Run the migrations**: After the config and migration have been published and configured, you can create the tables for this package by running: +8. **Run the migrations**: After the config and migration have been published and configured, you can create the tables for this package by running: php artisan migrate -10. **Add the necessary trait to your User model**: +9. **Add the necessary trait to your User model**: // The User model requires this trait use HasRoles; - Consult the **Basic Usage** section of the docs to get started using the features of this package. +10. Consult the **Basic Usage** section of the docs to get started using the features of this package. . From e915a89b25bdbf99d8bbad656e975486e177a843 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Mon, 21 Aug 2023 11:24:52 -0400 Subject: [PATCH 363/648] [Docs] Simplify instructions --- docs/installation-laravel.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/installation-laravel.md b/docs/installation-laravel.md index 54894042d..8ace927b3 100644 --- a/docs/installation-laravel.md +++ b/docs/installation-laravel.md @@ -47,7 +47,9 @@ Package Version | Laravel Version - **If you are using UUIDs**, see the Advanced section of the docs on UUID steps, before you continue. It explains some changes you may want to make to the migrations and config file before continuing. It also mentions important considerations after extending this package's models for UUID capability. - - **If you are going to use the TEAMS features**, you must update your [`config/permission.php` config file](https://github.com/spatie/laravel-permission/blob/main/config/permission.php) and set `'teams' => true,`; and in your database if you want to use a custom foreign key for teams you must change `team_foreign_key`. + - **If you are going to use the TEAMS features** you must update your [`config/permission.php` config file](https://github.com/spatie/laravel-permission/blob/main/config/permission.php): + - must set `'teams' => true,` + - and (optional) you may set `team_foreign_key` name in the config file if you want to use a custom foreign key in your database for teams - **If you are using MySQL 8**, look at the migration files for notes about MySQL 8 to set/limit the index key length, and edit accordingly. From 9e7e4c23d0641e81f389ec35fd468a9ea0957e58 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Mon, 21 Aug 2023 11:31:31 -0400 Subject: [PATCH 364/648] Add guard name to exceptions (#2481) * Add guard_name to DoesNotExist exceptions It's much easier to troubleshoot guard-related issues when the guard is included in the exception. --- src/Exceptions/PermissionDoesNotExist.php | 11 ++++++++--- src/Exceptions/RoleDoesNotExist.php | 12 ++++++++---- src/Models/Role.php | 4 ++-- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/Exceptions/PermissionDoesNotExist.php b/src/Exceptions/PermissionDoesNotExist.php index e6d7c101e..b451ace6b 100644 --- a/src/Exceptions/PermissionDoesNotExist.php +++ b/src/Exceptions/PermissionDoesNotExist.php @@ -6,13 +6,18 @@ class PermissionDoesNotExist extends InvalidArgumentException { - public static function create(string $permissionName, string $guardName = '') + public static function create(string $permissionName, ?string $guardName) { return new static("There is no permission named `{$permissionName}` for guard `{$guardName}`."); } - public static function withId(int $permissionId, string $guardName = '') + /** + * @param int|string $permissionId + * @param string $guardName + * @return static + */ + public static function withId($permissionId, ?string $guardName) { - return new static("There is no [permission] with id `{$permissionId}` for guard `{$guardName}`."); + return new static("There is no [permission] with ID `{$permissionId}` for guard `{$guardName}`."); } } diff --git a/src/Exceptions/RoleDoesNotExist.php b/src/Exceptions/RoleDoesNotExist.php index cee34e146..a4871c665 100644 --- a/src/Exceptions/RoleDoesNotExist.php +++ b/src/Exceptions/RoleDoesNotExist.php @@ -6,13 +6,17 @@ class RoleDoesNotExist extends InvalidArgumentException { - public static function named(string $roleName) + public static function named(string $roleName, ?string $guardName) { - return new static("There is no role named `{$roleName}`."); + return new static("There is no role named `{$roleName}` for guard `{$guardName}`."); } - public static function withId(int $roleId) + /** + * @param int|string $roleId + * @return static + */ + public static function withId($roleId, ?string $guardName) { - return new static("There is no role with id `{$roleId}`."); + return new static("There is no role with ID `{$roleId}` for guard `{$guardName}`."); } } diff --git a/src/Models/Role.php b/src/Models/Role.php index 025147864..d96102a2f 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -103,7 +103,7 @@ public static function findByName(string $name, $guardName = null): RoleContract $role = static::findByParam(['name' => $name, 'guard_name' => $guardName]); if (! $role) { - throw RoleDoesNotExist::named($name); + throw RoleDoesNotExist::named($name, $guardName); } return $role; @@ -123,7 +123,7 @@ public static function findById($id, $guardName = null): RoleContract $role = static::findByParam([(new static())->getKeyName() => $id, 'guard_name' => $guardName]); if (! $role) { - throw RoleDoesNotExist::withId($id); + throw RoleDoesNotExist::withId($id, $guardName); } return $role; From 6cbe27eba4a84d741abbfe4fec0eb080bf076e04 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Mon, 21 Aug 2023 11:47:44 -0400 Subject: [PATCH 365/648] Update middleware.md --- docs/basic-usage/middleware.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/basic-usage/middleware.md b/docs/basic-usage/middleware.md index 48a5ceee1..37f1dd850 100644 --- a/docs/basic-usage/middleware.md +++ b/docs/basic-usage/middleware.md @@ -1,5 +1,5 @@ --- -title: Using a middleware +title: Using a Middleware weight: 11 --- @@ -45,7 +45,7 @@ protected $middlewareAliases = [ Then you can protect your routes using middleware rules: ```php -Route::group(['middleware' => ['role:super-admin']], function () { +Route::group(['middleware' => ['role:manager']], function () { // }); @@ -53,7 +53,7 @@ Route::group(['middleware' => ['permission:publish articles']], function () { // }); -Route::group(['middleware' => ['role:super-admin','permission:publish articles']], function () { +Route::group(['middleware' => ['role:manager','permission:publish articles']], function () { // }); @@ -65,7 +65,7 @@ Route::group(['middleware' => ['role_or_permission:publish articles']], function You can specify multiple roles or permissions with a `|` (pipe) character, which is treated as `OR`: ```php -Route::group(['middleware' => ['role:super-admin|writer']], function () { +Route::group(['middleware' => ['role:manager|writer']], function () { // }); @@ -73,7 +73,7 @@ Route::group(['middleware' => ['permission:publish articles|edit articles']], fu // }); -Route::group(['middleware' => ['role_or_permission:super-admin|edit articles']], function () { +Route::group(['middleware' => ['role_or_permission:manager|edit articles']], function () { // }); ``` @@ -85,14 +85,14 @@ You can protect your controllers similarly, by setting desired middleware in the ```php public function __construct() { - $this->middleware(['role:super-admin','permission:publish articles|edit articles']); + $this->middleware(['role:manager','permission:publish articles|edit articles']); } ``` ```php public function __construct() { - $this->middleware(['role_or_permission:super-admin|edit articles']); + $this->middleware(['role_or_permission:manager|edit articles']); } ``` @@ -104,7 +104,7 @@ All of the middlewares can also be applied by calling the static `using` method, which accepts either a `|`-separated string or an array as input. ```php -Route::group(['middleware' => [\Spatie\Permission\Middlewares\RoleMiddleware::using('super-admin')]], function () { +Route::group(['middleware' => [\Spatie\Permission\Middlewares\RoleMiddleware::using('manager')]], function () { // }); @@ -112,7 +112,7 @@ Route::group(['middleware' => [\Spatie\Permission\Middlewares\PermissionMiddlewa // }); -Route::group(['middleware' => [\Spatie\Permission\Middlewares\RoleOrPermissionMiddleware::using(['super-admin', 'edit articles'])]], function () { +Route::group(['middleware' => [\Spatie\Permission\Middlewares\RoleOrPermissionMiddleware::using(['manager', 'edit articles'])]], function () { // }); ``` From 06714c7da6eeb1ae0947734692a684fb259fb864 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Mon, 21 Aug 2023 11:50:18 -0400 Subject: [PATCH 366/648] [Docs] Specifying a guard in middleware calls --- docs/basic-usage/middleware.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/basic-usage/middleware.md b/docs/basic-usage/middleware.md index 37f1dd850..e7f2ea773 100644 --- a/docs/basic-usage/middleware.md +++ b/docs/basic-usage/middleware.md @@ -49,6 +49,11 @@ Route::group(['middleware' => ['role:manager']], function () { // }); +// for a specific guard: +Route::group(['middleware' => ['role:manager,api']], function () { + // +}); + Route::group(['middleware' => ['permission:publish articles']], function () { // }); @@ -73,6 +78,11 @@ Route::group(['middleware' => ['permission:publish articles|edit articles']], fu // }); +// for a specific guard +Route::group(['middleware' => ['permission:publish articles|edit articles,api']], function () { + // +}); + Route::group(['middleware' => ['role_or_permission:manager|edit articles']], function () { // }); From 025d56b68e2eec3aa76734c771a18b5fe09f6ea6 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Mon, 21 Aug 2023 12:56:17 -0400 Subject: [PATCH 367/648] Drop PHP 7.4 support (#2485) (Note: Laravel 8.12 minimum is required for using PHP 8.0) --- .../{run-tests-L8.yml => run-tests.yml} | 20 ++++++++----------- composer.json | 12 +++++------ docs/installation-laravel.md | 2 +- 3 files changed, 15 insertions(+), 19 deletions(-) rename .github/workflows/{run-tests-L8.yml => run-tests.yml} (78%) diff --git a/.github/workflows/run-tests-L8.yml b/.github/workflows/run-tests.yml similarity index 78% rename from .github/workflows/run-tests-L8.yml rename to .github/workflows/run-tests.yml index 4d8f8e774..f4d0b4e92 100644 --- a/.github/workflows/run-tests-L8.yml +++ b/.github/workflows/run-tests.yml @@ -9,23 +9,19 @@ jobs: strategy: fail-fast: false matrix: - php: [8.2, 8.1, 8.0, 7.4] - laravel: [10.*, 9.*, 8.*] + php: [8.2, 8.1, 8.0] + laravel: ["^10.0", "^9.0", "^8.12"] dependency-version: [prefer-lowest, prefer-stable] include: - - laravel: 10.* + - laravel: "^10.0" testbench: 8.* - - laravel: 9.* + - laravel: "^9.0" testbench: 7.* - - laravel: 8.* - testbench: 6.23 + - laravel: "^8.12" + testbench: "^6.23" exclude: - - laravel: 10.* + - laravel: "^10.0" php: 8.0 - - laravel: 10.* - php: 7.4 - - laravel: 9.* - php: 7.4 name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }} @@ -42,7 +38,7 @@ jobs: - name: Install dependencies (remove passport) run: composer remove --dev laravel/passport --no-interaction --no-update - if: matrix.laravel == '8.*' + if: matrix.laravel == '^8.12' - name: Install dependencies run: | diff --git a/composer.json b/composer.json index 4e31db50e..e7af6faa2 100644 --- a/composer.json +++ b/composer.json @@ -22,15 +22,15 @@ ], "homepage": "https://github.com/spatie/laravel-permission", "require": { - "php": "^7.4|^8.0", - "illuminate/auth": "^8.0|^9.0|^10.0", - "illuminate/container": "^8.0|^9.0|^10.0", - "illuminate/contracts": "^8.0|^9.0|^10.0", - "illuminate/database": "^8.0|^9.0|^10.0" + "php": "^8.0", + "illuminate/auth": "^8.12|^9.0|^10.0", + "illuminate/container": "^8.12|^9.0|^10.0", + "illuminate/contracts": "^8.12|^9.0|^10.0", + "illuminate/database": "^8.12|^9.0|^10.0" }, "require-dev": { "laravel/passport": "^11.0", - "orchestra/testbench": "^6.0|^7.0|^8.0", + "orchestra/testbench": "^6.23|^7.0|^8.0", "phpunit/phpunit": "^9.4" }, "minimum-stability": "dev", diff --git a/docs/installation-laravel.md b/docs/installation-laravel.md index 8ace927b3..21e31edba 100644 --- a/docs/installation-laravel.md +++ b/docs/installation-laravel.md @@ -9,7 +9,7 @@ This package can be used with Laravel 6.0 or higher. Package Version | Laravel Version ----------------|----------- - ^6.0 | 8,9,10 + ^6.0 | 8,9,10 (PHP 8.0+) ^5.8 | 7,8,9,10 ^5.7 | 7,8,9 ^5.4-^5.6 | 7,8 From 4cb45949fabb6133ecb83a6299e47e7ffc16cc27 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Mon, 21 Aug 2023 13:25:47 -0400 Subject: [PATCH 368/648] Update contracts to allow for UUID (#2480) * Update contracts to allow for UUID --- docs/upgrading.md | 2 +- src/Contracts/Permission.php | 16 ++++++++-------- src/Contracts/Role.php | 15 ++++++--------- src/Models/Permission.php | 10 +++------- src/Models/Role.php | 13 ++++--------- 5 files changed, 22 insertions(+), 34 deletions(-) diff --git a/docs/upgrading.md b/docs/upgrading.md index f44ec1172..3709250ff 100644 --- a/docs/upgrading.md +++ b/docs/upgrading.md @@ -32,7 +32,7 @@ There are a few breaking-changes when upgrading to v6, but most of them won't af eg: if you have a custom model you will need to make changes, including accessing the model using `$this->permissionClass::` syntax (eg: using `::` instead of `->`) in all the overridden methods that make use of the models. Be sure to compare your custom models with originals to see what else may have changed. -2. If you have a custom Role model and (in the rare case that you might) have overridden the `hasPermissionTo()` method in it, you will need to update its method signature to `hasPermissionTo($permission, $guardName = null):bool`. See PR #2380. +2. Model and Contract/Interface updates. Both the Permission and Role contracts have been updated with syntax changes to method signatures, so if you have implemented those contracts in any models, you will need to update the function signatures. Further, if you have extended the Role or Permission models you will need to check any methods you have overridden and update method signatures. See PR #2380 and #2480 especially. 3. Migrations will need to be upgraded. (They have been updated to anonymous-class syntax that was introduced in Laravel 8, AND some structural coding changes in the registrar class changed the way we extracted configuration settings in the migration files.) If you had not customized it from the original then replacing the contents of the file should be straightforward. Usually the only customization is if you've switched to UUIDs or customized MySQL index name lengths. **If you get the following error, it means your migration file needs upgrading: `Error: Access to undeclared static property Spatie\Permission\PermissionRegistrar::$pivotPermission`** diff --git a/src/Contracts/Permission.php b/src/Contracts/Permission.php index 8b3d14e18..a2444adeb 100644 --- a/src/Contracts/Permission.php +++ b/src/Contracts/Permission.php @@ -5,9 +5,9 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany; /** - * @property int $id + * @property int|string $id * @property string $name - * @property string $guard_name + * @property string|null $guard_name * * @mixin \Spatie\Permission\Models\Permission */ @@ -21,25 +21,25 @@ public function roles(): BelongsToMany; /** * Find a permission by its name. * - * @param string|null $guardName + * @return \Spatie\Permission\Contracts\Permission * * @throws \Spatie\Permission\Exceptions\PermissionDoesNotExist */ - public static function findByName(string $name, $guardName): self; + public static function findByName(string $name, ?string $guardName): self; /** * Find a permission by its id. * - * @param string|null $guardName + * @return \Spatie\Permission\Contracts\Permission * * @throws \Spatie\Permission\Exceptions\PermissionDoesNotExist */ - public static function findById(int $id, $guardName): self; + public static function findById(int|string $id, ?string $guardName): self; /** * Find or Create a permission by its name and guard name. * - * @param string|null $guardName + * @return \Spatie\Permission\Contracts\Permission */ - public static function findOrCreate(string $name, $guardName): self; + public static function findOrCreate(string $name, ?string $guardName): self; } diff --git a/src/Contracts/Role.php b/src/Contracts/Role.php index 04009de37..5745dd6b3 100644 --- a/src/Contracts/Role.php +++ b/src/Contracts/Role.php @@ -5,9 +5,9 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany; /** - * @property int $id + * @property int|string $id * @property string $name - * @property string $guard_name + * @property string|null $guard_name * * @mixin \Spatie\Permission\Models\Role */ @@ -21,35 +21,32 @@ public function permissions(): BelongsToMany; /** * Find a role by its name and guard name. * - * @param string|null $guardName * @return \Spatie\Permission\Contracts\Role * * @throws \Spatie\Permission\Exceptions\RoleDoesNotExist */ - public static function findByName(string $name, $guardName): self; + public static function findByName(string $name, ?string $guardName): self; /** * Find a role by its id and guard name. * - * @param string|null $guardName * @return \Spatie\Permission\Contracts\Role * * @throws \Spatie\Permission\Exceptions\RoleDoesNotExist */ - public static function findById(int $id, $guardName): self; + public static function findById(int|string $id, ?string $guardName): self; /** * Find or create a role by its name and guard name. * - * @param string|null $guardName * @return \Spatie\Permission\Contracts\Role */ - public static function findOrCreate(string $name, $guardName): self; + public static function findOrCreate(string $name, ?string $guardName): self; /** * Determine if the user may perform the given permission. * * @param string|\Spatie\Permission\Contracts\Permission $permission */ - public function hasPermissionTo($permission): bool; + public function hasPermissionTo($permission, ?string $guardName): bool; } diff --git a/src/Models/Permission.php b/src/Models/Permission.php index 21ce630ea..95df94fb6 100644 --- a/src/Models/Permission.php +++ b/src/Models/Permission.php @@ -82,12 +82,11 @@ public function users(): BelongsToMany /** * Find a permission by its name (and optionally guardName). * - * @param string|null $guardName * @return PermissionContract|Permission * * @throws PermissionDoesNotExist */ - public static function findByName(string $name, $guardName = null): PermissionContract + public static function findByName(string $name, string $guardName = null): PermissionContract { $guardName = $guardName ?? Guard::getDefaultName(static::class); $permission = static::getPermission(['name' => $name, 'guard_name' => $guardName]); @@ -101,13 +100,11 @@ public static function findByName(string $name, $guardName = null): PermissionCo /** * Find a permission by its id (and optionally guardName). * - * @param int|string $id - * @param string|null $guardName * @return PermissionContract|Permission * * @throws PermissionDoesNotExist */ - public static function findById($id, $guardName = null): PermissionContract + public static function findById(int|string $id, string $guardName = null): PermissionContract { $guardName = $guardName ?? Guard::getDefaultName(static::class); $permission = static::getPermission([(new static())->getKeyName() => $id, 'guard_name' => $guardName]); @@ -122,10 +119,9 @@ public static function findById($id, $guardName = null): PermissionContract /** * Find or create permission by its name (and optionally guardName). * - * @param string|null $guardName * @return PermissionContract|Permission */ - public static function findOrCreate(string $name, $guardName = null): PermissionContract + public static function findOrCreate(string $name, string $guardName = null): PermissionContract { $guardName = $guardName ?? Guard::getDefaultName(static::class); $permission = static::getPermission(['name' => $name, 'guard_name' => $guardName]); diff --git a/src/Models/Role.php b/src/Models/Role.php index d96102a2f..4fd6e782e 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -91,12 +91,11 @@ public function users(): BelongsToMany /** * Find a role by its name and guard name. * - * @param string|null $guardName * @return RoleContract|Role * * @throws RoleDoesNotExist */ - public static function findByName(string $name, $guardName = null): RoleContract + public static function findByName(string $name, string $guardName = null): RoleContract { $guardName = $guardName ?? Guard::getDefaultName(static::class); @@ -112,11 +111,9 @@ public static function findByName(string $name, $guardName = null): RoleContract /** * Find a role by its id (and optionally guardName). * - * @param int|string $id - * @param string|null $guardName * @return RoleContract|Role */ - public static function findById($id, $guardName = null): RoleContract + public static function findById(int|string $id, string $guardName = null): RoleContract { $guardName = $guardName ?? Guard::getDefaultName(static::class); @@ -132,10 +129,9 @@ public static function findById($id, $guardName = null): RoleContract /** * Find or create role by its name (and optionally guardName). * - * @param string|null $guardName * @return RoleContract|Role */ - public static function findOrCreate(string $name, $guardName = null): RoleContract + public static function findOrCreate(string $name, string $guardName = null): RoleContract { $guardName = $guardName ?? Guard::getDefaultName(static::class); @@ -177,11 +173,10 @@ protected static function findByParam(array $params = []): ?RoleContract * Determine if the role may perform the given permission. * * @param string|int|Permission|\BackedEnum $permission - * @param string|null $guardName * * @throws PermissionDoesNotExist|GuardDoesNotMatch */ - public function hasPermissionTo($permission, $guardName = null): bool + public function hasPermissionTo($permission, string $guardName = null): bool { if ($this->getWildcardClass()) { return $this->hasWildcardPermission($permission, $guardName); From 46e9df3bc21380e23b6c314109b100ef49e2d89c Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Mon, 21 Aug 2023 13:37:08 -0400 Subject: [PATCH 369/648] [Docs] Update v6 upgrade instructions --- docs/upgrading.md | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/docs/upgrading.md b/docs/upgrading.md index 3709250ff..ade6e7fd4 100644 --- a/docs/upgrading.md +++ b/docs/upgrading.md @@ -15,13 +15,15 @@ ALL upgrades of this package should follow these steps: 3. Config file. Incorporate any changes to the permission.php config file, updating your existing file. (It may be easiest to make a backup copy of your existing file, re-publish it from this package, and then re-make your customizations to it.) -4. Models. If you have made any custom Models from this package into your own app, compare the old and new models and apply any relevant updates to your custom models. +4. Models. If you have made any custom Models by extending them into your own app, compare the package's old and new models and apply any relevant updates to your custom models. 5. Custom Methods/Traits. If you have overridden any methods from this package's Traits, compare the old and new traits, and apply any relevant updates to your overridden methods. -6. Apply any version-specific special updates as outlined below... +6. Contract/Interface updates. If you have implemented this package's contracts in any models, check to see if there were any changes to method signatures. Mismatches will trigger PHP errors. -7. Review the changelog, which details all the changes: [CHANGELOG](https://github.com/spatie/laravel-permission/blob/main/CHANGELOG.md) +7. Apply any version-specific special updates as outlined below... + +8. Review the changelog, which details all the changes: [CHANGELOG](https://github.com/spatie/laravel-permission/blob/main/CHANGELOG.md) and/or consult the [Release Notes](https://github.com/spatie/laravel-permission/releases) @@ -30,9 +32,10 @@ There are a few breaking-changes when upgrading to v6, but most of them won't af 1. If you have overridden the `getPermissionClass()` or `getRoleClass()` methods or have custom Models, you will need to revisit those customizations. See PR #2368 for details. eg: if you have a custom model you will need to make changes, including accessing the model using `$this->permissionClass::` syntax (eg: using `::` instead of `->`) in all the overridden methods that make use of the models. -Be sure to compare your custom models with originals to see what else may have changed. -2. Model and Contract/Interface updates. Both the Permission and Role contracts have been updated with syntax changes to method signatures, so if you have implemented those contracts in any models, you will need to update the function signatures. Further, if you have extended the Role or Permission models you will need to check any methods you have overridden and update method signatures. See PR #2380 and #2480 especially. + Be sure to compare your custom models with originals to see what else may have changed. + +2. Model and Contract/Interface updates. The Role and Permission Models and Contracts/Interfaces have been updated with syntax changes to method signatures. Update any models you have extended, or contracts implemented, accordingly. See PR #2380 and #2480 for some of the specifics. 3. Migrations will need to be upgraded. (They have been updated to anonymous-class syntax that was introduced in Laravel 8, AND some structural coding changes in the registrar class changed the way we extracted configuration settings in the migration files.) If you had not customized it from the original then replacing the contents of the file should be straightforward. Usually the only customization is if you've switched to UUIDs or customized MySQL index name lengths. **If you get the following error, it means your migration file needs upgrading: `Error: Access to undeclared static property Spatie\Permission\PermissionRegistrar::$pivotPermission`** @@ -41,7 +44,9 @@ Be sure to compare your custom models with originals to see what else may have c 5. In the unlikely event that you have customized the Wildcard Permissions feature by extending the `WildcardPermission` model, please note that the public interface has changed and you will need to update your extended model with the new method signatures. -6. Test suites. If you have tests which manually clear the permission cache and re-register permissions, you no longer need to call `\Spatie\Permission\PermissionRegistrar::class)->registerPermissions();`. Such calls MUST be deleted from your tests. +6. Test suites. If you have tests which manually clear the permission cache and re-register permissions, you no longer need to call `\Spatie\Permission\PermissionRegistrar::class)->registerPermissions();`. In fact, **calls to `->registerPermissions()` MUST be deleted from your tests**. + + (Calling `app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();` after creating roles and permissions is still okay and encouraged.) ## Upgrading from v4 to v5 From bd2c1e60dafb317083b491fdbe832448fa39f375 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Mon, 21 Aug 2023 13:57:33 -0400 Subject: [PATCH 370/648] Add cache heading in comments --- config/permission.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/permission.php b/config/permission.php index 0507baec5..9c0532d89 100644 --- a/config/permission.php +++ b/config/permission.php @@ -159,6 +159,8 @@ */ // 'permission.wildcard_permission' => Spatie\Permission\WildcardPermission::class, + /* Cache-specific settings */ + 'cache' => [ /* From ba5a7be2f6033768cd76748f484b41c8aa2bb226 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 25 Aug 2023 17:31:46 -0400 Subject: [PATCH 371/648] [docs] remove registerPolicies call that was removed with L10.0.2 The line was previously there just to be a goalpost, to give an idea where to put the suggested new code. --- docs/basic-usage/super-admin.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/basic-usage/super-admin.md b/docs/basic-usage/super-admin.md index 344a9a41e..70cb0b5a1 100644 --- a/docs/basic-usage/super-admin.md +++ b/docs/basic-usage/super-admin.md @@ -18,7 +18,7 @@ class AuthServiceProvider extends ServiceProvider { public function boot() { - $this->registerPolicies(); +//... // Implicitly grant "Super Admin" role all permissions // This works in the app by using gate-related functions like auth()->user->can() and @can() From 7fdf95db4993f4dee3447e016771d9e0074fb7e9 Mon Sep 17 00:00:00 2001 From: drbyte Date: Wed, 30 Aug 2023 23:42:18 +0000 Subject: [PATCH 372/648] Update CHANGELOG --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a9a38279..f1e5f9f0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to `laravel-permission` will be documented in this file +## 5.11.0 - 2023-08-30 + +### What's Changed + +- [V5] Avoid triggering `eloquent.retrieved` event by @erikn69 in https://github.com/spatie/laravel-permission/pull/2490 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/5.10.2...5.11.0 + ## 5.10.2 - 2023-07-04 ### What's Changed @@ -618,6 +626,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` 1. Also this is a good time to point out that now with v2.25.0 and v2.26.0 most permission-cache-reset scenarios may no longer be needed in your app, so it's worth reviewing those cases, as you may gain some app speed improvement by removing unnecessary cache resets. @@ -674,6 +683,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` ## 2.19.1 - 2018-09-14 From 4df31adf9a3564fef3f4a43ce30be421ac9f04de Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 1 Sep 2023 14:13:06 -0400 Subject: [PATCH 373/648] Add note about "1071 Specified key was too long" --- docs/installation-laravel.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation-laravel.md b/docs/installation-laravel.md index 21e31edba..8fb36b812 100644 --- a/docs/installation-laravel.md +++ b/docs/installation-laravel.md @@ -51,7 +51,7 @@ Package Version | Laravel Version - must set `'teams' => true,` - and (optional) you may set `team_foreign_key` name in the config file if you want to use a custom foreign key in your database for teams - - **If you are using MySQL 8**, look at the migration files for notes about MySQL 8 to set/limit the index key length, and edit accordingly. + - **If you are using MySQL 8**, look at the migration files for notes about MySQL 8 to set/limit the index key length, and edit accordingly. If you get `ERROR: 1071 Specified key was too long` then you need to do this. 7. **Clear your config cache**. This package requires access to the `permission` config settings in order to run migrations. If you've been caching configurations locally, clear your config cache with either of these commands: From c98a8b3d571771d6fc52cf4a25a031682efb0004 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 1 Sep 2023 14:14:32 -0400 Subject: [PATCH 374/648] Note about Laravel versions --- docs/installation-laravel.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation-laravel.md b/docs/installation-laravel.md index 8fb36b812..39e8087ee 100644 --- a/docs/installation-laravel.md +++ b/docs/installation-laravel.md @@ -5,7 +5,7 @@ weight: 4 ## Laravel Version Compatibility -This package can be used with Laravel 6.0 or higher. +Choose the version of this package that suits your Laravel version. Package Version | Laravel Version ----------------|----------- From 465b9d7760a577630f846bc5bfcb0cebe1d347c3 Mon Sep 17 00:00:00 2001 From: Arthur LORENT Date: Fri, 1 Sep 2023 20:21:51 +0200 Subject: [PATCH 375/648] [Docs] Listen to DatabaseRefreshed rather than MigrationsEnded in TestCase (#2492) Listening to `DatabaseRefreshed` event rather than `MigrationsEnded` can avoid issues during testing after `artisan schema:dump` command has been executed. In fact, `MigrationsEnded` is not called when the migration is based on a `mysql-schema.sql` file and has no migration executed anymore. `DatabaseRefreshed` is always executed in both cases. --- docs/advanced-usage/testing.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/advanced-usage/testing.md b/docs/advanced-usage/testing.md index f67ecd4a2..a98955951 100644 --- a/docs/advanced-usage/testing.md +++ b/docs/advanced-usage/testing.md @@ -22,10 +22,10 @@ In your tests simply add a `setUp()` instruction to re-register the permissions, ## Clear Cache When Using Seeders -If you are using Laravel's `LazilyRefreshDatabase` trait, you most likely want to avoid seeding permissions before every test, because that would negate the use of the `LazilyRefreshDatabase` trait. To overcome this, you should wrap your seeder in an event listener for the `MigrationsEnded` event: +If you are using Laravel's `LazilyRefreshDatabase` trait, you most likely want to avoid seeding permissions before every test, because that would negate the use of the `LazilyRefreshDatabase` trait. To overcome this, you should wrap your seeder in an event listener for the `DatabaseRefreshed` event: ```php -Event::listen(MigrationsEnded::class, function () { +Event::listen(DatabaseRefreshed::class, function () { $this->artisan('db:seed', ['--class' => RoleAndPermissionSeeder::class]); $this->app->make(\Spatie\Permission\PermissionRegistrar::class)->forgetCachedPermissions(); }); From f50e7cc4fd09c459fa83884c1f770f714bd808d0 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Mon, 4 Sep 2023 11:51:57 -0500 Subject: [PATCH 376/648] Bump actions/checkout from 3 to 4 (#2493) --- .github/workflows/fix-php-code-style-issues.yml | 2 +- .github/workflows/phpstan.yml | 2 +- .github/workflows/run-tests.yml | 2 +- .github/workflows/test-cache-drivers.yml | 2 +- .github/workflows/update-changelog.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/fix-php-code-style-issues.yml b/.github/workflows/fix-php-code-style-issues.yml index 49d14b77e..3687af011 100644 --- a/.github/workflows/fix-php-code-style-issues.yml +++ b/.github/workflows/fix-php-code-style-issues.yml @@ -11,7 +11,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{ github.head_ref }} diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index acd8b906e..587e6c2d7 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -11,7 +11,7 @@ jobs: name: phpstan runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index f4d0b4e92..d119985db 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -27,7 +27,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 diff --git a/.github/workflows/test-cache-drivers.yml b/.github/workflows/test-cache-drivers.yml index ab49f2cf2..403f23db6 100644 --- a/.github/workflows/test-cache-drivers.yml +++ b/.github/workflows/test-cache-drivers.yml @@ -21,7 +21,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 diff --git a/.github/workflows/update-changelog.yml b/.github/workflows/update-changelog.yml index 74f888987..ab69c62c1 100644 --- a/.github/workflows/update-changelog.yml +++ b/.github/workflows/update-changelog.yml @@ -10,7 +10,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: main From 3cafa8dabf4a13cc9d6b0c8a9e636fc7c2642ea4 Mon Sep 17 00:00:00 2001 From: Erin Dalzell Date: Mon, 4 Sep 2023 21:00:30 -0400 Subject: [PATCH 377/648] Typehint BackedEnum (#2494) --- src/Traits/HasRoles.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index 829afde22..3737b557d 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -163,7 +163,7 @@ private function collectRoles(...$roles): array /** * Assign the given role to the model. * - * @param array|string|int|Role|Collection ...$roles + * @param array|string|int|Role|Collection|\BackedEnum ...$roles * @return $this */ public function assignRole(...$roles) From 21829869f13a89b1e5dd4049443c97fab0bd5baa Mon Sep 17 00:00:00 2001 From: drbyte Date: Tue, 5 Sep 2023 01:01:13 +0000 Subject: [PATCH 378/648] Fix styling --- src/Contracts/Permission.php | 4 ---- src/Contracts/Role.php | 4 ---- tests/TestModels/Client.php | 2 +- tests/TestModels/UserWithoutHasRoles.php | 4 ++-- 4 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/Contracts/Permission.php b/src/Contracts/Permission.php index a2444adeb..111d2ed2e 100644 --- a/src/Contracts/Permission.php +++ b/src/Contracts/Permission.php @@ -21,7 +21,6 @@ public function roles(): BelongsToMany; /** * Find a permission by its name. * - * @return \Spatie\Permission\Contracts\Permission * * @throws \Spatie\Permission\Exceptions\PermissionDoesNotExist */ @@ -30,7 +29,6 @@ public static function findByName(string $name, ?string $guardName): self; /** * Find a permission by its id. * - * @return \Spatie\Permission\Contracts\Permission * * @throws \Spatie\Permission\Exceptions\PermissionDoesNotExist */ @@ -38,8 +36,6 @@ public static function findById(int|string $id, ?string $guardName): self; /** * Find or Create a permission by its name and guard name. - * - * @return \Spatie\Permission\Contracts\Permission */ public static function findOrCreate(string $name, ?string $guardName): self; } diff --git a/src/Contracts/Role.php b/src/Contracts/Role.php index 5745dd6b3..d00201a2a 100644 --- a/src/Contracts/Role.php +++ b/src/Contracts/Role.php @@ -21,7 +21,6 @@ public function permissions(): BelongsToMany; /** * Find a role by its name and guard name. * - * @return \Spatie\Permission\Contracts\Role * * @throws \Spatie\Permission\Exceptions\RoleDoesNotExist */ @@ -30,7 +29,6 @@ public static function findByName(string $name, ?string $guardName): self; /** * Find a role by its id and guard name. * - * @return \Spatie\Permission\Contracts\Role * * @throws \Spatie\Permission\Exceptions\RoleDoesNotExist */ @@ -38,8 +36,6 @@ public static function findById(int|string $id, ?string $guardName): self; /** * Find or create a role by its name and guard name. - * - * @return \Spatie\Permission\Contracts\Role */ public static function findOrCreate(string $name, ?string $guardName): self; diff --git a/tests/TestModels/Client.php b/tests/TestModels/Client.php index a920bddd0..267c36122 100644 --- a/tests/TestModels/Client.php +++ b/tests/TestModels/Client.php @@ -9,8 +9,8 @@ class Client extends BaseClient implements AuthorizableContract { - use HasRoles; use Authorizable; + use HasRoles; /** * Required to make clear that the client requires the api guard diff --git a/tests/TestModels/UserWithoutHasRoles.php b/tests/TestModels/UserWithoutHasRoles.php index dd9a29445..6cc1d840e 100644 --- a/tests/TestModels/UserWithoutHasRoles.php +++ b/tests/TestModels/UserWithoutHasRoles.php @@ -8,10 +8,10 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Foundation\Auth\Access\Authorizable; -class UserWithoutHasRoles extends Model implements AuthorizableContract, AuthenticatableContract +class UserWithoutHasRoles extends Model implements AuthenticatableContract, AuthorizableContract { - use Authorizable; use Authenticatable; + use Authorizable; protected $fillable = ['email']; From 905be9d457a1a8d90da047361935ce9ccdc82a5d Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Mon, 4 Sep 2023 21:04:59 -0400 Subject: [PATCH 379/648] Typehint BackedEnum --- src/Traits/HasRoles.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index 3737b557d..2fc31b012 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -136,7 +136,7 @@ public function scopeWithoutRole(Builder $query, $roles, $guard = null): Builder /** * Returns roles ids as array keys * - * @param array|string|int|Role|Collection|\BackedEnum $roles + * @param string|int|array|Role|Collection|\BackedEnum $roles */ private function collectRoles(...$roles): array { @@ -163,7 +163,7 @@ private function collectRoles(...$roles): array /** * Assign the given role to the model. * - * @param array|string|int|Role|Collection|\BackedEnum ...$roles + * @param string|int|array|Role|Collection|\BackedEnum ...$roles * @return $this */ public function assignRole(...$roles) @@ -221,7 +221,7 @@ public function removeRole($role) /** * Remove all current roles and set the given ones. * - * @param array|Role|Collection|string|int ...$roles + * @param string|int|array|Role|Collection|\BackedEnum ...$roles * @return $this */ public function syncRoles(...$roles) @@ -293,7 +293,7 @@ public function hasRole($roles, string $guard = null): bool * * Alias to hasRole() but without Guard controls * - * @param string|int|array|Role|Collection $roles + * @param string|int|array|Role|Collection|\BackedEnum $roles */ public function hasAnyRole(...$roles): bool { From 7c454fa1e01f4ee81fe5fa98cbc78e4e8a7b0688 Mon Sep 17 00:00:00 2001 From: Erin Dalzell Date: Tue, 12 Sep 2023 10:55:16 -0700 Subject: [PATCH 380/648] Missing typehint (#2495) --- src/Traits/HasRoles.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index 2fc31b012..1be3abb9d 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -203,7 +203,7 @@ function ($object) use ($roles, $model, $teamPivot) { /** * Revoke the given role from the model. * - * @param string|int|Role $role + * @param string|int|Role|\BackedEnum $role */ public function removeRole($role) { From 982d4d407643182a335774e53b92afc344828a2d Mon Sep 17 00:00:00 2001 From: drbyte Date: Tue, 12 Sep 2023 17:55:39 +0000 Subject: [PATCH 381/648] Fix styling --- src/PermissionRegistrar.php | 2 +- src/Traits/HasRoles.php | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index bc434e571..90f0d199a 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -398,7 +398,7 @@ public static function isUid($value): bool } // check if is ULID - $ulid = 26 == strlen($value) && 26 == strspn($value, '0123456789ABCDEFGHJKMNPQRSTVWXYZabcdefghjkmnpqrstvwxyz') && $value[0] <= '7'; + $ulid = strlen($value) == 26 && strspn($value, '0123456789ABCDEFGHJKMNPQRSTVWXYZabcdefghjkmnpqrstvwxyz') == 26 && $value[0] <= '7'; if ($ulid) { return true; } diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index 1be3abb9d..e0ac80301 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -244,7 +244,7 @@ public function hasRole($roles, string $guard = null): bool { $this->loadMissing('roles'); - if (is_string($roles) && false !== strpos($roles, '|')) { + if (is_string($roles) && strpos($roles, '|') !== false) { $roles = $this->convertPipeToArray($roles); } @@ -313,7 +313,7 @@ public function hasAllRoles($roles, string $guard = null): bool $roles = $roles->value; } - if (is_string($roles) && false !== strpos($roles, '|')) { + if (is_string($roles) && strpos($roles, '|') !== false) { $roles = $this->convertPipeToArray($roles); } @@ -351,7 +351,7 @@ public function hasExactRoles($roles, string $guard = null): bool { $this->loadMissing('roles'); - if (is_string($roles) && false !== strpos($roles, '|')) { + if (is_string($roles) && strpos($roles, '|') !== false) { $roles = $this->convertPipeToArray($roles); } From 8f15d1846aaa5039292978e7775bbf0f2ca73cc8 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Wed, 13 Sep 2023 13:27:46 -0500 Subject: [PATCH 382/648] [v6] Sync v5 fix: Avoid triggering eloquent.retrieved event #2498 --- src/PermissionRegistrar.php | 13 ++++++------- src/Traits/HasPermissions.php | 12 ++++-------- src/Traits/HasRoles.php | 3 +-- 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index 90f0d199a..30c6d5676 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -354,12 +354,11 @@ private function getSerializedRoleRelation($permission): array private function getHydratedPermissionCollection(): Collection { - $permissionClass = $this->getPermissionClass(); - $permissionInstance = new $permissionClass(); + $permissionInstance = new ($this->getPermissionClass())(); return Collection::make(array_map( - fn ($item) => $permissionInstance - ->newFromBuilder($this->aliasedArray(array_diff_key($item, ['r' => 0]))) + fn ($item) => $permissionInstance->newInstance([], true) + ->setRawAttributes($this->aliasedArray(array_diff_key($item, ['r' => 0])), true) ->setRelation('roles', $this->getHydratedRoleCollection($item['r'] ?? [])), $this->permissions['permissions'] )); @@ -374,11 +373,11 @@ private function getHydratedRoleCollection(array $roles): Collection private function hydrateRolesCache(): void { - $roleClass = $this->getRoleClass(); - $roleInstance = new $roleClass(); + $roleInstance = new ($this->getRoleClass())(); array_map(function ($item) use ($roleInstance) { - $role = $roleInstance->newFromBuilder($this->aliasedArray($item)); + $role = $roleInstance->newInstance([], true) + ->setRawAttributes($this->aliasedArray($item), true); $this->cachedRoles[$role->getKey()] = $role; }, $this->permissions['roles']); diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index 1404bb501..21b2d2420 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -101,10 +101,8 @@ public function scopePermission(Builder $query, $permissions): Builder { $permissions = $this->convertToPermissionModels($permissions); - $permissionClass = $this->getPermissionClass(); - $permissionKey = (new $permissionClass())->getKeyName(); - $roleClass = is_a($this, Role::class) ? static::class : $this->getRoleClass(); - $roleKey = (new $roleClass())->getKeyName(); + $permissionKey = (new ($this->getPermissionClass())())->getKeyName(); + $roleKey = (new (is_a($this, Role::class) ? static::class : $this->getRoleClass())())->getKeyName(); $rolesWithPermissions = is_a($this, Role::class) ? [] : array_unique( array_reduce($permissions, fn ($result, $permission) => array_merge($result, $permission->roles->all()), []) @@ -132,10 +130,8 @@ public function scopeWithoutPermission(Builder $query, $permissions, $debug = fa { $permissions = $this->convertToPermissionModels($permissions); - $permissionClass = $this->getPermissionClass(); - $permissionKey = (new $permissionClass())->getKeyName(); - $roleClass = is_a($this, Role::class) ? static::class : $this->getRoleClass(); - $roleKey = (new $roleClass())->getKeyName(); + $permissionKey = (new ($this->getPermissionClass())())->getKeyName(); + $roleKey = (new (is_a($this, Role::class) ? static::class : $this->getRoleClass())())->getKeyName(); $rolesWithPermissions = is_a($this, Role::class) ? [] : array_unique( array_reduce($permissions, fn ($result, $permission) => array_merge($result, $permission->roles->all()), []) diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index e0ac80301..78ec59812 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -253,8 +253,7 @@ public function hasRole($roles, string $guard = null): bool } if (is_int($roles) || PermissionRegistrar::isUid($roles)) { - $roleClass = $this->getRoleClass(); - $key = (new $roleClass())->getKeyName(); + $key = (new ($this->getRoleClass())())->getKeyName(); return $guard ? $this->roles->where('guard_name', $guard)->contains($key, $roles) From 5659d6abf6f66f662e9562b5409a52270178da48 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Wed, 13 Sep 2023 16:21:25 -0500 Subject: [PATCH 383/648] Reuse code on withoutRole and withoutPermission (#2500) --- src/Traits/HasPermissions.php | 29 ++++++----------------------- src/Traits/HasRoles.php | 33 +++++---------------------------- 2 files changed, 11 insertions(+), 51 deletions(-) diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index 21b2d2420..eee592af3 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -96,8 +96,9 @@ public function permissions(): BelongsToMany * Scope the model query to certain permissions only. * * @param string|int|array|Permission|Collection|\BackedEnum $permissions + * @param bool $without */ - public function scopePermission(Builder $query, $permissions): Builder + public function scopePermission(Builder $query, $permissions, $without = false): Builder { $permissions = $this->convertToPermissionModels($permissions); @@ -109,11 +110,11 @@ public function scopePermission(Builder $query, $permissions): Builder ); return $query->where(fn (Builder $query) => $query - ->whereHas('permissions', fn (Builder $subQuery) => $subQuery + ->{! $without ? 'whereHas' : 'whereDoesntHave'}('permissions', fn (Builder $subQuery) => $subQuery ->whereIn(config('permission.table_names.permissions').".$permissionKey", \array_column($permissions, $permissionKey)) ) ->when(count($rolesWithPermissions), fn ($whenQuery) => $whenQuery - ->orWhereHas('roles', fn (Builder $subQuery) => $subQuery + ->{! $without ? 'orWhereHas' : 'whereDoesntHave'}('roles', fn (Builder $subQuery) => $subQuery ->whereIn(config('permission.table_names.roles').".$roleKey", \array_column($rolesWithPermissions, $roleKey)) ) ) @@ -126,27 +127,9 @@ public function scopePermission(Builder $query, $permissions): Builder * * @param string|int|array|Permission|Collection|\BackedEnum $permissions */ - public function scopeWithoutPermission(Builder $query, $permissions, $debug = false): Builder + public function scopeWithoutPermission(Builder $query, $permissions): Builder { - $permissions = $this->convertToPermissionModels($permissions); - - $permissionKey = (new ($this->getPermissionClass())())->getKeyName(); - $roleKey = (new (is_a($this, Role::class) ? static::class : $this->getRoleClass())())->getKeyName(); - - $rolesWithPermissions = is_a($this, Role::class) ? [] : array_unique( - array_reduce($permissions, fn ($result, $permission) => array_merge($result, $permission->roles->all()), []) - ); - - return $query->where(fn (Builder $query) => $query - ->whereDoesntHave('permissions', fn (Builder $subQuery) => $subQuery - ->whereIn(config('permission.table_names.permissions').".$permissionKey", \array_column($permissions, $permissionKey)) - ) - ->when(count($rolesWithPermissions), fn ($whenQuery) => $whenQuery - ->whereDoesntHave('roles', fn (Builder $subQuery) => $subQuery - ->whereIn(config('permission.table_names.roles').".$roleKey", \array_column($rolesWithPermissions, $roleKey)) - ) - ) - ); + return $this->scopePermission($query, $permissions, true); } /** diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index 78ec59812..f02f7c160 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -70,8 +70,9 @@ public function roles(): BelongsToMany * * @param string|int|array|Role|Collection|\BackedEnum $roles * @param string $guard + * @param bool $without */ - public function scopeRole(Builder $query, $roles, $guard = null): Builder + public function scopeRole(Builder $query, $roles, $guard = null, $without = false): Builder { if ($roles instanceof Collection) { $roles = $roles->all(); @@ -91,10 +92,9 @@ public function scopeRole(Builder $query, $roles, $guard = null): Builder return $this->getRoleClass()::{$method}($role, $guard ?: $this->getDefaultGuardName()); }, Arr::wrap($roles)); - $roleClass = $this->getRoleClass(); - $key = (new $roleClass())->getKeyName(); + $key = (new ($this->getRoleClass())())->getKeyName(); - return $query->whereHas('roles', fn (Builder $subQuery) => $subQuery + return $query->{! $without ? 'whereHas' : 'whereDoesntHave'}('roles', fn (Builder $subQuery) => $subQuery ->whereIn(config('permission.table_names.roles').".$key", \array_column($roles, $key)) ); } @@ -107,30 +107,7 @@ public function scopeRole(Builder $query, $roles, $guard = null): Builder */ public function scopeWithoutRole(Builder $query, $roles, $guard = null): Builder { - if ($roles instanceof Collection) { - $roles = $roles->all(); - } - - $roles = array_map(function ($role) use ($guard) { - if ($role instanceof Role) { - return $role; - } - - if ($role instanceof \BackedEnum) { - $role = $role->value; - } - - $method = is_int($role) || PermissionRegistrar::isUid($role) ? 'findById' : 'findByName'; - - return $this->getRoleClass()::{$method}($role, $guard ?: $this->getDefaultGuardName()); - }, Arr::wrap($roles)); - - $roleClass = $this->getRoleClass(); - $key = (new $roleClass())->getKeyName(); - - return $query->whereDoesntHave('roles', fn (Builder $subQuery) => $subQuery - ->whereIn(config('permission.table_names.roles').".$key", \array_column($roles, $key)) - ); + return $this->scopeRole($query, $roles, $guard, true); } /** From d5eb73977b34248287bee8f530a69ad44ecfc3ba Mon Sep 17 00:00:00 2001 From: drbyte Date: Wed, 13 Sep 2023 21:21:50 +0000 Subject: [PATCH 384/648] Fix styling --- src/Traits/HasPermissions.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index eee592af3..2618b380b 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -111,11 +111,11 @@ public function scopePermission(Builder $query, $permissions, $without = false): return $query->where(fn (Builder $query) => $query ->{! $without ? 'whereHas' : 'whereDoesntHave'}('permissions', fn (Builder $subQuery) => $subQuery - ->whereIn(config('permission.table_names.permissions').".$permissionKey", \array_column($permissions, $permissionKey)) + ->whereIn(config('permission.table_names.permissions').".$permissionKey", \array_column($permissions, $permissionKey)) ) ->when(count($rolesWithPermissions), fn ($whenQuery) => $whenQuery ->{! $without ? 'orWhereHas' : 'whereDoesntHave'}('roles', fn (Builder $subQuery) => $subQuery - ->whereIn(config('permission.table_names.roles').".$roleKey", \array_column($rolesWithPermissions, $roleKey)) + ->whereIn(config('permission.table_names.roles').".$roleKey", \array_column($rolesWithPermissions, $roleKey)) ) ) ); From f9069aa5e0f9ca63d45cf406e5cb2d0f8907b0ed Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Mon, 18 Sep 2023 16:01:04 -0400 Subject: [PATCH 385/648] Rename "Middlewares" namespace to "Middleware" (#2499) --- docs/basic-usage/middleware.md | 14 +++++++------- docs/basic-usage/passport.md | 2 +- docs/installation-lumen.md | 4 ++-- docs/upgrading.md | 10 +++++++--- .../PermissionMiddleware.php | 2 +- src/{Middlewares => Middleware}/RoleMiddleware.php | 2 +- .../RoleOrPermissionMiddleware.php | 2 +- tests/PermissionMiddlewareTest.php | 8 ++++---- tests/RoleMiddlewareTest.php | 8 ++++---- tests/RoleOrPermissionMiddlewareTest.php | 8 ++++---- tests/WildcardMiddlewareTest.php | 6 +++--- 11 files changed, 35 insertions(+), 31 deletions(-) rename src/{Middlewares => Middleware}/PermissionMiddleware.php (97%) rename src/{Middlewares => Middleware}/RoleMiddleware.php (97%) rename src/{Middlewares => Middleware}/RoleOrPermissionMiddleware.php (97%) diff --git a/docs/basic-usage/middleware.md b/docs/basic-usage/middleware.md index e7f2ea773..592fe8343 100644 --- a/docs/basic-usage/middleware.md +++ b/docs/basic-usage/middleware.md @@ -34,9 +34,9 @@ Note the property name difference between Laravel 10 and older versions of Larav // Laravel 10+ uses $middlewareAliases = [ protected $middlewareAliases = [ // ... - 'role' => \Spatie\Permission\Middlewares\RoleMiddleware::class, - 'permission' => \Spatie\Permission\Middlewares\PermissionMiddleware::class, - 'role_or_permission' => \Spatie\Permission\Middlewares\RoleOrPermissionMiddleware::class, + 'role' => \Spatie\Permission\Middleware\RoleMiddleware::class, + 'permission' => \Spatie\Permission\Middleware\PermissionMiddleware::class, + 'role_or_permission' => \Spatie\Permission\Middleware\RoleOrPermissionMiddleware::class, ]; ``` @@ -110,19 +110,19 @@ public function __construct() ## Use middleware static methods -All of the middlewares can also be applied by calling the static `using` method, +All of the middleware can also be applied by calling the static `using` method, which accepts either a `|`-separated string or an array as input. ```php -Route::group(['middleware' => [\Spatie\Permission\Middlewares\RoleMiddleware::using('manager')]], function () { +Route::group(['middleware' => [\Spatie\Permission\Middleware\RoleMiddleware::using('manager')]], function () { // }); -Route::group(['middleware' => [\Spatie\Permission\Middlewares\PermissionMiddleware::using('publish articles|edit articles')]], function () { +Route::group(['middleware' => [\Spatie\Permission\Middleware\PermissionMiddleware::using('publish articles|edit articles')]], function () { // }); -Route::group(['middleware' => [\Spatie\Permission\Middlewares\RoleOrPermissionMiddleware::using(['manager', 'edit articles'])]], function () { +Route::group(['middleware' => [\Spatie\Permission\Middleware\RoleOrPermissionMiddleware::using(['manager', 'edit articles'])]], function () { // }); ``` diff --git a/docs/basic-usage/passport.md b/docs/basic-usage/passport.md index a5a007009..4faa50fc3 100644 --- a/docs/basic-usage/passport.md +++ b/docs/basic-usage/passport.md @@ -39,7 +39,7 @@ The extended Client should either provide a `$guard_name` property or a `guardNa They should return a string that matches the [configured](https://laravel.com/docs/master/passport#installation) guard name for the passport driver. ## Middleware -All middlewares provided by this package work with the Client. +All middleware provided by this package work with the Client. Do make sure that you only wrap your routes in the [`client`](https://laravel.com/docs/master/passport#via-middleware) middleware and not the `auth:api` middleware as well. Wrapping routes in the `auth:api` middleware currently does not work for the Client Credentials Grant. diff --git a/docs/installation-lumen.md b/docs/installation-lumen.md index 70268977d..197ff31fe 100644 --- a/docs/installation-lumen.md +++ b/docs/installation-lumen.md @@ -34,8 +34,8 @@ Then, in `bootstrap/app.php`, uncomment the `auth` middleware, and register this ```php $app->routeMiddleware([ 'auth' => App\Http\Middleware\Authenticate::class, - 'permission' => Spatie\Permission\Middlewares\PermissionMiddleware::class, - 'role' => Spatie\Permission\Middlewares\RoleMiddleware::class, + 'permission' => Spatie\Permission\Middleware\PermissionMiddleware::class, + 'role' => Spatie\Permission\Middleware\RoleMiddleware::class, ]); ``` diff --git a/docs/upgrading.md b/docs/upgrading.md index ade6e7fd4..ab83fd790 100644 --- a/docs/upgrading.md +++ b/docs/upgrading.md @@ -40,11 +40,15 @@ eg: if you have a custom model you will need to make changes, including accessin 3. Migrations will need to be upgraded. (They have been updated to anonymous-class syntax that was introduced in Laravel 8, AND some structural coding changes in the registrar class changed the way we extracted configuration settings in the migration files.) If you had not customized it from the original then replacing the contents of the file should be straightforward. Usually the only customization is if you've switched to UUIDs or customized MySQL index name lengths. **If you get the following error, it means your migration file needs upgrading: `Error: Access to undeclared static property Spatie\Permission\PermissionRegistrar::$pivotPermission`** -4. NOTE: For consistency with `PermissionMiddleware`, the `RoleOrPermissionMiddleware` has switched from only checking permissions provided by this package to using `canAny()` to check against any abilities registered by your application. This may have the effect of granting those other abilities (such as Super Admin) when using the `RoleOrPermissionMiddleware`, which previously would have failed silently. +4. MIDDLEWARE: -5. In the unlikely event that you have customized the Wildcard Permissions feature by extending the `WildcardPermission` model, please note that the public interface has changed and you will need to update your extended model with the new method signatures. + 1. The `\Spatie\Permission\Middlewares\` namespace has been renamed to `\Spatie\Permission\Middleware\` (singular). Update your references to them in your `/app/Http/Kernel.php` and any routes that have the fully qualified path. -6. Test suites. If you have tests which manually clear the permission cache and re-register permissions, you no longer need to call `\Spatie\Permission\PermissionRegistrar::class)->registerPermissions();`. In fact, **calls to `->registerPermissions()` MUST be deleted from your tests**. + 2. NOTE: For consistency with `PermissionMiddleware`, the `RoleOrPermissionMiddleware` has switched from only checking permissions provided by this package to using `canAny()` to check against any abilities registered by your application. This may have the effect of granting those other abilities (such as Super Admin) when using the `RoleOrPermissionMiddleware`, which previously would have failed silently. + + 3. In the unlikely event that you have customized the Wildcard Permissions feature by extending the `WildcardPermission` model, please note that the public interface has changed and you will need to update your extended model with the new method signatures. + +5. Test suites. If you have tests which manually clear the permission cache and re-register permissions, you no longer need to call `\Spatie\Permission\PermissionRegistrar::class)->registerPermissions();`. In fact, **calls to `->registerPermissions()` MUST be deleted from your tests**. (Calling `app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();` after creating roles and permissions is still okay and encouraged.) diff --git a/src/Middlewares/PermissionMiddleware.php b/src/Middleware/PermissionMiddleware.php similarity index 97% rename from src/Middlewares/PermissionMiddleware.php rename to src/Middleware/PermissionMiddleware.php index 7d303261c..3e78f1d60 100644 --- a/src/Middlewares/PermissionMiddleware.php +++ b/src/Middleware/PermissionMiddleware.php @@ -1,6 +1,6 @@ assertSame( - 'Spatie\Permission\Middlewares\PermissionMiddleware:edit-articles', + 'Spatie\Permission\Middleware\PermissionMiddleware:edit-articles', PermissionMiddleware::using('edit-articles') ); $this->assertEquals( - 'Spatie\Permission\Middlewares\PermissionMiddleware:edit-articles,my-guard', + 'Spatie\Permission\Middleware\PermissionMiddleware:edit-articles,my-guard', PermissionMiddleware::using('edit-articles', 'my-guard') ); $this->assertEquals( - 'Spatie\Permission\Middlewares\PermissionMiddleware:edit-articles|edit-news', + 'Spatie\Permission\Middleware\PermissionMiddleware:edit-articles|edit-news', PermissionMiddleware::using(['edit-articles', 'edit-news']) ); } diff --git a/tests/RoleMiddlewareTest.php b/tests/RoleMiddlewareTest.php index 25f411ee1..7ffc3a5c6 100644 --- a/tests/RoleMiddlewareTest.php +++ b/tests/RoleMiddlewareTest.php @@ -9,7 +9,7 @@ use InvalidArgumentException; use Laravel\Passport\Passport; use Spatie\Permission\Exceptions\UnauthorizedException; -use Spatie\Permission\Middlewares\RoleMiddleware; +use Spatie\Permission\Middleware\RoleMiddleware; use Spatie\Permission\Tests\TestModels\UserWithoutHasRoles; class RoleMiddlewareTest extends TestCase @@ -332,15 +332,15 @@ public function user_can_access_role_with_guard_admin_while_login_using_admin_gu public function the_middleware_can_be_created_with_static_using_method() { $this->assertSame( - 'Spatie\Permission\Middlewares\RoleMiddleware:testAdminRole', + 'Spatie\Permission\Middleware\RoleMiddleware:testAdminRole', RoleMiddleware::using('testAdminRole') ); $this->assertEquals( - 'Spatie\Permission\Middlewares\RoleMiddleware:testAdminRole,my-guard', + 'Spatie\Permission\Middleware\RoleMiddleware:testAdminRole,my-guard', RoleMiddleware::using('testAdminRole', 'my-guard') ); $this->assertEquals( - 'Spatie\Permission\Middlewares\RoleMiddleware:testAdminRole|anotherRole', + 'Spatie\Permission\Middleware\RoleMiddleware:testAdminRole|anotherRole', RoleMiddleware::using(['testAdminRole', 'anotherRole']) ); } diff --git a/tests/RoleOrPermissionMiddlewareTest.php b/tests/RoleOrPermissionMiddlewareTest.php index a3de1054d..4d473aa4f 100644 --- a/tests/RoleOrPermissionMiddlewareTest.php +++ b/tests/RoleOrPermissionMiddlewareTest.php @@ -10,7 +10,7 @@ use InvalidArgumentException; use Laravel\Passport\Passport; use Spatie\Permission\Exceptions\UnauthorizedException; -use Spatie\Permission\Middlewares\RoleOrPermissionMiddleware; +use Spatie\Permission\Middleware\RoleOrPermissionMiddleware; use Spatie\Permission\Tests\TestModels\UserWithoutHasRoles; class RoleOrPermissionMiddlewareTest extends TestCase @@ -278,15 +278,15 @@ public function the_required_permissions_or_roles_can_be_displayed_in_the_except public function the_middleware_can_be_created_with_static_using_method() { $this->assertSame( - 'Spatie\Permission\Middlewares\RoleOrPermissionMiddleware:edit-articles', + 'Spatie\Permission\Middleware\RoleOrPermissionMiddleware:edit-articles', RoleOrPermissionMiddleware::using('edit-articles') ); $this->assertEquals( - 'Spatie\Permission\Middlewares\RoleOrPermissionMiddleware:edit-articles,my-guard', + 'Spatie\Permission\Middleware\RoleOrPermissionMiddleware:edit-articles,my-guard', RoleOrPermissionMiddleware::using('edit-articles', 'my-guard') ); $this->assertEquals( - 'Spatie\Permission\Middlewares\RoleOrPermissionMiddleware:edit-articles|testAdminRole', + 'Spatie\Permission\Middleware\RoleOrPermissionMiddleware:edit-articles|testAdminRole', RoleOrPermissionMiddleware::using(['edit-articles', 'testAdminRole']) ); } diff --git a/tests/WildcardMiddlewareTest.php b/tests/WildcardMiddlewareTest.php index f2d099ae8..dc359fe49 100644 --- a/tests/WildcardMiddlewareTest.php +++ b/tests/WildcardMiddlewareTest.php @@ -6,9 +6,9 @@ use Illuminate\Http\Response; use Illuminate\Support\Facades\Auth; use Spatie\Permission\Exceptions\UnauthorizedException; -use Spatie\Permission\Middlewares\PermissionMiddleware; -use Spatie\Permission\Middlewares\RoleMiddleware; -use Spatie\Permission\Middlewares\RoleOrPermissionMiddleware; +use Spatie\Permission\Middleware\PermissionMiddleware; +use Spatie\Permission\Middleware\RoleMiddleware; +use Spatie\Permission\Middleware\RoleOrPermissionMiddleware; use Spatie\Permission\Models\Permission; class WildcardMiddlewareTest extends TestCase From 2cc536247cad8a2bebbad114a389d9af02612f97 Mon Sep 17 00:00:00 2001 From: Siros Fakhri <56381478+sirosfakhri@users.noreply.github.com> Date: Tue, 19 Sep 2023 23:52:32 +0330 Subject: [PATCH 386/648] fix Lumen Documentation Address (#2501) --- docs/installation-lumen.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation-lumen.md b/docs/installation-lumen.md index 197ff31fe..ff3e4bce2 100644 --- a/docs/installation-lumen.md +++ b/docs/installation-lumen.md @@ -5,7 +5,7 @@ weight: 5 NOTE: Lumen is **not** officially supported by this package. However, the following are some steps which may help get you started. -Lumen installation instructions can be found in the [Lumen documentation](https://lumen.laravel.com/docs/main). +Lumen installation instructions can be found in the [Lumen documentation](https://lumen.laravel.com/docs/master). ## Installing From 08d8e8c66faeab204b92add6f003c762681ee5de Mon Sep 17 00:00:00 2001 From: erikn69 Date: Thu, 5 Oct 2023 16:09:28 -0500 Subject: [PATCH 387/648] Test against php 8.3 (#2512) --- .github/workflows/run-tests.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index d119985db..84e1a0f95 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -9,7 +9,7 @@ jobs: strategy: fail-fast: false matrix: - php: [8.2, 8.1, 8.0] + php: [8.3, 8.2, 8.1, 8.0] laravel: ["^10.0", "^9.0", "^8.12"] dependency-version: [prefer-lowest, prefer-stable] include: @@ -22,6 +22,8 @@ jobs: exclude: - laravel: "^10.0" php: 8.0 + - laravel: "^8.12" + php: 8.3 name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }} From 3a7a17c03d3da28ea3a16949ab348e686e53dfe2 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Sun, 8 Oct 2023 15:51:00 -0400 Subject: [PATCH 388/648] [Docs] Add more UI options --- docs/advanced-usage/ui-options.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/advanced-usage/ui-options.md b/docs/advanced-usage/ui-options.md index 86151cfed..d9628e766 100644 --- a/docs/advanced-usage/ui-options.md +++ b/docs/advanced-usage/ui-options.md @@ -7,6 +7,10 @@ weight: 11 The package doesn't come with any screens out of the box, you should build that yourself. Here are some options to get you started: +- [Code With Tony - video series](https://www.youtube.com/watch?v=lGfV1ddMhHA) to create an admin panel for managing roles and permissions in Laravel 9. + +- [FilamentPHP plugin](https://filamentphp.com/plugins/tharinda-rodrigo-spatie-roles-permissions) to manage roles and permissions using this package. + - If you'd like to build your own UI, and understand the underlying logic for Gates and Roles and Users, the [Laravel 6 User Login and Management With Roles](https://www.youtube.com/watch?v=7PpJsho5aak&list=PLxFwlLOncxFLazmEPiB4N0iYc3Dwst6m4) video series by Mark Twigg of Penguin Digital gives thorough coverage to the topic, the theory, and implementation of a basic Roles system, independent of this Permissions Package. - [Laravel Nova package by @vyuldashev for managing Roles and Permissions](https://github.com/vyuldashev/nova-permission) From dcad17382210404bbe8c55ae78c5271e734d17b2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Oct 2023 14:18:34 -0400 Subject: [PATCH 389/648] Bump stefanzweifel/git-auto-commit-action from 4 to 5 (#2514) Bumps [stefanzweifel/git-auto-commit-action](https://github.com/stefanzweifel/git-auto-commit-action) from 4 to 5. - [Release notes](https://github.com/stefanzweifel/git-auto-commit-action/releases) - [Changelog](https://github.com/stefanzweifel/git-auto-commit-action/blob/master/CHANGELOG.md) - [Commits](https://github.com/stefanzweifel/git-auto-commit-action/compare/v4...v5) --- updated-dependencies: - dependency-name: stefanzweifel/git-auto-commit-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/fix-php-code-style-issues.yml | 2 +- .github/workflows/update-changelog.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/fix-php-code-style-issues.yml b/.github/workflows/fix-php-code-style-issues.yml index 3687af011..53c026f89 100644 --- a/.github/workflows/fix-php-code-style-issues.yml +++ b/.github/workflows/fix-php-code-style-issues.yml @@ -19,6 +19,6 @@ jobs: uses: aglipanci/laravel-pint-action@2.3.0 - name: Commit changes - uses: stefanzweifel/git-auto-commit-action@v4 + uses: stefanzweifel/git-auto-commit-action@v5 with: commit_message: Fix styling diff --git a/.github/workflows/update-changelog.yml b/.github/workflows/update-changelog.yml index ab69c62c1..0cdea2336 100644 --- a/.github/workflows/update-changelog.yml +++ b/.github/workflows/update-changelog.yml @@ -21,7 +21,7 @@ jobs: release-notes: ${{ github.event.release.body }} - name: Commit updated CHANGELOG - uses: stefanzweifel/git-auto-commit-action@v4.16.0 + uses: stefanzweifel/git-auto-commit-action@v5 with: branch: main commit_message: Update CHANGELOG From a10d72d40ec41e3e65fe390864667f4c2dec2fb7 Mon Sep 17 00:00:00 2001 From: Julian Gums Date: Thu, 12 Oct 2023 12:51:16 +0300 Subject: [PATCH 390/648] remove redundant semicolon (#2516) --- database/migrations/add_teams_fields.php.stub | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/migrations/add_teams_fields.php.stub b/database/migrations/add_teams_fields.php.stub index b8935dd09..01fcca697 100644 --- a/database/migrations/add_teams_fields.php.stub +++ b/database/migrations/add_teams_fields.php.stub @@ -59,7 +59,7 @@ return new class extends Migration if (! Schema::hasColumn($tableNames['model_has_roles'], $columnNames['team_foreign_key'])) { Schema::table($tableNames['model_has_roles'], function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole) { - $table->unsignedBigInteger($columnNames['team_foreign_key'])->default('1');; + $table->unsignedBigInteger($columnNames['team_foreign_key'])->default('1'); $table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index'); if (DB::getDriverName() !== 'sqlite') { From 6732077cfc85a13afb66ebf5cd81f8222ef5bd23 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 13 Oct 2023 16:59:26 -0400 Subject: [PATCH 391/648] mention Policy::before() --- docs/basic-usage/super-admin.md | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/docs/basic-usage/super-admin.md b/docs/basic-usage/super-admin.md index 70cb0b5a1..5fcfbccc1 100644 --- a/docs/basic-usage/super-admin.md +++ b/docs/basic-usage/super-admin.md @@ -5,7 +5,7 @@ weight: 8 We strongly recommend that a Super-Admin be handled by setting a global `Gate::before` or `Gate::after` rule which checks for the desired role. -Then you can implement the best-practice of primarily using permission-based controls (@can and $user->can, etc) throughout your app, without always having to check for "is this a super-admin" everywhere. Best not to use role-checking (ie: `hasRole`) when you have Super Admin features like this. +Then you can implement the best-practice of primarily using permission-based controls (@can and $user->can, etc) throughout your app, without always having to check for "is this a super-admin" everywhere. **Best not to use role-checking (ie: `hasRole`) (except here in Gate/Policy rules) when you have Super Admin features like this.** ## `Gate::before` @@ -33,6 +33,27 @@ NOTE: `Gate::before` rules need to return `null` rather than `false`, else it wi Jeffrey Way explains the concept of a super-admin (and a model owner, and model policies) in the [Laravel 6 Authorization Filters](https://laracasts.com/series/laravel-6-from-scratch/episodes/51) video and some related lessons in that chapter. +## Policy `before()` + +If you aren't using `Gate::before()` as described above, you could alternatively grant super-admin control by checking the role in individual Policy classes, using the `before()` method. + +Here is an example from the [Laravel Documentation on Policy Filters]([url](https://laravel.com/docs/master/authorization#policy-filters)) + +```php +use App\Models\User; // could be any model + +/** + * Perform pre-authorization checks on the model. + */ +public function before(User $user, string $ability): bool|null +{ + if ($user->hasRole('Super Admin') { + return true; + } + + return null; // see the note above in Gate::before about why null must be returned here. +} +``` ## `Gate::after` From 8e2d6632c1541fd4aabf9dad88ae5aa6783a5a40 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 13 Oct 2023 17:06:09 -0400 Subject: [PATCH 392/648] Links back to Laravel Docs --- docs/basic-usage/super-admin.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/basic-usage/super-admin.md b/docs/basic-usage/super-admin.md index 5fcfbccc1..fac0374aa 100644 --- a/docs/basic-usage/super-admin.md +++ b/docs/basic-usage/super-admin.md @@ -9,7 +9,7 @@ Then you can implement the best-practice of primarily using permission-based con ## `Gate::before` -If you want a "Super Admin" role to respond `true` to all permissions, without needing to assign all those permissions to a role, you can use Laravel's `Gate::before()` method. For example: +If you want a "Super Admin" role to respond `true` to all permissions, without needing to assign all those permissions to a role, you can use [Laravel's `Gate::before()` method](https://laravel.com/docs/master/authorization#intercepting-gate-checks). For example: ```php use Illuminate\Support\Facades\Gate; @@ -37,7 +37,7 @@ Jeffrey Way explains the concept of a super-admin (and a model owner, and model If you aren't using `Gate::before()` as described above, you could alternatively grant super-admin control by checking the role in individual Policy classes, using the `before()` method. -Here is an example from the [Laravel Documentation on Policy Filters]([url](https://laravel.com/docs/master/authorization#policy-filters)) +Here is an example from the [Laravel Documentation on Policy Filters](https://laravel.com/docs/master/authorization#policy-filters) ```php use App\Models\User; // could be any model @@ -59,7 +59,7 @@ public function before(User $user, string $ability): bool|null Alternatively you might want to move the Super Admin check to the `Gate::after` phase instead, particularly if your Super Admin shouldn't be allowed to do things your app doesn't want "anyone" to do, such as writing more than 1 review, or bypassing unsubscribe rules, etc. -The following code snippet is inspired from [Freek's blog article](https://freek.dev/1325-when-to-use-gateafter-in-laravel) where this topic is discussed further. +The following code snippet is inspired from [Freek's blog article](https://freek.dev/1325-when-to-use-gateafter-in-laravel) where this topic is discussed further. You can also consult the [Laravel Docs on gate interceptions](https://laravel.com/docs/master/authorization#intercepting-gate-checks) ```php // somewhere in a service provider From c4be1d931281cedfb2337fc63e921498687d4822 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 13 Oct 2023 17:16:25 -0400 Subject: [PATCH 393/648] [Docs] Lumen not officially supported --- docs/installation-lumen.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/installation-lumen.md b/docs/installation-lumen.md index ff3e4bce2..7709777e6 100644 --- a/docs/installation-lumen.md +++ b/docs/installation-lumen.md @@ -3,7 +3,9 @@ title: Installation in Lumen weight: 5 --- -NOTE: Lumen is **not** officially supported by this package. However, the following are some steps which may help get you started. +NOTE: Lumen is **not** officially supported by this package. And Lumen is no longer under active development. + +However, the following are some steps which may help get you started. Lumen installation instructions can be found in the [Lumen documentation](https://lumen.laravel.com/docs/master). From 8a5dd87f5157416d5605628fcb31a09d14eed1f1 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Mon, 16 Oct 2023 16:44:37 -0400 Subject: [PATCH 394/648] Add note about Middleware Priority in docs Fixes #2507 --- docs/basic-usage/middleware.md | 3 +++ docs/basic-usage/teams-permissions.md | 8 +++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/docs/basic-usage/middleware.md b/docs/basic-usage/middleware.md index 592fe8343..fee16f8ed 100644 --- a/docs/basic-usage/middleware.md +++ b/docs/basic-usage/middleware.md @@ -40,6 +40,9 @@ protected $middlewareAliases = [ ]; ``` +**YOU SHOULD ALSO** set [the `$middlewarePriority` array](https://laravel.com/docs/master/middleware#sorting-middleware) to include this package's middleware before the `SubstituteBindings` middleware, else you may get *404 Not Found* responses when a *403 Not Authorized* response might be expected. + + ## Middleware via Routes Then you can protect your routes using middleware rules: diff --git a/docs/basic-usage/teams-permissions.md b/docs/basic-usage/teams-permissions.md index d60657377..d6381daa6 100644 --- a/docs/basic-usage/teams-permissions.md +++ b/docs/basic-usage/teams-permissions.md @@ -35,7 +35,8 @@ Example Team Middleware: ```php namespace App\Http\Middleware; -class TeamsPermission{ +class TeamsPermission +{ public function handle($request, \Closure $next){ if(!empty(auth()->user())){ @@ -52,8 +53,9 @@ class TeamsPermission{ } } ``` -NOTE: You must add your custom `Middleware` to `$middlewarePriority` in `app/Http/Kernel.php`. - + +**YOU MUST ALSO** set [the `$middlewarePriority` array](https://laravel.com/docs/master/middleware#sorting-middleware) in `app/Http/Kernel.php` to include your custom middleware before the `SubstituteBindings` middleware, else you may get *404 Not Found* responses when a *403 Not Authorized* response might be expected. + ## Roles Creating When creating a role you can pass the `team_id` as an optional parameter From ddd396898a7abca829741230afd66bc5db6d4ee8 Mon Sep 17 00:00:00 2001 From: SuperDJ <6484766+SuperDJ@users.noreply.github.com> Date: Wed, 18 Oct 2023 22:14:26 +0200 Subject: [PATCH 395/648] Update passport.md (#2521) Add missing information --- docs/basic-usage/passport.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/basic-usage/passport.md b/docs/basic-usage/passport.md index 4faa50fc3..1926f1273 100644 --- a/docs/basic-usage/passport.md +++ b/docs/basic-usage/passport.md @@ -38,6 +38,11 @@ You need to extend the Client model to make it possible to add the required trai The extended Client should either provide a `$guard_name` property or a `guardName()` method. They should return a string that matches the [configured](https://laravel.com/docs/master/passport#installation) guard name for the passport driver. +To tell Passport to use this extended Client, add the rule below to the `boot` method of your `App\Providers\AuthServiceProvider` class. +```php +Passport::useClientModel(\App\Models\Client::class); // Use the namespace of your extended Client. +``` + ## Middleware All middleware provided by this package work with the Client. From 7545d85a61cf0cecbf20a471caea262bbdfbc62b Mon Sep 17 00:00:00 2001 From: Vitali K Date: Mon, 23 Oct 2023 21:21:47 +0400 Subject: [PATCH 396/648] [Docs] Add missing closing bracket (#2524) --- docs/basic-usage/super-admin.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/basic-usage/super-admin.md b/docs/basic-usage/super-admin.md index fac0374aa..35392772a 100644 --- a/docs/basic-usage/super-admin.md +++ b/docs/basic-usage/super-admin.md @@ -47,7 +47,7 @@ use App\Models\User; // could be any model */ public function before(User $user, string $ability): bool|null { - if ($user->hasRole('Super Admin') { + if ($user->hasRole('Super Admin')) { return true; } From 4e6e86d1a6a09f6b9ec6e92dd9cfcd6a614347b3 Mon Sep 17 00:00:00 2001 From: Axel Cervantes Date: Mon, 23 Oct 2023 23:16:41 -0600 Subject: [PATCH 397/648] Has permission directive - Non Default guard (#2515) * Has permission directive to use instead can directive when using non-default guard --- src/PermissionServiceProvider.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/PermissionServiceProvider.php b/src/PermissionServiceProvider.php index f2643cef0..b68f31984 100644 --- a/src/PermissionServiceProvider.php +++ b/src/PermissionServiceProvider.php @@ -123,6 +123,10 @@ protected function registerBladeExtensions($bladeCompiler): void $bladeCompiler->directive('elserole', fn ($args) => ""); $bladeCompiler->directive('endrole', fn () => ''); + $bladeCompiler->directive('haspermission', fn ($args) => ""); + $bladeCompiler->directive('elsehaspermission', fn ($args) => ""); + $bladeCompiler->directive('endhaspermission', fn () => ''); + $bladeCompiler->directive('hasrole', fn ($args) => ""); $bladeCompiler->directive('endhasrole', fn () => ''); From 914b9a4db99b93c65dcf95070f87977dab8f662f Mon Sep 17 00:00:00 2001 From: drbyte Date: Tue, 24 Oct 2023 05:17:06 +0000 Subject: [PATCH 398/648] Fix styling --- src/Exceptions/PermissionDoesNotExist.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Exceptions/PermissionDoesNotExist.php b/src/Exceptions/PermissionDoesNotExist.php index b451ace6b..c04f5eaf0 100644 --- a/src/Exceptions/PermissionDoesNotExist.php +++ b/src/Exceptions/PermissionDoesNotExist.php @@ -13,7 +13,6 @@ public static function create(string $permissionName, ?string $guardName) /** * @param int|string $permissionId - * @param string $guardName * @return static */ public static function withId($permissionId, ?string $guardName) From f8af645d2d98db9a8ed65105256981b128a386b1 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 24 Oct 2023 01:17:29 -0400 Subject: [PATCH 399/648] Add Policy Test (#2525) * Add Policy Test * Add Gate::after test --- tests/GateTest.php | 13 ++++++++++ tests/PolicyTest.php | 49 ++++++++++++++++++++++++++++++++++++ tests/TestCase.php | 7 ++++++ tests/TestModels/Content.php | 12 +++++++++ 4 files changed, 81 insertions(+) create mode 100644 tests/PolicyTest.php create mode 100644 tests/TestModels/Content.php diff --git a/tests/GateTest.php b/tests/GateTest.php index 93359834a..436900b84 100644 --- a/tests/GateTest.php +++ b/tests/GateTest.php @@ -19,6 +19,19 @@ public function it_allows_other_gate_before_callbacks_to_run_if_a_user_does_not_ $this->assertFalse($this->testUser->can('edit-articles')); app(Gate::class)->before(function () { + // this Gate-before intercept overrides everything to true ... like a typical Super-Admin might use + return true; + }); + + $this->assertTrue($this->testUser->can('edit-articles')); + } + + /** @test */ + public function it_allows_gate_after_callback_to_grant_denied_privileges() + { + $this->assertFalse($this->testUser->can('edit-articles')); + + app(Gate::class)->after(function ($user, $ability, $result) { return true; }); diff --git a/tests/PolicyTest.php b/tests/PolicyTest.php new file mode 100644 index 000000000..7708225b0 --- /dev/null +++ b/tests/PolicyTest.php @@ -0,0 +1,49 @@ + 'special admin content']); + $record2 = Content::create(['content' => 'viewable', 'user_id' => $this->testUser->id]); + + app(Gate::class)->policy(Content::class, ContentPolicy::class); + + $this->assertFalse($this->testUser->can('view', $record1)); // policy rule for 'view' + $this->assertFalse($this->testUser->can('update', $record1)); // policy rule for 'update' + + $this->assertTrue($this->testUser->can('update', $record2)); // policy rule for 'update' when matching user_id + + // test that the Admin cannot yet view 'special admin content', because doesn't have Role yet + $this->assertFalse($this->testAdmin->can('update', $record1)); + + $this->testAdmin->assignRole($this->testAdminRole); + // test that the Admin can view 'special admin content' + $this->assertTrue($this->testAdmin->can('update', $record1)); // policy override via 'before' + $this->assertTrue($this->testAdmin->can('update', $record2)); // policy override via 'before' + } +} +class ContentPolicy +{ + public function before(Authorizable $user, string $ability): ?bool + { + return $user->hasRole('testAdminRole', 'admin') ?: null; + } + + public function view($user, $content) + { + return $user->id === $content->user_id; + } + + public function update($user, $modelRecord): bool + { + return $user->id === $modelRecord->user_id || $user->can('edit-articles'); + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php index 302169f65..48de05ed5 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -157,6 +157,13 @@ protected function setUpDatabase($app) $table->string('email'); }); + $schema->create('content', function (Blueprint $table) { + $table->increments('id'); + $table->string('content'); + $table->foreignId('user_id')->nullable()->constrained()->nullOnDelete(); + $table->timestamps(); + }); + if (Cache::getStore() instanceof \Illuminate\Cache\DatabaseStore || $app[PermissionRegistrar::class]->getCacheStore() instanceof \Illuminate\Cache\DatabaseStore) { $this->createCacheTable(); diff --git a/tests/TestModels/Content.php b/tests/TestModels/Content.php new file mode 100644 index 000000000..6b959ea06 --- /dev/null +++ b/tests/TestModels/Content.php @@ -0,0 +1,12 @@ + Date: Tue, 24 Oct 2023 01:21:20 -0400 Subject: [PATCH 400/648] Add tests for @haspermission() directives Ref #2515 --- tests/BladeTest.php | 32 ++++++++++++++++++- tests/resources/views/haspermission.blade.php | 7 ++++ 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 tests/resources/views/haspermission.blade.php diff --git a/tests/BladeTest.php b/tests/BladeTest.php index 02a899083..18d17a113 100644 --- a/tests/BladeTest.php +++ b/tests/BladeTest.php @@ -27,8 +27,10 @@ public function all_blade_directives_will_evaluate_false_when_there_is_nobody_lo $role = 'writer'; $roles = [$role]; $elserole = 'na'; + $elsepermission = 'na'; $this->assertEquals('does not have permission', $this->renderView('can', ['permission' => $permission])); + $this->assertEquals('does not have permission', $this->renderView('haspermission', compact('permission', 'elsepermission'))); $this->assertEquals('does not have role', $this->renderView('role', compact('role', 'elserole'))); $this->assertEquals('does not have role', $this->renderView('hasRole', compact('role', 'elserole'))); $this->assertEquals('does not have all of the given roles', $this->renderView('hasAllRoles', compact('roles'))); @@ -44,10 +46,12 @@ public function all_blade_directives_will_evaluate_false_when_somebody_without_r $role = 'writer'; $roles = 'writer'; $elserole = 'na'; + $elsepermission = 'na'; auth()->setUser($this->testUser); - $this->assertEquals('does not have permission', $this->renderView('can', ['permission' => $permission])); + $this->assertEquals('does not have permission', $this->renderView('can', compact('permission'))); + $this->assertEquals('does not have permission', $this->renderView('haspermission', compact('permission', 'elsepermission'))); $this->assertEquals('does not have role', $this->renderView('role', compact('role', 'elserole'))); $this->assertEquals('does not have role', $this->renderView('hasRole', compact('role', 'elserole'))); $this->assertEquals('does not have all of the given roles', $this->renderView('hasAllRoles', compact('roles'))); @@ -61,10 +65,12 @@ public function all_blade_directives_will_evaluate_false_when_somebody_with_anot $role = 'writer'; $roles = 'writer'; $elserole = 'na'; + $elsepermission = 'na'; auth('admin')->setUser($this->testAdmin); $this->assertEquals('does not have permission', $this->renderView('can', compact('permission'))); + $this->assertEquals('does not have permission', $this->renderView('haspermission', compact('permission', 'elsepermission'))); $this->assertEquals('does not have role', $this->renderView('role', compact('role', 'elserole'))); $this->assertEquals('does not have role', $this->renderView('hasRole', compact('role', 'elserole'))); $this->assertEquals('does not have all of the given roles', $this->renderView('hasAllRoles', compact('roles'))); @@ -84,6 +90,30 @@ public function the_can_directive_will_evaluate_true_when_the_logged_in_user_has $this->assertEquals('has permission', $this->renderView('can', ['permission' => 'edit-articles'])); } + /** @test */ + public function the_haspermission_directive_will_evaluate_true_when_the_logged_in_user_has_the_permission() + { + $user = $this->getWriter(); + + $permission = 'edit-articles'; + $user->givePermissionTo('edit-articles'); + + auth()->setUser($user); + + $this->assertEquals('has permission', $this->renderView('haspermission', compact('permission'))); + + $guard = 'admin'; + $elsepermission = 'na'; + $this->assertEquals('does not have permission', $this->renderView('haspermission', compact('permission', 'elsepermission' ,'guard'))); + + $this->testAdminRole->givePermissionTo($this->testAdminPermission); + $this->testAdmin->assignRole($this->testAdminRole); + auth('admin')->setUser($this->testAdmin); + $guard = 'admin'; + $permission = 'admin-permission'; + $this->assertEquals('has permission', $this->renderView('haspermission', compact('permission', 'guard', 'elsepermission'))); + } + /** @test */ public function the_role_directive_will_evaluate_true_when_the_logged_in_user_has_the_role() { diff --git a/tests/resources/views/haspermission.blade.php b/tests/resources/views/haspermission.blade.php new file mode 100644 index 000000000..7dd4e7903 --- /dev/null +++ b/tests/resources/views/haspermission.blade.php @@ -0,0 +1,7 @@ +@haspermission($permission, $guard ?? null) +has permission +@elsehaspermission($elsepermission, $guard ?? null) +has else permission +@else +does not have permission +@endhaspermission From 669d828de3de05f866c234d4e315215efb525b01 Mon Sep 17 00:00:00 2001 From: drbyte Date: Tue, 24 Oct 2023 05:21:44 +0000 Subject: [PATCH 401/648] Fix styling --- tests/BladeTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/BladeTest.php b/tests/BladeTest.php index 18d17a113..c65973b43 100644 --- a/tests/BladeTest.php +++ b/tests/BladeTest.php @@ -104,7 +104,7 @@ public function the_haspermission_directive_will_evaluate_true_when_the_logged_i $guard = 'admin'; $elsepermission = 'na'; - $this->assertEquals('does not have permission', $this->renderView('haspermission', compact('permission', 'elsepermission' ,'guard'))); + $this->assertEquals('does not have permission', $this->renderView('haspermission', compact('permission', 'elsepermission', 'guard'))); $this->testAdminRole->givePermissionTo($this->testAdminPermission); $this->testAdmin->assignRole($this->testAdminRole); From 239abc50a20b7b5b8dc80793ecc466433542852a Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Wed, 25 Oct 2023 00:55:50 -0400 Subject: [PATCH 402/648] Add guard parameter to can() (#2526) * Add guard parameter to can() As discussed in comments on 2515: https://github.com/spatie/laravel-permission/pull/2515#issuecomment-1762134150 Co-authored-by: parallels999 --- src/PermissionRegistrar.php | 7 +++-- tests/BladeTest.php | 30 +++++++++++++++++++++ tests/MultipleGuardsTest.php | 42 +++++++++++++++++++++++++++++ tests/resources/views/can.blade.php | 2 +- 4 files changed, 78 insertions(+), 3 deletions(-) diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index 30c6d5676..8a6e28cad 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -122,9 +122,12 @@ public function getPermissionsTeamId() */ public function registerPermissions(Gate $gate): bool { - $gate->before(function (Authorizable $user, string $ability) { + $gate->before(function (Authorizable $user, string $ability, array &$args = []) { + if (is_string($args[0] ?? null) && ! class_exists($args[0])) { + $guard = array_shift($args); + } if (method_exists($user, 'checkPermissionTo')) { - return $user->checkPermissionTo($ability) ?: null; + return $user->checkPermissionTo($ability, $guard ?? null) ?: null; } }); diff --git a/tests/BladeTest.php b/tests/BladeTest.php index c65973b43..7e19ec81f 100644 --- a/tests/BladeTest.php +++ b/tests/BladeTest.php @@ -78,6 +78,36 @@ public function all_blade_directives_will_evaluate_false_when_somebody_with_anot $this->assertEquals('does not have any of the given roles', $this->renderView('hasAnyRole', compact('roles'))); } + /** @test */ + public function the_can_directive_can_accept_a_guard_name() + { + $user = $this->getWriter(); + $user->givePermissionTo('edit-articles'); + auth()->setUser($user); + + $permission = 'edit-articles'; + $guard = 'web'; + $this->assertEquals('has permission', $this->renderView('can', compact('permission', 'guard'))); + $guard = 'admin'; + $this->assertEquals('does not have permission', $this->renderView('can', compact('permission', 'guard'))); + + auth()->logout(); + + // log in as the Admin with the permission-via-role + $this->testAdmin->givePermissionTo($this->testAdminPermission); + $user = $this->testAdmin; + auth()->setUser($user); + + $permission = 'edit-articles'; + $guard = 'web'; + $this->assertEquals('does not have permission', $this->renderView('can', compact('permission', 'guard'))); + + $permission = 'admin-permission'; + $guard = 'admin'; + $this->assertTrue($this->testAdmin->checkPermissionTo($permission, $guard)); + $this->assertEquals('has permission', $this->renderView('can', compact('permission', 'guard'))); + } + /** @test */ public function the_can_directive_will_evaluate_true_when_the_logged_in_user_has_the_permission() { diff --git a/tests/MultipleGuardsTest.php b/tests/MultipleGuardsTest.php index c93069f58..64c229a56 100644 --- a/tests/MultipleGuardsTest.php +++ b/tests/MultipleGuardsTest.php @@ -18,6 +18,7 @@ protected function getEnvironmentSetUp($app) 'api' => ['driver' => 'token', 'provider' => 'users'], 'jwt' => ['driver' => 'token', 'provider' => 'users'], 'abc' => ['driver' => 'abc'], + 'admin' => ['driver' => 'session', 'provider' => 'admins'], ]); $this->setUpRoutes(); @@ -53,6 +54,47 @@ public function it_can_give_a_permission_to_a_model_that_is_used_by_multiple_gua $this->assertFalse($this->testUser->checkPermissionTo('do_that', 'web')); } + /** @test */ + public function the_gate_can_grant_permission_to_a_user_by_passing_a_guard_name() + { + $this->testUser->givePermissionTo(app(Permission::class)::create([ + 'name' => 'do_this', + 'guard_name' => 'web', + ])); + + $this->testUser->givePermissionTo(app(Permission::class)::create([ + 'name' => 'do_that', + 'guard_name' => 'api', + ])); + + $this->assertTrue($this->testUser->can('do_this', 'web')); + $this->assertTrue($this->testUser->can('do_that', 'api')); + $this->assertFalse($this->testUser->can('do_that', 'web')); + + $this->assertTrue($this->testUser->cannot('do_that', 'web')); + $this->assertTrue($this->testUser->canAny(['do_this', 'do_that'], 'web')); + + $this->testAdminRole->givePermissionTo($this->testAdminPermission); + $this->testAdmin->assignRole($this->testAdminRole); + + $this->assertTrue($this->testAdmin->hasPermissionTo($this->testAdminPermission)); + $this->assertTrue($this->testAdmin->can('admin-permission')); + $this->assertTrue($this->testAdmin->can('admin-permission', 'admin')); + $this->assertTrue($this->testAdmin->cannot('admin-permission', 'web')); + + $this->assertTrue($this->testAdmin->cannot('non-existing-permission')); + $this->assertTrue($this->testAdmin->cannot('non-existing-permission', 'web')); + $this->assertTrue($this->testAdmin->cannot('non-existing-permission', 'admin')); + $this->assertTrue($this->testAdmin->cannot(['admin-permission', 'non-existing-permission'], 'web')); + + $this->assertFalse($this->testAdmin->can('edit-articles', 'web')); + $this->assertFalse($this->testAdmin->can('edit-articles', 'admin')); + + $this->assertTrue($this->testUser->cannot('edit-articles', 'admin')); + $this->assertTrue($this->testUser->cannot('admin-permission', 'admin')); + $this->assertTrue($this->testUser->cannot('admin-permission', 'web')); + } + /** @test */ public function it_can_honour_guardName_function_on_model_for_overriding_guard_name_property() { diff --git a/tests/resources/views/can.blade.php b/tests/resources/views/can.blade.php index 81d526906..f0c6c8214 100644 --- a/tests/resources/views/can.blade.php +++ b/tests/resources/views/can.blade.php @@ -1,4 +1,4 @@ -@can($permission) +@can($permission, $guard ?? null) has permission @else does not have permission From cdc329c367bde4d35716c8d04f95e8adafc6afdb Mon Sep 17 00:00:00 2001 From: Hamid Dehnavi Date: Wed, 25 Oct 2023 08:27:56 +0330 Subject: [PATCH 403/648] Update uuid doc (#2527) --- docs/advanced-usage/uuid.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/advanced-usage/uuid.md b/docs/advanced-usage/uuid.md index 2209159d9..79c1331bb 100644 --- a/docs/advanced-usage/uuid.md +++ b/docs/advanced-usage/uuid.md @@ -64,6 +64,18 @@ If you also want the roles and permissions to use a UUID for their `id` value, t - $table->unsignedBigInteger(PermissionRegistrar::$pivotRole); + $table->uuid(PermissionRegistrar::$pivotPermission); + $table->uuid(PermissionRegistrar::$pivotRole); + + $table->foreign(PermissionRegistrar::$pivotPermission) +- ->references('id') // permission id ++ ->references('uuid') // permission id + ->on($tableNames['permissions']) + ->onDelete('cascade'); + + $table->foreign(PermissionRegistrar::$pivotRole) +- ->references('id') // role id ++ ->references('uuid') // role id + ->on($tableNames['roles']) + ->onDelete('cascade'); ``` From 9d1def88fd2b8b4a44937d995997c9ab2aff0a23 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Wed, 25 Oct 2023 01:06:49 -0400 Subject: [PATCH 404/648] [Docs] Mention @haspermission() directive --- docs/basic-usage/blade-directives.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/docs/basic-usage/blade-directives.md b/docs/basic-usage/blade-directives.md index 51924866b..3d95f8ec9 100644 --- a/docs/basic-usage/blade-directives.md +++ b/docs/basic-usage/blade-directives.md @@ -4,8 +4,7 @@ weight: 7 --- ## Permissions -This package doesn't add any **permission**-specific Blade directives. -Instead, use Laravel's native `@can` directive to check if a user has a certain permission. +This package lets you use Laravel's native `@can` directive to check if a user has a certain permission (whether you gave them that permission directly or if you granted it indirectly via a role): ```php @can('edit articles') @@ -21,13 +20,17 @@ or You can use `@can`, `@cannot`, `@canany`, and `@guest` to test for permission-related access. +When using a permission-name associated with permissions created in this package, you can use `@can('permission-name', 'guard_name')` if you need to check against a specific guard. + +You can also use `@haspermission('permission-name')` or `@haspermission('permission-name', 'guard_name')` in similar fashion. With corresponding `@endhaspermission`. + ## Roles As discussed in the Best Practices section of the docs, **it is strongly recommended to always use permission directives**, instead of role directives. Additionally, if your reason for testing against Roles is for a Super-Admin, see the *Defining A Super-Admin* section of the docs. -If you actually need to test for Roles, this package offers some Blade directives to verify whether the currently logged in user has all or any of a given list of roles. +If you actually need to directly test for Roles, this package offers some Blade directives to verify whether the currently logged in user has all or any of a given list of roles. Optionally you can pass in the `guard` that the check will be performed on as a second argument. @@ -48,6 +51,12 @@ is the same as I am not a writer... @endhasrole ``` +which is also the same as +```php +@if(auth()->user()->hasRole('writer')) + // +@endif +``` Check for any role in a list: ```php From e0d770c4a8c474dd7051e7e8b55fcdb349cb74b0 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Wed, 25 Oct 2023 01:22:02 -0400 Subject: [PATCH 405/648] [Docs] Update upgrade docs --- docs/upgrading.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/upgrading.md b/docs/upgrading.md index ab83fd790..5d3f00f2a 100644 --- a/docs/upgrading.md +++ b/docs/upgrading.md @@ -8,7 +8,7 @@ weight: 6 ALL upgrades of this package should follow these steps: 1. Composer. Upgrading between major versions of this package always require the usual Composer steps: - - Update your `composer.json` to specify the new major version, such as `^6.0` + - Update your `composer.json` to specify the new major version, for example: `^6.0` - Then run `composer update`. 2. Migrations. Compare the `migration` file stubs in the NEW version of this package against the migrations you've already run inside your app. If necessary, create a new migration (by hand) to apply any new database changes. @@ -30,6 +30,8 @@ and/or consult the [Release Notes](https://github.com/spatie/laravel-permission/ ## Upgrading from v5 to v6 There are a few breaking-changes when upgrading to v6, but most of them won't affect you unless you have been customizing things. +For guidance with upgrading your extended models, your migrations, your routes, etc, see the **Upgrade Essentials** section at the top of this file. + 1. If you have overridden the `getPermissionClass()` or `getRoleClass()` methods or have custom Models, you will need to revisit those customizations. See PR #2368 for details. eg: if you have a custom model you will need to make changes, including accessing the model using `$this->permissionClass::` syntax (eg: using `::` instead of `->`) in all the overridden methods that make use of the models. @@ -42,15 +44,15 @@ eg: if you have a custom model you will need to make changes, including accessin 4. MIDDLEWARE: - 1. The `\Spatie\Permission\Middlewares\` namespace has been renamed to `\Spatie\Permission\Middleware\` (singular). Update your references to them in your `/app/Http/Kernel.php` and any routes that have the fully qualified path. + 1. The `\Spatie\Permission\Middlewares\` namespace has been renamed to `\Spatie\Permission\Middleware\` (singular). Update any references to them in your `/app/Http/Kernel.php` and any routes (or imported classes in your routes files) that have the fully qualified namespace. 2. NOTE: For consistency with `PermissionMiddleware`, the `RoleOrPermissionMiddleware` has switched from only checking permissions provided by this package to using `canAny()` to check against any abilities registered by your application. This may have the effect of granting those other abilities (such as Super Admin) when using the `RoleOrPermissionMiddleware`, which previously would have failed silently. - 3. In the unlikely event that you have customized the Wildcard Permissions feature by extending the `WildcardPermission` model, please note that the public interface has changed and you will need to update your extended model with the new method signatures. + 3. In the unlikely event that you have customized the Wildcard Permissions feature by extending the `WildcardPermission` model, please note that the public interface has changed significantly and you will need to update your extended model with the new method signatures. 5. Test suites. If you have tests which manually clear the permission cache and re-register permissions, you no longer need to call `\Spatie\Permission\PermissionRegistrar::class)->registerPermissions();`. In fact, **calls to `->registerPermissions()` MUST be deleted from your tests**. - (Calling `app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();` after creating roles and permissions is still okay and encouraged.) + (Calling `app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();` after creating roles and permissions in migrations and factories and seeders is still okay and encouraged.) ## Upgrading from v4 to v5 From 095821962f8f287f0ce1d6265fc7aa7070dacf12 Mon Sep 17 00:00:00 2001 From: drbyte Date: Wed, 25 Oct 2023 05:25:50 +0000 Subject: [PATCH 406/648] Update CHANGELOG --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1e5f9f0b..e64ce47bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to `laravel-permission` will be documented in this file +## 5.11.1 - 2023-10-25 + +No functional changes. Just several small updates to the Documentation. + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/5.11.0...5.11.1 + ## 5.11.0 - 2023-08-30 ### What's Changed @@ -627,6 +633,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` 1. Also this is a good time to point out that now with v2.25.0 and v2.26.0 most permission-cache-reset scenarios may no longer be needed in your app, so it's worth reviewing those cases, as you may gain some app speed improvement by removing unnecessary cache resets. @@ -684,6 +691,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` ## 2.19.1 - 2018-09-14 From ddfe3870d850c1059fc8452ecfb9003f2232142e Mon Sep 17 00:00:00 2001 From: drbyte Date: Wed, 25 Oct 2023 05:38:11 +0000 Subject: [PATCH 407/648] Update CHANGELOG --- CHANGELOG.md | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e64ce47bc..cc5ade7aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,69 @@ All notable changes to `laravel-permission` will be documented in this file +## 6.0.0 - 2023-10-25 + +### What's Changed + +- Full uuid/guid/ulid support by @erikn69 in https://github.com/spatie/laravel-permission/pull/2089 +- Refactor: Change static properties to non-static by @olivernybroe in https://github.com/spatie/laravel-permission/pull/2324 +- Fix Role::withCount if belongsToMany declared by @xenaio-daniil in https://github.com/spatie/laravel-permission/pull/2280 +- Fix: Lazily bind dependencies by @olivernybroe in https://github.com/spatie/laravel-permission/pull/2321 +- Avoid loss of all permissions/roles pivots on sync error by @erikn69 in https://github.com/spatie/laravel-permission/pull/2341 +- Fix delete permissions on Permissions Model by @erikn69 in https://github.com/spatie/laravel-permission/pull/2366 +- Detach users on role/permission physical deletion by @erikn69 in https://github.com/spatie/laravel-permission/pull/2370 +- Rename clearClassPermissions method to clearPermissionsCollection by @erikn69 in https://github.com/spatie/laravel-permission/pull/2369 +- Use anonymous migrations (for L8+) by @erikn69 in https://github.com/spatie/laravel-permission/pull/2374 +- [BC] Return string on getPermissionClass(), getRoleClass() by @erikn69 in https://github.com/spatie/laravel-permission/pull/2368 +- Only offer publishing when running in console by @erikn69 in https://github.com/spatie/laravel-permission/pull/2377 +- Don't add commands in web interface context by @angeljqv in https://github.com/spatie/laravel-permission/pull/2405 +- [BC] Fix Role->hasPermissionTo() signature to match HasPermissions trait by @erikn69 in https://github.com/spatie/laravel-permission/pull/2380 +- Force that getPermissionsViaRoles, hasPermissionViaRole must be used only by authenticable by @erikn69 in https://github.com/spatie/laravel-permission/pull/2382 +- fix BadMethodCallException: undefined methods hasAnyRole, hasAnyPermissions by @erikn69 in https://github.com/spatie/laravel-permission/pull/2381 +- Add PHPStan workflow with fixes by @erikn69 in https://github.com/spatie/laravel-permission/pull/2376 +- Add BackedEnum support by @drbyte in https://github.com/spatie/laravel-permission/pull/2391 +- Drop PHP 7.3 support by @angeljqv in https://github.com/spatie/laravel-permission/pull/2388 +- Drop PHP 7.4 support by @drbyte in https://github.com/spatie/laravel-permission/pull/2485 +- Test against PHP 8.3 by @erikn69 in https://github.com/spatie/laravel-permission/pull/2512 +- Fix call to an undefined method Role::getRoleClass by @erikn69 in https://github.com/spatie/laravel-permission/pull/2411 +- Remove force loading model relationships by @erikn69 in https://github.com/spatie/laravel-permission/pull/2412 +- Test alternate cache drivers by @erikn69 in https://github.com/spatie/laravel-permission/pull/2416 +- Use attach instead of sync on traits by @erikn69 in https://github.com/spatie/laravel-permission/pull/2420 +- Fewer sqls in syncRoles, syncPermissions by @erikn69 in https://github.com/spatie/laravel-permission/pull/2423 +- Add middleware using static method by @jnoordsij in https://github.com/spatie/laravel-permission/pull/2424 +- Update PHPDocs for IDE autocompletion by @erikn69 in https://github.com/spatie/laravel-permission/pull/2437 +- [BC] Wildcard permissions algorithm performance improvements (ALERT: Breaking Changes) by @danharrin in https://github.com/spatie/laravel-permission/pull/2445 +- Add withoutRole and withoutPermission scopes by @drbyte in https://github.com/spatie/laravel-permission/pull/2463 +- Add support for service-to-service Passport client by @SuperDJ in https://github.com/spatie/laravel-permission/pull/2467 +- Register OctaneReloadPermissions listener for Laravel Octane by @erikn69 in https://github.com/spatie/laravel-permission/pull/2403 +- Add guard name to exceptions by @drbyte in https://github.com/spatie/laravel-permission/pull/2481 +- Update contracts to allow for UUID by @drbyte in https://github.com/spatie/laravel-permission/pull/2480 +- Avoid triggering eloquent.retrieved event by @erikn69 in https://github.com/spatie/laravel-permission/pull/2498 +- [BC] Rename "Middlewares" namespace to "Middleware" by @drbyte in https://github.com/spatie/laravel-permission/pull/2499 +- `@haspermission` directive by @axlwild in https://github.com/spatie/laravel-permission/pull/2515 +- Add guard parameter to can() by @drbyte in https://github.com/spatie/laravel-permission/pull/2526 + +### New Contributors + +- @xenaio-daniil made their first contribution in https://github.com/spatie/laravel-permission/pull/2280 +- @JensvandeWiel made their first contribution in https://github.com/spatie/laravel-permission/pull/2336 +- @fsamapoor made their first contribution in https://github.com/spatie/laravel-permission/pull/2361 +- @yungifez made their first contribution in https://github.com/spatie/laravel-permission/pull/2394 +- @HasanEksi made their first contribution in https://github.com/spatie/laravel-permission/pull/2418 +- @jnoordsij made their first contribution in https://github.com/spatie/laravel-permission/pull/2424 +- @danharrin made their first contribution in https://github.com/spatie/laravel-permission/pull/2445 +- @SuperDJ made their first contribution in https://github.com/spatie/laravel-permission/pull/2467 +- @ChillMouse made their first contribution in https://github.com/spatie/laravel-permission/pull/2438 +- @Okipa made their first contribution in https://github.com/spatie/laravel-permission/pull/2492 +- @edalzell made their first contribution in https://github.com/spatie/laravel-permission/pull/2494 +- @sirosfakhri made their first contribution in https://github.com/spatie/laravel-permission/pull/2501 +- @juliangums made their first contribution in https://github.com/spatie/laravel-permission/pull/2516 +- @nnnnnnnngu made their first contribution in https://github.com/spatie/laravel-permission/pull/2524 +- @axlwild made their first contribution in https://github.com/spatie/laravel-permission/pull/2515 +- @shdehnavi made their first contribution in https://github.com/spatie/laravel-permission/pull/2527 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/5.11.1...6.0.0 + ## 5.11.1 - 2023-10-25 No functional changes. Just several small updates to the Documentation. @@ -634,6 +697,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` 1. Also this is a good time to point out that now with v2.25.0 and v2.26.0 most permission-cache-reset scenarios may no longer be needed in your app, so it's worth reviewing those cases, as you may gain some app speed improvement by removing unnecessary cache resets. @@ -692,6 +756,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` ## 2.19.1 - 2018-09-14 From 30051d6486e20f8de95d415132611512c263d826 Mon Sep 17 00:00:00 2001 From: Freek Van der Herten Date: Wed, 25 Oct 2023 08:53:43 +0200 Subject: [PATCH 408/648] Update _index.md --- docs/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_index.md b/docs/_index.md index 591491f89..cf88be1b4 100644 --- a/docs/_index.md +++ b/docs/_index.md @@ -1,5 +1,5 @@ --- -title: v5 +title: v6 slogan: Associate users with roles and permissions githubUrl: https://github.com/spatie/laravel-permission branch: main From 56f91e842dba272ccc0825603588f7655e3add81 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 27 Oct 2023 17:35:08 -0400 Subject: [PATCH 409/648] Update v5-to-v6 migration-file note. --- docs/upgrading.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/upgrading.md b/docs/upgrading.md index 5d3f00f2a..0c7bc2b07 100644 --- a/docs/upgrading.md +++ b/docs/upgrading.md @@ -39,7 +39,7 @@ eg: if you have a custom model you will need to make changes, including accessin 2. Model and Contract/Interface updates. The Role and Permission Models and Contracts/Interfaces have been updated with syntax changes to method signatures. Update any models you have extended, or contracts implemented, accordingly. See PR #2380 and #2480 for some of the specifics. -3. Migrations will need to be upgraded. (They have been updated to anonymous-class syntax that was introduced in Laravel 8, AND some structural coding changes in the registrar class changed the way we extracted configuration settings in the migration files.) If you had not customized it from the original then replacing the contents of the file should be straightforward. Usually the only customization is if you've switched to UUIDs or customized MySQL index name lengths. +3. Migrations WILL need to be upgraded. (They have been updated to anonymous-class syntax that was introduced in Laravel 8, AND some structural coding changes in the registrar class changed the way we extracted configuration settings in the migration files.) There are no changes to the package's structure since v5, so if you had not customized it from the original then replacing the contents of the file should be enough. (Usually the only customization is if you've switched to UUIDs or customized MySQL index name lengths.) **If you get the following error, it means your migration file needs upgrading: `Error: Access to undeclared static property Spatie\Permission\PermissionRegistrar::$pivotPermission`** 4. MIDDLEWARE: From a5142d98ad60d676f47c0fc916f83906a37cf0d9 Mon Sep 17 00:00:00 2001 From: Hamid Dehnavi Date: Tue, 31 Oct 2023 02:24:52 +0330 Subject: [PATCH 410/648] Update teams-permissions doc (#2534) Update unsetRelation() example --- docs/basic-usage/teams-permissions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/basic-usage/teams-permissions.md b/docs/basic-usage/teams-permissions.md index d6381daa6..f4a0d729e 100644 --- a/docs/basic-usage/teams-permissions.md +++ b/docs/basic-usage/teams-permissions.md @@ -96,7 +96,7 @@ setPermissionsTeamId($new_team_id); // $user = Auth::user(); // unset cached model relations so new team relations will get reloaded -$user->unsetRelation('roles','permissions'); +$user->unsetRelation('roles')->unsetRelation('permissions'); // Now you can check: $roles = $user->roles; From 0e34712c309d248e8cee533cd5fcc22e37ed886d Mon Sep 17 00:00:00 2001 From: Sevan Nerse Date: Mon, 6 Nov 2023 22:21:43 +0300 Subject: [PATCH 411/648] Update direct-permissions.md (#2539) --- docs/basic-usage/direct-permissions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/basic-usage/direct-permissions.md b/docs/basic-usage/direct-permissions.md index c508ff9d5..7172f8448 100644 --- a/docs/basic-usage/direct-permissions.md +++ b/docs/basic-usage/direct-permissions.md @@ -7,7 +7,7 @@ weight: 2 It's better to assign permissions to Roles, and then assign Roles to Users. -See https://spatie.be/docs/laravel-permission/best-practices/roles-vs-permissions for a deeper explanation. +See the [Roles vs Permissions](./../best-practices/roles-vs-permissions.md) section of the docs for a deeper explanation. HOWEVER, If you have reason to directly assign individual permissions to specific users (instead of to roles assigned to those users), you can do that as described below: From 9e83ebe5084689a31263581e1fa821270957b8e6 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Mon, 6 Nov 2023 14:27:13 -0500 Subject: [PATCH 412/648] Set default team_foreign_key in case config file is old Fixes #2535 --- src/PermissionRegistrar.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index 8a6e28cad..0443eb026 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -66,7 +66,7 @@ public function initializeCache(): void $this->cacheExpirationTime = config('permission.cache.expiration_time') ?: \DateInterval::createFromDateString('24 hours'); $this->teams = config('permission.teams', false); - $this->teamsKey = config('permission.column_names.team_foreign_key'); + $this->teamsKey = config('permission.column_names.team_foreign_key', 'team_id'); $this->cacheKey = config('permission.cache.key'); From 988aa32b030d4ad5796062e55a1bb899d41515eb Mon Sep 17 00:00:00 2001 From: drbyte Date: Mon, 6 Nov 2023 19:31:22 +0000 Subject: [PATCH 413/648] Update CHANGELOG --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc5ade7aa..32b534be2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,20 @@ All notable changes to `laravel-permission` will be documented in this file +## 6.0.1 - 2023-11-06 + +### What's Changed + +- Provide a default team_foreign_key value in case config file isn't upgraded yet or teams feature is unused. Fixes #2535 +- [Docs] Update unsetRelation() example in teams-permissions.md by @shdehnavi in https://github.com/spatie/laravel-permission/pull/2534 +- [Docs] Update link in direct-permissions.md by @sevannerse in https://github.com/spatie/laravel-permission/pull/2539 + +### New Contributors + +- @sevannerse made their first contribution in https://github.com/spatie/laravel-permission/pull/2539 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/6.0.0...6.0.1 + ## 6.0.0 - 2023-10-25 ### What's Changed @@ -698,6 +712,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` 1. Also this is a good time to point out that now with v2.25.0 and v2.26.0 most permission-cache-reset scenarios may no longer be needed in your app, so it's worth reviewing those cases, as you may gain some app speed improvement by removing unnecessary cache resets. @@ -757,6 +772,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` ## 2.19.1 - 2018-09-14 From 9665eb96025fc1b35a0bd3bfb9d4e82acc871f36 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Mon, 6 Nov 2023 14:42:36 -0500 Subject: [PATCH 414/648] [Docs] Update direct-permissions.md --- docs/basic-usage/direct-permissions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/basic-usage/direct-permissions.md b/docs/basic-usage/direct-permissions.md index 7172f8448..5ebf52c92 100644 --- a/docs/basic-usage/direct-permissions.md +++ b/docs/basic-usage/direct-permissions.md @@ -7,7 +7,7 @@ weight: 2 It's better to assign permissions to Roles, and then assign Roles to Users. -See the [Roles vs Permissions](./../best-practices/roles-vs-permissions.md) section of the docs for a deeper explanation. +See the [Roles vs Permissions](../best-practices/roles-vs-permissions.md) section of the docs for a deeper explanation. HOWEVER, If you have reason to directly assign individual permissions to specific users (instead of to roles assigned to those users), you can do that as described below: From 24dc4f6c7eee17097cb971a3f519304faf77da84 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Mon, 6 Nov 2023 14:45:13 -0500 Subject: [PATCH 415/648] Update direct-permissions.md --- docs/basic-usage/direct-permissions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/basic-usage/direct-permissions.md b/docs/basic-usage/direct-permissions.md index 5ebf52c92..d8533b9b9 100644 --- a/docs/basic-usage/direct-permissions.md +++ b/docs/basic-usage/direct-permissions.md @@ -7,7 +7,7 @@ weight: 2 It's better to assign permissions to Roles, and then assign Roles to Users. -See the [Roles vs Permissions](../best-practices/roles-vs-permissions.md) section of the docs for a deeper explanation. +See the [Roles vs Permissions](../best-practices/roles-vs-permissions) section of the docs for a deeper explanation. HOWEVER, If you have reason to directly assign individual permissions to specific users (instead of to roles assigned to those users), you can do that as described below: From bd077c853670c290b69870ee089220e2657480ed Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 7 Nov 2023 01:53:08 -0500 Subject: [PATCH 416/648] [Docs] add note about ensuring IDs are passed as integers --- docs/upgrading.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/upgrading.md b/docs/upgrading.md index 0c7bc2b07..6c2e4017a 100644 --- a/docs/upgrading.md +++ b/docs/upgrading.md @@ -32,17 +32,19 @@ There are a few breaking-changes when upgrading to v6, but most of them won't af For guidance with upgrading your extended models, your migrations, your routes, etc, see the **Upgrade Essentials** section at the top of this file. -1. If you have overridden the `getPermissionClass()` or `getRoleClass()` methods or have custom Models, you will need to revisit those customizations. See PR #2368 for details. +1. Due to the improved ULID/UUID/GUID support, any package methods which accept a Permission or Role `id` must pass that `id` as an `integer`. If you pass it as a numeric string, the functions will attempt to lookup the role/permission as a string. In such cases you may see errors such as `There is no permission named '123' for guard 'web'.` (where `'123'` is being treated as a string because it was passed as a string instead of as an integer). This also applies to arrays of id's: if it's an array of strings we will do a lookup on the name instead of on the id. **This will mostly only affect UI pages** because an HTML Request is received as string data. **The solution is simple:** if you're passing integers to a form field, then convert them back to integers when using that field's data for calling functions to grant/assign/sync/remove/revoke permissions and roles. + +2. If you have overridden the `getPermissionClass()` or `getRoleClass()` methods or have custom Models, you will need to revisit those customizations. See PR #2368 for details. eg: if you have a custom model you will need to make changes, including accessing the model using `$this->permissionClass::` syntax (eg: using `::` instead of `->`) in all the overridden methods that make use of the models. Be sure to compare your custom models with originals to see what else may have changed. -2. Model and Contract/Interface updates. The Role and Permission Models and Contracts/Interfaces have been updated with syntax changes to method signatures. Update any models you have extended, or contracts implemented, accordingly. See PR #2380 and #2480 for some of the specifics. +3. Model and Contract/Interface updates. The Role and Permission Models and Contracts/Interfaces have been updated with syntax changes to method signatures. Update any models you have extended, or contracts implemented, accordingly. See PR #2380 and #2480 for some of the specifics. -3. Migrations WILL need to be upgraded. (They have been updated to anonymous-class syntax that was introduced in Laravel 8, AND some structural coding changes in the registrar class changed the way we extracted configuration settings in the migration files.) There are no changes to the package's structure since v5, so if you had not customized it from the original then replacing the contents of the file should be enough. (Usually the only customization is if you've switched to UUIDs or customized MySQL index name lengths.) +4. Migrations WILL need to be upgraded. (They have been updated to anonymous-class syntax that was introduced in Laravel 8, AND some structural coding changes in the registrar class changed the way we extracted configuration settings in the migration files.) There are no changes to the package's structure since v5, so if you had not customized it from the original then replacing the contents of the file should be enough. (Usually the only customization is if you've switched to UUIDs or customized MySQL index name lengths.) **If you get the following error, it means your migration file needs upgrading: `Error: Access to undeclared static property Spatie\Permission\PermissionRegistrar::$pivotPermission`** -4. MIDDLEWARE: +5. MIDDLEWARE: 1. The `\Spatie\Permission\Middlewares\` namespace has been renamed to `\Spatie\Permission\Middleware\` (singular). Update any references to them in your `/app/Http/Kernel.php` and any routes (or imported classes in your routes files) that have the fully qualified namespace. @@ -50,7 +52,7 @@ eg: if you have a custom model you will need to make changes, including accessin 3. In the unlikely event that you have customized the Wildcard Permissions feature by extending the `WildcardPermission` model, please note that the public interface has changed significantly and you will need to update your extended model with the new method signatures. -5. Test suites. If you have tests which manually clear the permission cache and re-register permissions, you no longer need to call `\Spatie\Permission\PermissionRegistrar::class)->registerPermissions();`. In fact, **calls to `->registerPermissions()` MUST be deleted from your tests**. +6. Test suites. If you have tests which manually clear the permission cache and re-register permissions, you no longer need to call `\Spatie\Permission\PermissionRegistrar::class)->registerPermissions();`. In fact, **calls to `->registerPermissions()` MUST be deleted from your tests**. (Calling `app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();` after creating roles and permissions in migrations and factories and seeders is still okay and encouraged.) From 62f22e192711fe56fb55b57b586001f8674b4396 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 7 Nov 2023 13:53:51 -0500 Subject: [PATCH 417/648] add example --- docs/upgrading.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/upgrading.md b/docs/upgrading.md index 6c2e4017a..da3d4423d 100644 --- a/docs/upgrading.md +++ b/docs/upgrading.md @@ -32,7 +32,7 @@ There are a few breaking-changes when upgrading to v6, but most of them won't af For guidance with upgrading your extended models, your migrations, your routes, etc, see the **Upgrade Essentials** section at the top of this file. -1. Due to the improved ULID/UUID/GUID support, any package methods which accept a Permission or Role `id` must pass that `id` as an `integer`. If you pass it as a numeric string, the functions will attempt to lookup the role/permission as a string. In such cases you may see errors such as `There is no permission named '123' for guard 'web'.` (where `'123'` is being treated as a string because it was passed as a string instead of as an integer). This also applies to arrays of id's: if it's an array of strings we will do a lookup on the name instead of on the id. **This will mostly only affect UI pages** because an HTML Request is received as string data. **The solution is simple:** if you're passing integers to a form field, then convert them back to integers when using that field's data for calling functions to grant/assign/sync/remove/revoke permissions and roles. +1. Due to the improved ULID/UUID/GUID support, any package methods which accept a Permission or Role `id` must pass that `id` as an `integer`. If you pass it as a numeric string, the functions will attempt to lookup the role/permission as a string. In such cases you may see errors such as `There is no permission named '123' for guard 'web'.` (where `'123'` is being treated as a string because it was passed as a string instead of as an integer). This also applies to arrays of id's: if it's an array of strings we will do a lookup on the name instead of on the id. **This will mostly only affect UI pages** because an HTML Request is received as string data. **The solution is simple:** if you're passing integers to a form field, then convert them back to integers when using that field's data for calling functions to grant/assign/sync/remove/revoke permissions and roles. One way to convert an array of permissions `id`'s from strings to integers is: `collect($validated['permission'])->map(fn($val)=>(int)$val)` 2. If you have overridden the `getPermissionClass()` or `getRoleClass()` methods or have custom Models, you will need to revisit those customizations. See PR #2368 for details. eg: if you have a custom model you will need to make changes, including accessing the model using `$this->permissionClass::` syntax (eg: using `::` instead of `->`) in all the overridden methods that make use of the models. From 8e584d3ac09856e106f989741a2d82b094278e58 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Thu, 9 Nov 2023 17:03:17 -0500 Subject: [PATCH 418/648] Reset teamId on octane (#2547) --- config/permission.php | 5 ++--- src/Listeners/OctaneReloadPermissions.php | 13 ------------- src/PermissionRegistrar.php | 2 +- src/PermissionServiceProvider.php | 19 +++++++++++++------ 4 files changed, 16 insertions(+), 23 deletions(-) delete mode 100644 src/Listeners/OctaneReloadPermissions.php diff --git a/config/permission.php b/config/permission.php index 9c0532d89..2a520f351 100644 --- a/config/permission.php +++ b/config/permission.php @@ -104,9 +104,8 @@ 'register_permission_check_method' => true, /* - * When set to true, the Spatie\Permission\Listeners\OctaneReloadPermissions listener will be registered - * on the Laravel\Octane\Events\OperationTerminated event, this will refresh permissions on every - * TickTerminated, TaskTerminated and RequestTerminated + * When set to true, Laravel\Octane\Events\OperationTerminated event listener will be registered + * this will refresh permissions on every TickTerminated, TaskTerminated and RequestTerminated * NOTE: This should not be needed in most cases, but an Octane/Vapor combination benefited from it. */ 'register_octane_reset_listener' => false, diff --git a/src/Listeners/OctaneReloadPermissions.php b/src/Listeners/OctaneReloadPermissions.php deleted file mode 100644 index 6f4544e5e..000000000 --- a/src/Listeners/OctaneReloadPermissions.php +++ /dev/null @@ -1,13 +0,0 @@ -sandbox->make(PermissionRegistrar::class)->clearPermissionsCollection(); - } -} diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index 0443eb026..051e48b0f 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -98,7 +98,7 @@ protected function getCacheStoreFromConfig(): Repository /** * Set the team id for teams/groups support, this id is used when querying permissions/roles * - * @param int|string|\Illuminate\Database\Eloquent\Model $id + * @param int|string|\Illuminate\Database\Eloquent\Model|null $id */ public function setPermissionsTeamId($id): void { diff --git a/src/PermissionServiceProvider.php b/src/PermissionServiceProvider.php index b68f31984..a9011cd2b 100644 --- a/src/PermissionServiceProvider.php +++ b/src/PermissionServiceProvider.php @@ -13,7 +13,6 @@ use Illuminate\View\Compilers\BladeCompiler; use Spatie\Permission\Contracts\Permission as PermissionContract; use Spatie\Permission\Contracts\Role as RoleContract; -use Spatie\Permission\Listeners\OctaneReloadPermissions; class PermissionServiceProvider extends ServiceProvider { @@ -91,17 +90,25 @@ protected function registerCommands(): void protected function registerOctaneListener(): void { - if ($this->app->runningInConsole() || ! $this->app['config']->get('permission.register_octane_reset_listener')) { + if ($this->app->runningInConsole() || ! $this->app['config']->get('octane.listeners')) { return; } - if (! $this->app['config']->get('octane.listeners')) { + $dispatcher = $this->app[Dispatcher::class]; + // @phpstan-ignore-next-line + $dispatcher->listen(function (\Laravel\Octane\Events\OperationTerminated $event) { + // @phpstan-ignore-next-line + $event->sandbox->make(PermissionRegistrar::class)->setPermissionsTeamId(null); + }); + + if (! $this->app['config']->get('permission.register_octane_reset_listener')) { return; } - - $dispatcher = $this->app[Dispatcher::class]; // @phpstan-ignore-next-line - $dispatcher->listen(\Laravel\Octane\Events\OperationTerminated::class, OctaneReloadPermissions::class); + $dispatcher->listen(function (\Laravel\Octane\Events\OperationTerminated $event) { + // @phpstan-ignore-next-line + $event->sandbox->make(PermissionRegistrar::class)->clearPermissionsCollection(); + }); } protected function registerModelBindings(): void From 6376f7be954f67b1a7a81ee9eaaecfbd1de0ab5a Mon Sep 17 00:00:00 2001 From: drbyte Date: Thu, 9 Nov 2023 22:06:43 +0000 Subject: [PATCH 419/648] Update CHANGELOG --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32b534be2..bf8211cb6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ All notable changes to `laravel-permission` will be documented in this file +## 6.1.0 - 2023-11-09 + +### What's Changed + +- Reset teamId on octane by @erikn69 in https://github.com/spatie/laravel-permission/pull/2547 + NOTE: The `\Spatie\Permission\Listeners\OctaneReloadPermissions` listener introduced in 6.0.0 is removed in 6.1.0, because the logic is directly incorporated into the ServiceProvider now. + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/6.0.1...6.1.0 + ## 6.0.1 - 2023-11-06 ### What's Changed @@ -713,6 +722,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` 1. Also this is a good time to point out that now with v2.25.0 and v2.26.0 most permission-cache-reset scenarios may no longer be needed in your app, so it's worth reviewing those cases, as you may gain some app speed improvement by removing unnecessary cache resets. @@ -773,6 +783,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` ## 2.19.1 - 2018-09-14 From 641bc2f10d7d5db62f8463a5fc011eb6ce24d3df Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Thu, 9 Nov 2023 17:09:17 -0500 Subject: [PATCH 420/648] Update CHANGELOG.md --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf8211cb6..472c27841 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,9 +6,11 @@ All notable changes to `laravel-permission` will be documented in this file ### What's Changed -- Reset teamId on octane by @erikn69 in https://github.com/spatie/laravel-permission/pull/2547 +- Reset teamId on Octane by @erikn69 in https://github.com/spatie/laravel-permission/pull/2547 NOTE: The `\Spatie\Permission\Listeners\OctaneReloadPermissions` listener introduced in 6.0.0 is removed in 6.1.0, because the logic is directly incorporated into the ServiceProvider now. + Thanks @jameshulse for the heads-up and code-review. + **Full Changelog**: https://github.com/spatie/laravel-permission/compare/6.0.1...6.1.0 ## 6.0.1 - 2023-11-06 From c66c0de99a95a88288507fa96676b89625f54488 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Mon, 13 Nov 2023 16:34:57 -0500 Subject: [PATCH 421/648] Update dependabot-auto-merge.yml (#2555) --- .github/workflows/dependabot-auto-merge.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml index 60183c521..144a946ce 100644 --- a/.github/workflows/dependabot-auto-merge.yml +++ b/.github/workflows/dependabot-auto-merge.yml @@ -13,7 +13,7 @@ jobs: - name: Dependabot metadata id: metadata - uses: dependabot/fetch-metadata@v1.6.0 + uses: dependabot/fetch-metadata@v1 with: github-token: "${{ secrets.GITHUB_TOKEN }}" compat-lookup: true From 3bf179b7eb2b7912032c1e7ced73e92edcd8206d Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Sun, 19 Nov 2023 02:58:03 -0500 Subject: [PATCH 422/648] Add note about needing to implement canAny in User model --- docs/installation-lumen.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation-lumen.md b/docs/installation-lumen.md index 7709777e6..84189b345 100644 --- a/docs/installation-lumen.md +++ b/docs/installation-lumen.md @@ -66,7 +66,7 @@ php artisan migrate --- ## User Model -NOTE: Remember that Laravel's authorization layer requires that your `User` model implement the `Illuminate\Contracts\Auth\Access\Authorizable` contract. In Lumen you will then also need to use the `Laravel\Lumen\Auth\Authorizable` trait. +NOTE: Remember that Laravel's authorization layer requires that your `User` model implement the `Illuminate\Contracts\Auth\Access\Authorizable` contract. In Lumen you will then also need to use the `Laravel\Lumen\Auth\Authorizable` trait. And if you want to use middleware from this package, you will need to implement the `canAny()` method from [Illuminate\Foundation\Auth\Access\Authorizable](https://github.com/laravel/framework/blob/10.x/src/Illuminate/Foundation/Auth/Access/Authorizable.php) in your `User` model because Lumen doesn't include it in its `Authorizable` trait. --- ## User Table From 4011bf8068307bfc065fa33c7bd0e3dd179073a1 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Sun, 19 Nov 2023 12:14:41 -0500 Subject: [PATCH 423/648] Middleware limitations --- docs/installation-lumen.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/installation-lumen.md b/docs/installation-lumen.md index 84189b345..1364e727e 100644 --- a/docs/installation-lumen.md +++ b/docs/installation-lumen.md @@ -31,17 +31,19 @@ You will also need the `config/auth.php` file. If you don't already have it, cop cp vendor/laravel/lumen-framework/config/auth.php config/auth.php ``` -Then, in `bootstrap/app.php`, uncomment the `auth` middleware, and register this package's middleware: +Next, if you wish to use this package's middleware, clone whichever ones you want from `Spatie\Permission\Middleware` namespace into your own `App\Http\Middleware` namespace AND replace the `canAny()` call with `hasAnyPermission()` (because Lumen doesn't support `canAny()`). + +Then, in `bootstrap/app.php`, uncomment the `auth` middleware, and register the middleware you've created. For example: ```php $app->routeMiddleware([ 'auth' => App\Http\Middleware\Authenticate::class, - 'permission' => Spatie\Permission\Middleware\PermissionMiddleware::class, - 'role' => Spatie\Permission\Middleware\RoleMiddleware::class, + 'permission' => App\Http\Middleware\PermissionMiddleware::class, // cloned from Spatie\Permission\Middleware + 'role' => App\Http\Middleware\RoleMiddleware::class, // cloned from Spatie\Permission\Middleware ]); ``` -... and in the same file, in the ServiceProviders section, register the package configuration, service provider, and cache alias: +... and also in `bootstrap/app.php`, in the ServiceProviders section, register the package configuration, service provider, and cache alias: ```php $app->configure('permission'); @@ -66,7 +68,7 @@ php artisan migrate --- ## User Model -NOTE: Remember that Laravel's authorization layer requires that your `User` model implement the `Illuminate\Contracts\Auth\Access\Authorizable` contract. In Lumen you will then also need to use the `Laravel\Lumen\Auth\Authorizable` trait. And if you want to use middleware from this package, you will need to implement the `canAny()` method from [Illuminate\Foundation\Auth\Access\Authorizable](https://github.com/laravel/framework/blob/10.x/src/Illuminate/Foundation/Auth/Access/Authorizable.php) in your `User` model because Lumen doesn't include it in its `Authorizable` trait. +NOTE: Remember that Laravel's authorization layer requires that your `User` model implement the `Illuminate\Contracts\Auth\Access\Authorizable` contract. In Lumen you will then also need to use the `Laravel\Lumen\Auth\Authorizable` trait. Note that Lumen does not support the `User::canAny()` authorization method. --- ## User Table From eb61a65cc4e9e3359ddcd78f740ffe616979af20 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Sun, 19 Nov 2023 12:46:28 -0500 Subject: [PATCH 424/648] Add create statements to examples --- docs/basic-usage/wildcard-permissions.md | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/docs/basic-usage/wildcard-permissions.md b/docs/basic-usage/wildcard-permissions.md index f1afc046d..c35ce0f5c 100644 --- a/docs/basic-usage/wildcard-permissions.md +++ b/docs/basic-usage/wildcard-permissions.md @@ -5,9 +5,9 @@ weight: 6 When enabled, wildcard permissions offers you a flexible representation for a variety of permission schemes. The idea behind wildcard permissions is inspired by the default permission implementation of - [Apache Shiro](https://shiro.apache.org/permissions.html). + [Apache Shiro](https://shiro.apache.org/permissions.html). See the Shiro documentation for more examples. -## Enabling Wildcard Feature +## Enabling Wildcard Features Wildcard permissions can be enabled in the permission config file: @@ -27,11 +27,11 @@ $permission = 'posts.create.1'; The meaning of each part of the string depends on the application layer. > You can use as many parts as you like. So you are not limited to the three-tiered structure, even though -this is the common use-case, representing {resource}.{action}.{target}. +this is the common use-case, representing `{resource}.{action}.{target}`. -> NOTE: You must actually create the permissions (eg: `posts.create.1`) before you can assign them or check for them. +> **NOTE: You must actually create the wildcarded permissions** (eg: `posts.create.1`) before you can assign them or check for them. -> NOTE: You must create any wildcard permission patterns (eg: `posts.create.*`) before you can assign them or check for them. +> **NOTE: You must create any wildcard permission patterns** (eg: `posts.create.*`) before you can assign them or check for them. ## Using Wildcards @@ -47,7 +47,7 @@ Permission::create(['name'=>'posts']); $user->givePermissionTo('posts'); ``` -Everyone who is assigned to this permission will be allowed every action on posts. It is not necessary to use a +Given the example above, everyone who is assigned to this permission will be allowed every action on posts. It is not necessary to use a wildcard on the last part of the string. This is automatically assumed. ```php @@ -55,13 +55,14 @@ wildcard on the last part of the string. This is automatically assumed. $user->can('posts.create'); $user->can('posts.edit'); $user->can('posts.delete'); -``` +``` +(Note that the `posts.create` and `posts.edit` and `posts.delete` permissions must also be created.) -## Meaning of the `*` Asterisk +## Meaning of the * Asterisk The `*` means "ALL". It does **not** mean "ANY". -Thus `can('post.*')` will only pass if the user has been assigned `post.*` explicitly. +Thus `can('post.*')` will only pass if the user has been assigned `post.*` explicitly, and the `post.*` Permission has been created. ## Subparts @@ -71,12 +72,15 @@ powerful feature that lets you create complex permission schemes. ```php // user can only do the actions create, update and view on both resources posts and users +Permission::create(['name'=>'posts,users.create,update,view']); $user->givePermissionTo('posts,users.create,update,view'); // user can do the actions create, update, view on any available resource +Permission::create(['name'=>'*.create,update,view']); $user->givePermissionTo('*.create,update,view'); // user can do any action on posts with ids 1, 4 and 6 +Permission::create(['name'=>'posts.*.1,4,6']); $user->givePermissionTo('posts.*.1,4,6'); ``` From 5e448d08a0c69cb29ed7dbd6eba92b77d2f5f506 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Sun, 19 Nov 2023 12:50:31 -0500 Subject: [PATCH 425/648] Reference Shiro docs for more examples --- docs/basic-usage/wildcard-permissions.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/basic-usage/wildcard-permissions.md b/docs/basic-usage/wildcard-permissions.md index c35ce0f5c..b1412f5cc 100644 --- a/docs/basic-usage/wildcard-permissions.md +++ b/docs/basic-usage/wildcard-permissions.md @@ -3,9 +3,10 @@ title: Wildcard permissions weight: 6 --- -When enabled, wildcard permissions offers you a flexible representation for a variety of permission schemes. The idea - behind wildcard permissions is inspired by the default permission implementation of - [Apache Shiro](https://shiro.apache.org/permissions.html). See the Shiro documentation for more examples. +When enabled, wildcard permissions offers you a flexible representation for a variety of permission schemes. + +The wildcard permissions implementation is inspired by the default permission implementation of + [Apache Shiro](https://shiro.apache.org/permissions.html). See the Shiro documentation for more examples and deeper explanation of the concepts. ## Enabling Wildcard Features @@ -84,4 +85,4 @@ Permission::create(['name'=>'posts.*.1,4,6']); $user->givePermissionTo('posts.*.1,4,6'); ``` -> As said before, the meaning of each part is determined by the application layer! So, you are free to use each part as you like. And you can use as many parts and subparts as you want. +> Remember: the meaning of each 'part' is determined by your application! So, you are free to use each part as you like. And you can use as many parts and subparts as you want. From 978cf240cca1556d2a12bf72b9167b8c9c9fdf08 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Sun, 19 Nov 2023 15:01:20 -0500 Subject: [PATCH 426/648] Do you really need a UI? --- docs/advanced-usage/ui-options.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/advanced-usage/ui-options.md b/docs/advanced-usage/ui-options.md index d9628e766..17f70b85f 100644 --- a/docs/advanced-usage/ui-options.md +++ b/docs/advanced-usage/ui-options.md @@ -5,11 +5,15 @@ weight: 11 ## Need a UI? -The package doesn't come with any screens out of the box, you should build that yourself. Here are some options to get you started: +The package doesn't come with any UI/screens out of the box, you should build that yourself. + +But: [do you really need a UI? Consider what Aaron and Joel have to say in this podcast episode](https://show.nocompromises.io/episodes/should-you-manage-roles-and-permissions-with-a-ui) + +If you decide you need a UI, even if it's not for creating/editing role/permission names, but just for controlling which Users have access to which roles/permissions, following are some options to get you started: - [Code With Tony - video series](https://www.youtube.com/watch?v=lGfV1ddMhHA) to create an admin panel for managing roles and permissions in Laravel 9. -- [FilamentPHP plugin](https://filamentphp.com/plugins/tharinda-rodrigo-spatie-roles-permissions) to manage roles and permissions using this package. +- [FilamentPHP plugin](https://filamentphp.com/plugins/tharinda-rodrigo-spatie-roles-permissions) to manage roles and permissions using this package. (There are a few other Filament plugins which do similarly; use whichever suits your needs best.) - If you'd like to build your own UI, and understand the underlying logic for Gates and Roles and Users, the [Laravel 6 User Login and Management With Roles](https://www.youtube.com/watch?v=7PpJsho5aak&list=PLxFwlLOncxFLazmEPiB4N0iYc3Dwst6m4) video series by Mark Twigg of Penguin Digital gives thorough coverage to the topic, the theory, and implementation of a basic Roles system, independent of this Permissions Package. From 8ab6bb1195750df55ef6f79dfb3ad46c73bb60c7 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Sat, 2 Dec 2023 19:05:44 -0500 Subject: [PATCH 427/648] Test suite updates --- phpunit.xml.dist | 5 +++-- tests/RoleWithNestingTest.php | 8 ++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 1e08d1014..bd9254886 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -12,7 +12,8 @@ - - + + + diff --git a/tests/RoleWithNestingTest.php b/tests/RoleWithNestingTest.php index 909f671de..c08476c20 100644 --- a/tests/RoleWithNestingTest.php +++ b/tests/RoleWithNestingTest.php @@ -10,10 +10,10 @@ class RoleWithNestingTest extends TestCase protected $useCustomModels = true; /** @var Role[] */ - protected $parent_roles = []; + protected array $parent_roles = []; /** @var Role[] */ - protected $child_roles = []; + protected array $child_roles = []; protected function setUp(): void { @@ -67,7 +67,7 @@ public function it_returns_correct_withCount_of_nested_roles($role_group, $index $role = $this->$role_group[$index]; $count_field_name = sprintf('%s_count', $relation); - $actualCount = intval(Role::withCount($relation)->find($role->getKey())->$count_field_name); + $actualCount = (int)Role::withCount($relation)->find($role->getKey())->$count_field_name; $this->assertSame( $expectedCount, @@ -76,7 +76,7 @@ public function it_returns_correct_withCount_of_nested_roles($role_group, $index ); } - public function roles_list() + public static function roles_list() { return [ ['parent_roles', 'has_no_children', 'children', 0], From 373dcb4b975c89003ec253c31047b0a65458acd4 Mon Sep 17 00:00:00 2001 From: drbyte Date: Sun, 3 Dec 2023 00:06:31 +0000 Subject: [PATCH 428/648] Fix styling --- tests/RoleWithNestingTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/RoleWithNestingTest.php b/tests/RoleWithNestingTest.php index c08476c20..fb28b8c18 100644 --- a/tests/RoleWithNestingTest.php +++ b/tests/RoleWithNestingTest.php @@ -67,7 +67,7 @@ public function it_returns_correct_withCount_of_nested_roles($role_group, $index $role = $this->$role_group[$index]; $count_field_name = sprintf('%s_count', $relation); - $actualCount = (int)Role::withCount($relation)->find($role->getKey())->$count_field_name; + $actualCount = (int) Role::withCount($relation)->find($role->getKey())->$count_field_name; $this->assertSame( $expectedCount, From 66638c1cf9eb137de5787c2bf44242db718f0ad3 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Sat, 2 Dec 2023 19:23:46 -0500 Subject: [PATCH 429/648] Clean up comment --- src/PermissionRegistrar.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index 051e48b0f..3c3b0d7f0 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -187,7 +187,7 @@ public function clearClassPermissions() /** * Load permissions from cache - * This get cache and turns array into \Illuminate\Database\Eloquent\Collection + * And turns permissions array into a \Illuminate\Database\Eloquent\Collection */ private function loadPermissions(): void { From b6b9f4f1120cb5cfbebfc2dc9bf59ef1191cfe38 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Sat, 2 Dec 2023 19:25:55 -0500 Subject: [PATCH 430/648] Update to accept Laravel 11 and PHPUnit 10 --- .gitignore | 1 + composer.json | 12 ++++++------ phpunit.xml.dist | 10 +++++++--- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index da5ec4b46..da248ba1f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ composer.lock vendor tests/temp .idea +.phpunit.cache .phpunit.result.cache .php-cs-fixer.cache tests/CreatePermissionCustomTables.php diff --git a/composer.json b/composer.json index e7af6faa2..ab0515b8e 100644 --- a/composer.json +++ b/composer.json @@ -23,15 +23,15 @@ "homepage": "https://github.com/spatie/laravel-permission", "require": { "php": "^8.0", - "illuminate/auth": "^8.12|^9.0|^10.0", - "illuminate/container": "^8.12|^9.0|^10.0", - "illuminate/contracts": "^8.12|^9.0|^10.0", - "illuminate/database": "^8.12|^9.0|^10.0" + "illuminate/auth": "^8.12|^9.0|^10.0|^11.0", + "illuminate/container": "^8.12|^9.0|^10.0|^11.0", + "illuminate/contracts": "^8.12|^9.0|^10.0|^11.0", + "illuminate/database": "^8.12|^9.0|^10.0|^11.0" }, "require-dev": { "laravel/passport": "^11.0", - "orchestra/testbench": "^6.23|^7.0|^8.0", - "phpunit/phpunit": "^9.4" + "orchestra/testbench": "^6.23|^7.0|^8.0|^9.0", + "phpunit/phpunit": "^9.4|^10.1" }, "minimum-stability": "dev", "prefer-stable": true, diff --git a/phpunit.xml.dist b/phpunit.xml.dist index bd9254886..96497e656 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,10 +1,14 @@ - - + + src/ - + tests From c24211a47bda2bdcb27e04114808ecad5fc25263 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Fri, 8 Dec 2023 20:39:42 -0500 Subject: [PATCH 431/648] fix duplicates on sync (#2574) --- src/Traits/HasPermissions.php | 7 ++++--- src/Traits/HasRoles.php | 7 ++++--- tests/HasPermissionsTest.php | 10 ++++++++++ tests/HasRolesTest.php | 10 ++++++++++ 4 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index 2618b380b..1582e36d6 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -370,9 +370,10 @@ private function collectPermissions(...$permissions): array return $array; } - $this->ensureModelSharesGuard($permission); - - $array[] = $permission->getKey(); + if (! in_array($permission->getKey(), $array)) { + $this->ensureModelSharesGuard($permission); + $array[] = $permission->getKey(); + } return $array; }, []); diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index f02f7c160..a8f183c04 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -129,9 +129,10 @@ private function collectRoles(...$roles): array return $array; } - $this->ensureModelSharesGuard($role); - - $array[] = $role->getKey(); + if (! in_array($role->getKey(), $array)) { + $this->ensureModelSharesGuard($role); + $array[] = $role->getKey(); + } return $array; }, []); diff --git a/tests/HasPermissionsTest.php b/tests/HasPermissionsTest.php index 0eeb961a9..bc7b84f93 100644 --- a/tests/HasPermissionsTest.php +++ b/tests/HasPermissionsTest.php @@ -541,6 +541,16 @@ public function it_can_sync_multiple_permissions() $this->assertFalse($this->testUser->hasDirectPermission('edit-news')); } + /** @test */ + public function it_can_avoid_sync_duplicated_permissions() + { + $this->testUser->syncPermissions('edit-articles', 'edit-blog', 'edit-blog'); + + $this->assertTrue($this->testUser->hasDirectPermission('edit-articles')); + + $this->assertTrue($this->testUser->hasDirectPermission('edit-blog')); + } + /** @test */ public function it_can_sync_multiple_permissions_by_id() { diff --git a/tests/HasRolesTest.php b/tests/HasRolesTest.php index ec7bccc72..7b0e54e3c 100644 --- a/tests/HasRolesTest.php +++ b/tests/HasRolesTest.php @@ -259,6 +259,16 @@ public function it_can_sync_roles_from_a_string_on_a_permission() $this->assertTrue($this->testUserPermission->hasRole('testRole2')); } + /** @test */ + public function it_can_avoid_sync_duplicated_roles() + { + $this->testUser->syncRoles('testRole', 'testRole', 'testRole2'); + + $this->assertTrue($this->testUser->hasRole('testRole')); + + $this->assertTrue($this->testUser->hasRole('testRole2')); + } + /** @test */ public function it_can_sync_multiple_roles() { From 299dd2c9bce700ea641021c1aea0dfece25e541d Mon Sep 17 00:00:00 2001 From: drbyte Date: Sat, 9 Dec 2023 01:40:07 +0000 Subject: [PATCH 432/648] Fix styling --- src/Models/Permission.php | 6 +++--- src/Models/Role.php | 8 ++++---- src/PermissionRegistrar.php | 2 +- src/Traits/HasRoles.php | 6 +++--- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Models/Permission.php b/src/Models/Permission.php index 95df94fb6..d23d15ac2 100644 --- a/src/Models/Permission.php +++ b/src/Models/Permission.php @@ -86,7 +86,7 @@ public function users(): BelongsToMany * * @throws PermissionDoesNotExist */ - public static function findByName(string $name, string $guardName = null): PermissionContract + public static function findByName(string $name, ?string $guardName = null): PermissionContract { $guardName = $guardName ?? Guard::getDefaultName(static::class); $permission = static::getPermission(['name' => $name, 'guard_name' => $guardName]); @@ -104,7 +104,7 @@ public static function findByName(string $name, string $guardName = null): Permi * * @throws PermissionDoesNotExist */ - public static function findById(int|string $id, string $guardName = null): PermissionContract + public static function findById(int|string $id, ?string $guardName = null): PermissionContract { $guardName = $guardName ?? Guard::getDefaultName(static::class); $permission = static::getPermission([(new static())->getKeyName() => $id, 'guard_name' => $guardName]); @@ -121,7 +121,7 @@ public static function findById(int|string $id, string $guardName = null): Permi * * @return PermissionContract|Permission */ - public static function findOrCreate(string $name, string $guardName = null): PermissionContract + public static function findOrCreate(string $name, ?string $guardName = null): PermissionContract { $guardName = $guardName ?? Guard::getDefaultName(static::class); $permission = static::getPermission(['name' => $name, 'guard_name' => $guardName]); diff --git a/src/Models/Role.php b/src/Models/Role.php index 4fd6e782e..4b2e48ff4 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -95,7 +95,7 @@ public function users(): BelongsToMany * * @throws RoleDoesNotExist */ - public static function findByName(string $name, string $guardName = null): RoleContract + public static function findByName(string $name, ?string $guardName = null): RoleContract { $guardName = $guardName ?? Guard::getDefaultName(static::class); @@ -113,7 +113,7 @@ public static function findByName(string $name, string $guardName = null): RoleC * * @return RoleContract|Role */ - public static function findById(int|string $id, string $guardName = null): RoleContract + public static function findById(int|string $id, ?string $guardName = null): RoleContract { $guardName = $guardName ?? Guard::getDefaultName(static::class); @@ -131,7 +131,7 @@ public static function findById(int|string $id, string $guardName = null): RoleC * * @return RoleContract|Role */ - public static function findOrCreate(string $name, string $guardName = null): RoleContract + public static function findOrCreate(string $name, ?string $guardName = null): RoleContract { $guardName = $guardName ?? Guard::getDefaultName(static::class); @@ -176,7 +176,7 @@ protected static function findByParam(array $params = []): ?RoleContract * * @throws PermissionDoesNotExist|GuardDoesNotMatch */ - public function hasPermissionTo($permission, string $guardName = null): bool + public function hasPermissionTo($permission, ?string $guardName = null): bool { if ($this->getWildcardClass()) { return $this->hasWildcardPermission($permission, $guardName); diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index 3c3b0d7f0..1300def8f 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -145,7 +145,7 @@ public function forgetCachedPermissions() return $this->cache->forget($this->cacheKey); } - public function forgetWildcardPermissionIndex(Model $record = null): void + public function forgetWildcardPermissionIndex(?Model $record = null): void { if ($record) { unset($this->wildcardPermissionsIndex[get_class($record)][$record->getKey()]); diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index a8f183c04..97f87ba9a 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -218,7 +218,7 @@ public function syncRoles(...$roles) * * @param string|int|array|Role|Collection|\BackedEnum $roles */ - public function hasRole($roles, string $guard = null): bool + public function hasRole($roles, ?string $guard = null): bool { $this->loadMissing('roles'); @@ -282,7 +282,7 @@ public function hasAnyRole(...$roles): bool * * @param string|array|Role|Collection|\BackedEnum $roles */ - public function hasAllRoles($roles, string $guard = null): bool + public function hasAllRoles($roles, ?string $guard = null): bool { $this->loadMissing('roles'); @@ -324,7 +324,7 @@ public function hasAllRoles($roles, string $guard = null): bool * * @param string|array|Role|Collection $roles */ - public function hasExactRoles($roles, string $guard = null): bool + public function hasExactRoles($roles, ?string $guard = null): bool { $this->loadMissing('roles'); From 31838377ca1896f45a766813ddee1b473bf7bb1e Mon Sep 17 00:00:00 2001 From: drbyte Date: Sat, 9 Dec 2023 01:42:13 +0000 Subject: [PATCH 433/648] Update CHANGELOG --- CHANGELOG.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 472c27841..d358d73a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,14 +2,23 @@ All notable changes to `laravel-permission` will be documented in this file +## 6.2.0 - 2023-12-09 + +### What's Changed + +* Skip duplicates on sync (was triggering Integrity Constraint Violation error) by @erikn69 in https://github.com/spatie/laravel-permission/pull/2574 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/6.1.0...6.2.0 + ## 6.1.0 - 2023-11-09 ### What's Changed -- Reset teamId on Octane by @erikn69 in https://github.com/spatie/laravel-permission/pull/2547 +- Reset teamId on Octane by @erikn69 in https://github.com/spatie/laravel-permission/pull/2547 NOTE: The `\Spatie\Permission\Listeners\OctaneReloadPermissions` listener introduced in 6.0.0 is removed in 6.1.0, because the logic is directly incorporated into the ServiceProvider now. - + Thanks @jameshulse for the heads-up and code-review. + **Full Changelog**: https://github.com/spatie/laravel-permission/compare/6.0.1...6.1.0 @@ -725,6 +734,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` 1. Also this is a good time to point out that now with v2.25.0 and v2.26.0 most permission-cache-reset scenarios may no longer be needed in your app, so it's worth reviewing those cases, as you may gain some app speed improvement by removing unnecessary cache resets. @@ -786,6 +796,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` ## 2.19.1 - 2018-09-14 From f518fd548c51f4d9b1ccd8aa395d003e2022106e Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 19 Dec 2023 14:19:04 -0500 Subject: [PATCH 434/648] Update bug_report.md --- .github/ISSUE_TEMPLATE/bug_report.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 328f945dd..6b62c8f54 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,6 +1,6 @@ --- name: Bug report -about: Report or reproducable bug +about: Report a reproducible bug title: '' labels: '' assignees: '' @@ -8,7 +8,7 @@ assignees: '' --- **Before creating a new bug report** -Please check if there isn't a similar issue on [the issue tracker](https://github.com/spatie/laravel-permission/issues) or in [the discussions](https://github.com/spatie/laravel-permission/discussions). +Please check that there isn't already a similar issue on [the issue tracker](https://github.com/spatie/laravel-permission/issues) or in [the discussions](https://github.com/spatie/laravel-permission/discussions). **Describe the bug** A clear and concise description of what the bug is. @@ -16,7 +16,7 @@ A clear and concise description of what the bug is. **Versions** You can use `composer show` to get the version numbers of: - spatie/laravel-permission package version: -- illuminate/framework package +- laravel/framework package PHP version: @@ -40,4 +40,5 @@ Add any other context about the problem here. **Environment (please complete the following information, because it helps us investigate better):** - OS: [e.g. macOS] - Version [e.g. 22] + From 4d119986c862ac0168b77338c85d8236bb559a88 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Sun, 24 Dec 2023 01:58:02 -0500 Subject: [PATCH 435/648] Octane: Clear wildcard permissions on Tick (#2583) Fixes #2575 --- src/PermissionRegistrar.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index 1300def8f..56c7415a8 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -173,6 +173,7 @@ public function getWildcardPermissionIndex(Model $record): array public function clearPermissionsCollection(): void { $this->permissions = null; + $this->wildcardPermissionsIndex = []; } /** From 8f1bf1029f8ceb81d8d99e209703b51747298841 Mon Sep 17 00:00:00 2001 From: drbyte Date: Sun, 24 Dec 2023 07:00:02 +0000 Subject: [PATCH 436/648] Update CHANGELOG --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d358d73a1..5b6100624 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to `laravel-permission` will be documented in this file +## 6.3.0 - 2023-12-24 + +### What's Changed + +* Octane Fix: Clear wildcard permissions on Tick in https://github.com/spatie/laravel-permission/pull/2583 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/6.2.0...6.3.0 + ## 6.2.0 - 2023-12-09 ### What's Changed @@ -735,6 +743,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` 1. Also this is a good time to point out that now with v2.25.0 and v2.26.0 most permission-cache-reset scenarios may no longer be needed in your app, so it's worth reviewing those cases, as you may gain some app speed improvement by removing unnecessary cache resets. @@ -797,6 +806,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` ## 2.19.1 - 2018-09-14 From 7c1ca135434ecedfd2132edd63929f9f797947e3 Mon Sep 17 00:00:00 2001 From: Arne Breitsprecher Date: Sun, 24 Dec 2023 18:40:27 +0100 Subject: [PATCH 437/648] Update to use Larastan Org (#2585) --- .github/workflows/phpstan.yml | 2 +- phpstan.neon.dist | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index 587e6c2d7..00e3e5efe 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -24,7 +24,7 @@ jobs: - name: Install larastan run: | - composer require "nunomaduro/larastan" --no-interaction --no-update + composer require "larastan/larastan" --no-interaction --no-update composer update --prefer-dist --no-interaction - name: Run PHPStan diff --git a/phpstan.neon.dist b/phpstan.neon.dist index f4b16051f..3a451a165 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,5 +1,5 @@ includes: - - ./vendor/nunomaduro/larastan/extension.neon + - ./vendor/larastan/larastan/extension.neon - phpstan-baseline.neon parameters: From 6e5d59cb9bd96c6637c404a124bdeab48c9fb346 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Tue, 26 Dec 2023 11:49:09 -0500 Subject: [PATCH 438/648] laravel-pint-action to major version tag (#2586) --- .github/workflows/fix-php-code-style-issues.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/fix-php-code-style-issues.yml b/.github/workflows/fix-php-code-style-issues.yml index 53c026f89..60dc90d6f 100644 --- a/.github/workflows/fix-php-code-style-issues.yml +++ b/.github/workflows/fix-php-code-style-issues.yml @@ -16,7 +16,7 @@ jobs: ref: ${{ github.head_ref }} - name: Fix PHP code style issues - uses: aglipanci/laravel-pint-action@2.3.0 + uses: aglipanci/laravel-pint-action@v2 - name: Commit changes uses: stefanzweifel/git-auto-commit-action@v5 From 16b8b478734b72a0a2ebbf407195aadffdcb4298 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 29 Dec 2023 12:19:29 -0500 Subject: [PATCH 439/648] Explain no-typo in Middleware namespace Closes #2588 --- docs/basic-usage/middleware.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/basic-usage/middleware.md b/docs/basic-usage/middleware.md index fee16f8ed..57290c68d 100644 --- a/docs/basic-usage/middleware.md +++ b/docs/basic-usage/middleware.md @@ -42,6 +42,7 @@ protected $middlewareAliases = [ **YOU SHOULD ALSO** set [the `$middlewarePriority` array](https://laravel.com/docs/master/middleware#sorting-middleware) to include this package's middleware before the `SubstituteBindings` middleware, else you may get *404 Not Found* responses when a *403 Not Authorized* response might be expected. +> See a typo? Note that since v6 the 'Middleware' namespace is singular. Prior to v6 it was 'Middlewares'. Time to upgrade your app! ## Middleware via Routes From 9b02e54c2b3aa009128b0df3099fb808fa36b85c Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Sat, 13 Jan 2024 19:06:35 -0500 Subject: [PATCH 440/648] Update Seeder docs with Factory States example --- docs/advanced-usage/seeding.md | 38 ++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/docs/advanced-usage/seeding.md b/docs/advanced-usage/seeding.md index 0f40e7f27..207fbff42 100644 --- a/docs/advanced-usage/seeding.md +++ b/docs/advanced-usage/seeding.md @@ -18,6 +18,8 @@ You can optionally flush the cache before seeding by using the `SetUp()` method Or it can be done directly in a seeder class, as shown below. +## Roles/Permissions Seeder + Here is a sample seeder, which first clears the cache, creates permissions and then assigns permissions to roles (the order of these steps is intentional): ```php @@ -54,6 +56,42 @@ class RolesAndPermissionsSeeder extends Seeder } ``` +## User Seeding with Factories and States + +To use Factory States to assign roles after creating users: + +```php +// Factory: + public function definition() {...} + + public function active(): static + { + return $this->state(fn (array $attributes) => [ + 'status' => 1, + ]) + ->afterCreating(function (User $user) { + $user->assignRole('ActiveMember'); + }); + } + +// Seeder: +// To create 4 users using this 'active' state in a Seeder: +User::factory(4)->active()->create(); +``` + +To seed multiple users and then assign each of them a role, WITHOUT using Factory States: + +```php +// Seeder: +User::factory() + ->count(50) + ->create() + ->each(function ($user) { + $user->assignRole('Member'); + }); +``` + + ## Speeding up seeding for large data sets When seeding large quantities of roles or permissions you may consider using Eloquent's `insert` command instead of `create`, as this bypasses all the internal checks that this package does when calling `create` (including extra queries to verify existence, test guards, etc). From 0e593bb9e6808cc55c67f150878b0847f20b9036 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 19 Jan 2024 19:47:32 -0500 Subject: [PATCH 441/648] cross-name uuid with ulid/guid --- docs/advanced-usage/uuid.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/advanced-usage/uuid.md b/docs/advanced-usage/uuid.md index 79c1331bb..108ea2142 100644 --- a/docs/advanced-usage/uuid.md +++ b/docs/advanced-usage/uuid.md @@ -1,14 +1,16 @@ --- -title: UUID +title: UUID/ULID weight: 7 --- -If you're using UUIDs for your User models there are a few considerations to note. +If you're using UUIDs (ULID, GUID, etc) for your User models or Role/Permission models there are a few considerations to note. -> THIS IS NOT A FULL LESSON ON HOW TO IMPLEMENT UUIDs IN YOUR APP. +> NOTE: THIS IS NOT A FULL LESSON ON HOW TO IMPLEMENT UUIDs IN YOUR APP. Since each UUID implementation approach is different, some of these may or may not benefit you. As always, your implementation may vary. +We use "uuid" in the examples below. Adapt for ULID or GUID as needed. + ## Migrations You will need to update the `create_permission_tables.php` migration after creating it with `php artisan vendor:publish`. After making your edits, be sure to run the migration! From eea80901effa275000439f1346a9d667370f024b Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 27 Feb 2024 15:37:15 -0500 Subject: [PATCH 442/648] Update .gitattributes --- .gitattributes | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.gitattributes b/.gitattributes index 47d2cd035..b47d0893d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,16 +5,15 @@ /.github export-ignore /.gitattributes export-ignore /.gitignore export-ignore -/.travis.yml export-ignore +/.phpunit.result.cache export-ignore /phpunit.xml.dist export-ignore -/.scrutinizer.yml export-ignore /art export-ignore /docs export-ignore /tests export-ignore /.editorconfig export-ignore /.php_cs.dist.php export-ignore /phpstan* export-ignore -/.styleci.yml export-ignore +/psalm.xml export-ignore /CHANGELOG.md export-ignore /CONTRIBUTING.md export-ignore From 05cce017fe3ac78f60a3fce78c07fe6e8e6e6e52 Mon Sep 17 00:00:00 2001 From: Raheel Khan Date: Wed, 28 Feb 2024 13:11:20 +0500 Subject: [PATCH 443/648] add laravel 11 to workflow run tests (#2605) * add laravel 11 to workflow run tests * Passport 12 * override passport:install --------- Co-authored-by: Chris Brown Co-authored-by: drbyte --- .github/workflows/run-tests.yml | 8 +++++++- composer.json | 2 +- src/Guard.php | 4 ++-- tests/TestCase.php | 8 ++++++-- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 84e1a0f95..cd56424e6 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -10,9 +10,11 @@ jobs: fail-fast: false matrix: php: [8.3, 8.2, 8.1, 8.0] - laravel: ["^10.0", "^9.0", "^8.12"] + laravel: ["^11.0", "^10.0", "^9.0", "^8.12"] dependency-version: [prefer-lowest, prefer-stable] include: + - laravel: "^11.0" + testbench: 9.* - laravel: "^10.0" testbench: 8.* - laravel: "^9.0" @@ -20,6 +22,10 @@ jobs: - laravel: "^8.12" testbench: "^6.23" exclude: + - laravel: "^11.0" + php: 8.1 + - laravel: "^11.0" + php: 8.0 - laravel: "^10.0" php: 8.0 - laravel: "^8.12" diff --git a/composer.json b/composer.json index ab0515b8e..bd29fa40e 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,7 @@ "illuminate/database": "^8.12|^9.0|^10.0|^11.0" }, "require-dev": { - "laravel/passport": "^11.0", + "laravel/passport": "^11.0|^12.0", "orchestra/testbench": "^6.23|^7.0|^8.0|^9.0", "phpunit/phpunit": "^9.4|^10.1" }, diff --git a/src/Guard.php b/src/Guard.php index dd5023148..4f697ce20 100644 --- a/src/Guard.php +++ b/src/Guard.php @@ -13,7 +13,7 @@ class Guard * Return a collection of guard names suitable for the $model, * as indicated by the presence of a $guard_name property or a guardName() method on the model. * - * @param string|Model $model model class object or name + * @param string|Model $model model class object or name */ public static function getNames($model): Collection { @@ -58,7 +58,7 @@ protected static function getConfigAuthGuards(string $class): Collection /** * Lookup a guard name relevant for the $class model and the current user. * - * @param string|Model $class model class object or name + * @param string|Model $class model class object or name * @return string guard name */ public static function getDefaultName($class): string diff --git a/tests/TestCase.php b/tests/TestCase.php index 48de05ed5..24af65bf9 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -203,8 +203,12 @@ protected function setUpPassport($app): void $app['config']->set('permission.use_passport_client_credentials', true); $app['config']->set('auth.guards.api', ['driver' => 'passport', 'provider' => 'users']); - $this->artisan('migrate'); - $this->artisan('passport:install'); + // mimic passport:install (must load migrations using our own call to loadMigrationsFrom() else rollbacks won't occur, and migrations will be left in skeleton directory + $this->artisan('passport:keys'); + $this->loadMigrationsFrom(__DIR__.'/../vendor/laravel/passport/database/migrations/'); + $provider = in_array('users', array_keys(config('auth.providers'))) ? 'users' : null; + $this->artisan('passport:client', ['--personal' => true, '--name' => config('app.name').' Personal Access Client']); + $this->artisan('passport:client', ['--password' => true, '--name' => config('app.name').' Password Grant Client', '--provider' => $provider]); $this->testClient = Client::create(['name' => 'Test', 'redirect' => 'https://example.com/', 'personal_access_client' => 0, 'password_client' => 0, 'revoked' => 0]); $this->testClientRole = $app[Role::class]->create(['name' => 'clientRole', 'guard_name' => 'api']); From edecbd8750b5223e462699e59202295e70cddc14 Mon Sep 17 00:00:00 2001 From: drbyte Date: Wed, 28 Feb 2024 08:14:24 +0000 Subject: [PATCH 444/648] Update CHANGELOG --- CHANGELOG.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b6100624..a66d5ea07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,27 @@ All notable changes to `laravel-permission` will be documented in this file +## 6.4.0 - 2024-02-28 + +* Laravel 11 Support + +### What's Changed + +* Add Laravel 11 to workflow run tests by @mraheelkhan in https://github.com/spatie/laravel-permission/pull/2605 +* And Passport 12 + +### Internals + +* Update to use Larastan Org by @arnebr in https://github.com/spatie/laravel-permission/pull/2585 +* laravel-pint-action to major version tag by @erikn69 in https://github.com/spatie/laravel-permission/pull/2586 + +### New Contributors + +* @arnebr made their first contribution in https://github.com/spatie/laravel-permission/pull/2585 +* @mraheelkhan made their first contribution in https://github.com/spatie/laravel-permission/pull/2605 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/6.3.0...6.4.0 + ## 6.3.0 - 2023-12-24 ### What's Changed @@ -744,6 +765,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` 1. Also this is a good time to point out that now with v2.25.0 and v2.26.0 most permission-cache-reset scenarios may no longer be needed in your app, so it's worth reviewing those cases, as you may gain some app speed improvement by removing unnecessary cache resets. @@ -807,6 +829,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` ## 2.19.1 - 2018-09-14 From e1bb52d2de03d002ee3f0b59324cc688f0ef53f7 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Wed, 28 Feb 2024 03:15:45 -0500 Subject: [PATCH 445/648] Update .gitattributes --- .gitattributes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitattributes b/.gitattributes index b47d0893d..07110aeb2 100644 --- a/.gitattributes +++ b/.gitattributes @@ -13,7 +13,7 @@ /.editorconfig export-ignore /.php_cs.dist.php export-ignore /phpstan* export-ignore -/psalm.xml export-ignore +/psalm.xml export-ignore /CHANGELOG.md export-ignore /CONTRIBUTING.md export-ignore From 29b16ce4a1eaaa1b0eb7ab185fb035e662a5523b Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Wed, 28 Feb 2024 05:14:22 -0500 Subject: [PATCH 446/648] Update .gitattributes --- .gitattributes | 2 -- 1 file changed, 2 deletions(-) diff --git a/.gitattributes b/.gitattributes index 07110aeb2..9da33a6cd 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,7 +5,6 @@ /.github export-ignore /.gitattributes export-ignore /.gitignore export-ignore -/.phpunit.result.cache export-ignore /phpunit.xml.dist export-ignore /art export-ignore /docs export-ignore @@ -13,7 +12,6 @@ /.editorconfig export-ignore /.php_cs.dist.php export-ignore /phpstan* export-ignore -/psalm.xml export-ignore /CHANGELOG.md export-ignore /CONTRIBUTING.md export-ignore From 5243ff97aa2bcc2d7d636a9720cd5d39cc315290 Mon Sep 17 00:00:00 2001 From: Ali Sasani Date: Fri, 1 Mar 2024 22:34:49 +0330 Subject: [PATCH 447/648] [feat] simplify the definition of multiple Blade "if" directives (#2628) * [feat] simplify the definition of a Blade directives --------- Co-authored-by: erikn69 --- src/PermissionServiceProvider.php | 30 +++++++++--------------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/src/PermissionServiceProvider.php b/src/PermissionServiceProvider.php index a9011cd2b..a23c1eddd 100644 --- a/src/PermissionServiceProvider.php +++ b/src/PermissionServiceProvider.php @@ -122,32 +122,20 @@ public static function bladeMethodWrapper($method, $role, $guard = null): bool return auth($guard)->check() && auth($guard)->user()->{$method}($role); } - protected function registerBladeExtensions($bladeCompiler): void + protected function registerBladeExtensions(BladeCompiler $bladeCompiler): void { $bladeMethodWrapper = '\\Spatie\\Permission\\PermissionServiceProvider::bladeMethodWrapper'; - $bladeCompiler->directive('role', fn ($args) => ""); - $bladeCompiler->directive('elserole', fn ($args) => ""); - $bladeCompiler->directive('endrole', fn () => ''); + // permission checks + $bladeCompiler->if('haspermission', fn () => $bladeMethodWrapper('checkPermissionTo', ...func_get_args())); - $bladeCompiler->directive('haspermission', fn ($args) => ""); - $bladeCompiler->directive('elsehaspermission', fn ($args) => ""); - $bladeCompiler->directive('endhaspermission', fn () => ''); - - $bladeCompiler->directive('hasrole', fn ($args) => ""); - $bladeCompiler->directive('endhasrole', fn () => ''); - - $bladeCompiler->directive('hasanyrole', fn ($args) => ""); - $bladeCompiler->directive('endhasanyrole', fn () => ''); - - $bladeCompiler->directive('hasallroles', fn ($args) => ""); - $bladeCompiler->directive('endhasallroles', fn () => ''); - - $bladeCompiler->directive('unlessrole', fn ($args) => ""); + // role checks + $bladeCompiler->if('role', fn () => $bladeMethodWrapper('hasRole', ...func_get_args())); + $bladeCompiler->if('hasrole', fn () => $bladeMethodWrapper('hasRole', ...func_get_args())); + $bladeCompiler->if('hasanyrole', fn () => $bladeMethodWrapper('hasAnyRole', ...func_get_args())); + $bladeCompiler->if('hasallroles', fn () => $bladeMethodWrapper('hasAllRoles', ...func_get_args())); + $bladeCompiler->if('hasexactroles', fn () => $bladeMethodWrapper('hasExactRoles', ...func_get_args())); $bladeCompiler->directive('endunlessrole', fn () => ''); - - $bladeCompiler->directive('hasexactroles', fn ($args) => ""); - $bladeCompiler->directive('endhasexactroles', fn () => ''); } protected function registerMacroHelpers(): void From 1b1ba2bf849c66178841542a10f3765563df37d1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 00:53:12 -0500 Subject: [PATCH 448/648] Bump ramsey/composer-install from 2 to 3 (#2630) --- .github/workflows/phpstan.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index 00e3e5efe..609d55a6d 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -20,7 +20,7 @@ jobs: coverage: none - name: Install composer dependencies - uses: ramsey/composer-install@v2 + uses: ramsey/composer-install@v3 - name: Install larastan run: | From 3e4630f3c2160d952ad96023b32b6c186a62ec5f Mon Sep 17 00:00:00 2001 From: Freek Van der Herten Date: Tue, 5 Mar 2024 15:01:13 +0100 Subject: [PATCH 449/648] Update README.md --- README.md | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/README.md b/README.md index 25ff759bd..1a5e00473 100644 --- a/README.md +++ b/README.md @@ -2,16 +2,6 @@ # Associate users with permissions and roles -### Sponsor - - - - - - -
If you want to quickly add authentication and authorization to Laravel projects, feel free to check Auth0's Laravel SDK and free plan at https://auth0.com/developers.
- - [![Latest Version on Packagist](https://img.shields.io/packagist/v/spatie/laravel-permission.svg?style=flat-square)](https://packagist.org/packages/spatie/laravel-permission) [![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/spatie/laravel-permission/run-tests-L8.yml?branch=main&label=Tests)](https://github.com/spatie/laravel-permission/actions?query=workflow%3ATests+branch%3Amain) [![Total Downloads](https://img.shields.io/packagist/dt/spatie/laravel-permission.svg?style=flat-square)](https://packagist.org/packages/spatie/laravel-permission) From e4fd5ca7be0d7f7654c1151ff3614eb28c3a19a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Benoit?= <39646949+Androlax2@users.noreply.github.com> Date: Wed, 6 Mar 2024 12:27:43 -0500 Subject: [PATCH 450/648] fix(team): Add nullable team_id (#2607) --- src/PermissionRegistrar.php | 2 +- src/helpers.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index 56c7415a8..3dcaac17b 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -109,7 +109,7 @@ public function setPermissionsTeamId($id): void } /** - * @return int|string + * @return int|string|null */ public function getPermissionsTeamId() { diff --git a/src/helpers.php b/src/helpers.php index d1aae1ee2..55048d753 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -14,7 +14,7 @@ function getModelForGuard(string $guard) if (! function_exists('setPermissionsTeamId')) { /** - * @param int|string|\Illuminate\Database\Eloquent\Model $id + * @param int|string|null|\Illuminate\Database\Eloquent\Model $id */ function setPermissionsTeamId($id) { @@ -24,7 +24,7 @@ function setPermissionsTeamId($id) if (! function_exists('getPermissionsTeamId')) { /** - * @return int|string + * @return int|string|null */ function getPermissionsTeamId() { From 00edc6659c6df4b782767c556a8c93caefceb403 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 12 Mar 2024 17:36:37 -0400 Subject: [PATCH 451/648] Update with Breeze example --- docs/basic-usage/new-app.md | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/basic-usage/new-app.md b/docs/basic-usage/new-app.md index b3f112d0b..ab3eeca65 100644 --- a/docs/basic-usage/new-app.md +++ b/docs/basic-usage/new-app.md @@ -16,6 +16,12 @@ If you're new to Laravel or to any of the concepts mentioned here, you can learn ```sh cd ~/Sites laravel new mypermissionsdemo +# (Choose Laravel Breeze, choose Blade with Alpine) +# (choose your own dark-mode-support choice) +# (choose your desired testing framework) +# (say Yes to initialize a Git repo, so that you can track your code changes) +# (Choose SQLite) + cd mypermissionsdemo git init git add . @@ -38,8 +44,10 @@ php artisan migrate:fresh sed -i '' $'s/use HasFactory, Notifiable;/use HasFactory, Notifiable;\\\n use \\\\Spatie\\\\Permission\\\\Traits\\\\HasRoles;/' app/Models/User.php sed -i '' $'s/use HasApiTokens, HasFactory, Notifiable;/use HasApiTokens, HasFactory, Notifiable;\\\n use \\\\Spatie\\\\Permission\\\\Traits\\\\HasRoles;/' app/Models/User.php git add . && git commit -m "Add HasRoles trait" +``` -# Add Laravel's basic auth scaffolding +If you didn't install Laravel Breeze or Jetstream, add Laravel's basic auth scaffolding: +```php composer require laravel/ui --dev php artisan ui bootstrap --auth # npm install && npm run prod @@ -125,11 +133,10 @@ Super-Admins are a common feature. The following approach allows that when your - Add a Gate::before check in your `AuthServiceProvider`: ```diff ++ use Illuminate\Support\Facades\Gate; + public function boot() { - $this->registerPolicies(); - - // + // Implicitly grant "Super-Admin" role all permission checks using can() + Gate::before(function ($user, $ability) { From 5703bd92329902ee96a89321a010cd071192955c Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 12 Mar 2024 17:37:01 -0400 Subject: [PATCH 452/648] Include some simple Laravel 11 demo examples --- docs/basic-usage/new-app.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/docs/basic-usage/new-app.md b/docs/basic-usage/new-app.md index ab3eeca65..14d319c17 100644 --- a/docs/basic-usage/new-app.md +++ b/docs/basic-usage/new-app.md @@ -155,6 +155,43 @@ Your app will have Models, Controllers, routes, Views, Factories, Policies, Test You can see examples of these in the demo app at https://github.com/drbyte/spatie-permissions-demo/ + +### Quick Examples +If you are creating a demo app for reporting a bug or getting help with troubleshooting something, skip this section and proceed to "Sharing" below. + +If this is your first app with this package, you may want some quick permission examples to see it in action. If you've set up your app using the instructions above, the following examples will work in conjunction with the users and permissions created in the seeder. + +Three users were created: test@example.com, admin@example.com, superadmin@example.com and the password for each is "password". + +`/resources/views/dashboard.php` +```diff +
+ {{ __("You're logged in!") }} +
++ @can('edit articles') ++ You can EDIT ARTICLES. ++ @endcan ++ @can('publish articles') ++ You can PUBLISH ARTICLES. ++ @endcan ++ @can('only super-admins can see this section') ++ Congratulations, you are a super-admin! ++ @endcan +``` +With the above code, when you login with each respective user, you will see different messages based on that access. + +Here's a routes example with Breeze and Laravel 11. +Edit `/routes/web.php`: +```diff +-Route::middleware('auth')->group(function () { ++Route::middleware('role_or_permission:publish articles')->group(function () { + Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit'); + Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update'); + Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy'); +}); +``` +With the above change, you will be unable to access the user "Profile" page unless you are logged in with "admin" or "super-admin". You could change `role_or_permission:publish_articles` to `role:writer` to make it only available to the "test" user. + ## Sharing To share your app on Github for easy collaboration: From 40eafbf5ef30141398647d01b828f2df3095676c Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 12 Mar 2024 17:37:19 -0400 Subject: [PATCH 453/648] Laravel 11 middleware --- docs/basic-usage/middleware.md | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/docs/basic-usage/middleware.md b/docs/basic-usage/middleware.md index 57290c68d..ca6d49e3c 100644 --- a/docs/basic-usage/middleware.md +++ b/docs/basic-usage/middleware.md @@ -13,7 +13,7 @@ Route::group(['middleware' => ['can:publish articles']], function () { }); ``` -In Laravel v10.9 and up, you can also call this middleware with a static method. +Since Laravel v10.9, you can also call this middleware with a static method. ```php Route::group(['middleware' => [\Illuminate\Auth\Middleware\Authorize::using('publish articles')]], function () { @@ -24,9 +24,24 @@ Route::group(['middleware' => [\Illuminate\Auth\Middleware\Authorize::using('pub ## Package Middleware This package comes with `RoleMiddleware`, `PermissionMiddleware` and `RoleOrPermissionMiddleware` middleware. -You can add them inside your `app/Http/Kernel.php` file to be able to use them through aliases. -Note the property name difference between Laravel 10 and older versions of Laravel: +You can register their aliases for easy reference elsewhere in your app: + +> See a typo? Note that since v6 the 'Middleware' namespace is singular. Prior to v6 it was 'Middlewares'. Time to upgrade your app! + +In Laravel 11 open `/bootstrap/app.php` and register it there: + +```php + ->withMiddleware(function (Middleware $middleware) { + $middleware->alias([ + 'role' => \Spatie\Permission\Middleware\RoleMiddleware::class, + 'permission' => \Spatie\Permission\Middleware\PermissionMiddleware::class, + 'role_or_permission' => \Spatie\Permission\Middleware\RoleOrPermissionMiddleware::class, + ]); + }) +``` + +In Laravel 9 and 10 you can add them in `app/Http/Kernel.php`: ```php // Laravel 9 uses $routeMiddleware = [ @@ -40,9 +55,9 @@ protected $middlewareAliases = [ ]; ``` -**YOU SHOULD ALSO** set [the `$middlewarePriority` array](https://laravel.com/docs/master/middleware#sorting-middleware) to include this package's middleware before the `SubstituteBindings` middleware, else you may get *404 Not Found* responses when a *403 Not Authorized* response might be expected. +### Middleware Priority +If your app is triggering *404 Not Found* responses when a *403 Not Authorized* response might be expected, it might be a middleware priority clash. Explore reordering priorities so that this package's middleware runs before Laravel's `SubstituteBindings` middleware. (See [Middleware docs](https://laravel.com/docs/master/middleware#sorting-middleware) ). In Laravel 11 you could explore `$middleware->prependToGroup()` instead. -> See a typo? Note that since v6 the 'Middleware' namespace is singular. Prior to v6 it was 'Middlewares'. Time to upgrade your app! ## Middleware via Routes From af37376a6061607b541ee90b150cc79bfb0d736d Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 12 Mar 2024 17:45:19 -0400 Subject: [PATCH 454/648] Update example syntax --- docs/basic-usage/new-app.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/basic-usage/new-app.md b/docs/basic-usage/new-app.md index 14d319c17..6834c717c 100644 --- a/docs/basic-usage/new-app.md +++ b/docs/basic-usage/new-app.md @@ -74,7 +74,7 @@ class PermissionsDemoSeeder extends Seeder * * @return void */ - public function run() + public function run(): void { // Reset cached roles and permissions app()[PermissionRegistrar::class]->forgetCachedPermissions(); @@ -137,7 +137,6 @@ Super-Admins are a common feature. The following approach allows that when your public function boot() { - + // Implicitly grant "Super-Admin" role all permission checks using can() + Gate::before(function ($user, $ability) { + if ($user->hasRole('Super-Admin')) { From f40a866abb415770593558152dcdbb372cfec886 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 12 Mar 2024 17:45:42 -0400 Subject: [PATCH 455/648] Add Laravel 11 training link --- docs/basic-usage/new-app.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/basic-usage/new-app.md b/docs/basic-usage/new-app.md index 6834c717c..87fcbfc04 100644 --- a/docs/basic-usage/new-app.md +++ b/docs/basic-usage/new-app.md @@ -9,7 +9,7 @@ If you want to just try out the features of this package you can get started wit The examples on this page are primarily added for assistance in creating a quick demo app for troubleshooting purposes, to post the repo on github for convenient sharing to collaborate or get support. -If you're new to Laravel or to any of the concepts mentioned here, you can learn more in the [Laravel documentation](https://laravel.com/docs/) and in the free videos at Laracasts such as with the [Laravel From Scratch series](https://laracasts.com/series/laravel-8-from-scratch/). +If you're new to Laravel or to any of the concepts mentioned here, you can learn more in the [Laravel documentation](https://laravel.com/docs/) and in the free videos at Laracasts such as with the [Laravel 11 in 30 days](https://laracasts.com/series/30-days-to-learn-laravel-11) or [Laravel 8 From Scratch](https://laracasts.com/series/laravel-8-from-scratch/) series. ### Initial setup: From ab7871a28c7821f9cf6de386d545d0447f073388 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 12 Mar 2024 20:48:02 -0400 Subject: [PATCH 456/648] Laravel 11 --- docs/installation-laravel.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation-laravel.md b/docs/installation-laravel.md index 39e8087ee..fd0cfb59f 100644 --- a/docs/installation-laravel.md +++ b/docs/installation-laravel.md @@ -9,7 +9,7 @@ Choose the version of this package that suits your Laravel version. Package Version | Laravel Version ----------------|----------- - ^6.0 | 8,9,10 (PHP 8.0+) + ^6.0 | 8,9,10,11 (PHP 8.0+) ^5.8 | 7,8,9,10 ^5.7 | 7,8,9 ^5.4-^5.6 | 7,8 From 15ab449cb5355c8e58e33c686085d8dfba474a9c Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 12 Mar 2024 21:04:02 -0400 Subject: [PATCH 457/648] Update link to example table --- docs/installation-lumen.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation-lumen.md b/docs/installation-lumen.md index 1364e727e..1cff50275 100644 --- a/docs/installation-lumen.md +++ b/docs/installation-lumen.md @@ -74,7 +74,7 @@ NOTE: Remember that Laravel's authorization layer requires that your `User` mode ## User Table NOTE: If you are working with a fresh install of Lumen, then you probably also need a migration file for your Users table. You can create your own, or you can copy a basic one from Laravel: -[https://github.com/laravel/laravel/blob/master/database/migrations/2014_10_12_000000_create_users_table.php](https://github.com/laravel/laravel/blob/master/database/migrations/2014_10_12_000000_create_users_table.php) +[https://github.com/laravel/laravel/blob/master/database/migrations/0001_01_01_000000_create_users_table.php](https://github.com/laravel/laravel/blob/master/database/migrations/0001_01_01_000000_create_users_table.php) (You will need to run `php artisan migrate` after adding this file.) From 9ef09aafea999268e4b342df085ba29e1833df35 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 12 Mar 2024 21:09:36 -0400 Subject: [PATCH 458/648] Highlight example in migration --- docs/prerequisites.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/prerequisites.md b/docs/prerequisites.md index 78773b379..88f3ad3e8 100644 --- a/docs/prerequisites.md +++ b/docs/prerequisites.md @@ -47,6 +47,12 @@ MySQL 8.0 limits index keys to 1000 characters. This package publishes a migrati Thus in your AppServiceProvider you will need to set `Schema::defaultStringLength(125)`. [See the Laravel Docs for instructions](https://laravel.com/docs/migrations#index-lengths-mysql-mariadb). +You may be able to bypass setting `defaultStringLength(125)` by editing the migration and specifying the `125` in 4 fields. There are 2 instances of this code snippet where you can explicitly set the `125`: +```php + $table->string('name'); // For MySQL 8.0 use string('name', 125); + $table->string('guard_name'); // For MySQL 8.0 use string('guard_name', 125); +``` + ## Note for apps using UUIDs/ULIDs/GUIDs This package expects the primary key of your `User` model to be an auto-incrementing `int`. If it is not, you may need to modify the `create_permission_tables` migration and/or modify the default configuration. See [https://spatie.be/docs/laravel-permission/advanced-usage/uuid](https://spatie.be/docs/laravel-permission/advanced-usage/uuid) for more information. From 359aae28c3c449471e74fb8c0fb23a4333e0ebe9 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 12 Mar 2024 21:24:17 -0400 Subject: [PATCH 459/648] Add clarity to cache-reset API usage --- docs/advanced-usage/cache.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/advanced-usage/cache.md b/docs/advanced-usage/cache.md index fc6b5e59a..588f77f42 100644 --- a/docs/advanced-usage/cache.md +++ b/docs/advanced-usage/cache.md @@ -10,9 +10,12 @@ Role and Permission data are cached to speed up performance. When you **use the built-in functions** for manipulating roles and permissions, the cache is automatically reset for you, and relations are automatically reloaded for the current model record: ```php +// When handling permissions assigned to roles: $role->givePermissionTo('edit articles'); $role->revokePermissionTo('edit articles'); $role->syncPermissions(params); + +// When linking roles to permissions: $permission->assignRole('writer'); $permission->removeRole('writer'); $permission->syncRoles(params); @@ -25,7 +28,7 @@ Additionally, because the Role and Permission models are Eloquent models which i **NOTE: User-specific role/permission assignments are kept in-memory since v4.4.0, so the cache-reset is no longer called since v5.1.0 when updating User-related assignments.** Examples: ```php -// These do not call a cache-reset, because the User-related assignments are in-memory. +// These operations on a User do not call a cache-reset, because the User-related assignments are in-memory. $user->assignRole('writer'); $user->removeRole('writer'); $user->syncRoles(params); From c102f2877f603ae4d34cb291997d5090790530dc Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 12 Mar 2024 21:25:21 -0400 Subject: [PATCH 460/648] Add import to example --- docs/advanced-usage/custom-permission-check.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/advanced-usage/custom-permission-check.md b/docs/advanced-usage/custom-permission-check.md index 54e9b3035..0f1c88728 100644 --- a/docs/advanced-usage/custom-permission-check.md +++ b/docs/advanced-usage/custom-permission-check.md @@ -18,6 +18,8 @@ You could, for example, create a `Gate::before()` method call to handle this: **app/Providers/AuthServiceProvider.php** ```php +use Illuminate\Support\Facades\Gate; + public function boot() { ... From 67181d22f960eea490a0b9ea2d8d530cfc600e2e Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 12 Mar 2024 21:29:48 -0400 Subject: [PATCH 461/648] Updated example --- docs/advanced-usage/seeding.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/advanced-usage/seeding.md b/docs/advanced-usage/seeding.md index 207fbff42..d4e58d810 100644 --- a/docs/advanced-usage/seeding.md +++ b/docs/advanced-usage/seeding.md @@ -29,7 +29,7 @@ use Spatie\Permission\Models\Permission; class RolesAndPermissionsSeeder extends Seeder { - public function run() + public function run(): void { // Reset cached roles and permissions app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions(); From 9e3cc860c54c1daa8462d8dd1d0fb3b9b230b74f Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 12 Mar 2024 21:33:35 -0400 Subject: [PATCH 462/648] Clarify configuration notes --- docs/advanced-usage/uuid.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/advanced-usage/uuid.md b/docs/advanced-usage/uuid.md index 108ea2142..c25f5b8d3 100644 --- a/docs/advanced-usage/uuid.md +++ b/docs/advanced-usage/uuid.md @@ -83,7 +83,7 @@ If you also want the roles and permissions to use a UUID for their `id` value, t ## Configuration (OPTIONAL) You might want to change the pivot table field name from `model_id` to `model_uuid`, just for semantic purposes. -For this, in the configuration file edit `column_names.model_morph_key`: +For this, in the `permissions.php` configuration file edit `column_names.model_morph_key`: - OPTIONAL: Change to `model_uuid` instead of the default `model_id`. ```diff @@ -105,10 +105,10 @@ For this, in the configuration file edit `column_names.model_morph_key`: + 'model_morph_key' => 'model_uuid', ], ``` -- If you extend the models into your app, be sure to list those models in your configuration file. See the Extending section of the documentation and the Models section below. +- If you extend the models into your app, be sure to list those models in your `permissions.php` configuration file. See the Extending section of the documentation and the Models section below. ## Models -If you want all the role/permission objects to have a UUID instead of an integer, you will need to Extend the default Role and Permission models into your own namespace in order to set some specific properties. (See the Extending section of the docs, where it explains requirements of Extending, as well as the configuration settings you need to update.) +If you want all the role/permission objects to have a UUID instead of an integer, you will need to Extend the default Role and Permission models into your own namespace in order to set some specific properties. (See the Extending section of the docs, where it explains requirements of Extending, as well as the `permissions.php` configuration settings you need to update.) Examples: From a21a4e3d377d672ee55d07ab9817e4147c3b4c5a Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Wed, 13 Mar 2024 14:43:37 -0400 Subject: [PATCH 463/648] Formatting updates for clarity --- docs/basic-usage/middleware.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/basic-usage/middleware.md b/docs/basic-usage/middleware.md index ca6d49e3c..67d6dc57a 100644 --- a/docs/basic-usage/middleware.md +++ b/docs/basic-usage/middleware.md @@ -23,14 +23,13 @@ Route::group(['middleware' => [\Illuminate\Auth\Middleware\Authorize::using('pub ## Package Middleware +**See a typo? Note that since v6 the _'Middleware'_ namespace is singular. Prior to v6 it was _'Middlewares'_. Time to upgrade your app!** + This package comes with `RoleMiddleware`, `PermissionMiddleware` and `RoleOrPermissionMiddleware` middleware. You can register their aliases for easy reference elsewhere in your app: -> See a typo? Note that since v6 the 'Middleware' namespace is singular. Prior to v6 it was 'Middlewares'. Time to upgrade your app! - In Laravel 11 open `/bootstrap/app.php` and register it there: - ```php ->withMiddleware(function (Middleware $middleware) { $middleware->alias([ @@ -42,7 +41,6 @@ In Laravel 11 open `/bootstrap/app.php` and register it there: ``` In Laravel 9 and 10 you can add them in `app/Http/Kernel.php`: - ```php // Laravel 9 uses $routeMiddleware = [ //protected $routeMiddleware = [ From e49aefe4f90c972e31e8a39ea020866d80db7791 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Wed, 13 Mar 2024 15:39:59 -0400 Subject: [PATCH 464/648] Formatting improvements --- docs/basic-usage/middleware.md | 105 +++++++++++---------------------- 1 file changed, 35 insertions(+), 70 deletions(-) diff --git a/docs/basic-usage/middleware.md b/docs/basic-usage/middleware.md index 67d6dc57a..b555683b4 100644 --- a/docs/basic-usage/middleware.md +++ b/docs/basic-usage/middleware.md @@ -8,28 +8,22 @@ weight: 11 For checking against a single permission (see Best Practices) using `can`, you can use the built-in Laravel middleware provided by `\Illuminate\Auth\Middleware\Authorize::class` like this: ```php -Route::group(['middleware' => ['can:publish articles']], function () { - // -}); -``` - -Since Laravel v10.9, you can also call this middleware with a static method. +Route::group(['middleware' => ['can:publish articles']], function () { ... }); -```php -Route::group(['middleware' => [\Illuminate\Auth\Middleware\Authorize::using('publish articles')]], function () { - // -}); +// or with static method (requires Laravel 10.9+) +Route::group(['middleware' => [\Illuminate\Auth\Middleware\Authorize::using('publish articles')]], function () { ... }); ``` ## Package Middleware -**See a typo? Note that since v6 the _'Middleware'_ namespace is singular. Prior to v6 it was _'Middlewares'_. Time to upgrade your app!** +**See a typo? Note that since v6 the _'Middleware'_ namespace is singular. Prior to v6 it was _'Middlewares'_. Time to upgrade your implementation!** This package comes with `RoleMiddleware`, `PermissionMiddleware` and `RoleOrPermissionMiddleware` middleware. You can register their aliases for easy reference elsewhere in your app: -In Laravel 11 open `/bootstrap/app.php` and register it there: +In Laravel 11 open `/bootstrap/app.php` and register them there: + ```php ->withMiddleware(function (Middleware $middleware) { $middleware->alias([ @@ -41,6 +35,7 @@ In Laravel 11 open `/bootstrap/app.php` and register it there: ``` In Laravel 9 and 10 you can add them in `app/Http/Kernel.php`: + ```php // Laravel 9 uses $routeMiddleware = [ //protected $routeMiddleware = [ @@ -54,92 +49,62 @@ protected $middlewareAliases = [ ``` ### Middleware Priority -If your app is triggering *404 Not Found* responses when a *403 Not Authorized* response might be expected, it might be a middleware priority clash. Explore reordering priorities so that this package's middleware runs before Laravel's `SubstituteBindings` middleware. (See [Middleware docs](https://laravel.com/docs/master/middleware#sorting-middleware) ). In Laravel 11 you could explore `$middleware->prependToGroup()` instead. +If your app is triggering *404 Not Found* responses when a *403 Not Authorized* response might be expected, it might be a middleware priority clash. Explore reordering priorities so that this package's middleware runs before Laravel's `SubstituteBindings` middleware. (See [Middleware docs](https://laravel.com/docs/master/middleware#sorting-middleware) ). +In Laravel 11 you could explore `$middleware->prependToGroup()` instead. See the Laravel Documentation for details. -## Middleware via Routes -Then you can protect your routes using middleware rules: +## Using Middleware in Routes and Controllers -```php -Route::group(['middleware' => ['role:manager']], function () { - // -}); +After you have registered the aliases as shown above, you can use them in your Routes and Controllers much the same way you use any other middleware: -// for a specific guard: -Route::group(['middleware' => ['role:manager,api']], function () { - // -}); +### Routes -Route::group(['middleware' => ['permission:publish articles']], function () { - // -}); +```php +Route::group(['middleware' => ['role:manager']], function () { ... }); +Route::group(['middleware' => ['permission:publish articles']], function () { ... }); +Route::group(['middleware' => ['role_or_permission:publish articles']], function () { ... }); -Route::group(['middleware' => ['role:manager','permission:publish articles']], function () { - // -}); +// for a specific guard: +Route::group(['middleware' => ['role:manager,api']], function () { ... }); -Route::group(['middleware' => ['role_or_permission:publish articles']], function () { - // -}); +// multiple middleware +Route::group(['middleware' => ['role:manager','permission:publish articles']], function () { ... }); ``` You can specify multiple roles or permissions with a `|` (pipe) character, which is treated as `OR`: ```php -Route::group(['middleware' => ['role:manager|writer']], function () { - // -}); - -Route::group(['middleware' => ['permission:publish articles|edit articles']], function () { - // -}); +Route::group(['middleware' => ['role:manager|writer']], function () { ... }); +Route::group(['middleware' => ['permission:publish articles|edit articles']], function () { ... }); +Route::group(['middleware' => ['role_or_permission:manager|edit articles']], function () { ... }); // for a specific guard -Route::group(['middleware' => ['permission:publish articles|edit articles,api']], function () { - // -}); - -Route::group(['middleware' => ['role_or_permission:manager|edit articles']], function () { - // -}); +Route::group(['middleware' => ['permission:publish articles|edit articles,api']], function () { ... }); ``` -## Middleware with Controllers - -You can protect your controllers similarly, by setting desired middleware in the constructor: +### Controllers ```php public function __construct() { $this->middleware(['role:manager','permission:publish articles|edit articles']); -} -``` - -```php -public function __construct() -{ + // or $this->middleware(['role_or_permission:manager|edit articles']); + // or with specific guard + $this->middleware(['role_or_permission:manager|edit articles,api']); } ``` -(You can use Laravel's Model Policy feature with your controller methods. See the Model Policies section of these docs.) +You can also use Laravel's Model Policy feature in your controller methods. See the Model Policies section of these docs. -## Use middleware static methods +## Middleware via Static Methods -All of the middleware can also be applied by calling the static `using` method, -which accepts either a `|`-separated string or an array as input. +All of the middleware can also be applied by calling the static `using` method, which accepts either an array or a `|`-separated string as input. ```php -Route::group(['middleware' => [\Spatie\Permission\Middleware\RoleMiddleware::using('manager')]], function () { - // -}); - -Route::group(['middleware' => [\Spatie\Permission\Middleware\PermissionMiddleware::using('publish articles|edit articles')]], function () { - // -}); - -Route::group(['middleware' => [\Spatie\Permission\Middleware\RoleOrPermissionMiddleware::using(['manager', 'edit articles'])]], function () { - // -}); +Route::group(['middleware' => [\Spatie\Permission\Middleware\RoleMiddleware::using('manager')]], function () { ... }); +Route::group(['middleware' => [\Spatie\Permission\Middleware\PermissionMiddleware::using('publish articles|edit articles')]], function () { ... }); +Route::group(['middleware' => [\Spatie\Permission\Middleware\RoleOrPermissionMiddleware::using(['manager', 'edit articles'])]], function () { ... }); ``` + From 5d5049b8ec6ef4f46b6bbce4dc7f85aaef950f00 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Wed, 13 Mar 2024 16:14:16 -0400 Subject: [PATCH 465/648] Shorten title --- docs/basic-usage/middleware.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/basic-usage/middleware.md b/docs/basic-usage/middleware.md index b555683b4..5527a6540 100644 --- a/docs/basic-usage/middleware.md +++ b/docs/basic-usage/middleware.md @@ -1,5 +1,5 @@ --- -title: Using a Middleware +title: Middleware weight: 11 --- From cfbcb0e967df35ec0c6d03d5040536e3e57134d5 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Wed, 13 Mar 2024 16:14:46 -0400 Subject: [PATCH 466/648] Shorten title --- docs/basic-usage/artisan.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/basic-usage/artisan.md b/docs/basic-usage/artisan.md index 2091560de..0e259da0c 100644 --- a/docs/basic-usage/artisan.md +++ b/docs/basic-usage/artisan.md @@ -1,5 +1,5 @@ --- -title: Using artisan commands +title: Artisan Commands weight: 10 --- From 48769a23893ab9271f6d5d2eb6e648ec2123db77 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 19 Mar 2024 12:20:23 -0400 Subject: [PATCH 467/648] Explain canany --- docs/basic-usage/blade-directives.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/basic-usage/blade-directives.md b/docs/basic-usage/blade-directives.md index 3d95f8ec9..4ee4b7b38 100644 --- a/docs/basic-usage/blade-directives.md +++ b/docs/basic-usage/blade-directives.md @@ -24,6 +24,8 @@ When using a permission-name associated with permissions created in this package You can also use `@haspermission('permission-name')` or `@haspermission('permission-name', 'guard_name')` in similar fashion. With corresponding `@endhaspermission`. +There is no `@hasanypermission` directive: use `@canany` instead. + ## Roles As discussed in the Best Practices section of the docs, **it is strongly recommended to always use permission directives**, instead of role directives. From 0c234d6d08dba3d5d429d3a916ed74c587410677 Mon Sep 17 00:00:00 2001 From: Vytautas Smilingis Date: Thu, 21 Mar 2024 16:32:00 +0100 Subject: [PATCH 468/648] Update HasPermissions::collectPermissions() docblock (#2641) * Update HasPermissions::collectPermissions() docblock * Update HasRoles::collectRoles() docblock --------- Co-authored-by: Chris Brown --- src/Traits/HasPermissions.php | 2 +- src/Traits/HasRoles.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index 1582e36d6..0bc409543 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -352,7 +352,7 @@ public function getAllPermissions(): Collection } /** - * Returns permissions ids as array keys + * Returns array of permissions ids * * @param string|int|array|Permission|Collection|\BackedEnum $permissions */ diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index 97f87ba9a..d711a396c 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -111,7 +111,7 @@ public function scopeWithoutRole(Builder $query, $roles, $guard = null): Builder } /** - * Returns roles ids as array keys + * Returns array of role ids * * @param string|int|array|Role|Collection|\BackedEnum $roles */ From 170279843e708ce924a98445932b01c25f2864eb Mon Sep 17 00:00:00 2001 From: spman Date: Fri, 22 Mar 2024 00:19:35 +0800 Subject: [PATCH 469/648] Update role-permissions.md (#2631) * Revert #1122 --------- Co-authored-by: Chris Brown --- docs/basic-usage/role-permissions.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/docs/basic-usage/role-permissions.md b/docs/basic-usage/role-permissions.md index ddfd0ce57..20eeedd5e 100644 --- a/docs/basic-usage/role-permissions.md +++ b/docs/basic-usage/role-permissions.md @@ -172,8 +172,3 @@ All these responses are collections of `Spatie\Permission\Models\Permission` obj If we follow the previous example, the first response will be a collection with the `delete article` permission and the second will be a collection with the `edit article` permission and the third will contain both. - - -## NOTE about using permission names in policies - -When calling `authorize()` for a policy method, if you have a permission named the same as one of those policy methods, your permission "name" will take precedence and not fire the policy. For this reason it may be wise to avoid naming your permissions the same as the methods in your policy. While you can define your own method names, you can read more about the defaults Laravel offers in Laravel's documentation at [Writing Policies](https://laravel.com/docs/authorization#writing-policies). From 7408b1c3b62e15419edf777549a804708d692900 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Mar 2024 10:26:46 -0400 Subject: [PATCH 470/648] Bump dependabot/fetch-metadata from 1 to 2 (#2642) Bumps [dependabot/fetch-metadata](https://github.com/dependabot/fetch-metadata) from 1 to 2. - [Release notes](https://github.com/dependabot/fetch-metadata/releases) - [Commits](https://github.com/dependabot/fetch-metadata/compare/v1...v2) --- updated-dependencies: - dependency-name: dependabot/fetch-metadata dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/dependabot-auto-merge.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml index 144a946ce..c8ac6efa1 100644 --- a/.github/workflows/dependabot-auto-merge.yml +++ b/.github/workflows/dependabot-auto-merge.yml @@ -13,7 +13,7 @@ jobs: - name: Dependabot metadata id: metadata - uses: dependabot/fetch-metadata@v1 + uses: dependabot/fetch-metadata@v2 with: github-token: "${{ secrets.GITHUB_TOKEN }}" compat-lookup: true From 44bbe88b74b43f9888629326c6d53a24ac65466a Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 2 Apr 2024 17:31:30 -0400 Subject: [PATCH 471/648] Note optional steps --- docs/basic-usage/new-app.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/basic-usage/new-app.md b/docs/basic-usage/new-app.md index 87fcbfc04..8ec16914d 100644 --- a/docs/basic-usage/new-app.md +++ b/docs/basic-usage/new-app.md @@ -23,11 +23,13 @@ laravel new mypermissionsdemo # (Choose SQLite) cd mypermissionsdemo + +# the following git commands are not needed if you Initialized a git repo while "laravel new" was running above: git init git add . git commit -m "Fresh Laravel Install" -# Environment +# These Environment steps are not needed if you already selected SQLite while "laravel new" was running above: cp -n .env.example .env sed -i '' 's/DB_CONNECTION=mysql/DB_CONNECTION=sqlite/' .env sed -i '' 's/DB_DATABASE=/#DB_DATABASE=/' .env @@ -71,8 +73,6 @@ class PermissionsDemoSeeder extends Seeder { /** * Create the initial roles and permissions. - * - * @return void */ public function run(): void { From 96d800ac460de71f82e8dab57e337131b325ddf5 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 2 Apr 2024 17:39:31 -0400 Subject: [PATCH 472/648] Add example for HasMiddleware interface in Laravel 11 Closes #2647 --- docs/basic-usage/middleware.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/basic-usage/middleware.md b/docs/basic-usage/middleware.md index 5527a6540..dfd147a3c 100644 --- a/docs/basic-usage/middleware.md +++ b/docs/basic-usage/middleware.md @@ -85,6 +85,18 @@ Route::group(['middleware' => ['permission:publish articles|edit articles,api']] ### Controllers +In Laravel 11, if your controller implements the `HasMiddleware` interface, you can register controller middleware using the `middleware()` method: + +```php +public static function middleware(): array +{ + return [ + 'role_or_permission:manager|edit articles', + ]; +} +``` + +Or, like in prior versions, you can register it in the constructor: ```php public function __construct() { From 8ed61aa42031f3cf06b5f68569af5e2e1e481284 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 2 Apr 2024 17:54:30 -0400 Subject: [PATCH 473/648] Clarify older versions --- docs/basic-usage/middleware.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/basic-usage/middleware.md b/docs/basic-usage/middleware.md index dfd147a3c..9f8aa2690 100644 --- a/docs/basic-usage/middleware.md +++ b/docs/basic-usage/middleware.md @@ -96,7 +96,7 @@ public static function middleware(): array } ``` -Or, like in prior versions, you can register it in the constructor: +In Laravel 10 and older, you can register it in the constructor: ```php public function __construct() { From d26563e7aed0cbe7ad983a4416425da922c0f209 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Wed, 3 Apr 2024 03:14:35 -0400 Subject: [PATCH 474/648] Add another example --- docs/basic-usage/middleware.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/basic-usage/middleware.md b/docs/basic-usage/middleware.md index 9f8aa2690..89115b139 100644 --- a/docs/basic-usage/middleware.md +++ b/docs/basic-usage/middleware.md @@ -85,13 +85,14 @@ Route::group(['middleware' => ['permission:publish articles|edit articles,api']] ### Controllers -In Laravel 11, if your controller implements the `HasMiddleware` interface, you can register controller middleware using the `middleware()` method: +In Laravel 11, if your controller implements the `HasMiddleware` interface, you can register [controller middleware](https://laravel.com/docs/11.x/controllers#controller-middleware) using the `middleware()` method: ```php public static function middleware(): array { return [ 'role_or_permission:manager|edit articles', + new Middleware('role:author', only: ['index']), ]; } ``` From 536d41d0c82e8fdbeeb2917ef9225b8059d1d96b Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Thu, 4 Apr 2024 00:12:47 -0400 Subject: [PATCH 475/648] Added more Laravel 11 examples --- docs/basic-usage/middleware.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/basic-usage/middleware.md b/docs/basic-usage/middleware.md index 89115b139..259e9214f 100644 --- a/docs/basic-usage/middleware.md +++ b/docs/basic-usage/middleware.md @@ -91,8 +91,11 @@ In Laravel 11, if your controller implements the `HasMiddleware` interface, you public static function middleware(): array { return [ + // examples with aliases, pipe-separated names, guards, etc: 'role_or_permission:manager|edit articles', new Middleware('role:author', only: ['index']), + new Middleware(\Spatie\Permission\Middleware\RoleMiddleware::using('manager'), except:['show']), + new Middleware(\Spatie\Permission\Middleware\PermissionMiddleware::using('delete records,api'), only:['destroy']), ]; } ``` @@ -101,8 +104,8 @@ In Laravel 10 and older, you can register it in the constructor: ```php public function __construct() { + // examples: $this->middleware(['role:manager','permission:publish articles|edit articles']); - // or $this->middleware(['role_or_permission:manager|edit articles']); // or with specific guard $this->middleware(['role_or_permission:manager|edit articles,api']); From a229ad5aa211b809839f3147f0f32ec71b5fef82 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Thu, 18 Apr 2024 13:56:57 -0500 Subject: [PATCH 476/648] Fix wrong octane event listener (#2656) --- src/PermissionServiceProvider.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PermissionServiceProvider.php b/src/PermissionServiceProvider.php index a23c1eddd..b3a77308c 100644 --- a/src/PermissionServiceProvider.php +++ b/src/PermissionServiceProvider.php @@ -96,7 +96,7 @@ protected function registerOctaneListener(): void $dispatcher = $this->app[Dispatcher::class]; // @phpstan-ignore-next-line - $dispatcher->listen(function (\Laravel\Octane\Events\OperationTerminated $event) { + $dispatcher->listen(function (\Laravel\Octane\Contracts\OperationTerminated $event) { // @phpstan-ignore-next-line $event->sandbox->make(PermissionRegistrar::class)->setPermissionsTeamId(null); }); From 1890d8b49516d84accb8ad8595f596612e135ce3 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Thu, 18 Apr 2024 17:26:05 -0400 Subject: [PATCH 477/648] [Docs] Fix syntax in docs for UUID migration changes --- docs/advanced-usage/uuid.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/advanced-usage/uuid.md b/docs/advanced-usage/uuid.md index c25f5b8d3..d2740f291 100644 --- a/docs/advanced-usage/uuid.md +++ b/docs/advanced-usage/uuid.md @@ -40,11 +40,11 @@ If you also want the roles and permissions to use a UUID for their `id` value, t }); Schema::create($tableNames['model_has_permissions'], function (Blueprint $table) use ($tableNames, $columnNames) { -- $table->unsignedBigInteger(PermissionRegistrar::$pivotPermission); -+ $table->uuid(PermissionRegistrar::$pivotPermission); +- $table->unsignedBigInteger($pivotPermission); ++ $table->uuid($pivotPermission); $table->string('model_type'); //... - $table->foreign(PermissionRegistrar::$pivotPermission) + $table->foreign($pivotPermission) - ->references('id') // permission id + ->references('uuid') // permission id ->on($tableNames['permissions']) @@ -52,28 +52,28 @@ If you also want the roles and permissions to use a UUID for their `id` value, t //... Schema::create($tableNames['model_has_roles'], function (Blueprint $table) use ($tableNames, $columnNames) { -- $table->unsignedBigInteger(PermissionRegistrar::$pivotRole); -+ $table->uuid(PermissionRegistrar::$pivotRole); +- $table->unsignedBigInteger($pivotRole); ++ $table->uuid($pivotRole); //... - $table->foreign(PermissionRegistrar::$pivotRole) + $table->foreign($pivotRole) - ->references('id') // role id + ->references('uuid') // role id ->on($tableNames['roles']) ->onDelete('cascade');//... Schema::create($tableNames['role_has_permissions'], function (Blueprint $table) use ($tableNames) { -- $table->unsignedBigInteger(PermissionRegistrar::$pivotPermission); -- $table->unsignedBigInteger(PermissionRegistrar::$pivotRole); -+ $table->uuid(PermissionRegistrar::$pivotPermission); -+ $table->uuid(PermissionRegistrar::$pivotRole); +- $table->unsignedBigInteger($pivotPermission); +- $table->unsignedBigInteger($pivotRole); ++ $table->uuid($pivotPermission); ++ $table->uuid($pivotRole); - $table->foreign(PermissionRegistrar::$pivotPermission) + $table->foreign($pivotPermission) - ->references('id') // permission id + ->references('uuid') // permission id ->on($tableNames['permissions']) ->onDelete('cascade'); - $table->foreign(PermissionRegistrar::$pivotRole) + $table->foreign($pivotRole) - ->references('id') // role id + ->references('uuid') // role id ->on($tableNames['roles']) From d191adae3c8e99251ffd979bd0e7c28f0bbe1597 Mon Sep 17 00:00:00 2001 From: drbyte Date: Thu, 18 Apr 2024 21:54:24 +0000 Subject: [PATCH 478/648] Update CHANGELOG --- CHANGELOG.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a66d5ea07..9c36389fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,30 @@ All notable changes to `laravel-permission` will be documented in this file +## 6.5.0 - 2024-04-18 + +### What's Changed + +* Octane: Fix wrong event listener by @erikn69 in https://github.com/spatie/laravel-permission/pull/2656 +* Teams: Add nullable team_id by @Androlax2 in https://github.com/spatie/laravel-permission/pull/2607 +* Blade: simplify the definition of multiple Blade "if" directives by @alissn in https://github.com/spatie/laravel-permission/pull/2628 +* DocBlocks: Update HasPermissions::collectPermissions() docblock by @Plytas in https://github.com/spatie/laravel-permission/pull/2641 + +#### Internals + +* Update role-permissions.md by @killjin in https://github.com/spatie/laravel-permission/pull/2631 +* Bump ramsey/composer-install from 2 to 3 by @dependabot in https://github.com/spatie/laravel-permission/pull/2630 +* Bump dependabot/fetch-metadata from 1 to 2 by @dependabot in https://github.com/spatie/laravel-permission/pull/2642 + +### New Contributors + +* @alissn made their first contribution in https://github.com/spatie/laravel-permission/pull/2628 +* @Androlax2 made their first contribution in https://github.com/spatie/laravel-permission/pull/2607 +* @Plytas made their first contribution in https://github.com/spatie/laravel-permission/pull/2641 +* @killjin made their first contribution in https://github.com/spatie/laravel-permission/pull/2631 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/6.4.0...6.5.0 + ## 6.4.0 - 2024-02-28 * Laravel 11 Support @@ -766,6 +790,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` 1. Also this is a good time to point out that now with v2.25.0 and v2.26.0 most permission-cache-reset scenarios may no longer be needed in your app, so it's worth reviewing those cases, as you may gain some app speed improvement by removing unnecessary cache resets. @@ -830,6 +855,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` ## 2.19.1 - 2018-09-14 From 13ff85b1fe61e8dcf7acff839a8cc7cc79685933 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Thu, 18 Apr 2024 18:33:13 -0400 Subject: [PATCH 479/648] Register about details (#2584) * Register package information in About command output * Fix styling --- src/PermissionServiceProvider.php | 27 +++++++++++++++++++++++++++ tests/CommandTest.php | 18 ++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/src/PermissionServiceProvider.php b/src/PermissionServiceProvider.php index b3a77308c..1273b8c1c 100644 --- a/src/PermissionServiceProvider.php +++ b/src/PermissionServiceProvider.php @@ -2,10 +2,12 @@ namespace Spatie\Permission; +use Composer\InstalledVersions; use Illuminate\Contracts\Auth\Access\Gate; use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Contracts\Foundation\Application; use Illuminate\Filesystem\Filesystem; +use Illuminate\Foundation\Console\AboutCommand; use Illuminate\Routing\Route; use Illuminate\Support\Arr; use Illuminate\Support\Collection; @@ -38,6 +40,8 @@ public function boot() }); $this->app->singleton(PermissionRegistrar::class); + + $this->registerAbout(); } public function register() @@ -169,4 +173,27 @@ protected function getMigrationFileName(string $migrationFileName): string ->push($this->app->databasePath()."/migrations/{$timestamp}_{$migrationFileName}") ->first(); } + + protected function registerAbout(): void + { + if (! class_exists(InstalledVersions::class) || ! class_exists(AboutCommand::class)) { + return; + } + + $features = [ + 'Teams' => 'teams', + 'Wildcard-Permissions' => 'enable_wildcard_permission', + 'Octane-Listener' => 'register_octane_reset_listener', + 'Passport' => 'use_passport_client_credentials', + ]; + + AboutCommand::add('Spatie Permissions', fn () => [ + 'Features Enabled' => collect($features) + ->filter(fn (string $feature, string $name): bool => $this->app['config']->get("permission.{$feature}")) + ->keys() + ->whenEmpty(fn (Collection $collection) => $collection->push('Default')) + ->join(', '), + 'Version' => InstalledVersions::getPrettyVersion('spatie/laravel-permission'), + ]); + } } diff --git a/tests/CommandTest.php b/tests/CommandTest.php index 923cad4e0..d4ee4bd18 100644 --- a/tests/CommandTest.php +++ b/tests/CommandTest.php @@ -184,4 +184,22 @@ public function it_can_show_roles_by_teams() $this->assertRegExp('/\|\s+\|\s+testRole\s+\|\s+testRole_2\s+\|\s+testRole_Team\s+\|\s+testRole_Team\s+\|/', $output); } } + + /** @test */ + public function it_can_respond_to_about_command() + { + config()->set('permission.teams', true); + app(\Spatie\Permission\PermissionRegistrar::class)->initializeCache(); + + Artisan::call('about'); + + $output = Artisan::output(); + + $pattern = '/Spatie Permissions[ .\n]*Features Enabled[ .]*Teams[ .\n]*Version/'; + if (method_exists($this, 'assertMatchesRegularExpression')) { + $this->assertMatchesRegularExpression($pattern, $output); + } else { // phpUnit 9/8 + $this->assertRegExp($pattern, $output); + } + } } From 63b29ad227b53f1a7f91909dd62d9f9616f3b172 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Thu, 18 Apr 2024 22:49:29 -0400 Subject: [PATCH 480/648] Fix "about" tests --- src/PermissionServiceProvider.php | 7 ++++-- tests/CommandTest.php | 36 +++++++++++++++++++++++++++++-- tests/TestCase.php | 10 +++++++++ 3 files changed, 49 insertions(+), 4 deletions(-) diff --git a/src/PermissionServiceProvider.php b/src/PermissionServiceProvider.php index 1273b8c1c..f6dfb8909 100644 --- a/src/PermissionServiceProvider.php +++ b/src/PermissionServiceProvider.php @@ -180,6 +180,7 @@ protected function registerAbout(): void return; } + // array format: 'Display Text' => 'boolean-config-key name' $features = [ 'Teams' => 'teams', 'Wildcard-Permissions' => 'enable_wildcard_permission', @@ -187,9 +188,11 @@ protected function registerAbout(): void 'Passport' => 'use_passport_client_credentials', ]; - AboutCommand::add('Spatie Permissions', fn () => [ + $config = $this->app['config']; + + AboutCommand::add('Spatie Permissions', static fn () => [ 'Features Enabled' => collect($features) - ->filter(fn (string $feature, string $name): bool => $this->app['config']->get("permission.{$feature}")) + ->filter(fn(string $feature, string $name): bool => $config->get("permission.{$feature}")) ->keys() ->whenEmpty(fn (Collection $collection) => $collection->push('Default')) ->join(', '), diff --git a/tests/CommandTest.php b/tests/CommandTest.php index d4ee4bd18..a5bd5b558 100644 --- a/tests/CommandTest.php +++ b/tests/CommandTest.php @@ -2,6 +2,8 @@ namespace Spatie\Permission\Tests; +use Composer\InstalledVersions; +use Illuminate\Foundation\Console\AboutCommand; use Illuminate\Support\Facades\Artisan; use Spatie\Permission\Models\Permission; use Spatie\Permission\Models\Role; @@ -186,13 +188,43 @@ public function it_can_show_roles_by_teams() } /** @test */ - public function it_can_respond_to_about_command() + public function it_can_respond_to_about_command_with_default() { - config()->set('permission.teams', true); + if (! class_exists(InstalledVersions::class) || ! class_exists(AboutCommand::class)) { + $this->markTestSkipped(); + } + if (! method_exists(AboutCommand::class, 'flushState')) { + $this->markTestSkipped(); + } + app(\Spatie\Permission\PermissionRegistrar::class)->initializeCache(); Artisan::call('about'); + $output = Artisan::output(); + + $pattern = '/Spatie Permissions[ .\n]*Features Enabled[ .]*Default[ .\n]*Version/'; + if (method_exists($this, 'assertMatchesRegularExpression')) { + $this->assertMatchesRegularExpression($pattern, $output); + } else { // phpUnit 9/8 + $this->assertRegExp($pattern, $output); + } + } + + /** @test */ + public function it_can_respond_to_about_command_with_teams() + { + if (! class_exists(InstalledVersions::class) || ! class_exists(AboutCommand::class)) { + $this->markTestSkipped(); + } + if (! method_exists(AboutCommand::class, 'flushState')) { + $this->markTestSkipped(); + } + app(\Spatie\Permission\PermissionRegistrar::class)->initializeCache(); + + config()->set('permission.teams', true); + + Artisan::call('about'); $output = Artisan::output(); $pattern = '/Spatie Permissions[ .\n]*Features Enabled[ .]*Teams[ .\n]*Version/'; diff --git a/tests/TestCase.php b/tests/TestCase.php index 24af65bf9..3a5532c8b 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -3,6 +3,7 @@ namespace Spatie\Permission\Tests; use Illuminate\Database\Schema\Blueprint; +use Illuminate\Foundation\Console\AboutCommand; use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Support\Facades\Cache; @@ -79,6 +80,15 @@ protected function setUp(): void $this->setUpRoutes(); } + protected function tearDown(): void + { + parent::tearDown(); + + if (method_exists(AboutCommand::class, 'flushState')) { + AboutCommand::flushState(); + } + } + /** * @param \Illuminate\Foundation\Application $app * @return array From 11c1c60cfd20225c7330219d952f8def47bb8378 Mon Sep 17 00:00:00 2001 From: drbyte Date: Fri, 19 Apr 2024 02:49:54 +0000 Subject: [PATCH 481/648] Fix styling --- src/PermissionServiceProvider.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PermissionServiceProvider.php b/src/PermissionServiceProvider.php index f6dfb8909..b57c20ff7 100644 --- a/src/PermissionServiceProvider.php +++ b/src/PermissionServiceProvider.php @@ -192,7 +192,7 @@ protected function registerAbout(): void AboutCommand::add('Spatie Permissions', static fn () => [ 'Features Enabled' => collect($features) - ->filter(fn(string $feature, string $name): bool => $config->get("permission.{$feature}")) + ->filter(fn (string $feature, string $name): bool => $config->get("permission.{$feature}")) ->keys() ->whenEmpty(fn (Collection $collection) => $collection->push('Default')) ->join(', '), From 2c5369e3fbbfb52fae382d82549faa86a4e2a698 Mon Sep 17 00:00:00 2001 From: Gajos Date: Fri, 19 Apr 2024 05:22:50 +0200 Subject: [PATCH 482/648] feat(Roles): Support for casting role names to enums (#2616) * feat(Roles): Support for casting role names to enums --- src/Traits/HasRoles.php | 34 ++++++++++++++------ tests/HasRolesTest.php | 26 +++++++++++++-- tests/TestModels/Role.php | 14 ++++++++ tests/TestModels/TestRolePermissionsEnum.php | 2 ++ 4 files changed, 64 insertions(+), 12 deletions(-) diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index d711a396c..c7a4b8010 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -228,6 +228,16 @@ public function hasRole($roles, ?string $guard = null): bool if ($roles instanceof \BackedEnum) { $roles = $roles->value; + + return $this->roles + ->when($guard, fn ($q) => $q->where('guard_name', $guard)) + ->contains(function ($role) use ($roles) { + if ($role->name instanceof \BackedEnum) { + return $role->name->value == $roles; + } + + return $role->name == $roles; + }); } if (is_int($roles) || PermissionRegistrar::isUid($roles)) { @@ -295,9 +305,7 @@ public function hasAllRoles($roles, ?string $guard = null): bool } if (is_string($roles)) { - return $guard - ? $this->roles->where('guard_name', $guard)->contains('name', $roles) - : $this->roles->contains('name', $roles); + return $this->hasRole($roles, $guard); } if ($roles instanceof Role) { @@ -312,17 +320,25 @@ public function hasAllRoles($roles, ?string $guard = null): bool return $role instanceof Role ? $role->name : $role; }); - return $roles->intersect( - $guard - ? $this->roles->where('guard_name', $guard)->pluck('name') - : $this->getRoleNames() - ) == $roles; + $roleNames = $guard + ? $this->roles->where('guard_name', $guard)->pluck('name') + : $this->getRoleNames(); + + $roleNames = $roleNames->transform(function ($roleName) { + if ($roleName instanceof \BackedEnum) { + return $roleName->value; + } + + return $roleName; + }); + + return $roles->intersect($roleNames) == $roles; } /** * Determine if the model has exactly all of the given role(s). * - * @param string|array|Role|Collection $roles + * @param string|array|Role|Collection|\BackedEnum $roles */ public function hasExactRoles($roles, ?string $guard = null): bool { diff --git a/tests/HasRolesTest.php b/tests/HasRolesTest.php index 7b0e54e3c..a6ebef1d8 100644 --- a/tests/HasRolesTest.php +++ b/tests/HasRolesTest.php @@ -47,23 +47,43 @@ public function it_can_assign_and_remove_a_role_using_enums() { $enum1 = TestModels\TestRolePermissionsEnum::USERMANAGER; $enum2 = TestModels\TestRolePermissionsEnum::WRITER; + $enum3 = TestModels\TestRolePermissionsEnum::CASTED_ENUM_1; + $enum4 = TestModels\TestRolePermissionsEnum::CASTED_ENUM_2; app(Role::class)->findOrCreate($enum1->value, 'web'); app(Role::class)->findOrCreate($enum2->value, 'web'); + app(Role::class)->findOrCreate($enum3->value, 'web'); + app(Role::class)->findOrCreate($enum4->value, 'web'); $this->assertFalse($this->testUser->hasRole($enum1)); $this->assertFalse($this->testUser->hasRole($enum2)); + $this->assertFalse($this->testUser->hasRole($enum3)); + $this->assertFalse($this->testUser->hasRole($enum4)); + $this->assertFalse($this->testUser->hasRole('user-manager')); + $this->assertFalse($this->testUser->hasRole('writer')); + $this->assertFalse($this->testUser->hasRole('casted_enum-1')); + $this->assertFalse($this->testUser->hasRole('casted_enum-2')); $this->testUser->assignRole($enum1); $this->testUser->assignRole($enum2); + $this->testUser->assignRole($enum3); + $this->testUser->assignRole($enum4); $this->assertTrue($this->testUser->hasRole($enum1)); $this->assertTrue($this->testUser->hasRole($enum2)); + $this->assertTrue($this->testUser->hasRole($enum3)); + $this->assertTrue($this->testUser->hasRole($enum4)); - $this->assertTrue($this->testUser->hasAllRoles([$enum1, $enum2])); - $this->assertFalse($this->testUser->hasAllRoles([$enum1, $enum2, 'not exist'])); + $this->assertTrue($this->testUser->hasRole([$enum1, 'writer'])); + $this->assertTrue($this->testUser->hasRole([$enum3, 'casted_enum-2'])); - $this->assertTrue($this->testUser->hasExactRoles([$enum2, $enum1])); + $this->assertTrue($this->testUser->hasAllRoles([$enum1, $enum2, $enum3, $enum4])); + $this->assertTrue($this->testUser->hasAllRoles(['user-manager', 'writer', 'casted_enum-1', 'casted_enum-2'])); + $this->assertFalse($this->testUser->hasAllRoles([$enum1, $enum2, $enum3, $enum4, 'not exist'])); + $this->assertFalse($this->testUser->hasAllRoles(['user-manager', 'writer', 'casted_enum-1', 'casted_enum-2', 'not exist'])); + + $this->assertTrue($this->testUser->hasExactRoles([$enum4, $enum3, $enum2, $enum1])); + $this->assertTrue($this->testUser->hasExactRoles(['user-manager', 'writer', 'casted_enum-1', 'casted_enum-2'])); $this->testUser->removeRole($enum1); diff --git a/tests/TestModels/Role.php b/tests/TestModels/Role.php index e3b4d77ab..5bd2c55e0 100644 --- a/tests/TestModels/Role.php +++ b/tests/TestModels/Role.php @@ -17,6 +17,20 @@ class Role extends \Spatie\Permission\Models\Role const HIERARCHY_TABLE = 'roles_hierarchy'; + /** + * @return string|\BackedEnum + */ + public function getNameAttribute() + { + $name = $this->attributes['name']; + + if (str_contains($name, 'casted_enum')) { + return TestRolePermissionsEnum::from($name); + } + + return $name; + } + /** * @return BelongsToMany */ diff --git a/tests/TestModels/TestRolePermissionsEnum.php b/tests/TestModels/TestRolePermissionsEnum.php index 858b7f937..0f2badd42 100644 --- a/tests/TestModels/TestRolePermissionsEnum.php +++ b/tests/TestModels/TestRolePermissionsEnum.php @@ -28,6 +28,8 @@ enum TestRolePermissionsEnum: string case EDITOR = 'editor'; case USERMANAGER = 'user-manager'; case ADMIN = 'administrator'; + case CASTED_ENUM_1 = 'casted_enum-1'; + case CASTED_ENUM_2 = 'casted_enum-2'; case VIEWARTICLES = 'view articles'; case EDITARTICLES = 'edit articles'; From 96c276117a91951d851562b29e2767275c41c3de Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Thu, 18 Apr 2024 23:29:11 -0400 Subject: [PATCH 483/648] Fix permission:show uuid error #2581 (#2582) Use `getKeyName()` instead of `'id'` Fixes #2581 --- src/Commands/Show.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Commands/Show.php b/src/Commands/Show.php index e20a371ed..2cbfb04f5 100644 --- a/src/Commands/Show.php +++ b/src/Commands/Show.php @@ -40,12 +40,12 @@ public function handle() ->when($teamsEnabled, fn ($q) => $q->orderBy($team_key)) ->orderBy('name')->get()->mapWithKeys(fn ($role) => [ $role->name.'_'.($teamsEnabled ? ($role->$team_key ?: '') : '') => [ - 'permissions' => $role->permissions->pluck('id'), + 'permissions' => $role->permissions->pluck($permissionClass->getKeyName()), $team_key => $teamsEnabled ? $role->$team_key : null, ], ]); - $permissions = $permissionClass::whereGuardName($guard)->orderBy('name')->pluck('name', 'id'); + $permissions = $permissionClass::whereGuardName($guard)->orderBy('name')->pluck('name', $permissionClass->getKeyName()); $body = $permissions->map(fn ($permission, $id) => $roles->map( fn (array $role_data) => $role_data['permissions']->contains($id) ? ' ✔' : ' ·' From 7a39f044b682298025b85ce603bd0ee15837b416 Mon Sep 17 00:00:00 2001 From: Alexandre Batistella Bellas Date: Fri, 19 Apr 2024 00:47:07 -0300 Subject: [PATCH 484/648] feat: cover permission instance verification based on its own guard (#2608) --- src/Traits/HasPermissions.php | 1 + tests/HasPermissionsTest.php | 14 ++++++++++ tests/WildcardHasPermissionsTest.php | 41 ++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+) diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index 0bc409543..a71da3ad5 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -231,6 +231,7 @@ protected function hasWildcardPermission($permission, $guardName = null): bool } if ($permission instanceof Permission) { + $guardName = $permission->guard_name ?? $guardName; $permission = $permission->name; } diff --git a/tests/HasPermissionsTest.php b/tests/HasPermissionsTest.php index bc7b84f93..164ee3dc1 100644 --- a/tests/HasPermissionsTest.php +++ b/tests/HasPermissionsTest.php @@ -20,6 +20,20 @@ public function it_can_assign_a_permission_to_a_user() $this->assertTrue($this->testUser->hasPermissionTo($this->testUserPermission)); } + + /** @test */ + public function it_can_assign_a_permission_to_a_user_with_a_non_default_guard() + { + $testUserPermission = app(Permission::class)->create([ + 'name' => 'edit-articles', + 'guard_name' => 'api', + ]); + + $this->testUser->givePermissionTo($testUserPermission); + + $this->assertTrue($this->testUser->hasPermissionTo($testUserPermission)); + } + /** @test */ public function it_throws_an_exception_when_assigning_a_permission_that_does_not_exist() { diff --git a/tests/WildcardHasPermissionsTest.php b/tests/WildcardHasPermissionsTest.php index 873f35a96..8db36c63b 100644 --- a/tests/WildcardHasPermissionsTest.php +++ b/tests/WildcardHasPermissionsTest.php @@ -31,6 +31,47 @@ public function it_can_check_wildcard_permission() $this->assertFalse($user1->hasPermissionTo('projects.view')); } + /** @test */ + public function it_can_check_wildcard_permission_for_a_non_default_guard() + { + app('config')->set('permission.enable_wildcard_permission', true); + + $user1 = User::create(['email' => 'user1@test.com']); + + $permission1 = Permission::create(['name' => 'articles.edit,view,create', 'guard_name' => 'api']); + $permission2 = Permission::create(['name' => 'news.*', 'guard_name' => 'api']); + $permission3 = Permission::create(['name' => 'posts.*', 'guard_name' => 'api']); + + $user1->givePermissionTo([$permission1, $permission2, $permission3]); + + $this->assertTrue($user1->hasPermissionTo('posts.create', 'api')); + $this->assertTrue($user1->hasPermissionTo('posts.create.123', 'api')); + $this->assertTrue($user1->hasPermissionTo('posts.*', 'api')); + $this->assertTrue($user1->hasPermissionTo('articles.view', 'api')); + $this->assertFalse($user1->hasPermissionTo('projects.view', 'api')); + } + + /** @test */ + public function it_can_check_wildcard_permission_from_instance_without_explicit_guard_argument() + { + app('config')->set('permission.enable_wildcard_permission', true); + + $user1 = User::create(['email' => 'user1@test.com']); + + $permission2 = Permission::create(['name' => 'articles.view']); + $permission1 = Permission::create(['name' => 'articles.edit', 'guard_name' => 'api']); + $permission3 = Permission::create(['name' => 'news.*', 'guard_name' => 'api']); + $permission4 = Permission::create(['name' => 'posts.*', 'guard_name' => 'api']); + + $user1->givePermissionTo([$permission1, $permission2, $permission3]); + + $this->assertTrue($user1->hasPermissionTo($permission1)); + $this->assertTrue($user1->hasPermissionTo($permission2)); + $this->assertTrue($user1->hasPermissionTo($permission3)); + $this->assertFalse($user1->hasPermissionTo($permission4)); + $this->assertFalse($user1->hasPermissionTo('articles.edit')); + } + /** * @test * From 6c529ddb990be9de19c507ed8683eaa28f39fdfb Mon Sep 17 00:00:00 2001 From: drbyte Date: Fri, 19 Apr 2024 03:47:32 +0000 Subject: [PATCH 485/648] Fix styling --- tests/HasPermissionsTest.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/HasPermissionsTest.php b/tests/HasPermissionsTest.php index 164ee3dc1..ea156a28e 100644 --- a/tests/HasPermissionsTest.php +++ b/tests/HasPermissionsTest.php @@ -20,12 +20,11 @@ public function it_can_assign_a_permission_to_a_user() $this->assertTrue($this->testUser->hasPermissionTo($this->testUserPermission)); } - /** @test */ public function it_can_assign_a_permission_to_a_user_with_a_non_default_guard() { $testUserPermission = app(Permission::class)->create([ - 'name' => 'edit-articles', + 'name' => 'edit-articles', 'guard_name' => 'api', ]); From 4b786324445622a2a7b3db7e94aa8a54598c5e54 Mon Sep 17 00:00:00 2001 From: drbyte Date: Fri, 19 Apr 2024 03:55:36 +0000 Subject: [PATCH 486/648] Update CHANGELOG --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c36389fa..e84ccf216 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ All notable changes to `laravel-permission` will be documented in this file +## 6.6.0 - 2024-04-19 + +### What's Changed + +* Roles: Support for casting role names to enums by @gajosadrian in https://github.com/spatie/laravel-permission/pull/2616 +* Fix permission:show UUID error #2581 by @drbyte in https://github.com/spatie/laravel-permission/pull/2582 +* Cover WilcardPermission instance verification based on its own guard (Allow hasAllPermissions and hasAnyPermission to run on custom guard for WildcardPermission) by @AlexandreBellas in https://github.com/spatie/laravel-permission/pull/2608 +* Register Laravel "About" details by @drbyte in https://github.com/spatie/laravel-permission/pull/2584 + +### New Contributors + +* @gajosadrian made their first contribution in https://github.com/spatie/laravel-permission/pull/2616 +* @AlexandreBellas made their first contribution in https://github.com/spatie/laravel-permission/pull/2608 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/6.5.0...6.6.0 + ## 6.5.0 - 2024-04-18 ### What's Changed @@ -791,6 +807,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` 1. Also this is a good time to point out that now with v2.25.0 and v2.26.0 most permission-cache-reset scenarios may no longer be needed in your app, so it's worth reviewing those cases, as you may gain some app speed improvement by removing unnecessary cache resets. @@ -856,6 +873,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` ## 2.19.1 - 2018-09-14 From 17607924aa0aa89bc0153c2ce45ed7c55083367b Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 19 Apr 2024 08:35:28 -0400 Subject: [PATCH 487/648] Update Octane contract --- src/PermissionServiceProvider.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PermissionServiceProvider.php b/src/PermissionServiceProvider.php index b57c20ff7..1dc28f595 100644 --- a/src/PermissionServiceProvider.php +++ b/src/PermissionServiceProvider.php @@ -109,7 +109,7 @@ protected function registerOctaneListener(): void return; } // @phpstan-ignore-next-line - $dispatcher->listen(function (\Laravel\Octane\Events\OperationTerminated $event) { + $dispatcher->listen(function (\Laravel\Octane\Contracts\OperationTerminated $event) { // @phpstan-ignore-next-line $event->sandbox->make(PermissionRegistrar::class)->clearPermissionsCollection(); }); From da8390f45c1c7eaed72d3cef6d99146811ecf702 Mon Sep 17 00:00:00 2001 From: drbyte Date: Fri, 19 Apr 2024 12:39:03 +0000 Subject: [PATCH 488/648] Update CHANGELOG --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e84ccf216..319fe62d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to `laravel-permission` will be documented in this file +## 6.7.0 - 2024-04-19 + +### What's Changed + +- Fixed remaining Octane event contract. Update to #2656 in release `6.5.0` + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/6.6.0...6.7.0 + ## 6.6.0 - 2024-04-19 ### What's Changed @@ -808,6 +816,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` 1. Also this is a good time to point out that now with v2.25.0 and v2.26.0 most permission-cache-reset scenarios may no longer be needed in your app, so it's worth reviewing those cases, as you may gain some app speed improvement by removing unnecessary cache resets. @@ -874,6 +883,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` ## 2.19.1 - 2018-09-14 From ce67a8b0b08c9c54659984cdfd9ea7bac07920b3 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 19 Apr 2024 15:11:37 -0400 Subject: [PATCH 489/648] Update property typehint to match use Updates #2607 --- src/PermissionRegistrar.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index 3dcaac17b..383629906 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -36,8 +36,7 @@ class PermissionRegistrar public string $teamsKey; - /** @var int|string */ - protected $teamId = null; + protected string|int|null $teamId = null; public string $cacheKey; From 3f64c251759324c18ef2b1c42a01dda21b75f932 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 19 Apr 2024 21:36:03 -0400 Subject: [PATCH 490/648] Add PHP 8.4 --- .github/workflows/run-tests.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index cd56424e6..842b1ca98 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -9,7 +9,7 @@ jobs: strategy: fail-fast: false matrix: - php: [8.3, 8.2, 8.1, 8.0] + php: [8.4, 8.3, 8.2, 8.1, 8.0] laravel: ["^11.0", "^10.0", "^9.0", "^8.12"] dependency-version: [prefer-lowest, prefer-stable] include: @@ -30,6 +30,8 @@ jobs: php: 8.0 - laravel: "^8.12" php: 8.3 + - laravel: "^8.12" + php: 8.4 name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }} From da5c8bc931692fa0a1a0fbda1a5995c9379e4159 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 19 Apr 2024 21:38:27 -0400 Subject: [PATCH 491/648] Leave PHP 8.4 until dependencies support it --- .github/workflows/run-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 842b1ca98..38d97615f 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -9,7 +9,7 @@ jobs: strategy: fail-fast: false matrix: - php: [8.4, 8.3, 8.2, 8.1, 8.0] + php: [8.3, 8.2, 8.1, 8.0] laravel: ["^11.0", "^10.0", "^9.0", "^8.12"] dependency-version: [prefer-lowest, prefer-stable] include: From 51e3a7552896333ca5afd0ef44e006556cd52a54 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Thu, 2 May 2024 18:55:57 -0400 Subject: [PATCH 492/648] [Docs] Update Gate examples for Laravel 11 --- docs/basic-usage/super-admin.md | 27 ++++++------ docs/best-practices/using-policies.md | 62 ++++++++++++++++++++++++++- 2 files changed, 73 insertions(+), 16 deletions(-) diff --git a/docs/basic-usage/super-admin.md b/docs/basic-usage/super-admin.md index 35392772a..4eefee99d 100644 --- a/docs/basic-usage/super-admin.md +++ b/docs/basic-usage/super-admin.md @@ -11,21 +11,18 @@ Then you can implement the best-practice of primarily using permission-based con ## `Gate::before` If you want a "Super Admin" role to respond `true` to all permissions, without needing to assign all those permissions to a role, you can use [Laravel's `Gate::before()` method](https://laravel.com/docs/master/authorization#intercepting-gate-checks). For example: +In Laravel 11 this would go in the `boot()` method of `AppServiceProvider`: +In Laravel 10 and below it would go in the `boot()` method of `AuthServiceProvider.php`: ```php use Illuminate\Support\Facades\Gate; - -class AuthServiceProvider extends ServiceProvider +// ... +public function boot() { - public function boot() - { -//... - - // Implicitly grant "Super Admin" role all permissions - // This works in the app by using gate-related functions like auth()->user->can() and @can() - Gate::before(function ($user, $ability) { - return $user->hasRole('Super Admin') ? true : null; - }); - } + // Implicitly grant "Super Admin" role all permissions + // This works in the app by using gate-related functions like auth()->user->can() and @can() + Gate::before(function ($user, $ability) { + return $user->hasRole('Super Admin') ? true : null; + }); } ``` @@ -37,11 +34,11 @@ Jeffrey Way explains the concept of a super-admin (and a model owner, and model If you aren't using `Gate::before()` as described above, you could alternatively grant super-admin control by checking the role in individual Policy classes, using the `before()` method. -Here is an example from the [Laravel Documentation on Policy Filters](https://laravel.com/docs/master/authorization#policy-filters) +Here is an example from the [Laravel Documentation on Policy Filters](https://laravel.com/docs/master/authorization#policy-filters), where you can define `before()` in your Policy where needed: ```php -use App\Models\User; // could be any model - +use App\Models\User; // could be any Authorizable model + /** * Perform pre-authorization checks on the model. */ diff --git a/docs/best-practices/using-policies.md b/docs/best-practices/using-policies.md index 607f90e81..504042e09 100644 --- a/docs/best-practices/using-policies.md +++ b/docs/best-practices/using-policies.md @@ -9,4 +9,64 @@ Using Policies allows you to simplify things by abstracting your "control" rules Jeffrey Way explains the concept simply in the [Laravel 6 Authorization Filters](https://laracasts.com/series/laravel-6-from-scratch/episodes/51) and [policies](https://laracasts.com/series/laravel-6-from-scratch/episodes/63) videos and in other related lessons in that chapter. He also mentions how to set up a super-admin, both in a model policy and globally in your application. -You can find an example of implementing a model policy with this Laravel Permissions package in this demo app: [https://github.com/drbyte/spatie-permissions-demo/blob/master/app/Policies/PostPolicy.php](https://github.com/drbyte/spatie-permissions-demo/blob/master/app/Policies/PostPolicy.php) +Here's an example of a PostPolicy which could control access to Post model records: +```php +published) { + return true; + } + + // visitors cannot view unpublished items + if ($user === null) { + return false; + } + + // admin overrides published status + if ($user->can('view unpublished posts')) { + return true; + } + + // authors can view their own unpublished posts + return $user->id == $post->user_id; + } + + public function create(User $user) + { + return ($user->can('create posts')); + } + + public function update(User $user, Post $post) + { + if ($user->can('edit own posts')) { + return $user->id == $post->user_id; + } + + if ($user->can('edit all posts')) { + return true; + } + } + + public function delete(User $user, Post $post) + { + if ($user->can('delete own posts')) { + return $user->id == $post->user_id; + } + + if ($user->can('delete any post')) { + return true; + } + } +} +``` From 84f3138432903636273e3a79cd56eff381fbd312 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 10 May 2024 14:38:54 -0400 Subject: [PATCH 493/648] Update bug report template --- .github/ISSUE_TEMPLATE/1_Bug_report.yml | 55 +++++++++++++++++++++++++ .github/ISSUE_TEMPLATE/bug_report.md | 44 -------------------- 2 files changed, 55 insertions(+), 44 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/1_Bug_report.yml delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md diff --git a/.github/ISSUE_TEMPLATE/1_Bug_report.yml b/.github/ISSUE_TEMPLATE/1_Bug_report.yml new file mode 100644 index 000000000..96b2d6571 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/1_Bug_report.yml @@ -0,0 +1,55 @@ +name: Bug Report +description: "Report a reproducible bug." +body: + - type: markdown + attributes: + value: | + Before creating a new Bug Report, please check that there isn't already a similar issue on [the issue tracker](https://github.com/spatie/laravel-permission/issues) or in [the discussions](https://github.com/spatie/laravel-permission/discussions). + Also, **many issues/questions/problems are already answered** in the [documentation](https://spatie.be/docs/laravel-permission) already. **Please be sure to check the docs** because it will save you time! + + - type: textarea + attributes: + label: Description + description: A clear and concise description of what the bug is. + validations: + required: true + - type: textarea + attributes: + label: Steps To Reproduce + description: How do you trigger this bug? Please walk us through it step by step. + value: | + 1. + 2. + 3. + ... + validations: + required: true + - type: input + attributes: + label: Example Application + description: "Here is a link to my Github repo containing a minimal Laravel application which shows my problem:" + - type: markdown + attributes: + value: | + You can use `composer show` to get package version numbers: + - type: input + attributes: + label: "Version of spatie/laravel-permission package:" + validations: + required: true + - type: input + attributes: + label: "Version of laravel/framework package:" + validations: + required: true + - type: input + attributes: + label: "PHP version:" + validations: + required: true + - type: input + attributes: + label: "Database engine and version:" + - type: input + attributes: + label: "OS: Windows/Mac/Linux version:" diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 6b62c8f54..000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -name: Bug report -about: Report a reproducible bug -title: '' -labels: '' -assignees: '' - ---- - -**Before creating a new bug report** -Please check that there isn't already a similar issue on [the issue tracker](https://github.com/spatie/laravel-permission/issues) or in [the discussions](https://github.com/spatie/laravel-permission/discussions). - -**Describe the bug** -A clear and concise description of what the bug is. - -**Versions** -You can use `composer show` to get the version numbers of: -- spatie/laravel-permission package version: -- laravel/framework package - -PHP version: - -Database version: - - -**To Reproduce** -Steps to reproduce the behavior: - -Here is my example code and/or tests showing the problem in my app: - -**Example Application** -Here is a link to my Github repo containing a minimal Laravel application which shows my problem: - -**Expected behavior** -A clear and concise description of what you expected to happen. - - **Additional context** -Add any other context about the problem here. - -**Environment (please complete the following information, because it helps us investigate better):** - - OS: [e.g. macOS] - - Version [e.g. 22] - - From c99dca701d60e1a2d1f839c9bfd102b8098432fe Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Sat, 25 May 2024 21:54:23 -0400 Subject: [PATCH 494/648] Document workarounds for 1071 Specified key was too long; max key length is 1000 (or 767) bytes Fixes #2563 Ref #1689 --- .../create_permission_tables.php.stub | 10 +++++---- docs/prerequisites.md | 22 ++++++++++++++----- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/database/migrations/create_permission_tables.php.stub b/database/migrations/create_permission_tables.php.stub index b865d480c..9c7044b46 100644 --- a/database/migrations/create_permission_tables.php.stub +++ b/database/migrations/create_permission_tables.php.stub @@ -25,22 +25,24 @@ return new class extends Migration } Schema::create($tableNames['permissions'], function (Blueprint $table) { + //$table->engine('InnoDB'); $table->bigIncrements('id'); // permission id - $table->string('name'); // For MySQL 8.0 use string('name', 125); - $table->string('guard_name'); // For MySQL 8.0 use string('guard_name', 125); + $table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format) + $table->string('guard_name'); // For MyISAM use string('guard_name', 25); $table->timestamps(); $table->unique(['name', 'guard_name']); }); Schema::create($tableNames['roles'], function (Blueprint $table) use ($teams, $columnNames) { + //$table->engine('InnoDB'); $table->bigIncrements('id'); // role id if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing $table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable(); $table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index'); } - $table->string('name'); // For MySQL 8.0 use string('name', 125); - $table->string('guard_name'); // For MySQL 8.0 use string('guard_name', 125); + $table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format) + $table->string('guard_name'); // For MyISAM use string('guard_name', 25); $table->timestamps(); if ($teams || config('permission.testing')) { $table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']); diff --git a/docs/prerequisites.md b/docs/prerequisites.md index 88f3ad3e8..0b0cf300b 100644 --- a/docs/prerequisites.md +++ b/docs/prerequisites.md @@ -43,14 +43,26 @@ This package publishes a `config/permission.php` file. If you already have a fil ## Schema Limitation in MySQL -MySQL 8.0 limits index keys to 1000 characters. This package publishes a migration which combines multiple columns in single index. With `utf8mb4` the 4-bytes-per-character requirement of `mb4` means the max length of the columns in the hybrid index can only be `125` characters. +Potential error message: "1071 Specified key was too long; max key length is 1000 bytes" -Thus in your AppServiceProvider you will need to set `Schema::defaultStringLength(125)`. [See the Laravel Docs for instructions](https://laravel.com/docs/migrations#index-lengths-mysql-mariadb). +MySQL 8.0 limits index key lengths, which might be too short for some compound indexes used by this package. +This package publishes a migration which combines multiple columns in a single index. With `utf8mb4` the 4-bytes-per-character requirement of `mb4` means the total length of the columns in the hybrid index can only be `25%` of that maximum index length. -You may be able to bypass setting `defaultStringLength(125)` by editing the migration and specifying the `125` in 4 fields. There are 2 instances of this code snippet where you can explicitly set the `125`: +- MyISAM tables limit the index to 1000 characters (which is only 250 total chars in `utf8mb4`) +- InnoDB tables using ROW_FORMAT of 'Redundant' or 'Compact' limit the index to 767 characters (which is only 191 total chars in `utf8mb4`) +- InnoDB tables using ROW_FORMAT of 'Dynamic' or 'Compressed' have a 3072 character limit (which is 768 total chars in `utf8mb4`). + +Depending on your MySQL or MariaDB configuration, you may implement one of the following approaches: + +1. Ideally, configure the database to use InnoDB by default, and use ROW FORMAT of 'Dynamic' by default for all new tables. (See [MySQL](https://dev.mysql.com/doc/refman/8.0/en/innodb-limits.html) and [MariaDB](https://mariadb.com/kb/en/innodb-dynamic-row-format/) docs.) + +2. OR if your app doesn't require a longer default, in your AppServiceProvider you can set `Schema::defaultStringLength(125)`. [See the Laravel Docs for instructions](https://laravel.com/docs/migrations#index-lengths-mysql-mariadb). This will have Laravel set all strings to 125 characters by default. + +3. OR you could edit the migration and specify a shorter length for 4 fields. Then in your app be sure to manually impose validation limits on any form fields related to these fields. +There are 2 instances of this code snippet where you can explicitly set the length.: ```php - $table->string('name'); // For MySQL 8.0 use string('name', 125); - $table->string('guard_name'); // For MySQL 8.0 use string('guard_name', 125); + $table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format) + $table->string('guard_name'); // For MyISAM use string('guard_name', 25); ``` ## Note for apps using UUIDs/ULIDs/GUIDs From 650d7e9c82f39043b121961e18021439be1892ee Mon Sep 17 00:00:00 2001 From: erikn69 Date: Fri, 21 Jun 2024 15:13:11 -0500 Subject: [PATCH 495/648] Fix can't save the same model twice (#2658) --- src/Traits/HasPermissions.php | 6 ++++-- src/Traits/HasRoles.php | 6 ++++-- tests/HasPermissionsTest.php | 1 + tests/HasRolesTest.php | 1 + 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index a71da3ad5..4e38f8b0c 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -401,14 +401,16 @@ public function givePermissionTo(...$permissions) $model->unsetRelation('permissions'); } else { $class = \get_class($model); + $saved = false; $class::saved( - function ($object) use ($permissions, $model, $teamPivot) { - if ($model->getKey() != $object->getKey()) { + function ($object) use ($permissions, $model, $teamPivot, &$saved) { + if ($saved || $model->getKey() != $object->getKey()) { return; } $model->permissions()->attach($permissions, $teamPivot); $model->unsetRelation('permissions'); + $saved = true; } ); } diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index c7a4b8010..88013e850 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -159,14 +159,16 @@ public function assignRole(...$roles) $model->unsetRelation('roles'); } else { $class = \get_class($model); + $saved = false; $class::saved( - function ($object) use ($roles, $model, $teamPivot) { - if ($model->getKey() != $object->getKey()) { + function ($object) use ($roles, $model, $teamPivot, &$saved) { + if ($saved || $model->getKey() != $object->getKey()) { return; } $model->roles()->attach($roles, $teamPivot); $model->unsetRelation('roles'); + $saved = true; } ); } diff --git a/tests/HasPermissionsTest.php b/tests/HasPermissionsTest.php index ea156a28e..b785f01d1 100644 --- a/tests/HasPermissionsTest.php +++ b/tests/HasPermissionsTest.php @@ -636,6 +636,7 @@ public function it_can_sync_permissions_to_a_model_that_is_not_persisted() $user = new User(['email' => 'test@user.com']); $user->syncPermissions('edit-articles'); $user->save(); + $user->save(); // test save same model twice $this->assertTrue($user->hasPermissionTo('edit-articles')); diff --git a/tests/HasRolesTest.php b/tests/HasRolesTest.php index a6ebef1d8..9cc13a7c3 100644 --- a/tests/HasRolesTest.php +++ b/tests/HasRolesTest.php @@ -341,6 +341,7 @@ public function it_will_sync_roles_to_a_model_that_is_not_persisted() $user = new User(['email' => 'test@user.com']); $user->syncRoles([$this->testUserRole]); $user->save(); + $user->save(); // test save same model twice $this->assertTrue($user->hasRole($this->testUserRole)); From 41977f3c553b92c91563e195ae34d2689066f6ce Mon Sep 17 00:00:00 2001 From: drbyte Date: Fri, 21 Jun 2024 20:13:32 +0000 Subject: [PATCH 496/648] Fix styling --- tests/HasPermissionsTest.php | 2 +- tests/HasRolesTest.php | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/HasPermissionsTest.php b/tests/HasPermissionsTest.php index b785f01d1..4c9528d0e 100644 --- a/tests/HasPermissionsTest.php +++ b/tests/HasPermissionsTest.php @@ -636,7 +636,7 @@ public function it_can_sync_permissions_to_a_model_that_is_not_persisted() $user = new User(['email' => 'test@user.com']); $user->syncPermissions('edit-articles'); $user->save(); - $user->save(); // test save same model twice + $user->save(); // test save same model twice $this->assertTrue($user->hasPermissionTo('edit-articles')); diff --git a/tests/HasRolesTest.php b/tests/HasRolesTest.php index 9cc13a7c3..93ee35749 100644 --- a/tests/HasRolesTest.php +++ b/tests/HasRolesTest.php @@ -341,7 +341,7 @@ public function it_will_sync_roles_to_a_model_that_is_not_persisted() $user = new User(['email' => 'test@user.com']); $user->syncRoles([$this->testUserRole]); $user->save(); - $user->save(); // test save same model twice + $user->save(); // test save same model twice $this->assertTrue($user->hasRole($this->testUserRole)); @@ -831,9 +831,7 @@ public function it_throws_an_exception_if_an_unsupported_type_is_passed_to_hasRo { $this->expectException(\TypeError::class); - $this->testUser->hasRole(new class - { - }); + $this->testUser->hasRole(new class {}); } /** @test */ From 3d248f82b0dea3463d1b25fc1fb5469d11635fff Mon Sep 17 00:00:00 2001 From: erikn69 Date: Fri, 21 Jun 2024 18:49:50 -0500 Subject: [PATCH 497/648] Fix phpstan (#2685) --- .github/workflows/phpstan.yml | 6 +++++- phpstan.neon.dist | 1 - src/Contracts/Permission.php | 1 + src/Contracts/Role.php | 1 + src/Traits/HasRoles.php | 10 ++++++---- 5 files changed, 13 insertions(+), 6 deletions(-) diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index 609d55a6d..75eb21d8d 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -5,6 +5,10 @@ on: paths: - '**.php' - 'phpstan.neon.dist' + pull_request: + paths: + - '**.php' + - 'phpstan.neon.dist' jobs: phpstan: @@ -16,7 +20,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: 8.1 + php-version: 8.3 coverage: none - name: Install composer dependencies diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 3a451a165..022878407 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -11,7 +11,6 @@ parameters: - database/migrations/add_teams_fields.php.stub tmpDir: build/phpstan checkOctaneCompatibility: true - checkMissingIterableValueType: false ignoreErrors: - '#Unsafe usage of new static#' diff --git a/src/Contracts/Permission.php b/src/Contracts/Permission.php index 111d2ed2e..f706dbb8d 100644 --- a/src/Contracts/Permission.php +++ b/src/Contracts/Permission.php @@ -10,6 +10,7 @@ * @property string|null $guard_name * * @mixin \Spatie\Permission\Models\Permission + * @phpstan-require-extends \Spatie\Permission\Models\Permission */ interface Permission { diff --git a/src/Contracts/Role.php b/src/Contracts/Role.php index d00201a2a..aae0fff59 100644 --- a/src/Contracts/Role.php +++ b/src/Contracts/Role.php @@ -10,6 +10,7 @@ * @property string|null $guard_name * * @mixin \Spatie\Permission\Models\Role + * @phpstan-require-extends \Spatie\Permission\Models\Role */ interface Role { diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index 88013e850..fa537725f 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -233,12 +233,14 @@ public function hasRole($roles, ?string $guard = null): bool return $this->roles ->when($guard, fn ($q) => $q->where('guard_name', $guard)) - ->contains(function ($role) use ($roles) { - if ($role->name instanceof \BackedEnum) { - return $role->name->value == $roles; + ->pluck('name') + ->contains(function ($name) use ($roles) { + /** @var string|\BackedEnum $name */ + if ($name instanceof \BackedEnum) { + return $name->value == $roles; } - return $role->name == $roles; + return $name == $roles; }); } From 5147997e92001bf6cc5cd207eac554bc8f16c02a Mon Sep 17 00:00:00 2001 From: drbyte Date: Fri, 21 Jun 2024 23:50:10 +0000 Subject: [PATCH 498/648] Fix styling --- src/Contracts/Permission.php | 1 + src/Contracts/Role.php | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Contracts/Permission.php b/src/Contracts/Permission.php index f706dbb8d..5446e501a 100644 --- a/src/Contracts/Permission.php +++ b/src/Contracts/Permission.php @@ -10,6 +10,7 @@ * @property string|null $guard_name * * @mixin \Spatie\Permission\Models\Permission + * * @phpstan-require-extends \Spatie\Permission\Models\Permission */ interface Permission diff --git a/src/Contracts/Role.php b/src/Contracts/Role.php index aae0fff59..7545b7582 100644 --- a/src/Contracts/Role.php +++ b/src/Contracts/Role.php @@ -10,6 +10,7 @@ * @property string|null $guard_name * * @mixin \Spatie\Permission\Models\Role + * * @phpstan-require-extends \Spatie\Permission\Models\Role */ interface Role From b34b5a3f7a5ebe2ce13de7fc84dd68a82d9b8a04 Mon Sep 17 00:00:00 2001 From: drbyte Date: Fri, 21 Jun 2024 23:52:01 +0000 Subject: [PATCH 499/648] Update CHANGELOG --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 319fe62d8..d08f1aa85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ All notable changes to `laravel-permission` will be documented in this file +## 6.8.0 - 2024-06-21 + +### What's Changed + +* Fix can't save the same model twice by @erikn69 in https://github.com/spatie/laravel-permission/pull/2658 +* Fix phpstan from #2616 by @erikn69 in https://github.com/spatie/laravel-permission/pull/2685 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/6.7.0...6.8.0 + ## 6.7.0 - 2024-04-19 ### What's Changed @@ -816,6 +825,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` 1. Also this is a good time to point out that now with v2.25.0 and v2.26.0 most permission-cache-reset scenarios may no longer be needed in your app, so it's worth reviewing those cases, as you may gain some app speed improvement by removing unnecessary cache resets. @@ -883,6 +893,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` ## 2.19.1 - 2018-09-14 From ee0817f0fdf8c7ad967e3725fe77a7e215e7babb Mon Sep 17 00:00:00 2001 From: Julian Gums Date: Sat, 22 Jun 2024 22:44:54 +0200 Subject: [PATCH 500/648] Use ->withPivot() for teamed relationships (#2679) * use ->withPivot for teamed relationships * add ->withPivot() method to permissions --- src/Traits/HasPermissions.php | 5 ++++- src/Traits/HasRoles.php | 8 +++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index 4e38f8b0c..2514458d8 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -89,7 +89,10 @@ public function permissions(): BelongsToMany return $relation; } - return $relation->wherePivot(app(PermissionRegistrar::class)->teamsKey, getPermissionsTeamId()); + $teamsKey = app(PermissionRegistrar::class)->teamsKey; + $relation->withPivot($teamsKey); + + return $relation->wherePivot($teamsKey, getPermissionsTeamId()); } /** diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index fa537725f..3e63a9a2b 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -58,10 +58,12 @@ public function roles(): BelongsToMany if (! app(PermissionRegistrar::class)->teams) { return $relation; } + + $teamsKey = app(PermissionRegistrar::class)->teamsKey; + $relation->withPivot($teamsKey); + $teamField = config('permission.table_names.roles').'.'.$teamsKey; - $teamField = config('permission.table_names.roles').'.'.app(PermissionRegistrar::class)->teamsKey; - - return $relation->wherePivot(app(PermissionRegistrar::class)->teamsKey, getPermissionsTeamId()) + return $relation->wherePivot($teamsKey, getPermissionsTeamId()) ->where(fn ($q) => $q->whereNull($teamField)->orWhere($teamField, getPermissionsTeamId())); } From 5232e754734e0e8829be01b0303f0c73029ac8a9 Mon Sep 17 00:00:00 2001 From: drbyte Date: Sat, 22 Jun 2024 20:45:16 +0000 Subject: [PATCH 501/648] Fix styling --- src/Traits/HasRoles.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index 3e63a9a2b..782ed20a1 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -58,7 +58,7 @@ public function roles(): BelongsToMany if (! app(PermissionRegistrar::class)->teams) { return $relation; } - + $teamsKey = app(PermissionRegistrar::class)->teamsKey; $relation->withPivot($teamsKey); $teamField = config('permission.table_names.roles').'.'.$teamsKey; From 854f87c0a751d5744a140f8a9162a6a6856b89a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20Sz=C3=A9pe?= Date: Sat, 22 Jun 2024 22:51:32 +0200 Subject: [PATCH 502/648] Fix typos in changelog (#2686) --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d08f1aa85..f294540a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,7 +25,7 @@ All notable changes to `laravel-permission` will be documented in this file * Roles: Support for casting role names to enums by @gajosadrian in https://github.com/spatie/laravel-permission/pull/2616 * Fix permission:show UUID error #2581 by @drbyte in https://github.com/spatie/laravel-permission/pull/2582 -* Cover WilcardPermission instance verification based on its own guard (Allow hasAllPermissions and hasAnyPermission to run on custom guard for WildcardPermission) by @AlexandreBellas in https://github.com/spatie/laravel-permission/pull/2608 +* Cover WildcardPermission instance verification based on its own guard (Allow hasAllPermissions and hasAnyPermission to run on custom guard for WildcardPermission) by @AlexandreBellas in https://github.com/spatie/laravel-permission/pull/2608 * Register Laravel "About" details by @drbyte in https://github.com/spatie/laravel-permission/pull/2584 ### New Contributors @@ -538,7 +538,7 @@ Just a maintenance release. ## 5.2.0 - 2021-10-28 -- [V5] Fix detaching on all teams intstead of only current #1888 by @erikn69 in https://github.com/spatie/laravel-permission/pull/1890 +- [V5] Fix detaching on all teams instead of only current #1888 by @erikn69 in https://github.com/spatie/laravel-permission/pull/1890 - [V5] Add uuid compatibility support on teams by @erikn69 in https://github.com/spatie/laravel-permission/pull/1857 - Adds setRoleClass method to PermissionRegistrar by @timschwartz in https://github.com/spatie/laravel-permission/pull/1867 - Load permissions for preventLazyLoading by @bahramsadin in https://github.com/spatie/laravel-permission/pull/1884 @@ -1205,7 +1205,7 @@ BEST NOT TO USE v2.7.7 if you've changed tablenames in the config file. ** this version does not work in Laravel 5.1, please upgrade to version 1.5.1 of this package -- allowed `givePermissonTo` to accept multiple permissions +- allowed `givePermissionTo` to accept multiple permissions - allowed `assignRole` to accept multiple roles - added `syncPermissions`-method - added `syncRoles`-method From 3ebab01df0e0c040f84838f47a73c17e00e836c7 Mon Sep 17 00:00:00 2001 From: Jeremy Angele <131715596+angelej@users.noreply.github.com> Date: Sat, 22 Jun 2024 23:17:29 +0200 Subject: [PATCH 503/648] Update multiple-guards.md (#2659) * Update multiple-guards.md It took me a while to figure out, that you have to add `protected array $guard_name = ['web', 'admin'];` in order to allow an user to use roles / permissions from different guards. Especially since it's not a requirement to specify the `$guard_name`. * Add array example of multiple guards on User model --------- Co-authored-by: Chris Brown --- docs/basic-usage/multiple-guards.md | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/docs/basic-usage/multiple-guards.md b/docs/basic-usage/multiple-guards.md index 9a1911906..d9360d6cf 100644 --- a/docs/basic-usage/multiple-guards.md +++ b/docs/basic-usage/multiple-guards.md @@ -3,21 +3,21 @@ title: Using multiple guards weight: 9 --- -When using the default Laravel auth configuration all of the core methods of this package will work out of the box, no extra configuration required. +When using the default Laravel auth configuration all of the core methods of this package will work out of the box, with no extra configuration required. -However, when using multiple guards they will act like namespaces for your permissions and roles. Meaning every guard has its own set of permissions and roles that can be assigned to their user model. +However, when using multiple guards they will act like namespaces for your permissions and roles: Every guard has its own set of permissions and roles that can be assigned to its user model. ## The Downside To Multiple Guards -Note that this package requires you to register a permission name for each guard you want to authenticate with. So, "edit-article" would have to be created multiple times for each guard your app uses. An exception will be thrown if you try to authenticate against a non-existing permission+guard combination. Same for roles. +Note that this package requires you to register a permission name (same for roles) for each guard you want to authenticate with. So, "edit-article" would have to be created multiple times for each guard your app uses. An exception will be thrown if you try to authenticate against a non-existing permission+guard combination. Same for roles. -> **Tip**: If your app uses only a single guard, but is not `web` (Laravel's default, which shows "first" in the auth config file) then change the order of your listed guards in your `config/auth.php` to list your primary guard as the default and as the first in the list of defined guards. While you're editing that file, best to remove any guards you don't use, too. +> **Tip**: If your app uses only a single guard, but it is not `web` (Laravel's default, which shows "first" in the auth config file) then change the order of your listed guards in your `config/auth.php` to list your primary guard as the default and as the first in the list of defined guards. While you're editing that file, it is best to remove any guards you don't use, too. > > OR you could use the suggestion below to force the use of a single guard: ## Forcing Use Of A Single Guard -If your app structure does NOT differentiate between guards when it comes to roles/permissions, (ie: if ALL your roles/permissions are the SAME for ALL guards), you can override the `getDefaultGuardName` function by adding it to your User model, and specifying your desired guard_name. Then you only need to create roles/permissions for that single guard_name, not duplicating them. The example here sets it to `web`, but use whatever your application's default is: +If your app structure does NOT differentiate between guards when it comes to roles/permissions, (ie: if ALL your roles/permissions are the SAME for ALL guards), you can override the `getDefaultGuardName` function by adding it to your User model, and specifying your desired `$guard_name`. Then you only need to create roles/permissions for that single `$guard_name`, not duplicating them. The example here sets it to `web`, but use whatever your application's default is: ```php protected function getDefaultGuardName(): string { return 'web'; } @@ -46,16 +46,25 @@ $user->hasPermissionTo('publish articles', 'admin'); ``` > **Note**: When determining whether a role/permission is valid on a given model, it checks against the first matching guard in this order (it does NOT check role/permission for EACH possibility, just the first match): -- first the guardName() method if it exists on the model; -- then the `$guard_name` property if it exists on the model; +- first the guardName() method if it exists on the model (may return a string or array); +- then the `$guard_name` property if it exists on the model (may return a string or array); - then the first-defined guard/provider combination in the `auth.guards` config array that matches the loaded model's guard; - then the `auth.defaults.guard` config (which is the user's guard if they are logged in, else the default in the file). ## Assigning permissions and roles to guard users -You can use the same core methods to assign permissions and roles to users; just make sure the `guard_name` on the permission or role matches the guard of the user, otherwise a `GuardDoesNotMatch` or `Role/PermissionDoesNotExist` exception will be thrown. +You can use the same core methods to assign permissions and roles to users; just make sure the `guard_name` on the permission or role matches the guard of the user, otherwise a `GuardDoesNotMatch` or `Role/PermissionDoesNotExist` exception will be thrown. +If your User is able to consume multiple roles or permissions from different guards; make sure the User class's `$guard_name` property or `guardName()` method returns all allowed guards as an array: + +```php + protected $guard_name = ['web', 'admin']; +```` +or +```php + public function guardName() { return ['web', 'admin']; } +```` ## Using blade directives with multiple guards From 59b966fbb201c8dfc97dda31547c90a175f75b02 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Sat, 22 Jun 2024 19:04:28 -0400 Subject: [PATCH 504/648] Update docblock on $role->hasPermissionTo() to include BackedEnum Co-authored-by: Sander Muller --- src/Contracts/Role.php | 2 +- src/Models/Role.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Contracts/Role.php b/src/Contracts/Role.php index 7545b7582..31a67a082 100644 --- a/src/Contracts/Role.php +++ b/src/Contracts/Role.php @@ -44,7 +44,7 @@ public static function findOrCreate(string $name, ?string $guardName): self; /** * Determine if the user may perform the given permission. * - * @param string|\Spatie\Permission\Contracts\Permission $permission + * @param string|int|\Spatie\Permission\Contracts\Permission|\BackedEnum $permission */ public function hasPermissionTo($permission, ?string $guardName): bool; } diff --git a/src/Models/Role.php b/src/Models/Role.php index 4b2e48ff4..951355a1f 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -172,7 +172,7 @@ protected static function findByParam(array $params = []): ?RoleContract /** * Determine if the role may perform the given permission. * - * @param string|int|Permission|\BackedEnum $permission + * @param string|int|\Spatie\Permission\Contracts\Permission|\BackedEnum $permission * * @throws PermissionDoesNotExist|GuardDoesNotMatch */ From fe973a58b44380d0e8620107259b7bda22f70408 Mon Sep 17 00:00:00 2001 From: drbyte Date: Sat, 22 Jun 2024 23:04:52 +0000 Subject: [PATCH 505/648] Fix styling --- src/Models/Role.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Models/Role.php b/src/Models/Role.php index 951355a1f..86d81d379 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -172,7 +172,7 @@ protected static function findByParam(array $params = []): ?RoleContract /** * Determine if the role may perform the given permission. * - * @param string|int|\Spatie\Permission\Contracts\Permission|\BackedEnum $permission + * @param string|int|\Spatie\Permission\Contracts\Permission|\BackedEnum $permission * * @throws PermissionDoesNotExist|GuardDoesNotMatch */ From 80a32a10f7439fe381a5684e78e72cadd8fbd170 Mon Sep 17 00:00:00 2001 From: drbyte Date: Sat, 22 Jun 2024 23:26:39 +0000 Subject: [PATCH 506/648] Update CHANGELOG --- CHANGELOG.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f294540a2..c3c7c41a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,23 @@ All notable changes to `laravel-permission` will be documented in this file +## 6.9.0 - 2024-06-22 + +### What's Changed + +* Use `->withPivot()` for teamed relationships (allows `getPivotColumns()`) by @juliangums in https://github.com/spatie/laravel-permission/pull/2679 +* Update docblock on `$role->hasPermissionTo()` to include `BackedEnum` by @drbyte co-authored by @SanderMuller +* [Docs] Clarify that `$guard_name` can be an array by @angelej in https://github.com/spatie/laravel-permission/pull/2659 +* Fix misc typos in changelog by @szepeviktor in https://github.com/spatie/laravel-permission/pull/2686 + +### New Contributors + +* @angelej made their first contribution in https://github.com/spatie/laravel-permission/pull/2659 +* @SanderMuller made their first contribution in #2676 +* @szepeviktor made their first contribution in https://github.com/spatie/laravel-permission/pull/2686 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/6.8.0...6.9.0 + ## 6.8.0 - 2024-06-21 ### What's Changed @@ -825,6 +842,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` @@ -893,6 +911,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` From 29e498310aa565ce6e70d02b9ac45c77b40aab5d Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Sat, 20 Jul 2024 22:32:20 -0400 Subject: [PATCH 507/648] L11 note --- docs/advanced-usage/custom-permission-check.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/advanced-usage/custom-permission-check.md b/docs/advanced-usage/custom-permission-check.md index 0f1c88728..956fda047 100644 --- a/docs/advanced-usage/custom-permission-check.md +++ b/docs/advanced-usage/custom-permission-check.md @@ -16,7 +16,7 @@ Let's say that your application uses access tokens for authentication and when i You could, for example, create a `Gate::before()` method call to handle this: -**app/Providers/AuthServiceProvider.php** +**app/Providers/AuthServiceProvider.php** (or maybe `AppServiceProvider.php` since Laravel 11) ```php use Illuminate\Support\Facades\Gate; From 7a0b503acbc7e92e82f920626aba8e68552fec07 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Sat, 20 Jul 2024 22:34:34 -0400 Subject: [PATCH 508/648] L11 --- docs/basic-usage/new-app.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/basic-usage/new-app.md b/docs/basic-usage/new-app.md index 8ec16914d..04b52f9f4 100644 --- a/docs/basic-usage/new-app.md +++ b/docs/basic-usage/new-app.md @@ -130,7 +130,7 @@ php artisan migrate:fresh --seed --seeder=PermissionsDemoSeeder Super-Admins are a common feature. The following approach allows that when your Super-Admin user is logged in, all permission-checks in your app which call `can()` or `@can()` will return true. - Create a role named `Super-Admin`. (Or whatever name you wish; but use it consistently just like you must with any role name.) -- Add a Gate::before check in your `AuthServiceProvider`: +- Add a Gate::before check in your `AuthServiceProvider` (or `AppServiceProvider` since Laravel 11): ```diff + use Illuminate\Support\Facades\Gate; From f81fb020e0045735ca2ab56b91cc91fdd0de726c Mon Sep 17 00:00:00 2001 From: Mike Scott Date: Sat, 27 Jul 2024 00:20:09 +0100 Subject: [PATCH 509/648] Check for 'all' or 'any' permissions before specific permissions (#2694) Shouldn't the check for `edit all posts` or `delete any post` be done first, before checking if a user can edit or delete their own posts? The original code checked if the user can edit their own posts and, if so, would return false if they were not the post auther, **even though they had the permission to edit any post**. By performing the `all`/`any` check first, these permissions still work correctly when the user also has permissions to edit or delete their own posts. --- docs/best-practices/using-policies.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/best-practices/using-policies.md b/docs/best-practices/using-policies.md index 504042e09..5afc36026 100644 --- a/docs/best-practices/using-policies.md +++ b/docs/best-practices/using-policies.md @@ -49,24 +49,24 @@ class PostPolicy public function update(User $user, Post $post) { - if ($user->can('edit own posts')) { - return $user->id == $post->user_id; - } - if ($user->can('edit all posts')) { return true; } - } - public function delete(User $user, Post $post) - { - if ($user->can('delete own posts')) { + if ($user->can('edit own posts')) { return $user->id == $post->user_id; } + } + public function delete(User $user, Post $post) + { if ($user->can('delete any post')) { return true; } + + if ($user->can('delete own posts')) { + return $user->id == $post->user_id; + } } } ``` From 231530a5e07b1bb81f046331e4d077f0f002448b Mon Sep 17 00:00:00 2001 From: Levi Zoesch Sr Date: Wed, 14 Aug 2024 23:03:24 -0700 Subject: [PATCH 510/648] Update uuid.md (#2705) --- docs/advanced-usage/uuid.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/advanced-usage/uuid.md b/docs/advanced-usage/uuid.md index d2740f291..18fdd82f4 100644 --- a/docs/advanced-usage/uuid.md +++ b/docs/advanced-usage/uuid.md @@ -83,7 +83,7 @@ If you also want the roles and permissions to use a UUID for their `id` value, t ## Configuration (OPTIONAL) You might want to change the pivot table field name from `model_id` to `model_uuid`, just for semantic purposes. -For this, in the `permissions.php` configuration file edit `column_names.model_morph_key`: +For this, in the `permission.php` configuration file edit `column_names.model_morph_key`: - OPTIONAL: Change to `model_uuid` instead of the default `model_id`. ```diff From 43550a16901a2e35df7703eee5d36a7c8ea81228 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Mon, 26 Aug 2024 19:27:42 -0400 Subject: [PATCH 511/648] Change example user details to avoid clash with defaults --- docs/basic-usage/new-app.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/basic-usage/new-app.md b/docs/basic-usage/new-app.md index 04b52f9f4..23903c3ab 100644 --- a/docs/basic-usage/new-app.md +++ b/docs/basic-usage/new-app.md @@ -100,7 +100,7 @@ class PermissionsDemoSeeder extends Seeder // create demo users $user = \App\Models\User::factory()->create([ 'name' => 'Example User', - 'email' => 'test@example.com', + 'email' => 'tester@example.com', ]); $user->assignRole($role1); From 631799b6393206714e49cbca58e7d02b76ae7b29 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Mon, 26 Aug 2024 20:02:27 -0400 Subject: [PATCH 512/648] Updates regarding WithoutModelEvents --- docs/advanced-usage/seeding.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/advanced-usage/seeding.md b/docs/advanced-usage/seeding.md index d4e58d810..b40b10050 100644 --- a/docs/advanced-usage/seeding.md +++ b/docs/advanced-usage/seeding.md @@ -7,7 +7,7 @@ weight: 2 You may discover that it is best to flush this package's cache **BEFORE seeding, to avoid cache conflict errors**. -And if you use the `WithoutModelEvents` trait in your seeders, flush it **AFTER seeding as well**. +And if you use the `WithoutModelEvents` trait in your seeders, flush it **AFTER creating any roles/permissions as well, before assigning or granting them.**. ```php // reset cached roles and permissions @@ -40,6 +40,10 @@ class RolesAndPermissionsSeeder extends Seeder Permission::create(['name' => 'publish articles']); Permission::create(['name' => 'unpublish articles']); + // update cache to know about the newly created permissions (required if using WithoutModelEvents in seeders) + app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions(); + + // create roles and assign created permissions // this can be done as separate statements From fe70ca8e2b30ba00aa44eaee18fdb0cfec0a67dc Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 30 Aug 2024 04:03:15 -0400 Subject: [PATCH 513/648] Clarify that "super" access requires using Laravel Gate methods --- docs/basic-usage/super-admin.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/basic-usage/super-admin.md b/docs/basic-usage/super-admin.md index 4eefee99d..e4e2585b2 100644 --- a/docs/basic-usage/super-admin.md +++ b/docs/basic-usage/super-admin.md @@ -7,6 +7,8 @@ We strongly recommend that a Super-Admin be handled by setting a global `Gate::b Then you can implement the best-practice of primarily using permission-based controls (@can and $user->can, etc) throughout your app, without always having to check for "is this a super-admin" everywhere. **Best not to use role-checking (ie: `hasRole`) (except here in Gate/Policy rules) when you have Super Admin features like this.** +NOTE: Using this approach, you can/must call Laravel's standard `can()`, `canAny()`, `cannot()`, etc checks for permission authorization to get a correct Super response. Calls which bypass Laravel's Gate (such as a direct call to `->hasPermissionTo()`) will not go through the Gate, and will not get the Super response. + ## `Gate::before` If you want a "Super Admin" role to respond `true` to all permissions, without needing to assign all those permissions to a role, you can use [Laravel's `Gate::before()` method](https://laravel.com/docs/master/authorization#intercepting-gate-checks). For example: From 4e5c1b3c11cf90a3209304cf8670971c5e2fbcae Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 30 Aug 2024 16:25:52 -0400 Subject: [PATCH 514/648] Note about `database` cache store dependencies --- docs/advanced-usage/seeding.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/advanced-usage/seeding.md b/docs/advanced-usage/seeding.md index b40b10050..30dc21bf2 100644 --- a/docs/advanced-usage/seeding.md +++ b/docs/advanced-usage/seeding.md @@ -18,6 +18,10 @@ You can optionally flush the cache before seeding by using the `SetUp()` method Or it can be done directly in a seeder class, as shown below. +## Database Cache Store + +TIP: If you have `CACHE_STORE=database` set in your `.env`, remember that [you must install Laravel's cache tables via a migration before performing any cache operations](https://laravel.com/docs/cache#prerequisites-database). If you fail to install those migrations, you'll run into errors like `Call to a member function perform() on null` when the cache store attempts to purge or update the cache. This package does strategic cache resets in various places, so may trigger that error if your app's cache dependencies aren't set up. + ## Roles/Permissions Seeder Here is a sample seeder, which first clears the cache, creates permissions and then assigns permissions to roles (the order of these steps is intentional): From 3d2a3d2e9c764293d781dad60b539df937284851 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 30 Aug 2024 16:28:42 -0400 Subject: [PATCH 515/648] Database cache dependencies --- docs/advanced-usage/cache.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/advanced-usage/cache.md b/docs/advanced-usage/cache.md index 588f77f42..6c3a3fe6a 100644 --- a/docs/advanced-usage/cache.md +++ b/docs/advanced-usage/cache.md @@ -48,6 +48,8 @@ php artisan permission:cache-reset ## Cache Configuration Settings +This package allows you to customize cache-related operations via its config file. In most cases the defaults are fine; however, in a multitenancy situation you may wish to do some cache-prefix overrides when switching tenants. See below for more details. + ### Cache Expiration Time The default cache `expiration_time` is `24 hours`. @@ -82,10 +84,15 @@ Setting `'cache.store' => 'array'` in `config/permission.php` will effectively d Alternatively, in development mode you can bypass ALL of Laravel's caching between visits by setting `CACHE_DRIVER=array` in `.env`. You can see an example of this in the default `phpunit.xml` file that comes with a new Laravel install. Of course, don't do this in production though! -## File cache driver +## File cache Store This situation is not specific to this package, but is mentioned here due to the common question being asked. -If you are using the `File` cache driver and run into problems clearing the cache, it is most likely because your filesystem's permissions are preventing the PHP CLI from altering the cache files because the PHP-FPM process is running as a different user. +If you are using the `File` cache Store and run into problems clearing the cache, it is most likely because your filesystem's permissions are preventing the PHP CLI from altering the cache files because the PHP-FPM process is running as a different user. Work with your server administrator to fix filesystem ownership on your cache files. + +## Database cache Store + +TIP: If you have `CACHE_STORE=database` set in your `.env`, remember that [you must install Laravel's cache tables via a migration before performing any cache operations](https://laravel.com/docs/cache#prerequisites-database). If you fail to install those migrations, you'll run into errors like `Call to a member function perform() on null` when the cache store attempts to purge or update the cache. This package does strategic cache resets in various places, so may trigger that error if your app's cache dependencies aren't set up. + From 0068a219970da2bf778bb14d8612d62fd68dd010 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 30 Aug 2024 16:31:12 -0400 Subject: [PATCH 516/648] Database cache reminder --- docs/installation-laravel.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/installation-laravel.md b/docs/installation-laravel.md index fd0cfb59f..f7cb644f9 100644 --- a/docs/installation-laravel.md +++ b/docs/installation-laravel.md @@ -53,6 +53,8 @@ Package Version | Laravel Version - **If you are using MySQL 8**, look at the migration files for notes about MySQL 8 to set/limit the index key length, and edit accordingly. If you get `ERROR: 1071 Specified key was too long` then you need to do this. + - **If you are using CACHE_STORE=database**, be sure to [install Laravel's cache migration](https://laravel.com/docs/cache#prerequisites-database), else you will encounter cache errors. + 7. **Clear your config cache**. This package requires access to the `permission` config settings in order to run migrations. If you've been caching configurations locally, clear your config cache with either of these commands: php artisan optimize:clear From 36bb36777fb8ad5928b806cd63178aff06efeaa0 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 30 Aug 2024 17:03:50 -0400 Subject: [PATCH 517/648] Gate notes --- docs/basic-usage/direct-permissions.md | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/docs/basic-usage/direct-permissions.md b/docs/basic-usage/direct-permissions.md index d8533b9b9..944392b0f 100644 --- a/docs/basic-usage/direct-permissions.md +++ b/docs/basic-usage/direct-permissions.md @@ -13,6 +13,8 @@ HOWEVER, If you have reason to directly assign individual permissions to specifi ## Direct Permissions to Users +### Giving/Revoking direct permissions + A permission can be given to any user: ```php @@ -37,6 +39,15 @@ Or revoke & add new permissions in one go: $user->syncPermissions(['edit articles', 'delete articles']); ``` +## Checking Direct Permissions +Like all permissions assigned via roles, you can check if a user has a permission by using Laravel's default `can` function. This will also allow you to use Super-Admin features provided by Laravel's Gate: + +```php +$user->can('edit articles'); +``` + +NOTE: The following `hasPermissionTo`, `hasAnyPermission`, `hasAllPermissions` functions do not support Super-Admin functionality. Use `can`, `canAny`, `canAll` instead. + You can check if a user has a permission: ```php @@ -68,9 +79,3 @@ You may also pass integers to lookup by permission id ```php $user->hasAnyPermission(['edit articles', 1, 5]); ``` - -Like all permissions assigned via roles, you can check if a user has a permission by using Laravel's default `can` function: - -```php -$user->can('edit articles'); -``` From 514cb7ead7f8a7db3804bb017d51e4a0edf8aff4 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 30 Aug 2024 17:13:35 -0400 Subject: [PATCH 518/648] Clarify internal has methods vs Gate can methods --- docs/basic-usage/super-admin.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/basic-usage/super-admin.md b/docs/basic-usage/super-admin.md index e4e2585b2..0603db2bd 100644 --- a/docs/basic-usage/super-admin.md +++ b/docs/basic-usage/super-admin.md @@ -7,7 +7,14 @@ We strongly recommend that a Super-Admin be handled by setting a global `Gate::b Then you can implement the best-practice of primarily using permission-based controls (@can and $user->can, etc) throughout your app, without always having to check for "is this a super-admin" everywhere. **Best not to use role-checking (ie: `hasRole`) (except here in Gate/Policy rules) when you have Super Admin features like this.** -NOTE: Using this approach, you can/must call Laravel's standard `can()`, `canAny()`, `cannot()`, etc checks for permission authorization to get a correct Super response. Calls which bypass Laravel's Gate (such as a direct call to `->hasPermissionTo()`) will not go through the Gate, and will not get the Super response. +## Gate::before/Policy::before vs HasPermissionTo / HasAnyPermission / HasDirectPermission / HasAllPermissions +IMPORTANT: +The Gate::before is the best approach for Super-Admin functionality, and aligns well with the described "Best Practices" of using roles as a way of grouping permissions, and assigning that access to Users. Using this approach, you can/must call Laravel's standard `can()`, `canAny()`, `cannot()`, etc checks for permission authorization to get a correct Super response. + +### HasPermissionTo, HasAllPermissions, HasAnyPermission, HasDirectPermission +Calls to this package's internal API which bypass Laravel's Gate (such as a direct call to `->hasPermissionTo()`) will not go through the Gate, and thus will not get the Super response, unless you have actually added that specific permission to the Super-Admin "role". + +The only reason for giving specific permissions to a Super-Admin role is if you intend to call the `has` methods directly instead of the Gate's `can()` methods. ## `Gate::before` From b881c1a64e6017dbdb354495d7c774969cccd5c0 Mon Sep 17 00:00:00 2001 From: mraheelkhan Date: Wed, 4 Sep 2024 09:24:29 +0000 Subject: [PATCH 519/648] Fix styling --- src/Models/Permission.php | 2 +- src/Models/Role.php | 2 +- src/Traits/HasPermissions.php | 2 +- tests/HasPermissionsTest.php | 6 +++--- tests/PermissionMiddlewareTest.php | 14 +++++++------- tests/RoleMiddlewareTest.php | 14 +++++++------- tests/RoleOrPermissionMiddlewareTest.php | 14 +++++++------- tests/TestCase.php | 4 ++-- tests/TestHelper.php | 4 ++-- tests/WildcardMiddlewareTest.php | 10 +++++----- 10 files changed, 36 insertions(+), 36 deletions(-) diff --git a/src/Models/Permission.php b/src/Models/Permission.php index d23d15ac2..fd54b8ca0 100644 --- a/src/Models/Permission.php +++ b/src/Models/Permission.php @@ -107,7 +107,7 @@ public static function findByName(string $name, ?string $guardName = null): Perm public static function findById(int|string $id, ?string $guardName = null): PermissionContract { $guardName = $guardName ?? Guard::getDefaultName(static::class); - $permission = static::getPermission([(new static())->getKeyName() => $id, 'guard_name' => $guardName]); + $permission = static::getPermission([(new static)->getKeyName() => $id, 'guard_name' => $guardName]); if (! $permission) { throw PermissionDoesNotExist::withId($id, $guardName); diff --git a/src/Models/Role.php b/src/Models/Role.php index 86d81d379..e29e2d449 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -117,7 +117,7 @@ public static function findById(int|string $id, ?string $guardName = null): Role { $guardName = $guardName ?? Guard::getDefaultName(static::class); - $role = static::findByParam([(new static())->getKeyName() => $id, 'guard_name' => $guardName]); + $role = static::findByParam([(new static)->getKeyName() => $id, 'guard_name' => $guardName]); if (! $role) { throw RoleDoesNotExist::withId($id, $guardName); diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index 2514458d8..25d506088 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -190,7 +190,7 @@ public function filterPermission($permission, $guardName = null) } if (! $permission instanceof Permission) { - throw new PermissionDoesNotExist(); + throw new PermissionDoesNotExist; } return $permission; diff --git a/tests/HasPermissionsTest.php b/tests/HasPermissionsTest.php index 4c9528d0e..ce197b16c 100644 --- a/tests/HasPermissionsTest.php +++ b/tests/HasPermissionsTest.php @@ -263,7 +263,7 @@ public function it_throws_an_exception_when_calling_hasPermissionTo_with_an_inva $this->expectException(PermissionDoesNotExist::class); - $user->hasPermissionTo(new \stdClass()); + $user->hasPermissionTo(new \stdClass); } /** @test */ @@ -283,7 +283,7 @@ public function it_throws_an_exception_when_calling_hasDirectPermission_with_an_ $this->expectException(PermissionDoesNotExist::class); - $user->hasDirectPermission(new \stdClass()); + $user->hasDirectPermission(new \stdClass); } /** @test */ @@ -405,7 +405,7 @@ public function it_throws_an_exception_when_the_permission_does_not_exist_for_th /** @test */ public function it_can_reject_a_user_that_does_not_have_any_permissions_at_all() { - $user = new User(); + $user = new User; $this->assertFalse($user->hasPermissionTo('edit-articles')); } diff --git a/tests/PermissionMiddlewareTest.php b/tests/PermissionMiddlewareTest.php index aefc7849e..354f1f8b1 100644 --- a/tests/PermissionMiddlewareTest.php +++ b/tests/PermissionMiddlewareTest.php @@ -24,7 +24,7 @@ protected function setUp(): void { parent::setUp(); - $this->permissionMiddleware = new PermissionMiddleware(); + $this->permissionMiddleware = new PermissionMiddleware; } /** @test */ @@ -305,8 +305,8 @@ public function the_required_permissions_can_be_fetched_from_the_exception() $requiredPermissions = []; try { - $this->permissionMiddleware->handle(new Request(), function () { - return (new Response())->setContent(''); + $this->permissionMiddleware->handle(new Request, function () { + return (new Response)->setContent(''); }, 'some-permission'); } catch (UnauthorizedException $e) { $message = $e->getMessage(); @@ -326,8 +326,8 @@ public function the_required_permissions_can_be_displayed_in_the_exception() $message = null; try { - $this->permissionMiddleware->handle(new Request(), function () { - return (new Response())->setContent(''); + $this->permissionMiddleware->handle(new Request, function () { + return (new Response)->setContent(''); }, 'some-permission'); } catch (UnauthorizedException $e) { $message = $e->getMessage(); @@ -342,8 +342,8 @@ public function use_not_existing_custom_guard_in_permission() $class = null; try { - $this->permissionMiddleware->handle(new Request(), function () { - return (new Response())->setContent(''); + $this->permissionMiddleware->handle(new Request, function () { + return (new Response)->setContent(''); }, 'edit-articles', 'xxx'); } catch (InvalidArgumentException $e) { $class = get_class($e); diff --git a/tests/RoleMiddlewareTest.php b/tests/RoleMiddlewareTest.php index 7ffc3a5c6..780277068 100644 --- a/tests/RoleMiddlewareTest.php +++ b/tests/RoleMiddlewareTest.php @@ -22,7 +22,7 @@ protected function setUp(): void { parent::setUp(); - $this->roleMiddleware = new RoleMiddleware(); + $this->roleMiddleware = new RoleMiddleware; } /** @test */ @@ -238,8 +238,8 @@ public function the_required_roles_can_be_fetched_from_the_exception() $requiredRoles = []; try { - $this->roleMiddleware->handle(new Request(), function () { - return (new Response())->setContent(''); + $this->roleMiddleware->handle(new Request, function () { + return (new Response)->setContent(''); }, 'some-role'); } catch (UnauthorizedException $e) { $message = $e->getMessage(); @@ -259,8 +259,8 @@ public function the_required_roles_can_be_displayed_in_the_exception() $message = null; try { - $this->roleMiddleware->handle(new Request(), function () { - return (new Response())->setContent(''); + $this->roleMiddleware->handle(new Request, function () { + return (new Response)->setContent(''); }, 'some-role'); } catch (UnauthorizedException $e) { $message = $e->getMessage(); @@ -275,8 +275,8 @@ public function use_not_existing_custom_guard_in_role() $class = null; try { - $this->roleMiddleware->handle(new Request(), function () { - return (new Response())->setContent(''); + $this->roleMiddleware->handle(new Request, function () { + return (new Response)->setContent(''); }, 'testRole', 'xxx'); } catch (InvalidArgumentException $e) { $class = get_class($e); diff --git a/tests/RoleOrPermissionMiddlewareTest.php b/tests/RoleOrPermissionMiddlewareTest.php index 4d473aa4f..617c4c5af 100644 --- a/tests/RoleOrPermissionMiddlewareTest.php +++ b/tests/RoleOrPermissionMiddlewareTest.php @@ -23,7 +23,7 @@ protected function setUp(): void { parent::setUp(); - $this->roleOrPermissionMiddleware = new RoleOrPermissionMiddleware(); + $this->roleOrPermissionMiddleware = new RoleOrPermissionMiddleware; } /** @test */ @@ -177,8 +177,8 @@ public function use_not_existing_custom_guard_in_role_or_permission() $class = null; try { - $this->roleOrPermissionMiddleware->handle(new Request(), function () { - return (new Response())->setContent(''); + $this->roleOrPermissionMiddleware->handle(new Request, function () { + return (new Response)->setContent(''); }, 'testRole', 'xxx'); } catch (InvalidArgumentException $e) { $class = get_class($e); @@ -242,8 +242,8 @@ public function the_required_permissions_or_roles_can_be_fetched_from_the_except $requiredRolesOrPermissions = []; try { - $this->roleOrPermissionMiddleware->handle(new Request(), function () { - return (new Response())->setContent(''); + $this->roleOrPermissionMiddleware->handle(new Request, function () { + return (new Response)->setContent(''); }, 'some-permission|some-role'); } catch (UnauthorizedException $e) { $message = $e->getMessage(); @@ -264,8 +264,8 @@ public function the_required_permissions_or_roles_can_be_displayed_in_the_except $message = null; try { - $this->roleOrPermissionMiddleware->handle(new Request(), function () { - return (new Response())->setContent(''); + $this->roleOrPermissionMiddleware->handle(new Request, function () { + return (new Response)->setContent(''); }, 'some-permission|some-role'); } catch (UnauthorizedException $e) { $message = $e->getMessage(); diff --git a/tests/TestCase.php b/tests/TestCase.php index 3a5532c8b..9e5e36c34 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -292,7 +292,7 @@ public function runMiddleware($middleware, $permission, $guard = null, bool $cli try { return $middleware->handle($request, function () { - return (new Response())->setContent(''); + return (new Response)->setContent(''); }, $permission, $guard)->status(); } catch (UnauthorizedException $e) { return $e->getStatusCode(); @@ -312,7 +312,7 @@ public function getRouter() public function getRouteResponse() { return function () { - return (new Response())->setContent(''); + return (new Response)->setContent(''); }; } diff --git a/tests/TestHelper.php b/tests/TestHelper.php index 5b8efa5cd..5eddab214 100644 --- a/tests/TestHelper.php +++ b/tests/TestHelper.php @@ -16,8 +16,8 @@ class TestHelper public function testMiddleware($middleware, $parameter) { try { - return $middleware->handle(new Request(), function () { - return (new Response())->setContent(''); + return $middleware->handle(new Request, function () { + return (new Response)->setContent(''); }, $parameter)->status(); } catch (HttpException $e) { return $e->getStatusCode(); diff --git a/tests/WildcardMiddlewareTest.php b/tests/WildcardMiddlewareTest.php index dc359fe49..596913707 100644 --- a/tests/WildcardMiddlewareTest.php +++ b/tests/WildcardMiddlewareTest.php @@ -23,11 +23,11 @@ protected function setUp(): void { parent::setUp(); - $this->roleMiddleware = new RoleMiddleware(); + $this->roleMiddleware = new RoleMiddleware; - $this->permissionMiddleware = new PermissionMiddleware(); + $this->permissionMiddleware = new PermissionMiddleware; - $this->roleOrPermissionMiddleware = new RoleOrPermissionMiddleware(); + $this->roleOrPermissionMiddleware = new RoleOrPermissionMiddleware; app('config')->set('permission.enable_wildcard_permission', true); } @@ -146,8 +146,8 @@ public function the_required_permissions_can_be_fetched_from_the_exception() $requiredPermissions = []; try { - $this->permissionMiddleware->handle(new Request(), function () { - return (new Response())->setContent(''); + $this->permissionMiddleware->handle(new Request, function () { + return (new Response)->setContent(''); }, 'permission.some'); } catch (UnauthorizedException $e) { $requiredPermissions = $e->getRequiredPermissions(); From df0ebb997d77ae01df0bf4a52073b61b17859606 Mon Sep 17 00:00:00 2001 From: Raheel Khan <30007262+mraheelkhan@users.noreply.github.com> Date: Wed, 4 Sep 2024 14:27:19 +0500 Subject: [PATCH 520/648] Add PR links to upgrade guide --- docs/upgrading.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/upgrading.md b/docs/upgrading.md index da3d4423d..2d56f9b18 100644 --- a/docs/upgrading.md +++ b/docs/upgrading.md @@ -39,7 +39,7 @@ eg: if you have a custom model you will need to make changes, including accessin Be sure to compare your custom models with originals to see what else may have changed. -3. Model and Contract/Interface updates. The Role and Permission Models and Contracts/Interfaces have been updated with syntax changes to method signatures. Update any models you have extended, or contracts implemented, accordingly. See PR #2380 and #2480 for some of the specifics. +3. Model and Contract/Interface updates. The Role and Permission Models and Contracts/Interfaces have been updated with syntax changes to method signatures. Update any models you have extended, or contracts implemented, accordingly. See PR [#2380](https://github.com/spatie/laravel-permission/pull/2380) and [#2480](https://github.com/spatie/laravel-permission/pull/2480) for some of the specifics. 4. Migrations WILL need to be upgraded. (They have been updated to anonymous-class syntax that was introduced in Laravel 8, AND some structural coding changes in the registrar class changed the way we extracted configuration settings in the migration files.) There are no changes to the package's structure since v5, so if you had not customized it from the original then replacing the contents of the file should be enough. (Usually the only customization is if you've switched to UUIDs or customized MySQL index name lengths.) **If you get the following error, it means your migration file needs upgrading: `Error: Access to undeclared static property Spatie\Permission\PermissionRegistrar::$pivotPermission`** From 28577dc646784600d822eb95f9bef5b8fa7632a9 Mon Sep 17 00:00:00 2001 From: Galang Aidil Akbar Date: Sat, 14 Sep 2024 16:34:50 +0700 Subject: [PATCH 521/648] Make return type nullable --- docs/basic-usage/super-admin.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/basic-usage/super-admin.md b/docs/basic-usage/super-admin.md index 0603db2bd..00a2af27f 100644 --- a/docs/basic-usage/super-admin.md +++ b/docs/basic-usage/super-admin.md @@ -51,7 +51,7 @@ use App\Models\User; // could be any Authorizable model /** * Perform pre-authorization checks on the model. */ -public function before(User $user, string $ability): bool|null +public function before(User $user, string $ability): ?bool { if ($user->hasRole('Super Admin')) { return true; From 01d2e33aaaeafd208ab2be664cf989dcbfb7f84c Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Mon, 16 Sep 2024 11:45:58 -0400 Subject: [PATCH 522/648] Rename PermissionRegistarTest.php to PermissionRegistrarTest.php --- .../{PermissionRegistarTest.php => PermissionRegistrarTest.php} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename tests/{PermissionRegistarTest.php => PermissionRegistrarTest.php} (99%) diff --git a/tests/PermissionRegistarTest.php b/tests/PermissionRegistrarTest.php similarity index 99% rename from tests/PermissionRegistarTest.php rename to tests/PermissionRegistrarTest.php index d5ed03547..115926d4c 100644 --- a/tests/PermissionRegistarTest.php +++ b/tests/PermissionRegistrarTest.php @@ -10,7 +10,7 @@ use Spatie\Permission\Tests\TestModels\Permission as TestPermission; use Spatie\Permission\Tests\TestModels\Role as TestRole; -class PermissionRegistarTest extends TestCase +class PermissionRegistrarTest extends TestCase { /** @test */ public function it_can_clear_loaded_permissions_collection() From 44083f48b4cb7c1c1ffe32b9e339141261ef0cfb Mon Sep 17 00:00:00 2001 From: drbyte Date: Mon, 16 Sep 2024 15:46:24 +0000 Subject: [PATCH 523/648] Fix styling --- src/Models/Permission.php | 2 +- src/Models/Role.php | 2 +- src/Traits/HasPermissions.php | 2 +- tests/HasPermissionsTest.php | 6 +++--- tests/PermissionMiddlewareTest.php | 14 +++++++------- tests/RoleMiddlewareTest.php | 14 +++++++------- tests/RoleOrPermissionMiddlewareTest.php | 14 +++++++------- tests/TestCase.php | 4 ++-- tests/TestHelper.php | 4 ++-- tests/WildcardMiddlewareTest.php | 10 +++++----- 10 files changed, 36 insertions(+), 36 deletions(-) diff --git a/src/Models/Permission.php b/src/Models/Permission.php index d23d15ac2..fd54b8ca0 100644 --- a/src/Models/Permission.php +++ b/src/Models/Permission.php @@ -107,7 +107,7 @@ public static function findByName(string $name, ?string $guardName = null): Perm public static function findById(int|string $id, ?string $guardName = null): PermissionContract { $guardName = $guardName ?? Guard::getDefaultName(static::class); - $permission = static::getPermission([(new static())->getKeyName() => $id, 'guard_name' => $guardName]); + $permission = static::getPermission([(new static)->getKeyName() => $id, 'guard_name' => $guardName]); if (! $permission) { throw PermissionDoesNotExist::withId($id, $guardName); diff --git a/src/Models/Role.php b/src/Models/Role.php index 86d81d379..e29e2d449 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -117,7 +117,7 @@ public static function findById(int|string $id, ?string $guardName = null): Role { $guardName = $guardName ?? Guard::getDefaultName(static::class); - $role = static::findByParam([(new static())->getKeyName() => $id, 'guard_name' => $guardName]); + $role = static::findByParam([(new static)->getKeyName() => $id, 'guard_name' => $guardName]); if (! $role) { throw RoleDoesNotExist::withId($id, $guardName); diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index 2514458d8..25d506088 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -190,7 +190,7 @@ public function filterPermission($permission, $guardName = null) } if (! $permission instanceof Permission) { - throw new PermissionDoesNotExist(); + throw new PermissionDoesNotExist; } return $permission; diff --git a/tests/HasPermissionsTest.php b/tests/HasPermissionsTest.php index 4c9528d0e..ce197b16c 100644 --- a/tests/HasPermissionsTest.php +++ b/tests/HasPermissionsTest.php @@ -263,7 +263,7 @@ public function it_throws_an_exception_when_calling_hasPermissionTo_with_an_inva $this->expectException(PermissionDoesNotExist::class); - $user->hasPermissionTo(new \stdClass()); + $user->hasPermissionTo(new \stdClass); } /** @test */ @@ -283,7 +283,7 @@ public function it_throws_an_exception_when_calling_hasDirectPermission_with_an_ $this->expectException(PermissionDoesNotExist::class); - $user->hasDirectPermission(new \stdClass()); + $user->hasDirectPermission(new \stdClass); } /** @test */ @@ -405,7 +405,7 @@ public function it_throws_an_exception_when_the_permission_does_not_exist_for_th /** @test */ public function it_can_reject_a_user_that_does_not_have_any_permissions_at_all() { - $user = new User(); + $user = new User; $this->assertFalse($user->hasPermissionTo('edit-articles')); } diff --git a/tests/PermissionMiddlewareTest.php b/tests/PermissionMiddlewareTest.php index aefc7849e..354f1f8b1 100644 --- a/tests/PermissionMiddlewareTest.php +++ b/tests/PermissionMiddlewareTest.php @@ -24,7 +24,7 @@ protected function setUp(): void { parent::setUp(); - $this->permissionMiddleware = new PermissionMiddleware(); + $this->permissionMiddleware = new PermissionMiddleware; } /** @test */ @@ -305,8 +305,8 @@ public function the_required_permissions_can_be_fetched_from_the_exception() $requiredPermissions = []; try { - $this->permissionMiddleware->handle(new Request(), function () { - return (new Response())->setContent(''); + $this->permissionMiddleware->handle(new Request, function () { + return (new Response)->setContent(''); }, 'some-permission'); } catch (UnauthorizedException $e) { $message = $e->getMessage(); @@ -326,8 +326,8 @@ public function the_required_permissions_can_be_displayed_in_the_exception() $message = null; try { - $this->permissionMiddleware->handle(new Request(), function () { - return (new Response())->setContent(''); + $this->permissionMiddleware->handle(new Request, function () { + return (new Response)->setContent(''); }, 'some-permission'); } catch (UnauthorizedException $e) { $message = $e->getMessage(); @@ -342,8 +342,8 @@ public function use_not_existing_custom_guard_in_permission() $class = null; try { - $this->permissionMiddleware->handle(new Request(), function () { - return (new Response())->setContent(''); + $this->permissionMiddleware->handle(new Request, function () { + return (new Response)->setContent(''); }, 'edit-articles', 'xxx'); } catch (InvalidArgumentException $e) { $class = get_class($e); diff --git a/tests/RoleMiddlewareTest.php b/tests/RoleMiddlewareTest.php index 7ffc3a5c6..780277068 100644 --- a/tests/RoleMiddlewareTest.php +++ b/tests/RoleMiddlewareTest.php @@ -22,7 +22,7 @@ protected function setUp(): void { parent::setUp(); - $this->roleMiddleware = new RoleMiddleware(); + $this->roleMiddleware = new RoleMiddleware; } /** @test */ @@ -238,8 +238,8 @@ public function the_required_roles_can_be_fetched_from_the_exception() $requiredRoles = []; try { - $this->roleMiddleware->handle(new Request(), function () { - return (new Response())->setContent(''); + $this->roleMiddleware->handle(new Request, function () { + return (new Response)->setContent(''); }, 'some-role'); } catch (UnauthorizedException $e) { $message = $e->getMessage(); @@ -259,8 +259,8 @@ public function the_required_roles_can_be_displayed_in_the_exception() $message = null; try { - $this->roleMiddleware->handle(new Request(), function () { - return (new Response())->setContent(''); + $this->roleMiddleware->handle(new Request, function () { + return (new Response)->setContent(''); }, 'some-role'); } catch (UnauthorizedException $e) { $message = $e->getMessage(); @@ -275,8 +275,8 @@ public function use_not_existing_custom_guard_in_role() $class = null; try { - $this->roleMiddleware->handle(new Request(), function () { - return (new Response())->setContent(''); + $this->roleMiddleware->handle(new Request, function () { + return (new Response)->setContent(''); }, 'testRole', 'xxx'); } catch (InvalidArgumentException $e) { $class = get_class($e); diff --git a/tests/RoleOrPermissionMiddlewareTest.php b/tests/RoleOrPermissionMiddlewareTest.php index 4d473aa4f..617c4c5af 100644 --- a/tests/RoleOrPermissionMiddlewareTest.php +++ b/tests/RoleOrPermissionMiddlewareTest.php @@ -23,7 +23,7 @@ protected function setUp(): void { parent::setUp(); - $this->roleOrPermissionMiddleware = new RoleOrPermissionMiddleware(); + $this->roleOrPermissionMiddleware = new RoleOrPermissionMiddleware; } /** @test */ @@ -177,8 +177,8 @@ public function use_not_existing_custom_guard_in_role_or_permission() $class = null; try { - $this->roleOrPermissionMiddleware->handle(new Request(), function () { - return (new Response())->setContent(''); + $this->roleOrPermissionMiddleware->handle(new Request, function () { + return (new Response)->setContent(''); }, 'testRole', 'xxx'); } catch (InvalidArgumentException $e) { $class = get_class($e); @@ -242,8 +242,8 @@ public function the_required_permissions_or_roles_can_be_fetched_from_the_except $requiredRolesOrPermissions = []; try { - $this->roleOrPermissionMiddleware->handle(new Request(), function () { - return (new Response())->setContent(''); + $this->roleOrPermissionMiddleware->handle(new Request, function () { + return (new Response)->setContent(''); }, 'some-permission|some-role'); } catch (UnauthorizedException $e) { $message = $e->getMessage(); @@ -264,8 +264,8 @@ public function the_required_permissions_or_roles_can_be_displayed_in_the_except $message = null; try { - $this->roleOrPermissionMiddleware->handle(new Request(), function () { - return (new Response())->setContent(''); + $this->roleOrPermissionMiddleware->handle(new Request, function () { + return (new Response)->setContent(''); }, 'some-permission|some-role'); } catch (UnauthorizedException $e) { $message = $e->getMessage(); diff --git a/tests/TestCase.php b/tests/TestCase.php index 3a5532c8b..9e5e36c34 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -292,7 +292,7 @@ public function runMiddleware($middleware, $permission, $guard = null, bool $cli try { return $middleware->handle($request, function () { - return (new Response())->setContent(''); + return (new Response)->setContent(''); }, $permission, $guard)->status(); } catch (UnauthorizedException $e) { return $e->getStatusCode(); @@ -312,7 +312,7 @@ public function getRouter() public function getRouteResponse() { return function () { - return (new Response())->setContent(''); + return (new Response)->setContent(''); }; } diff --git a/tests/TestHelper.php b/tests/TestHelper.php index 5b8efa5cd..5eddab214 100644 --- a/tests/TestHelper.php +++ b/tests/TestHelper.php @@ -16,8 +16,8 @@ class TestHelper public function testMiddleware($middleware, $parameter) { try { - return $middleware->handle(new Request(), function () { - return (new Response())->setContent(''); + return $middleware->handle(new Request, function () { + return (new Response)->setContent(''); }, $parameter)->status(); } catch (HttpException $e) { return $e->getStatusCode(); diff --git a/tests/WildcardMiddlewareTest.php b/tests/WildcardMiddlewareTest.php index dc359fe49..596913707 100644 --- a/tests/WildcardMiddlewareTest.php +++ b/tests/WildcardMiddlewareTest.php @@ -23,11 +23,11 @@ protected function setUp(): void { parent::setUp(); - $this->roleMiddleware = new RoleMiddleware(); + $this->roleMiddleware = new RoleMiddleware; - $this->permissionMiddleware = new PermissionMiddleware(); + $this->permissionMiddleware = new PermissionMiddleware; - $this->roleOrPermissionMiddleware = new RoleOrPermissionMiddleware(); + $this->roleOrPermissionMiddleware = new RoleOrPermissionMiddleware; app('config')->set('permission.enable_wildcard_permission', true); } @@ -146,8 +146,8 @@ public function the_required_permissions_can_be_fetched_from_the_exception() $requiredPermissions = []; try { - $this->permissionMiddleware->handle(new Request(), function () { - return (new Response())->setContent(''); + $this->permissionMiddleware->handle(new Request, function () { + return (new Response)->setContent(''); }, 'permission.some'); } catch (UnauthorizedException $e) { $requiredPermissions = $e->getRequiredPermissions(); From 78122ea5746c82c6b78652419a323924f30b5e74 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Wed, 18 Sep 2024 00:36:48 -0500 Subject: [PATCH 524/648] Only show error if cache key exists and forgetCachedPermissions fail (#2707) --- src/Commands/CacheReset.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Commands/CacheReset.php b/src/Commands/CacheReset.php index 180d16972..7cfc65ae7 100644 --- a/src/Commands/CacheReset.php +++ b/src/Commands/CacheReset.php @@ -13,9 +13,12 @@ class CacheReset extends Command public function handle() { - if (app(PermissionRegistrar::class)->forgetCachedPermissions()) { + $permissionRegistrar = app(PermissionRegistrar::class); + $cacheExists = $permissionRegistrar->getCacheRepository()->has($permissionRegistrar->cacheKey); + + if ($permissionRegistrar->forgetCachedPermissions()) { $this->info('Permission cache flushed.'); - } else { + } else if ($cacheExists) { $this->error('Unable to flush cache.'); } } From 92b287c842ab128bb96d67376bfa1dffd94f1db0 Mon Sep 17 00:00:00 2001 From: drbyte Date: Wed, 18 Sep 2024 05:37:08 +0000 Subject: [PATCH 525/648] Fix styling --- src/Commands/CacheReset.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Commands/CacheReset.php b/src/Commands/CacheReset.php index 7cfc65ae7..85f7c24eb 100644 --- a/src/Commands/CacheReset.php +++ b/src/Commands/CacheReset.php @@ -18,7 +18,7 @@ public function handle() if ($permissionRegistrar->forgetCachedPermissions()) { $this->info('Permission cache flushed.'); - } else if ($cacheExists) { + } elseif ($cacheExists) { $this->error('Unable to flush cache.'); } } From 581c628bf7f5d6a645743ca9b8df78614646584f Mon Sep 17 00:00:00 2001 From: kamil_wojtalak <48104288+KamilWojtalak@users.noreply.github.com> Date: Wed, 18 Sep 2024 10:12:22 +0200 Subject: [PATCH 526/648] normalize variable naming --- docs/basic-usage/basic-usage.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/basic-usage/basic-usage.md b/docs/basic-usage/basic-usage.md index ea0d9e172..bd8f2acb0 100644 --- a/docs/basic-usage/basic-usage.md +++ b/docs/basic-usage/basic-usage.md @@ -98,11 +98,11 @@ The scope can accept a string, a `\Spatie\Permission\Models\Permission` object o Since Role and Permission models are extended from Eloquent models, basic Eloquent calls can be used as well: ```php -$all_users_with_all_their_roles = User::with('roles')->get(); -$all_users_with_all_their_direct_permissions = User::with('permissions')->get(); -$all_roles_in_database = Role::all()->pluck('name'); -$users_without_any_roles = User::doesntHave('roles')->get(); -$all_roles_except_a_and_b = Role::whereNotIn('name', ['role A', 'role B'])->get(); +$allUsersWithAllTheirRoles = User::with('roles')->get(); +$allUsersWithAllTheirDirectPermissions = User::with('permissions')->get(); +$allRolesInDatabase = Role::all()->pluck('name'); +$usersWithoutAnyRoles = User::doesntHave('roles')->get(); +$allRolesExceptAandB = Role::whereNotIn('name', ['role A', 'role B'])->get(); ``` ## Counting Users Having A Role From a6cfb37c395a364124cde7b392899bf2d89c1d78 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Thu, 19 Sep 2024 17:26:25 -0400 Subject: [PATCH 527/648] Update policy code examples --- docs/best-practices/using-policies.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/best-practices/using-policies.md b/docs/best-practices/using-policies.md index 5afc36026..c967ca9db 100644 --- a/docs/best-practices/using-policies.md +++ b/docs/best-practices/using-policies.md @@ -22,7 +22,7 @@ class PostPolicy { use HandlesAuthorization; - public function view(?User $user, Post $post) + public function view(?User $user, Post $post): bool { if ($post->published) { return true; @@ -42,12 +42,12 @@ class PostPolicy return $user->id == $post->user_id; } - public function create(User $user) + public function create(User $user): bool { return ($user->can('create posts')); } - public function update(User $user, Post $post) + public function update(User $user, Post $post): bool { if ($user->can('edit all posts')) { return true; @@ -58,7 +58,7 @@ class PostPolicy } } - public function delete(User $user, Post $post) + public function delete(User $user, Post $post): bool { if ($user->can('delete any post')) { return true; From 05f620c903f632a9ed26cb5e81d1c5e86601669c Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Thu, 19 Sep 2024 17:27:09 -0400 Subject: [PATCH 528/648] [Docs] Update to note Laravel 11.23 updates --- docs/basic-usage/enums.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/basic-usage/enums.md b/docs/basic-usage/enums.md index 2de63642b..493c9380b 100644 --- a/docs/basic-usage/enums.md +++ b/docs/basic-usage/enums.md @@ -47,7 +47,7 @@ enum RolesEnum: string ## Creating Roles/Permissions using Enums -When creating roles/permissions, you cannot pass a Enum name directly, because Eloquent expects a string for the name. +When **creating** roles/permissions, you cannot pass an Enum name directly, because Eloquent expects a string for the name. You must manually convert the name to its value in order to pass the correct string to Eloquent for the role/permission name. @@ -62,7 +62,7 @@ Same with creating Permissions. In your application code, when checking for authorization using features of this package, you can use `MyEnum::NAME` directly in most cases, without passing `->value` to convert to a string. -There will be times where you will need to manually fallback to adding `->value` (eg: `MyEnum::NAME->value`) when using features that aren't aware of Enum support. This will occur when you need to pass `string` values instead of an `Enum`, such as when interacting with Laravel's Gate via the `can()` methods/helpers (eg: `can`, `canAny`, etc). +There may occasionally be times where you will need to manually fallback to adding `->value` (eg: `MyEnum::NAME->value`) when using features that aren't aware of Enum support, such as when you need to pass `string` values instead of an `Enum` to a function that doesn't recognize Enums (Prior to Laravel v11.23.0 the framework didn't support Enums when interacting with Gate via the `can()` methods/helpers (eg: `can`, `canAny`, etc)). Examples: ```php @@ -70,7 +70,7 @@ Examples: $user->hasPermissionTo(PermissionsEnum::VIEWPOSTS); $user->hasPermissionTo(PermissionsEnum::VIEWPOSTS->value); -// when calling Gate features, such as Model Policies, etc +// when calling Gate features, such as Model Policies, etc, prior to Laravel v11.23.0 $user->can(PermissionsEnum::VIEWPOSTS->value); $model->can(PermissionsEnum::VIEWPOSTS->value); From 43bc084157aeb603c0262a148838126a7a343e01 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 18 Oct 2024 17:00:21 -0400 Subject: [PATCH 529/648] Add clarity around using custom guard --- docs/basic-usage/blade-directives.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/basic-usage/blade-directives.md b/docs/basic-usage/blade-directives.md index 4ee4b7b38..56629be32 100644 --- a/docs/basic-usage/blade-directives.md +++ b/docs/basic-usage/blade-directives.md @@ -22,6 +22,13 @@ You can use `@can`, `@cannot`, `@canany`, and `@guest` to test for permission-re When using a permission-name associated with permissions created in this package, you can use `@can('permission-name', 'guard_name')` if you need to check against a specific guard. +Example: +```php +@can('edit articles', 'guard_name') + // +@endcan +``` + You can also use `@haspermission('permission-name')` or `@haspermission('permission-name', 'guard_name')` in similar fashion. With corresponding `@endhaspermission`. There is no `@hasanypermission` directive: use `@canany` instead. From 0562f29c32da3de0483c467249def95614693a2c Mon Sep 17 00:00:00 2001 From: Wyatt Castaneda Date: Sat, 19 Oct 2024 23:32:52 -0700 Subject: [PATCH 530/648] Update teams-permissions.md Provide an example of pushing your custom middleware before the SubstituteBindings middleware in the applications middleware stack. In the new Laravel skeleton, this method provides a much cleaner approach than copying all default middlewares in the `bootstrap\app.php` `withMiddleware` method. --- docs/basic-usage/teams-permissions.md | 28 +++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/docs/basic-usage/teams-permissions.md b/docs/basic-usage/teams-permissions.md index f4a0d729e..ad220a1ac 100644 --- a/docs/basic-usage/teams-permissions.md +++ b/docs/basic-usage/teams-permissions.md @@ -56,6 +56,34 @@ class TeamsPermission **YOU MUST ALSO** set [the `$middlewarePriority` array](https://laravel.com/docs/master/middleware#sorting-middleware) in `app/Http/Kernel.php` to include your custom middleware before the `SubstituteBindings` middleware, else you may get *404 Not Found* responses when a *403 Not Authorized* response might be expected. +For example, in Laravel 11.27+ you can add something similiar to the `boot` method of your `AppServiceProvider`. + +```php +use App\Http\Middleware\YourCustomMiddlewareClass; +use Illuminate\Foundation\Http\Kernel; +use Illuminate\Routing\Middleware\SubstituteBindings; +use Illuminate\Support\ServiceProvider; + +class AppServiceProvider extends ServiceProvider +{ + public function register(): void + { + // + } + + public function boot(): void + { + /** @var Kernel $kernel */ + $kernel = app()->make(Kernel::class); + + $kernel->addToMiddlewarePriorityBefore( + SubstituteBindings::class, + YourCustomMiddlewareClass::class, + ); + } +} +``` + ## Roles Creating When creating a role you can pass the `team_id` as an optional parameter From d97529b30b1530dd7cd1ca2d5aecec46ad0cc378 Mon Sep 17 00:00:00 2001 From: Marc Leonhard Date: Tue, 22 Oct 2024 08:47:38 +0200 Subject: [PATCH 531/648] Update using-policies.md Remove not needed brackets in return statement --- docs/best-practices/using-policies.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/best-practices/using-policies.md b/docs/best-practices/using-policies.md index c967ca9db..648ad1971 100644 --- a/docs/best-practices/using-policies.md +++ b/docs/best-practices/using-policies.md @@ -44,7 +44,7 @@ class PostPolicy public function create(User $user): bool { - return ($user->can('create posts')); + return $user->can('create posts'); } public function update(User $user, Post $post): bool From 7cd173b74b77af25b785651612fd263b4e0dc07b Mon Sep 17 00:00:00 2001 From: erikn69 Date: Wed, 30 Oct 2024 14:54:37 -0500 Subject: [PATCH 532/648] Fix GuardDoesNotMatch should accept collection --- src/Models/Role.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Models/Role.php b/src/Models/Role.php index e29e2d449..6bd47b75f 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -185,7 +185,7 @@ public function hasPermissionTo($permission, ?string $guardName = null): bool $permission = $this->filterPermission($permission, $guardName); if (! $this->getGuardNames()->contains($permission->guard_name)) { - throw GuardDoesNotMatch::create($permission->guard_name, $guardName ?? $this->getGuardNames()); + throw GuardDoesNotMatch::create($permission->guard_name, $guardName ? collect([$guardName]) : $this->getGuardNames()); } return $this->permissions->contains($permission->getKeyName(), $permission->getKey()); From eb8795c39ea3c9a401b5fef720c52ab2ca095e82 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Wed, 30 Oct 2024 15:00:32 -0500 Subject: [PATCH 533/648] PHP 8.4 tests --- .github/workflows/run-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 38d97615f..842b1ca98 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -9,7 +9,7 @@ jobs: strategy: fail-fast: false matrix: - php: [8.3, 8.2, 8.1, 8.0] + php: [8.4, 8.3, 8.2, 8.1, 8.0] laravel: ["^11.0", "^10.0", "^9.0", "^8.12"] dependency-version: [prefer-lowest, prefer-stable] include: From 4772ff80d38c388a736972bdd63a0c0f8a429162 Mon Sep 17 00:00:00 2001 From: Paul Radt Date: Thu, 31 Oct 2024 09:13:22 +0100 Subject: [PATCH 534/648] Improve performance by using clone in stead of the newInstance method --- src/PermissionRegistrar.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index 383629906..a73223762 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -360,7 +360,7 @@ private function getHydratedPermissionCollection(): Collection $permissionInstance = new ($this->getPermissionClass())(); return Collection::make(array_map( - fn ($item) => $permissionInstance->newInstance([], true) + fn ($item) => (clone $permissionInstance) ->setRawAttributes($this->aliasedArray(array_diff_key($item, ['r' => 0])), true) ->setRelation('roles', $this->getHydratedRoleCollection($item['r'] ?? [])), $this->permissions['permissions'] @@ -379,7 +379,7 @@ private function hydrateRolesCache(): void $roleInstance = new ($this->getRoleClass())(); array_map(function ($item) use ($roleInstance) { - $role = $roleInstance->newInstance([], true) + $role = (clone $roleInstance) ->setRawAttributes($this->aliasedArray($item), true); $this->cachedRoles[$role->getKey()] = $role; }, $this->permissions['roles']); From 5f483f51204bd17b89372e03ca3472e1cebac8bf Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 4 Nov 2024 08:27:43 -0800 Subject: [PATCH 535/648] Fix typo Fixed a very serious issue <3 --- src/PermissionRegistrar.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index a73223762..45d5d2238 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -199,7 +199,7 @@ private function loadPermissions(): void $this->cacheKey, $this->cacheExpirationTime, fn () => $this->getSerializedPermissionsForCache() ); - // fallback for old cache method, must be removed on next mayor version + // fallback for old cache method, must be removed on next major version if (! isset($this->permissions['alias'])) { $this->forgetCachedPermissions(); $this->loadPermissions(); From e8b97e350cdf8be233f48470c2172919d7cfc73d Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Mon, 4 Nov 2024 18:43:28 -0500 Subject: [PATCH 536/648] Remove v5 cache fallback Ref #1912 --- src/PermissionRegistrar.php | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index 45d5d2238..643ed92ea 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -199,14 +199,6 @@ private function loadPermissions(): void $this->cacheKey, $this->cacheExpirationTime, fn () => $this->getSerializedPermissionsForCache() ); - // fallback for old cache method, must be removed on next major version - if (! isset($this->permissions['alias'])) { - $this->forgetCachedPermissions(); - $this->loadPermissions(); - - return; - } - $this->alias = $this->permissions['alias']; $this->hydrateRolesCache(); From 2444bb914a52c570c00ae8c94e096a58e01b2317 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 5 Nov 2024 12:30:49 -0500 Subject: [PATCH 537/648] Include larastan (#2755) * Include larastan --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index bd29fa40e..bd7d0299a 100644 --- a/composer.json +++ b/composer.json @@ -29,6 +29,7 @@ "illuminate/database": "^8.12|^9.0|^10.0|^11.0" }, "require-dev": { + "larastan/larastan": "^1.0|^2.0", "laravel/passport": "^11.0|^12.0", "orchestra/testbench": "^6.23|^7.0|^8.0|^9.0", "phpunit/phpunit": "^9.4|^10.1" From 93477f5cc2d1ae9730254349443f0f07df694d63 Mon Sep 17 00:00:00 2001 From: drbyte Date: Tue, 5 Nov 2024 17:38:35 +0000 Subject: [PATCH 538/648] Update CHANGELOG --- CHANGELOG.md | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3c7c41a1..d50a3b359 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,44 @@ All notable changes to `laravel-permission` will be documented in this file +## 6.10.0 - 2024-11-05 + +### What's Changed + +* Fix `GuardDoesNotMatch should accept collection` by @erikn69 in https://github.com/spatie/laravel-permission/pull/2748 +* Improve performance for hydrated collections by @inserve-paul in https://github.com/spatie/laravel-permission/pull/2749 +* Only show error if `cache key exists` and `forgetCachedPermissions` fails by @erikn69 in https://github.com/spatie/laravel-permission/pull/2707 +* Remove v5 cache fallback alias by @drbyte in https://github.com/spatie/laravel-permission/pull/2754 +* Include `Larastan` in `dev` by @drbyte in https://github.com/spatie/laravel-permission/pull/2755 + +#### Docs + +* [Docs example] Check for 'all' or 'any' permissions before specific permissions by @ceilidhboy in https://github.com/spatie/laravel-permission/pull/2694 +* [Docs] Fix typo in uuid.md by @levizoesch in https://github.com/spatie/laravel-permission/pull/2705 +* [Docs] Upgrade Guide - Add PR links to upgrade guide by @mraheelkhan in https://github.com/spatie/laravel-permission/pull/2716 +* [Docs] use more modern syntax for nullable return type by @galangaidilakbar in https://github.com/spatie/laravel-permission/pull/2719 +* [Docs] camelCase variable naming in example by @KamilWojtalak in https://github.com/spatie/laravel-permission/pull/2723 +* [Docs] Update using-policies.md by @marcleonhard in https://github.com/spatie/laravel-permission/pull/2741 +* [Docs] Example of pushing custom middleware before SubstituteBindings middleware by @WyattCast44 in https://github.com/spatie/laravel-permission/pull/2740 + +#### Other + +* PHP 8.4 tests by @erikn69 in https://github.com/spatie/laravel-permission/pull/2747 +* Fix comment typo by @machacekmartin in https://github.com/spatie/laravel-permission/pull/2753 + +### New Contributors + +* @ceilidhboy made their first contribution in https://github.com/spatie/laravel-permission/pull/2694 +* @levizoesch made their first contribution in https://github.com/spatie/laravel-permission/pull/2705 +* @galangaidilakbar made their first contribution in https://github.com/spatie/laravel-permission/pull/2719 +* @KamilWojtalak made their first contribution in https://github.com/spatie/laravel-permission/pull/2723 +* @marcleonhard made their first contribution in https://github.com/spatie/laravel-permission/pull/2741 +* @WyattCast44 made their first contribution in https://github.com/spatie/laravel-permission/pull/2740 +* @inserve-paul made their first contribution in https://github.com/spatie/laravel-permission/pull/2749 +* @machacekmartin made their first contribution in https://github.com/spatie/laravel-permission/pull/2753 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/6.9.0...6.10.0 + ## 6.9.0 - 2024-06-22 ### What's Changed @@ -843,6 +881,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` @@ -912,6 +951,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` From 8bb69d6d67387f7a00d93a2f5fab98860f06e704 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Fri, 8 Nov 2024 13:45:41 -0500 Subject: [PATCH 539/648] Fix: #2749 bug "Can no longer delete permissions" (#2759) * Fix: #2749 bug * Test added --- src/PermissionRegistrar.php | 4 ++-- tests/PermissionTest.php | 11 +++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index 643ed92ea..cc2e1ca5f 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -349,7 +349,7 @@ private function getSerializedRoleRelation($permission): array private function getHydratedPermissionCollection(): Collection { - $permissionInstance = new ($this->getPermissionClass())(); + $permissionInstance = (new ($this->getPermissionClass())())->newInstance([], true); return Collection::make(array_map( fn ($item) => (clone $permissionInstance) @@ -368,7 +368,7 @@ private function getHydratedRoleCollection(array $roles): Collection private function hydrateRolesCache(): void { - $roleInstance = new ($this->getRoleClass())(); + $roleInstance = (new ($this->getRoleClass())())->newInstance([], true); array_map(function ($item) use ($roleInstance) { $role = (clone $roleInstance) diff --git a/tests/PermissionTest.php b/tests/PermissionTest.php index b23094544..d43645730 100644 --- a/tests/PermissionTest.php +++ b/tests/PermissionTest.php @@ -67,4 +67,15 @@ public function it_is_retrievable_by_id() $this->assertEquals($this->testUserPermission->id, $permission_by_id->id); } + + /** @test */ + public function it_can_delete_hydrated_permissions() + { + $this->reloadPermissions(); + + $permission = app(Permission::class)->findByName($this->testUserPermission->name); + $permission->delete(); + + $this->assertCount(0, app(Permission::class)->where($this->testUserPermission->getKeyName(), $this->testUserPermission->getKey())->get()); + } } From 516e7bde2f2f7e75a51cb11bdae7759ecb03f4b6 Mon Sep 17 00:00:00 2001 From: drbyte Date: Fri, 8 Nov 2024 18:48:33 +0000 Subject: [PATCH 540/648] Update CHANGELOG --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d50a3b359..094cdbc55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to `laravel-permission` will be documented in this file +## 6.10.1 - 2024-11-08 + +### What's Changed + +* Fix #2749 regression bug in `6.10.0` : "Can no longer delete permissions" by @erikn69 in https://github.com/spatie/laravel-permission/pull/2759 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/6.10.0...6.10.1 + ## 6.10.0 - 2024-11-05 ### What's Changed @@ -882,6 +890,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` @@ -952,6 +961,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` From 4981e716642d11988bd0402160c14f18c2f389d8 Mon Sep 17 00:00:00 2001 From: Ken <26869657+ken-tam@users.noreply.github.com> Date: Tue, 19 Nov 2024 08:47:52 -0800 Subject: [PATCH 541/648] Update uuid.md (#2764) fix class location --- docs/advanced-usage/uuid.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/advanced-usage/uuid.md b/docs/advanced-usage/uuid.md index 18fdd82f4..806df99d6 100644 --- a/docs/advanced-usage/uuid.md +++ b/docs/advanced-usage/uuid.md @@ -165,7 +165,7 @@ And edit `config/permission.php` */ - 'permission' => Spatie\Permission\Models\Permission::class -+ 'permission' => App\Models\Permission::class, ++ 'permission' => \App\Models\Permission::class, /* * When using the "HasRoles" trait from this package, we need to know which @@ -177,7 +177,7 @@ And edit `config/permission.php` */ - 'role' => Spatie\Permission\Models\Role::class, -+ 'role' => App\Models\Role::class, ++ 'role' => \App\Models\Role::class, ], ``` From 52119a132fbc210ebb0dc07a5eb672df6cc1bb6c Mon Sep 17 00:00:00 2001 From: Iwobi Okwudili Frank <102930821+frankliniwobi@users.noreply.github.com> Date: Wed, 27 Nov 2024 22:25:28 +0100 Subject: [PATCH 542/648] [Docs] Include Laravel 11 example in exceptions.md (#2768) * include a documentation for customizing exception in laravel 11 --------- Co-authored-by: Chris Brown --- docs/advanced-usage/exceptions.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/docs/advanced-usage/exceptions.md b/docs/advanced-usage/exceptions.md index 291f85feb..231159107 100644 --- a/docs/advanced-usage/exceptions.md +++ b/docs/advanced-usage/exceptions.md @@ -3,14 +3,14 @@ title: Exceptions weight: 3 --- -If you need to override exceptions thrown by this package, you can simply use normal [Laravel practices for handling exceptions](https://laravel.com/docs/10.x/errors#rendering-exceptions). +If you need to override exceptions thrown by this package, you can simply use normal [Laravel practices for handling exceptions](https://laravel.com/docs/errors#rendering-exceptions). An example is shown below for your convenience, but nothing here is specific to this package other than the name of the exception. You can find all the exceptions added by this package in the code here: [https://github.com/spatie/laravel-permission/tree/main/src/Exceptions](https://github.com/spatie/laravel-permission/tree/main/src/Exceptions) -**app/Exceptions/Handler.php** +**Laravel 10: app/Exceptions/Handler.php** ```php public function register() @@ -23,3 +23,16 @@ public function register() }); } ``` + +**Laravel 11: bootstrap/app.php** +```php + +->withExceptions(function (Exceptions $exceptions) { + $exceptions->render(function (\Spatie\Permission\Exceptions\UnauthorizedException $e, $request) { + return response()->json([ + 'responseMessage' => 'You do not have the required authorization.', + 'responseStatus' => 403, + ]); + }); +} +``` From a002fbacb2cde1350142138862910ba41b2090d6 Mon Sep 17 00:00:00 2001 From: Iman Date: Tue, 3 Dec 2024 22:05:49 +0330 Subject: [PATCH 543/648] Update upgrading.md `composer update` will upgrade all the packages which can be problematic or unwanted. Other non-technical changes are suggested by Grammarly. --- docs/upgrading.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/upgrading.md b/docs/upgrading.md index 2d56f9b18..f0b58f64c 100644 --- a/docs/upgrading.md +++ b/docs/upgrading.md @@ -7,9 +7,9 @@ weight: 6 ALL upgrades of this package should follow these steps: -1. Composer. Upgrading between major versions of this package always require the usual Composer steps: +1. Composer. Upgrading between major versions of this package always requires the usual Composer steps: - Update your `composer.json` to specify the new major version, for example: `^6.0` - - Then run `composer update`. + - Then run `composer update spatie/laravel-permission`. 2. Migrations. Compare the `migration` file stubs in the NEW version of this package against the migrations you've already run inside your app. If necessary, create a new migration (by hand) to apply any new database changes. @@ -32,16 +32,16 @@ There are a few breaking-changes when upgrading to v6, but most of them won't af For guidance with upgrading your extended models, your migrations, your routes, etc, see the **Upgrade Essentials** section at the top of this file. -1. Due to the improved ULID/UUID/GUID support, any package methods which accept a Permission or Role `id` must pass that `id` as an `integer`. If you pass it as a numeric string, the functions will attempt to lookup the role/permission as a string. In such cases you may see errors such as `There is no permission named '123' for guard 'web'.` (where `'123'` is being treated as a string because it was passed as a string instead of as an integer). This also applies to arrays of id's: if it's an array of strings we will do a lookup on the name instead of on the id. **This will mostly only affect UI pages** because an HTML Request is received as string data. **The solution is simple:** if you're passing integers to a form field, then convert them back to integers when using that field's data for calling functions to grant/assign/sync/remove/revoke permissions and roles. One way to convert an array of permissions `id`'s from strings to integers is: `collect($validated['permission'])->map(fn($val)=>(int)$val)` +1. Due to the improved ULID/UUID/GUID support, any package methods which accept a Permission or Role `id` must pass that `id` as an `integer`. If you pass it as a numeric string, the functions will attempt to look up the role/permission as a string. In such cases, you may see errors such as `There is no permission named '123' for guard 'web'.` (where `'123'` is being treated as a string because it was passed as a string instead of as an integer). This also applies to arrays of id's: if it's an array of strings we will do a lookup on the name instead of on the id. **This will mostly only affect UI pages** because an HTML Request is received as string data. **The solution is simple:** if you're passing integers to a form field, then convert them back to integers when using that field's data for calling functions to grant/assign/sync/remove/revoke permissions and roles. One way to convert an array of permissions `id`'s from strings to integers is: `collect($validated['permission'])->map(fn($val)=>(int)$val)` 2. If you have overridden the `getPermissionClass()` or `getRoleClass()` methods or have custom Models, you will need to revisit those customizations. See PR #2368 for details. eg: if you have a custom model you will need to make changes, including accessing the model using `$this->permissionClass::` syntax (eg: using `::` instead of `->`) in all the overridden methods that make use of the models. - Be sure to compare your custom models with originals to see what else may have changed. + Be sure to compare your custom models with the originals to see what else may have changed. 3. Model and Contract/Interface updates. The Role and Permission Models and Contracts/Interfaces have been updated with syntax changes to method signatures. Update any models you have extended, or contracts implemented, accordingly. See PR [#2380](https://github.com/spatie/laravel-permission/pull/2380) and [#2480](https://github.com/spatie/laravel-permission/pull/2480) for some of the specifics. -4. Migrations WILL need to be upgraded. (They have been updated to anonymous-class syntax that was introduced in Laravel 8, AND some structural coding changes in the registrar class changed the way we extracted configuration settings in the migration files.) There are no changes to the package's structure since v5, so if you had not customized it from the original then replacing the contents of the file should be enough. (Usually the only customization is if you've switched to UUIDs or customized MySQL index name lengths.) +4. Migrations WILL need to be upgraded. (They have been updated to anonymous-class syntax that was introduced in Laravel 8, AND some structural coding changes in the registrar class changed the way we extracted configuration settings in the migration files.) There are no changes to the package's structure since v5, so if you had not customized it from the original then replacing the contents of the file should be enough. (Usually, the only customization is if you've switched to UUIDs or customized MySQL index name lengths.) **If you get the following error, it means your migration file needs upgrading: `Error: Access to undeclared static property Spatie\Permission\PermissionRegistrar::$pivotPermission`** 5. MIDDLEWARE: @@ -52,7 +52,7 @@ eg: if you have a custom model you will need to make changes, including accessin 3. In the unlikely event that you have customized the Wildcard Permissions feature by extending the `WildcardPermission` model, please note that the public interface has changed significantly and you will need to update your extended model with the new method signatures. -6. Test suites. If you have tests which manually clear the permission cache and re-register permissions, you no longer need to call `\Spatie\Permission\PermissionRegistrar::class)->registerPermissions();`. In fact, **calls to `->registerPermissions()` MUST be deleted from your tests**. +6. Test suites. If you have tests that manually clear the permission cache and re-register permissions, you no longer need to call `\Spatie\Permission\PermissionRegistrar::class)->registerPermissions();`. In fact, **calls to `->registerPermissions()` MUST be deleted from your tests**. (Calling `app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();` after creating roles and permissions in migrations and factories and seeders is still okay and encouraged.) From ee71301a43c0b7e91e01e59895679a952d7a0c87 Mon Sep 17 00:00:00 2001 From: Robert Brodie Date: Thu, 26 Dec 2024 10:47:28 -0500 Subject: [PATCH 544/648] Replace php-cs-fixer with Laravel Pint (#2780) * Replace php-cs-fixer with Laravel Pint * Require laravel/pint ^1.0 to support PHP 8.0 like the main package --- composer.json | 3 ++- pint.json | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 pint.json diff --git a/composer.json b/composer.json index bd7d0299a..b6d404d75 100644 --- a/composer.json +++ b/composer.json @@ -31,6 +31,7 @@ "require-dev": { "larastan/larastan": "^1.0|^2.0", "laravel/passport": "^11.0|^12.0", + "laravel/pint": "^1.0", "orchestra/testbench": "^6.23|^7.0|^8.0|^9.0", "phpunit/phpunit": "^9.4|^10.1" }, @@ -65,7 +66,7 @@ }, "scripts": { "test": "phpunit", - "format": "php-cs-fixer fix --allow-risky=yes", + "format": "pint", "analyse": "phpstan analyse" } } diff --git a/pint.json b/pint.json new file mode 100644 index 000000000..ea56b7b88 --- /dev/null +++ b/pint.json @@ -0,0 +1,6 @@ +{ + "preset": "laravel", + "rules": { + "php_unit_method_casing": false + } +} From b7ab3b774f4ede85aabd058bf06e43caaaf8988a Mon Sep 17 00:00:00 2001 From: m3skalina <43817736+m3skalina@users.noreply.github.com> Date: Thu, 2 Jan 2025 18:29:29 +0100 Subject: [PATCH 545/648] Update passport.md semicolon added --- docs/basic-usage/passport.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/basic-usage/passport.md b/docs/basic-usage/passport.md index 1926f1273..9bfe86d50 100644 --- a/docs/basic-usage/passport.md +++ b/docs/basic-usage/passport.md @@ -29,7 +29,7 @@ class Client extends BaseClient implements AuthorizableContract public function guardName() { - return 'api' + return 'api'; } } ``` From c1284ba07e74b05cfc63cd2958411ce659451609 Mon Sep 17 00:00:00 2001 From: Mike Date: Sun, 5 Jan 2025 19:39:18 +0100 Subject: [PATCH 546/648] Update new-app.md The user created is tester@example.com; not test@example.com --- docs/basic-usage/new-app.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/basic-usage/new-app.md b/docs/basic-usage/new-app.md index 23903c3ab..7f1ed8361 100644 --- a/docs/basic-usage/new-app.md +++ b/docs/basic-usage/new-app.md @@ -160,7 +160,7 @@ If you are creating a demo app for reporting a bug or getting help with troubles If this is your first app with this package, you may want some quick permission examples to see it in action. If you've set up your app using the instructions above, the following examples will work in conjunction with the users and permissions created in the seeder. -Three users were created: test@example.com, admin@example.com, superadmin@example.com and the password for each is "password". +Three users were created: tester@example.com, admin@example.com, superadmin@example.com and the password for each is "password". `/resources/views/dashboard.php` ```diff From d4b9db425badb874742d09de19fa6e3ee8d6dd29 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 10 Jan 2025 14:14:19 -0500 Subject: [PATCH 547/648] [Docs] Add related article --- docs/best-practices/roles-vs-permissions.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/best-practices/roles-vs-permissions.md b/docs/best-practices/roles-vs-permissions.md index ad94432aa..033d3eb52 100644 --- a/docs/best-practices/roles-vs-permissions.md +++ b/docs/best-practices/roles-vs-permissions.md @@ -32,3 +32,6 @@ Summary: Sometimes certain groups of `route` rules may make best sense to group them around a `role`, but still, whenever possible, there is less overhead used if you can check against a specific `permission` instead. +### FURTHER READING: + +[@joelclermont](https://github.com/joelclermont) at [masteringlaravel.io](https://masteringlaravel.io/daily) offers similar guidance in his post about [Treating Feature Access As Data, Not Code](https://masteringlaravel.io/daily/2025-01-09-treat-feature-access-as-data-not-code) From ba95ccd32e23add451b71212c929de014627e085 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 10 Jan 2025 14:20:05 -0500 Subject: [PATCH 548/648] Clarification for Laravel 11 optional provider registration Replaces and Closes #2786 --- docs/installation-laravel.md | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/docs/installation-laravel.md b/docs/installation-laravel.md index f7cb644f9..07e4ab54b 100644 --- a/docs/installation-laravel.md +++ b/docs/installation-laravel.md @@ -28,14 +28,7 @@ Package Version | Laravel Version composer require spatie/laravel-permission -4. Optional: The service provider will automatically get registered. Or you may manually add the service provider in your `config/app.php` file: - - ``` - 'providers' => [ - // ... - Spatie\Permission\PermissionServiceProvider::class, - ]; - ``` +4. Optional: The **`Spatie\Permission\PermissionServiceProvider::class`** service provider will automatically get registered. Or you may manually add the service provider to the array in your `config/providers.php` (or `config/app.php` in Laravel 10 or older) file. 5. **You should publish** [the migration](https://github.com/spatie/laravel-permission/blob/main/database/migrations/create_permission_tables.php.stub) and the [`config/permission.php` config file](https://github.com/spatie/laravel-permission/blob/main/config/permission.php) with: From ecd39dc304bf853456c99ad95b8906873b3b37d4 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 28 Jan 2025 14:01:35 -0500 Subject: [PATCH 549/648] Update package compatibilities --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index b6d404d75..8e7ea5cc4 100644 --- a/composer.json +++ b/composer.json @@ -29,11 +29,11 @@ "illuminate/database": "^8.12|^9.0|^10.0|^11.0" }, "require-dev": { - "larastan/larastan": "^1.0|^2.0", + "larastan/larastan": "^1.0|^2.0|^3.0", "laravel/passport": "^11.0|^12.0", "laravel/pint": "^1.0", "orchestra/testbench": "^6.23|^7.0|^8.0|^9.0", - "phpunit/phpunit": "^9.4|^10.1" + "phpunit/phpunit": "^9.4|^10.1|^11.5" }, "minimum-stability": "dev", "prefer-stable": true, From 1b67ee8d3c9faef6cf1b1cd8a3450261d36f1b08 Mon Sep 17 00:00:00 2001 From: Curious Team Date: Thu, 30 Jan 2025 01:13:39 +0600 Subject: [PATCH 550/648] Update installation-laravel.md to fix providers.php location. 4. Optional: The **`Spatie\Permission\PermissionServiceProvider::class`** service provider will automatically get registered. Or you may manually add the service provider to the array in your `bootstrap/providers.php` (or `config/app.php` in Laravel 10 or older) file. --- docs/installation-laravel.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation-laravel.md b/docs/installation-laravel.md index 07e4ab54b..54bac7b4b 100644 --- a/docs/installation-laravel.md +++ b/docs/installation-laravel.md @@ -28,7 +28,7 @@ Package Version | Laravel Version composer require spatie/laravel-permission -4. Optional: The **`Spatie\Permission\PermissionServiceProvider::class`** service provider will automatically get registered. Or you may manually add the service provider to the array in your `config/providers.php` (or `config/app.php` in Laravel 10 or older) file. +4. Optional: The **`Spatie\Permission\PermissionServiceProvider::class`** service provider will automatically get registered. Or you may manually add the service provider to the array in your `bootstrap/providers.php` (or `config/app.php` in Laravel 10 or older) file. 5. **You should publish** [the migration](https://github.com/spatie/laravel-permission/blob/main/database/migrations/create_permission_tables.php.stub) and the [`config/permission.php` config file](https://github.com/spatie/laravel-permission/blob/main/config/permission.php) with: From 34744b866c0d021dbc9077481693607893ea28c8 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Thu, 30 Jan 2025 15:19:23 -0500 Subject: [PATCH 551/648] phpstan - ignore --- src/PermissionServiceProvider.php | 1 + src/Traits/HasPermissions.php | 1 + src/Traits/HasRoles.php | 1 + 3 files changed, 3 insertions(+) diff --git a/src/PermissionServiceProvider.php b/src/PermissionServiceProvider.php index 1dc28f595..31a029f48 100644 --- a/src/PermissionServiceProvider.php +++ b/src/PermissionServiceProvider.php @@ -144,6 +144,7 @@ protected function registerBladeExtensions(BladeCompiler $bladeCompiler): void protected function registerMacroHelpers(): void { + // @phpstan-ignore-next-line if (! method_exists(Route::class, 'macro')) { // Lumen return; } diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index 25d506088..8147cac60 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -348,6 +348,7 @@ public function getAllPermissions(): Collection /** @var Collection $permissions */ $permissions = $this->permissions; + // @phpstan-ignore-next-line if (method_exists($this, 'roles')) { $permissions = $permissions->merge($this->getPermissionsViaRoles()); } diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index 782ed20a1..4a332739d 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -127,6 +127,7 @@ private function collectRoles(...$roles): array } $role = $this->getStoredRole($role); + // @phpstan-ignore-next-line if (! $role instanceof Role) { return $array; } From a629566b6624a3167ff7d95129f946b36cb8c39e Mon Sep 17 00:00:00 2001 From: drbyte <404472+drbyte@users.noreply.github.com> Date: Thu, 30 Jan 2025 20:19:49 +0000 Subject: [PATCH 552/648] Fix styling --- config/permission.php | 4 ++-- tests/CommandTest.php | 2 +- tests/HasPermissionsTest.php | 6 +++--- tests/HasPermissionsWithCustomModelsTest.php | 2 +- tests/HasRolesTest.php | 6 +++--- tests/TeamHasRolesTest.php | 8 ++++---- tests/TestCase.php | 8 ++++---- 7 files changed, 18 insertions(+), 18 deletions(-) diff --git a/config/permission.php b/config/permission.php index 2a520f351..74c6402d2 100644 --- a/config/permission.php +++ b/config/permission.php @@ -75,8 +75,8 @@ /* * Change this if you want to name the related pivots other than defaults */ - 'role_pivot_key' => null, //default 'role_id', - 'permission_pivot_key' => null, //default 'permission_id', + 'role_pivot_key' => null, // default 'role_id', + 'permission_pivot_key' => null, // default 'permission_id', /* * Change this if you want to name the related model primary key other than diff --git a/tests/CommandTest.php b/tests/CommandTest.php index a5bd5b558..4fbb6fa3a 100644 --- a/tests/CommandTest.php +++ b/tests/CommandTest.php @@ -149,7 +149,7 @@ public function it_can_setup_teams_upgrade() $AddTeamsFields = require $matchingFiles[count($matchingFiles) - 1]; $AddTeamsFields->up(); - $AddTeamsFields->up(); //test upgrade teams migration fresh + $AddTeamsFields->up(); // test upgrade teams migration fresh Role::create(['name' => 'new-role', 'team_test_id' => 1]); $role = Role::where('name', 'new-role')->first(); diff --git a/tests/HasPermissionsTest.php b/tests/HasPermissionsTest.php index ce197b16c..e5657536f 100644 --- a/tests/HasPermissionsTest.php +++ b/tests/HasPermissionsTest.php @@ -654,7 +654,7 @@ public function it_does_not_run_unnecessary_sqls_when_assigning_new_permissions( $this->testUser->syncPermissions($this->testUserPermission, $permission2); DB::disableQueryLog(); - $this->assertSame(2, count(DB::getQueryLog())); //avoid unnecessary sqls + $this->assertSame(2, count(DB::getQueryLog())); // avoid unnecessary sqls } /** @test */ @@ -676,7 +676,7 @@ public function calling_givePermissionTo_before_saving_object_doesnt_interfere_w $this->assertTrue($user2->fresh()->hasPermissionTo('edit-articles')); $this->assertFalse($user2->fresh()->hasPermissionTo('edit-news')); - $this->assertSame(2, count(DB::getQueryLog())); //avoid unnecessary sync + $this->assertSame(2, count(DB::getQueryLog())); // avoid unnecessary sync } /** @test */ @@ -698,7 +698,7 @@ public function calling_syncPermissions_before_saving_object_doesnt_interfere_wi $this->assertTrue($user2->fresh()->hasPermissionTo('edit-articles')); $this->assertFalse($user2->fresh()->hasPermissionTo('edit-news')); - $this->assertSame(2, count(DB::getQueryLog())); //avoid unnecessary sync + $this->assertSame(2, count(DB::getQueryLog())); // avoid unnecessary sync } /** @test */ diff --git a/tests/HasPermissionsWithCustomModelsTest.php b/tests/HasPermissionsWithCustomModelsTest.php index 60aa481e2..d26b92286 100644 --- a/tests/HasPermissionsWithCustomModelsTest.php +++ b/tests/HasPermissionsWithCustomModelsTest.php @@ -123,7 +123,7 @@ public function it_does_detach_roles_and_users_when_force_deleting() $this->testUserPermission->forceDelete(); DB::disableQueryLog(); - $this->assertSame(3 + $this->resetDatabaseQuery, count(DB::getQueryLog())); //avoid detach permissions on permissions + $this->assertSame(3 + $this->resetDatabaseQuery, count(DB::getQueryLog())); // avoid detach permissions on permissions $permission = Permission::withTrashed()->find($permission_id); diff --git a/tests/HasRolesTest.php b/tests/HasRolesTest.php index 93ee35749..c5f78169e 100644 --- a/tests/HasRolesTest.php +++ b/tests/HasRolesTest.php @@ -359,7 +359,7 @@ public function it_does_not_run_unnecessary_sqls_when_assigning_new_roles() $this->testUser->syncRoles($this->testUserRole, $role2); DB::disableQueryLog(); - $this->assertSame(2, count(DB::getQueryLog())); //avoid unnecessary sqls + $this->assertSame(2, count(DB::getQueryLog())); // avoid unnecessary sqls } /** @test */ @@ -381,7 +381,7 @@ public function calling_syncRoles_before_saving_object_doesnt_interfere_with_oth $this->assertTrue($user2->fresh()->hasRole('testRole2')); $this->assertFalse($user2->fresh()->hasRole('testRole')); - $this->assertSame(2, count(DB::getQueryLog())); //avoid unnecessary sync + $this->assertSame(2, count(DB::getQueryLog())); // avoid unnecessary sync } /** @test */ @@ -403,7 +403,7 @@ public function calling_assignRole_before_saving_object_doesnt_interfere_with_ot $this->assertTrue($admin_user->fresh()->hasRole('testRole2')); $this->assertFalse($admin_user->fresh()->hasRole('testRole')); - $this->assertSame(2, count(DB::getQueryLog())); //avoid unnecessary sync + $this->assertSame(2, count(DB::getQueryLog())); // avoid unnecessary sync } /** @test */ diff --git a/tests/TeamHasRolesTest.php b/tests/TeamHasRolesTest.php index 16d8cdd6d..6ad51ec4e 100644 --- a/tests/TeamHasRolesTest.php +++ b/tests/TeamHasRolesTest.php @@ -39,9 +39,9 @@ public function it_deletes_pivot_table_entries_when_deleting_models() /** @test */ public function it_can_assign_same_and_different_roles_on_same_user_different_teams() { - app(Role::class)->create(['name' => 'testRole3']); //team_test_id = 1 by main class + app(Role::class)->create(['name' => 'testRole3']); // team_test_id = 1 by main class app(Role::class)->create(['name' => 'testRole3', 'team_test_id' => 2]); - app(Role::class)->create(['name' => 'testRole4', 'team_test_id' => null]); //global role + app(Role::class)->create(['name' => 'testRole4', 'team_test_id' => null]); // global role $testRole3Team1 = app(Role::class)->where(['name' => 'testRole3', 'team_test_id' => 1])->first(); $testRole3Team2 = app(Role::class)->where(['name' => 'testRole3', 'team_test_id' => 2])->first(); @@ -66,7 +66,7 @@ public function it_can_assign_same_and_different_roles_on_same_user_different_te $this->testUser->assignRole('testRole3', 'testRole4'); $this->assertTrue($this->testUser->hasExactRoles(['testRole', 'testRole2', 'testRole3', 'testRole4'])); - $this->assertTrue($this->testUser->hasRole($testRole3Team1)); //testRole3 team=1 + $this->assertTrue($this->testUser->hasRole($testRole3Team1)); // testRole3 team=1 $this->assertTrue($this->testUser->hasRole($testRole4NoTeam)); // global role team=null setPermissionsTeamId(2); @@ -77,7 +77,7 @@ public function it_can_assign_same_and_different_roles_on_same_user_different_te $this->testUser->getRoleNames()->sort()->values() ); $this->assertTrue($this->testUser->hasExactRoles(['testRole', 'testRole3'])); - $this->assertTrue($this->testUser->hasRole($testRole3Team2)); //testRole3 team=2 + $this->assertTrue($this->testUser->hasRole($testRole3Team2)); // testRole3 team=2 $this->testUser->assignRole('testRole4'); $this->assertTrue($this->testUser->hasExactRoles(['testRole', 'testRole3', 'testRole4'])); $this->assertTrue($this->testUser->hasRole($testRole4NoTeam)); // global role team=null diff --git a/tests/TestCase.php b/tests/TestCase.php index 9e5e36c34..b6ec94970 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -112,7 +112,7 @@ protected function getEnvironmentSetUp($app) { $app['config']->set('permission.register_permission_check_method', true); $app['config']->set('permission.teams', $this->hasTeams); - $app['config']->set('permission.testing', true); //fix sqlite + $app['config']->set('permission.testing', true); // fix sqlite $app['config']->set('permission.column_names.model_morph_key', 'model_test_id'); $app['config']->set('permission.column_names.team_foreign_key', 'team_test_id'); $app['config']->set('database.default', 'sqlite'); @@ -143,8 +143,8 @@ protected function getEnvironmentSetUp($app) // FOR MANUAL TESTING OF ALTERNATE CACHE STORES: // $app['config']->set('cache.default', 'array'); - //Laravel supports: array, database, file - //requires extensions: apc, memcached, redis, dynamodb, octane + // Laravel supports: array, database, file + // requires extensions: apc, memcached, redis, dynamodb, octane } /** @@ -282,7 +282,7 @@ public function setUpRoutes(): void }); } - ////// TEST HELPERS + // //// TEST HELPERS public function runMiddleware($middleware, $permission, $guard = null, bool $client = false) { $request = new Request; From 3cea227902152c43b31bf403cdaf60d8fd2b41b3 Mon Sep 17 00:00:00 2001 From: Garrett Date: Thu, 30 Jan 2025 15:26:56 -0500 Subject: [PATCH 553/648] Add configurable team resolver for permission team id (#2790) * Add Permissions Team Resolver Implementation - Introduced a new `PermissionsTeamResolver` interface to standardize team ID handling for permissions. - Implemented `DefaultPermissionsTeamResolver` class to manage team IDs. - Updated `permission.php` configuration to include `team_resolver` setting. - Modified `PermissionRegistrar` to utilize the new team resolver for setting and getting permissions team IDs. --- config/permission.php | 6 +++++ src/Contracts/PermissionsTeamResolver.php | 18 +++++++++++++ src/DefaultTeamResolver.php | 31 +++++++++++++++++++++++ src/PermissionRegistrar.php | 13 +++++----- 4 files changed, 61 insertions(+), 7 deletions(-) create mode 100644 src/Contracts/PermissionsTeamResolver.php create mode 100644 src/DefaultTeamResolver.php diff --git a/config/permission.php b/config/permission.php index 74c6402d2..c05c52645 100644 --- a/config/permission.php +++ b/config/permission.php @@ -122,6 +122,12 @@ 'teams' => false, + + /* + * The class to use to resolve the permissions team id + */ + 'team_resolver' => \Spatie\Permission\DefaultTeamResolver::class, + /* * Passport Client Credentials Grant * When set to true the package will use Passports Client to check permissions diff --git a/src/Contracts/PermissionsTeamResolver.php b/src/Contracts/PermissionsTeamResolver.php new file mode 100644 index 000000000..1c8fe8985 --- /dev/null +++ b/src/Contracts/PermissionsTeamResolver.php @@ -0,0 +1,18 @@ +getKey(); + } + $this->teamId = $id; + } + + /** + * @return int|string|null + */ + public function getPermissionsTeamId() : int|string|null + { + return $this->teamId; + } +} diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index cc2e1ca5f..92e6edc4e 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -10,6 +10,7 @@ use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Model; use Spatie\Permission\Contracts\Permission; +use Spatie\Permission\Contracts\PermissionsTeamResolver; use Spatie\Permission\Contracts\Role; class PermissionRegistrar @@ -34,9 +35,9 @@ class PermissionRegistrar public bool $teams; - public string $teamsKey; + protected PermissionsTeamResolver $teamResolver; - protected string|int|null $teamId = null; + public string $teamsKey; public string $cacheKey; @@ -55,6 +56,7 @@ public function __construct(CacheManager $cacheManager) { $this->permissionClass = config('permission.models.permission'); $this->roleClass = config('permission.models.role'); + $this->teamResolver = new (config('permission.team_resolver', DefaultTeamResolver::class)); $this->cacheManager = $cacheManager; $this->initializeCache(); @@ -101,10 +103,7 @@ protected function getCacheStoreFromConfig(): Repository */ public function setPermissionsTeamId($id): void { - if ($id instanceof \Illuminate\Database\Eloquent\Model) { - $id = $id->getKey(); - } - $this->teamId = $id; + $this->teamResolver->setPermissionsTeamId($id); } /** @@ -112,7 +111,7 @@ public function setPermissionsTeamId($id): void */ public function getPermissionsTeamId() { - return $this->teamId; + return $this->teamResolver->getPermissionsTeamId(); } /** From f9f8c230a4e61dfcc81273d1485b05dc2082badc Mon Sep 17 00:00:00 2001 From: drbyte <404472+drbyte@users.noreply.github.com> Date: Thu, 30 Jan 2025 20:27:22 +0000 Subject: [PATCH 554/648] Fix styling --- config/permission.php | 1 - src/Contracts/PermissionsTeamResolver.php | 5 +---- src/DefaultTeamResolver.php | 5 +---- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/config/permission.php b/config/permission.php index c05c52645..c3b69a5ec 100644 --- a/config/permission.php +++ b/config/permission.php @@ -122,7 +122,6 @@ 'teams' => false, - /* * The class to use to resolve the permissions team id */ diff --git a/src/Contracts/PermissionsTeamResolver.php b/src/Contracts/PermissionsTeamResolver.php index 1c8fe8985..780cd2a1d 100644 --- a/src/Contracts/PermissionsTeamResolver.php +++ b/src/Contracts/PermissionsTeamResolver.php @@ -4,10 +4,7 @@ interface PermissionsTeamResolver { - /** - * @return int|string|null - */ - public function getPermissionsTeamId() : int|string|null; + public function getPermissionsTeamId(): int|string|null; /** * Set the team id for teams/groups support, this id is used when querying permissions/roles diff --git a/src/DefaultTeamResolver.php b/src/DefaultTeamResolver.php index 93bf5481a..889898e2c 100644 --- a/src/DefaultTeamResolver.php +++ b/src/DefaultTeamResolver.php @@ -21,10 +21,7 @@ public function setPermissionsTeamId($id): void $this->teamId = $id; } - /** - * @return int|string|null - */ - public function getPermissionsTeamId() : int|string|null + public function getPermissionsTeamId(): int|string|null { return $this->teamId; } From 1da8aeedb1f84c49d5df01f5f1637b00e60c77ce Mon Sep 17 00:00:00 2001 From: drbyte <404472+drbyte@users.noreply.github.com> Date: Thu, 30 Jan 2025 20:32:26 +0000 Subject: [PATCH 555/648] Update CHANGELOG --- CHANGELOG.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 094cdbc55..62c8daaac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,38 @@ All notable changes to `laravel-permission` will be documented in this file +## 6.11.0 - 2025-01-30 + +### What's Changed + +* Add configurable team resolver for permission team id (helpful for Jetstream, etc) by @adrenallen in https://github.com/spatie/laravel-permission/pull/2790 + +### Internals + +* Replace php-cs-fixer with Laravel Pint by @bobbrodie in https://github.com/spatie/laravel-permission/pull/2780 + +### Documentation Updates + +* [Docs] Include namespace in example in uuid.md by @ken-tam in https://github.com/spatie/laravel-permission/pull/2764 +* [Docs] Include Laravel 11 example in exceptions.md by @frankliniwobi in https://github.com/spatie/laravel-permission/pull/2768 +* [Docs] Fix typo in code example in passport.md by @m3skalina in https://github.com/spatie/laravel-permission/pull/2782 +* [Docs] Correct username in new-app.md by @trippodi in https://github.com/spatie/laravel-permission/pull/2785 +* [Docs] Add composer specificity by @imanghafoori1 in https://github.com/spatie/laravel-permission/pull/2772 +* [Docs] Update installation-laravel.md to fix providers.php location. by @curiousteam in https://github.com/spatie/laravel-permission/pull/2796 + +### New Contributors + +* @ken-tam made their first contribution in https://github.com/spatie/laravel-permission/pull/2764 +* @frankliniwobi made their first contribution in https://github.com/spatie/laravel-permission/pull/2768 +* @bobbrodie made their first contribution in https://github.com/spatie/laravel-permission/pull/2780 +* @m3skalina made their first contribution in https://github.com/spatie/laravel-permission/pull/2782 +* @trippodi made their first contribution in https://github.com/spatie/laravel-permission/pull/2785 +* @imanghafoori1 made their first contribution in https://github.com/spatie/laravel-permission/pull/2772 +* @curiousteam made their first contribution in https://github.com/spatie/laravel-permission/pull/2796 +* @adrenallen made their first contribution in https://github.com/spatie/laravel-permission/pull/2790 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/6.10.1...6.11.0 + ## 6.10.1 - 2024-11-08 ### What's Changed @@ -891,6 +923,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` @@ -962,6 +995,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` From f1d198a2e929be1185700146c725a703832716f1 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Thu, 30 Jan 2025 15:49:18 -0500 Subject: [PATCH 556/648] Remove larastan as dependency Larastan composer is not updated yet to support latest Laravel, so leaving Larastan out of this package's dependencies allows the package to be used/tested regardless of larastan's availability. The phpstan workflow already will load larastan when needed, so remains compatible with this change. --- composer.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 8e7ea5cc4..f9adb59a4 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,6 @@ "illuminate/database": "^8.12|^9.0|^10.0|^11.0" }, "require-dev": { - "larastan/larastan": "^1.0|^2.0|^3.0", "laravel/passport": "^11.0|^12.0", "laravel/pint": "^1.0", "orchestra/testbench": "^6.23|^7.0|^8.0|^9.0", @@ -67,6 +66,6 @@ "scripts": { "test": "phpunit", "format": "pint", - "analyse": "phpstan analyse" + "analyse": "echo 'Checking dependencies...' && composer require --dev larastan/larastan && phpstan analyse" } } From cbeecfc0452ee8e588fceba45cc18c2e94cd3abd Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Thu, 30 Jan 2025 15:52:16 -0500 Subject: [PATCH 557/648] Add Laravel 12 --- composer.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index f9adb59a4..c04c5bff9 100644 --- a/composer.json +++ b/composer.json @@ -23,15 +23,15 @@ "homepage": "https://github.com/spatie/laravel-permission", "require": { "php": "^8.0", - "illuminate/auth": "^8.12|^9.0|^10.0|^11.0", - "illuminate/container": "^8.12|^9.0|^10.0|^11.0", - "illuminate/contracts": "^8.12|^9.0|^10.0|^11.0", - "illuminate/database": "^8.12|^9.0|^10.0|^11.0" + "illuminate/auth": "^8.12|^9.0|^10.0|^11.0|^12.0", + "illuminate/container": "^8.12|^9.0|^10.0|^11.0|^12.0", + "illuminate/contracts": "^8.12|^9.0|^10.0|^11.0|^12.0", + "illuminate/database": "^8.12|^9.0|^10.0|^11.0|^12.0" }, "require-dev": { "laravel/passport": "^11.0|^12.0", "laravel/pint": "^1.0", - "orchestra/testbench": "^6.23|^7.0|^8.0|^9.0", + "orchestra/testbench": "^6.23|^7.0|^8.0|^9.0|^10.0", "phpunit/phpunit": "^9.4|^10.1|^11.5" }, "minimum-stability": "dev", From 89c64bf0eab641f45fb34026e90f01ed47653469 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Thu, 30 Jan 2025 15:53:03 -0500 Subject: [PATCH 558/648] [Docs] Laravel 12 --- docs/installation-laravel.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation-laravel.md b/docs/installation-laravel.md index 54bac7b4b..c3a6646ce 100644 --- a/docs/installation-laravel.md +++ b/docs/installation-laravel.md @@ -9,7 +9,7 @@ Choose the version of this package that suits your Laravel version. Package Version | Laravel Version ----------------|----------- - ^6.0 | 8,9,10,11 (PHP 8.0+) + ^6.0 | 8,9,10,11,12 (PHP 8.0+) ^5.8 | 7,8,9,10 ^5.7 | 7,8,9 ^5.4-^5.6 | 7,8 From 722e6428e4fc1f9e2c4fcb4f2f5fd3eba843ba83 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Thu, 30 Jan 2025 16:03:31 -0500 Subject: [PATCH 559/648] Add Laravel 12 to GH workflows --- .github/workflows/run-tests.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 842b1ca98..3f47884ba 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -10,9 +10,11 @@ jobs: fail-fast: false matrix: php: [8.4, 8.3, 8.2, 8.1, 8.0] - laravel: ["^11.0", "^10.0", "^9.0", "^8.12"] + laravel: ["^12.0", "^11.0", "^10.0", "^9.0", "^8.12"] dependency-version: [prefer-lowest, prefer-stable] include: + - laravel: "^12.0" + testbench: 10.* - laravel: "^11.0" testbench: 9.* - laravel: "^10.0" @@ -22,6 +24,10 @@ jobs: - laravel: "^8.12" testbench: "^6.23" exclude: + - laravel: "^12.0" + php: 8.1 + - laravel: "^12.0" + php: 8.0 - laravel: "^11.0" php: 8.1 - laravel: "^11.0" @@ -52,7 +58,7 @@ jobs: - name: Install dependencies run: | - composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" "symfony/console:>=4.3.4" "mockery/mockery:^1.3.2" "nesbot/carbon:>=2.62.1" --no-interaction --no-update + composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" "symfony/console:>=4.3.4" "mockery/mockery:^1.3.2" "nesbot/carbon:>=2.72.6" --no-interaction --no-update composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction - name: Execute tests From 9d9be0a0cd6f4d5df28dbf463e13d07ec310fb84 Mon Sep 17 00:00:00 2001 From: drbyte <404472+drbyte@users.noreply.github.com> Date: Fri, 31 Jan 2025 01:10:25 +0000 Subject: [PATCH 560/648] Update CHANGELOG --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62c8daaac..2771bf0b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to `laravel-permission` will be documented in this file +## 6.12.0 - 2025-01-31 + +### What's Changed + +* Support Laravel 12 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/6.11.0...6.12.0 + ## 6.11.0 - 2025-01-30 ### What's Changed @@ -924,6 +932,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` @@ -996,6 +1005,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` From 262b34104ae2c06d4817ff99b72bd83a7947aebe Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Thu, 30 Jan 2025 20:55:13 -0500 Subject: [PATCH 561/648] phpstan fixes Cherry-picked 0c5789d36315df41e1660dd42fbb0120f0798ec3 Co-authored-by: erikn69 --- src/PermissionServiceProvider.php | 3 +-- src/Traits/HasPermissions.php | 3 +-- src/Traits/HasRoles.php | 4 ---- tests/CommandTest.php | 4 ++-- 4 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/PermissionServiceProvider.php b/src/PermissionServiceProvider.php index 31a029f48..96ae5abc6 100644 --- a/src/PermissionServiceProvider.php +++ b/src/PermissionServiceProvider.php @@ -144,8 +144,7 @@ protected function registerBladeExtensions(BladeCompiler $bladeCompiler): void protected function registerMacroHelpers(): void { - // @phpstan-ignore-next-line - if (! method_exists(Route::class, 'macro')) { // Lumen + if (! method_exists(Route::class, 'macro')) { // @phpstan-ignore-line Lumen return; } diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index 8147cac60..cc69d096e 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -348,8 +348,7 @@ public function getAllPermissions(): Collection /** @var Collection $permissions */ $permissions = $this->permissions; - // @phpstan-ignore-next-line - if (method_exists($this, 'roles')) { + if (!is_a($this, Permission::class)) { $permissions = $permissions->merge($this->getPermissionsViaRoles()); } diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index 4a332739d..409f1636c 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -127,10 +127,6 @@ private function collectRoles(...$roles): array } $role = $this->getStoredRole($role); - // @phpstan-ignore-next-line - if (! $role instanceof Role) { - return $array; - } if (! in_array($role->getKey(), $array)) { $this->ensureModelSharesGuard($role); diff --git a/tests/CommandTest.php b/tests/CommandTest.php index 4fbb6fa3a..c053416e9 100644 --- a/tests/CommandTest.php +++ b/tests/CommandTest.php @@ -200,7 +200,7 @@ public function it_can_respond_to_about_command_with_default() app(\Spatie\Permission\PermissionRegistrar::class)->initializeCache(); Artisan::call('about'); - $output = Artisan::output(); + $output = str_replace("\r\n", "\n", Artisan::output()); $pattern = '/Spatie Permissions[ .\n]*Features Enabled[ .]*Default[ .\n]*Version/'; if (method_exists($this, 'assertMatchesRegularExpression')) { @@ -225,7 +225,7 @@ public function it_can_respond_to_about_command_with_teams() config()->set('permission.teams', true); Artisan::call('about'); - $output = Artisan::output(); + $output = str_replace("\r\n", "\n", Artisan::output()); $pattern = '/Spatie Permissions[ .\n]*Features Enabled[ .]*Teams[ .\n]*Version/'; if (method_exists($this, 'assertMatchesRegularExpression')) { From 9895552dba4b4e98777bc4c28bca66b9473462d2 Mon Sep 17 00:00:00 2001 From: drbyte <404472+drbyte@users.noreply.github.com> Date: Fri, 31 Jan 2025 01:55:39 +0000 Subject: [PATCH 562/648] Fix styling --- src/Traits/HasPermissions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index cc69d096e..98214ca4e 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -348,7 +348,7 @@ public function getAllPermissions(): Collection /** @var Collection $permissions */ $permissions = $this->permissions; - if (!is_a($this, Permission::class)) { + if (! is_a($this, Permission::class)) { $permissions = $permissions->merge($this->getPermissionsViaRoles()); } From dd3ccf51f73e728f531886af59691ecc7254bbe8 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Fri, 31 Jan 2025 15:02:09 -0500 Subject: [PATCH 563/648] Explicitly call `loadMissing('permissions')` when the relation is needed, and test with `Model::preventLazyLoading()` (#2776) * call loadMissing('permissions') when relation is referenced * Test with `Model::preventLazyLoading()` * Model::preventsLazyLoading() check added --------- Co-authored-by: Chris Brown --- src/Models/Role.php | 3 ++- src/Traits/HasPermissions.php | 3 ++- tests/HasPermissionsTest.php | 34 ++++++++++++++++++++++++++++++++++ tests/HasRolesTest.php | 35 +++++++++++++++++++++++++++++++++++ tests/TestCase.php | 2 ++ 5 files changed, 75 insertions(+), 2 deletions(-) diff --git a/src/Models/Role.php b/src/Models/Role.php index 6bd47b75f..5bab4878e 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -188,6 +188,7 @@ public function hasPermissionTo($permission, ?string $guardName = null): bool throw GuardDoesNotMatch::create($permission->guard_name, $guardName ? collect([$guardName]) : $this->getGuardNames()); } - return $this->permissions->contains($permission->getKeyName(), $permission->getKey()); + return $this->loadMissing('permissions')->permissions + ->contains($permission->getKeyName(), $permission->getKey()); } } diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index 98214ca4e..15f9e9971 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -323,7 +323,8 @@ public function hasDirectPermission($permission): bool { $permission = $this->filterPermission($permission); - return $this->permissions->contains($permission->getKeyName(), $permission->getKey()); + return $this->loadMissing('permissions')->permissions + ->contains($permission->getKeyName(), $permission->getKey()); } /** diff --git a/tests/HasPermissionsTest.php b/tests/HasPermissionsTest.php index e5657536f..4c2d410e4 100644 --- a/tests/HasPermissionsTest.php +++ b/tests/HasPermissionsTest.php @@ -2,6 +2,7 @@ namespace Spatie\Permission\Tests; +use Illuminate\Database\Eloquent\Model; use DB; use Spatie\Permission\Contracts\Permission; use Spatie\Permission\Contracts\Role; @@ -765,4 +766,37 @@ public function it_can_reject_permission_based_on_logged_in_user_guard() 'status' => false, ]); } + + /** @test */ + public function it_can_be_given_a_permission_on_role_when_lazy_loading_is_restricted() + { + $this->assertTrue(Model::preventsLazyLoading()); + + try { + $testRole = app(Role::class)->with('permissions')->get()->first(); + + $testRole->givePermissionTo('edit-articles'); + + $this->assertTrue($testRole->hasPermissionTo('edit-articles')); + } catch (Exception $e) { + $this->fail('Lazy loading detected in the givePermissionTo method: ' . $e->getMessage()); + } + } + + /** @test */ + public function it_can_be_given_a_permission_on_user_when_lazy_loading_is_restricted() + { + $this->assertTrue(Model::preventsLazyLoading()); + + try { + User::create(['email' => 'other@user.com']); + $testUser = User::with('permissions')->get()->first(); + + $testUser->givePermissionTo('edit-articles'); + + $this->assertTrue($testUser->hasPermissionTo('edit-articles')); + } catch (Exception $e) { + $this->fail('Lazy loading detected in the givePermissionTo method: ' . $e->getMessage()); + } + } } diff --git a/tests/HasRolesTest.php b/tests/HasRolesTest.php index c5f78169e..c2f8459f0 100644 --- a/tests/HasRolesTest.php +++ b/tests/HasRolesTest.php @@ -2,7 +2,9 @@ namespace Spatie\Permission\Tests; +use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\DB; +use Spatie\Permission\Contracts\Permission; use Spatie\Permission\Contracts\Role; use Spatie\Permission\Exceptions\GuardDoesNotMatch; use Spatie\Permission\Exceptions\RoleDoesNotExist; @@ -856,4 +858,37 @@ public function it_does_not_detach_roles_when_user_soft_deleting() $this->assertTrue($user->hasRole('testRole')); } + + /** @test */ + public function it_can_be_given_a_role_on_permission_when_lazy_loading_is_restricted() + { + $this->assertTrue(Model::preventsLazyLoading()); + + try { + $testPermission = app(Permission::class)->with('roles')->get()->first(); + + $testPermission->assignRole('testRole'); + + $this->assertTrue($testPermission->hasRole('testRole')); + } catch (Exception $e) { + $this->fail('Lazy loading detected in the givePermissionTo method: ' . $e->getMessage()); + } + } + + /** @test */ + public function it_can_be_given_a_role_on_user_when_lazy_loading_is_restricted() + { + $this->assertTrue(Model::preventsLazyLoading()); + + try { + User::create(['email' => 'other@user.com']); + $user = User::with('roles')->get()->first(); + $user->assignRole('testRole'); + + $this->assertTrue($user->hasRole('testRole')); + } catch (Exception $e) { + $this->fail('Lazy loading detected in the givePermissionTo method: ' . $e->getMessage()); + } + } + } diff --git a/tests/TestCase.php b/tests/TestCase.php index b6ec94970..1b222ad01 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,6 +2,7 @@ namespace Spatie\Permission\Tests; +use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Schema\Blueprint; use Illuminate\Foundation\Console\AboutCommand; use Illuminate\Http\Request; @@ -110,6 +111,7 @@ protected function getPackageProviders($app) */ protected function getEnvironmentSetUp($app) { + Model::preventLazyLoading(); $app['config']->set('permission.register_permission_check_method', true); $app['config']->set('permission.teams', $this->hasTeams); $app['config']->set('permission.testing', true); // fix sqlite From 9d4fa2987f9b745854a13e1b36ba68e0b6dd4bdb Mon Sep 17 00:00:00 2001 From: drbyte <404472+drbyte@users.noreply.github.com> Date: Fri, 31 Jan 2025 20:02:32 +0000 Subject: [PATCH 564/648] Fix styling --- tests/HasPermissionsTest.php | 6 +++--- tests/HasRolesTest.php | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/tests/HasPermissionsTest.php b/tests/HasPermissionsTest.php index 4c2d410e4..39d13fcec 100644 --- a/tests/HasPermissionsTest.php +++ b/tests/HasPermissionsTest.php @@ -2,8 +2,8 @@ namespace Spatie\Permission\Tests; -use Illuminate\Database\Eloquent\Model; use DB; +use Illuminate\Database\Eloquent\Model; use Spatie\Permission\Contracts\Permission; use Spatie\Permission\Contracts\Role; use Spatie\Permission\Exceptions\GuardDoesNotMatch; @@ -779,7 +779,7 @@ public function it_can_be_given_a_permission_on_role_when_lazy_loading_is_restri $this->assertTrue($testRole->hasPermissionTo('edit-articles')); } catch (Exception $e) { - $this->fail('Lazy loading detected in the givePermissionTo method: ' . $e->getMessage()); + $this->fail('Lazy loading detected in the givePermissionTo method: '.$e->getMessage()); } } @@ -796,7 +796,7 @@ public function it_can_be_given_a_permission_on_user_when_lazy_loading_is_restri $this->assertTrue($testUser->hasPermissionTo('edit-articles')); } catch (Exception $e) { - $this->fail('Lazy loading detected in the givePermissionTo method: ' . $e->getMessage()); + $this->fail('Lazy loading detected in the givePermissionTo method: '.$e->getMessage()); } } } diff --git a/tests/HasRolesTest.php b/tests/HasRolesTest.php index c2f8459f0..4aa7c9073 100644 --- a/tests/HasRolesTest.php +++ b/tests/HasRolesTest.php @@ -871,7 +871,7 @@ public function it_can_be_given_a_role_on_permission_when_lazy_loading_is_restri $this->assertTrue($testPermission->hasRole('testRole')); } catch (Exception $e) { - $this->fail('Lazy loading detected in the givePermissionTo method: ' . $e->getMessage()); + $this->fail('Lazy loading detected in the givePermissionTo method: '.$e->getMessage()); } } @@ -887,8 +887,7 @@ public function it_can_be_given_a_role_on_user_when_lazy_loading_is_restricted() $this->assertTrue($user->hasRole('testRole')); } catch (Exception $e) { - $this->fail('Lazy loading detected in the givePermissionTo method: ' . $e->getMessage()); + $this->fail('Lazy loading detected in the givePermissionTo method: '.$e->getMessage()); } } - } From bb3ad222d65ec804c219cc52a8b54f5af2e1636a Mon Sep 17 00:00:00 2001 From: Sudhir Mitharwal <6812992+sudkumar@users.noreply.github.com> Date: Wed, 5 Feb 2025 21:12:48 +0530 Subject: [PATCH 565/648] [Docs] Add instructions to reinitialize cache for multi-tenancy key settings (#2804) * docs(cache): add instructions for cache store change in multi-tenancy when switching cache prefix/key/store during a request lifecycle, after the service provider has been loaded, the cache store should be reinitialized to make sure that the updated CacheStore is used for upcoming cache hits. --------- Co-authored-by: Chris Brown --- docs/advanced-usage/cache.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/advanced-usage/cache.md b/docs/advanced-usage/cache.md index 6c3a3fe6a..374410af8 100644 --- a/docs/advanced-usage/cache.md +++ b/docs/advanced-usage/cache.md @@ -68,7 +68,13 @@ Laravel Tip: If you are leveraging a caching service such as `redis` or `memcach To prevent other applications from accidentally using/changing your cached data, it is prudent to set your own cache `prefix` in Laravel's `/config/cache.php` to something unique for each application which shares the same caching service. -Most multi-tenant "packages" take care of this for you when switching tenants. +Most multi-tenant "packages" take care of this for you when switching tenants. + +Tip: Most parts of your multitenancy app will relate to a single tenant during a given request lifecycle, so the following step will not be needed: However, in the less-common situation where your app might be switching between multiple tenants during a single request lifecycle (specifically: where changing the cache key/prefix (such as when switching between tenants) or switching the cache store), then after switching tenants or changing the cache configuration you will need to reinitialize the cache of the `PermissionRegistrar` so that the updated `CacheStore` and cache configuration are used. + +```php +app()->make(\Spatie\Permission\PermissionRegistrar::class)->initializeCache(); +``` ### Custom Cache Store From 0356f8de6001aa7e55d0dc1f3dff12ee3c0f69ca Mon Sep 17 00:00:00 2001 From: drbyte <404472+drbyte@users.noreply.github.com> Date: Wed, 5 Feb 2025 15:45:58 +0000 Subject: [PATCH 566/648] Update CHANGELOG --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2771bf0b9..21c9539df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,19 @@ All notable changes to `laravel-permission` will be documented in this file +## 6.13.0 - 2025-02-05 + +### What's Changed + +* LazyLoading: Explicitly call `loadMissing('permissions')` when the relation is needed, and test with `Model::preventLazyLoading()` by @erikn69 in https://github.com/spatie/laravel-permission/pull/2776 +* [Docs] Add instructions to reinitialize cache for multi-tenancy key settings when updating multiple tenants in a single request cycle, by @sudkumar in https://github.com/spatie/laravel-permission/pull/2804 + +### New Contributors + +* @sudkumar made their first contribution in https://github.com/spatie/laravel-permission/pull/2804 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/6.12.0...6.13.0 + ## 6.12.0 - 2025-01-31 ### What's Changed @@ -933,6 +946,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` @@ -1006,6 +1020,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` From a07467dcbd015977de9b765ab799c59bb4c90952 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Wed, 5 Feb 2025 17:02:05 -0500 Subject: [PATCH 567/648] Mention custom cache bootstrapper --- docs/advanced-usage/cache.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/advanced-usage/cache.md b/docs/advanced-usage/cache.md index 374410af8..5e76828ec 100644 --- a/docs/advanced-usage/cache.md +++ b/docs/advanced-usage/cache.md @@ -68,7 +68,7 @@ Laravel Tip: If you are leveraging a caching service such as `redis` or `memcach To prevent other applications from accidentally using/changing your cached data, it is prudent to set your own cache `prefix` in Laravel's `/config/cache.php` to something unique for each application which shares the same caching service. -Most multi-tenant "packages" take care of this for you when switching tenants. +Most multi-tenant "packages" take care of this for you when switching tenants. Optionally you might need to change cache boot order by writing a custom [cache boostrapper](https://github.com/spatie/laravel-permission/discussions/2310#discussioncomment-10855389). Tip: Most parts of your multitenancy app will relate to a single tenant during a given request lifecycle, so the following step will not be needed: However, in the less-common situation where your app might be switching between multiple tenants during a single request lifecycle (specifically: where changing the cache key/prefix (such as when switching between tenants) or switching the cache store), then after switching tenants or changing the cache configuration you will need to reinitialize the cache of the `PermissionRegistrar` so that the updated `CacheStore` and cache configuration are used. From aa3548f9e1c4b27827b7a0c3780a9ffd76811df7 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Thu, 6 Feb 2025 14:30:23 -0500 Subject: [PATCH 568/648] Add note about avoiding $casts property with enums. --- docs/basic-usage/enums.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/basic-usage/enums.md b/docs/basic-usage/enums.md index 493c9380b..23a4167f4 100644 --- a/docs/basic-usage/enums.md +++ b/docs/basic-usage/enums.md @@ -13,6 +13,8 @@ If you are using PHP 8.1+ you can implement Enums as native types. Internally, Enums implicitly implement `\BackedEnum`, which is how this package recognizes that you're passing an Enum. +NOTE: Presently (version 6) this package does not support using `$casts` to specify enums on the `Permission` model. You can still use enums to reference things as shown below, just without declaring it in a `$casts` property. + ## Code Requirements From a6e35eee62ebd10da75df2c7220941797b5880bd Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Wed, 12 Feb 2025 20:18:29 -0500 Subject: [PATCH 569/648] Formatting --- .../create_permission_tables.php.stub | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/database/migrations/create_permission_tables.php.stub b/database/migrations/create_permission_tables.php.stub index 9c7044b46..70a120f30 100644 --- a/database/migrations/create_permission_tables.php.stub +++ b/database/migrations/create_permission_tables.php.stub @@ -1,8 +1,8 @@ engine('InnoDB'); + Schema::create($tableNames['permissions'], static function (Blueprint $table) { + // $table->engine('InnoDB'); $table->bigIncrements('id'); // permission id $table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format) $table->string('guard_name'); // For MyISAM use string('guard_name', 25); @@ -34,8 +34,8 @@ return new class extends Migration $table->unique(['name', 'guard_name']); }); - Schema::create($tableNames['roles'], function (Blueprint $table) use ($teams, $columnNames) { - //$table->engine('InnoDB'); + Schema::create($tableNames['roles'], static function (Blueprint $table) use ($teams, $columnNames) { + // $table->engine('InnoDB'); $table->bigIncrements('id'); // role id if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing $table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable(); @@ -51,7 +51,7 @@ return new class extends Migration } }); - Schema::create($tableNames['model_has_permissions'], function (Blueprint $table) use ($tableNames, $columnNames, $pivotPermission, $teams) { + Schema::create($tableNames['model_has_permissions'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotPermission, $teams) { $table->unsignedBigInteger($pivotPermission); $table->string('model_type'); @@ -75,7 +75,7 @@ return new class extends Migration }); - Schema::create($tableNames['model_has_roles'], function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams) { + Schema::create($tableNames['model_has_roles'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams) { $table->unsignedBigInteger($pivotRole); $table->string('model_type'); @@ -98,7 +98,7 @@ return new class extends Migration } }); - Schema::create($tableNames['role_has_permissions'], function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) { + Schema::create($tableNames['role_has_permissions'], static function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) { $table->unsignedBigInteger($pivotPermission); $table->unsignedBigInteger($pivotRole); From 85da60957d45c4224837e603d6931e004dc62496 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Wed, 12 Feb 2025 20:22:12 -0500 Subject: [PATCH 570/648] Add PHPUnit annotations, for future compatibility with PHPUnit 12 Annotations supported since PHPUnit 10 Docblock comments will be ignored in future In a later release of this package (probably `v7.0`), will remove PHPUnit 9 support and all associated doc-comments. --- tests/BladeTest.php | 26 +++++++++ tests/CacheTest.php | 17 ++++++ tests/CommandTest.php | 14 +++++ tests/CustomGateTest.php | 3 + tests/GateTest.php | 10 ++++ tests/HasPermissionsTest.php | 59 +++++++++++++++++++ tests/HasPermissionsWithCustomModelsTest.php | 9 +++ tests/HasRolesTest.php | 60 ++++++++++++++++++++ tests/HasRolesWithCustomModelsTest.php | 6 ++ tests/MultipleGuardsTest.php | 4 ++ tests/PermissionMiddlewareTest.php | 23 ++++++++ tests/PermissionRegistrarTest.php | 8 +++ tests/PermissionTest.php | 8 +++ tests/PolicyTest.php | 2 + tests/RoleMiddlewareTest.php | 22 +++++++ tests/RoleOrPermissionMiddlewareTest.php | 15 +++++ tests/RoleTest.php | 25 ++++++++ tests/RoleWithNestingTest.php | 4 ++ tests/RouteTest.php | 5 ++ tests/TeamHasPermissionsTest.php | 5 ++ tests/TeamHasRolesTest.php | 5 ++ tests/TestCase.php | 4 +- tests/TestModels/Client.php | 4 +- tests/TestModels/Manager.php | 4 +- tests/TestModels/Permission.php | 9 +-- tests/TestModels/Role.php | 25 +++----- tests/TestModels/SoftDeletingUser.php | 2 +- tests/TestModels/TestRolePermissionsEnum.php | 4 ++ tests/TestModels/WildcardPermission.php | 4 +- tests/WildcardHasPermissionsTest.php | 18 ++++++ tests/WildcardMiddlewareTest.php | 8 +++ tests/WildcardRoleTest.php | 10 ++++ tests/WildcardRouteTest.php | 4 ++ 33 files changed, 396 insertions(+), 30 deletions(-) diff --git a/tests/BladeTest.php b/tests/BladeTest.php index 7e19ec81f..aea3ca2f8 100644 --- a/tests/BladeTest.php +++ b/tests/BladeTest.php @@ -3,6 +3,7 @@ namespace Spatie\Permission\Tests; use Illuminate\Support\Facades\Artisan; +use PHPUnit\Framework\Attributes\Test; use Spatie\Permission\Contracts\Role; class BladeTest extends TestCase @@ -21,6 +22,7 @@ protected function setUp(): void } /** @test */ + #[Test] public function all_blade_directives_will_evaluate_false_when_there_is_nobody_logged_in() { $permission = 'edit-articles'; @@ -40,6 +42,7 @@ public function all_blade_directives_will_evaluate_false_when_there_is_nobody_lo } /** @test */ + #[Test] public function all_blade_directives_will_evaluate_false_when_somebody_without_roles_or_permissions_is_logged_in() { $permission = 'edit-articles'; @@ -59,6 +62,7 @@ public function all_blade_directives_will_evaluate_false_when_somebody_without_r } /** @test */ + #[Test] public function all_blade_directives_will_evaluate_false_when_somebody_with_another_guard_is_logged_in() { $permission = 'edit-articles'; @@ -79,6 +83,7 @@ public function all_blade_directives_will_evaluate_false_when_somebody_with_anot } /** @test */ + #[Test] public function the_can_directive_can_accept_a_guard_name() { $user = $this->getWriter(); @@ -109,6 +114,7 @@ public function the_can_directive_can_accept_a_guard_name() } /** @test */ + #[Test] public function the_can_directive_will_evaluate_true_when_the_logged_in_user_has_the_permission() { $user = $this->getWriter(); @@ -121,6 +127,7 @@ public function the_can_directive_will_evaluate_true_when_the_logged_in_user_has } /** @test */ + #[Test] public function the_haspermission_directive_will_evaluate_true_when_the_logged_in_user_has_the_permission() { $user = $this->getWriter(); @@ -145,6 +152,7 @@ public function the_haspermission_directive_will_evaluate_true_when_the_logged_i } /** @test */ + #[Test] public function the_role_directive_will_evaluate_true_when_the_logged_in_user_has_the_role() { auth()->setUser($this->getWriter()); @@ -153,6 +161,7 @@ public function the_role_directive_will_evaluate_true_when_the_logged_in_user_ha } /** @test */ + #[Test] public function the_elserole_directive_will_evaluate_true_when_the_logged_in_user_has_the_role() { auth()->setUser($this->getMember()); @@ -161,6 +170,7 @@ public function the_elserole_directive_will_evaluate_true_when_the_logged_in_use } /** @test */ + #[Test] public function the_role_directive_will_evaluate_true_when_the_logged_in_user_has_the_role_for_the_given_guard() { auth('admin')->setUser($this->getSuperAdmin()); @@ -169,6 +179,7 @@ public function the_role_directive_will_evaluate_true_when_the_logged_in_user_ha } /** @test */ + #[Test] public function the_hasrole_directive_will_evaluate_true_when_the_logged_in_user_has_the_role() { auth()->setUser($this->getWriter()); @@ -177,6 +188,7 @@ public function the_hasrole_directive_will_evaluate_true_when_the_logged_in_user } /** @test */ + #[Test] public function the_hasrole_directive_will_evaluate_true_when_the_logged_in_user_has_the_role_for_the_given_guard() { auth('admin')->setUser($this->getSuperAdmin()); @@ -185,6 +197,7 @@ public function the_hasrole_directive_will_evaluate_true_when_the_logged_in_user } /** @test */ + #[Test] public function the_unlessrole_directive_will_evaluate_true_when_the_logged_in_user_does_not_have_the_role() { auth()->setUser($this->getWriter()); @@ -193,6 +206,7 @@ public function the_unlessrole_directive_will_evaluate_true_when_the_logged_in_u } /** @test */ + #[Test] public function the_unlessrole_directive_will_evaluate_true_when_the_logged_in_user_does_not_have_the_role_for_the_given_guard() { auth('admin')->setUser($this->getSuperAdmin()); @@ -202,6 +216,7 @@ public function the_unlessrole_directive_will_evaluate_true_when_the_logged_in_u } /** @test */ + #[Test] public function the_hasanyrole_directive_will_evaluate_false_when_the_logged_in_user_does_not_have_any_of_the_required_roles() { $roles = ['writer', 'intern']; @@ -213,6 +228,7 @@ public function the_hasanyrole_directive_will_evaluate_false_when_the_logged_in_ } /** @test */ + #[Test] public function the_hasanyrole_directive_will_evaluate_true_when_the_logged_in_user_does_have_some_of_the_required_roles() { $roles = ['member', 'writer', 'intern']; @@ -224,6 +240,7 @@ public function the_hasanyrole_directive_will_evaluate_true_when_the_logged_in_u } /** @test */ + #[Test] public function the_hasanyrole_directive_will_evaluate_true_when_the_logged_in_user_does_have_some_of_the_required_roles_for_the_given_guard() { $roles = ['super-admin', 'moderator']; @@ -235,6 +252,7 @@ public function the_hasanyrole_directive_will_evaluate_true_when_the_logged_in_u } /** @test */ + #[Test] public function the_hasanyrole_directive_will_evaluate_true_when_the_logged_in_user_does_have_some_of_the_required_roles_in_pipe() { $guard = 'admin'; @@ -245,6 +263,7 @@ public function the_hasanyrole_directive_will_evaluate_true_when_the_logged_in_u } /** @test */ + #[Test] public function the_hasanyrole_directive_will_evaluate_false_when_the_logged_in_user_doesnt_have_some_of_the_required_roles_in_pipe() { $guard = ''; @@ -255,6 +274,7 @@ public function the_hasanyrole_directive_will_evaluate_false_when_the_logged_in_ } /** @test */ + #[Test] public function the_hasallroles_directive_will_evaluate_false_when_the_logged_in_user_does_not_have_all_required_roles() { $roles = ['member', 'writer']; @@ -266,6 +286,7 @@ public function the_hasallroles_directive_will_evaluate_false_when_the_logged_in } /** @test */ + #[Test] public function the_hasallroles_directive_will_evaluate_true_when_the_logged_in_user_does_have_all_required_roles() { $roles = ['member', 'writer']; @@ -281,6 +302,7 @@ public function the_hasallroles_directive_will_evaluate_true_when_the_logged_in_ } /** @test */ + #[Test] public function the_hasallroles_directive_will_evaluate_true_when_the_logged_in_user_does_have_all_required_roles_for_the_given_guard() { $roles = ['super-admin', 'moderator']; @@ -296,6 +318,7 @@ public function the_hasallroles_directive_will_evaluate_true_when_the_logged_in_ } /** @test */ + #[Test] public function the_hasallroles_directive_will_evaluate_true_when_the_logged_in_user_does_have_all_required_roles_in_pipe() { $guard = 'admin'; @@ -310,6 +333,7 @@ public function the_hasallroles_directive_will_evaluate_true_when_the_logged_in_ } /** @test */ + #[Test] public function the_hasallroles_directive_will_evaluate_false_when_the_logged_in_user_doesnt_have_all_required_roles_in_pipe() { $guard = ''; @@ -323,6 +347,7 @@ public function the_hasallroles_directive_will_evaluate_false_when_the_logged_in } /** @test */ + #[Test] public function the_hasallroles_directive_will_evaluate_true_when_the_logged_in_user_does_have_all_required_roles_in_array() { $guard = 'admin'; @@ -337,6 +362,7 @@ public function the_hasallroles_directive_will_evaluate_true_when_the_logged_in_ } /** @test */ + #[Test] public function the_hasallroles_directive_will_evaluate_false_when_the_logged_in_user_doesnt_have_all_required_roles_in_array() { $guard = ''; diff --git a/tests/CacheTest.php b/tests/CacheTest.php index ba8ca6a1e..be5510fe9 100644 --- a/tests/CacheTest.php +++ b/tests/CacheTest.php @@ -4,6 +4,7 @@ use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\DB; +use PHPUnit\Framework\Attributes\Test; use Spatie\Permission\Contracts\Permission; use Spatie\Permission\Contracts\Role; use Spatie\Permission\Exceptions\PermissionDoesNotExist; @@ -42,6 +43,7 @@ protected function setUp(): void } /** @test */ + #[Test] public function it_can_cache_the_permissions() { $this->resetQueryCount(); @@ -52,6 +54,7 @@ public function it_can_cache_the_permissions() } /** @test */ + #[Test] public function it_flushes_the_cache_when_creating_a_permission() { app(Permission::class)->create(['name' => 'new']); @@ -64,6 +67,7 @@ public function it_flushes_the_cache_when_creating_a_permission() } /** @test */ + #[Test] public function it_flushes_the_cache_when_updating_a_permission() { $permission = app(Permission::class)->create(['name' => 'new']); @@ -79,6 +83,7 @@ public function it_flushes_the_cache_when_updating_a_permission() } /** @test */ + #[Test] public function it_flushes_the_cache_when_creating_a_role() { app(Role::class)->create(['name' => 'new']); @@ -91,6 +96,7 @@ public function it_flushes_the_cache_when_creating_a_role() } /** @test */ + #[Test] public function it_flushes_the_cache_when_updating_a_role() { $role = app(Role::class)->create(['name' => 'new']); @@ -106,6 +112,7 @@ public function it_flushes_the_cache_when_updating_a_role() } /** @test */ + #[Test] public function removing_a_permission_from_a_user_should_not_flush_the_cache() { $this->testUser->givePermissionTo('edit-articles'); @@ -122,6 +129,7 @@ public function removing_a_permission_from_a_user_should_not_flush_the_cache() } /** @test */ + #[Test] public function removing_a_role_from_a_user_should_not_flush_the_cache() { $this->testUser->assignRole('testRole'); @@ -138,6 +146,7 @@ public function removing_a_role_from_a_user_should_not_flush_the_cache() } /** @test */ + #[Test] public function it_flushes_the_cache_when_removing_a_role_from_a_permission() { $this->testUserPermission->assignRole('testRole'); @@ -154,6 +163,7 @@ public function it_flushes_the_cache_when_removing_a_role_from_a_permission() } /** @test */ + #[Test] public function it_flushes_the_cache_when_assign_a_permission_to_a_role() { $this->testUserRole->givePermissionTo('edit-articles'); @@ -166,6 +176,7 @@ public function it_flushes_the_cache_when_assign_a_permission_to_a_role() } /** @test */ + #[Test] public function user_creation_should_not_flush_the_cache() { $this->registrar->getPermissions(); @@ -181,6 +192,7 @@ public function user_creation_should_not_flush_the_cache() } /** @test */ + #[Test] public function it_flushes_the_cache_when_giving_a_permission_to_a_role() { $this->testUserRole->givePermissionTo($this->testUserPermission); @@ -193,6 +205,7 @@ public function it_flushes_the_cache_when_giving_a_permission_to_a_role() } /** @test */ + #[Test] public function has_permission_to_should_use_the_cache() { $this->testUserRole->givePermissionTo(['edit-articles', 'edit-news', 'Edit News']); @@ -217,6 +230,7 @@ public function has_permission_to_should_use_the_cache() } /** @test */ + #[Test] public function the_cache_should_differentiate_by_guard_name() { $this->expectException(PermissionDoesNotExist::class); @@ -235,6 +249,7 @@ public function the_cache_should_differentiate_by_guard_name() } /** @test */ + #[Test] public function get_all_permissions_should_use_the_cache() { $this->testUserRole->givePermissionTo($expected = ['edit-articles', 'edit-news']); @@ -253,6 +268,7 @@ public function get_all_permissions_should_use_the_cache() } /** @test */ + #[Test] public function get_all_permissions_should_not_over_hydrate_roles() { $this->testUserRole->givePermissionTo(['edit-articles', 'edit-news']); @@ -264,6 +280,7 @@ public function get_all_permissions_should_not_over_hydrate_roles() } /** @test */ + #[Test] public function it_can_reset_the_cache_with_artisan_command() { Artisan::call('permission:create-permission', ['name' => 'new-permission']); diff --git a/tests/CommandTest.php b/tests/CommandTest.php index c053416e9..e1961d812 100644 --- a/tests/CommandTest.php +++ b/tests/CommandTest.php @@ -5,12 +5,14 @@ use Composer\InstalledVersions; use Illuminate\Foundation\Console\AboutCommand; use Illuminate\Support\Facades\Artisan; +use PHPUnit\Framework\Attributes\Test; use Spatie\Permission\Models\Permission; use Spatie\Permission\Models\Role; class CommandTest extends TestCase { /** @test */ + #[Test] public function it_can_create_a_role() { Artisan::call('permission:create-role', ['name' => 'new-role']); @@ -20,6 +22,7 @@ public function it_can_create_a_role() } /** @test */ + #[Test] public function it_can_create_a_role_with_a_specific_guard() { Artisan::call('permission:create-role', [ @@ -33,6 +36,7 @@ public function it_can_create_a_role_with_a_specific_guard() } /** @test */ + #[Test] public function it_can_create_a_permission() { Artisan::call('permission:create-permission', ['name' => 'new-permission']); @@ -41,6 +45,7 @@ public function it_can_create_a_permission() } /** @test */ + #[Test] public function it_can_create_a_permission_with_a_specific_guard() { Artisan::call('permission:create-permission', [ @@ -54,6 +59,7 @@ public function it_can_create_a_permission_with_a_specific_guard() } /** @test */ + #[Test] public function it_can_create_a_role_and_permissions_at_same_time() { Artisan::call('permission:create-role', [ @@ -68,6 +74,7 @@ public function it_can_create_a_role_and_permissions_at_same_time() } /** @test */ + #[Test] public function it_can_create_a_role_without_duplication() { Artisan::call('permission:create-role', ['name' => 'new-role']); @@ -78,6 +85,7 @@ public function it_can_create_a_role_without_duplication() } /** @test */ + #[Test] public function it_can_create_a_permission_without_duplication() { Artisan::call('permission:create-permission', ['name' => 'new-permission']); @@ -87,6 +95,7 @@ public function it_can_create_a_permission_without_duplication() } /** @test */ + #[Test] public function it_can_show_permission_tables() { Role::where('name', 'testRole2')->delete(); @@ -125,6 +134,7 @@ public function it_can_show_permission_tables() } /** @test */ + #[Test] public function it_can_show_permissions_for_guard() { Artisan::call('permission:show', ['guard' => 'web']); @@ -136,6 +146,7 @@ public function it_can_show_permissions_for_guard() } /** @test */ + #[Test] public function it_can_setup_teams_upgrade() { config()->set('permission.teams', true); @@ -163,6 +174,7 @@ public function it_can_setup_teams_upgrade() } /** @test */ + #[Test] public function it_can_show_roles_by_teams() { config()->set('permission.teams', true); @@ -188,6 +200,7 @@ public function it_can_show_roles_by_teams() } /** @test */ + #[Test] public function it_can_respond_to_about_command_with_default() { if (! class_exists(InstalledVersions::class) || ! class_exists(AboutCommand::class)) { @@ -211,6 +224,7 @@ public function it_can_respond_to_about_command_with_default() } /** @test */ + #[Test] public function it_can_respond_to_about_command_with_teams() { if (! class_exists(InstalledVersions::class) || ! class_exists(AboutCommand::class)) { diff --git a/tests/CustomGateTest.php b/tests/CustomGateTest.php index 2c7e1fe17..498291ee8 100644 --- a/tests/CustomGateTest.php +++ b/tests/CustomGateTest.php @@ -3,6 +3,7 @@ namespace Spatie\Permission\Tests; use Illuminate\Contracts\Auth\Access\Gate; +use PHPUnit\Framework\Attributes\Test; class CustomGateTest extends TestCase { @@ -14,6 +15,7 @@ protected function getEnvironmentSetUp($app) } /** @test */ + #[Test] public function it_doesnt_register_the_method_for_checking_permissions_on_the_gate() { $this->testUser->givePermissionTo('edit-articles'); @@ -23,6 +25,7 @@ public function it_doesnt_register_the_method_for_checking_permissions_on_the_ga } /** @test */ + #[Test] public function it_can_authorize_using_custom_method_for_checking_permissions() { app(Gate::class)->define('edit-articles', function () { diff --git a/tests/GateTest.php b/tests/GateTest.php index 436900b84..6ec0fdcb3 100644 --- a/tests/GateTest.php +++ b/tests/GateTest.php @@ -3,17 +3,21 @@ namespace Spatie\Permission\Tests; use Illuminate\Contracts\Auth\Access\Gate; +use PHPUnit\Framework\Attributes\RequiresPhp; +use PHPUnit\Framework\Attributes\Test; use Spatie\Permission\Contracts\Permission; class GateTest extends TestCase { /** @test */ + #[Test] public function it_can_determine_if_a_user_does_not_have_a_permission() { $this->assertFalse($this->testUser->can('edit-articles')); } /** @test */ + #[Test] public function it_allows_other_gate_before_callbacks_to_run_if_a_user_does_not_have_a_permission() { $this->assertFalse($this->testUser->can('edit-articles')); @@ -27,6 +31,7 @@ public function it_allows_other_gate_before_callbacks_to_run_if_a_user_does_not_ } /** @test */ + #[Test] public function it_allows_gate_after_callback_to_grant_denied_privileges() { $this->assertFalse($this->testUser->can('edit-articles')); @@ -39,6 +44,7 @@ public function it_allows_gate_after_callback_to_grant_denied_privileges() } /** @test */ + #[Test] public function it_can_determine_if_a_user_has_a_direct_permission() { $this->testUser->givePermissionTo('edit-articles'); @@ -55,6 +61,8 @@ public function it_can_determine_if_a_user_has_a_direct_permission() * * @requires PHP >= 8.1 */ + #[RequiresPhp('>= 8.1')] + #[Test] public function it_can_determine_if_a_user_has_a_direct_permission_using_enums() { $enum = TestModels\TestRolePermissionsEnum::VIEWARTICLES; @@ -73,6 +81,7 @@ public function it_can_determine_if_a_user_has_a_direct_permission_using_enums() } /** @test */ + #[Test] public function it_can_determine_if_a_user_has_a_permission_through_roles() { $this->testUserRole->givePermissionTo($this->testUserPermission); @@ -89,6 +98,7 @@ public function it_can_determine_if_a_user_has_a_permission_through_roles() } /** @test */ + #[Test] public function it_can_determine_if_a_user_with_a_different_guard_has_a_permission_when_using_roles() { $this->testAdminRole->givePermissionTo($this->testAdminPermission); diff --git a/tests/HasPermissionsTest.php b/tests/HasPermissionsTest.php index 39d13fcec..0e2de3aa7 100644 --- a/tests/HasPermissionsTest.php +++ b/tests/HasPermissionsTest.php @@ -4,6 +4,8 @@ use DB; use Illuminate\Database\Eloquent\Model; +use PHPUnit\Framework\Attributes\RequiresPhp; +use PHPUnit\Framework\Attributes\Test; use Spatie\Permission\Contracts\Permission; use Spatie\Permission\Contracts\Role; use Spatie\Permission\Exceptions\GuardDoesNotMatch; @@ -14,6 +16,7 @@ class HasPermissionsTest extends TestCase { /** @test */ + #[Test] public function it_can_assign_a_permission_to_a_user() { $this->testUser->givePermissionTo($this->testUserPermission); @@ -22,6 +25,7 @@ public function it_can_assign_a_permission_to_a_user() } /** @test */ + #[Test] public function it_can_assign_a_permission_to_a_user_with_a_non_default_guard() { $testUserPermission = app(Permission::class)->create([ @@ -35,6 +39,7 @@ public function it_can_assign_a_permission_to_a_user_with_a_non_default_guard() } /** @test */ + #[Test] public function it_throws_an_exception_when_assigning_a_permission_that_does_not_exist() { $this->expectException(PermissionDoesNotExist::class); @@ -43,6 +48,7 @@ public function it_throws_an_exception_when_assigning_a_permission_that_does_not } /** @test */ + #[Test] public function it_throws_an_exception_when_assigning_a_permission_to_a_user_from_a_different_guard() { $this->expectException(GuardDoesNotMatch::class); @@ -55,6 +61,7 @@ public function it_throws_an_exception_when_assigning_a_permission_to_a_user_fro } /** @test */ + #[Test] public function it_can_revoke_a_permission_from_a_user() { $this->testUser->givePermissionTo($this->testUserPermission); @@ -71,6 +78,8 @@ public function it_can_revoke_a_permission_from_a_user() * * @requires PHP >= 8.1 */ + #[RequiresPhp('>= 8.1')] + #[Test] public function it_can_assign_and_remove_a_permission_using_enums() { $enum = TestModels\TestRolePermissionsEnum::VIEWARTICLES; @@ -95,6 +104,8 @@ public function it_can_assign_and_remove_a_permission_using_enums() * * @requires PHP >= 8.1 */ + #[RequiresPhp('>= 8.1')] + #[Test] public function it_can_scope_users_using_enums() { $enum1 = TestModels\TestRolePermissionsEnum::VIEWARTICLES; @@ -122,6 +133,7 @@ public function it_can_scope_users_using_enums() } /** @test */ + #[Test] public function it_can_scope_users_using_a_string() { User::all()->each(fn ($item) => $item->delete()); @@ -142,6 +154,7 @@ public function it_can_scope_users_using_a_string() } /** @test */ + #[Test] public function it_can_scope_users_using_a_int() { User::all()->each(fn ($item) => $item->delete()); @@ -162,6 +175,7 @@ public function it_can_scope_users_using_a_int() } /** @test */ + #[Test] public function it_can_scope_users_using_an_array() { User::all()->each(fn ($item) => $item->delete()); @@ -183,6 +197,7 @@ public function it_can_scope_users_using_an_array() } /** @test */ + #[Test] public function it_can_scope_users_using_a_collection() { User::all()->each(fn ($item) => $item->delete()); @@ -204,6 +219,7 @@ public function it_can_scope_users_using_a_collection() } /** @test */ + #[Test] public function it_can_scope_users_using_an_object() { User::all()->each(fn ($item) => $item->delete()); @@ -222,6 +238,7 @@ public function it_can_scope_users_using_an_object() } /** @test */ + #[Test] public function it_can_scope_users_without_direct_permissions_only_role() { User::all()->each(fn ($item) => $item->delete()); @@ -241,6 +258,7 @@ public function it_can_scope_users_without_direct_permissions_only_role() } /** @test */ + #[Test] public function it_can_scope_users_with_only_direct_permission() { User::all()->each(fn ($item) => $item->delete()); @@ -258,6 +276,7 @@ public function it_can_scope_users_with_only_direct_permission() } /** @test */ + #[Test] public function it_throws_an_exception_when_calling_hasPermissionTo_with_an_invalid_type() { $user = User::create(['email' => 'user1@test.com']); @@ -268,6 +287,7 @@ public function it_throws_an_exception_when_calling_hasPermissionTo_with_an_inva } /** @test */ + #[Test] public function it_throws_an_exception_when_calling_hasPermissionTo_with_null() { $user = User::create(['email' => 'user1@test.com']); @@ -278,6 +298,7 @@ public function it_throws_an_exception_when_calling_hasPermissionTo_with_null() } /** @test */ + #[Test] public function it_throws_an_exception_when_calling_hasDirectPermission_with_an_invalid_type() { $user = User::create(['email' => 'user1@test.com']); @@ -288,6 +309,7 @@ public function it_throws_an_exception_when_calling_hasDirectPermission_with_an_ } /** @test */ + #[Test] public function it_throws_an_exception_when_calling_hasDirectPermission_with_null() { $user = User::create(['email' => 'user1@test.com']); @@ -298,6 +320,7 @@ public function it_throws_an_exception_when_calling_hasDirectPermission_with_nul } /** @test */ + #[Test] public function it_throws_an_exception_when_trying_to_scope_a_non_existing_permission() { $this->expectException(PermissionDoesNotExist::class); @@ -310,6 +333,7 @@ public function it_throws_an_exception_when_trying_to_scope_a_non_existing_permi } /** @test */ + #[Test] public function it_throws_an_exception_when_trying_to_scope_a_permission_from_another_guard() { $this->expectException(PermissionDoesNotExist::class); @@ -330,6 +354,7 @@ public function it_throws_an_exception_when_trying_to_scope_a_permission_from_an } /** @test */ + #[Test] public function it_doesnt_detach_permissions_when_user_soft_deleting() { $user = SoftDeletingUser::create(['email' => 'test@example.com']); @@ -342,6 +367,7 @@ public function it_doesnt_detach_permissions_when_user_soft_deleting() } /** @test */ + #[Test] public function it_can_give_and_revoke_multiple_permissions() { $this->testUserRole->givePermissionTo(['edit-articles', 'edit-news']); @@ -354,6 +380,7 @@ public function it_can_give_and_revoke_multiple_permissions() } /** @test */ + #[Test] public function it_can_give_and_revoke_permissions_models_array() { $models = [app(Permission::class)::where('name', 'edit-articles')->first(), app(Permission::class)::where('name', 'edit-news')->first()]; @@ -368,6 +395,7 @@ public function it_can_give_and_revoke_permissions_models_array() } /** @test */ + #[Test] public function it_can_give_and_revoke_permissions_models_collection() { $models = app(Permission::class)::whereIn('name', ['edit-articles', 'edit-news'])->get(); @@ -382,12 +410,14 @@ public function it_can_give_and_revoke_permissions_models_collection() } /** @test */ + #[Test] public function it_can_determine_that_the_user_does_not_have_a_permission() { $this->assertFalse($this->testUser->hasPermissionTo('edit-articles')); } /** @test */ + #[Test] public function it_throws_an_exception_when_the_permission_does_not_exist() { $this->expectException(PermissionDoesNotExist::class); @@ -396,6 +426,7 @@ public function it_throws_an_exception_when_the_permission_does_not_exist() } /** @test */ + #[Test] public function it_throws_an_exception_when_the_permission_does_not_exist_for_this_guard() { $this->expectException(PermissionDoesNotExist::class); @@ -404,6 +435,7 @@ public function it_throws_an_exception_when_the_permission_does_not_exist_for_th } /** @test */ + #[Test] public function it_can_reject_a_user_that_does_not_have_any_permissions_at_all() { $user = new User; @@ -412,6 +444,7 @@ public function it_can_reject_a_user_that_does_not_have_any_permissions_at_all() } /** @test */ + #[Test] public function it_can_determine_that_the_user_has_any_of_the_permissions_directly() { $this->assertFalse($this->testUser->hasAnyPermission('edit-articles')); @@ -429,6 +462,7 @@ public function it_can_determine_that_the_user_has_any_of_the_permissions_direct } /** @test */ + #[Test] public function it_can_determine_that_the_user_has_any_of_the_permissions_directly_using_an_array() { $this->assertFalse($this->testUser->hasAnyPermission(['edit-articles'])); @@ -445,6 +479,7 @@ public function it_can_determine_that_the_user_has_any_of_the_permissions_direct } /** @test */ + #[Test] public function it_can_determine_that_the_user_has_any_of_the_permissions_via_role() { $this->testUserRole->givePermissionTo('edit-articles'); @@ -456,6 +491,7 @@ public function it_can_determine_that_the_user_has_any_of_the_permissions_via_ro } /** @test */ + #[Test] public function it_can_determine_that_the_user_has_all_of_the_permissions_directly() { $this->testUser->givePermissionTo('edit-articles', 'edit-news'); @@ -469,6 +505,7 @@ public function it_can_determine_that_the_user_has_all_of_the_permissions_direct } /** @test */ + #[Test] public function it_can_determine_that_the_user_has_all_of_the_permissions_directly_using_an_array() { $this->assertFalse($this->testUser->hasAllPermissions(['edit-articles', 'edit-news'])); @@ -485,6 +522,7 @@ public function it_can_determine_that_the_user_has_all_of_the_permissions_direct } /** @test */ + #[Test] public function it_can_determine_that_the_user_has_all_of_the_permissions_via_role() { $this->testUserRole->givePermissionTo('edit-articles', 'edit-news'); @@ -495,6 +533,7 @@ public function it_can_determine_that_the_user_has_all_of_the_permissions_via_ro } /** @test */ + #[Test] public function it_can_determine_that_user_has_direct_permission() { $this->testUser->givePermissionTo('edit-articles'); @@ -513,6 +552,7 @@ public function it_can_determine_that_user_has_direct_permission() } /** @test */ + #[Test] public function it_can_list_all_the_permissions_via_roles_of_user() { $roleModel = app(Role::class); @@ -528,6 +568,7 @@ public function it_can_list_all_the_permissions_via_roles_of_user() } /** @test */ + #[Test] public function it_can_list_all_the_coupled_permissions_both_directly_and_via_roles() { $this->testUser->givePermissionTo('edit-news'); @@ -542,6 +583,7 @@ public function it_can_list_all_the_coupled_permissions_both_directly_and_via_ro } /** @test */ + #[Test] public function it_can_sync_multiple_permissions() { $this->testUser->givePermissionTo('edit-news'); @@ -556,6 +598,7 @@ public function it_can_sync_multiple_permissions() } /** @test */ + #[Test] public function it_can_avoid_sync_duplicated_permissions() { $this->testUser->syncPermissions('edit-articles', 'edit-blog', 'edit-blog'); @@ -566,6 +609,7 @@ public function it_can_avoid_sync_duplicated_permissions() } /** @test */ + #[Test] public function it_can_sync_multiple_permissions_by_id() { $this->testUser->givePermissionTo('edit-news'); @@ -582,6 +626,7 @@ public function it_can_sync_multiple_permissions_by_id() } /** @test */ + #[Test] public function sync_permission_ignores_null_inputs() { $this->testUser->givePermissionTo('edit-news'); @@ -600,6 +645,7 @@ public function sync_permission_ignores_null_inputs() } /** @test */ + #[Test] public function sync_permission_error_does_not_detach_permissions() { $this->testUser->givePermissionTo('edit-news'); @@ -612,6 +658,7 @@ public function sync_permission_error_does_not_detach_permissions() } /** @test */ + #[Test] public function it_does_not_remove_already_associated_permissions_when_assigning_new_permissions() { $this->testUser->givePermissionTo('edit-news'); @@ -622,6 +669,7 @@ public function it_does_not_remove_already_associated_permissions_when_assigning } /** @test */ + #[Test] public function it_does_not_throw_an_exception_when_assigning_a_permission_that_is_already_assigned() { $this->testUser->givePermissionTo('edit-news'); @@ -632,6 +680,7 @@ public function it_does_not_throw_an_exception_when_assigning_a_permission_that_ } /** @test */ + #[Test] public function it_can_sync_permissions_to_a_model_that_is_not_persisted() { $user = new User(['email' => 'test@user.com']); @@ -647,6 +696,7 @@ public function it_can_sync_permissions_to_a_model_that_is_not_persisted() } /** @test */ + #[Test] public function it_does_not_run_unnecessary_sqls_when_assigning_new_permissions() { $permission2 = app(Permission::class)->where('name', ['edit-news'])->first(); @@ -659,6 +709,7 @@ public function it_does_not_run_unnecessary_sqls_when_assigning_new_permissions( } /** @test */ + #[Test] public function calling_givePermissionTo_before_saving_object_doesnt_interfere_with_other_objects() { $user = new User(['email' => 'test@user.com']); @@ -681,6 +732,7 @@ public function calling_givePermissionTo_before_saving_object_doesnt_interfere_w } /** @test */ + #[Test] public function calling_syncPermissions_before_saving_object_doesnt_interfere_with_other_objects() { $user = new User(['email' => 'test@user.com']); @@ -703,6 +755,7 @@ public function calling_syncPermissions_before_saving_object_doesnt_interfere_wi } /** @test */ + #[Test] public function it_can_retrieve_permission_names() { $this->testUser->givePermissionTo('edit-news', 'edit-articles'); @@ -713,6 +766,7 @@ public function it_can_retrieve_permission_names() } /** @test */ + #[Test] public function it_can_check_many_direct_permissions() { $this->testUser->givePermissionTo(['edit-articles', 'edit-news']); @@ -723,6 +777,7 @@ public function it_can_check_many_direct_permissions() } /** @test */ + #[Test] public function it_can_check_if_there_is_any_of_the_direct_permissions_given() { $this->testUser->givePermissionTo(['edit-articles', 'edit-news']); @@ -732,6 +787,7 @@ public function it_can_check_if_there_is_any_of_the_direct_permissions_given() } /** @test */ + #[Test] public function it_can_check_permission_based_on_logged_in_user_guard() { $this->testUser->givePermissionTo(app(Permission::class)::create([ @@ -746,6 +802,7 @@ public function it_can_check_permission_based_on_logged_in_user_guard() } /** @test */ + #[Test] public function it_can_reject_permission_based_on_logged_in_user_guard() { $unassignedPermission = app(Permission::class)::create([ @@ -768,6 +825,7 @@ public function it_can_reject_permission_based_on_logged_in_user_guard() } /** @test */ + #[Test] public function it_can_be_given_a_permission_on_role_when_lazy_loading_is_restricted() { $this->assertTrue(Model::preventsLazyLoading()); @@ -784,6 +842,7 @@ public function it_can_be_given_a_permission_on_role_when_lazy_loading_is_restri } /** @test */ + #[Test] public function it_can_be_given_a_permission_on_user_when_lazy_loading_is_restricted() { $this->assertTrue(Model::preventsLazyLoading()); diff --git a/tests/HasPermissionsWithCustomModelsTest.php b/tests/HasPermissionsWithCustomModelsTest.php index d26b92286..acbd53fcc 100644 --- a/tests/HasPermissionsWithCustomModelsTest.php +++ b/tests/HasPermissionsWithCustomModelsTest.php @@ -4,6 +4,7 @@ use Illuminate\Support\Carbon; use Illuminate\Support\Facades\DB; +use PHPUnit\Framework\Attributes\Test; use Spatie\Permission\PermissionRegistrar; use Spatie\Permission\Tests\TestModels\Admin; use Spatie\Permission\Tests\TestModels\Permission; @@ -27,12 +28,14 @@ protected function getEnvironmentSetUp($app) } /** @test */ + #[Test] public function it_can_use_custom_model_permission() { $this->assertSame(get_class($this->testUserPermission), Permission::class); } /** @test */ + #[Test] public function it_can_use_custom_fields_from_cache() { DB::connection()->getSchemaBuilder()->table(config('permission.table_names.roles'), function ($table) { @@ -54,6 +57,7 @@ public function it_can_use_custom_fields_from_cache() } /** @test */ + #[Test] public function it_can_scope_users_using_a_int() { // Skipped because custom model uses uuid, @@ -62,6 +66,7 @@ public function it_can_scope_users_using_a_int() } /** @test */ + #[Test] public function it_can_scope_users_using_a_uuid() { $uuid1 = $this->testUserPermission->getKey(); @@ -81,6 +86,7 @@ public function it_can_scope_users_using_a_uuid() } /** @test */ + #[Test] public function it_doesnt_detach_roles_when_soft_deleting() { $this->testUserRole->givePermissionTo($this->testUserPermission); @@ -97,6 +103,7 @@ public function it_doesnt_detach_roles_when_soft_deleting() } /** @test */ + #[Test] public function it_doesnt_detach_users_when_soft_deleting() { $this->testUser->givePermissionTo($this->testUserPermission); @@ -113,6 +120,7 @@ public function it_doesnt_detach_users_when_soft_deleting() } /** @test */ + #[Test] public function it_does_detach_roles_and_users_when_force_deleting() { $permission_id = $this->testUserPermission->getKey(); @@ -133,6 +141,7 @@ public function it_does_detach_roles_and_users_when_force_deleting() } /** @test */ + #[Test] public function it_should_touch_when_assigning_new_permissions() { Carbon::setTestNow('2021-07-19 10:13:14'); diff --git a/tests/HasRolesTest.php b/tests/HasRolesTest.php index 4aa7c9073..5777f7bdb 100644 --- a/tests/HasRolesTest.php +++ b/tests/HasRolesTest.php @@ -4,6 +4,8 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\DB; +use PHPUnit\Framework\Attributes\RequiresPhp; +use PHPUnit\Framework\Attributes\Test; use Spatie\Permission\Contracts\Permission; use Spatie\Permission\Contracts\Role; use Spatie\Permission\Exceptions\GuardDoesNotMatch; @@ -15,6 +17,7 @@ class HasRolesTest extends TestCase { /** @test */ + #[Test] public function it_can_determine_that_the_user_does_not_have_a_role() { $this->assertFalse($this->testUser->hasRole('testRole')); @@ -45,6 +48,8 @@ public function it_can_determine_that_the_user_does_not_have_a_role() * * @requires PHP >= 8.1 */ + #[RequiresPhp('>= 8.1')] + #[Test] public function it_can_assign_and_remove_a_role_using_enums() { $enum1 = TestModels\TestRolePermissionsEnum::USERMANAGER; @@ -97,6 +102,8 @@ public function it_can_assign_and_remove_a_role_using_enums() * * @requires PHP >= 8.1 */ + #[RequiresPhp('>= 8.1')] + #[Test] public function it_can_scope_a_role_using_enums() { $enum1 = TestModels\TestRolePermissionsEnum::USERMANAGER; @@ -124,6 +131,7 @@ public function it_can_scope_a_role_using_enums() } /** @test */ + #[Test] public function it_can_assign_and_remove_a_role() { $this->assertFalse($this->testUser->hasRole('testRole')); @@ -138,6 +146,7 @@ public function it_can_assign_and_remove_a_role() } /** @test */ + #[Test] public function it_removes_a_role_and_returns_roles() { $this->testUser->assignRole('testRole'); @@ -154,6 +163,7 @@ public function it_removes_a_role_and_returns_roles() } /** @test */ + #[Test] public function it_can_assign_and_remove_a_role_on_a_permission() { $this->testUserPermission->assignRole('testRole'); @@ -166,6 +176,7 @@ public function it_can_assign_and_remove_a_role_on_a_permission() } /** @test */ + #[Test] public function it_can_assign_a_role_using_an_object() { $this->testUser->assignRole($this->testUserRole); @@ -174,6 +185,7 @@ public function it_can_assign_a_role_using_an_object() } /** @test */ + #[Test] public function it_can_assign_a_role_using_an_id() { $this->testUser->assignRole($this->testUserRole->getKey()); @@ -182,6 +194,7 @@ public function it_can_assign_a_role_using_an_id() } /** @test */ + #[Test] public function it_can_assign_multiple_roles_at_once() { $this->testUser->assignRole($this->testUserRole->getKey(), 'testRole2'); @@ -192,6 +205,7 @@ public function it_can_assign_multiple_roles_at_once() } /** @test */ + #[Test] public function it_can_assign_multiple_roles_using_an_array() { $this->testUser->assignRole([$this->testUserRole->getKey(), 'testRole2']); @@ -202,6 +216,7 @@ public function it_can_assign_multiple_roles_using_an_array() } /** @test */ + #[Test] public function it_does_not_remove_already_associated_roles_when_assigning_new_roles() { $this->testUser->assignRole($this->testUserRole->getKey()); @@ -212,6 +227,7 @@ public function it_does_not_remove_already_associated_roles_when_assigning_new_r } /** @test */ + #[Test] public function it_does_not_throw_an_exception_when_assigning_a_role_that_is_already_assigned() { $this->testUser->assignRole($this->testUserRole->getKey()); @@ -222,6 +238,7 @@ public function it_does_not_throw_an_exception_when_assigning_a_role_that_is_alr } /** @test */ + #[Test] public function it_throws_an_exception_when_assigning_a_role_that_does_not_exist() { $this->expectException(RoleDoesNotExist::class); @@ -230,6 +247,7 @@ public function it_throws_an_exception_when_assigning_a_role_that_does_not_exist } /** @test */ + #[Test] public function it_can_only_assign_roles_from_the_correct_guard() { $this->expectException(RoleDoesNotExist::class); @@ -238,6 +256,7 @@ public function it_can_only_assign_roles_from_the_correct_guard() } /** @test */ + #[Test] public function it_throws_an_exception_when_assigning_a_role_from_a_different_guard() { $this->expectException(GuardDoesNotMatch::class); @@ -246,6 +265,7 @@ public function it_throws_an_exception_when_assigning_a_role_from_a_different_gu } /** @test */ + #[Test] public function it_ignores_null_roles_when_syncing() { $this->testUser->assignRole('testRole'); @@ -258,6 +278,7 @@ public function it_ignores_null_roles_when_syncing() } /** @test */ + #[Test] public function it_can_sync_roles_from_a_string() { $this->testUser->assignRole('testRole'); @@ -270,6 +291,7 @@ public function it_can_sync_roles_from_a_string() } /** @test */ + #[Test] public function it_can_sync_roles_from_a_string_on_a_permission() { $this->testUserPermission->assignRole('testRole'); @@ -282,6 +304,7 @@ public function it_can_sync_roles_from_a_string_on_a_permission() } /** @test */ + #[Test] public function it_can_avoid_sync_duplicated_roles() { $this->testUser->syncRoles('testRole', 'testRole', 'testRole2'); @@ -292,6 +315,7 @@ public function it_can_avoid_sync_duplicated_roles() } /** @test */ + #[Test] public function it_can_sync_multiple_roles() { $this->testUser->syncRoles('testRole', 'testRole2'); @@ -302,6 +326,7 @@ public function it_can_sync_multiple_roles() } /** @test */ + #[Test] public function it_can_sync_multiple_roles_from_an_array() { $this->testUser->syncRoles(['testRole', 'testRole2']); @@ -312,6 +337,7 @@ public function it_can_sync_multiple_roles_from_an_array() } /** @test */ + #[Test] public function it_will_remove_all_roles_when_an_empty_array_is_passed_to_sync_roles() { $this->testUser->assignRole('testRole'); @@ -326,6 +352,7 @@ public function it_will_remove_all_roles_when_an_empty_array_is_passed_to_sync_r } /** @test */ + #[Test] public function sync_roles_error_does_not_detach_roles() { $this->testUser->assignRole('testRole'); @@ -338,6 +365,7 @@ public function sync_roles_error_does_not_detach_roles() } /** @test */ + #[Test] public function it_will_sync_roles_to_a_model_that_is_not_persisted() { $user = new User(['email' => 'test@user.com']); @@ -353,6 +381,7 @@ public function it_will_sync_roles_to_a_model_that_is_not_persisted() } /** @test */ + #[Test] public function it_does_not_run_unnecessary_sqls_when_assigning_new_roles() { $role2 = app(Role::class)->where('name', ['testRole2'])->first(); @@ -365,6 +394,7 @@ public function it_does_not_run_unnecessary_sqls_when_assigning_new_roles() } /** @test */ + #[Test] public function calling_syncRoles_before_saving_object_doesnt_interfere_with_other_objects() { $user = new User(['email' => 'test@user.com']); @@ -387,6 +417,7 @@ public function calling_syncRoles_before_saving_object_doesnt_interfere_with_oth } /** @test */ + #[Test] public function calling_assignRole_before_saving_object_doesnt_interfere_with_other_objects() { $user = new User(['email' => 'test@user.com']); @@ -409,6 +440,7 @@ public function calling_assignRole_before_saving_object_doesnt_interfere_with_ot } /** @test */ + #[Test] public function it_throws_an_exception_when_syncing_a_role_from_another_guard() { $this->expectException(RoleDoesNotExist::class); @@ -421,6 +453,7 @@ public function it_throws_an_exception_when_syncing_a_role_from_another_guard() } /** @test */ + #[Test] public function it_deletes_pivot_table_entries_when_deleting_models() { $user = User::create(['email' => 'user@test.com']); @@ -438,6 +471,7 @@ public function it_deletes_pivot_table_entries_when_deleting_models() } /** @test */ + #[Test] public function it_can_scope_users_using_a_string() { $user1 = User::create(['email' => 'user1@test.com']); @@ -451,6 +485,7 @@ public function it_can_scope_users_using_a_string() } /** @test */ + #[Test] public function it_can_withoutscope_users_using_a_string() { User::all()->each(fn ($item) => $item->delete()); @@ -467,6 +502,7 @@ public function it_can_withoutscope_users_using_a_string() } /** @test */ + #[Test] public function it_can_scope_users_using_an_array() { $user1 = User::create(['email' => 'user1@test.com']); @@ -482,6 +518,7 @@ public function it_can_scope_users_using_an_array() } /** @test */ + #[Test] public function it_can_withoutscope_users_using_an_array() { User::all()->each(fn ($item) => $item->delete()); @@ -500,6 +537,7 @@ public function it_can_withoutscope_users_using_an_array() } /** @test */ + #[Test] public function it_can_scope_users_using_an_array_of_ids_and_names() { $user1 = User::create(['email' => 'user1@test.com']); @@ -516,6 +554,7 @@ public function it_can_scope_users_using_an_array_of_ids_and_names() } /** @test */ + #[Test] public function it_can_withoutscope_users_using_an_array_of_ids_and_names() { app(Role::class)->create(['name' => 'testRole3']); @@ -537,6 +576,7 @@ public function it_can_withoutscope_users_using_an_array_of_ids_and_names() } /** @test */ + #[Test] public function it_can_scope_users_using_a_collection() { $user1 = User::create(['email' => 'user1@test.com']); @@ -552,6 +592,7 @@ public function it_can_scope_users_using_a_collection() } /** @test */ + #[Test] public function it_can_withoutscope_users_using_a_collection() { app(Role::class)->create(['name' => 'testRole3']); @@ -572,6 +613,7 @@ public function it_can_withoutscope_users_using_a_collection() } /** @test */ + #[Test] public function it_can_scope_users_using_an_object() { $user1 = User::create(['email' => 'user1@test.com']); @@ -589,6 +631,7 @@ public function it_can_scope_users_using_an_object() } /** @test */ + #[Test] public function it_can_withoutscope_users_using_an_object() { User::all()->each(fn ($item) => $item->delete()); @@ -609,6 +652,7 @@ public function it_can_withoutscope_users_using_an_object() } /** @test */ + #[Test] public function it_can_scope_against_a_specific_guard() { $user1 = User::create(['email' => 'user1@test.com']); @@ -635,6 +679,7 @@ public function it_can_scope_against_a_specific_guard() } /** @test */ + #[Test] public function it_can_withoutscope_against_a_specific_guard() { User::all()->each(fn ($item) => $item->delete()); @@ -665,6 +710,7 @@ public function it_can_withoutscope_against_a_specific_guard() } /** @test */ + #[Test] public function it_throws_an_exception_when_trying_to_scope_a_role_from_another_guard() { $this->expectException(RoleDoesNotExist::class); @@ -677,6 +723,7 @@ public function it_throws_an_exception_when_trying_to_scope_a_role_from_another_ } /** @test */ + #[Test] public function it_throws_an_exception_when_trying_to_call_withoutscope_on_a_role_from_another_guard() { $this->expectException(RoleDoesNotExist::class); @@ -689,6 +736,7 @@ public function it_throws_an_exception_when_trying_to_call_withoutscope_on_a_rol } /** @test */ + #[Test] public function it_throws_an_exception_when_trying_to_scope_a_non_existing_role() { $this->expectException(RoleDoesNotExist::class); @@ -697,6 +745,7 @@ public function it_throws_an_exception_when_trying_to_scope_a_non_existing_role( } /** @test */ + #[Test] public function it_throws_an_exception_when_trying_to_use_withoutscope_on_a_non_existing_role() { $this->expectException(RoleDoesNotExist::class); @@ -705,6 +754,7 @@ public function it_throws_an_exception_when_trying_to_use_withoutscope_on_a_non_ } /** @test */ + #[Test] public function it_can_determine_that_a_user_has_one_of_the_given_roles() { $roleModel = app(Role::class); @@ -733,6 +783,7 @@ public function it_can_determine_that_a_user_has_one_of_the_given_roles() } /** @test */ + #[Test] public function it_can_determine_that_a_user_has_all_of_the_given_roles() { $roleModel = app(Role::class); @@ -762,6 +813,7 @@ public function it_can_determine_that_a_user_has_all_of_the_given_roles() } /** @test */ + #[Test] public function it_can_determine_that_a_user_has_exact_all_of_the_given_roles() { $roleModel = app(Role::class); @@ -801,6 +853,7 @@ public function it_can_determine_that_a_user_has_exact_all_of_the_given_roles() } /** @test */ + #[Test] public function it_can_determine_that_a_user_does_not_have_a_role_from_another_guard() { $this->assertFalse($this->testUser->hasRole('testAdminRole')); @@ -815,6 +868,7 @@ public function it_can_determine_that_a_user_does_not_have_a_role_from_another_g } /** @test */ + #[Test] public function it_can_check_against_any_multiple_roles_using_multiple_arguments() { $this->testUser->assignRole('testRole'); @@ -823,12 +877,14 @@ public function it_can_check_against_any_multiple_roles_using_multiple_arguments } /** @test */ + #[Test] public function it_returns_false_instead_of_an_exception_when_checking_against_any_undefined_roles_using_multiple_arguments() { $this->assertFalse($this->testUser->hasAnyRole('This Role Does Not Even Exist', $this->testAdminRole)); } /** @test */ + #[Test] public function it_throws_an_exception_if_an_unsupported_type_is_passed_to_hasRoles() { $this->expectException(\TypeError::class); @@ -837,6 +893,7 @@ public function it_throws_an_exception_if_an_unsupported_type_is_passed_to_hasRo } /** @test */ + #[Test] public function it_can_retrieve_role_names() { $this->testUser->assignRole('testRole', 'testRole2'); @@ -848,6 +905,7 @@ public function it_can_retrieve_role_names() } /** @test */ + #[Test] public function it_does_not_detach_roles_when_user_soft_deleting() { $user = SoftDeletingUser::create(['email' => 'test@example.com']); @@ -860,6 +918,7 @@ public function it_does_not_detach_roles_when_user_soft_deleting() } /** @test */ + #[Test] public function it_can_be_given_a_role_on_permission_when_lazy_loading_is_restricted() { $this->assertTrue(Model::preventsLazyLoading()); @@ -876,6 +935,7 @@ public function it_can_be_given_a_role_on_permission_when_lazy_loading_is_restri } /** @test */ + #[Test] public function it_can_be_given_a_role_on_user_when_lazy_loading_is_restricted() { $this->assertTrue(Model::preventsLazyLoading()); diff --git a/tests/HasRolesWithCustomModelsTest.php b/tests/HasRolesWithCustomModelsTest.php index b306676af..767ab6752 100644 --- a/tests/HasRolesWithCustomModelsTest.php +++ b/tests/HasRolesWithCustomModelsTest.php @@ -4,6 +4,7 @@ use Illuminate\Support\Carbon; use Illuminate\Support\Facades\DB; +use PHPUnit\Framework\Attributes\Test; use Spatie\Permission\Tests\TestModels\Admin; use Spatie\Permission\Tests\TestModels\Role; @@ -25,12 +26,14 @@ protected function getEnvironmentSetUp($app) } /** @test */ + #[Test] public function it_can_use_custom_model_role() { $this->assertSame(get_class($this->testUserRole), Role::class); } /** @test */ + #[Test] public function it_doesnt_detach_permissions_when_soft_deleting() { $this->testUserRole->givePermissionTo($this->testUserPermission); @@ -47,6 +50,7 @@ public function it_doesnt_detach_permissions_when_soft_deleting() } /** @test */ + #[Test] public function it_doesnt_detach_users_when_soft_deleting() { $this->testUser->assignRole($this->testUserRole); @@ -63,6 +67,7 @@ public function it_doesnt_detach_users_when_soft_deleting() } /** @test */ + #[Test] public function it_does_detach_permissions_and_users_when_force_deleting() { $role_id = $this->testUserRole->getKey(); @@ -83,6 +88,7 @@ public function it_does_detach_permissions_and_users_when_force_deleting() } /** @test */ + #[Test] public function it_should_touch_when_assigning_new_roles() { Carbon::setTestNow('2021-07-19 10:13:14'); diff --git a/tests/MultipleGuardsTest.php b/tests/MultipleGuardsTest.php index 64c229a56..cd57639cf 100644 --- a/tests/MultipleGuardsTest.php +++ b/tests/MultipleGuardsTest.php @@ -4,6 +4,7 @@ use Illuminate\Http\Request; use Illuminate\Support\Facades\Route; +use PHPUnit\Framework\Attributes\Test; use Spatie\Permission\Contracts\Permission; use Spatie\Permission\Tests\TestModels\Manager; @@ -37,6 +38,7 @@ public function setUpRoutes(): void } /** @test */ + #[Test] public function it_can_give_a_permission_to_a_model_that_is_used_by_multiple_guards() { $this->testUser->givePermissionTo(app(Permission::class)::create([ @@ -55,6 +57,7 @@ public function it_can_give_a_permission_to_a_model_that_is_used_by_multiple_gua } /** @test */ + #[Test] public function the_gate_can_grant_permission_to_a_user_by_passing_a_guard_name() { $this->testUser->givePermissionTo(app(Permission::class)::create([ @@ -96,6 +99,7 @@ public function the_gate_can_grant_permission_to_a_user_by_passing_a_guard_name( } /** @test */ + #[Test] public function it_can_honour_guardName_function_on_model_for_overriding_guard_name_property() { $user = Manager::create(['email' => 'manager@test.com']); diff --git a/tests/PermissionMiddlewareTest.php b/tests/PermissionMiddlewareTest.php index 354f1f8b1..4dc233193 100644 --- a/tests/PermissionMiddlewareTest.php +++ b/tests/PermissionMiddlewareTest.php @@ -9,6 +9,7 @@ use Illuminate\Support\Facades\Gate; use InvalidArgumentException; use Laravel\Passport\Passport; +use PHPUnit\Framework\Attributes\Test; use Spatie\Permission\Contracts\Permission; use Spatie\Permission\Exceptions\UnauthorizedException; use Spatie\Permission\Middleware\PermissionMiddleware; @@ -28,6 +29,7 @@ protected function setUp(): void } /** @test */ + #[Test] public function a_guest_cannot_access_a_route_protected_by_the_permission_middleware() { $this->assertEquals( @@ -37,6 +39,7 @@ public function a_guest_cannot_access_a_route_protected_by_the_permission_middle } /** @test */ + #[Test] public function a_user_cannot_access_a_route_protected_by_the_permission_middleware_of_a_different_guard() { // These permissions are created fresh here in reverse order of guard being applied, so they are not "found first" in the db lookup when matching @@ -75,6 +78,7 @@ public function a_user_cannot_access_a_route_protected_by_the_permission_middlew } /** @test */ + #[Test] public function a_client_cannot_access_a_route_protected_by_the_permission_middleware_of_a_different_guard(): void { if ($this->getLaravelVersion() < 9) { @@ -101,6 +105,7 @@ public function a_client_cannot_access_a_route_protected_by_the_permission_middl } /** @test */ + #[Test] public function a_super_admin_user_can_access_a_route_protected_by_permission_middleware() { Auth::login($this->testUser); @@ -116,6 +121,7 @@ public function a_super_admin_user_can_access_a_route_protected_by_permission_mi } /** @test */ + #[Test] public function a_user_can_access_a_route_protected_by_permission_middleware_if_have_this_permission() { Auth::login($this->testUser); @@ -129,6 +135,7 @@ public function a_user_can_access_a_route_protected_by_permission_middleware_if_ } /** @test */ + #[Test] public function a_client_can_access_a_route_protected_by_permission_middleware_if_have_this_permission(): void { if ($this->getLaravelVersion() < 9) { @@ -146,6 +153,7 @@ public function a_client_can_access_a_route_protected_by_permission_middleware_i } /** @test */ + #[Test] public function a_user_can_access_a_route_protected_by_this_permission_middleware_if_have_one_of_the_permissions() { Auth::login($this->testUser); @@ -164,6 +172,7 @@ public function a_user_can_access_a_route_protected_by_this_permission_middlewar } /** @test */ + #[Test] public function a_client_can_access_a_route_protected_by_this_permission_middleware_if_have_one_of_the_permissions(): void { if ($this->getLaravelVersion() < 9) { @@ -186,6 +195,7 @@ public function a_client_can_access_a_route_protected_by_this_permission_middlew } /** @test */ + #[Test] public function a_user_cannot_access_a_route_protected_by_the_permission_middleware_if_have_not_has_roles_trait() { $userWithoutHasRoles = UserWithoutHasRoles::create(['email' => 'test_not_has_roles@user.com']); @@ -199,6 +209,7 @@ public function a_user_cannot_access_a_route_protected_by_the_permission_middlew } /** @test */ + #[Test] public function a_user_cannot_access_a_route_protected_by_the_permission_middleware_if_have_a_different_permission() { Auth::login($this->testUser); @@ -212,6 +223,7 @@ public function a_user_cannot_access_a_route_protected_by_the_permission_middlew } /** @test */ + #[Test] public function a_client_cannot_access_a_route_protected_by_the_permission_middleware_if_have_a_different_permission(): void { if ($this->getLaravelVersion() < 9) { @@ -229,6 +241,7 @@ public function a_client_cannot_access_a_route_protected_by_the_permission_middl } /** @test */ + #[Test] public function a_user_cannot_access_a_route_protected_by_permission_middleware_if_have_not_permissions() { Auth::login($this->testUser); @@ -240,6 +253,7 @@ public function a_user_cannot_access_a_route_protected_by_permission_middleware_ } /** @test */ + #[Test] public function a_client_cannot_access_a_route_protected_by_permission_middleware_if_have_not_permissions(): void { if ($this->getLaravelVersion() < 9) { @@ -255,6 +269,7 @@ public function a_client_cannot_access_a_route_protected_by_permission_middlewar } /** @test */ + #[Test] public function a_user_can_access_a_route_protected_by_permission_middleware_if_has_permission_via_role() { Auth::login($this->testUser); @@ -274,6 +289,7 @@ public function a_user_can_access_a_route_protected_by_permission_middleware_if_ } /** @test */ + #[Test] public function a_client_can_access_a_route_protected_by_permission_middleware_if_has_permission_via_role(): void { if ($this->getLaravelVersion() < 9) { @@ -297,6 +313,7 @@ public function a_client_can_access_a_route_protected_by_permission_middleware_i } /** @test */ + #[Test] public function the_required_permissions_can_be_fetched_from_the_exception() { Auth::login($this->testUser); @@ -318,6 +335,7 @@ public function the_required_permissions_can_be_fetched_from_the_exception() } /** @test */ + #[Test] public function the_required_permissions_can_be_displayed_in_the_exception() { Auth::login($this->testUser); @@ -337,6 +355,7 @@ public function the_required_permissions_can_be_displayed_in_the_exception() } /** @test */ + #[Test] public function use_not_existing_custom_guard_in_permission() { $class = null; @@ -353,6 +372,7 @@ public function use_not_existing_custom_guard_in_permission() } /** @test */ + #[Test] public function user_can_not_access_permission_with_guard_admin_while_login_using_default_guard() { Auth::login($this->testUser); @@ -366,6 +386,7 @@ public function user_can_not_access_permission_with_guard_admin_while_login_usin } /** @test */ + #[Test] public function client_can_not_access_permission_with_guard_admin_while_login_using_default_guard(): void { if ($this->getLaravelVersion() < 9) { @@ -383,6 +404,7 @@ public function client_can_not_access_permission_with_guard_admin_while_login_us } /** @test */ + #[Test] public function user_can_access_permission_with_guard_admin_while_login_using_admin_guard() { Auth::guard('admin')->login($this->testAdmin); @@ -396,6 +418,7 @@ public function user_can_access_permission_with_guard_admin_while_login_using_ad } /** @test */ + #[Test] public function the_middleware_can_be_created_with_static_using_method() { $this->assertSame( diff --git a/tests/PermissionRegistrarTest.php b/tests/PermissionRegistrarTest.php index 115926d4c..c9df969e4 100644 --- a/tests/PermissionRegistrarTest.php +++ b/tests/PermissionRegistrarTest.php @@ -2,6 +2,7 @@ namespace Spatie\Permission\Tests; +use PHPUnit\Framework\Attributes\Test; use Spatie\Permission\Contracts\Permission as PermissionContract; use Spatie\Permission\Contracts\Role as RoleContract; use Spatie\Permission\Models\Permission as SpatiePermission; @@ -13,6 +14,7 @@ class PermissionRegistrarTest extends TestCase { /** @test */ + #[Test] public function it_can_clear_loaded_permissions_collection() { $reflectedClass = new \ReflectionClass(app(PermissionRegistrar::class)); @@ -29,6 +31,7 @@ public function it_can_clear_loaded_permissions_collection() } /** @test */ + #[Test] public function it_can_check_uids() { $uids = [ @@ -72,6 +75,7 @@ public function it_can_check_uids() } /** @test */ + #[Test] public function it_can_get_permission_class() { $this->assertSame(SpatiePermission::class, app(PermissionRegistrar::class)->getPermissionClass()); @@ -79,6 +83,7 @@ public function it_can_get_permission_class() } /** @test */ + #[Test] public function it_can_change_permission_class() { $this->assertSame(SpatiePermission::class, config('permission.models.permission')); @@ -93,6 +98,7 @@ public function it_can_change_permission_class() } /** @test */ + #[Test] public function it_can_get_role_class() { $this->assertSame(SpatieRole::class, app(PermissionRegistrar::class)->getRoleClass()); @@ -100,6 +106,7 @@ public function it_can_get_role_class() } /** @test */ + #[Test] public function it_can_change_role_class() { $this->assertSame(SpatieRole::class, config('permission.models.role')); @@ -114,6 +121,7 @@ public function it_can_change_role_class() } /** @test */ + #[Test] public function it_can_change_team_id() { $team_id = '00000000-0000-0000-0000-000000000000'; diff --git a/tests/PermissionTest.php b/tests/PermissionTest.php index d43645730..553b3650a 100644 --- a/tests/PermissionTest.php +++ b/tests/PermissionTest.php @@ -2,6 +2,7 @@ namespace Spatie\Permission\Tests; +use PHPUnit\Framework\Attributes\Test; use Spatie\Permission\Contracts\Permission; use Spatie\Permission\Exceptions\PermissionAlreadyExists; use Spatie\Permission\Tests\TestModels\User; @@ -9,6 +10,7 @@ class PermissionTest extends TestCase { /** @test */ + #[Test] public function it_get_user_models_using_with() { $this->testUser->givePermissionTo($this->testUserPermission); @@ -23,6 +25,7 @@ public function it_get_user_models_using_with() } /** @test */ + #[Test] public function it_throws_an_exception_when_the_permission_already_exists() { $this->expectException(PermissionAlreadyExists::class); @@ -32,6 +35,7 @@ public function it_throws_an_exception_when_the_permission_already_exists() } /** @test */ + #[Test] public function it_belongs_to_a_guard() { $permission = app(Permission::class)->create(['name' => 'can-edit', 'guard_name' => 'admin']); @@ -40,6 +44,7 @@ public function it_belongs_to_a_guard() } /** @test */ + #[Test] public function it_belongs_to_the_default_guard_by_default() { $this->assertEquals( @@ -49,6 +54,7 @@ public function it_belongs_to_the_default_guard_by_default() } /** @test */ + #[Test] public function it_has_user_models_of_the_right_class() { $this->testAdmin->givePermissionTo($this->testAdminPermission); @@ -61,6 +67,7 @@ public function it_has_user_models_of_the_right_class() } /** @test */ + #[Test] public function it_is_retrievable_by_id() { $permission_by_id = app(Permission::class)->findById($this->testUserPermission->id); @@ -69,6 +76,7 @@ public function it_is_retrievable_by_id() } /** @test */ + #[Test] public function it_can_delete_hydrated_permissions() { $this->reloadPermissions(); diff --git a/tests/PolicyTest.php b/tests/PolicyTest.php index 7708225b0..78490f997 100644 --- a/tests/PolicyTest.php +++ b/tests/PolicyTest.php @@ -4,11 +4,13 @@ use Illuminate\Contracts\Auth\Access\Authorizable; use Illuminate\Contracts\Auth\Access\Gate; +use PHPUnit\Framework\Attributes\Test; use Spatie\Permission\Tests\TestModels\Content; class PolicyTest extends TestCase { /** @test */ + #[Test] public function policy_methods_and_before_intercepts_can_allow_and_deny() { $record1 = Content::create(['content' => 'special admin content']); diff --git a/tests/RoleMiddlewareTest.php b/tests/RoleMiddlewareTest.php index 780277068..532f2061f 100644 --- a/tests/RoleMiddlewareTest.php +++ b/tests/RoleMiddlewareTest.php @@ -8,6 +8,7 @@ use Illuminate\Support\Facades\Config; use InvalidArgumentException; use Laravel\Passport\Passport; +use PHPUnit\Framework\Attributes\Test; use Spatie\Permission\Exceptions\UnauthorizedException; use Spatie\Permission\Middleware\RoleMiddleware; use Spatie\Permission\Tests\TestModels\UserWithoutHasRoles; @@ -26,6 +27,7 @@ protected function setUp(): void } /** @test */ + #[Test] public function a_guest_cannot_access_a_route_protected_by_rolemiddleware() { $this->assertEquals( @@ -35,6 +37,7 @@ public function a_guest_cannot_access_a_route_protected_by_rolemiddleware() } /** @test */ + #[Test] public function a_user_cannot_access_a_route_protected_by_role_middleware_of_another_guard() { Auth::login($this->testUser); @@ -48,6 +51,7 @@ public function a_user_cannot_access_a_route_protected_by_role_middleware_of_ano } /** @test */ + #[Test] public function a_client_cannot_access_a_route_protected_by_role_middleware_of_another_guard(): void { if ($this->getLaravelVersion() < 9) { @@ -65,6 +69,7 @@ public function a_client_cannot_access_a_route_protected_by_role_middleware_of_a } /** @test */ + #[Test] public function a_user_can_access_a_route_protected_by_role_middleware_if_have_this_role() { Auth::login($this->testUser); @@ -78,6 +83,7 @@ public function a_user_can_access_a_route_protected_by_role_middleware_if_have_t } /** @test */ + #[Test] public function a_client_can_access_a_route_protected_by_role_middleware_if_have_this_role(): void { if ($this->getLaravelVersion() < 9) { @@ -95,6 +101,7 @@ public function a_client_can_access_a_route_protected_by_role_middleware_if_have } /** @test */ + #[Test] public function a_user_can_access_a_route_protected_by_this_role_middleware_if_have_one_of_the_roles() { Auth::login($this->testUser); @@ -113,6 +120,7 @@ public function a_user_can_access_a_route_protected_by_this_role_middleware_if_h } /** @test */ + #[Test] public function a_client_can_access_a_route_protected_by_this_role_middleware_if_have_one_of_the_roles(): void { if ($this->getLaravelVersion() < 9) { @@ -135,6 +143,7 @@ public function a_client_can_access_a_route_protected_by_this_role_middleware_if } /** @test */ + #[Test] public function a_user_cannot_access_a_route_protected_by_the_role_middleware_if_have_not_has_roles_trait() { $userWithoutHasRoles = UserWithoutHasRoles::create(['email' => 'test_not_has_roles@user.com']); @@ -148,6 +157,7 @@ public function a_user_cannot_access_a_route_protected_by_the_role_middleware_if } /** @test */ + #[Test] public function a_user_cannot_access_a_route_protected_by_the_role_middleware_if_have_a_different_role() { Auth::login($this->testUser); @@ -161,6 +171,7 @@ public function a_user_cannot_access_a_route_protected_by_the_role_middleware_if } /** @test */ + #[Test] public function a_client_cannot_access_a_route_protected_by_the_role_middleware_if_have_a_different_role(): void { if ($this->getLaravelVersion() < 9) { @@ -178,6 +189,7 @@ public function a_client_cannot_access_a_route_protected_by_the_role_middleware_ } /** @test */ + #[Test] public function a_user_cannot_access_a_route_protected_by_role_middleware_if_have_not_roles() { Auth::login($this->testUser); @@ -189,6 +201,7 @@ public function a_user_cannot_access_a_route_protected_by_role_middleware_if_hav } /** @test */ + #[Test] public function a_client_cannot_access_a_route_protected_by_role_middleware_if_have_not_roles(): void { if ($this->getLaravelVersion() < 9) { @@ -204,6 +217,7 @@ public function a_client_cannot_access_a_route_protected_by_role_middleware_if_h } /** @test */ + #[Test] public function a_user_cannot_access_a_route_protected_by_role_middleware_if_role_is_undefined() { Auth::login($this->testUser); @@ -215,6 +229,7 @@ public function a_user_cannot_access_a_route_protected_by_role_middleware_if_rol } /** @test */ + #[Test] public function a_client_cannot_access_a_route_protected_by_role_middleware_if_role_is_undefined(): void { if ($this->getLaravelVersion() < 9) { @@ -230,6 +245,7 @@ public function a_client_cannot_access_a_route_protected_by_role_middleware_if_r } /** @test */ + #[Test] public function the_required_roles_can_be_fetched_from_the_exception() { Auth::login($this->testUser); @@ -251,6 +267,7 @@ public function the_required_roles_can_be_fetched_from_the_exception() } /** @test */ + #[Test] public function the_required_roles_can_be_displayed_in_the_exception() { Auth::login($this->testUser); @@ -270,6 +287,7 @@ public function the_required_roles_can_be_displayed_in_the_exception() } /** @test */ + #[Test] public function use_not_existing_custom_guard_in_role() { $class = null; @@ -286,6 +304,7 @@ public function use_not_existing_custom_guard_in_role() } /** @test */ + #[Test] public function user_can_not_access_role_with_guard_admin_while_login_using_default_guard() { Auth::login($this->testUser); @@ -299,6 +318,7 @@ public function user_can_not_access_role_with_guard_admin_while_login_using_defa } /** @test */ + #[Test] public function client_can_not_access_role_with_guard_admin_while_login_using_default_guard(): void { if ($this->getLaravelVersion() < 9) { @@ -316,6 +336,7 @@ public function client_can_not_access_role_with_guard_admin_while_login_using_de } /** @test */ + #[Test] public function user_can_access_role_with_guard_admin_while_login_using_admin_guard() { Auth::guard('admin')->login($this->testAdmin); @@ -329,6 +350,7 @@ public function user_can_access_role_with_guard_admin_while_login_using_admin_gu } /** @test */ + #[Test] public function the_middleware_can_be_created_with_static_using_method() { $this->assertSame( diff --git a/tests/RoleOrPermissionMiddlewareTest.php b/tests/RoleOrPermissionMiddlewareTest.php index 617c4c5af..8a60e9f4f 100644 --- a/tests/RoleOrPermissionMiddlewareTest.php +++ b/tests/RoleOrPermissionMiddlewareTest.php @@ -9,6 +9,7 @@ use Illuminate\Support\Facades\Gate; use InvalidArgumentException; use Laravel\Passport\Passport; +use PHPUnit\Framework\Attributes\Test; use Spatie\Permission\Exceptions\UnauthorizedException; use Spatie\Permission\Middleware\RoleOrPermissionMiddleware; use Spatie\Permission\Tests\TestModels\UserWithoutHasRoles; @@ -27,6 +28,7 @@ protected function setUp(): void } /** @test */ + #[Test] public function a_guest_cannot_access_a_route_protected_by_the_role_or_permission_middleware() { $this->assertEquals( @@ -36,6 +38,7 @@ public function a_guest_cannot_access_a_route_protected_by_the_role_or_permissio } /** @test */ + #[Test] public function a_user_can_access_a_route_protected_by_permission_or_role_middleware_if_has_this_permission_or_role() { Auth::login($this->testUser); @@ -70,6 +73,7 @@ public function a_user_can_access_a_route_protected_by_permission_or_role_middle } /** @test */ + #[Test] public function a_client_can_access_a_route_protected_by_permission_or_role_middleware_if_has_this_permission_or_role(): void { if ($this->getLaravelVersion() < 9) { @@ -108,6 +112,7 @@ public function a_client_can_access_a_route_protected_by_permission_or_role_midd } /** @test */ + #[Test] public function a_super_admin_user_can_access_a_route_protected_by_permission_or_role_middleware() { Auth::login($this->testUser); @@ -123,6 +128,7 @@ public function a_super_admin_user_can_access_a_route_protected_by_permission_or } /** @test */ + #[Test] public function a_user_can_not_access_a_route_protected_by_permission_or_role_middleware_if_have_not_has_roles_trait() { $userWithoutHasRoles = UserWithoutHasRoles::create(['email' => 'test_not_has_roles@user.com']); @@ -136,6 +142,7 @@ public function a_user_can_not_access_a_route_protected_by_permission_or_role_mi } /** @test */ + #[Test] public function a_user_can_not_access_a_route_protected_by_permission_or_role_middleware_if_have_not_this_permission_and_role() { Auth::login($this->testUser); @@ -152,6 +159,7 @@ public function a_user_can_not_access_a_route_protected_by_permission_or_role_mi } /** @test */ + #[Test] public function a_client_can_not_access_a_route_protected_by_permission_or_role_middleware_if_have_not_this_permission_and_role(): void { if ($this->getLaravelVersion() < 9) { @@ -172,6 +180,7 @@ public function a_client_can_not_access_a_route_protected_by_permission_or_role_ } /** @test */ + #[Test] public function use_not_existing_custom_guard_in_role_or_permission() { $class = null; @@ -188,6 +197,7 @@ public function use_not_existing_custom_guard_in_role_or_permission() } /** @test */ + #[Test] public function user_can_not_access_permission_or_role_with_guard_admin_while_login_using_default_guard() { Auth::login($this->testUser); @@ -202,6 +212,7 @@ public function user_can_not_access_permission_or_role_with_guard_admin_while_lo } /** @test */ + #[Test] public function client_can_not_access_permission_or_role_with_guard_admin_while_login_using_default_guard(): void { if ($this->getLaravelVersion() < 9) { @@ -220,6 +231,7 @@ public function client_can_not_access_permission_or_role_with_guard_admin_while_ } /** @test */ + #[Test] public function user_can_access_permission_or_role_with_guard_admin_while_login_using_admin_guard() { Auth::guard('admin')->login($this->testAdmin); @@ -234,6 +246,7 @@ public function user_can_access_permission_or_role_with_guard_admin_while_login_ } /** @test */ + #[Test] public function the_required_permissions_or_roles_can_be_fetched_from_the_exception() { Auth::login($this->testUser); @@ -255,6 +268,7 @@ public function the_required_permissions_or_roles_can_be_fetched_from_the_except } /** @test */ + #[Test] public function the_required_permissions_or_roles_can_be_displayed_in_the_exception() { Auth::login($this->testUser); @@ -275,6 +289,7 @@ public function the_required_permissions_or_roles_can_be_displayed_in_the_except } /** @test */ + #[Test] public function the_middleware_can_be_created_with_static_using_method() { $this->assertSame( diff --git a/tests/RoleTest.php b/tests/RoleTest.php index 68d7c1aee..af88b0af6 100644 --- a/tests/RoleTest.php +++ b/tests/RoleTest.php @@ -2,6 +2,7 @@ namespace Spatie\Permission\Tests; +use PHPUnit\Framework\Attributes\Test; use Spatie\Permission\Contracts\Role; use Spatie\Permission\Exceptions\GuardDoesNotMatch; use Spatie\Permission\Exceptions\PermissionDoesNotExist; @@ -25,6 +26,7 @@ protected function setUp(): void } /** @test */ + #[Test] public function it_get_user_models_using_with() { $this->testUser->assignRole($this->testUserRole); @@ -38,6 +40,7 @@ public function it_get_user_models_using_with() } /** @test */ + #[Test] public function it_has_user_models_of_the_right_class() { $this->testAdmin->assignRole($this->testAdminRole); @@ -54,6 +57,7 @@ public function it_has_user_models_of_the_right_class() } /** @test */ + #[Test] public function it_throws_an_exception_when_the_role_already_exists() { $this->expectException(RoleAlreadyExists::class); @@ -63,6 +67,7 @@ public function it_throws_an_exception_when_the_role_already_exists() } /** @test */ + #[Test] public function it_can_be_given_a_permission() { $this->testUserRole->givePermissionTo('edit-articles'); @@ -71,6 +76,7 @@ public function it_can_be_given_a_permission() } /** @test */ + #[Test] public function it_throws_an_exception_when_given_a_permission_that_does_not_exist() { $this->expectException(PermissionDoesNotExist::class); @@ -79,6 +85,7 @@ public function it_throws_an_exception_when_given_a_permission_that_does_not_exi } /** @test */ + #[Test] public function it_throws_an_exception_when_given_a_permission_that_belongs_to_another_guard() { $this->expectException(PermissionDoesNotExist::class); @@ -91,6 +98,7 @@ public function it_throws_an_exception_when_given_a_permission_that_belongs_to_a } /** @test */ + #[Test] public function it_can_be_given_multiple_permissions_using_an_array() { $this->testUserRole->givePermissionTo(['edit-articles', 'edit-news']); @@ -100,6 +108,7 @@ public function it_can_be_given_multiple_permissions_using_an_array() } /** @test */ + #[Test] public function it_can_be_given_multiple_permissions_using_multiple_arguments() { $this->testUserRole->givePermissionTo('edit-articles', 'edit-news'); @@ -109,6 +118,7 @@ public function it_can_be_given_multiple_permissions_using_multiple_arguments() } /** @test */ + #[Test] public function it_can_sync_permissions() { $this->testUserRole->givePermissionTo('edit-articles'); @@ -121,6 +131,7 @@ public function it_can_sync_permissions() } /** @test */ + #[Test] public function it_throws_an_exception_when_syncing_permissions_that_do_not_exist() { $this->testUserRole->givePermissionTo('edit-articles'); @@ -131,6 +142,7 @@ public function it_throws_an_exception_when_syncing_permissions_that_do_not_exis } /** @test */ + #[Test] public function it_throws_an_exception_when_syncing_permissions_that_belong_to_a_different_guard() { $this->testUserRole->givePermissionTo('edit-articles'); @@ -145,6 +157,7 @@ public function it_throws_an_exception_when_syncing_permissions_that_belong_to_a } /** @test */ + #[Test] public function it_will_remove_all_permissions_when_passing_an_empty_array_to_sync_permissions() { $this->testUserRole->givePermissionTo('edit-articles'); @@ -159,6 +172,7 @@ public function it_will_remove_all_permissions_when_passing_an_empty_array_to_sy } /** @test */ + #[Test] public function sync_permission_error_does_not_detach_permissions() { $this->testUserRole->givePermissionTo('edit-news'); @@ -171,6 +185,7 @@ public function sync_permission_error_does_not_detach_permissions() } /** @test */ + #[Test] public function it_can_revoke_a_permission() { $this->testUserRole->givePermissionTo('edit-articles'); @@ -185,6 +200,7 @@ public function it_can_revoke_a_permission() } /** @test */ + #[Test] public function it_can_be_given_a_permission_using_objects() { $this->testUserRole->givePermissionTo($this->testUserPermission); @@ -193,12 +209,14 @@ public function it_can_be_given_a_permission_using_objects() } /** @test */ + #[Test] public function it_returns_false_if_it_does_not_have_the_permission() { $this->assertFalse($this->testUserRole->hasPermissionTo('other-permission')); } /** @test */ + #[Test] public function it_throws_an_exception_if_the_permission_does_not_exist() { $this->expectException(PermissionDoesNotExist::class); @@ -207,6 +225,7 @@ public function it_throws_an_exception_if_the_permission_does_not_exist() } /** @test */ + #[Test] public function it_returns_false_if_it_does_not_have_a_permission_object() { $permission = app(Permission::class)->findByName('other-permission'); @@ -215,6 +234,7 @@ public function it_returns_false_if_it_does_not_have_a_permission_object() } /** @test */ + #[Test] public function it_creates_permission_object_with_findOrCreate_if_it_does_not_have_a_permission_object() { $permission = app(Permission::class)->findOrCreate('another-permission'); @@ -229,6 +249,7 @@ public function it_creates_permission_object_with_findOrCreate_if_it_does_not_ha } /** @test */ + #[Test] public function it_creates_a_role_with_findOrCreate_if_the_named_role_does_not_exist() { $this->expectException(RoleDoesNotExist::class); @@ -243,6 +264,7 @@ public function it_creates_a_role_with_findOrCreate_if_the_named_role_does_not_e } /** @test */ + #[Test] public function it_throws_an_exception_when_a_permission_of_the_wrong_guard_is_passed_in() { $this->expectException(GuardDoesNotMatch::class); @@ -253,6 +275,7 @@ public function it_throws_an_exception_when_a_permission_of_the_wrong_guard_is_p } /** @test */ + #[Test] public function it_belongs_to_a_guard() { $role = app(Role::class)->create(['name' => 'admin', 'guard_name' => 'admin']); @@ -261,6 +284,7 @@ public function it_belongs_to_a_guard() } /** @test */ + #[Test] public function it_belongs_to_the_default_guard_by_default() { $this->assertEquals( @@ -270,6 +294,7 @@ public function it_belongs_to_the_default_guard_by_default() } /** @test */ + #[Test] public function it_can_change_role_class_on_runtime() { $role = app(Role::class)->create(['name' => 'test-role-old']); diff --git a/tests/RoleWithNestingTest.php b/tests/RoleWithNestingTest.php index fb28b8c18..5ddf3cac0 100644 --- a/tests/RoleWithNestingTest.php +++ b/tests/RoleWithNestingTest.php @@ -2,6 +2,8 @@ namespace Spatie\Permission\Tests; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Test; use Spatie\Permission\Tests\TestModels\Role; class RoleWithNestingTest extends TestCase @@ -62,6 +64,8 @@ protected function setUpDatabase($app) /** @test * @dataProvider roles_list */ + #[DataProvider('roles_list')] + #[Test] public function it_returns_correct_withCount_of_nested_roles($role_group, $index, $relation, $expectedCount) { $role = $this->$role_group[$index]; diff --git a/tests/RouteTest.php b/tests/RouteTest.php index e4843f0cb..0533ea74c 100644 --- a/tests/RouteTest.php +++ b/tests/RouteTest.php @@ -2,9 +2,12 @@ namespace Spatie\Permission\Tests; +use PHPUnit\Framework\Attributes\Test; + class RouteTest extends TestCase { /** @test */ + #[Test] public function test_role_function() { $router = $this->getRouter(); @@ -17,6 +20,7 @@ public function test_role_function() } /** @test */ + #[Test] public function test_permission_function() { $router = $this->getRouter(); @@ -29,6 +33,7 @@ public function test_permission_function() } /** @test */ + #[Test] public function test_role_and_permission_function_together() { $router = $this->getRouter(); diff --git a/tests/TeamHasPermissionsTest.php b/tests/TeamHasPermissionsTest.php index 5c9826b15..1e3ab20af 100644 --- a/tests/TeamHasPermissionsTest.php +++ b/tests/TeamHasPermissionsTest.php @@ -2,6 +2,7 @@ namespace Spatie\Permission\Tests; +use PHPUnit\Framework\Attributes\Test; use Spatie\Permission\Tests\TestModels\User; class TeamHasPermissionsTest extends HasPermissionsTest @@ -10,6 +11,7 @@ class TeamHasPermissionsTest extends HasPermissionsTest protected $hasTeams = true; /** @test */ + #[Test] public function it_can_assign_same_and_different_permission_on_same_user_on_different_teams() { setPermissionsTeamId(1); @@ -38,6 +40,7 @@ public function it_can_assign_same_and_different_permission_on_same_user_on_diff } /** @test */ + #[Test] public function it_can_list_all_the_coupled_permissions_both_directly_and_via_roles_on_same_user_on_different_teams() { $this->testUserRole->givePermissionTo('edit-articles'); @@ -68,6 +71,7 @@ public function it_can_list_all_the_coupled_permissions_both_directly_and_via_ro } /** @test */ + #[Test] public function it_can_sync_or_remove_permission_without_detach_on_different_teams() { setPermissionsTeamId(1); @@ -99,6 +103,7 @@ public function it_can_sync_or_remove_permission_without_detach_on_different_tea } /** @test */ + #[Test] public function it_can_scope_users_on_different_teams() { $user1 = User::create(['email' => 'user1@test.com']); diff --git a/tests/TeamHasRolesTest.php b/tests/TeamHasRolesTest.php index 6ad51ec4e..928ed7419 100644 --- a/tests/TeamHasRolesTest.php +++ b/tests/TeamHasRolesTest.php @@ -2,6 +2,7 @@ namespace Spatie\Permission\Tests; +use PHPUnit\Framework\Attributes\Test; use Spatie\Permission\Contracts\Role; use Spatie\Permission\Tests\TestModels\User; @@ -11,6 +12,7 @@ class TeamHasRolesTest extends HasRolesTest protected $hasTeams = true; /** @test */ + #[Test] public function it_deletes_pivot_table_entries_when_deleting_models() { $user1 = User::create(['email' => 'user2@test.com']); @@ -37,6 +39,7 @@ public function it_deletes_pivot_table_entries_when_deleting_models() } /** @test */ + #[Test] public function it_can_assign_same_and_different_roles_on_same_user_different_teams() { app(Role::class)->create(['name' => 'testRole3']); // team_test_id = 1 by main class @@ -84,6 +87,7 @@ public function it_can_assign_same_and_different_roles_on_same_user_different_te } /** @test */ + #[Test] public function it_can_sync_or_remove_roles_without_detach_on_different_teams() { app(Role::class)->create(['name' => 'testRole3', 'team_test_id' => 2]); @@ -118,6 +122,7 @@ public function it_can_sync_or_remove_roles_without_detach_on_different_teams() } /** @test */ + #[Test] public function it_can_scope_users_on_different_teams() { User::all()->each(fn ($item) => $item->delete()); diff --git a/tests/TestCase.php b/tests/TestCase.php index 1b222ad01..3a4f1c829 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -23,10 +23,10 @@ abstract class TestCase extends Orchestra { - /** @var \Spatie\Permission\Tests\User */ + /** @var \Spatie\Permission\Tests\TestModels\User */ protected $testUser; - /** @var \Spatie\Permission\Tests\Admin */ + /** @var \Spatie\Permission\Tests\TestModels\Admin */ protected $testAdmin; /** @var \Spatie\Permission\Models\Role */ diff --git a/tests/TestModels/Client.php b/tests/TestModels/Client.php index 267c36122..a0170dd13 100644 --- a/tests/TestModels/Client.php +++ b/tests/TestModels/Client.php @@ -14,8 +14,6 @@ class Client extends BaseClient implements AuthorizableContract /** * Required to make clear that the client requires the api guard - * - * @var string */ - protected $guard_name = 'api'; + protected string $guard_name = 'api'; } diff --git a/tests/TestModels/Manager.php b/tests/TestModels/Manager.php index 3405d924e..c96fecd83 100644 --- a/tests/TestModels/Manager.php +++ b/tests/TestModels/Manager.php @@ -6,11 +6,11 @@ class Manager extends User { // this function is added here to support the unit tests verifying it works // When present, it takes precedence over the $guard_name property. - public function guardName() + public function guardName(): string { return 'jwt'; } // intentionally different property value for the sake of unit tests - protected $guard_name = 'web'; + protected string $guard_name = 'web'; } diff --git a/tests/TestModels/Permission.php b/tests/TestModels/Permission.php index 5751490af..38ae68240 100644 --- a/tests/TestModels/Permission.php +++ b/tests/TestModels/Permission.php @@ -3,6 +3,7 @@ namespace Spatie\Permission\Tests\TestModels; use Illuminate\Database\Eloquent\SoftDeletes; +use Illuminate\Support\Str; class Permission extends \Spatie\Permission\Models\Permission { @@ -18,19 +19,19 @@ class Permission extends \Spatie\Permission\Models\Permission protected static function boot() { parent::boot(); - static::creating(function ($model) { + static::creating(static function ($model) { if (empty($model->{$model->getKeyName()})) { - $model->{$model->getKeyName()} = \Str::uuid()->toString(); + $model->{$model->getKeyName()} = Str::uuid()->toString(); } }); } - public function getIncrementing() + public function getIncrementing(): bool { return false; } - public function getKeyType() + public function getKeyType(): string { return 'string'; } diff --git a/tests/TestModels/Role.php b/tests/TestModels/Role.php index 5bd2c55e0..a5d4a2702 100644 --- a/tests/TestModels/Role.php +++ b/tests/TestModels/Role.php @@ -2,7 +2,9 @@ namespace Spatie\Permission\Tests\TestModels; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\SoftDeletes; +use Illuminate\Support\Str; class Role extends \Spatie\Permission\Models\Role { @@ -17,10 +19,7 @@ class Role extends \Spatie\Permission\Models\Role const HIERARCHY_TABLE = 'roles_hierarchy'; - /** - * @return string|\BackedEnum - */ - public function getNameAttribute() + public function getNameAttribute(): \BackedEnum|string { $name = $this->attributes['name']; @@ -31,10 +30,7 @@ public function getNameAttribute() return $name; } - /** - * @return BelongsToMany - */ - public function parents() + public function parents(): BelongsToMany { return $this->belongsToMany( static::class, @@ -43,10 +39,7 @@ public function parents() 'parent_id'); } - /** - * @return BelongsToMany - */ - public function children() + public function children(): BelongsToMany { return $this->belongsToMany( static::class, @@ -58,19 +51,19 @@ public function children() protected static function boot() { parent::boot(); - static::creating(function ($model) { + static::creating(static function ($model) { if (empty($model->{$model->getKeyName()})) { - $model->{$model->getKeyName()} = \Str::uuid()->toString(); + $model->{$model->getKeyName()} = Str::uuid()->toString(); } }); } - public function getIncrementing() + public function getIncrementing(): bool { return false; } - public function getKeyType() + public function getKeyType(): string { return 'string'; } diff --git a/tests/TestModels/SoftDeletingUser.php b/tests/TestModels/SoftDeletingUser.php index e31278519..a6efba029 100644 --- a/tests/TestModels/SoftDeletingUser.php +++ b/tests/TestModels/SoftDeletingUser.php @@ -8,5 +8,5 @@ class SoftDeletingUser extends User { use SoftDeletes; - protected $guard_name = 'web'; + protected string $guard_name = 'web'; } diff --git a/tests/TestModels/TestRolePermissionsEnum.php b/tests/TestModels/TestRolePermissionsEnum.php index 0f2badd42..7b3ba7485 100644 --- a/tests/TestModels/TestRolePermissionsEnum.php +++ b/tests/TestModels/TestRolePermissionsEnum.php @@ -2,6 +2,8 @@ namespace Spatie\Permission\Tests\TestModels; +use Illuminate\Support\Str; + /** * Enum example * @@ -53,6 +55,8 @@ public function label(): string self::VIEWARTICLES => 'View Articles', self::EDITARTICLES => 'Edit Articles', + + default => Str::words($this->name) }; } } diff --git a/tests/TestModels/WildcardPermission.php b/tests/TestModels/WildcardPermission.php index a6b7a6f10..6be905a24 100644 --- a/tests/TestModels/WildcardPermission.php +++ b/tests/TestModels/WildcardPermission.php @@ -9,9 +9,9 @@ class WildcardPermission extends BaseWildcardPermission /** @var string */ public const WILDCARD_TOKEN = '@'; - /** @var string */ + /** @var non-empty-string */ public const PART_DELIMITER = ':'; - /** @var string */ + /** @var non-empty-string */ public const SUBPART_DELIMITER = ';'; } diff --git a/tests/WildcardHasPermissionsTest.php b/tests/WildcardHasPermissionsTest.php index 8db36c63b..1abbc9e2a 100644 --- a/tests/WildcardHasPermissionsTest.php +++ b/tests/WildcardHasPermissionsTest.php @@ -2,6 +2,8 @@ namespace Spatie\Permission\Tests; +use PHPUnit\Framework\Attributes\RequiresPhp; +use PHPUnit\Framework\Attributes\Test; use Spatie\Permission\Exceptions\PermissionDoesNotExist; use Spatie\Permission\Exceptions\WildcardPermissionInvalidArgument; use Spatie\Permission\Exceptions\WildcardPermissionNotProperlyFormatted; @@ -12,6 +14,7 @@ class WildcardHasPermissionsTest extends TestCase { /** @test */ + #[Test] public function it_can_check_wildcard_permission() { app('config')->set('permission.enable_wildcard_permission', true); @@ -32,6 +35,7 @@ public function it_can_check_wildcard_permission() } /** @test */ + #[Test] public function it_can_check_wildcard_permission_for_a_non_default_guard() { app('config')->set('permission.enable_wildcard_permission', true); @@ -52,6 +56,7 @@ public function it_can_check_wildcard_permission_for_a_non_default_guard() } /** @test */ + #[Test] public function it_can_check_wildcard_permission_from_instance_without_explicit_guard_argument() { app('config')->set('permission.enable_wildcard_permission', true); @@ -77,6 +82,8 @@ public function it_can_check_wildcard_permission_from_instance_without_explicit_ * * @requires PHP >= 8.1 */ + #[RequiresPhp('>= 8.1')] + #[Test] public function it_can_assign_wildcard_permissions_using_enums() { app('config')->set('permission.enable_wildcard_permission', true); @@ -114,6 +121,7 @@ public function it_can_assign_wildcard_permissions_using_enums() } /** @test */ + #[Test] public function it_can_check_wildcard_permissions_via_roles() { app('config')->set('permission.enable_wildcard_permission', true); @@ -137,6 +145,7 @@ public function it_can_check_wildcard_permissions_via_roles() } /** @test */ + #[Test] public function it_can_check_custom_wildcard_permission() { app('config')->set('permission.enable_wildcard_permission', true); @@ -160,6 +169,7 @@ public function it_can_check_custom_wildcard_permission() } /** @test */ + #[Test] public function it_can_check_custom_wildcard_permissions_via_roles() { app('config')->set('permission.enable_wildcard_permission', true); @@ -186,6 +196,7 @@ public function it_can_check_custom_wildcard_permissions_via_roles() } /** @test */ + #[Test] public function it_can_check_non_wildcard_permissions() { app('config')->set('permission.enable_wildcard_permission', true); @@ -204,6 +215,7 @@ public function it_can_check_non_wildcard_permissions() } /** @test */ + #[Test] public function it_can_verify_complex_wildcard_permissions() { app('config')->set('permission.enable_wildcard_permission', true); @@ -224,6 +236,7 @@ public function it_can_verify_complex_wildcard_permissions() } /** @test */ + #[Test] public function it_throws_exception_when_wildcard_permission_is_not_properly_formatted() { app('config')->set('permission.enable_wildcard_permission', true); @@ -240,6 +253,7 @@ public function it_throws_exception_when_wildcard_permission_is_not_properly_for } /** @test */ + #[Test] public function it_can_verify_permission_instances_not_assigned_to_user() { app('config')->set('permission.enable_wildcard_permission', true); @@ -258,6 +272,7 @@ public function it_can_verify_permission_instances_not_assigned_to_user() } /** @test */ + #[Test] public function it_can_verify_permission_instances_assigned_to_user() { app('config')->set('permission.enable_wildcard_permission', true); @@ -276,6 +291,7 @@ public function it_can_verify_permission_instances_assigned_to_user() } /** @test */ + #[Test] public function it_can_verify_integers_as_strings() { app('config')->set('permission.enable_wildcard_permission', true); @@ -290,6 +306,7 @@ public function it_can_verify_integers_as_strings() } /** @test */ + #[Test] public function it_throws_exception_when_permission_has_invalid_arguments() { app('config')->set('permission.enable_wildcard_permission', true); @@ -302,6 +319,7 @@ public function it_throws_exception_when_permission_has_invalid_arguments() } /** @test */ + #[Test] public function it_throws_exception_when_permission_id_not_exists() { app('config')->set('permission.enable_wildcard_permission', true); diff --git a/tests/WildcardMiddlewareTest.php b/tests/WildcardMiddlewareTest.php index 596913707..9bb06873b 100644 --- a/tests/WildcardMiddlewareTest.php +++ b/tests/WildcardMiddlewareTest.php @@ -5,6 +5,7 @@ use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Support\Facades\Auth; +use PHPUnit\Framework\Attributes\Test; use Spatie\Permission\Exceptions\UnauthorizedException; use Spatie\Permission\Middleware\PermissionMiddleware; use Spatie\Permission\Middleware\RoleMiddleware; @@ -33,6 +34,7 @@ protected function setUp(): void } /** @test */ + #[Test] public function a_guest_cannot_access_a_route_protected_by_the_permission_middleware() { $this->assertEquals( @@ -42,6 +44,7 @@ public function a_guest_cannot_access_a_route_protected_by_the_permission_middle } /** @test */ + #[Test] public function a_user_can_access_a_route_protected_by_permission_middleware_if_have_this_permission() { Auth::login($this->testUser); @@ -57,6 +60,7 @@ public function a_user_can_access_a_route_protected_by_permission_middleware_if_ } /** @test */ + #[Test] public function a_user_can_access_a_route_protected_by_this_permission_middleware_if_have_one_of_the_permissions() { Auth::login($this->testUser); @@ -77,6 +81,7 @@ public function a_user_can_access_a_route_protected_by_this_permission_middlewar } /** @test */ + #[Test] public function a_user_cannot_access_a_route_protected_by_the_permission_middleware_if_have_a_different_permission() { Auth::login($this->testUser); @@ -92,6 +97,7 @@ public function a_user_cannot_access_a_route_protected_by_the_permission_middlew } /** @test */ + #[Test] public function a_user_cannot_access_a_route_protected_by_permission_middleware_if_have_not_permissions() { Auth::login($this->testUser); @@ -103,6 +109,7 @@ public function a_user_cannot_access_a_route_protected_by_permission_middleware_ } /** @test */ + #[Test] public function a_user_can_access_a_route_protected_by_permission_or_role_middleware_if_has_this_permission_or_role() { Auth::login($this->testUser); @@ -139,6 +146,7 @@ public function a_user_can_access_a_route_protected_by_permission_or_role_middle } /** @test */ + #[Test] public function the_required_permissions_can_be_fetched_from_the_exception() { Auth::login($this->testUser); diff --git a/tests/WildcardRoleTest.php b/tests/WildcardRoleTest.php index da674c6f6..d0e9212f5 100644 --- a/tests/WildcardRoleTest.php +++ b/tests/WildcardRoleTest.php @@ -2,6 +2,7 @@ namespace Spatie\Permission\Tests; +use PHPUnit\Framework\Attributes\Test; use Spatie\Permission\Models\Permission; class WildcardRoleTest extends TestCase @@ -18,6 +19,7 @@ protected function setUp(): void } /** @test */ + #[Test] public function it_can_be_given_a_permission() { Permission::create(['name' => 'posts.*']); @@ -27,6 +29,7 @@ public function it_can_be_given_a_permission() } /** @test */ + #[Test] public function it_can_be_given_multiple_permissions_using_an_array() { Permission::create(['name' => 'posts.*']); @@ -39,6 +42,7 @@ public function it_can_be_given_multiple_permissions_using_an_array() } /** @test */ + #[Test] public function it_can_be_given_multiple_permissions_using_multiple_arguments() { Permission::create(['name' => 'posts.*']); @@ -51,6 +55,7 @@ public function it_can_be_given_multiple_permissions_using_multiple_arguments() } /** @test */ + #[Test] public function it_can_be_given_a_permission_using_objects() { $this->testUserRole->givePermissionTo($this->testUserPermission); @@ -59,18 +64,21 @@ public function it_can_be_given_a_permission_using_objects() } /** @test */ + #[Test] public function it_returns_false_if_it_does_not_have_the_permission() { $this->assertFalse($this->testUserRole->hasPermissionTo('other-permission')); } /** @test */ + #[Test] public function it_returns_false_if_permission_does_not_exists() { $this->assertFalse($this->testUserRole->hasPermissionTo('doesnt-exist')); } /** @test */ + #[Test] public function it_returns_false_if_it_does_not_have_a_permission_object() { $permission = app(Permission::class)->findByName('other-permission'); @@ -79,6 +87,7 @@ public function it_returns_false_if_it_does_not_have_a_permission_object() } /** @test */ + #[Test] public function it_creates_permission_object_with_findOrCreate_if_it_does_not_have_a_permission_object() { $permission = app(Permission::class)->findOrCreate('another-permission'); @@ -93,6 +102,7 @@ public function it_creates_permission_object_with_findOrCreate_if_it_does_not_ha } /** @test */ + #[Test] public function it_returns_false_when_a_permission_of_the_wrong_guard_is_passed_in() { $permission = app(Permission::class)->findByName('wrong-guard-permission', 'admin'); diff --git a/tests/WildcardRouteTest.php b/tests/WildcardRouteTest.php index f56dfcb6c..65e3c437c 100644 --- a/tests/WildcardRouteTest.php +++ b/tests/WildcardRouteTest.php @@ -2,9 +2,12 @@ namespace Spatie\Permission\Tests; +use PHPUnit\Framework\Attributes\Test; + class WildcardRouteTest extends TestCase { /** @test */ + #[Test] public function test_permission_function() { app('config')->set('permission.enable_wildcard_permission', true); @@ -19,6 +22,7 @@ public function test_permission_function() } /** @test */ + #[Test] public function test_role_and_permission_function_together() { app('config')->set('permission.enable_wildcard_permission', true); From 6c7b1de8027cee9efdc0fb1f8b50ef9b04ae9300 Mon Sep 17 00:00:00 2001 From: Jason/CrossPlatform <68124218+crossplatformconsulting@users.noreply.github.com> Date: Thu, 13 Feb 2025 21:56:53 +0200 Subject: [PATCH 571/648] LDAP model lookup from Auth Provider (#2750) --------- Co-authored-by: Chris Brown --- src/Guard.php | 45 ++++++++++++++++++++++++++++++++++++++++++++- src/helpers.php | 7 +++---- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/src/Guard.php b/src/Guard.php index 4f697ce20..55c5d68a3 100644 --- a/src/Guard.php +++ b/src/Guard.php @@ -38,6 +38,25 @@ public static function getNames($model): Collection return self::getConfigAuthGuards($class); } + /** + * Get the model class associated with a given provider. + * + * @param string $provider + * @return string|null + */ + protected static function getProviderModel(string $provider): ?string + { + // Get the provider configuration + $providerConfig = config("auth.providers.{$provider}"); + + // Handle LDAP provider or standard Eloquent provider + if (isset($providerConfig['driver']) && $providerConfig['driver'] === 'ldap') { + return $providerConfig['database']['model'] ?? null; + } + + return $providerConfig['model'] ?? null; + } + /** * Get list of relevant guards for the $class model based on config(auth) settings. * @@ -50,11 +69,35 @@ public static function getNames($model): Collection protected static function getConfigAuthGuards(string $class): Collection { return collect(config('auth.guards')) - ->map(fn ($guard) => isset($guard['provider']) ? config("auth.providers.{$guard['provider']}.model") : null) + ->map(function ($guard) { + if (!isset($guard['provider'])) { + return null; + } + + return static::getProviderModel($guard['provider']); + }) ->filter(fn ($model) => $class === $model) ->keys(); } + /** + * Get the model associated with a given guard name. + * + * @param string $guard + * @return string|null + */ + public static function getModelForGuard(string $guard): ?string + { + // Get the provider configuration for the given guard + $provider = config("auth.guards.{$guard}.provider"); + + if (!$provider) { + return null; + } + + return static::getProviderModel($provider); + } + /** * Lookup a guard name relevant for the $class model and the current user. * diff --git a/src/helpers.php b/src/helpers.php index 55048d753..cf7d38873 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -4,12 +4,11 @@ /** * @return string|null */ - function getModelForGuard(string $guard) + function getModelForGuard(string $guard): ?string { - return collect(config('auth.guards')) - ->map(fn ($guard) => isset($guard['provider']) ? config("auth.providers.{$guard['provider']}.model") : null) - ->get($guard); + return Spatie\Permission\Guard::getModelForGuard($guard); } + } if (! function_exists('setPermissionsTeamId')) { From addf63f14c037afc16298724f2fa69bc010cd00d Mon Sep 17 00:00:00 2001 From: drbyte <404472+drbyte@users.noreply.github.com> Date: Thu, 13 Feb 2025 19:57:18 +0000 Subject: [PATCH 572/648] Fix styling --- src/Guard.php | 10 ++-------- src/helpers.php | 3 --- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/src/Guard.php b/src/Guard.php index 55c5d68a3..3e157be63 100644 --- a/src/Guard.php +++ b/src/Guard.php @@ -40,9 +40,6 @@ public static function getNames($model): Collection /** * Get the model class associated with a given provider. - * - * @param string $provider - * @return string|null */ protected static function getProviderModel(string $provider): ?string { @@ -70,7 +67,7 @@ protected static function getConfigAuthGuards(string $class): Collection { return collect(config('auth.guards')) ->map(function ($guard) { - if (!isset($guard['provider'])) { + if (! isset($guard['provider'])) { return null; } @@ -82,16 +79,13 @@ protected static function getConfigAuthGuards(string $class): Collection /** * Get the model associated with a given guard name. - * - * @param string $guard - * @return string|null */ public static function getModelForGuard(string $guard): ?string { // Get the provider configuration for the given guard $provider = config("auth.guards.{$guard}.provider"); - if (!$provider) { + if (! $provider) { return null; } diff --git a/src/helpers.php b/src/helpers.php index cf7d38873..e7d0e18a4 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -1,9 +1,6 @@ Date: Thu, 13 Feb 2025 20:01:33 +0000 Subject: [PATCH 573/648] Update CHANGELOG --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21c9539df..b44056294 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ All notable changes to `laravel-permission` will be documented in this file +## 6.14.0 - 2025-02-13 + +### What's Changed + +* LDAP model lookup from Auth Provider by @crossplatformconsulting in https://github.com/spatie/laravel-permission/pull/2750 + +### Internals + +* Add PHPUnit annotations, for future compatibility with PHPUnit 12 by @drbyte in https://github.com/spatie/laravel-permission/pull/2806 + +### New Contributors + +* @crossplatformconsulting made their first contribution in https://github.com/spatie/laravel-permission/pull/2750 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/6.13.0...6.14.0 + ## 6.13.0 - 2025-02-05 ### What's Changed @@ -947,6 +963,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` @@ -1021,6 +1038,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` From d1eab370413b522819315c9883b76b6e004d2eb4 Mon Sep 17 00:00:00 2001 From: Sven Wegner <139229112+sven-wegner@users.noreply.github.com> Date: Sun, 16 Feb 2025 18:56:49 +0100 Subject: [PATCH 574/648] 4 events for adding and removing roles or permissions (#2742) * Added Events `PermissionAttached`, `PermissionDetached`, `RoleAttached` and `RoleDetached` --------- Co-authored-by: Sven Wegner Co-authored-by: Chris Brown --- config/permission.php | 11 ++++++ src/Events/PermissionAttached.php | 28 ++++++++++++++ src/Events/PermissionDetached.php | 28 ++++++++++++++ src/Events/RoleAttached.php | 28 ++++++++++++++ src/Events/RoleDetached.php | 28 ++++++++++++++ src/Traits/HasPermissions.php | 14 ++++++- src/Traits/HasRoles.php | 14 ++++++- tests/HasPermissionsTest.php | 62 +++++++++++++++++++++++++++++++ tests/HasRolesTest.php | 42 +++++++++++++++++++++ 9 files changed, 253 insertions(+), 2 deletions(-) create mode 100644 src/Events/PermissionAttached.php create mode 100644 src/Events/PermissionDetached.php create mode 100644 src/Events/RoleAttached.php create mode 100644 src/Events/RoleDetached.php diff --git a/config/permission.php b/config/permission.php index c3b69a5ec..8e84e9d53 100644 --- a/config/permission.php +++ b/config/permission.php @@ -110,6 +110,17 @@ */ 'register_octane_reset_listener' => false, + /* + * Events will fire when a role or permission is assigned/unassigned: + * \Spatie\Permission\Events\RoleAttached + * \Spatie\Permission\Events\RoleDetached + * \Spatie\Permission\Events\PermissionAttached + * \Spatie\Permission\Events\PermissionDetached + * + * To enable, set to true, and then create listeners to watch these events. + */ + 'events_enabled' => false, + /* * Teams Feature. * When set to true the package implements teams using the 'team_foreign_key'. diff --git a/src/Events/PermissionAttached.php b/src/Events/PermissionAttached.php new file mode 100644 index 000000000..c7734f382 --- /dev/null +++ b/src/Events/PermissionAttached.php @@ -0,0 +1,28 @@ +forgetCachedPermissions(); } + if (config('permission.events_enabled')) { + event(new PermissionAttached($this->getModel(), $permissions)); + } + $this->forgetWildcardPermissionIndex(); return $this; @@ -460,12 +466,18 @@ public function syncPermissions(...$permissions) */ public function revokePermissionTo($permission) { - $this->permissions()->detach($this->getStoredPermission($permission)); + $storedPermission = $this->getStoredPermission($permission); + + $this->permissions()->detach($storedPermission); if (is_a($this, Role::class)) { $this->forgetCachedPermissions(); } + if (config('permission.events_enabled')) { + event(new PermissionDetached($this->getModel(), $storedPermission)); + } + $this->forgetWildcardPermissionIndex(); $this->unsetRelation('permissions'); diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index 409f1636c..149303a19 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -8,6 +8,8 @@ use Illuminate\Support\Collection; use Spatie\Permission\Contracts\Permission; use Spatie\Permission\Contracts\Role; +use Spatie\Permission\Events\RoleAttached; +use Spatie\Permission\Events\RoleDetached; use Spatie\Permission\PermissionRegistrar; trait HasRoles @@ -176,6 +178,10 @@ function ($object) use ($roles, $model, $teamPivot, &$saved) { $this->forgetCachedPermissions(); } + if (config('permission.events_enabled')) { + event(new RoleAttached($this->getModel(), $roles)); + } + return $this; } @@ -186,7 +192,9 @@ function ($object) use ($roles, $model, $teamPivot, &$saved) { */ public function removeRole($role) { - $this->roles()->detach($this->getStoredRole($role)); + $storedRole = $this->getStoredRole($role); + + $this->roles()->detach($storedRole); $this->unsetRelation('roles'); @@ -194,6 +202,10 @@ public function removeRole($role) $this->forgetCachedPermissions(); } + if (config('permission.events_enabled')) { + event(new RoleDetached($this->getModel(), $storedRole)); + } + return $this; } diff --git a/tests/HasPermissionsTest.php b/tests/HasPermissionsTest.php index 0e2de3aa7..d4242a553 100644 --- a/tests/HasPermissionsTest.php +++ b/tests/HasPermissionsTest.php @@ -3,11 +3,15 @@ namespace Spatie\Permission\Tests; use DB; +use Illuminate\Support\Facades\Event; use Illuminate\Database\Eloquent\Model; use PHPUnit\Framework\Attributes\RequiresPhp; use PHPUnit\Framework\Attributes\Test; use Spatie\Permission\Contracts\Permission; use Spatie\Permission\Contracts\Role; +use Spatie\Permission\Events\PermissionAttached; +use Spatie\Permission\Events\PermissionDetached; +use Spatie\Permission\Events\RoleAttached; use Spatie\Permission\Exceptions\GuardDoesNotMatch; use Spatie\Permission\Exceptions\PermissionDoesNotExist; use Spatie\Permission\Tests\TestModels\SoftDeletingUser; @@ -824,6 +828,64 @@ public function it_can_reject_permission_based_on_logged_in_user_guard() ]); } + /** @test */ + #[Test] + public function it_fires_an_event_when_a_permission_is_added() + { + Event::fake(); + app('config')->set('permission.events_enabled', true); + + $this->testUser->givePermissionTo(['edit-articles', 'edit-news']); + + $ids = app(Permission::class)::whereIn('name', ['edit-articles', 'edit-news']) + ->pluck($this->testUserPermission->getKeyName()) + ->toArray(); + + Event::assertDispatched(PermissionAttached::class, function ($event) use ($ids) { + return $event->model instanceof User + && $event->model->hasPermissionTo('edit-news') + && $event->model->hasPermissionTo('edit-articles') + && $ids === $event->permissionsOrIds; + }); + } + + /** @test */ + #[Test] + public function it_does_not_fire_an_event_when_events_are_not_enabled() + { + Event::fake(); + app('config')->set('permission.events_enabled', false); + + $this->testUser->givePermissionTo(['edit-articles', 'edit-news']); + + $ids = app(Permission::class)::whereIn('name', ['edit-articles', 'edit-news']) + ->pluck($this->testUserPermission->getKeyName()) + ->toArray(); + + Event::assertNotDispatched(PermissionAttached::class); + } + + /** @test */ + #[Test] + public function it_fires_an_event_when_a_permission_is_removed() + { + Event::fake(); + app('config')->set('permission.events_enabled', true); + + $permissions = app(Permission::class)::whereIn('name', ['edit-articles', 'edit-news'])->get(); + + $this->testUser->givePermissionTo($permissions); + + $this->testUser->revokePermissionTo($permissions); + + Event::assertDispatched(PermissionDetached::class, function ($event) use ($permissions) { + return $event->model instanceof User + && !$event->model->hasPermissionTo('edit-news') + && !$event->model->hasPermissionTo('edit-articles') + && $event->permissionsOrIds === $permissions; + }); + } + /** @test */ #[Test] public function it_can_be_given_a_permission_on_role_when_lazy_loading_is_restricted() diff --git a/tests/HasRolesTest.php b/tests/HasRolesTest.php index 5777f7bdb..2401b8dd7 100644 --- a/tests/HasRolesTest.php +++ b/tests/HasRolesTest.php @@ -4,10 +4,13 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Event; use PHPUnit\Framework\Attributes\RequiresPhp; use PHPUnit\Framework\Attributes\Test; use Spatie\Permission\Contracts\Permission; use Spatie\Permission\Contracts\Role; +use Spatie\Permission\Events\RoleAttached; +use Spatie\Permission\Events\RoleDetached; use Spatie\Permission\Exceptions\GuardDoesNotMatch; use Spatie\Permission\Exceptions\RoleDoesNotExist; use Spatie\Permission\Tests\TestModels\Admin; @@ -917,6 +920,45 @@ public function it_does_not_detach_roles_when_user_soft_deleting() $this->assertTrue($user->hasRole('testRole')); } + /** @test */ + #[Test] + public function it_fires_an_event_when_a_role_is_added() + { + Event::fake(); + app('config')->set('permission.events_enabled', true); + + $this->testUser->assignRole(['testRole', 'testRole2']); + + $roleIds = app(Role::class)::whereIn('name', ['testRole', 'testRole2']) + ->pluck($this->testUserRole->getKeyName()) + ->toArray(); + + Event::assertDispatched(RoleAttached::class, function ($event) use ($roleIds) { + return $event->model instanceof User + && $event->model->hasRole('testRole') + && $event->model->hasRole('testRole2') + && $event->rolesOrIds === $roleIds; + }); + } + + /** @test */ + #[Test] + public function it_fires_an_event_when_a_role_is_removed() + { + Event::fake(); + app('config')->set('permission.events_enabled', true); + + $this->testUser->assignRole('testRole'); + + $this->testUser->removeRole('testRole'); + + Event::assertDispatched(RoleDetached::class, function ($event) { + return $event->model instanceof User + && !$event->model->hasRole('testRole') + && $event->rolesOrIds->name === 'testRole'; + }); + } + /** @test */ #[Test] public function it_can_be_given_a_role_on_permission_when_lazy_loading_is_restricted() From 062e08bfaf2b62a81209c489352bc8bfceb3b919 Mon Sep 17 00:00:00 2001 From: drbyte <404472+drbyte@users.noreply.github.com> Date: Sun, 16 Feb 2025 17:57:09 +0000 Subject: [PATCH 575/648] Fix styling --- tests/HasPermissionsTest.php | 15 +++++++-------- tests/HasRolesTest.php | 8 ++++---- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/tests/HasPermissionsTest.php b/tests/HasPermissionsTest.php index d4242a553..77e909ca8 100644 --- a/tests/HasPermissionsTest.php +++ b/tests/HasPermissionsTest.php @@ -3,15 +3,14 @@ namespace Spatie\Permission\Tests; use DB; -use Illuminate\Support\Facades\Event; use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Facades\Event; use PHPUnit\Framework\Attributes\RequiresPhp; use PHPUnit\Framework\Attributes\Test; use Spatie\Permission\Contracts\Permission; use Spatie\Permission\Contracts\Role; use Spatie\Permission\Events\PermissionAttached; use Spatie\Permission\Events\PermissionDetached; -use Spatie\Permission\Events\RoleAttached; use Spatie\Permission\Exceptions\GuardDoesNotMatch; use Spatie\Permission\Exceptions\PermissionDoesNotExist; use Spatie\Permission\Tests\TestModels\SoftDeletingUser; @@ -834,7 +833,7 @@ public function it_fires_an_event_when_a_permission_is_added() { Event::fake(); app('config')->set('permission.events_enabled', true); - + $this->testUser->givePermissionTo(['edit-articles', 'edit-news']); $ids = app(Permission::class)::whereIn('name', ['edit-articles', 'edit-news']) @@ -855,7 +854,7 @@ public function it_does_not_fire_an_event_when_events_are_not_enabled() { Event::fake(); app('config')->set('permission.events_enabled', false); - + $this->testUser->givePermissionTo(['edit-articles', 'edit-news']); $ids = app(Permission::class)::whereIn('name', ['edit-articles', 'edit-news']) @@ -871,7 +870,7 @@ public function it_fires_an_event_when_a_permission_is_removed() { Event::fake(); app('config')->set('permission.events_enabled', true); - + $permissions = app(Permission::class)::whereIn('name', ['edit-articles', 'edit-news'])->get(); $this->testUser->givePermissionTo($permissions); @@ -880,12 +879,12 @@ public function it_fires_an_event_when_a_permission_is_removed() Event::assertDispatched(PermissionDetached::class, function ($event) use ($permissions) { return $event->model instanceof User - && !$event->model->hasPermissionTo('edit-news') - && !$event->model->hasPermissionTo('edit-articles') + && ! $event->model->hasPermissionTo('edit-news') + && ! $event->model->hasPermissionTo('edit-articles') && $event->permissionsOrIds === $permissions; }); } - + /** @test */ #[Test] public function it_can_be_given_a_permission_on_role_when_lazy_loading_is_restricted() diff --git a/tests/HasRolesTest.php b/tests/HasRolesTest.php index 2401b8dd7..31356e8fc 100644 --- a/tests/HasRolesTest.php +++ b/tests/HasRolesTest.php @@ -926,7 +926,7 @@ public function it_fires_an_event_when_a_role_is_added() { Event::fake(); app('config')->set('permission.events_enabled', true); - + $this->testUser->assignRole(['testRole', 'testRole2']); $roleIds = app(Role::class)::whereIn('name', ['testRole', 'testRole2']) @@ -947,18 +947,18 @@ public function it_fires_an_event_when_a_role_is_removed() { Event::fake(); app('config')->set('permission.events_enabled', true); - + $this->testUser->assignRole('testRole'); $this->testUser->removeRole('testRole'); Event::assertDispatched(RoleDetached::class, function ($event) { return $event->model instanceof User - && !$event->model->hasRole('testRole') + && ! $event->model->hasRole('testRole') && $event->rolesOrIds->name === 'testRole'; }); } - + /** @test */ #[Test] public function it_can_be_given_a_role_on_permission_when_lazy_loading_is_restricted() From 18825c62216fbe4833f9bf4fd5726924f4f477da Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Sun, 16 Feb 2025 13:12:40 -0500 Subject: [PATCH 576/648] Set a query count var for incrementing in future tests; simplifies future refactoring --- tests/HasPermissionsTest.php | 4 +++- tests/HasRolesTest.php | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/HasPermissionsTest.php b/tests/HasPermissionsTest.php index 77e909ca8..49f450359 100644 --- a/tests/HasPermissionsTest.php +++ b/tests/HasPermissionsTest.php @@ -708,7 +708,9 @@ public function it_does_not_run_unnecessary_sqls_when_assigning_new_permissions( $this->testUser->syncPermissions($this->testUserPermission, $permission2); DB::disableQueryLog(); - $this->assertSame(2, count(DB::getQueryLog())); // avoid unnecessary sqls + $necessaryQueriesCount = 2; + + $this->assertCount($necessaryQueriesCount, DB::getQueryLog()); } /** @test */ diff --git a/tests/HasRolesTest.php b/tests/HasRolesTest.php index 31356e8fc..40a719343 100644 --- a/tests/HasRolesTest.php +++ b/tests/HasRolesTest.php @@ -393,7 +393,9 @@ public function it_does_not_run_unnecessary_sqls_when_assigning_new_roles() $this->testUser->syncRoles($this->testUserRole, $role2); DB::disableQueryLog(); - $this->assertSame(2, count(DB::getQueryLog())); // avoid unnecessary sqls + $necessaryQueriesCount = 2; + + $this->assertCount($necessaryQueriesCount, DB::getQueryLog()); } /** @test */ From 7dee857091ae65e98c51875ec89607f87ff03c68 Mon Sep 17 00:00:00 2001 From: Mohammed DS <92916932+mohamedds-12@users.noreply.github.com> Date: Sun, 16 Feb 2025 19:46:57 +0100 Subject: [PATCH 577/648] Fixed bug of loading user roles of different teams to current team (#2803) * Fixed calling $this->roles property which could load old roles * Added reloading roles before asigning role if teams feature is enabled --------- Co-authored-by: Chris Brown --- src/Traits/HasRoles.php | 5 +++++ tests/HasRolesTest.php | 6 ++++++ tests/TeamHasRolesTest.php | 5 +++++ 3 files changed, 16 insertions(+) diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index 149303a19..d10dd77e3 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -154,6 +154,11 @@ public function assignRole(...$roles) [app(PermissionRegistrar::class)->teamsKey => getPermissionsTeamId()] : []; if ($model->exists) { + if (app(PermissionRegistrar::class)->teams) { + // explicit reload in case team has been changed since last load + $this->load('roles'); + } + $currentRoles = $this->roles->map(fn ($role) => $role->getKey())->toArray(); $this->roles()->attach(array_diff($roles, $currentRoles), $teamPivot); diff --git a/tests/HasRolesTest.php b/tests/HasRolesTest.php index 40a719343..696fe8525 100644 --- a/tests/HasRolesTest.php +++ b/tests/HasRolesTest.php @@ -13,6 +13,7 @@ use Spatie\Permission\Events\RoleDetached; use Spatie\Permission\Exceptions\GuardDoesNotMatch; use Spatie\Permission\Exceptions\RoleDoesNotExist; +use Spatie\Permission\PermissionRegistrar; use Spatie\Permission\Tests\TestModels\Admin; use Spatie\Permission\Tests\TestModels\SoftDeletingUser; use Spatie\Permission\Tests\TestModels\User; @@ -394,6 +395,11 @@ public function it_does_not_run_unnecessary_sqls_when_assigning_new_roles() DB::disableQueryLog(); $necessaryQueriesCount = 2; + + // Teams reloads relation, adding an extra query + if (app(PermissionRegistrar::class)->teams) { + $necessaryQueriesCount++; + } $this->assertCount($necessaryQueriesCount, DB::getQueryLog()); } diff --git a/tests/TeamHasRolesTest.php b/tests/TeamHasRolesTest.php index 928ed7419..9f76da49b 100644 --- a/tests/TeamHasRolesTest.php +++ b/tests/TeamHasRolesTest.php @@ -55,6 +55,11 @@ public function it_can_assign_same_and_different_roles_on_same_user_different_te setPermissionsTeamId(1); $this->testUser->assignRole('testRole', 'testRole2'); + // explicit load of roles to assert no mismatch + // when same role assigned in diff teams + // while old team's roles are loaded + $this->testUser->load('roles'); + setPermissionsTeamId(2); $this->testUser->assignRole('testRole', 'testRole3'); From fa5ea338fd5d88dd554dad662bebe91e234f06da Mon Sep 17 00:00:00 2001 From: drbyte <404472+drbyte@users.noreply.github.com> Date: Sun, 16 Feb 2025 18:47:21 +0000 Subject: [PATCH 578/648] Fix styling --- tests/HasRolesTest.php | 2 +- tests/TeamHasRolesTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/HasRolesTest.php b/tests/HasRolesTest.php index 696fe8525..7f8a137e0 100644 --- a/tests/HasRolesTest.php +++ b/tests/HasRolesTest.php @@ -395,7 +395,7 @@ public function it_does_not_run_unnecessary_sqls_when_assigning_new_roles() DB::disableQueryLog(); $necessaryQueriesCount = 2; - + // Teams reloads relation, adding an extra query if (app(PermissionRegistrar::class)->teams) { $necessaryQueriesCount++; diff --git a/tests/TeamHasRolesTest.php b/tests/TeamHasRolesTest.php index 9f76da49b..113bdc5aa 100644 --- a/tests/TeamHasRolesTest.php +++ b/tests/TeamHasRolesTest.php @@ -57,7 +57,7 @@ public function it_can_assign_same_and_different_roles_on_same_user_different_te // explicit load of roles to assert no mismatch // when same role assigned in diff teams - // while old team's roles are loaded + // while old team's roles are loaded $this->testUser->load('roles'); setPermissionsTeamId(2); From 86074fcfd127f9fa7bcdf550b7c938e3beb0d65f Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Mon, 17 Feb 2025 14:18:01 -0500 Subject: [PATCH 579/648] Updates to example app setup --- docs/basic-usage/new-app.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/basic-usage/new-app.md b/docs/basic-usage/new-app.md index 7f1ed8361..c08978344 100644 --- a/docs/basic-usage/new-app.md +++ b/docs/basic-usage/new-app.md @@ -19,8 +19,9 @@ laravel new mypermissionsdemo # (Choose Laravel Breeze, choose Blade with Alpine) # (choose your own dark-mode-support choice) # (choose your desired testing framework) -# (say Yes to initialize a Git repo, so that you can track your code changes) +# (If offered, say Yes to initialize a Git repo, so that you can track your code changes) # (Choose SQLite) +# (say Yes to run default database migrations) cd mypermissionsdemo @@ -52,7 +53,7 @@ If you didn't install Laravel Breeze or Jetstream, add Laravel's basic auth scaf ```php composer require laravel/ui --dev php artisan ui bootstrap --auth -# npm install && npm run prod +# npm install && npm run build git add . && git commit -m "Setup auth scaffold" ``` From c528d29aeff7fbf60b4d4f04d69ca73af35c363d Mon Sep 17 00:00:00 2001 From: drbyte <404472+drbyte@users.noreply.github.com> Date: Mon, 17 Feb 2025 19:20:28 +0000 Subject: [PATCH 580/648] Update CHANGELOG --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b44056294..a4a056de3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,20 @@ All notable changes to `laravel-permission` will be documented in this file +## 6.15.0 - 2025-02-17 + +### What's Changed + +* Added 4 events for adding and removing roles or permissions by @sven-wegner in https://github.com/spatie/laravel-permission/pull/2742 +* Fixed bug of loading user roles of different teams to current team by @mohamedds-12 in https://github.com/spatie/laravel-permission/pull/2803 + +### New Contributors + +* @sven-wegner made their first contribution in https://github.com/spatie/laravel-permission/pull/2742 +* @mohamedds-12 made their first contribution in https://github.com/spatie/laravel-permission/pull/2803 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/6.14.0...6.15.0 + ## 6.14.0 - 2025-02-13 ### What's Changed @@ -964,6 +978,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` @@ -1039,6 +1054,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` From 43a7ea2d25ba8c128f22da049b3f46532b7f9426 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Mon, 17 Feb 2025 14:29:29 -0500 Subject: [PATCH 581/648] [Docs] Create events.md --- docs/advanced-usage/events.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 docs/advanced-usage/events.md diff --git a/docs/advanced-usage/events.md b/docs/advanced-usage/events.md new file mode 100644 index 000000000..04682d1ac --- /dev/null +++ b/docs/advanced-usage/events.md @@ -0,0 +1,21 @@ +--- +title: Events +weight: 5 +--- + +By default Events are not enabled, because not all apps need to fire events related to roles and permissions. + +However, you may enable events by setting the `events_enabled => true` in `config/permission.php` + +## Available Events + +The following events are available since `v6.15.0`: + +``` +\Spatie\Permission\Events\RoleAttached::class +\Spatie\Permission\Events\RoleDetached::class +\Spatie\Permission\Events\PermissionAttached::class +\Spatie\Permission\Events\PermissionDetached::class +``` +Note that the events can receive the role or permission details as a model ID or as an Eloquent record, or as an array or collection of ids or records. Be sure to inspect the parameter before acting on it. + From de5893ec02325ba50f1f72e9275fc9c73ea5bdfe Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Thu, 20 Feb 2025 22:51:46 -0500 Subject: [PATCH 582/648] Bump PHP version --- .github/workflows/test-cache-drivers.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-cache-drivers.yml b/.github/workflows/test-cache-drivers.yml index 403f23db6..2bfd5b28a 100644 --- a/.github/workflows/test-cache-drivers.yml +++ b/.github/workflows/test-cache-drivers.yml @@ -26,7 +26,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: 8.2 + php-version: 8.4 extensions: curl, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, iconv, memcache coverage: none From f9b692dfd06b54b3e206f062f2e874c9783cd868 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Thu, 20 Feb 2025 22:53:20 -0500 Subject: [PATCH 583/648] Bump PHP version --- .github/workflows/phpstan.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index 75eb21d8d..be7882397 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -20,7 +20,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: 8.3 + php-version: 8.4 coverage: none - name: Install composer dependencies From 402cb42aba8a24fec11f6a989b5d815d09d365fc Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Mon, 24 Feb 2025 20:28:05 -0500 Subject: [PATCH 584/648] Update example app instruction --- docs/basic-usage/new-app.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/basic-usage/new-app.md b/docs/basic-usage/new-app.md index c08978344..04cf9d212 100644 --- a/docs/basic-usage/new-app.md +++ b/docs/basic-usage/new-app.md @@ -16,16 +16,17 @@ If you're new to Laravel or to any of the concepts mentioned here, you can learn ```sh cd ~/Sites laravel new mypermissionsdemo -# (Choose Laravel Breeze, choose Blade with Alpine) -# (choose your own dark-mode-support choice) -# (choose your desired testing framework) +# (No Starter Kit is needed, but you could go with Livewire or Breeze/Jetstream, with Laravel's Built-In-Auth; or use Bootstrap using laravel/ui described later, below) +# (You might be asked to select a dark-mode-support choice) +# (Choose your desired testing framework: Pest or PHPUnit) # (If offered, say Yes to initialize a Git repo, so that you can track your code changes) -# (Choose SQLite) -# (say Yes to run default database migrations) +# (If offered a database selection, choose SQLite, because it is simplest for test scenarios) +# (If prompted, say Yes to run default database migrations) +# (If prompted, say Yes to run npm install and related commands) cd mypermissionsdemo -# the following git commands are not needed if you Initialized a git repo while "laravel new" was running above: +# The following git commands are not needed if you Initialized a git repo while "laravel new" was running above: git init git add . git commit -m "Fresh Laravel Install" @@ -49,7 +50,8 @@ sed -i '' $'s/use HasApiTokens, HasFactory, Notifiable;/use HasApiTokens, HasFac git add . && git commit -m "Add HasRoles trait" ``` -If you didn't install Laravel Breeze or Jetstream, add Laravel's basic auth scaffolding: +If you didn't install a Starter Kit like Livewire or Breeze or Jetstream, add Laravel's basic auth scaffolding: +This Auth scaffolding will make it simpler to provide login capability for a test/demo user, and test roles/permissions with them. ```php composer require laravel/ui --dev php artisan ui bootstrap --auth From aa08de37c0373a9eb3807980c38b5bff20cfb170 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Wed, 26 Feb 2025 18:04:21 -0500 Subject: [PATCH 585/648] Relocate compatibility chart to Prerequisites page --- docs/installation-laravel.md | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/docs/installation-laravel.md b/docs/installation-laravel.md index c3a6646ce..951690478 100644 --- a/docs/installation-laravel.md +++ b/docs/installation-laravel.md @@ -5,18 +5,7 @@ weight: 4 ## Laravel Version Compatibility -Choose the version of this package that suits your Laravel version. - -Package Version | Laravel Version -----------------|----------- - ^6.0 | 8,9,10,11,12 (PHP 8.0+) - ^5.8 | 7,8,9,10 - ^5.7 | 7,8,9 - ^5.4-^5.6 | 7,8 - 5.0-5.3 | 6,7,8 - ^4 | 6,7,8 - ^3 | 5.8 - +See the "Prerequisites" documentation page for compatibility details. ## Installing From 835ac8b8bcc6a12e5722e2a82fc5dc688c64b5c8 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Wed, 26 Feb 2025 18:17:58 -0500 Subject: [PATCH 586/648] Relocate version compatibility to Prerequisites page --- docs/prerequisites.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/docs/prerequisites.md b/docs/prerequisites.md index 0b0cf300b..f566be624 100644 --- a/docs/prerequisites.md +++ b/docs/prerequisites.md @@ -3,10 +3,18 @@ title: Prerequisites weight: 3 --- -## Laravel Version - -This package can be used in Laravel 6 or higher. Check the "Installing on Laravel" page for package versions compatible with various Laravel versions. - +## Laravel Version Compatibility + +Laravel Version | Package Version +----------------|----------- + 8,9,10,11,12 | `^6.0` (PHP 8.0+) + 7,8,9,10 | `^5.8` + 7,8,9 | `^5.7` + 7,8 | `^5.4`-`^5.6` + 6,7,8 | `^5.0`-`^5.3` + 6,7,8 | `^4` + 5.8 | `^3` + ## User Model / Contract/Interface This package uses Laravel's Gate layer to provide Authorization capabilities. From 2c9ae7f76c9af581bcefcb81176d3255e2ebffaa Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Wed, 26 Feb 2025 18:26:33 -0500 Subject: [PATCH 587/648] Clarify relation name limitations --- docs/prerequisites.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/prerequisites.md b/docs/prerequisites.md index f566be624..c4be5cb88 100644 --- a/docs/prerequisites.md +++ b/docs/prerequisites.md @@ -37,23 +37,23 @@ class User extends Authenticatable } ``` -## Must not have a [role] or [roles] property, nor a [roles()] method +## Must not have a [role] or [roles] property/relation, nor a [roles()] method -Your `User` model/object MUST NOT have a `role` or `roles` property (or field in the database by that name), nor a `roles()` method on it. Those will interfere with the properties and methods added by the `HasRoles` trait provided by this package, thus causing unexpected outcomes when this package's methods are used to inspect roles and permissions. +Your `User` model/object MUST NOT have a `role` or `roles` property (or field in the database by that name), nor a `roles()` method on it (nor a `roles` relation). Those will interfere with the properties and methods and relations added by the `HasRoles` trait provided by this package, thus causing unexpected outcomes when this package's methods are used to inspect roles and permissions. -## Must not have a [permission] or [permissions] property, nor a [permissions()] method +## Must not have a [permission] or [permissions] property/relation, nor a [permissions()] method -Your `User` model/object MUST NOT have a `permission` or `permissions` property (or field in the database by that name), nor a `permissions()` method on it. Those will interfere with the properties and methods added by the `HasPermissions` trait provided by this package (which is invoked via the `HasRoles` trait). +Your `User` model/object MUST NOT have a `permission` or `permissions` property (or field in the database by that name), nor a `permissions()` method on it (nor a `permissions` relation). Those will interfere with the properties and methods and relations added by the `HasPermissions` trait provided by this package (which is invoked via the `HasRoles` trait). ## Config file This package publishes a `config/permission.php` file. If you already have a file by that name, you must rename or remove it, as it will conflict with this package. You could optionally merge your own values with those required by this package, as long as the keys that this package expects are present. See the source file for more details. -## Schema Limitation in MySQL +## Database Schema Limitations Potential error message: "1071 Specified key was too long; max key length is 1000 bytes" -MySQL 8.0 limits index key lengths, which might be too short for some compound indexes used by this package. +MySQL 8.0+ limits index key lengths, which might be too short for some compound indexes used by this package. This package publishes a migration which combines multiple columns in a single index. With `utf8mb4` the 4-bytes-per-character requirement of `mb4` means the total length of the columns in the hybrid index can only be `25%` of that maximum index length. - MyISAM tables limit the index to 1000 characters (which is only 250 total chars in `utf8mb4`) @@ -64,7 +64,7 @@ Depending on your MySQL or MariaDB configuration, you may implement one of the f 1. Ideally, configure the database to use InnoDB by default, and use ROW FORMAT of 'Dynamic' by default for all new tables. (See [MySQL](https://dev.mysql.com/doc/refman/8.0/en/innodb-limits.html) and [MariaDB](https://mariadb.com/kb/en/innodb-dynamic-row-format/) docs.) -2. OR if your app doesn't require a longer default, in your AppServiceProvider you can set `Schema::defaultStringLength(125)`. [See the Laravel Docs for instructions](https://laravel.com/docs/migrations#index-lengths-mysql-mariadb). This will have Laravel set all strings to 125 characters by default. +2. OR if your app doesn't require a longer default, in your AppServiceProvider you can set `Schema::defaultStringLength(125)`. [See the Laravel Docs for instructions](https://laravel.com/docs/10.x/migrations#index-lengths-mysql-mariadb). This will have Laravel set all strings to 125 characters by default. 3. OR you could edit the migration and specify a shorter length for 4 fields. Then in your app be sure to manually impose validation limits on any form fields related to these fields. There are 2 instances of this code snippet where you can explicitly set the length.: From 32eed884acf930f4bed86fbcb6a2c1637135448b Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Wed, 26 Feb 2025 18:31:35 -0500 Subject: [PATCH 588/648] Clarify optional service provider registration --- docs/installation-laravel.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/installation-laravel.md b/docs/installation-laravel.md index 951690478..06b430727 100644 --- a/docs/installation-laravel.md +++ b/docs/installation-laravel.md @@ -17,7 +17,8 @@ See the "Prerequisites" documentation page for compatibility details. composer require spatie/laravel-permission -4. Optional: The **`Spatie\Permission\PermissionServiceProvider::class`** service provider will automatically get registered. Or you may manually add the service provider to the array in your `bootstrap/providers.php` (or `config/app.php` in Laravel 10 or older) file. +4. The Service Provider will automatically be registered; however, if you wish to manually register it, you can manually add the `Spatie\Permission\PermissionServiceProvider::class` service provider to the array in `bootstrap/providers.php` (`config/app.php` in Laravel 10 or older). + 5. **You should publish** [the migration](https://github.com/spatie/laravel-permission/blob/main/database/migrations/create_permission_tables.php.stub) and the [`config/permission.php` config file](https://github.com/spatie/laravel-permission/blob/main/config/permission.php) with: @@ -33,7 +34,7 @@ See the "Prerequisites" documentation page for compatibility details. - must set `'teams' => true,` - and (optional) you may set `team_foreign_key` name in the config file if you want to use a custom foreign key in your database for teams - - **If you are using MySQL 8**, look at the migration files for notes about MySQL 8 to set/limit the index key length, and edit accordingly. If you get `ERROR: 1071 Specified key was too long` then you need to do this. + - **If you are using MySQL 8+**, look at the migration files for notes about MySQL 8+ to set/limit the index key length, and edit accordingly. If you get `ERROR: 1071 Specified key was too long` then you need to do this. - **If you are using CACHE_STORE=database**, be sure to [install Laravel's cache migration](https://laravel.com/docs/cache#prerequisites-database), else you will encounter cache errors. From 3718fdb618d5daf36d3f61e131e8fe76313413ad Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Wed, 26 Feb 2025 18:34:53 -0500 Subject: [PATCH 589/648] Lumen not supported --- docs/installation-lumen.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation-lumen.md b/docs/installation-lumen.md index 1cff50275..b5f079e75 100644 --- a/docs/installation-lumen.md +++ b/docs/installation-lumen.md @@ -7,7 +7,7 @@ NOTE: Lumen is **not** officially supported by this package. And Lumen is no lon However, the following are some steps which may help get you started. -Lumen installation instructions can be found in the [Lumen documentation](https://lumen.laravel.com/docs/master). +Lumen installation instructions can be found in the [Lumen documentation](https://lumen.laravel.com/docs). ## Installing From 5faededea5f46e582c40915c41e3dfb210582126 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Wed, 26 Feb 2025 18:40:00 -0500 Subject: [PATCH 590/648] Update link name --- docs/basic-usage/basic-usage.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/basic-usage/basic-usage.md b/docs/basic-usage/basic-usage.md index bd8f2acb0..76e41e040 100644 --- a/docs/basic-usage/basic-usage.md +++ b/docs/basic-usage/basic-usage.md @@ -55,7 +55,7 @@ $permission->removeRole($role); ``` ## Guard Name -If you're using multiple guards then the `guard_name` attribute must be set as well. Read about it in the [using multiple guards](./multiple-guards) section of the readme. +If you're using multiple guards then the `guard_name` attribute must be set as well. Read about it in the [using multiple guards](./multiple-guards) documentation. ## Get Permissions For A User The `HasRoles` trait adds Eloquent relationships to your models, which can be accessed directly or used as a base query: @@ -108,7 +108,7 @@ $allRolesExceptAandB = Role::whereNotIn('name', ['role A', 'role B'])->get(); ## Counting Users Having A Role One way to count all users who have a certain role is by filtering the collection of all Users with their Roles: ```php -$superAdminCount = User::with('roles')->get()->filter( - fn ($user) => $user->roles->where('name', 'Super Admin')->toArray() +$managersCount = User::with('roles')->get()->filter( + fn ($user) => $user->roles->where('name', 'Manager')->toArray() )->count(); ``` From 5e87096da2a0b86cc46dca4ea6aef224cecc585f Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Wed, 26 Feb 2025 18:43:22 -0500 Subject: [PATCH 591/648] Better to use roles for giving permissions as a group --- docs/basic-usage/direct-permissions.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/basic-usage/direct-permissions.md b/docs/basic-usage/direct-permissions.md index 944392b0f..e171c8163 100644 --- a/docs/basic-usage/direct-permissions.md +++ b/docs/basic-usage/direct-permissions.md @@ -5,11 +5,11 @@ weight: 2 ## Best Practice -It's better to assign permissions to Roles, and then assign Roles to Users. +INSTEAD OF DIRECT PERMISSIONS, it is better to assign permissions to Roles, and then assign Roles to Users. See the [Roles vs Permissions](../best-practices/roles-vs-permissions) section of the docs for a deeper explanation. -HOWEVER, If you have reason to directly assign individual permissions to specific users (instead of to roles assigned to those users), you can do that as described below: +HOWEVER, If you have reason to directly assign individual permissions to specific users (instead of to roles which are assigned to those users), you can do that as well: ## Direct Permissions to Users @@ -46,7 +46,7 @@ Like all permissions assigned via roles, you can check if a user has a permissio $user->can('edit articles'); ``` -NOTE: The following `hasPermissionTo`, `hasAnyPermission`, `hasAllPermissions` functions do not support Super-Admin functionality. Use `can`, `canAny`, `canAll` instead. +> NOTE: The following `hasPermissionTo`, `hasAnyPermission`, `hasAllPermissions` functions do not support Super-Admin functionality. Use `can`, `canAny`, `canAll` instead. You can check if a user has a permission: From 877e2c73cd939620eabc3972c65a7b9dcbc7ed56 Mon Sep 17 00:00:00 2001 From: Mark Lontoc Date: Sat, 1 Mar 2025 04:29:32 +0800 Subject: [PATCH 592/648] Middleware: support enums in role/permission middleware (#2813) * Allow middleware to handle enum based permission or role --------- Co-authored-by: Chris Brown --- src/Middleware/PermissionMiddleware.php | 35 ++++++++++++++--- src/Middleware/RoleMiddleware.php | 30 +++++++++++--- tests/PermissionMiddlewareTest.php | 51 ++++++++++++++++++++++++ tests/RoleMiddlewareTest.php | 52 +++++++++++++++++++++++++ 4 files changed, 158 insertions(+), 10 deletions(-) diff --git a/src/Middleware/PermissionMiddleware.php b/src/Middleware/PermissionMiddleware.php index 3e78f1d60..9a157611f 100644 --- a/src/Middleware/PermissionMiddleware.php +++ b/src/Middleware/PermissionMiddleware.php @@ -28,9 +28,7 @@ public function handle($request, Closure $next, $permission, $guard = null) throw UnauthorizedException::missingTraitHasRoles($user); } - $permissions = is_array($permission) - ? $permission - : explode('|', $permission); + $permissions = explode('|', self::parsePermissionsToString($permission)); if (! $user->canAny($permissions)) { throw UnauthorizedException::forPermissions($permissions); @@ -42,15 +40,42 @@ public function handle($request, Closure $next, $permission, $guard = null) /** * Specify the permission and guard for the middleware. * - * @param array|string $permission + * @param array|string|\BackedEnum $permission * @param string|null $guard * @return string */ public static function using($permission, $guard = null) { - $permissionString = is_string($permission) ? $permission : implode('|', $permission); + // Convert Enum to its value if an Enum is passed + if ($permission instanceof \BackedEnum) { + $permission = $permission->value; + } + + $permissionString = self::parsePermissionsToString($permission); + $args = is_null($guard) ? $permissionString : "$permissionString,$guard"; return static::class.':'.$args; } + + /** + * Convert array or string of permissions to string representation. + * + * @return string + */ + protected static function parsePermissionsToString(array|string|\BackedEnum $permission) + { + // Convert Enum to its value if an Enum is passed + if ($permission instanceof \BackedEnum) { + $permission = $permission->value; + } + + if (is_array($permission)) { + $permission = array_map(fn ($r) => $r instanceof \BackedEnum ? $r->value : $r, $permission); + + return implode('|', $permission); + } + + return (string) $permission; + } } diff --git a/src/Middleware/RoleMiddleware.php b/src/Middleware/RoleMiddleware.php index 010363f6e..edc02ff90 100644 --- a/src/Middleware/RoleMiddleware.php +++ b/src/Middleware/RoleMiddleware.php @@ -28,9 +28,7 @@ public function handle($request, Closure $next, $role, $guard = null) throw UnauthorizedException::missingTraitHasRoles($user); } - $roles = is_array($role) - ? $role - : explode('|', $role); + $roles = explode('|', self::parseRolesToString($role)); if (! $user->hasAnyRole($roles)) { throw UnauthorizedException::forRoles($roles); @@ -42,15 +40,37 @@ public function handle($request, Closure $next, $role, $guard = null) /** * Specify the role and guard for the middleware. * - * @param array|string $role + * @param array|string|\BackedEnum $role * @param string|null $guard * @return string */ public static function using($role, $guard = null) { - $roleString = is_string($role) ? $role : implode('|', $role); + $roleString = self::parseRolesToString($role); + $args = is_null($guard) ? $roleString : "$roleString,$guard"; return static::class.':'.$args; } + + /** + * Convert array or string of roles to string representation. + * + * @return string + */ + protected static function parseRolesToString(array|string|\BackedEnum $role) + { + // Convert Enum to its value if an Enum is passed + if ($role instanceof \BackedEnum) { + $role = $role->value; + } + + if (is_array($role)) { + $role = array_map(fn ($r) => $r instanceof \BackedEnum ? $r->value : $r, $role); + + return implode('|', $role); + } + + return (string) $role; + } } diff --git a/tests/PermissionMiddlewareTest.php b/tests/PermissionMiddlewareTest.php index 4dc233193..ecd88894c 100644 --- a/tests/PermissionMiddlewareTest.php +++ b/tests/PermissionMiddlewareTest.php @@ -434,4 +434,55 @@ public function the_middleware_can_be_created_with_static_using_method() PermissionMiddleware::using(['edit-articles', 'edit-news']) ); } + + /** + * @test + * + * @requires PHP >= 8.1 + */ + #[RequiresPhp('>= 8.1')] + #[Test] + public function the_middleware_can_handle_enum_based_permissions_with_static_using_method() + { + $this->assertSame( + 'Spatie\Permission\Middleware\PermissionMiddleware:view articles', + PermissionMiddleware::using(TestModels\TestRolePermissionsEnum::VIEWARTICLES) + ); + $this->assertEquals( + 'Spatie\Permission\Middleware\PermissionMiddleware:view articles,my-guard', + PermissionMiddleware::using(TestModels\TestRolePermissionsEnum::VIEWARTICLES, 'my-guard') + ); + $this->assertEquals( + 'Spatie\Permission\Middleware\PermissionMiddleware:view articles|edit articles', + PermissionMiddleware::using([TestModels\TestRolePermissionsEnum::VIEWARTICLES, TestModels\TestRolePermissionsEnum::EDITARTICLES]) + ); + } + + /** + * @test + * + * @requires PHP >= 8.1 + */ + #[RequiresPhp('>= 8.1')] + #[Test] + public function the_middleware_can_handle_enum_based_permissions_with_handle_method() + { + app(Permission::class)->create(['name' => TestModels\TestRolePermissionsEnum::VIEWARTICLES->value]); + app(Permission::class)->create(['name' => TestModels\TestRolePermissionsEnum::EDITARTICLES->value]); + + Auth::login($this->testUser); + $this->testUser->givePermissionTo(TestModels\TestRolePermissionsEnum::VIEWARTICLES); + + $this->assertEquals( + 200, + $this->runMiddleware($this->permissionMiddleware, TestModels\TestRolePermissionsEnum::VIEWARTICLES) + ); + + $this->testUser->givePermissionTo(TestModels\TestRolePermissionsEnum::EDITARTICLES); + + $this->assertEquals( + 200, + $this->runMiddleware($this->permissionMiddleware, [TestModels\TestRolePermissionsEnum::VIEWARTICLES, TestModels\TestRolePermissionsEnum::EDITARTICLES]) + ); + } } diff --git a/tests/RoleMiddlewareTest.php b/tests/RoleMiddlewareTest.php index 532f2061f..6cf01e2dd 100644 --- a/tests/RoleMiddlewareTest.php +++ b/tests/RoleMiddlewareTest.php @@ -9,6 +9,7 @@ use InvalidArgumentException; use Laravel\Passport\Passport; use PHPUnit\Framework\Attributes\Test; +use Spatie\Permission\Contracts\Role; use Spatie\Permission\Exceptions\UnauthorizedException; use Spatie\Permission\Middleware\RoleMiddleware; use Spatie\Permission\Tests\TestModels\UserWithoutHasRoles; @@ -366,4 +367,55 @@ public function the_middleware_can_be_created_with_static_using_method() RoleMiddleware::using(['testAdminRole', 'anotherRole']) ); } + + /** + * @test + * + * @requires PHP >= 8.1 + */ + #[RequiresPhp('>= 8.1')] + #[Test] + public function the_middleware_can_handle_enum_based_roles_with_static_using_method() + { + $this->assertSame( + 'Spatie\Permission\Middleware\RoleMiddleware:writer', + RoleMiddleware::using(TestModels\TestRolePermissionsEnum::WRITER) + ); + $this->assertEquals( + 'Spatie\Permission\Middleware\RoleMiddleware:writer,my-guard', + RoleMiddleware::using(TestModels\TestRolePermissionsEnum::WRITER, 'my-guard') + ); + $this->assertEquals( + 'Spatie\Permission\Middleware\RoleMiddleware:writer|editor', + RoleMiddleware::using([TestModels\TestRolePermissionsEnum::WRITER, TestModels\TestRolePermissionsEnum::EDITOR]) + ); + } + + /** + * @test + * + * @requires PHP >= 8.1 + */ + #[RequiresPhp('>= 8.1')] + #[Test] + public function the_middleware_can_handle_enum_based_roles_with_handle_method() + { + app(Role::class)->create(['name' => TestModels\TestRolePermissionsEnum::WRITER->value]); + app(Role::class)->create(['name' => TestModels\TestRolePermissionsEnum::EDITOR->value]); + + Auth::login($this->testUser); + $this->testUser->assignRole(TestModels\TestRolePermissionsEnum::WRITER); + + $this->assertEquals( + 200, + $this->runMiddleware($this->roleMiddleware, TestModels\TestRolePermissionsEnum::WRITER) + ); + + $this->testUser->assignRole(TestModels\TestRolePermissionsEnum::EDITOR); + + $this->assertEquals( + 200, + $this->runMiddleware($this->roleMiddleware, [TestModels\TestRolePermissionsEnum::WRITER, TestModels\TestRolePermissionsEnum::EDITOR]) + ); + } } From 4fa03c06509e037a4d42c131d0f181e3e4bbd483 Mon Sep 17 00:00:00 2001 From: drbyte <404472+drbyte@users.noreply.github.com> Date: Fri, 28 Feb 2025 20:29:57 +0000 Subject: [PATCH 593/648] Fix styling --- tests/PermissionMiddlewareTest.php | 4 +-- tests/RoleMiddlewareTest.php | 54 +++++++++++++++--------------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/tests/PermissionMiddlewareTest.php b/tests/PermissionMiddlewareTest.php index ecd88894c..2aa233307 100644 --- a/tests/PermissionMiddlewareTest.php +++ b/tests/PermissionMiddlewareTest.php @@ -472,14 +472,14 @@ public function the_middleware_can_handle_enum_based_permissions_with_handle_met Auth::login($this->testUser); $this->testUser->givePermissionTo(TestModels\TestRolePermissionsEnum::VIEWARTICLES); - + $this->assertEquals( 200, $this->runMiddleware($this->permissionMiddleware, TestModels\TestRolePermissionsEnum::VIEWARTICLES) ); $this->testUser->givePermissionTo(TestModels\TestRolePermissionsEnum::EDITARTICLES); - + $this->assertEquals( 200, $this->runMiddleware($this->permissionMiddleware, [TestModels\TestRolePermissionsEnum::VIEWARTICLES, TestModels\TestRolePermissionsEnum::EDITARTICLES]) diff --git a/tests/RoleMiddlewareTest.php b/tests/RoleMiddlewareTest.php index 6cf01e2dd..96adea0a9 100644 --- a/tests/RoleMiddlewareTest.php +++ b/tests/RoleMiddlewareTest.php @@ -391,31 +391,31 @@ public function the_middleware_can_handle_enum_based_roles_with_static_using_met ); } - /** - * @test - * - * @requires PHP >= 8.1 - */ - #[RequiresPhp('>= 8.1')] - #[Test] - public function the_middleware_can_handle_enum_based_roles_with_handle_method() - { - app(Role::class)->create(['name' => TestModels\TestRolePermissionsEnum::WRITER->value]); - app(Role::class)->create(['name' => TestModels\TestRolePermissionsEnum::EDITOR->value]); - - Auth::login($this->testUser); - $this->testUser->assignRole(TestModels\TestRolePermissionsEnum::WRITER); - - $this->assertEquals( - 200, - $this->runMiddleware($this->roleMiddleware, TestModels\TestRolePermissionsEnum::WRITER) - ); - - $this->testUser->assignRole(TestModels\TestRolePermissionsEnum::EDITOR); - - $this->assertEquals( - 200, - $this->runMiddleware($this->roleMiddleware, [TestModels\TestRolePermissionsEnum::WRITER, TestModels\TestRolePermissionsEnum::EDITOR]) - ); - } + /** + * @test + * + * @requires PHP >= 8.1 + */ + #[RequiresPhp('>= 8.1')] + #[Test] + public function the_middleware_can_handle_enum_based_roles_with_handle_method() + { + app(Role::class)->create(['name' => TestModels\TestRolePermissionsEnum::WRITER->value]); + app(Role::class)->create(['name' => TestModels\TestRolePermissionsEnum::EDITOR->value]); + + Auth::login($this->testUser); + $this->testUser->assignRole(TestModels\TestRolePermissionsEnum::WRITER); + + $this->assertEquals( + 200, + $this->runMiddleware($this->roleMiddleware, TestModels\TestRolePermissionsEnum::WRITER) + ); + + $this->testUser->assignRole(TestModels\TestRolePermissionsEnum::EDITOR); + + $this->assertEquals( + 200, + $this->runMiddleware($this->roleMiddleware, [TestModels\TestRolePermissionsEnum::WRITER, TestModels\TestRolePermissionsEnum::EDITOR]) + ); + } } From 05dec51715d03fe178b10140bc9676cdabf42158 Mon Sep 17 00:00:00 2001 From: drbyte <404472+drbyte@users.noreply.github.com> Date: Fri, 28 Feb 2025 21:22:53 +0000 Subject: [PATCH 594/648] Update CHANGELOG --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4a056de3..a721eb9e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ All notable changes to `laravel-permission` will be documented in this file +## 6.16.0 - 2025-02-28 + +### What's Changed + +* Middleware: support enums in role/permission middleware by @marklawntalk in https://github.com/spatie/laravel-permission/pull/2813 + +### New Contributors + +* @marklawntalk made their first contribution in https://github.com/spatie/laravel-permission/pull/2813 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/6.15.0...6.16.0 + ## 6.15.0 - 2025-02-17 ### What's Changed @@ -979,6 +991,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` @@ -1055,6 +1068,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` From 9158ee379bbd5717b94c97beb9793c61ac13b7bd Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 28 Feb 2025 17:42:14 -0500 Subject: [PATCH 595/648] formatting --- tests/TestModels/TestRolePermissionsEnum.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/TestModels/TestRolePermissionsEnum.php b/tests/TestModels/TestRolePermissionsEnum.php index 7b3ba7485..61fec1a12 100644 --- a/tests/TestModels/TestRolePermissionsEnum.php +++ b/tests/TestModels/TestRolePermissionsEnum.php @@ -56,7 +56,7 @@ public function label(): string self::VIEWARTICLES => 'View Articles', self::EDITARTICLES => 'Edit Articles', - default => Str::words($this->name) + default => Str::words($this->value), }; } } From 02687346fe01242038a5ef9346696a42eda81250 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 28 Feb 2025 17:42:54 -0500 Subject: [PATCH 596/648] Tests refactoring --- tests/TestCase.php | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/tests/TestCase.php b/tests/TestCase.php index 3a4f1c829..073e7c8fb 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -70,6 +70,9 @@ protected function setUp(): void // Note: this also flushes the cache from within the migration $this->setUpDatabase($this->app); + + $this->setUpBaseTestPermissions($this->app); + if ($this->hasTeams) { setPermissionsTeamId(1); } @@ -92,9 +95,8 @@ protected function tearDown(): void /** * @param \Illuminate\Foundation\Application $app - * @return array */ - protected function getPackageProviders($app) + protected function getPackageProviders($app): array { return $this->getLaravelVersion() < 9 ? [ PermissionServiceProvider::class, @@ -196,13 +198,25 @@ protected function setUpDatabase($app) $this->testUser = User::create(['email' => 'test@user.com']); $this->testAdmin = Admin::create(['email' => 'admin@user.com']); + } + + /** + * Set up initial roles and permissions used in many tests + * + * @param \Illuminate\Foundation\Application $app + */ + protected function setUpBaseTestPermissions($app): void + { $this->testUserRole = $app[Role::class]->create(['name' => 'testRole']); $app[Role::class]->create(['name' => 'testRole2']); $this->testAdminRole = $app[Role::class]->create(['name' => 'testAdminRole', 'guard_name' => 'admin']); $this->testUserPermission = $app[Permission::class]->create(['name' => 'edit-articles']); $app[Permission::class]->create(['name' => 'edit-news']); $app[Permission::class]->create(['name' => 'edit-blog']); - $this->testAdminPermission = $app[Permission::class]->create(['name' => 'admin-permission', 'guard_name' => 'admin']); + $this->testAdminPermission = $app[Permission::class]->create([ + 'name' => 'admin-permission', + 'guard_name' => 'admin', + ]); $app[Permission::class]->create(['name' => 'Edit News']); } From d15a731b4b0db37a644f8721c13dfdf8ed04f1c2 Mon Sep 17 00:00:00 2001 From: Ali Qasemzadeh Date: Sun, 2 Mar 2025 18:16:49 +0330 Subject: [PATCH 597/648] Add JetAdmin link to UI options documentation Updated the advanced usage UI options documentation to include JetAdmin, a Laravel Livewire starter kit for --- docs/advanced-usage/ui-options.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/advanced-usage/ui-options.md b/docs/advanced-usage/ui-options.md index 17f70b85f..ebfb552ef 100644 --- a/docs/advanced-usage/ui-options.md +++ b/docs/advanced-usage/ui-options.md @@ -29,3 +29,5 @@ If you decide you need a UI, even if it's not for creating/editing role/permissi - [LiveWire Base Admin Panel](https://github.com/aliqasemzadeh/bap) User management by [AliQasemzadeh](https://github.com/aliqasemzadeh) + +- [JetAdmin](https://github.com/aliqasemzadeh/jetadmin) JetAdmin use laravel livewire starter kit and manage permissions. [AliQasemzadeh](https://github.com/aliqasemzadeh) From 759f15c9ec8d170d88409b110539815d8d702053 Mon Sep 17 00:00:00 2001 From: so1e <31845646+Yi-pixel@users.noreply.github.com> Date: Sat, 15 Mar 2025 13:42:34 +0800 Subject: [PATCH 598/648] Route macro functions add backed enum support --- src/PermissionServiceProvider.php | 10 ++++- tests/RouteTest.php | 64 +++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/src/PermissionServiceProvider.php b/src/PermissionServiceProvider.php index 96ae5abc6..3aa63def0 100644 --- a/src/PermissionServiceProvider.php +++ b/src/PermissionServiceProvider.php @@ -149,13 +149,19 @@ protected function registerMacroHelpers(): void } Route::macro('role', function ($roles = []) { + $roles = Arr::wrap($roles); + $roles = array_map(fn ($role) => $role instanceof \BackedEnum ? $role->value : $role, $roles); + /** @var Route $this */ - return $this->middleware('role:'.implode('|', Arr::wrap($roles))); + return $this->middleware('role:'.implode('|', $roles)); }); Route::macro('permission', function ($permissions = []) { + $permissions = Arr::wrap($permissions); + $permissions = array_map(fn ($permission) => $permission instanceof \BackedEnum ? $permission->value : $permission, $permissions); + /** @var Route $this */ - return $this->middleware('permission:'.implode('|', Arr::wrap($permissions))); + return $this->middleware('permission:'.implode('|', $permissions)); }); } diff --git a/tests/RouteTest.php b/tests/RouteTest.php index 0533ea74c..7b213eee8 100644 --- a/tests/RouteTest.php +++ b/tests/RouteTest.php @@ -2,7 +2,9 @@ namespace Spatie\Permission\Tests; +use PHPUnit\Framework\Attributes\RequiresPhp; use PHPUnit\Framework\Attributes\Test; +use Spatie\Permission\Tests\TestModels\TestRolePermissionsEnum; class RouteTest extends TestCase { @@ -51,4 +53,66 @@ public function test_role_and_permission_function_together() $this->getLastRouteMiddlewareFromRouter($router) ); } + + /** + * @test + * + * @requires PHP 8.1.0 + */ + #[RequiresPhp('>= 8.1.0')] + #[Test] + public function test_role_function_with_backed_enum() + { + $router = $this->getRouter(); + + $router->get('role-test.enum', $this->getRouteResponse()) + ->name('role.test.enum') + ->role(TestRolePermissionsEnum::USERMANAGER); + + $this->assertEquals(['role:'.TestRolePermissionsEnum::USERMANAGER->value], $this->getLastRouteMiddlewareFromRouter($router)); + } + + /** + * @test + * + * @requires PHP 8.1.0 + */ + #[RequiresPhp('>= 8.1.0')] + #[Test] + public function test_permission_function_with_backed_enum() + { + $router = $this->getRouter(); + + $router->get('permission-test.enum', $this->getRouteResponse()) + ->name('permission.test.enum') + ->permission(TestRolePermissionsEnum::WRITER); + + $expected = ['permission:'.TestRolePermissionsEnum::WRITER->value]; + $this->assertEquals($expected, $this->getLastRouteMiddlewareFromRouter($router)); + } + + /** + * @test + * + * @requires PHP 8.1.0 + */ + #[RequiresPhp('>= 8.1.0')] + #[Test] + public function test_role_and_permission_function_together_with_backed_enum() + { + $router = $this->getRouter(); + + $router->get('roles-permissions-test.enum', $this->getRouteResponse()) + ->name('roles-permissions.test.enum') + ->role([TestRolePermissionsEnum::USERMANAGER, TestRolePermissionsEnum::ADMIN]) + ->permission([TestRolePermissionsEnum::WRITER, TestRolePermissionsEnum::EDITOR]); + + $this->assertEquals( + [ + 'role:'.TestRolePermissionsEnum::USERMANAGER->value.'|'.TestRolePermissionsEnum::ADMIN->value, + 'permission:'.TestRolePermissionsEnum::WRITER->value.'|'.TestRolePermissionsEnum::EDITOR->value, + ], + $this->getLastRouteMiddlewareFromRouter($router) + ); + } } From f5b59d92282845f0a8c9e025130097097370925a Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 4 Apr 2025 17:12:23 -0400 Subject: [PATCH 599/648] Mention Octane Fixes #2827 --- docs/advanced-usage/cache.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/advanced-usage/cache.md b/docs/advanced-usage/cache.md index 5e76828ec..c2dd260c2 100644 --- a/docs/advanced-usage/cache.md +++ b/docs/advanced-usage/cache.md @@ -46,6 +46,9 @@ php artisan permission:cache-reset ``` (This command is effectively an alias for `artisan cache:forget spatie.permission.cache` but respects the package config as well.) +## Octane cache reset +In many cases Octane will not need additional cache resets; however, if you find that cache results are stale or crossing over between requests, you can force a cache flush upon every Octane reset cycle by editing the `/config/permission.php` and setting `register_octane_reset_listener` to true. + ## Cache Configuration Settings This package allows you to customize cache-related operations via its config file. In most cases the defaults are fine; however, in a multitenancy situation you may wish to do some cache-prefix overrides when switching tenants. See below for more details. From 02ada8f638b643713fa2fb543384738e27346ddb Mon Sep 17 00:00:00 2001 From: Jimi Robaer Date: Tue, 8 Apr 2025 17:06:14 +0200 Subject: [PATCH 600/648] Update README.md --- README.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1a5e00473..a8a54d3fd 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,18 @@ -

Social Card of Laravel Permission

+
+ + + + Logo for laravel-permission + + -# Associate users with permissions and roles +

Associate users with permissions and roles

[![Latest Version on Packagist](https://img.shields.io/packagist/v/spatie/laravel-permission.svg?style=flat-square)](https://packagist.org/packages/spatie/laravel-permission) [![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/spatie/laravel-permission/run-tests-L8.yml?branch=main&label=Tests)](https://github.com/spatie/laravel-permission/actions?query=workflow%3ATests+branch%3Amain) [![Total Downloads](https://img.shields.io/packagist/dt/spatie/laravel-permission.svg?style=flat-square)](https://packagist.org/packages/spatie/laravel-permission) + +
## Documentation, Installation, and Usage Instructions @@ -77,7 +85,7 @@ can be found [in this repo on GitHub](https://github.com/laracasts/laravel-5-rol Special thanks to [Alex Vanderbist](https://github.com/AlexVanderbist) who greatly helped with `v2`, and to [Chris Brown](https://github.com/drbyte) for his longtime support helping us maintain the package. -And a special thanks to [Caneco](https://twitter.com/caneco) for the logo ✨ +Special thanks to [Caneco](https://twitter.com/caneco) for the original logo. ## Alternatives From 9bdd93375f5e296c3a1d19bbf87c3711be2e6c0a Mon Sep 17 00:00:00 2001 From: drbyte <404472+drbyte@users.noreply.github.com> Date: Wed, 9 Apr 2025 22:03:12 +0000 Subject: [PATCH 601/648] Update CHANGELOG --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a721eb9e2..3778ef834 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ All notable changes to `laravel-permission` will be documented in this file +## 6.17.0 - 2025-04-09 + +### What's Changed + +* Route macro functions: add backed enum support by @Yi-pixel in https://github.com/spatie/laravel-permission/pull/2823 + +### New Contributors + +* @Yi-pixel made their first contribution in https://github.com/spatie/laravel-permission/pull/2823 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/6.16.0...6.17.0 + ## 6.16.0 - 2025-02-28 ### What's Changed @@ -992,6 +1004,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` @@ -1069,6 +1082,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` From 13873682fdc6ee91c4d4408f57a3ce881f0a4e5e Mon Sep 17 00:00:00 2001 From: Caio Adriano <66336349+ccaioadriano@users.noreply.github.com> Date: Wed, 9 Apr 2025 19:07:29 -0300 Subject: [PATCH 602/648] Refactor exception throwing in migration file to use throw_if (#2819) Refactor: refactoring exception throwing --- database/migrations/create_permission_tables.php.stub | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/database/migrations/create_permission_tables.php.stub b/database/migrations/create_permission_tables.php.stub index 70a120f30..ce4d9d2d4 100644 --- a/database/migrations/create_permission_tables.php.stub +++ b/database/migrations/create_permission_tables.php.stub @@ -17,12 +17,8 @@ return new class extends Migration $pivotRole = $columnNames['role_pivot_key'] ?? 'role_id'; $pivotPermission = $columnNames['permission_pivot_key'] ?? 'permission_id'; - if (empty($tableNames)) { - throw new \Exception('Error: config/permission.php not loaded. Run [php artisan config:clear] and try again.'); - } - if ($teams && empty($columnNames['team_foreign_key'] ?? null)) { - throw new \Exception('Error: team_foreign_key on config/permission.php not loaded. Run [php artisan config:clear] and try again.'); - } + throw_if(empty($tableNames), new Exception('Error: config/permission.php not loaded. Run [php artisan config:clear] and try again.')); + throw_if($teams && empty($columnNames['team_foreign_key'] ?? null), new Exception('Error: team_foreign_key on config/permission.php not loaded. Run [php artisan config:clear] and try again.')); Schema::create($tableNames['permissions'], static function (Blueprint $table) { // $table->engine('InnoDB'); From cc264a1d959e70742301156a1b06bbbcd262357e Mon Sep 17 00:00:00 2001 From: Ali Qasemzadeh Date: Thu, 10 Apr 2025 01:40:36 +0330 Subject: [PATCH 603/648] Add JetAdmin link to UI options documentation (#2814) Updated the advanced usage UI options documentation to include JetAdmin, a Laravel Livewire starter kit for --- docs/advanced-usage/ui-options.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/advanced-usage/ui-options.md b/docs/advanced-usage/ui-options.md index 17f70b85f..ebfb552ef 100644 --- a/docs/advanced-usage/ui-options.md +++ b/docs/advanced-usage/ui-options.md @@ -29,3 +29,5 @@ If you decide you need a UI, even if it's not for creating/editing role/permissi - [LiveWire Base Admin Panel](https://github.com/aliqasemzadeh/bap) User management by [AliQasemzadeh](https://github.com/aliqasemzadeh) + +- [JetAdmin](https://github.com/aliqasemzadeh/jetadmin) JetAdmin use laravel livewire starter kit and manage permissions. [AliQasemzadeh](https://github.com/aliqasemzadeh) From 0e45a3cd96074f5aa6c484172224a1d54b828236 Mon Sep 17 00:00:00 2001 From: Samiullah Sediqzada Date: Mon, 21 Apr 2025 10:14:27 +0430 Subject: [PATCH 604/648] Add assign-role command --- src/Commands/AssignRole.php | 51 ++++++++++++++++++++++++++ src/PermissionServiceProvider.php | 1 + tests/CommandTest.php | 59 +++++++++++++++++++++++++++++++ 3 files changed, 111 insertions(+) create mode 100644 src/Commands/AssignRole.php diff --git a/src/Commands/AssignRole.php b/src/Commands/AssignRole.php new file mode 100644 index 000000000..108114b8e --- /dev/null +++ b/src/Commands/AssignRole.php @@ -0,0 +1,51 @@ +argument('name'); + $userId = $this->argument('userId'); + $guardName = $this->argument('guard'); + $userModelClass = $this->argument('userModelNamespace'); + + // Validate that the model class exists and is instantiable + if (! class_exists($userModelClass)) { + $this->error("User model class [{$userModelClass}] does not exist."); + + return Command::FAILURE; + } + + $user = (new $userModelClass)::find($userId); + + if (! $user) { + $this->error("User with ID {$userId} not found."); + + return Command::FAILURE; + } + + /** @var \Spatie\Permission\Contracts\Role $roleClass */ + $roleClass = app(RoleContract::class); + + $role = $roleClass::findOrCreate($roleName, $guardName); + + $user->assignRole($role); + + $this->info("Role `{$role->name}` assigned to user ID {$userId} successfully."); + + return Command::SUCCESS; + } +} diff --git a/src/PermissionServiceProvider.php b/src/PermissionServiceProvider.php index 3aa63def0..588a1f220 100644 --- a/src/PermissionServiceProvider.php +++ b/src/PermissionServiceProvider.php @@ -89,6 +89,7 @@ protected function registerCommands(): void Commands\CreatePermission::class, Commands\Show::class, Commands\UpgradeForTeams::class, + Commands\AssignRole::class, ]); } diff --git a/tests/CommandTest.php b/tests/CommandTest.php index e1961d812..a808aa5ee 100644 --- a/tests/CommandTest.php +++ b/tests/CommandTest.php @@ -8,6 +8,7 @@ use PHPUnit\Framework\Attributes\Test; use Spatie\Permission\Models\Permission; use Spatie\Permission\Models\Role; +use Spatie\Permission\Tests\TestModels\User; class CommandTest extends TestCase { @@ -248,4 +249,62 @@ public function it_can_respond_to_about_command_with_teams() $this->assertRegExp($pattern, $output); } } + + /** @test */ + #[Test] + public function test_is_assign_role_to_user() + { + $user = User::first(); + + Artisan::call('permission:assign-role', [ + 'name' => 'testRole', + 'userId' => $user->id, + 'guard' => 'web', + 'userModelNamespace' => User::class, + ]); + + $output = Artisan::output(); + + $this->assertStringContainsString('Role `testRole` assigned to user ID '.$user->id.' successfully.', $output); + $this->assertCount(1, Role::where('name', 'testRole')->get()); + $this->assertCount(1, $user->roles); + $this->assertTrue($user->hasRole('testRole')); + } + + /** @test */ + #[Test] + public function test_assigning_role_fails_when_user_not_found() + { + + Artisan::call('permission:assign-role', [ + 'name' => 'testRole', + 'userId' => 99999, + 'guard' => 'web', + 'userModelNamespace' => User::class, + ]); + + $output = Artisan::output(); + + $this->assertStringContainsString('User with ID 99999 not found.', $output); + } + + /** @test */ + #[Test] + public function test_assigning_role_fails_when_namspace_not_existed() + { + $user = User::first(); + + $userModelClass = 'App\Models\NonExistentUser'; + + Artisan::call('permission:assign-role', [ + 'name' => 'testRole', + 'userId' => $user->id, + 'guard' => 'web', + 'userModelNamespace' => $userModelClass, + ]); + + $output = Artisan::output(); + + $this->assertStringContainsString("User model class [{$userModelClass}] does not exist.", $output); + } } From df3997986f5d08eb734d3ee1c85725c7a79b5b26 Mon Sep 17 00:00:00 2001 From: Jerren Saunders Date: Mon, 21 Apr 2025 15:59:24 -0400 Subject: [PATCH 605/648] Fix: `wildcard_permission` example includes `permission.` prefix In the `permission.php` template file, the example in the comments for how to override the `WildcardPermission` class incorrectly uses `permission.wildcard_permission` as the key instead of just `wildcard_permission`. --- config/permission.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/permission.php b/config/permission.php index 8e84e9d53..f39f6b5bf 100644 --- a/config/permission.php +++ b/config/permission.php @@ -172,7 +172,7 @@ * The class to use for interpreting wildcard permissions. * If you need to modify delimiters, override the class and specify its name here. */ - // 'permission.wildcard_permission' => Spatie\Permission\WildcardPermission::class, + // 'wildcard_permission' => Spatie\Permission\WildcardPermission::class, /* Cache-specific settings */ From bfb824aa8bf03b3bd8b77ef4b227496f03dbad77 Mon Sep 17 00:00:00 2001 From: Jimi Robaer Date: Tue, 22 Apr 2025 09:32:40 +0200 Subject: [PATCH 606/648] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a8a54d3fd..12bed721f 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ - Logo for laravel-permission + Logo for laravel-permission From ca77ab3c9c3da231df0f6c6a8e5d44d8813cc9fe Mon Sep 17 00:00:00 2001 From: Ken van der Eerden <15888558+Ken-vdE@users.noreply.github.com> Date: Wed, 23 Apr 2025 16:47:25 +0200 Subject: [PATCH 607/648] Update multiple-guards.md Fix, otherwise `Guard::getNames(User::class)` does not work. --- docs/basic-usage/multiple-guards.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/basic-usage/multiple-guards.md b/docs/basic-usage/multiple-guards.md index d9360d6cf..062e49fad 100644 --- a/docs/basic-usage/multiple-guards.md +++ b/docs/basic-usage/multiple-guards.md @@ -20,7 +20,8 @@ Note that this package requires you to register a permission name (same for role If your app structure does NOT differentiate between guards when it comes to roles/permissions, (ie: if ALL your roles/permissions are the SAME for ALL guards), you can override the `getDefaultGuardName` function by adding it to your User model, and specifying your desired `$guard_name`. Then you only need to create roles/permissions for that single `$guard_name`, not duplicating them. The example here sets it to `web`, but use whatever your application's default is: ```php - protected function getDefaultGuardName(): string { return 'web'; } + protected string $guard_name = 'web'; + protected function getDefaultGuardName(): string { return $this->guard_name; } ```` From 39bb908380d903cf7f89d68ef6c45529e2bf37a9 Mon Sep 17 00:00:00 2001 From: Ken van der Eerden <15888558+Ken-vdE@users.noreply.github.com> Date: Wed, 23 Apr 2025 17:07:05 +0200 Subject: [PATCH 608/648] Update Role.php --- src/Models/Role.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Models/Role.php b/src/Models/Role.php index 5bab4878e..7aa729dc1 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -27,7 +27,7 @@ class Role extends Model implements RoleContract public function __construct(array $attributes = []) { - $attributes['guard_name'] = $attributes['guard_name'] ?? config('auth.defaults.guard'); + $attributes['guard_name'] ??= Guard::getDefaultName(static::class); parent::__construct($attributes); @@ -42,7 +42,7 @@ public function __construct(array $attributes = []) */ public static function create(array $attributes = []) { - $attributes['guard_name'] = $attributes['guard_name'] ?? Guard::getDefaultName(static::class); + $attributes['guard_name'] ??= Guard::getDefaultName(static::class); $params = ['name' => $attributes['name'], 'guard_name' => $attributes['guard_name']]; if (app(PermissionRegistrar::class)->teams) { @@ -97,7 +97,7 @@ public function users(): BelongsToMany */ public static function findByName(string $name, ?string $guardName = null): RoleContract { - $guardName = $guardName ?? Guard::getDefaultName(static::class); + $guardName ??= Guard::getDefaultName(static::class); $role = static::findByParam(['name' => $name, 'guard_name' => $guardName]); @@ -115,7 +115,7 @@ public static function findByName(string $name, ?string $guardName = null): Role */ public static function findById(int|string $id, ?string $guardName = null): RoleContract { - $guardName = $guardName ?? Guard::getDefaultName(static::class); + $guardName ??= Guard::getDefaultName(static::class); $role = static::findByParam([(new static)->getKeyName() => $id, 'guard_name' => $guardName]); @@ -133,7 +133,7 @@ public static function findById(int|string $id, ?string $guardName = null): Role */ public static function findOrCreate(string $name, ?string $guardName = null): RoleContract { - $guardName = $guardName ?? Guard::getDefaultName(static::class); + $guardName ??= Guard::getDefaultName(static::class); $role = static::findByParam(['name' => $name, 'guard_name' => $guardName]); From 6f2f96fb2e9d9a888d449ae399484e92272beb3d Mon Sep 17 00:00:00 2001 From: Ken van der Eerden <15888558+Ken-vdE@users.noreply.github.com> Date: Wed, 23 Apr 2025 17:09:10 +0200 Subject: [PATCH 609/648] Update Permission.php --- src/Models/Permission.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Models/Permission.php b/src/Models/Permission.php index fd54b8ca0..490a6c23c 100644 --- a/src/Models/Permission.php +++ b/src/Models/Permission.php @@ -26,7 +26,7 @@ class Permission extends Model implements PermissionContract public function __construct(array $attributes = []) { - $attributes['guard_name'] = $attributes['guard_name'] ?? config('auth.defaults.guard'); + $attributes['guard_name'] ??= Guard::getDefaultName(static::class); parent::__construct($attributes); @@ -41,7 +41,7 @@ public function __construct(array $attributes = []) */ public static function create(array $attributes = []) { - $attributes['guard_name'] = $attributes['guard_name'] ?? Guard::getDefaultName(static::class); + $attributes['guard_name'] ??= Guard::getDefaultName(static::class); $permission = static::getPermission(['name' => $attributes['name'], 'guard_name' => $attributes['guard_name']]); @@ -88,7 +88,7 @@ public function users(): BelongsToMany */ public static function findByName(string $name, ?string $guardName = null): PermissionContract { - $guardName = $guardName ?? Guard::getDefaultName(static::class); + $guardName ??= Guard::getDefaultName(static::class); $permission = static::getPermission(['name' => $name, 'guard_name' => $guardName]); if (! $permission) { throw PermissionDoesNotExist::create($name, $guardName); @@ -106,7 +106,7 @@ public static function findByName(string $name, ?string $guardName = null): Perm */ public static function findById(int|string $id, ?string $guardName = null): PermissionContract { - $guardName = $guardName ?? Guard::getDefaultName(static::class); + $guardName ??= Guard::getDefaultName(static::class); $permission = static::getPermission([(new static)->getKeyName() => $id, 'guard_name' => $guardName]); if (! $permission) { @@ -123,7 +123,7 @@ public static function findById(int|string $id, ?string $guardName = null): Perm */ public static function findOrCreate(string $name, ?string $guardName = null): PermissionContract { - $guardName = $guardName ?? Guard::getDefaultName(static::class); + $guardName ??= Guard::getDefaultName(static::class); $permission = static::getPermission(['name' => $name, 'guard_name' => $guardName]); if (! $permission) { From 502ec932de77278bb30315e377905d5812199ea3 Mon Sep 17 00:00:00 2001 From: Jordan Welch Date: Fri, 9 May 2025 07:13:50 -0500 Subject: [PATCH 610/648] Remove `collectPermissions` that is not being assigned --- src/Traits/HasPermissions.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index ff7339b19..1a8071659 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -450,7 +450,6 @@ public function forgetWildcardPermissionIndex(): void public function syncPermissions(...$permissions) { if ($this->getModel()->exists) { - $this->collectPermissions($permissions); $this->permissions()->detach(); $this->setRelation('permissions', collect()); } From 556f25131d809fcccaa483fccf3527037060576b Mon Sep 17 00:00:00 2001 From: coreyhn Date: Tue, 13 May 2025 12:23:36 -0600 Subject: [PATCH 611/648] [Docs] Remove extra period (#2841) --- docs/advanced-usage/seeding.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/advanced-usage/seeding.md b/docs/advanced-usage/seeding.md index 30dc21bf2..51c56e8d2 100644 --- a/docs/advanced-usage/seeding.md +++ b/docs/advanced-usage/seeding.md @@ -7,7 +7,7 @@ weight: 2 You may discover that it is best to flush this package's cache **BEFORE seeding, to avoid cache conflict errors**. -And if you use the `WithoutModelEvents` trait in your seeders, flush it **AFTER creating any roles/permissions as well, before assigning or granting them.**. +And if you use the `WithoutModelEvents` trait in your seeders, flush it **AFTER creating any roles/permissions as well, before assigning or granting them**. ```php // reset cached roles and permissions From 7d53c90ed56030afb163549f14810f8b45f9b562 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Tue, 13 May 2025 16:03:55 -0500 Subject: [PATCH 612/648] Fix #2843 --- config/permission.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/permission.php b/config/permission.php index 8e84e9d53..f39f6b5bf 100644 --- a/config/permission.php +++ b/config/permission.php @@ -172,7 +172,7 @@ * The class to use for interpreting wildcard permissions. * If you need to modify delimiters, override the class and specify its name here. */ - // 'permission.wildcard_permission' => Spatie\Permission\WildcardPermission::class, + // 'wildcard_permission' => Spatie\Permission\WildcardPermission::class, /* Cache-specific settings */ From 68f9b48ea9cbb1599dfbd9efafdb221fc8cb7c0e Mon Sep 17 00:00:00 2001 From: drbyte <404472+drbyte@users.noreply.github.com> Date: Wed, 14 May 2025 03:34:44 +0000 Subject: [PATCH 613/648] Update CHANGELOG --- CHANGELOG.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3778ef834..45e6dcb5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,28 @@ All notable changes to `laravel-permission` will be documented in this file +## 6.18.0 - 2025-05-14 + +### What's Changed + +* refactor exception throwing in migration file to use throw_if() by @ccaioadriano in https://github.com/spatie/laravel-permission/pull/2819 +* Fix: Example in config comment includes `permission.` prefix on `wildcard_permission` key by @jerrens in https://github.com/spatie/laravel-permission/pull/2835 +* Fix commented config key typo by @erikn69 in https://github.com/spatie/laravel-permission/pull/2844 +* Remove `collectPermissions` that is not being assigned by @JHWelch in https://github.com/spatie/laravel-permission/pull/2840 +* [Docs] Update multiple-guards.md by @Ken-vdE in https://github.com/spatie/laravel-permission/pull/2836 +* [Docs] Remove extra period by @coreyhn in https://github.com/spatie/laravel-permission/pull/2841 +* Add JetAdmin as UI Option. by @aliqasemzadeh in https://github.com/spatie/laravel-permission/pull/2814 + +### New Contributors + +* @ccaioadriano made their first contribution in https://github.com/spatie/laravel-permission/pull/2819 +* @coreyhn made their first contribution in https://github.com/spatie/laravel-permission/pull/2841 +* @jerrens made their first contribution in https://github.com/spatie/laravel-permission/pull/2835 +* @Ken-vdE made their first contribution in https://github.com/spatie/laravel-permission/pull/2836 +* @JHWelch made their first contribution in https://github.com/spatie/laravel-permission/pull/2840 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/6.17.0...6.18.0 + ## 6.17.0 - 2025-04-09 ### What's Changed @@ -1005,6 +1027,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` @@ -1083,6 +1106,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` From 78630c7f8a343106d10dfad076e50534ee52b990 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Tue, 27 May 2025 08:55:53 -0500 Subject: [PATCH 614/648] Revert "Remove `collectPermissions`, test added" --- src/Traits/HasPermissions.php | 1 + tests/HasPermissionsTest.php | 17 +++++++++++++++++ tests/HasRolesTest.php | 17 +++++++++++++++++ 3 files changed, 35 insertions(+) diff --git a/src/Traits/HasPermissions.php b/src/Traits/HasPermissions.php index 1a8071659..ff7339b19 100644 --- a/src/Traits/HasPermissions.php +++ b/src/Traits/HasPermissions.php @@ -450,6 +450,7 @@ public function forgetWildcardPermissionIndex(): void public function syncPermissions(...$permissions) { if ($this->getModel()->exists) { + $this->collectPermissions($permissions); $this->permissions()->detach(); $this->setRelation('permissions', collect()); } diff --git a/tests/HasPermissionsTest.php b/tests/HasPermissionsTest.php index 49f450359..e3b3e3474 100644 --- a/tests/HasPermissionsTest.php +++ b/tests/HasPermissionsTest.php @@ -611,6 +611,23 @@ public function it_can_avoid_sync_duplicated_permissions() $this->assertTrue($this->testUser->hasDirectPermission('edit-blog')); } + /** @test */ + #[Test] + public function it_can_avoid_detach_on_permission_that_does_not_exist_sync() + { + $this->testUser->syncPermissions('edit-articles'); + + try { + $this->testUser->syncPermissions('permission-does-not-exist'); + $this->fail('Expected PermissionDoesNotExist exception was not thrown.'); + } catch (PermissionDoesNotExist $e){ + // + } + + $this->assertTrue($this->testUser->hasDirectPermission('edit-articles')); + $this->assertFalse($this->testUser->checkPermissionTo('permission-does-not-exist')); + } + /** @test */ #[Test] public function it_can_sync_multiple_permissions_by_id() diff --git a/tests/HasRolesTest.php b/tests/HasRolesTest.php index 7f8a137e0..59941f78d 100644 --- a/tests/HasRolesTest.php +++ b/tests/HasRolesTest.php @@ -318,6 +318,23 @@ public function it_can_avoid_sync_duplicated_roles() $this->assertTrue($this->testUser->hasRole('testRole2')); } + /** @test */ + #[Test] + public function it_can_avoid_detach_on_role_that_does_not_exist_sync() + { + $this->testUser->syncRoles('testRole'); + + try { + $this->testUser->syncRoles('role-does-not-exist'); + $this->fail('Expected RoleDoesNotExist exception was not thrown.'); + } catch (RoleDoesNotExist $e){ + // + } + + $this->assertTrue($this->testUser->hasRole('testRole')); + $this->assertFalse($this->testUser->hasRole('role-does-not-exist')); + } + /** @test */ #[Test] public function it_can_sync_multiple_roles() From e64507622153f0afa2734f6bc87c050c2c8d7fd5 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Tue, 27 May 2025 10:04:28 -0500 Subject: [PATCH 615/648] Make phpstan happy --- phpstan.neon.dist | 3 +++ 1 file changed, 3 insertions(+) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 022878407..e6a4b0f89 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -17,3 +17,6 @@ parameters: # wildcard permissions: - '#Call to an undefined method Illuminate\\Database\\Eloquent\\Model::getWildcardClass#' - '#Call to an undefined method Illuminate\\Database\\Eloquent\\Model::getAllPermissions#' + # contract checks: + - '#Call to function is_a\(\) with (.*) and ''Spatie\\\\Permission\\\\Contracts\\\\Permission'' will always evaluate to true\.$#' + - '#Call to function is_a\(\) with (.*) and ''Spatie\\\\Permission\\\\Contracts\\\\Role'' will always evaluate to true\.$#' From 15b15e10c812791d1bbe1f5502a2773bbd54892b Mon Sep 17 00:00:00 2001 From: drbyte <404472+drbyte@users.noreply.github.com> Date: Wed, 28 May 2025 02:51:34 +0000 Subject: [PATCH 616/648] Fix styling --- tests/HasPermissionsTest.php | 2 +- tests/HasRolesTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/HasPermissionsTest.php b/tests/HasPermissionsTest.php index e3b3e3474..95ad3a473 100644 --- a/tests/HasPermissionsTest.php +++ b/tests/HasPermissionsTest.php @@ -620,7 +620,7 @@ public function it_can_avoid_detach_on_permission_that_does_not_exist_sync() try { $this->testUser->syncPermissions('permission-does-not-exist'); $this->fail('Expected PermissionDoesNotExist exception was not thrown.'); - } catch (PermissionDoesNotExist $e){ + } catch (PermissionDoesNotExist $e) { // } diff --git a/tests/HasRolesTest.php b/tests/HasRolesTest.php index 59941f78d..2ead3982e 100644 --- a/tests/HasRolesTest.php +++ b/tests/HasRolesTest.php @@ -327,7 +327,7 @@ public function it_can_avoid_detach_on_role_that_does_not_exist_sync() try { $this->testUser->syncRoles('role-does-not-exist'); $this->fail('Expected RoleDoesNotExist exception was not thrown.'); - } catch (RoleDoesNotExist $e){ + } catch (RoleDoesNotExist $e) { // } From c5c63c145c2e0f34896c66765aede8a6019e1d5a Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Tue, 27 May 2025 23:16:44 -0400 Subject: [PATCH 617/648] remove apc --- tests/TestCase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/TestCase.php b/tests/TestCase.php index 073e7c8fb..45831ce15 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -148,7 +148,7 @@ protected function getEnvironmentSetUp($app) // FOR MANUAL TESTING OF ALTERNATE CACHE STORES: // $app['config']->set('cache.default', 'array'); // Laravel supports: array, database, file - // requires extensions: apc, memcached, redis, dynamodb, octane + // requires extensions: memcached, redis, dynamodb, octane } /** From 51a919e5acde63620088b8ad48084aaa846d9fca Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Wed, 28 May 2025 12:36:25 -0400 Subject: [PATCH 618/648] ampersand --- docs/basic-usage/teams-permissions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/basic-usage/teams-permissions.md b/docs/basic-usage/teams-permissions.md index ad220a1ac..684770ded 100644 --- a/docs/basic-usage/teams-permissions.md +++ b/docs/basic-usage/teams-permissions.md @@ -99,7 +99,7 @@ Role::create(['name' => 'reader', 'team_id' => 1]); Role::create(['name' => 'reviewer']); ``` -## Roles/Permissions Assignment & Removal +## Roles/Permissions Assignment and Removal The role/permission assignment and removal for teams are the same as without teams, but they take the global `team_id` which is set on login. From f241ca71546e0b8f82284e172908ec6d59bb29ef Mon Sep 17 00:00:00 2001 From: drbyte <404472+drbyte@users.noreply.github.com> Date: Sat, 31 May 2025 23:39:13 +0000 Subject: [PATCH 619/648] Update CHANGELOG --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45e6dcb5a..56bd868eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ All notable changes to `laravel-permission` will be documented in this file +## 6.19.0 - 2025-05-31 + +### What's Changed + +* Revert "Remove `collectPermissions` that is not being assigned" by @erikn69 in https://github.com/spatie/laravel-permission/pull/2851 +* Fix guard_name not used to set default attribute in Role and Permission model by @Ken-vdE in https://github.com/spatie/laravel-permission/pull/2837 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/6.18.0...6.19.0 + ## 6.18.0 - 2025-05-14 ### What's Changed @@ -1028,6 +1037,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` @@ -1107,6 +1117,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` From 92d02871f0e955991de03a373a9f094edcdc1e3f Mon Sep 17 00:00:00 2001 From: nAa666 Date: Tue, 3 Jun 2025 10:26:43 +0300 Subject: [PATCH 620/648] Add translations support for exception messages --- src/Exceptions/GuardDoesNotMatch.php | 5 ++++- src/Exceptions/PermissionAlreadyExists.php | 5 ++++- src/Exceptions/PermissionDoesNotExist.php | 10 ++++++++-- src/Exceptions/RoleAlreadyExists.php | 5 ++++- src/Exceptions/RoleDoesNotExist.php | 10 ++++++++-- src/Exceptions/UnauthorizedException.php | 18 ++++++++++-------- .../WildcardPermissionInvalidArgument.php | 2 +- ...WildcardPermissionNotImplementsContract.php | 2 +- .../WildcardPermissionNotProperlyFormatted.php | 4 +++- 9 files changed, 43 insertions(+), 18 deletions(-) diff --git a/src/Exceptions/GuardDoesNotMatch.php b/src/Exceptions/GuardDoesNotMatch.php index 6506bb658..7c57b3155 100644 --- a/src/Exceptions/GuardDoesNotMatch.php +++ b/src/Exceptions/GuardDoesNotMatch.php @@ -9,6 +9,9 @@ class GuardDoesNotMatch extends InvalidArgumentException { public static function create(string $givenGuard, Collection $expectedGuards) { - return new static("The given role or permission should use guard `{$expectedGuards->implode(', ')}` instead of `{$givenGuard}`."); + return new static(__('The given role or permission should use guard `:expected` instead of `:given`.', [ + 'expected' => $expectedGuards->implode(', '), + 'given' => $givenGuard, + ])); } } diff --git a/src/Exceptions/PermissionAlreadyExists.php b/src/Exceptions/PermissionAlreadyExists.php index 2748c9d1d..2270db148 100644 --- a/src/Exceptions/PermissionAlreadyExists.php +++ b/src/Exceptions/PermissionAlreadyExists.php @@ -8,6 +8,9 @@ class PermissionAlreadyExists extends InvalidArgumentException { public static function create(string $permissionName, string $guardName) { - return new static("A `{$permissionName}` permission already exists for guard `{$guardName}`."); + return new static(__('A `:permission` permission already exists for guard `:guard`.', [ + 'permission' => $permissionName, + 'guard' => $guardName, + ])); } } diff --git a/src/Exceptions/PermissionDoesNotExist.php b/src/Exceptions/PermissionDoesNotExist.php index c04f5eaf0..853bab10e 100644 --- a/src/Exceptions/PermissionDoesNotExist.php +++ b/src/Exceptions/PermissionDoesNotExist.php @@ -8,7 +8,10 @@ class PermissionDoesNotExist extends InvalidArgumentException { public static function create(string $permissionName, ?string $guardName) { - return new static("There is no permission named `{$permissionName}` for guard `{$guardName}`."); + return new static(__('There is no permission named `:permission` for guard `:guard`.', [ + 'permission' => $permissionName, + 'guard' => $guardName, + ])); } /** @@ -17,6 +20,9 @@ public static function create(string $permissionName, ?string $guardName) */ public static function withId($permissionId, ?string $guardName) { - return new static("There is no [permission] with ID `{$permissionId}` for guard `{$guardName}`."); + return new static(__('There is no [permission] with ID `:id` for guard `:guard`.', [ + 'id' => $permissionId, + 'guard' => $guardName, + ])); } } diff --git a/src/Exceptions/RoleAlreadyExists.php b/src/Exceptions/RoleAlreadyExists.php index 939c55bac..141be8d26 100644 --- a/src/Exceptions/RoleAlreadyExists.php +++ b/src/Exceptions/RoleAlreadyExists.php @@ -8,6 +8,9 @@ class RoleAlreadyExists extends InvalidArgumentException { public static function create(string $roleName, string $guardName) { - return new static("A role `{$roleName}` already exists for guard `{$guardName}`."); + return new static(__('A role `:role` already exists for guard `:guard`.', [ + 'role' => $roleName, + 'guard' => $guardName, + ])); } } diff --git a/src/Exceptions/RoleDoesNotExist.php b/src/Exceptions/RoleDoesNotExist.php index a4871c665..641b4d37f 100644 --- a/src/Exceptions/RoleDoesNotExist.php +++ b/src/Exceptions/RoleDoesNotExist.php @@ -8,7 +8,10 @@ class RoleDoesNotExist extends InvalidArgumentException { public static function named(string $roleName, ?string $guardName) { - return new static("There is no role named `{$roleName}` for guard `{$guardName}`."); + return new static(__('There is no role named `:role` for guard `:guard`.', [ + 'role' => $roleName, + 'guard' => $guardName, + ])); } /** @@ -17,6 +20,9 @@ public static function named(string $roleName, ?string $guardName) */ public static function withId($roleId, ?string $guardName) { - return new static("There is no role with ID `{$roleId}` for guard `{$guardName}`."); + return new static(__('There is no role with ID `:id` for guard `:guard`.', [ + 'id' => $roleId, + 'guard' => $guardName, + ])); } } diff --git a/src/Exceptions/UnauthorizedException.php b/src/Exceptions/UnauthorizedException.php index 249898a75..be5d47402 100644 --- a/src/Exceptions/UnauthorizedException.php +++ b/src/Exceptions/UnauthorizedException.php @@ -13,10 +13,10 @@ class UnauthorizedException extends HttpException public static function forRoles(array $roles): self { - $message = 'User does not have the right roles.'; + $message = __('User does not have the right roles.'); if (config('permission.display_role_in_exception')) { - $message .= ' Necessary roles are '.implode(', ', $roles); + $message .= ' ' . __('Necessary roles are :roles', ['roles' => implode(', ', $roles)]); } $exception = new static(403, $message, null, []); @@ -27,10 +27,10 @@ public static function forRoles(array $roles): self public static function forPermissions(array $permissions): self { - $message = 'User does not have the right permissions.'; + $message = __('User does not have the right permissions.'); if (config('permission.display_permission_in_exception')) { - $message .= ' Necessary permissions are '.implode(', ', $permissions); + $message .= ' ' . __('Necessary permissions are :permissions', ['permissions' => implode(', ', $permissions)]); } $exception = new static(403, $message, null, []); @@ -41,10 +41,10 @@ public static function forPermissions(array $permissions): self public static function forRolesOrPermissions(array $rolesOrPermissions): self { - $message = 'User does not have any of the necessary access rights.'; + $message = __('User does not have any of the necessary access rights.'); if (config('permission.display_permission_in_exception') && config('permission.display_role_in_exception')) { - $message .= ' Necessary roles or permissions are '.implode(', ', $rolesOrPermissions); + $message .= ' ' . __('Necessary roles or permissions are :values', ['values' => implode(', ', $rolesOrPermissions)]); } $exception = new static(403, $message, null, []); @@ -57,12 +57,14 @@ public static function missingTraitHasRoles(Authorizable $user): self { $class = get_class($user); - return new static(403, "Authorizable class `{$class}` must use Spatie\Permission\Traits\HasRoles trait.", null, []); + return new static(403, __('Authorizable class `:class` must use Spatie\\Permission\\Traits\\HasRoles trait.', [ + 'class' => $class, + ]), null, []); } public static function notLoggedIn(): self { - return new static(403, 'User is not logged in.', null, []); + return new static(403, __('User is not logged in.'), null, []); } public function getRequiredRoles(): array diff --git a/src/Exceptions/WildcardPermissionInvalidArgument.php b/src/Exceptions/WildcardPermissionInvalidArgument.php index 44f092401..2c0a802ee 100644 --- a/src/Exceptions/WildcardPermissionInvalidArgument.php +++ b/src/Exceptions/WildcardPermissionInvalidArgument.php @@ -8,6 +8,6 @@ class WildcardPermissionInvalidArgument extends InvalidArgumentException { public static function create() { - return new static('Wildcard permission must be string, permission id or permission instance'); + return new static(__('Wildcard permission must be string, permission id or permission instance')); } } diff --git a/src/Exceptions/WildcardPermissionNotImplementsContract.php b/src/Exceptions/WildcardPermissionNotImplementsContract.php index 7b81c73c9..52deaa456 100644 --- a/src/Exceptions/WildcardPermissionNotImplementsContract.php +++ b/src/Exceptions/WildcardPermissionNotImplementsContract.php @@ -8,6 +8,6 @@ class WildcardPermissionNotImplementsContract extends InvalidArgumentException { public static function create() { - return new static('Wildcard permission class must implements Spatie\Permission\Contracts\Wildcard contract'); + return new static(__('Wildcard permission class must implement Spatie\\Permission\\Contracts\\Wildcard contract')); } } diff --git a/src/Exceptions/WildcardPermissionNotProperlyFormatted.php b/src/Exceptions/WildcardPermissionNotProperlyFormatted.php index 721157e6c..54b1e2915 100644 --- a/src/Exceptions/WildcardPermissionNotProperlyFormatted.php +++ b/src/Exceptions/WildcardPermissionNotProperlyFormatted.php @@ -8,6 +8,8 @@ class WildcardPermissionNotProperlyFormatted extends InvalidArgumentException { public static function create(string $permission) { - return new static("Wildcard permission `{$permission}` is not properly formatted."); + return new static(__('Wildcard permission `:permission` is not properly formatted.', [ + 'permission' => $permission, + ])); } } From db1184b113f37ba6639bd7fdefa1872d9b38a4e9 Mon Sep 17 00:00:00 2001 From: drbyte <404472+drbyte@users.noreply.github.com> Date: Tue, 3 Jun 2025 21:46:35 +0000 Subject: [PATCH 621/648] Fix styling --- src/Exceptions/UnauthorizedException.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Exceptions/UnauthorizedException.php b/src/Exceptions/UnauthorizedException.php index be5d47402..b9e9fe902 100644 --- a/src/Exceptions/UnauthorizedException.php +++ b/src/Exceptions/UnauthorizedException.php @@ -16,7 +16,7 @@ public static function forRoles(array $roles): self $message = __('User does not have the right roles.'); if (config('permission.display_role_in_exception')) { - $message .= ' ' . __('Necessary roles are :roles', ['roles' => implode(', ', $roles)]); + $message .= ' '.__('Necessary roles are :roles', ['roles' => implode(', ', $roles)]); } $exception = new static(403, $message, null, []); @@ -30,7 +30,7 @@ public static function forPermissions(array $permissions): self $message = __('User does not have the right permissions.'); if (config('permission.display_permission_in_exception')) { - $message .= ' ' . __('Necessary permissions are :permissions', ['permissions' => implode(', ', $permissions)]); + $message .= ' '.__('Necessary permissions are :permissions', ['permissions' => implode(', ', $permissions)]); } $exception = new static(403, $message, null, []); @@ -44,7 +44,7 @@ public static function forRolesOrPermissions(array $rolesOrPermissions): self $message = __('User does not have any of the necessary access rights.'); if (config('permission.display_permission_in_exception') && config('permission.display_role_in_exception')) { - $message .= ' ' . __('Necessary roles or permissions are :values', ['values' => implode(', ', $rolesOrPermissions)]); + $message .= ' '.__('Necessary roles or permissions are :values', ['values' => implode(', ', $rolesOrPermissions)]); } $exception = new static(403, $message, null, []); From 31c05679102c73f3b0d05790d2400182745a5615 Mon Sep 17 00:00:00 2001 From: Jimi Robaer Date: Thu, 5 Jun 2025 09:33:07 +0200 Subject: [PATCH 622/648] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 12bed721f..cf4e7e7f8 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ - Logo for laravel-permission + Logo for laravel-permission From 5b26459ea34d79de6a9b021e7cb3775d69cc2c5b Mon Sep 17 00:00:00 2001 From: drbyte <404472+drbyte@users.noreply.github.com> Date: Sat, 14 Jun 2025 01:22:04 +0000 Subject: [PATCH 623/648] Update CHANGELOG --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 56bd868eb..0005da4eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ All notable changes to `laravel-permission` will be documented in this file +## 6.20.0 - 2025-06-14 + +### What's Changed + +* Add translations support for exception messages by @nAa6666 in https://github.com/spatie/laravel-permission/pull/2852 + +### New Contributors + +* @nAa6666 made their first contribution in https://github.com/spatie/laravel-permission/pull/2852 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/6.19.0...6.20.0 + ## 6.19.0 - 2025-05-31 ### What's Changed @@ -1038,6 +1050,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` @@ -1118,6 +1131,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` From a5e2ab61e77f605129d35f72353eac92b91bb0d4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 05:20:45 +0000 Subject: [PATCH 624/648] Bump stefanzweifel/git-auto-commit-action from 5 to 6 Bumps [stefanzweifel/git-auto-commit-action](https://github.com/stefanzweifel/git-auto-commit-action) from 5 to 6. - [Release notes](https://github.com/stefanzweifel/git-auto-commit-action/releases) - [Changelog](https://github.com/stefanzweifel/git-auto-commit-action/blob/master/CHANGELOG.md) - [Commits](https://github.com/stefanzweifel/git-auto-commit-action/compare/v5...v6) --- updated-dependencies: - dependency-name: stefanzweifel/git-auto-commit-action dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/fix-php-code-style-issues.yml | 2 +- .github/workflows/update-changelog.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/fix-php-code-style-issues.yml b/.github/workflows/fix-php-code-style-issues.yml index 60dc90d6f..376238340 100644 --- a/.github/workflows/fix-php-code-style-issues.yml +++ b/.github/workflows/fix-php-code-style-issues.yml @@ -19,6 +19,6 @@ jobs: uses: aglipanci/laravel-pint-action@v2 - name: Commit changes - uses: stefanzweifel/git-auto-commit-action@v5 + uses: stefanzweifel/git-auto-commit-action@v6 with: commit_message: Fix styling diff --git a/.github/workflows/update-changelog.yml b/.github/workflows/update-changelog.yml index 0cdea2336..de5865b94 100644 --- a/.github/workflows/update-changelog.yml +++ b/.github/workflows/update-changelog.yml @@ -21,7 +21,7 @@ jobs: release-notes: ${{ github.event.release.body }} - name: Commit updated CHANGELOG - uses: stefanzweifel/git-auto-commit-action@v5 + uses: stefanzweifel/git-auto-commit-action@v6 with: branch: main commit_message: Update CHANGELOG From e71f6f32535c86166fcf511d87464c35c8cf50e3 Mon Sep 17 00:00:00 2001 From: TobMoeller Date: Mon, 7 Jul 2025 00:09:16 +0200 Subject: [PATCH 625/648] add option to remove multiple roles in one function call --- src/Events/RoleDetached.php | 4 ++-- src/Traits/HasRoles.php | 11 +++++----- tests/HasRolesTest.php | 41 +++++++++++++++++++++++++++++-------- 3 files changed, 41 insertions(+), 15 deletions(-) diff --git a/src/Events/RoleDetached.php b/src/Events/RoleDetached.php index 287995fe6..9a32518ab 100644 --- a/src/Events/RoleDetached.php +++ b/src/Events/RoleDetached.php @@ -18,8 +18,8 @@ class RoleDetached use SerializesModels; /** - * Internally the HasRoles trait passes $rolesOrIds as a single Eloquent record - * Theoretically one could register the event to other places with an array etc + * Internally the HasRoles trait passes an array of role ids (eg: int's or uuid's) + * Theoretically one could register the event to other places passing other types * So a Listener should inspect the type of $rolesOrIds received before using. * * @param array|int[]|string[]|Role|Role[]|Collection $rolesOrIds diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index d10dd77e3..38d2f02c0 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -193,13 +193,14 @@ function ($object) use ($roles, $model, $teamPivot, &$saved) { /** * Revoke the given role from the model. * - * @param string|int|Role|\BackedEnum $role + * @param string|int|array|Role|Collection|\BackedEnum ...$role + * @return $this */ - public function removeRole($role) + public function removeRole(...$role) { - $storedRole = $this->getStoredRole($role); + $roles = $this->collectRoles($role); - $this->roles()->detach($storedRole); + $this->roles()->detach($roles); $this->unsetRelation('roles'); @@ -208,7 +209,7 @@ public function removeRole($role) } if (config('permission.events_enabled')) { - event(new RoleDetached($this->getModel(), $storedRole)); + event(new RoleDetached($this->getModel(), $roles)); } return $this; diff --git a/tests/HasRolesTest.php b/tests/HasRolesTest.php index 2ead3982e..734394706 100644 --- a/tests/HasRolesTest.php +++ b/tests/HasRolesTest.php @@ -181,42 +181,62 @@ public function it_can_assign_and_remove_a_role_on_a_permission() /** @test */ #[Test] - public function it_can_assign_a_role_using_an_object() + public function it_can_assign_and_remove_a_role_using_an_object() { $this->testUser->assignRole($this->testUserRole); $this->assertTrue($this->testUser->hasRole($this->testUserRole)); + + $this->testUser->removeRole($this->testUserRole); + + $this->assertFalse($this->testUser->hasRole($this->testUserRole)); } /** @test */ #[Test] - public function it_can_assign_a_role_using_an_id() + public function it_can_assign_and_remove_a_role_using_an_id() { $this->testUser->assignRole($this->testUserRole->getKey()); $this->assertTrue($this->testUser->hasRole($this->testUserRole)); + + $this->testUser->removeRole($this->testUserRole->getKey()); + + $this->assertFalse($this->testUser->hasRole($this->testUserRole)); } /** @test */ #[Test] - public function it_can_assign_multiple_roles_at_once() + public function it_can_assign_and_remove_multiple_roles_at_once() { $this->testUser->assignRole($this->testUserRole->getKey(), 'testRole2'); $this->assertTrue($this->testUser->hasRole('testRole')); $this->assertTrue($this->testUser->hasRole('testRole2')); + + $this->testUser->removeRole($this->testUserRole->getKey(), 'testRole2'); + + $this->assertFalse($this->testUser->hasRole('testRole')); + + $this->assertFalse($this->testUser->hasRole('testRole2')); } /** @test */ #[Test] - public function it_can_assign_multiple_roles_using_an_array() + public function it_can_assign_and_remove_multiple_roles_using_an_array() { $this->testUser->assignRole([$this->testUserRole->getKey(), 'testRole2']); $this->assertTrue($this->testUser->hasRole('testRole')); $this->assertTrue($this->testUser->hasRole('testRole2')); + + $this->testUser->removeRole([$this->testUserRole->getKey(), 'testRole2']); + + $this->assertFalse($this->testUser->hasRole('testRole')); + + $this->assertFalse($this->testUser->hasRole('testRole2')); } /** @test */ @@ -973,14 +993,19 @@ public function it_fires_an_event_when_a_role_is_removed() Event::fake(); app('config')->set('permission.events_enabled', true); - $this->testUser->assignRole('testRole'); + $this->testUser->assignRole('testRole', 'testRole2'); - $this->testUser->removeRole('testRole'); + $this->testUser->removeRole('testRole', 'testRole2'); - Event::assertDispatched(RoleDetached::class, function ($event) { + $roleIds = app(Role::class)::whereIn('name', ['testRole', 'testRole2']) + ->pluck($this->testUserRole->getKeyName()) + ->toArray(); + + Event::assertDispatched(RoleDetached::class, function ($event) use ($roleIds) { return $event->model instanceof User && ! $event->model->hasRole('testRole') - && $event->rolesOrIds->name === 'testRole'; + && ! $event->model->hasRole('testRole2') + && $event->rolesOrIds === $roleIds; }); } From 52a2970793d6788b000261b6fbf284d0523bf6b2 Mon Sep 17 00:00:00 2001 From: dualklip Date: Thu, 17 Jul 2025 15:25:45 +0200 Subject: [PATCH 626/648] Correct middleware order for documentation example in `teams-permissions.md`. --- docs/basic-usage/teams-permissions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/basic-usage/teams-permissions.md b/docs/basic-usage/teams-permissions.md index 684770ded..868ae4281 100644 --- a/docs/basic-usage/teams-permissions.md +++ b/docs/basic-usage/teams-permissions.md @@ -77,8 +77,8 @@ class AppServiceProvider extends ServiceProvider $kernel = app()->make(Kernel::class); $kernel->addToMiddlewarePriorityBefore( - SubstituteBindings::class, YourCustomMiddlewareClass::class, + SubstituteBindings::class, ); } } From da53256252f71a1a42051382142ee930b07999f9 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Wed, 23 Jul 2025 07:37:53 -0400 Subject: [PATCH 627/648] Update direct-permissions.md --- docs/basic-usage/direct-permissions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/basic-usage/direct-permissions.md b/docs/basic-usage/direct-permissions.md index e171c8163..6b4987ec5 100644 --- a/docs/basic-usage/direct-permissions.md +++ b/docs/basic-usage/direct-permissions.md @@ -46,7 +46,7 @@ Like all permissions assigned via roles, you can check if a user has a permissio $user->can('edit articles'); ``` -> NOTE: The following `hasPermissionTo`, `hasAnyPermission`, `hasAllPermissions` functions do not support Super-Admin functionality. Use `can`, `canAny`, `canAll` instead. +> NOTE: The following `hasPermissionTo`, `hasAnyPermission`, `hasAllPermissions` functions do not support Super-Admin functionality. Use `can`, `canAny` instead. You can check if a user has a permission: From a028147780a2832aa7b3a6d6b522a61c87ea51fd Mon Sep 17 00:00:00 2001 From: drbyte <404472+drbyte@users.noreply.github.com> Date: Wed, 23 Jul 2025 16:13:20 +0000 Subject: [PATCH 628/648] Update CHANGELOG --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0005da4eb..b76054f36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,20 @@ All notable changes to `laravel-permission` will be documented in this file +## 6.21.0 - 2025-07-23 + +### What's Changed + +* Allow removing multiple roles with the `removeRole` method by @TobMoeller in https://github.com/spatie/laravel-permission/pull/2859 +* [Docs] Correct middleware order for documentation example in `teams-permissions.md` by @dualklip in https://github.com/spatie/laravel-permission/pull/2863 + +### New Contributors + +* @dualklip made their first contribution in https://github.com/spatie/laravel-permission/pull/2863 +* @TobMoeller made their first contribution in https://github.com/spatie/laravel-permission/pull/2859 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/6.20.0...6.21.0 + ## 6.20.0 - 2025-06-14 ### What's Changed @@ -1051,6 +1065,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` @@ -1132,6 +1147,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` From 7bd6e44f99809bfcb73db3ee7d019ccc6e2a2f34 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Thu, 31 Jul 2025 16:05:07 -0400 Subject: [PATCH 629/648] Add livewire middleware persistence doc link --- docs/basic-usage/teams-permissions.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/basic-usage/teams-permissions.md b/docs/basic-usage/teams-permissions.md index 868ae4281..15095a7ab 100644 --- a/docs/basic-usage/teams-permissions.md +++ b/docs/basic-usage/teams-permissions.md @@ -83,6 +83,9 @@ class AppServiceProvider extends ServiceProvider } } ``` +### Using LiveWire? + +You may need to register your team middleware as Persisted in Livewire. See [Livewire docs: Configuring Persistent Middleware](https://livewire.laravel.com/docs/security#configuring-persistent-middleware) ## Roles Creating From ae68164eba5f09119d165f367052c026e6bd1734 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 1 Aug 2025 03:12:39 -0400 Subject: [PATCH 630/648] Update tests badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cf4e7e7f8..413063953 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@

Associate users with permissions and roles

[![Latest Version on Packagist](https://img.shields.io/packagist/v/spatie/laravel-permission.svg?style=flat-square)](https://packagist.org/packages/spatie/laravel-permission) -[![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/spatie/laravel-permission/run-tests-L8.yml?branch=main&label=Tests)](https://github.com/spatie/laravel-permission/actions?query=workflow%3ATests+branch%3Amain) +[![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/spatie/laravel-permission/run-tests.yml?branch=main&label=Tests)](https://github.com/spatie/laravel-permission/actions?query=workflow%3ATests+branch%3Amain) [![Total Downloads](https://img.shields.io/packagist/dt/spatie/laravel-permission.svg?style=flat-square)](https://packagist.org/packages/spatie/laravel-permission) From a4316d6e01377bf29ac4c382eafa3f0492bcabd0 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 8 Aug 2025 18:14:38 -0400 Subject: [PATCH 631/648] Add roave/security-advisories --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index c04c5bff9..c83d07b2e 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,8 @@ "laravel/passport": "^11.0|^12.0", "laravel/pint": "^1.0", "orchestra/testbench": "^6.23|^7.0|^8.0|^9.0|^10.0", - "phpunit/phpunit": "^9.4|^10.1|^11.5" + "phpunit/phpunit": "^9.4|^10.1|^11.5", + "roave/security-advisories": "dev-latest" }, "minimum-stability": "dev", "prefer-stable": true, From 0b9284c00640febe7a7d01471c5a145f543b65f0 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 8 Aug 2025 21:05:22 -0400 Subject: [PATCH 632/648] Revert "Add roave/security-advisories" This reverts commit a4316d6e01377bf29ac4c382eafa3f0492bcabd0. --- composer.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/composer.json b/composer.json index c83d07b2e..c04c5bff9 100644 --- a/composer.json +++ b/composer.json @@ -32,8 +32,7 @@ "laravel/passport": "^11.0|^12.0", "laravel/pint": "^1.0", "orchestra/testbench": "^6.23|^7.0|^8.0|^9.0|^10.0", - "phpunit/phpunit": "^9.4|^10.1|^11.5", - "roave/security-advisories": "dev-latest" + "phpunit/phpunit": "^9.4|^10.1|^11.5" }, "minimum-stability": "dev", "prefer-stable": true, From c3edf29c4d0fb40b12e3f65e0ffa16026ce757c1 Mon Sep 17 00:00:00 2001 From: josedaian Date: Sun, 17 Aug 2025 11:22:21 -0300 Subject: [PATCH 633/648] feat: dispatch RoleDetached on syncRoles when events are enabled --- src/Traits/HasRoles.php | 11 +++++++++-- tests/HasRolesTest.php | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php index 38d2f02c0..40839e94c 100644 --- a/src/Traits/HasRoles.php +++ b/src/Traits/HasRoles.php @@ -225,8 +225,15 @@ public function syncRoles(...$roles) { if ($this->getModel()->exists) { $this->collectRoles($roles); - $this->roles()->detach(); - $this->setRelation('roles', collect()); + if (config('permission.events_enabled')) { + $currentRoles = $this->roles()->get(); + if ($currentRoles->isNotEmpty()) { + $this->removeRole($currentRoles); + } + } else { + $this->roles()->detach(); + $this->setRelation('roles', collect()); + } } return $this->assignRole($roles); diff --git a/tests/HasRolesTest.php b/tests/HasRolesTest.php index 734394706..cf49e3dd6 100644 --- a/tests/HasRolesTest.php +++ b/tests/HasRolesTest.php @@ -1042,4 +1042,43 @@ public function it_can_be_given_a_role_on_user_when_lazy_loading_is_restricted() $this->fail('Lazy loading detected in the givePermissionTo method: '.$e->getMessage()); } } + + /** @test */ + #[Test] + public function it_fires_detach_event_when_syncing_roles() + { + Event::fake([RoleDetached::class, RoleAttached::class]); + app('config')->set('permission.events_enabled', true); + + $this->testUser->assignRole('testRole', 'testRole2'); + + app(Role::class)->create(['name' => 'testRole3']); + + $this->testUser->syncRoles('testRole3'); + + $this->assertFalse($this->testUser->hasRole('testRole')); + $this->assertFalse($this->testUser->hasRole('testRole2')); + $this->assertTrue($this->testUser->hasRole('testRole3')); + + $removedRoleIds = app(Role::class)::whereIn('name', ['testRole', 'testRole2']) + ->pluck($this->testUserRole->getKeyName()) + ->toArray(); + + Event::assertDispatched(RoleDetached::class, function ($event) use ($removedRoleIds) { + return $event->model instanceof User + && ! $event->model->hasRole('testRole') + && ! $event->model->hasRole('testRole2') + && $event->rolesOrIds === $removedRoleIds; + }); + + $attachedRoleIds = app(Role::class)::whereIn('name', ['testRole3']) + ->pluck($this->testUserRole->getKeyName()) + ->toArray(); + + Event::assertDispatched(RoleAttached::class, function ($event) use ($attachedRoleIds) { + return $event->model instanceof User + && $event->model->hasRole('testRole3') + && $event->rolesOrIds === $attachedRoleIds; + }); + } } From 041c43beab5cf6783a07e302d178c5f2c7bcc287 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Aug 2025 07:29:50 +0000 Subject: [PATCH 634/648] Bump actions/checkout from 4 to 5 Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/fix-php-code-style-issues.yml | 2 +- .github/workflows/phpstan.yml | 2 +- .github/workflows/run-tests.yml | 2 +- .github/workflows/test-cache-drivers.yml | 2 +- .github/workflows/update-changelog.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/fix-php-code-style-issues.yml b/.github/workflows/fix-php-code-style-issues.yml index 376238340..26b2a4f84 100644 --- a/.github/workflows/fix-php-code-style-issues.yml +++ b/.github/workflows/fix-php-code-style-issues.yml @@ -11,7 +11,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: ref: ${{ github.head_ref }} diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index be7882397..17bbcdf3f 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -15,7 +15,7 @@ jobs: name: phpstan runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup PHP uses: shivammathur/setup-php@v2 diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 3f47884ba..869f05809 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -43,7 +43,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup PHP uses: shivammathur/setup-php@v2 diff --git a/.github/workflows/test-cache-drivers.yml b/.github/workflows/test-cache-drivers.yml index 2bfd5b28a..3b9398c9c 100644 --- a/.github/workflows/test-cache-drivers.yml +++ b/.github/workflows/test-cache-drivers.yml @@ -21,7 +21,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup PHP uses: shivammathur/setup-php@v2 diff --git a/.github/workflows/update-changelog.yml b/.github/workflows/update-changelog.yml index de5865b94..da74ce831 100644 --- a/.github/workflows/update-changelog.yml +++ b/.github/workflows/update-changelog.yml @@ -10,7 +10,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: ref: main From f6ea7b3e1c40e8724425c61cd6585ae28c862cd6 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Sat, 23 Aug 2025 11:23:05 -0400 Subject: [PATCH 635/648] Indicate 11+ --- docs/basic-usage/middleware.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/basic-usage/middleware.md b/docs/basic-usage/middleware.md index 259e9214f..e9173ec59 100644 --- a/docs/basic-usage/middleware.md +++ b/docs/basic-usage/middleware.md @@ -22,7 +22,7 @@ This package comes with `RoleMiddleware`, `PermissionMiddleware` and `RoleOrPerm You can register their aliases for easy reference elsewhere in your app: -In Laravel 11 open `/bootstrap/app.php` and register them there: +In Laravel 11+ open `/bootstrap/app.php` and register them there: ```php ->withMiddleware(function (Middleware $middleware) { From ba8c37a1f590d526f2cc6a133e698e972892039b Mon Sep 17 00:00:00 2001 From: Alex Vanderbist Date: Mon, 8 Sep 2025 08:51:35 +0200 Subject: [PATCH 636/648] Update issue template --- .github/ISSUE_TEMPLATE/config.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 5940c1979..8c6da4eb4 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,8 +1,8 @@ -blank_issues_enabled: false +blank_issues_enabled: true contact_links: - - name: Ask a Question - url: https://github.com/spatie/laravel-permission/discussions/new?category=q-a - about: Ask the community for help - name: Feature Request url: https://github.com/spatie/laravel-permission/discussions/new?category=ideas about: Share ideas for new features + - name: Ask a Question + url: https://github.com/spatie/laravel-permission/discussions/new?category=q-a + about: Ask the community for help From 115f5267b45f7654487de26b6d383de21c179d1a Mon Sep 17 00:00:00 2001 From: erikn69 Date: Wed, 1 Oct 2025 16:31:27 -0500 Subject: [PATCH 637/648] Test PHP 8.5 --- .github/workflows/run-tests.yml | 8 +++++++- tests/TestCase.php | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 869f05809..2dd07ca39 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -9,7 +9,7 @@ jobs: strategy: fail-fast: false matrix: - php: [8.4, 8.3, 8.2, 8.1, 8.0] + php: [8.5, 8.4, 8.3, 8.2, 8.1, 8.0] laravel: ["^12.0", "^11.0", "^10.0", "^9.0", "^8.12"] dependency-version: [prefer-lowest, prefer-stable] include: @@ -34,10 +34,16 @@ jobs: php: 8.0 - laravel: "^10.0" php: 8.0 + - laravel: "^10.0" + php: 8.5 + - laravel: "^9.0" + php: 8.5 - laravel: "^8.12" php: 8.3 - laravel: "^8.12" php: 8.4 + - laravel: "^8.12" + php: 8.5 name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }} diff --git a/tests/TestCase.php b/tests/TestCase.php index 45831ce15..ebd66f3cc 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -230,7 +230,7 @@ protected function setUpPassport($app): void $app['config']->set('auth.guards.api', ['driver' => 'passport', 'provider' => 'users']); // mimic passport:install (must load migrations using our own call to loadMigrationsFrom() else rollbacks won't occur, and migrations will be left in skeleton directory - $this->artisan('passport:keys'); + //$this->artisan('passport:keys'); $this->loadMigrationsFrom(__DIR__.'/../vendor/laravel/passport/database/migrations/'); $provider = in_array('users', array_keys(config('auth.providers'))) ? 'users' : null; $this->artisan('passport:client', ['--personal' => true, '--name' => config('app.name').' Personal Access Client']); From 8a795bab595607d8c525eb04615ae88bbcf26616 Mon Sep 17 00:00:00 2001 From: drbyte <404472+drbyte@users.noreply.github.com> Date: Thu, 2 Oct 2025 19:19:17 +0000 Subject: [PATCH 638/648] Fix styling --- tests/TestCase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/TestCase.php b/tests/TestCase.php index ebd66f3cc..cc243faa1 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -230,7 +230,7 @@ protected function setUpPassport($app): void $app['config']->set('auth.guards.api', ['driver' => 'passport', 'provider' => 'users']); // mimic passport:install (must load migrations using our own call to loadMigrationsFrom() else rollbacks won't occur, and migrations will be left in skeleton directory - //$this->artisan('passport:keys'); + // $this->artisan('passport:keys'); $this->loadMigrationsFrom(__DIR__.'/../vendor/laravel/passport/database/migrations/'); $provider = in_array('users', array_keys(config('auth.providers'))) ? 'users' : null; $this->artisan('passport:client', ['--personal' => true, '--name' => config('app.name').' Personal Access Client']); From 69062a9a51bf1620584e50c8d1c99434b25b8a1e Mon Sep 17 00:00:00 2001 From: Ali Qasemzadeh Date: Sun, 12 Oct 2025 08:31:53 +0330 Subject: [PATCH 639/648] Add QuickPanel link to UI options documentation --- docs/advanced-usage/ui-options.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/advanced-usage/ui-options.md b/docs/advanced-usage/ui-options.md index ebfb552ef..4e1d5d41d 100644 --- a/docs/advanced-usage/ui-options.md +++ b/docs/advanced-usage/ui-options.md @@ -31,3 +31,5 @@ If you decide you need a UI, even if it's not for creating/editing role/permissi - [LiveWire Base Admin Panel](https://github.com/aliqasemzadeh/bap) User management by [AliQasemzadeh](https://github.com/aliqasemzadeh) - [JetAdmin](https://github.com/aliqasemzadeh/jetadmin) JetAdmin use laravel livewire starter kit and manage permissions. [AliQasemzadeh](https://github.com/aliqasemzadeh) + +- [QuickPanel](https://github.com/aliqasemzadeh/quickpanel) Quick Panel (TALL Flowbite Starter Kit). [AliQasemzadeh](https://github.com/aliqasemzadeh) From 691ae1050d8cf4e8eee3de294563110529ab2c7c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 05:08:33 +0000 Subject: [PATCH 640/648] Bump stefanzweifel/git-auto-commit-action from 6 to 7 Bumps [stefanzweifel/git-auto-commit-action](https://github.com/stefanzweifel/git-auto-commit-action) from 6 to 7. - [Release notes](https://github.com/stefanzweifel/git-auto-commit-action/releases) - [Changelog](https://github.com/stefanzweifel/git-auto-commit-action/blob/master/CHANGELOG.md) - [Commits](https://github.com/stefanzweifel/git-auto-commit-action/compare/v6...v7) --- updated-dependencies: - dependency-name: stefanzweifel/git-auto-commit-action dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/fix-php-code-style-issues.yml | 2 +- .github/workflows/update-changelog.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/fix-php-code-style-issues.yml b/.github/workflows/fix-php-code-style-issues.yml index 26b2a4f84..e3be7f171 100644 --- a/.github/workflows/fix-php-code-style-issues.yml +++ b/.github/workflows/fix-php-code-style-issues.yml @@ -19,6 +19,6 @@ jobs: uses: aglipanci/laravel-pint-action@v2 - name: Commit changes - uses: stefanzweifel/git-auto-commit-action@v6 + uses: stefanzweifel/git-auto-commit-action@v7 with: commit_message: Fix styling diff --git a/.github/workflows/update-changelog.yml b/.github/workflows/update-changelog.yml index da74ce831..cb7fa9f8b 100644 --- a/.github/workflows/update-changelog.yml +++ b/.github/workflows/update-changelog.yml @@ -21,7 +21,7 @@ jobs: release-notes: ${{ github.event.release.body }} - name: Commit updated CHANGELOG - uses: stefanzweifel/git-auto-commit-action@v6 + uses: stefanzweifel/git-auto-commit-action@v7 with: branch: main commit_message: Update CHANGELOG From 76a6dd258733f19a6f1c6355252f3c0abba003d1 Mon Sep 17 00:00:00 2001 From: Hayatunnabi Nabil Date: Mon, 13 Oct 2025 17:14:31 +0600 Subject: [PATCH 641/648] fix:Critical race condition in permission loading for concurrent environments --- src/PermissionRegistrar.php | 49 +++++++++++++++++++++++++++++++------ 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index 92e6edc4e..eaf9733e8 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -49,6 +49,8 @@ class PermissionRegistrar private array $wildcardPermissionsIndex = []; + private bool $isLoadingPermissions = false; + /** * PermissionRegistrar constructor. */ @@ -172,6 +174,7 @@ public function clearPermissionsCollection(): void { $this->permissions = null; $this->wildcardPermissionsIndex = []; + $this->isLoadingPermissions = false; } /** @@ -187,24 +190,56 @@ public function clearClassPermissions() /** * Load permissions from cache * And turns permissions array into a \Illuminate\Database\Eloquent\Collection + * + * Thread-safe implementation to prevent race conditions in concurrent environments + * (e.g., Laravel Octane, Swoole, parallel requests) */ private function loadPermissions(): void { + // First check (without lock) - fast path for already loaded permissions if ($this->permissions) { return; } - $this->permissions = $this->cache->remember( - $this->cacheKey, $this->cacheExpirationTime, fn () => $this->getSerializedPermissionsForCache() - ); + // Prevent concurrent loading using a flag-based lock + // This protects against cache stampede and duplicate database queries + if ($this->isLoadingPermissions) { + // Another thread is loading, wait and retry + usleep(10000); // Wait 10ms + + // Recursive retry with loaded permissions check + if ($this->permissions) { + return; + } - $this->alias = $this->permissions['alias']; + // If still not loaded after wait, proceed normally + // The cache->remember() will handle cache-level locking + } - $this->hydrateRolesCache(); + // Set loading flag to prevent concurrent loads + $this->isLoadingPermissions = true; - $this->permissions = $this->getHydratedPermissionCollection(); + try { + // Double-check after acquiring lock + if ($this->permissions) { + return; + } - $this->cachedRoles = $this->alias = $this->except = []; + $this->permissions = $this->cache->remember( + $this->cacheKey, $this->cacheExpirationTime, fn () => $this->getSerializedPermissionsForCache() + ); + + $this->alias = $this->permissions['alias']; + + $this->hydrateRolesCache(); + + $this->permissions = $this->getHydratedPermissionCollection(); + + $this->cachedRoles = $this->alias = $this->except = []; + } finally { + // Always release the loading flag, even if an exception occurs + $this->isLoadingPermissions = false; + } } /** From a1a2d745e91dd5674dfa5e00b4eca9530785c763 Mon Sep 17 00:00:00 2001 From: Hayatunnabi Nabil Date: Tue, 14 Oct 2025 10:34:42 +0600 Subject: [PATCH 642/648] refactor: Simplify permission loading logic to enhance concurrency handling --- src/PermissionRegistrar.php | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index eaf9733e8..a703e1c04 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -207,24 +207,16 @@ private function loadPermissions(): void // Another thread is loading, wait and retry usleep(10000); // Wait 10ms - // Recursive retry with loaded permissions check - if ($this->permissions) { - return; - } + // After wait, recursively check again if permissions were loaded + $this->loadPermissions(); - // If still not loaded after wait, proceed normally - // The cache->remember() will handle cache-level locking + return; } // Set loading flag to prevent concurrent loads $this->isLoadingPermissions = true; try { - // Double-check after acquiring lock - if ($this->permissions) { - return; - } - $this->permissions = $this->cache->remember( $this->cacheKey, $this->cacheExpirationTime, fn () => $this->getSerializedPermissionsForCache() ); From c9bd50dee6db12f2fb7ae9d13ce547d64d17d3e7 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Wed, 22 Oct 2025 11:33:45 -0400 Subject: [PATCH 643/648] Updated test names --- tests/CommandTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/CommandTest.php b/tests/CommandTest.php index a808aa5ee..87657d0dd 100644 --- a/tests/CommandTest.php +++ b/tests/CommandTest.php @@ -252,7 +252,7 @@ public function it_can_respond_to_about_command_with_teams() /** @test */ #[Test] - public function test_is_assign_role_to_user() + public function it_can_assign_role_to_user() { $user = User::first(); @@ -273,7 +273,7 @@ public function test_is_assign_role_to_user() /** @test */ #[Test] - public function test_assigning_role_fails_when_user_not_found() + public function it_fails_to_assign_role_when_user_not_found() { Artisan::call('permission:assign-role', [ @@ -290,7 +290,7 @@ public function test_assigning_role_fails_when_user_not_found() /** @test */ #[Test] - public function test_assigning_role_fails_when_namspace_not_existed() + public function it_fails_to_assign_role_when_namespace_invalid() { $user = User::first(); From 93dc11518609a105d1511186b26c0926c8612f9b Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Wed, 22 Oct 2025 12:05:44 -0400 Subject: [PATCH 644/648] Update description --- src/Commands/AssignRole.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Commands/AssignRole.php b/src/Commands/AssignRole.php index 108114b8e..8c56b4bd0 100644 --- a/src/Commands/AssignRole.php +++ b/src/Commands/AssignRole.php @@ -13,7 +13,7 @@ class AssignRole extends Command {guard? : The name of the guard} {userModelNamespace=App\Models\User : The fully qualified class name of the user model}'; - protected $description = 'Assign a role to a user'; + protected $description = 'Assign a role to a user. (Note: does not support Teams.)'; public function handle() { From 3e4474d50f500fd79ce47c88e1bb5cee0c0d5577 Mon Sep 17 00:00:00 2001 From: Hayatunnabi Nabil Date: Thu, 23 Oct 2025 18:24:51 +0600 Subject: [PATCH 645/648] ref: enhance permission loading with retry mechanism for improved concurrency --- src/PermissionRegistrar.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/PermissionRegistrar.php b/src/PermissionRegistrar.php index a703e1c04..2f9479eb8 100644 --- a/src/PermissionRegistrar.php +++ b/src/PermissionRegistrar.php @@ -194,7 +194,7 @@ public function clearClassPermissions() * Thread-safe implementation to prevent race conditions in concurrent environments * (e.g., Laravel Octane, Swoole, parallel requests) */ - private function loadPermissions(): void + private function loadPermissions(int $retries = 0): void { // First check (without lock) - fast path for already loaded permissions if ($this->permissions) { @@ -203,12 +203,13 @@ private function loadPermissions(): void // Prevent concurrent loading using a flag-based lock // This protects against cache stampede and duplicate database queries - if ($this->isLoadingPermissions) { + if ($this->isLoadingPermissions && $retries < 10) { // Another thread is loading, wait and retry usleep(10000); // Wait 10ms + $retries++; // After wait, recursively check again if permissions were loaded - $this->loadPermissions(); + $this->loadPermissions($retries); return; } From 2aa76718fc79294eee41b97f01dc2a11a6df25b3 Mon Sep 17 00:00:00 2001 From: Ali Salehi <111766206+alisalehi1380@users.noreply.github.com> Date: Fri, 24 Oct 2025 17:27:03 +0330 Subject: [PATCH 646/648] Refactor exception handling in migration stub --- database/migrations/create_permission_tables.php.stub | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/database/migrations/create_permission_tables.php.stub b/database/migrations/create_permission_tables.php.stub index ce4d9d2d4..a2916aca4 100644 --- a/database/migrations/create_permission_tables.php.stub +++ b/database/migrations/create_permission_tables.php.stub @@ -17,8 +17,8 @@ return new class extends Migration $pivotRole = $columnNames['role_pivot_key'] ?? 'role_id'; $pivotPermission = $columnNames['permission_pivot_key'] ?? 'permission_id'; - throw_if(empty($tableNames), new Exception('Error: config/permission.php not loaded. Run [php artisan config:clear] and try again.')); - throw_if($teams && empty($columnNames['team_foreign_key'] ?? null), new Exception('Error: team_foreign_key on config/permission.php not loaded. Run [php artisan config:clear] and try again.')); + throw_if(empty($tableNames), Exception::class, 'Error: config/permission.php not loaded. Run [php artisan config:clear] and try again.'); + throw_if($teams && empty($columnNames['team_foreign_key'] ?? null), Exception::class, 'Error: team_foreign_key on config/permission.php not loaded. Run [php artisan config:clear] and try again.'); Schema::create($tableNames['permissions'], static function (Blueprint $table) { // $table->engine('InnoDB'); From d81d9ce332042b4c8e30866add048e487c3db268 Mon Sep 17 00:00:00 2001 From: Ali Salehi <111766206+alisalehi1380@users.noreply.github.com> Date: Fri, 24 Oct 2025 20:17:47 +0330 Subject: [PATCH 647/648] Update create_permission_tables.php.stub --- database/migrations/create_permission_tables.php.stub | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/database/migrations/create_permission_tables.php.stub b/database/migrations/create_permission_tables.php.stub index a2916aca4..66ce1f970 100644 --- a/database/migrations/create_permission_tables.php.stub +++ b/database/migrations/create_permission_tables.php.stub @@ -123,9 +123,7 @@ return new class extends Migration { $tableNames = config('permission.table_names'); - if (empty($tableNames)) { - throw new \Exception('Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.'); - } + throw_if(empty($tableNames), Exception::class, 'Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.'); Schema::drop($tableNames['role_has_permissions']); Schema::drop($tableNames['model_has_roles']); From ea7d29cf733705eba94b002462e42c6ff5585f4c Mon Sep 17 00:00:00 2001 From: drbyte <404472+drbyte@users.noreply.github.com> Date: Mon, 27 Oct 2025 22:20:22 +0000 Subject: [PATCH 648/648] Update CHANGELOG --- CHANGELOG.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b76054f36..8ce2c57ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,29 @@ All notable changes to `laravel-permission` will be documented in this file +## 6.22.0 - 2025-10-27 + +### What's Changed + +* Dispatch RoleDetached on syncRoles when events are enabled by @josedaian in https://github.com/spatie/laravel-permission/pull/2869 +* Refactor exception handling in migration stub by @alisalehi1380 in https://github.com/spatie/laravel-permission/pull/2886 +* Fix TOCTOU race condition in permission loading for concurrent (Octane etc) environments by @imhayatunnabi in https://github.com/spatie/laravel-permission/pull/2883 +* Add assign-role command by @sediqzada94 in https://github.com/spatie/laravel-permission/pull/2834 +* Test PHP 8.5 by @erikn69 in https://github.com/spatie/laravel-permission/pull/2880 +* Bump stefanzweifel/git-auto-commit-action from 6 to 7 by @dependabot[bot] in https://github.com/spatie/laravel-permission/pull/2882 +* Update issue template by @AlexVanderbist in https://github.com/spatie/laravel-permission/pull/2875 +* Bump actions/checkout from 4 to 5 by @dependabot[bot] in https://github.com/spatie/laravel-permission/pull/2870 +* Quick Panel (TALL Flowbite Starter Kit) by @aliqasemzadeh in https://github.com/spatie/laravel-permission/pull/2881 + +### New Contributors + +* @josedaian made their first contribution in https://github.com/spatie/laravel-permission/pull/2869 +* @alisalehi1380 made their first contribution in https://github.com/spatie/laravel-permission/pull/2886 +* @imhayatunnabi made their first contribution in https://github.com/spatie/laravel-permission/pull/2883 +* @sediqzada94 made their first contribution in https://github.com/spatie/laravel-permission/pull/2834 + +**Full Changelog**: https://github.com/spatie/laravel-permission/compare/6.21.0...6.22.0 + ## 6.21.0 - 2025-07-23 ### What's Changed @@ -1066,6 +1089,7 @@ The following changes are not "breaking", but worth making the updates to your a + ``` @@ -1148,6 +1172,7 @@ The following changes are not "breaking", but worth making the updates to your a + ```