glastree_on_gitea
This commit is contained in:
+21
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) Spatie bvba <info@spatie.be>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
<div align="left">
|
||||
<a href="https://spatie.be/open-source?utm_source=github&utm_medium=banner&utm_campaign=laravel-permission">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://spatie.be/packages/header/laravel-permission/html/dark.webp">
|
||||
<img alt="Logo for laravel-permission" src="https://spatie.be/packages/header/laravel-permission/html/light.webp">
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
<h1>Associate users with permissions and roles</h1>
|
||||
|
||||
[](https://packagist.org/packages/spatie/laravel-permission)
|
||||
[](https://github.com/spatie/laravel-permission/actions?query=workflow%3ATests+branch%3Amain)
|
||||
[](https://packagist.org/packages/spatie/laravel-permission)
|
||||
|
||||
</div>
|
||||
|
||||
## Documentation, Installation, and Usage Instructions
|
||||
|
||||
See the [documentation](https://spatie.be/docs/laravel-permission/) for detailed instructions for how-to-use, as well as installation and upgrade guidance.
|
||||
|
||||
## What It Does
|
||||
This package allows you to manage user permissions and roles in a database.
|
||||
|
||||
Once installed you can do stuff like this:
|
||||
|
||||
```php
|
||||
// Adding permissions to a user
|
||||
$user->givePermissionTo('edit articles');
|
||||
|
||||
// Adding permissions via a role
|
||||
$user->assignRole('writer');
|
||||
|
||||
$role->givePermissionTo('edit articles');
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```php
|
||||
$user->can('edit articles');
|
||||
```
|
||||
|
||||
## Support us
|
||||
|
||||
[<img src="https://github-ads.s3.eu-central-1.amazonaws.com/laravel-permission.jpg?t=1" width="419px" />](https://spatie.be/github-ad-click/laravel-permission)
|
||||
|
||||
We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can support us by [buying one of our paid products](https://spatie.be/open-source/support-us).
|
||||
|
||||
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).
|
||||
|
||||
## Changelog
|
||||
|
||||
Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently.
|
||||
|
||||
## Contributing
|
||||
|
||||
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.
|
||||
|
||||
## Postcardware
|
||||
|
||||
You're free to use this package, but if it makes it to your production environment we highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using.
|
||||
|
||||
Our address is: Spatie, Kruikstraat 22, 2018 Antwerp, Belgium.
|
||||
|
||||
We publish all received postcards [on our company website](https://spatie.be/en/opensource/postcards).
|
||||
|
||||
## Credits
|
||||
|
||||
- [Chris Brown](https://github.com/drbyte)
|
||||
- [Freek Van der Herten](https://github.com/freekmurze)
|
||||
- [All Contributors](../../contributors)
|
||||
|
||||
This package is heavily based on [Jeffrey Way](https://twitter.com/jeffrey_way)'s awesome [Laracasts](https://laracasts.com) lessons
|
||||
on [permissions and roles](https://laracasts.com/series/whats-new-in-laravel-5-1/episodes/16). His original code
|
||||
can be found [in this repo on GitHub](https://github.com/laracasts/laravel-5-roles-and-permissions-demo).
|
||||
|
||||
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.
|
||||
|
||||
Special thanks to [Caneco](https://twitter.com/caneco) for the original 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.
|
||||
- [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
|
||||
|
||||
The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"name": "spatie/laravel-permission",
|
||||
"description": "Permission handling for Laravel 12 and up",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"spatie",
|
||||
"laravel",
|
||||
"permission",
|
||||
"permissions",
|
||||
"roles",
|
||||
"acl",
|
||||
"rbac",
|
||||
"security"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Freek Van der Herten",
|
||||
"email": "freek@spatie.be",
|
||||
"homepage": "https://spatie.be",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"homepage": "https://github.com/spatie/laravel-permission",
|
||||
"require": {
|
||||
"php": "^8.3",
|
||||
"illuminate/auth": "^12.0|^13.0",
|
||||
"illuminate/container": "^12.0|^13.0",
|
||||
"illuminate/contracts": "^12.0|^13.0",
|
||||
"illuminate/database": "^12.0|^13.0",
|
||||
"spatie/laravel-package-tools": "^1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"larastan/larastan": "^3.9",
|
||||
"laravel/passport": "^13.0",
|
||||
"laravel/pint": "^1.0",
|
||||
"orchestra/testbench": "^10.0|^11.0",
|
||||
"pestphp/pest": "^3.0|^4.0",
|
||||
"pestphp/pest-plugin-laravel": "^3.0|^4.1",
|
||||
"phpstan/phpstan": "^2.1"
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true,
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Spatie\\Permission\\": "src"
|
||||
},
|
||||
"files": [
|
||||
"src/helpers.php"
|
||||
]
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Spatie\\Permission\\Tests\\": "tests"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true,
|
||||
"allow-plugins": {
|
||||
"pestphp/pest-plugin": true
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "7.x-dev",
|
||||
"dev-master": "7.x-dev"
|
||||
},
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"Spatie\\Permission\\PermissionServiceProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"test": "pest",
|
||||
"format": "pint",
|
||||
"analyse": "echo 'Checking dependencies...' && composer require --dev larastan/larastan && phpstan analyse"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
<?php
|
||||
|
||||
use Spatie\Permission\DefaultTeamResolver;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
return [
|
||||
|
||||
'models' => [
|
||||
|
||||
/*
|
||||
* 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' => 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' => Role::class,
|
||||
|
||||
/*
|
||||
* When using the "Teams" feature from this package, we need to know which
|
||||
* Eloquent model should be used to retrieve your teams. Of course, it
|
||||
* is often just the "Team" model but you may use whatever you like.
|
||||
*/
|
||||
'team' => null,
|
||||
|
||||
/*
|
||||
* When using the "HasModels" trait and passing raw IDs to syncModels,
|
||||
* attachModels, or detachModels, this model class will be used to
|
||||
* resolve those IDs. If null, defaults to the guard's model.
|
||||
*/
|
||||
'default_model' => null,
|
||||
],
|
||||
|
||||
'table_names' => [
|
||||
|
||||
/*
|
||||
* When using the "HasRoles" trait from this package, we need to know which
|
||||
* table should be used to retrieve your roles. We have chosen a basic
|
||||
* default value but you may easily change it to any table you like.
|
||||
*/
|
||||
|
||||
'roles' => 'roles',
|
||||
|
||||
/*
|
||||
* When using the "HasPermissions" trait from this package, we need to know which
|
||||
* table should be used to retrieve your permissions. We have chosen a basic
|
||||
* default value but you may easily change it to any table you like.
|
||||
*/
|
||||
|
||||
'permissions' => 'permissions',
|
||||
|
||||
/*
|
||||
* When using the "HasPermissions" trait from this package, we need to know which
|
||||
* table should be used to retrieve your models permissions. We have chosen a
|
||||
* basic default value but you may easily change it to any table you like.
|
||||
*/
|
||||
|
||||
'model_has_permissions' => 'model_has_permissions',
|
||||
|
||||
/*
|
||||
* When using the "HasRoles" trait from this package, we need to know which
|
||||
* table should be used to retrieve your models roles. We have chosen a
|
||||
* basic default value but you may easily change it to any table you like.
|
||||
*/
|
||||
|
||||
'model_has_roles' => 'model_has_roles',
|
||||
|
||||
/*
|
||||
* When using the "HasRoles" trait from this package, we need to know which
|
||||
* table should be used to retrieve your roles permissions. We have chosen a
|
||||
* basic default value but you may easily change it to any table you like.
|
||||
*/
|
||||
|
||||
'role_has_permissions' => 'role_has_permissions',
|
||||
],
|
||||
|
||||
'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
|
||||
* `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 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 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, 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,
|
||||
|
||||
/*
|
||||
* Events will fire when a role or permission is assigned/unassigned:
|
||||
* \Spatie\Permission\Events\RoleAttachedEvent
|
||||
* \Spatie\Permission\Events\RoleDetachedEvent
|
||||
* \Spatie\Permission\Events\PermissionAttachedEvent
|
||||
* \Spatie\Permission\Events\PermissionDetachedEvent
|
||||
*
|
||||
* 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'.
|
||||
* 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,
|
||||
|
||||
/*
|
||||
* The class to use to resolve the permissions team id
|
||||
*/
|
||||
'team_resolver' => DefaultTeamResolver::class,
|
||||
|
||||
/*
|
||||
* 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
|
||||
* setting is false here for optimum safety.
|
||||
*/
|
||||
|
||||
'display_permission_in_exception' => false,
|
||||
|
||||
/*
|
||||
* 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,
|
||||
|
||||
/*
|
||||
* The class to use for interpreting wildcard permissions.
|
||||
* If you need to modify delimiters, override the class and specify its name here.
|
||||
*/
|
||||
// 'wildcard_permission' => Spatie\Permission\WildcardPermission::class,
|
||||
|
||||
/* Cache-specific settings */
|
||||
|
||||
'cache' => [
|
||||
|
||||
/*
|
||||
* By default all permissions are cached for 24 hours to speed up performance.
|
||||
* When permissions or roles are updated the cache is flushed automatically.
|
||||
*/
|
||||
|
||||
'expiration_time' => DateInterval::createFromDateString('24 hours'),
|
||||
|
||||
/*
|
||||
* The cache key used to store all permissions.
|
||||
*/
|
||||
|
||||
'key' => 'spatie.permission.cache',
|
||||
|
||||
/*
|
||||
* 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
|
||||
* file. Using 'default' here means to use the `default` set in cache.php.
|
||||
*/
|
||||
|
||||
'store' => 'default',
|
||||
],
|
||||
];
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$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;
|
||||
}
|
||||
|
||||
throw_if(empty($tableNames), 'Error: config/permission.php not loaded. Run [php artisan config:clear] and try again.');
|
||||
throw_if(empty($columnNames['team_foreign_key'] ?? null), 'Error: team_foreign_key on config/permission.php not loaded. Run [php artisan config:clear] and try again.');
|
||||
|
||||
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()->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']);
|
||||
});
|
||||
}
|
||||
|
||||
if (! Schema::hasColumn($tableNames['model_has_permissions'], $columnNames['team_foreign_key'])) {
|
||||
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([$pivotPermission]);
|
||||
}
|
||||
$table->dropPrimary();
|
||||
|
||||
$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($pivotPermission)
|
||||
->references('id')
|
||||
->on($tableNames['permissions'])
|
||||
->cascadeOnDelete();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index');
|
||||
|
||||
if (DB::getDriverName() !== 'sqlite') {
|
||||
$table->dropForeign([$pivotRole]);
|
||||
}
|
||||
$table->dropPrimary();
|
||||
|
||||
$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($pivotRole)
|
||||
->references('id')
|
||||
->on($tableNames['roles'])
|
||||
->cascadeOnDelete();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
app('cache')
|
||||
->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null)
|
||||
->forget(config('permission.cache.key'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void {}
|
||||
};
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$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';
|
||||
|
||||
throw_if(empty($tableNames), 'Error: config/permission.php not loaded. Run [php artisan config:clear] and try again.');
|
||||
throw_if($teams && empty($columnNames['team_foreign_key'] ?? null), 'Error: team_foreign_key on config/permission.php not loaded. Run [php artisan config:clear] and try again.');
|
||||
|
||||
/**
|
||||
* See `docs/prerequisites.md` for suggested lengths on 'name' and 'guard_name' if "1071 Specified key was too long" errors are encountered.
|
||||
*/
|
||||
Schema::create($tableNames['permissions'], static function (Blueprint $table) {
|
||||
$table->id(); // permission id
|
||||
$table->string('name');
|
||||
$table->string('guard_name');
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['name', 'guard_name']);
|
||||
});
|
||||
|
||||
/**
|
||||
* See `docs/prerequisites.md` for suggested lengths on 'name' and 'guard_name' if "1071 Specified key was too long" errors are encountered.
|
||||
*/
|
||||
Schema::create($tableNames['roles'], static function (Blueprint $table) use ($teams, $columnNames) {
|
||||
$table->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');
|
||||
$table->string('guard_name');
|
||||
$table->timestamps();
|
||||
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'], static 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($pivotPermission)
|
||||
->references('id') // permission id
|
||||
->on($tableNames['permissions'])
|
||||
->cascadeOnDelete();
|
||||
if ($teams) {
|
||||
$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'], $pivotPermission, $columnNames['model_morph_key'], 'model_type'],
|
||||
'model_has_permissions_permission_model_type_primary');
|
||||
} else {
|
||||
$table->primary([$pivotPermission, $columnNames['model_morph_key'], 'model_type'],
|
||||
'model_has_permissions_permission_model_type_primary');
|
||||
}
|
||||
});
|
||||
|
||||
Schema::create($tableNames['model_has_roles'], static 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($pivotRole)
|
||||
->references('id') // role id
|
||||
->on($tableNames['roles'])
|
||||
->cascadeOnDelete();
|
||||
if ($teams) {
|
||||
$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'], $pivotRole, $columnNames['model_morph_key'], 'model_type'],
|
||||
'model_has_roles_role_model_type_primary');
|
||||
} else {
|
||||
$table->primary([$pivotRole, $columnNames['model_morph_key'], 'model_type'],
|
||||
'model_has_roles_role_model_type_primary');
|
||||
}
|
||||
});
|
||||
|
||||
Schema::create($tableNames['role_has_permissions'], static function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) {
|
||||
$table->unsignedBigInteger($pivotPermission);
|
||||
$table->unsignedBigInteger($pivotRole);
|
||||
|
||||
$table->foreign($pivotPermission)
|
||||
->references('id') // permission id
|
||||
->on($tableNames['permissions'])
|
||||
->cascadeOnDelete();
|
||||
|
||||
$table->foreign($pivotRole)
|
||||
->references('id') // role id
|
||||
->on($tableNames['roles'])
|
||||
->cascadeOnDelete();
|
||||
|
||||
$table->primary([$pivotPermission, $pivotRole], 'role_has_permissions_permission_id_role_id_primary');
|
||||
});
|
||||
|
||||
app('cache')
|
||||
->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null)
|
||||
->forget(config('permission.cache.key'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
$tableNames = config('permission.table_names');
|
||||
|
||||
throw_if(empty($tableNames), '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::dropIfExists($tableNames['role_has_permissions']);
|
||||
Schema::dropIfExists($tableNames['model_has_roles']);
|
||||
Schema::dropIfExists($tableNames['model_has_permissions']);
|
||||
Schema::dropIfExists($tableNames['roles']);
|
||||
Schema::dropIfExists($tableNames['permissions']);
|
||||
}
|
||||
};
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"$schema": "https://laravel-ide.com/schema/laravel-ide-v2.json",
|
||||
"blade": {
|
||||
"directives": [
|
||||
{
|
||||
"name": "role",
|
||||
"prefix": "<?php if(\\Spatie\\Permission\\PermissionServiceProvider::bladeMethodWrapper('hasRole', {",
|
||||
"suffix": "})): ?>"
|
||||
},
|
||||
{
|
||||
"name": "elserole",
|
||||
"prefix": "<?php elseif(\\Spatie\\Permission\\PermissionServiceProvider::bladeMethodWrapper('hasRole', {",
|
||||
"suffix": "})): ?>"
|
||||
},
|
||||
{
|
||||
"name": "endrole",
|
||||
"prefix": "",
|
||||
"suffix": ""
|
||||
},
|
||||
{
|
||||
"name": "hasrole",
|
||||
"prefix": "<?php if(\\Spatie\\Permission\\PermissionServiceProvider::bladeMethodWrapper('hasRole', {",
|
||||
"suffix": "})): ?>"
|
||||
},
|
||||
{
|
||||
"name": "endhasrole",
|
||||
"prefix": "",
|
||||
"suffix": ""
|
||||
},
|
||||
{
|
||||
"name": "hasanyrole",
|
||||
"prefix": "<?php if(\\Spatie\\Permission\\PermissionServiceProvider::bladeMethodWrapper('hasAnyRole', {",
|
||||
"suffix": "})): ?>"
|
||||
},
|
||||
{
|
||||
"name": "endhasanyrole",
|
||||
"prefix": "",
|
||||
"suffix": ""
|
||||
},
|
||||
{
|
||||
"name": "hasallroles",
|
||||
"prefix": "<?php if(\\Spatie\\Permission\\PermissionServiceProvider::bladeMethodWrapper('hasAllRoles', {",
|
||||
"suffix": "})): ?>"
|
||||
},
|
||||
{
|
||||
"name": "endhasallroles",
|
||||
"prefix": "",
|
||||
"suffix": ""
|
||||
},
|
||||
{
|
||||
"name": "unlessrole",
|
||||
"prefix": "<?php if(! \\Spatie\\Permission\\PermissionServiceProvider::bladeMethodWrapper('hasRole', {",
|
||||
"suffix": "})): ?>"
|
||||
},
|
||||
{
|
||||
"name": "endunlessrole",
|
||||
"prefix": "",
|
||||
"suffix": ""
|
||||
},
|
||||
{
|
||||
"name": "hasexactroles",
|
||||
"prefix": "<?php if(\\Spatie\\Permission\\PermissionServiceProvider::bladeMethodWrapper('hasExactRoles', {",
|
||||
"suffix": "})): ?>"
|
||||
},
|
||||
{
|
||||
"name": "endhasexactroles",
|
||||
"prefix": "",
|
||||
"suffix": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"preset": "laravel",
|
||||
"rules": {
|
||||
"php_unit_method_casing": false
|
||||
}
|
||||
}
|
||||
Vendored
+277
@@ -0,0 +1,277 @@
|
||||
---
|
||||
name: laravel-permission-development
|
||||
description: Build and work with Spatie Laravel Permission features, including roles, permissions, middleware, policies, teams, and Blade directives.
|
||||
---
|
||||
|
||||
# Laravel Permission Development
|
||||
|
||||
## When to use this skill
|
||||
|
||||
Use this skill when working with authorization, roles, permissions, access control, middleware guards, or Blade permission directives using spatie/laravel-permission.
|
||||
|
||||
## Core Concepts
|
||||
|
||||
- **Users have Roles, Roles have Permissions, Apps check Permissions** (not Roles).
|
||||
- Direct permissions on users are an anti-pattern; assign permissions to roles instead.
|
||||
- Use `$user->can('permission-name')` for all authorization checks (supports Super Admin via Gate).
|
||||
- The `HasRoles` trait (which includes `HasPermissions`) is added to User models.
|
||||
|
||||
## Setup
|
||||
|
||||
Add the `HasRoles` trait to your User model:
|
||||
|
||||
```php
|
||||
use Spatie\Permission\Traits\HasRoles;
|
||||
|
||||
class User extends Authenticatable
|
||||
{
|
||||
use HasRoles;
|
||||
}
|
||||
```
|
||||
|
||||
## Creating Roles and Permissions
|
||||
|
||||
```php
|
||||
use Spatie\Permission\Models\Role;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
|
||||
$role = Role::create(['name' => 'writer']);
|
||||
$permission = Permission::create(['name' => 'edit articles']);
|
||||
|
||||
// findOrCreate is idempotent (safe for seeders)
|
||||
$role = Role::findOrCreate('writer', 'web');
|
||||
$permission = Permission::findOrCreate('edit articles', 'web');
|
||||
```
|
||||
|
||||
## Assigning Roles and Permissions
|
||||
|
||||
```php
|
||||
// Assign roles to users
|
||||
$user->assignRole('writer');
|
||||
$user->assignRole('writer', 'admin');
|
||||
$user->assignRole(['writer', 'admin']);
|
||||
$user->syncRoles(['writer', 'admin']); // replaces all
|
||||
$user->removeRole('writer');
|
||||
|
||||
// Assign permissions to roles (preferred)
|
||||
$role->givePermissionTo('edit articles');
|
||||
$role->givePermissionTo(['edit articles', 'delete articles']);
|
||||
$role->syncPermissions(['edit articles', 'delete articles']);
|
||||
$role->revokePermissionTo('edit articles');
|
||||
|
||||
// Reverse assignment
|
||||
$permission->assignRole('writer');
|
||||
$permission->syncRoles(['writer', 'editor']);
|
||||
$permission->removeRole('writer');
|
||||
```
|
||||
|
||||
## Checking Roles and Permissions
|
||||
|
||||
```php
|
||||
// Permission checks (preferred - supports Super Admin via Gate)
|
||||
$user->can('edit articles');
|
||||
$user->canAny(['edit articles', 'delete articles']);
|
||||
|
||||
// Direct package methods (bypass Gate, no Super Admin support)
|
||||
$user->hasPermissionTo('edit articles');
|
||||
$user->hasAnyPermission(['edit articles', 'publish articles']);
|
||||
$user->hasAllPermissions(['edit articles', 'publish articles']);
|
||||
$user->hasDirectPermission('edit articles');
|
||||
|
||||
// Role checks
|
||||
$user->hasRole('writer');
|
||||
$user->hasAnyRole(['writer', 'editor']);
|
||||
$user->hasAllRoles(['writer', 'editor']);
|
||||
$user->hasExactRoles(['writer', 'editor']);
|
||||
|
||||
// Get assigned roles and permissions
|
||||
$user->getRoleNames(); // Collection of role name strings
|
||||
$user->getPermissionNames(); // Collection of permission name strings
|
||||
$user->getDirectPermissions(); // Direct permissions only
|
||||
$user->getPermissionsViaRoles(); // Inherited via roles
|
||||
$user->getAllPermissions(); // Both direct and inherited
|
||||
```
|
||||
|
||||
## Query Scopes
|
||||
|
||||
```php
|
||||
$users = User::role('writer')->get();
|
||||
$users = User::withoutRole('writer')->get();
|
||||
$users = User::permission('edit articles')->get();
|
||||
$users = User::withoutPermission('edit articles')->get();
|
||||
```
|
||||
|
||||
## Middleware
|
||||
|
||||
Register middleware aliases in `bootstrap/app.php`:
|
||||
|
||||
```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,
|
||||
]);
|
||||
})
|
||||
```
|
||||
|
||||
Use in routes (pipe `|` for OR logic):
|
||||
|
||||
```php
|
||||
Route::middleware(['permission:edit articles'])->group(function () { ... });
|
||||
Route::middleware(['role:manager|writer'])->group(function () { ... });
|
||||
Route::middleware(['role_or_permission:manager|edit articles'])->group(function () { ... });
|
||||
|
||||
// With specific guard
|
||||
Route::middleware(['role:manager,api'])->group(function () { ... });
|
||||
```
|
||||
|
||||
For single permissions, Laravel's built-in `can` middleware also works:
|
||||
|
||||
```php
|
||||
Route::middleware(['can:edit articles'])->group(function () { ... });
|
||||
```
|
||||
|
||||
## Blade Directives
|
||||
|
||||
Prefer `@can` (permission-based) over `@role` (role-based):
|
||||
|
||||
```blade
|
||||
@can('edit articles')
|
||||
{{-- User can edit articles (supports Super Admin) --}}
|
||||
@endcan
|
||||
|
||||
@canany(['edit articles', 'delete articles'])
|
||||
{{-- User can do at least one --}}
|
||||
@endcanany
|
||||
|
||||
@role('admin')
|
||||
{{-- Only use for super-admin type checks --}}
|
||||
@endrole
|
||||
|
||||
@hasanyrole('writer|admin')
|
||||
{{-- Has writer or admin --}}
|
||||
@endhasanyrole
|
||||
```
|
||||
|
||||
## Super Admin
|
||||
|
||||
Use `Gate::before` in `AppServiceProvider::boot()`:
|
||||
|
||||
```php
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
|
||||
public function boot(): void
|
||||
{
|
||||
Gate::before(function ($user, $ability) {
|
||||
return $user->hasRole('Super Admin') ? true : null;
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
This makes `$user->can()` and `@can` always return true for Super Admins. Must return `null` (not `false`) to allow normal checks for other users.
|
||||
|
||||
## Policies
|
||||
|
||||
Use `$user->can()` inside policy methods to check permissions:
|
||||
|
||||
```php
|
||||
class PostPolicy
|
||||
{
|
||||
public function update(User $user, Post $post): bool
|
||||
{
|
||||
if ($user->can('edit all posts')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $user->can('edit own posts') && $user->id === $post->user_id;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Enums
|
||||
|
||||
```php
|
||||
enum RolesEnum: string
|
||||
{
|
||||
case WRITER = 'writer';
|
||||
case EDITOR = 'editor';
|
||||
}
|
||||
|
||||
enum PermissionsEnum: string
|
||||
{
|
||||
case EDIT_POSTS = 'edit posts';
|
||||
case DELETE_POSTS = 'delete posts';
|
||||
}
|
||||
|
||||
// Creation requires ->value
|
||||
Permission::findOrCreate(PermissionsEnum::EDIT_POSTS->value, 'web');
|
||||
|
||||
// Most methods accept enums directly
|
||||
$user->assignRole(RolesEnum::WRITER);
|
||||
$user->hasRole(RolesEnum::WRITER);
|
||||
$role->givePermissionTo(PermissionsEnum::EDIT_POSTS);
|
||||
$user->hasPermissionTo(PermissionsEnum::EDIT_POSTS);
|
||||
```
|
||||
|
||||
## Seeding
|
||||
|
||||
Always flush the permission cache when seeding:
|
||||
|
||||
```php
|
||||
class RolesAndPermissionsSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
// Reset cache
|
||||
app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();
|
||||
|
||||
// Create permissions
|
||||
Permission::findOrCreate('edit articles', 'web');
|
||||
Permission::findOrCreate('delete articles', 'web');
|
||||
|
||||
// Create roles and assign permissions
|
||||
Role::findOrCreate('writer', 'web')
|
||||
->givePermissionTo(['edit articles']);
|
||||
|
||||
Role::findOrCreate('admin', 'web')
|
||||
->givePermissionTo(Permission::all());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Teams (Multi-Tenancy)
|
||||
|
||||
Enable in `config/permission.php` before running migrations:
|
||||
|
||||
```php
|
||||
'teams' => true,
|
||||
```
|
||||
|
||||
Set the active team in middleware:
|
||||
|
||||
```php
|
||||
setPermissionsTeamId($teamId);
|
||||
```
|
||||
|
||||
When switching teams, unset cached relations:
|
||||
|
||||
```php
|
||||
$user->unsetRelation('roles')->unsetRelation('permissions');
|
||||
```
|
||||
|
||||
## Events
|
||||
|
||||
Enable in `config/permission.php`:
|
||||
|
||||
```php
|
||||
'events_enabled' => true,
|
||||
```
|
||||
|
||||
Available events: `RoleAttachedEvent`, `RoleDetachedEvent`, `PermissionAttachedEvent`, `PermissionDetachedEvent` in the `Spatie\Permission\Events` namespace.
|
||||
|
||||
## Performance
|
||||
|
||||
- Permissions are cached automatically. The cache is flushed when roles/permissions change via package methods.
|
||||
- After direct DB operations, flush manually: `app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions()`
|
||||
- For bulk seeding, use `Permission::insert()` for speed, but flush the cache afterward.
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Permission\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Spatie\Permission\Contracts\Role;
|
||||
use Spatie\Permission\Contracts\Role as RoleContract;
|
||||
use Spatie\Permission\PermissionRegistrar;
|
||||
|
||||
class AssignRoleCommand extends Command
|
||||
{
|
||||
protected $signature = 'permission:assign-role
|
||||
{name : The name of the role}
|
||||
{userId : The ID of the user to assign the role to}
|
||||
{guard? : The name of the guard}
|
||||
{userModelNamespace=App\Models\User : The fully qualified class name of the user model}
|
||||
{--team-id=}';
|
||||
|
||||
protected $description = 'Assign a role to a user';
|
||||
|
||||
public function handle(PermissionRegistrar $permissionRegistrar): int
|
||||
{
|
||||
$roleName = $this->argument('name');
|
||||
$userId = $this->argument('userId');
|
||||
$guardName = $this->argument('guard');
|
||||
$userModelClass = $this->argument('userModelNamespace');
|
||||
|
||||
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 self::SUCCESS;
|
||||
}
|
||||
|
||||
// Validate that the model class exists and is instantiable
|
||||
if (! class_exists($userModelClass)) {
|
||||
$this->error("User model class [{$userModelClass}] does not exist.");
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$user = (new $userModelClass)::find($userId);
|
||||
|
||||
if (! $user) {
|
||||
$this->error("User with ID {$userId} not found.");
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$teamIdAux = getPermissionsTeamId();
|
||||
setPermissionsTeamId($this->option('team-id') ?: null);
|
||||
|
||||
/** @var Role $roleClass */
|
||||
$roleClass = app(RoleContract::class);
|
||||
|
||||
$role = $roleClass::findOrCreate($roleName, $guardName);
|
||||
|
||||
$user->assignRole($role);
|
||||
|
||||
setPermissionsTeamId($teamIdAux);
|
||||
|
||||
$this->info("Role `{$role->name}` assigned to user ID {$userId} successfully.");
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Permission\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Spatie\Permission\PermissionRegistrar;
|
||||
|
||||
class CacheResetCommand extends Command
|
||||
{
|
||||
protected $signature = 'permission:cache-reset';
|
||||
|
||||
protected $description = 'Reset the permission cache';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$permissionRegistrar = app(PermissionRegistrar::class);
|
||||
$cacheExists = $permissionRegistrar->getCacheRepository()->has($permissionRegistrar->cacheKey);
|
||||
|
||||
if ($permissionRegistrar->forgetCachedPermissions()) {
|
||||
$this->info('Permission cache flushed.');
|
||||
} elseif ($cacheExists) {
|
||||
$this->error('Unable to flush cache.');
|
||||
}
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Permission\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Spatie\Permission\Contracts\Permission as PermissionContract;
|
||||
|
||||
class CreatePermissionCommand extends Command
|
||||
{
|
||||
protected $signature = 'permission:create-permission
|
||||
{name : The name of the permission}
|
||||
{guard? : The name of the guard}';
|
||||
|
||||
protected $description = 'Create a permission';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$permissionClass = app(PermissionContract::class);
|
||||
|
||||
$permission = $permissionClass::findOrCreate($this->argument('name'), $this->argument('guard'));
|
||||
|
||||
$this->info("Permission `{$permission->name}` ".($permission->wasRecentlyCreated ? 'created' : 'already exists'));
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Permission\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Collection;
|
||||
use Spatie\Permission\Contracts\Permission as PermissionContract;
|
||||
use Spatie\Permission\Contracts\Role as RoleContract;
|
||||
use Spatie\Permission\PermissionRegistrar;
|
||||
|
||||
class CreateRoleCommand 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 | }
|
||||
{--team-id=}';
|
||||
|
||||
protected $description = 'Create a role';
|
||||
|
||||
public function handle(PermissionRegistrar $permissionRegistrar): int
|
||||
{
|
||||
$roleClass = app(RoleContract::class);
|
||||
|
||||
$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');
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
$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)) {
|
||||
$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}` ".($role->wasRecentlyCreated ? 'created' : 'updated'));
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
protected function makePermissions(?string $string = null): ?Collection
|
||||
{
|
||||
if (empty($string)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$permissionClass = app(PermissionContract::class);
|
||||
|
||||
$permissions = explode('|', $string);
|
||||
|
||||
$models = [];
|
||||
|
||||
foreach ($permissions as $permission) {
|
||||
$models[] = $permissionClass::findOrCreate(trim($permission), $this->argument('guard'));
|
||||
}
|
||||
|
||||
return collect($models);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Permission\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Collection;
|
||||
use Spatie\Permission\Contracts\Permission as PermissionContract;
|
||||
use Spatie\Permission\Contracts\Role as RoleContract;
|
||||
use Spatie\Permission\Support\Config;
|
||||
use Symfony\Component\Console\Helper\TableCell;
|
||||
|
||||
class ShowCommand extends Command
|
||||
{
|
||||
protected $signature = 'permission:show
|
||||
{guard? : The name of the guard}
|
||||
{style? : The display style (default|borderless|compact|box)}';
|
||||
|
||||
protected $description = 'Show a table of roles and permissions per guard';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$permissionClass = app(PermissionContract::class);
|
||||
$roleClass = app(RoleContract::class);
|
||||
$teamsEnabled = Config::teamsEnabled();
|
||||
$team_key = Config::teamForeignKey();
|
||||
|
||||
$style = $this->argument('style') ?? 'default';
|
||||
$guard = $this->argument('guard');
|
||||
|
||||
if ($guard) {
|
||||
$guards = Collection::make([$guard]);
|
||||
} else {
|
||||
$guards = $permissionClass::pluck('guard_name')->merge($roleClass::pluck('guard_name'))->unique();
|
||||
}
|
||||
|
||||
foreach ($guards as $guard) {
|
||||
$this->info("Guard: $guard");
|
||||
|
||||
$roles = $roleClass::whereGuardName($guard)
|
||||
->with('permissions')
|
||||
->when($teamsEnabled, fn ($q) => $q->orderBy($team_key))
|
||||
->orderBy('name')->get()->mapWithKeys(fn ($role) => [
|
||||
$role->name.'_'.($teamsEnabled ? ($role->$team_key ?: '') : '') => [
|
||||
'permissions' => $role->permissions->pluck($permissionClass->getKeyName()),
|
||||
$team_key => $teamsEnabled ? $role->$team_key : null,
|
||||
],
|
||||
]);
|
||||
|
||||
$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) ? ' ✔' : ' ·'
|
||||
)->prepend($permission)
|
||||
);
|
||||
|
||||
if ($teamsEnabled) {
|
||||
$teams = $roles->groupBy($team_key)->values()->map(
|
||||
fn ($group, $id) => new TableCell('Team ID: '.($id ?: 'NULL'), ['colspan' => $group->count()])
|
||||
);
|
||||
}
|
||||
|
||||
$this->table(
|
||||
array_merge(
|
||||
isset($teams) ? $teams->prepend(new TableCell(''))->toArray() : [],
|
||||
$roles->keys()->map(function ($val) {
|
||||
$name = explode('_', $val);
|
||||
array_pop($name);
|
||||
|
||||
return implode('_', $name);
|
||||
})
|
||||
->prepend(new TableCell(''))->toArray(),
|
||||
),
|
||||
$body->toArray(),
|
||||
$style
|
||||
);
|
||||
}
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Permission\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
|
||||
class UpgradeForTeamsCommand extends Command
|
||||
{
|
||||
protected $signature = 'permission:setup-teams';
|
||||
|
||||
protected $description = 'Setup the teams feature by generating the associated migration.';
|
||||
|
||||
protected string $migrationSuffix = 'add_teams_fields.php';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
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 self::FAILURE;
|
||||
}
|
||||
|
||||
$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?', true)) {
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
$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('');
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
protected function createMigration(): bool
|
||||
{
|
||||
try {
|
||||
$migrationStub = __DIR__."/../../database/migrations/{$this->migrationSuffix}.stub";
|
||||
copy($migrationStub, $this->getMigrationPath());
|
||||
|
||||
return true;
|
||||
} catch (\Throwable $e) {
|
||||
$this->error($e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected function getExistingMigrationsWarning(array $existingMigrations): string
|
||||
{
|
||||
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, fn ($carry, $fileName) => $carry."\n - ".$fileName);
|
||||
}
|
||||
|
||||
protected function alreadyExistingMigrations(): array
|
||||
{
|
||||
$matchingFiles = glob($this->getMigrationPath('*'));
|
||||
|
||||
return array_map(fn ($path) => basename($path), $matchingFiles);
|
||||
}
|
||||
|
||||
protected function getMigrationPath(?string $date = null): string
|
||||
{
|
||||
$date = $date ?: now()->format('Y_m_d_His');
|
||||
|
||||
return database_path("migrations/{$date}_{$this->migrationSuffix}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Permission\Contracts;
|
||||
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Spatie\Permission\Exceptions\PermissionDoesNotExist;
|
||||
|
||||
/**
|
||||
* @property int|string $id
|
||||
* @property string $name
|
||||
* @property string|null $guard_name
|
||||
*
|
||||
* @mixin \Spatie\Permission\Models\Permission
|
||||
*
|
||||
* @phpstan-require-extends \Spatie\Permission\Models\Permission
|
||||
*/
|
||||
interface Permission
|
||||
{
|
||||
/**
|
||||
* A permission can be applied to roles.
|
||||
*/
|
||||
public function roles(): BelongsToMany;
|
||||
|
||||
/**
|
||||
* Find a permission by its name.
|
||||
*
|
||||
*
|
||||
* @throws PermissionDoesNotExist
|
||||
*/
|
||||
public static function findByName(string $name, ?string $guardName): self;
|
||||
|
||||
/**
|
||||
* Find a permission by its id.
|
||||
*
|
||||
*
|
||||
* @throws PermissionDoesNotExist
|
||||
*/
|
||||
public static function findById(int|string $id, ?string $guardName): self;
|
||||
|
||||
/**
|
||||
* Find or Create a permission by its name and guard name.
|
||||
*/
|
||||
public static function findOrCreate(string $name, ?string $guardName): self;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Permission\Contracts;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
interface PermissionsTeamResolver
|
||||
{
|
||||
public function getPermissionsTeamId(): int|string|null;
|
||||
|
||||
public function setPermissionsTeamId(int|string|Model|null $id): void;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Permission\Contracts;
|
||||
|
||||
use BackedEnum;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Spatie\Permission\Exceptions\RoleDoesNotExist;
|
||||
|
||||
/**
|
||||
* @property int|string $id
|
||||
* @property string $name
|
||||
* @property string|null $guard_name
|
||||
*
|
||||
* @mixin \Spatie\Permission\Models\Role
|
||||
*
|
||||
* @phpstan-require-extends \Spatie\Permission\Models\Role
|
||||
*/
|
||||
interface Role
|
||||
{
|
||||
/**
|
||||
* A role may be given various permissions.
|
||||
*/
|
||||
public function permissions(): BelongsToMany;
|
||||
|
||||
/**
|
||||
* Find a role by its name and guard name.
|
||||
*
|
||||
*
|
||||
* @throws RoleDoesNotExist
|
||||
*/
|
||||
public static function findByName(string $name, ?string $guardName): self;
|
||||
|
||||
/**
|
||||
* Find a role by its id and guard name.
|
||||
*
|
||||
*
|
||||
* @throws RoleDoesNotExist
|
||||
*/
|
||||
public static function findById(int|string $id, ?string $guardName): self;
|
||||
|
||||
/**
|
||||
* Find or create a role by its name and guard name.
|
||||
*/
|
||||
public static function findOrCreate(string $name, ?string $guardName): self;
|
||||
|
||||
/**
|
||||
* Determine if the user may perform the given permission.
|
||||
*/
|
||||
public function hasPermissionTo(string|int|Permission|BackedEnum $permission, ?string $guardName = null): bool;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Permission\Contracts;
|
||||
|
||||
interface Wildcard
|
||||
{
|
||||
public function getIndex(): array;
|
||||
|
||||
public function implies(string $permission, string $guardName, array $index): bool;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Permission;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Spatie\Permission\Contracts\PermissionsTeamResolver;
|
||||
|
||||
class DefaultTeamResolver implements PermissionsTeamResolver
|
||||
{
|
||||
protected int|string|null $teamId = null;
|
||||
|
||||
public function setPermissionsTeamId(int|string|Model|null $id): void
|
||||
{
|
||||
if ($id instanceof Model) {
|
||||
$id = $id->getKey();
|
||||
}
|
||||
$this->teamId = $id;
|
||||
}
|
||||
|
||||
public function getPermissionsTeamId(): int|string|null
|
||||
{
|
||||
return $this->teamId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Spatie\Permission\Events;
|
||||
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Collection;
|
||||
use Spatie\Permission\Contracts\Permission;
|
||||
|
||||
class PermissionAttachedEvent
|
||||
{
|
||||
use Dispatchable;
|
||||
use InteractsWithSockets;
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Internally the HasPermissions trait passes an array of permission ids (eg: int's or uuid's)
|
||||
* Theoretically one could register the event to other places and pass an Eloquent record.
|
||||
* So a Listener should inspect the type of $permissionsOrIds received before using.
|
||||
*
|
||||
* @param array|int[]|string[]|Permission|Permission[]|Collection $permissionsOrIds
|
||||
*/
|
||||
public function __construct(public Model $model, public mixed $permissionsOrIds) {}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Spatie\Permission\Events;
|
||||
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Collection;
|
||||
use Spatie\Permission\Contracts\Permission;
|
||||
|
||||
class PermissionDetachedEvent
|
||||
{
|
||||
use Dispatchable;
|
||||
use InteractsWithSockets;
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Internally the HasPermissions trait passes $permissionsOrIds as an Eloquent record.
|
||||
* Theoretically one could register the event to other places and pass an array etc.
|
||||
* So a Listener should inspect the type of $permissionsOrIds received before using.
|
||||
*
|
||||
* @param array|int[]|string[]|Permission|Permission[]|Collection $permissionsOrIds
|
||||
*/
|
||||
public function __construct(public Model $model, public mixed $permissionsOrIds) {}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Spatie\Permission\Events;
|
||||
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Collection;
|
||||
use Spatie\Permission\Contracts\Role;
|
||||
|
||||
class RoleAttachedEvent
|
||||
{
|
||||
use Dispatchable;
|
||||
use InteractsWithSockets;
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public function __construct(public Model $model, public mixed $rolesOrIds) {}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Spatie\Permission\Events;
|
||||
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Collection;
|
||||
use Spatie\Permission\Contracts\Role;
|
||||
|
||||
class RoleDetachedEvent
|
||||
{
|
||||
use Dispatchable;
|
||||
use InteractsWithSockets;
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public function __construct(public Model $model, public mixed $rolesOrIds) {}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Permission\Exceptions;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class GuardDoesNotMatch extends InvalidArgumentException
|
||||
{
|
||||
public static function create(string $givenGuard, Collection $expectedGuards): static
|
||||
{
|
||||
return new static(__('The given role or permission should use guard `:expected` instead of `:given`.', [
|
||||
'expected' => $expectedGuards->implode(', '),
|
||||
'given' => $givenGuard,
|
||||
]));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Permission\Exceptions;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
class PermissionAlreadyExists extends InvalidArgumentException
|
||||
{
|
||||
public static function create(string $permissionName, string $guardName): static
|
||||
{
|
||||
return new static(__('A `:permission` permission already exists for guard `:guard`.', [
|
||||
'permission' => $permissionName,
|
||||
'guard' => $guardName,
|
||||
]));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Permission\Exceptions;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
class PermissionDoesNotExist extends InvalidArgumentException
|
||||
{
|
||||
public static function create(string $permissionName, ?string $guardName): static
|
||||
{
|
||||
return new static(__('There is no permission named `:permission` for guard `:guard`.', [
|
||||
'permission' => $permissionName,
|
||||
'guard' => $guardName,
|
||||
]));
|
||||
}
|
||||
|
||||
public static function withId(int|string $permissionId, ?string $guardName): static
|
||||
{
|
||||
return new static(__('There is no [permission] with ID `:id` for guard `:guard`.', [
|
||||
'id' => $permissionId,
|
||||
'guard' => $guardName,
|
||||
]));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Permission\Exceptions;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
class RoleAlreadyExists extends InvalidArgumentException
|
||||
{
|
||||
public static function create(string $roleName, string $guardName): static
|
||||
{
|
||||
return new static(__('A role `:role` already exists for guard `:guard`.', [
|
||||
'role' => $roleName,
|
||||
'guard' => $guardName,
|
||||
]));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Permission\Exceptions;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
class RoleDoesNotExist extends InvalidArgumentException
|
||||
{
|
||||
public static function named(string $roleName, ?string $guardName): static
|
||||
{
|
||||
return new static(__('There is no role named `:role` for guard `:guard`.', [
|
||||
'role' => $roleName,
|
||||
'guard' => $guardName,
|
||||
]));
|
||||
}
|
||||
|
||||
public static function withId(int|string $roleId, ?string $guardName): static
|
||||
{
|
||||
return new static(__('There is no role with ID `:id` for guard `:guard`.', [
|
||||
'id' => $roleId,
|
||||
'guard' => $guardName,
|
||||
]));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Permission\Exceptions;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class TeamModelNotConfigured extends RuntimeException
|
||||
{
|
||||
public static function create(): static
|
||||
{
|
||||
return new static(__('No team model configured. Set `models.team` in your permission config file.'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Permission\Exceptions;
|
||||
|
||||
use BadMethodCallException;
|
||||
|
||||
class TeamsNotEnabled extends BadMethodCallException
|
||||
{
|
||||
public static function create(): static
|
||||
{
|
||||
return new static(__('The teams feature is not enabled. Set `teams` to `true` in your permission config file.'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Permission\Exceptions;
|
||||
|
||||
use Illuminate\Contracts\Auth\Access\Authorizable;
|
||||
use Spatie\Permission\Support\Config;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
|
||||
class UnauthorizedException extends HttpException
|
||||
{
|
||||
private array $requiredRoles = [];
|
||||
|
||||
private array $requiredPermissions = [];
|
||||
|
||||
public static function forRoles(array $roles): static
|
||||
{
|
||||
$message = __('User does not have the right roles.');
|
||||
|
||||
if (Config::displayRoleInException()) {
|
||||
$message .= ' '.__('Necessary roles are :roles', ['roles' => implode(', ', $roles)]);
|
||||
}
|
||||
|
||||
$exception = new static(403, $message, null, []);
|
||||
$exception->requiredRoles = $roles;
|
||||
|
||||
return $exception;
|
||||
}
|
||||
|
||||
public static function forPermissions(array $permissions): static
|
||||
{
|
||||
$message = __('User does not have the right permissions.');
|
||||
|
||||
if (Config::displayPermissionInException()) {
|
||||
$message .= ' '.__('Necessary permissions are :permissions', ['permissions' => implode(', ', $permissions)]);
|
||||
}
|
||||
|
||||
$exception = new static(403, $message, null, []);
|
||||
$exception->requiredPermissions = $permissions;
|
||||
|
||||
return $exception;
|
||||
}
|
||||
|
||||
public static function forRolesOrPermissions(array $rolesOrPermissions): static
|
||||
{
|
||||
$message = __('User does not have any of the necessary access rights.');
|
||||
|
||||
if (Config::displayPermissionInException() && Config::displayRoleInException()) {
|
||||
$message .= ' '.__('Necessary roles or permissions are :values', ['values' => implode(', ', $rolesOrPermissions)]);
|
||||
}
|
||||
|
||||
$exception = new static(403, $message, null, []);
|
||||
$exception->requiredPermissions = $rolesOrPermissions;
|
||||
|
||||
return $exception;
|
||||
}
|
||||
|
||||
public static function missingTraitHasRoles(Authorizable $user): static
|
||||
{
|
||||
return new static(403, __('Authorizable class `:class` must use Spatie\\Permission\\Traits\\HasRoles trait.', [
|
||||
'class' => $user::class,
|
||||
]), null, []);
|
||||
}
|
||||
|
||||
public static function notLoggedIn(): static
|
||||
{
|
||||
return new static(403, __('User is not logged in.'), null, []);
|
||||
}
|
||||
|
||||
public function getRequiredRoles(): array
|
||||
{
|
||||
return $this->requiredRoles;
|
||||
}
|
||||
|
||||
public function getRequiredPermissions(): array
|
||||
{
|
||||
return $this->requiredPermissions;
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Permission\Exceptions;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
class WildcardPermissionInvalidArgument extends InvalidArgumentException
|
||||
{
|
||||
public static function create(): static
|
||||
{
|
||||
return new static(__('Wildcard permission must be string, permission id or permission instance'));
|
||||
}
|
||||
}
|
||||
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Permission\Exceptions;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
class WildcardPermissionNotImplementsContract extends InvalidArgumentException
|
||||
{
|
||||
public static function create(): static
|
||||
{
|
||||
return new static(__('Wildcard permission class must implement Spatie\\Permission\\Contracts\\Wildcard contract'));
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Permission\Exceptions;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
class WildcardPermissionNotProperlyFormatted extends InvalidArgumentException
|
||||
{
|
||||
public static function create(string $permission): static
|
||||
{
|
||||
return new static(__('Wildcard permission `:permission` is not properly formatted.', [
|
||||
'permission' => $permission,
|
||||
]));
|
||||
}
|
||||
}
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Permission;
|
||||
|
||||
use Illuminate\Contracts\Auth\Access\Authorizable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use ReflectionClass;
|
||||
|
||||
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
|
||||
*/
|
||||
public static function getNames(string|Model $model): Collection
|
||||
{
|
||||
$class = is_object($model) ? $model::class : $model;
|
||||
|
||||
if (is_object($model)) {
|
||||
if (method_exists($model, 'guardName')) {
|
||||
$guardName = $model->guardName();
|
||||
} else {
|
||||
$guardName = $model->getAttributeValue('guard_name');
|
||||
}
|
||||
}
|
||||
|
||||
if (! isset($guardName)) {
|
||||
$guardName = (new ReflectionClass($class))->getDefaultProperties()['guard_name'] ?? null;
|
||||
}
|
||||
|
||||
if ($guardName) {
|
||||
return collect($guardName);
|
||||
}
|
||||
|
||||
return self::getConfigAuthGuards($class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the model class associated with a given provider.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* 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
|
||||
* - keys() gives just the names of the matched guards
|
||||
* - return collection of guard names
|
||||
*/
|
||||
protected static function getConfigAuthGuards(string $class): Collection
|
||||
{
|
||||
return collect(config('auth.guards'))
|
||||
->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.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @param string|Model $class model class object or name
|
||||
*/
|
||||
public static function getDefaultName(string|Model $class): string
|
||||
{
|
||||
$default = config('auth.defaults.guard');
|
||||
|
||||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup a passport guard
|
||||
*/
|
||||
public static function getPassportClient(?string $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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Permission\Middleware;
|
||||
|
||||
use BackedEnum;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Spatie\Permission\Exceptions\UnauthorizedException;
|
||||
use Spatie\Permission\Guard;
|
||||
use Spatie\Permission\Support\Config;
|
||||
|
||||
use function Illuminate\Support\enum_value;
|
||||
|
||||
class PermissionMiddleware
|
||||
{
|
||||
public function handle(Request $request, Closure $next, $permission, ?string $guard = null)
|
||||
{
|
||||
$authGuard = Auth::guard($guard);
|
||||
|
||||
$user = $authGuard->user();
|
||||
|
||||
// For machine-to-machine Passport clients
|
||||
if (! $user && $request->bearerToken() && Config::usePassportClientCredentials()) {
|
||||
$user = Guard::getPassportClient($guard);
|
||||
}
|
||||
|
||||
if (! $user) {
|
||||
throw UnauthorizedException::notLoggedIn();
|
||||
}
|
||||
|
||||
if (! method_exists($user, 'hasAnyPermission')) {
|
||||
throw UnauthorizedException::missingTraitHasRoles($user);
|
||||
}
|
||||
|
||||
$permissions = explode('|', self::parsePermissionsToString($permission));
|
||||
|
||||
if (! $user->canAny($permissions)) {
|
||||
throw UnauthorizedException::forPermissions($permissions);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the permission and guard for the middleware.
|
||||
*/
|
||||
public static function using(array|string|BackedEnum $permission, ?string $guard = null): string
|
||||
{
|
||||
$permissionString = self::parsePermissionsToString(enum_value($permission));
|
||||
|
||||
$args = is_null($guard) ? $permissionString : "$permissionString,$guard";
|
||||
|
||||
return static::class.':'.$args;
|
||||
}
|
||||
|
||||
protected static function parsePermissionsToString(array|string|BackedEnum $permission): string
|
||||
{
|
||||
$permission = enum_value($permission);
|
||||
|
||||
if (is_array($permission)) {
|
||||
return implode('|', array_map(fn ($r) => enum_value($r), $permission));
|
||||
}
|
||||
|
||||
return (string) $permission;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Permission\Middleware;
|
||||
|
||||
use BackedEnum;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Spatie\Permission\Exceptions\UnauthorizedException;
|
||||
use Spatie\Permission\Guard;
|
||||
use Spatie\Permission\Support\Config;
|
||||
|
||||
use function Illuminate\Support\enum_value;
|
||||
|
||||
class RoleMiddleware
|
||||
{
|
||||
public function handle(Request $request, Closure $next, $role, ?string $guard = null)
|
||||
{
|
||||
$authGuard = Auth::guard($guard);
|
||||
|
||||
$user = $authGuard->user();
|
||||
|
||||
// For machine-to-machine Passport clients
|
||||
if (! $user && $request->bearerToken() && Config::usePassportClientCredentials()) {
|
||||
$user = Guard::getPassportClient($guard);
|
||||
}
|
||||
|
||||
if (! $user) {
|
||||
throw UnauthorizedException::notLoggedIn();
|
||||
}
|
||||
|
||||
if (! method_exists($user, 'hasAnyRole')) {
|
||||
throw UnauthorizedException::missingTraitHasRoles($user);
|
||||
}
|
||||
|
||||
$roles = explode('|', self::parseRolesToString($role));
|
||||
|
||||
if (! $user->hasAnyRole($roles)) {
|
||||
throw UnauthorizedException::forRoles($roles);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the role and guard for the middleware.
|
||||
*/
|
||||
public static function using(array|string|BackedEnum $role, ?string $guard = null): string
|
||||
{
|
||||
$roleString = self::parseRolesToString($role);
|
||||
|
||||
$args = is_null($guard) ? $roleString : "$roleString,$guard";
|
||||
|
||||
return static::class.':'.$args;
|
||||
}
|
||||
|
||||
protected static function parseRolesToString(array|string|BackedEnum $role): string
|
||||
{
|
||||
$role = enum_value($role);
|
||||
|
||||
if (is_array($role)) {
|
||||
return implode('|', array_map(fn ($r) => enum_value($r), $role));
|
||||
}
|
||||
|
||||
return (string) $role;
|
||||
}
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Permission\Middleware;
|
||||
|
||||
use BackedEnum;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Spatie\Permission\Exceptions\UnauthorizedException;
|
||||
use Spatie\Permission\Guard;
|
||||
use Spatie\Permission\Support\Config;
|
||||
|
||||
use function Illuminate\Support\enum_value;
|
||||
|
||||
class RoleOrPermissionMiddleware
|
||||
{
|
||||
public function handle(Request $request, Closure $next, $roleOrPermission, ?string $guard = null)
|
||||
{
|
||||
$authGuard = Auth::guard($guard);
|
||||
|
||||
$user = $authGuard->user();
|
||||
|
||||
// For machine-to-machine Passport clients
|
||||
if (! $user && $request->bearerToken() && Config::usePassportClientCredentials()) {
|
||||
$user = Guard::getPassportClient($guard);
|
||||
}
|
||||
|
||||
if (! $user) {
|
||||
throw UnauthorizedException::notLoggedIn();
|
||||
}
|
||||
|
||||
if (! method_exists($user, 'hasAnyRole') || ! method_exists($user, 'hasAnyPermission')) {
|
||||
throw UnauthorizedException::missingTraitHasRoles($user);
|
||||
}
|
||||
|
||||
$rolesOrPermissions = explode('|', self::parseRoleOrPermissionToString($roleOrPermission));
|
||||
|
||||
if (! $user->canAny($rolesOrPermissions) && ! $user->hasAnyRole($rolesOrPermissions)) {
|
||||
throw UnauthorizedException::forRolesOrPermissions($rolesOrPermissions);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the role or permission and guard for the middleware.
|
||||
*/
|
||||
public static function using(array|string|BackedEnum $roleOrPermission, ?string $guard = null): string
|
||||
{
|
||||
$roleOrPermissionString = self::parseRoleOrPermissionToString($roleOrPermission);
|
||||
$args = is_null($guard) ? $roleOrPermissionString : "$roleOrPermissionString,$guard";
|
||||
|
||||
return static::class.':'.$args;
|
||||
}
|
||||
|
||||
protected static function parseRoleOrPermissionToString(array|string|BackedEnum $roleOrPermission): string
|
||||
{
|
||||
$roleOrPermission = enum_value($roleOrPermission);
|
||||
|
||||
if (is_array($roleOrPermission)) {
|
||||
return implode('|', array_map(fn ($r) => enum_value($r), $roleOrPermission));
|
||||
}
|
||||
|
||||
return (string) $roleOrPermission;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Permission\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Spatie\Permission\Contracts\Permission as PermissionContract;
|
||||
use Spatie\Permission\Exceptions\PermissionAlreadyExists;
|
||||
use Spatie\Permission\Exceptions\PermissionDoesNotExist;
|
||||
use Spatie\Permission\Guard;
|
||||
use Spatie\Permission\PermissionRegistrar;
|
||||
use Spatie\Permission\Support\Config;
|
||||
use Spatie\Permission\Traits\HasRoles;
|
||||
use Spatie\Permission\Traits\RefreshesPermissionCache;
|
||||
|
||||
/**
|
||||
* @property int|string $id
|
||||
* @property string $name
|
||||
* @property string $guard_name
|
||||
* @property ?Carbon $created_at
|
||||
* @property ?Carbon $updated_at
|
||||
* @property-read Collection<int, Role> $roles
|
||||
* @property-read Collection<int, Model> $users
|
||||
*/
|
||||
class Permission extends Model implements PermissionContract
|
||||
{
|
||||
use HasRoles;
|
||||
use RefreshesPermissionCache;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
public function __construct(array $attributes = [])
|
||||
{
|
||||
$attributes['guard_name'] ??= Guard::getDefaultName(static::class);
|
||||
|
||||
parent::__construct($attributes);
|
||||
|
||||
$this->guarded[] = $this->primaryKey;
|
||||
$this->table = Config::permissionsTable() ?: parent::getTable();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return PermissionContract|Permission
|
||||
*
|
||||
* @throws PermissionAlreadyExists
|
||||
*/
|
||||
public static function create(array $attributes = [])
|
||||
{
|
||||
$attributes['guard_name'] ??= Guard::getDefaultName(static::class);
|
||||
|
||||
$permission = static::getPermission(['name' => $attributes['name'], 'guard_name' => $attributes['guard_name']]);
|
||||
|
||||
if ($permission) {
|
||||
throw PermissionAlreadyExists::create($attributes['name'], $attributes['guard_name']);
|
||||
}
|
||||
|
||||
return static::query()->create($attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* A permission can be applied to roles.
|
||||
*/
|
||||
public function roles(): BelongsToMany
|
||||
{
|
||||
$registrar = app(PermissionRegistrar::class);
|
||||
|
||||
return $this->belongsToMany(
|
||||
Config::roleModel(),
|
||||
Config::roleHasPermissionsTable(),
|
||||
$registrar->pivotPermission,
|
||||
$registrar->pivotRole
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A permission belongs to some users of the model associated with its guard.
|
||||
*/
|
||||
public function users(): BelongsToMany
|
||||
{
|
||||
return $this->morphedByMany(
|
||||
getModelForGuard($this->attributes['guard_name'] ?? config('auth.defaults.guard')),
|
||||
'model',
|
||||
Config::modelHasPermissionsTable(),
|
||||
app(PermissionRegistrar::class)->pivotPermission,
|
||||
Config::morphKey()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a permission by its name (and optionally guardName).
|
||||
*
|
||||
* @return PermissionContract|Permission
|
||||
*
|
||||
* @throws PermissionDoesNotExist
|
||||
*/
|
||||
public static function findByName(string $name, ?string $guardName = null): PermissionContract
|
||||
{
|
||||
$guardName ??= Guard::getDefaultName(static::class);
|
||||
$permission = static::getPermission(['name' => $name, 'guard_name' => $guardName]);
|
||||
if (! $permission) {
|
||||
throw PermissionDoesNotExist::create($name, $guardName);
|
||||
}
|
||||
|
||||
return $permission;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a permission by its id (and optionally guardName).
|
||||
*
|
||||
* @return PermissionContract|Permission
|
||||
*
|
||||
* @throws PermissionDoesNotExist
|
||||
*/
|
||||
public static function findById(int|string $id, ?string $guardName = null): PermissionContract
|
||||
{
|
||||
$guardName ??= Guard::getDefaultName(static::class);
|
||||
$permission = static::getPermission([(new static)->getKeyName() => $id, 'guard_name' => $guardName]);
|
||||
|
||||
if (! $permission) {
|
||||
throw PermissionDoesNotExist::withId($id, $guardName);
|
||||
}
|
||||
|
||||
return $permission;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find or create permission by its name (and optionally guardName).
|
||||
*
|
||||
* @return PermissionContract|Permission
|
||||
*/
|
||||
public static function findOrCreate(string $name, ?string $guardName = null): PermissionContract
|
||||
{
|
||||
$guardName ??= Guard::getDefaultName(static::class);
|
||||
$permission = static::getPermission(['name' => $name, 'guard_name' => $guardName]);
|
||||
|
||||
if (! $permission) {
|
||||
return static::query()->create(['name' => $name, 'guard_name' => $guardName]);
|
||||
}
|
||||
|
||||
return $permission;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current cached permissions.
|
||||
*/
|
||||
protected static function getPermissions(array $params = [], bool $onlyOne = false): Collection
|
||||
{
|
||||
return app(PermissionRegistrar::class)
|
||||
->setPermissionClass(static::class)
|
||||
->getPermissions($params, $onlyOne);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current cached first permission.
|
||||
*
|
||||
* @return PermissionContract|Permission|null
|
||||
*/
|
||||
protected static function getPermission(array $params = []): ?PermissionContract
|
||||
{
|
||||
/** @var PermissionContract|null */
|
||||
return static::getPermissions($params, true)->first();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Permission\Models;
|
||||
|
||||
use BackedEnum;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Support\Carbon;
|
||||
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;
|
||||
use Spatie\Permission\PermissionRegistrar;
|
||||
use Spatie\Permission\Support\Config;
|
||||
use Spatie\Permission\Traits\HasAssignedModels;
|
||||
use Spatie\Permission\Traits\HasPermissions;
|
||||
use Spatie\Permission\Traits\RefreshesPermissionCache;
|
||||
|
||||
/**
|
||||
* @property int|string $id
|
||||
* @property string $name
|
||||
* @property string $guard_name
|
||||
* @property ?Carbon $created_at
|
||||
* @property ?Carbon $updated_at
|
||||
* @property-read Collection<int, Permission> $permissions
|
||||
* @property-read Collection<int, Model> $users
|
||||
*/
|
||||
class Role extends Model implements RoleContract
|
||||
{
|
||||
use HasAssignedModels;
|
||||
use HasPermissions;
|
||||
use RefreshesPermissionCache;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
public function __construct(array $attributes = [])
|
||||
{
|
||||
$attributes['guard_name'] ??= Guard::getDefaultName(static::class);
|
||||
|
||||
parent::__construct($attributes);
|
||||
|
||||
$this->guarded[] = $this->primaryKey;
|
||||
$this->table = Config::rolesTable() ?: parent::getTable();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RoleContract|Role
|
||||
*
|
||||
* @throws RoleAlreadyExists
|
||||
*/
|
||||
public static function create(array $attributes = [])
|
||||
{
|
||||
$attributes['guard_name'] ??= Guard::getDefaultName(static::class);
|
||||
|
||||
$params = ['name' => $attributes['name'], 'guard_name' => $attributes['guard_name']];
|
||||
|
||||
$registrar = app(PermissionRegistrar::class);
|
||||
|
||||
if ($registrar->teams) {
|
||||
$teamsKey = $registrar->teamsKey;
|
||||
|
||||
if (array_key_exists($teamsKey, $attributes)) {
|
||||
$params[$teamsKey] = $attributes[$teamsKey];
|
||||
} else {
|
||||
$attributes[$teamsKey] = getPermissionsTeamId();
|
||||
}
|
||||
}
|
||||
|
||||
if (static::findByParam($params)) {
|
||||
throw RoleAlreadyExists::create($attributes['name'], $attributes['guard_name']);
|
||||
}
|
||||
|
||||
return static::query()->create($attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* A role may be given various permissions.
|
||||
*/
|
||||
public function permissions(): BelongsToMany
|
||||
{
|
||||
$registrar = app(PermissionRegistrar::class);
|
||||
|
||||
return $this->belongsToMany(
|
||||
Config::permissionModel(),
|
||||
Config::roleHasPermissionsTable(),
|
||||
$registrar->pivotRole,
|
||||
$registrar->pivotPermission
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A role belongs to some users of the model associated with its guard.
|
||||
*/
|
||||
public function users(): BelongsToMany
|
||||
{
|
||||
return $this->morphedByMany(
|
||||
getModelForGuard($this->attributes['guard_name'] ?? config('auth.defaults.guard')),
|
||||
'model',
|
||||
Config::modelHasRolesTable(),
|
||||
app(PermissionRegistrar::class)->pivotRole,
|
||||
Config::morphKey()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a role by its name and guard name.
|
||||
*
|
||||
* @return RoleContract|Role
|
||||
*
|
||||
* @throws RoleDoesNotExist
|
||||
*/
|
||||
public static function findByName(string $name, ?string $guardName = null): RoleContract
|
||||
{
|
||||
$guardName ??= Guard::getDefaultName(static::class);
|
||||
|
||||
$role = static::findByParam(['name' => $name, 'guard_name' => $guardName]);
|
||||
|
||||
if (! $role) {
|
||||
throw RoleDoesNotExist::named($name, $guardName);
|
||||
}
|
||||
|
||||
return $role;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a role by its id (and optionally guardName).
|
||||
*
|
||||
* @return RoleContract|Role
|
||||
*/
|
||||
public static function findById(int|string $id, ?string $guardName = null): RoleContract
|
||||
{
|
||||
$guardName ??= Guard::getDefaultName(static::class);
|
||||
|
||||
$role = static::findByParam([(new static)->getKeyName() => $id, 'guard_name' => $guardName]);
|
||||
|
||||
if (! $role) {
|
||||
throw RoleDoesNotExist::withId($id, $guardName);
|
||||
}
|
||||
|
||||
return $role;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find or create role by its name (and optionally guardName).
|
||||
*
|
||||
* @return RoleContract|Role
|
||||
*/
|
||||
public static function findOrCreate(string $name, ?string $guardName = null): RoleContract
|
||||
{
|
||||
$guardName ??= Guard::getDefaultName(static::class);
|
||||
|
||||
$attributes = ['name' => $name, 'guard_name' => $guardName];
|
||||
|
||||
$role = static::findByParam($attributes);
|
||||
|
||||
if (! $role) {
|
||||
$registrar = app(PermissionRegistrar::class);
|
||||
if ($registrar->teams) {
|
||||
$teamsKey = $registrar->teamsKey;
|
||||
$attributes[$teamsKey] = getPermissionsTeamId();
|
||||
}
|
||||
|
||||
return static::query()->create($attributes);
|
||||
}
|
||||
|
||||
return $role;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a role based on an array of parameters.
|
||||
*
|
||||
* @return RoleContract|Role|null
|
||||
*/
|
||||
protected static function findByParam(array $params = []): ?RoleContract
|
||||
{
|
||||
$query = static::query();
|
||||
|
||||
$registrar = app(PermissionRegistrar::class);
|
||||
|
||||
if ($registrar->teams) {
|
||||
$teamsKey = $registrar->teamsKey;
|
||||
|
||||
$query->where(fn ($q) => $q->whereNull($teamsKey)
|
||||
->orWhere($teamsKey, $params[$teamsKey] ?? getPermissionsTeamId())
|
||||
);
|
||||
unset($params[$teamsKey]);
|
||||
}
|
||||
|
||||
foreach ($params as $key => $value) {
|
||||
$query->where($key, $value);
|
||||
}
|
||||
|
||||
return $query->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the role may perform the given permission.
|
||||
*
|
||||
* @param string|int|\Spatie\Permission\Contracts\Permission|BackedEnum $permission
|
||||
*
|
||||
* @throws PermissionDoesNotExist|GuardDoesNotMatch
|
||||
*/
|
||||
public function hasPermissionTo($permission, ?string $guardName = null): bool
|
||||
{
|
||||
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, $guardName ? collect([$guardName]) : $this->getGuardNames());
|
||||
}
|
||||
|
||||
return $this->loadMissing('permissions')->permissions
|
||||
->contains($permission->getKeyName(), $permission->getKey());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,418 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Permission;
|
||||
|
||||
use DateInterval;
|
||||
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 Illuminate\Database\Eloquent\Model;
|
||||
use Spatie\Permission\Contracts\Permission;
|
||||
use Spatie\Permission\Contracts\PermissionsTeamResolver;
|
||||
use Spatie\Permission\Contracts\Role;
|
||||
|
||||
class PermissionRegistrar
|
||||
{
|
||||
protected Repository $cache;
|
||||
|
||||
protected CacheManager $cacheManager;
|
||||
|
||||
protected string $permissionClass;
|
||||
|
||||
protected string $roleClass;
|
||||
|
||||
protected ?string $teamClass = null;
|
||||
|
||||
protected Collection|array|null $permissions = null;
|
||||
|
||||
public string $pivotRole;
|
||||
|
||||
public string $pivotPermission;
|
||||
|
||||
public DateInterval|int $cacheExpirationTime;
|
||||
|
||||
public bool $teams;
|
||||
|
||||
protected PermissionsTeamResolver $teamResolver;
|
||||
|
||||
public string $teamsKey;
|
||||
|
||||
public string $cacheKey;
|
||||
|
||||
private array $cachedRoles = [];
|
||||
|
||||
private array $alias = [];
|
||||
|
||||
private array $except = [];
|
||||
|
||||
private array $wildcardPermissionsIndex = [];
|
||||
|
||||
private bool $isLoadingPermissions = false;
|
||||
|
||||
public function __construct(CacheManager $cacheManager)
|
||||
{
|
||||
$this->permissionClass = config('permission.models.permission');
|
||||
$this->roleClass = config('permission.models.role');
|
||||
$this->teamClass = config('permission.models.team');
|
||||
$this->teamResolver = new (config('permission.team_resolver', DefaultTeamResolver::class));
|
||||
|
||||
$this->cacheManager = $cacheManager;
|
||||
$this->initializeCache();
|
||||
}
|
||||
|
||||
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', 'team_id');
|
||||
|
||||
$this->cacheKey = config('permission.cache.key');
|
||||
|
||||
$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();
|
||||
}
|
||||
|
||||
protected function getCacheStoreFromConfig(): Repository
|
||||
{
|
||||
// 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
|
||||
if ($cacheDriver === 'default') {
|
||||
return $this->cacheManager->store();
|
||||
}
|
||||
|
||||
// if an undefined cache store is specified, fallback to 'array' which is Laravel's closest equiv to 'none'
|
||||
if (! array_key_exists($cacheDriver, config('cache.stores'))) {
|
||||
$cacheDriver = 'array';
|
||||
}
|
||||
|
||||
return $this->cacheManager->store($cacheDriver);
|
||||
}
|
||||
|
||||
public function setPermissionsTeamId(int|string|Model|null $id): void
|
||||
{
|
||||
$this->teamResolver->setPermissionsTeamId($id);
|
||||
}
|
||||
|
||||
public function getPermissionsTeamId(): int|string|null
|
||||
{
|
||||
return $this->teamResolver->getPermissionsTeamId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the permission check method on the gate.
|
||||
* We resolve the Gate fresh here, for benefit of long-running instances.
|
||||
*/
|
||||
public function registerPermissions(Gate $gate): bool
|
||||
{
|
||||
$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, $guard ?? null) ?: null;
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush the cache.
|
||||
*/
|
||||
public function forgetCachedPermissions(): bool
|
||||
{
|
||||
$this->permissions = null;
|
||||
$this->forgetWildcardPermissionIndex();
|
||||
|
||||
return $this->cache->forget($this->cacheKey);
|
||||
}
|
||||
|
||||
public function forgetWildcardPermissionIndex(?Model $record = null): void
|
||||
{
|
||||
if ($record) {
|
||||
unset($this->wildcardPermissionsIndex[$record::class][$record->getKey()]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->wildcardPermissionsIndex = [];
|
||||
}
|
||||
|
||||
public function getWildcardPermissionIndex(Model $record): array
|
||||
{
|
||||
if (isset($this->wildcardPermissionsIndex[$record::class][$record->getKey()])) {
|
||||
return $this->wildcardPermissionsIndex[$record::class][$record->getKey()];
|
||||
}
|
||||
|
||||
return $this->wildcardPermissionsIndex[$record::class][$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;
|
||||
$this->wildcardPermissionsIndex = [];
|
||||
$this->isLoadingPermissions = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(int $retries = 0): void
|
||||
{
|
||||
// First check (without lock) - fast path for already loaded permissions
|
||||
if ($this->permissions) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Prevent concurrent loading using a flag-based lock
|
||||
// This protects against cache stampede and duplicate database queries
|
||||
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($retries);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Set loading flag to prevent concurrent loads
|
||||
$this->isLoadingPermissions = true;
|
||||
|
||||
try {
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the permissions based on the passed params.
|
||||
*/
|
||||
public function getPermissions(array $params = [], bool $onlyOne = false): Collection
|
||||
{
|
||||
$this->loadPermissions();
|
||||
|
||||
$method = $onlyOne ? 'first' : 'filter';
|
||||
|
||||
$permissions = $this->permissions->$method(static function ($permission) use ($params) {
|
||||
return array_all($params, fn ($value, $attr) => $permission->getAttribute($attr) == $value);
|
||||
|
||||
});
|
||||
|
||||
if ($onlyOne) {
|
||||
$permissions = new Collection($permissions ? [$permissions] : []);
|
||||
}
|
||||
|
||||
return $permissions;
|
||||
}
|
||||
|
||||
public function getPermissionClass(): string
|
||||
{
|
||||
return $this->permissionClass;
|
||||
}
|
||||
|
||||
public function setPermissionClass(string $permissionClass): static
|
||||
{
|
||||
$this->permissionClass = $permissionClass;
|
||||
config()->set('permission.models.permission', $permissionClass);
|
||||
app()->bind(Permission::class, $permissionClass);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRoleClass(): string
|
||||
{
|
||||
return $this->roleClass;
|
||||
}
|
||||
|
||||
public function setRoleClass(string $roleClass): static
|
||||
{
|
||||
$this->roleClass = $roleClass;
|
||||
config()->set('permission.models.role', $roleClass);
|
||||
app()->bind(Role::class, $roleClass);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTeamClass(): ?string
|
||||
{
|
||||
return $this->teamClass;
|
||||
}
|
||||
|
||||
public function setTeamClass(?string $teamClass): static
|
||||
{
|
||||
$this->teamClass = $teamClass;
|
||||
config()->set('permission.models.team', $teamClass);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCacheRepository(): Repository
|
||||
{
|
||||
return $this->cache;
|
||||
}
|
||||
|
||||
public function getCacheStore(): Store
|
||||
{
|
||||
return $this->cache->getStore();
|
||||
}
|
||||
|
||||
protected function getPermissionsWithRoles(): Collection
|
||||
{
|
||||
return $this->permissionClass::select()->with('roles')->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes array keys with alias
|
||||
*/
|
||||
private function aliasedArray(array|Model $model): array
|
||||
{
|
||||
return collect(is_array($model) ? $model : $model->getAttributes())->except($this->except)
|
||||
->keyBy(fn ($value, $key) => $this->alias[$key] ?? $key)
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Array for cache alias
|
||||
*/
|
||||
private function aliasModelFields(Model $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(): array
|
||||
{
|
||||
$this->except = config('permission.cache.column_names_except', ['created_at', 'updated_at', 'deleted_at']);
|
||||
|
||||
$permissions = $this->getPermissionsWithRoles()
|
||||
->map(function ($permission) {
|
||||
if (! $this->alias) {
|
||||
$this->aliasModelFields($permission);
|
||||
}
|
||||
|
||||
return $this->aliasedArray($permission) + $this->getSerializedRoleRelation($permission);
|
||||
})->all();
|
||||
$roles = array_values($this->cachedRoles);
|
||||
$this->cachedRoles = [];
|
||||
|
||||
return ['alias' => array_flip($this->alias)] + compact('permissions', 'roles');
|
||||
}
|
||||
|
||||
private function getSerializedRoleRelation(Model $permission): array
|
||||
{
|
||||
if (! $permission->roles->count()) {
|
||||
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->getKey()])) {
|
||||
$this->cachedRoles[$role->getKey()] = $this->aliasedArray($role);
|
||||
}
|
||||
|
||||
return $role->getKey();
|
||||
})->all(),
|
||||
];
|
||||
}
|
||||
|
||||
private function getHydratedPermissionCollection(): Collection
|
||||
{
|
||||
$permissionInstance = (new ($this->getPermissionClass())())->newInstance([], true);
|
||||
|
||||
return Collection::make(array_map(
|
||||
fn ($item) => (clone $permissionInstance)
|
||||
->setRawAttributes($this->aliasedArray(array_diff_key($item, ['r' => 0])), true)
|
||||
->setRelation('roles', $this->getHydratedRoleCollection($item['r'] ?? [])),
|
||||
$this->permissions['permissions']
|
||||
));
|
||||
}
|
||||
|
||||
private function getHydratedRoleCollection(array $roles): Collection
|
||||
{
|
||||
return Collection::make(array_values(
|
||||
array_intersect_key($this->cachedRoles, array_flip($roles))
|
||||
));
|
||||
}
|
||||
|
||||
private function hydrateRolesCache(): void
|
||||
{
|
||||
$roleInstance = (new ($this->getRoleClass())())->newInstance([], true);
|
||||
|
||||
array_map(function ($item) use ($roleInstance) {
|
||||
$role = (clone $roleInstance)
|
||||
->setRawAttributes($this->aliasedArray($item), true);
|
||||
$this->cachedRoles[$role->getKey()] = $role;
|
||||
}, $this->permissions['roles']);
|
||||
|
||||
$this->permissions['roles'] = [];
|
||||
}
|
||||
|
||||
public static function isUid(mixed $value): bool
|
||||
{
|
||||
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 = strlen($value) === 26 && strspn($value, '0123456789ABCDEFGHJKMNPQRSTVWXYZabcdefghjkmnpqrstvwxyz') === 26 && $value[0] <= '7';
|
||||
if ($ulid) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Permission;
|
||||
|
||||
use Composer\InstalledVersions;
|
||||
use Illuminate\Contracts\Auth\Access\Gate;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Contracts\Foundation\Application;
|
||||
use Illuminate\Foundation\Console\AboutCommand;
|
||||
use Illuminate\Routing\Route;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\View\Compilers\BladeCompiler;
|
||||
use Laravel\Octane\Contracts\OperationTerminated;
|
||||
use Spatie\LaravelPackageTools\Package;
|
||||
use Spatie\LaravelPackageTools\PackageServiceProvider;
|
||||
use Spatie\Permission\Contracts\Permission as PermissionContract;
|
||||
use Spatie\Permission\Contracts\Role as RoleContract;
|
||||
|
||||
use function Illuminate\Support\enum_value;
|
||||
|
||||
class PermissionServiceProvider extends PackageServiceProvider
|
||||
{
|
||||
public function configurePackage(Package $package): void
|
||||
{
|
||||
$package
|
||||
->name('laravel-permission')
|
||||
->hasConfigFile('permission')
|
||||
->hasMigrations(['create_permission_tables'])
|
||||
->hasCommands([
|
||||
Commands\CacheResetCommand::class,
|
||||
Commands\CreateRoleCommand::class,
|
||||
Commands\CreatePermissionCommand::class,
|
||||
Commands\ShowCommand::class,
|
||||
Commands\UpgradeForTeamsCommand::class,
|
||||
Commands\AssignRoleCommand::class,
|
||||
]);
|
||||
}
|
||||
|
||||
public function registeringPackage(): void
|
||||
{
|
||||
$this->callAfterResolving('blade.compiler', fn (BladeCompiler $bladeCompiler) => $this->registerBladeExtensions($bladeCompiler));
|
||||
}
|
||||
|
||||
public function packageBooted(): void
|
||||
{
|
||||
$this->registerMacroHelpers();
|
||||
$this->registerModelBindings();
|
||||
$this->registerOctaneListener();
|
||||
|
||||
$this->callAfterResolving(Gate::class, function (Gate $gate, Application $app) {
|
||||
if ($this->app['config']->get('permission.register_permission_check_method')) {
|
||||
$permissionLoader = $app->get(PermissionRegistrar::class);
|
||||
$permissionLoader->clearPermissionsCollection();
|
||||
$permissionLoader->registerPermissions($gate);
|
||||
}
|
||||
});
|
||||
|
||||
$this->app->singleton(PermissionRegistrar::class);
|
||||
$this->registerAbout();
|
||||
}
|
||||
|
||||
public static function bladeMethodWrapper(string $method, mixed $role, ?string $guard = null): bool
|
||||
{
|
||||
return auth($guard)->check() && auth($guard)->user()->{$method}($role);
|
||||
}
|
||||
|
||||
protected function registerBladeExtensions(BladeCompiler $bladeCompiler): void
|
||||
{
|
||||
$bladeMethodWrapper = '\\Spatie\\Permission\\PermissionServiceProvider::bladeMethodWrapper';
|
||||
|
||||
// permission checks
|
||||
$bladeCompiler->if('haspermission', fn () => $bladeMethodWrapper('checkPermissionTo', ...func_get_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 () => '<?php endif; ?>');
|
||||
}
|
||||
|
||||
protected function registerModelBindings(): void
|
||||
{
|
||||
$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']));
|
||||
}
|
||||
|
||||
protected function registerMacroHelpers(): void
|
||||
{
|
||||
Route::macro('role', function ($roles = []) {
|
||||
$roles = Arr::wrap($roles);
|
||||
$roles = array_map(fn ($role) => enum_value($role), $roles);
|
||||
|
||||
/** @var Route $this */
|
||||
return $this->middleware('role:'.implode('|', $roles));
|
||||
});
|
||||
|
||||
Route::macro('permission', function ($permissions = []) {
|
||||
$permissions = Arr::wrap($permissions);
|
||||
$permissions = array_map(fn ($permission) => enum_value($permission), $permissions);
|
||||
|
||||
/** @var Route $this */
|
||||
return $this->middleware('permission:'.implode('|', $permissions));
|
||||
});
|
||||
|
||||
Route::macro('roleOrPermission', function ($rolesOrPermissions = []) {
|
||||
$rolesOrPermissions = Arr::wrap($rolesOrPermissions);
|
||||
$rolesOrPermissions = array_map(fn ($item) => enum_value($item), $rolesOrPermissions);
|
||||
|
||||
/** @var Route $this */
|
||||
return $this->middleware('role_or_permission:'.implode('|', $rolesOrPermissions));
|
||||
});
|
||||
}
|
||||
|
||||
protected function registerOctaneListener(): void
|
||||
{
|
||||
if ($this->app->runningInConsole() || ! $this->app['config']->get('octane.listeners')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$dispatcher = $this->app[Dispatcher::class];
|
||||
// @phpstan-ignore-next-line
|
||||
$dispatcher->listen(function (OperationTerminated $event) {
|
||||
// @phpstan-ignore-next-line
|
||||
$event->sandbox->make(PermissionRegistrar::class)->setPermissionsTeamId(null);
|
||||
});
|
||||
|
||||
if (! $this->app['config']->get('permission.register_octane_reset_listener')) {
|
||||
return;
|
||||
}
|
||||
// @phpstan-ignore-next-line
|
||||
$dispatcher->listen(function (OperationTerminated $event) {
|
||||
// @phpstan-ignore-next-line
|
||||
$event->sandbox->make(PermissionRegistrar::class)->clearPermissionsCollection();
|
||||
});
|
||||
}
|
||||
|
||||
protected function registerAbout(): void
|
||||
{
|
||||
if (! class_exists(InstalledVersions::class) || ! class_exists(AboutCommand::class)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// array format: 'Display Text' => 'boolean-config-key name'
|
||||
$features = [
|
||||
'Teams' => 'teams',
|
||||
'Wildcard-Permissions' => 'enable_wildcard_permission',
|
||||
'Octane-Listener' => 'register_octane_reset_listener',
|
||||
'Passport' => 'use_passport_client_credentials',
|
||||
];
|
||||
|
||||
$config = $this->app['config'];
|
||||
|
||||
AboutCommand::add('Spatie Permissions', static fn () => [
|
||||
'Features Enabled' => collect($features)
|
||||
->filter(fn (string $feature, string $name): bool => $config->get("permission.{$feature}"))
|
||||
->keys()
|
||||
->whenEmpty(fn (Collection $collection) => $collection->push('Default'))
|
||||
->join(', '),
|
||||
'Version' => InstalledVersions::getPrettyVersion('spatie/laravel-permission'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Permission\Support;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Spatie\Permission\Contracts\Wildcard;
|
||||
use Spatie\Permission\Exceptions\TeamModelNotConfigured;
|
||||
use Spatie\Permission\Exceptions\TeamsNotEnabled;
|
||||
use Spatie\Permission\PermissionRegistrar;
|
||||
use Spatie\Permission\WildcardPermission;
|
||||
|
||||
class Config
|
||||
{
|
||||
public static function teamsEnabled(): bool
|
||||
{
|
||||
return app(PermissionRegistrar::class)->teams;
|
||||
}
|
||||
|
||||
public static function ensureTeamsEnabled(): void
|
||||
{
|
||||
if (! self::teamsEnabled()) {
|
||||
throw TeamsNotEnabled::create();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return class-string<Model>
|
||||
*/
|
||||
public static function teamModel(): string
|
||||
{
|
||||
self::ensureTeamsEnabled();
|
||||
|
||||
$teamModel = app(PermissionRegistrar::class)->getTeamClass();
|
||||
|
||||
if (! $teamModel) {
|
||||
throw TeamModelNotConfigured::create();
|
||||
}
|
||||
|
||||
return $teamModel;
|
||||
}
|
||||
|
||||
public static function modelHasRolesTable(): string
|
||||
{
|
||||
return config('permission.table_names.model_has_roles');
|
||||
}
|
||||
|
||||
public static function modelHasPermissionsTable(): string
|
||||
{
|
||||
return config('permission.table_names.model_has_permissions');
|
||||
}
|
||||
|
||||
public static function roleHasPermissionsTable(): string
|
||||
{
|
||||
return config('permission.table_names.role_has_permissions');
|
||||
}
|
||||
|
||||
public static function rolesTable(): string
|
||||
{
|
||||
return config('permission.table_names.roles');
|
||||
}
|
||||
|
||||
public static function permissionsTable(): string
|
||||
{
|
||||
return config('permission.table_names.permissions');
|
||||
}
|
||||
|
||||
public static function morphKey(): string
|
||||
{
|
||||
return config('permission.column_names.model_morph_key');
|
||||
}
|
||||
|
||||
public static function teamForeignKey(): string
|
||||
{
|
||||
return app(PermissionRegistrar::class)->teamsKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return class-string<Model>
|
||||
*/
|
||||
public static function roleModel(): string
|
||||
{
|
||||
return app(PermissionRegistrar::class)->getRoleClass();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return class-string<Model>
|
||||
*/
|
||||
public static function permissionModel(): string
|
||||
{
|
||||
return app(PermissionRegistrar::class)->getPermissionClass();
|
||||
}
|
||||
|
||||
public static function eventsEnabled(): bool
|
||||
{
|
||||
return (bool) config('permission.events_enabled');
|
||||
}
|
||||
|
||||
public static function usePassportClientCredentials(): bool
|
||||
{
|
||||
return (bool) config('permission.use_passport_client_credentials');
|
||||
}
|
||||
|
||||
public static function displayRoleInException(): bool
|
||||
{
|
||||
return (bool) config('permission.display_role_in_exception');
|
||||
}
|
||||
|
||||
public static function displayPermissionInException(): bool
|
||||
{
|
||||
return (bool) config('permission.display_permission_in_exception');
|
||||
}
|
||||
|
||||
public static function wildcardPermissionsEnabled(): bool
|
||||
{
|
||||
return (bool) config('permission.enable_wildcard_permission');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return class-string<Wildcard>
|
||||
*/
|
||||
public static function wildcardPermissionClass(): string
|
||||
{
|
||||
return config('permission.wildcard_permission', WildcardPermission::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Permission\Traits;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphToMany;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Spatie\Permission\PermissionRegistrar;
|
||||
use Spatie\Permission\Support\Config;
|
||||
|
||||
trait HasAssignedModels
|
||||
{
|
||||
/**
|
||||
* Assign this role to the given models without removing existing assignments.
|
||||
*
|
||||
* @param Model|int|string|array<int, Model|int|string>|Collection<int, Model|int|string> $models
|
||||
* @return $this
|
||||
*/
|
||||
public function assignToModels(array|Collection|Model|int|string $models, ?string $modelClass = null): static
|
||||
{
|
||||
if (! $this->exists) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$teamPivot = $this->teamPivot();
|
||||
|
||||
foreach ($this->groupModelsByMorphClass($models, $modelClass) as $morphClass => $ids) {
|
||||
$relation = $this->relationForModel($morphClass);
|
||||
$existingIds = $relation->pluck(Config::morphKey())->all();
|
||||
|
||||
$relation->attach(array_diff($ids, $existingIds), $teamPivot);
|
||||
}
|
||||
|
||||
$this->unsetRelation('users');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove this role from the given models.
|
||||
*
|
||||
* @param Model|int|string|array<int, Model|int|string>|Collection<int, Model|int|string> $models
|
||||
* @return $this
|
||||
*/
|
||||
public function removeFromModels(array|Collection|Model|int|string $models, ?string $modelClass = null): static
|
||||
{
|
||||
foreach ($this->groupModelsByMorphClass($models, $modelClass) as $morphClass => $ids) {
|
||||
$this->relationForModel($morphClass)->detach($ids);
|
||||
}
|
||||
|
||||
$this->unsetRelation('users');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all current model associations and set the given ones.
|
||||
*
|
||||
* @param Model|int|string|array<int, Model|int|string>|Collection<int, Model|int|string> $models
|
||||
* @return $this
|
||||
*/
|
||||
public function syncModels(array|Collection|Model|int|string $models, ?string $modelClass = null): static
|
||||
{
|
||||
if ($this->exists) {
|
||||
$this->newPivotQueryForRole()->delete();
|
||||
}
|
||||
|
||||
$teamPivot = $this->teamPivot();
|
||||
|
||||
foreach ($this->groupModelsByMorphClass($models, $modelClass) as $morphClass => $ids) {
|
||||
$this->relationForModel($morphClass)->attach($ids, $teamPivot);
|
||||
}
|
||||
|
||||
$this->unsetRelation('users');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a morphedByMany relation pointing to a specific model class.
|
||||
*/
|
||||
protected function relationForModel(string $modelClass): MorphToMany
|
||||
{
|
||||
return $this->morphedByMany(
|
||||
$modelClass,
|
||||
'model',
|
||||
Config::modelHasRolesTable(),
|
||||
app(PermissionRegistrar::class)->pivotRole,
|
||||
Config::morphKey(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Group the given models by class, deduplicating IDs within each class.
|
||||
*
|
||||
* @param Model|int|string|array<int, Model|int|string>|Collection<int, Model|int|string> $models
|
||||
* @return array<class-string, list<int|string>>
|
||||
*/
|
||||
private function groupModelsByMorphClass(
|
||||
array|Collection|Model|int|string $models,
|
||||
?string $modelClass,
|
||||
): array {
|
||||
$defaultModelClass = $this->resolveDefaultModelClass($modelClass);
|
||||
|
||||
return collect(Arr::flatten(Arr::wrap($models)))
|
||||
->reject(fn ($value) => $value === null || $value === '')
|
||||
->reduce(function (array $grouped, $value) use ($defaultModelClass) {
|
||||
$class = $value instanceof Model ? $value::class : $defaultModelClass;
|
||||
$id = $value instanceof Model ? $value->getKey() : $value;
|
||||
|
||||
if (! in_array($id, $grouped[$class] ?? [], strict: true)) {
|
||||
$grouped[$class][] = $id;
|
||||
}
|
||||
|
||||
return $grouped;
|
||||
}, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the model class to use when raw IDs are passed.
|
||||
*/
|
||||
private function resolveDefaultModelClass(?string $modelClass): string
|
||||
{
|
||||
return $modelClass
|
||||
?? config('permission.models.default_model')
|
||||
?? getModelForGuard($this->attributes['guard_name'] ?? config('auth.defaults.guard'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, int|string|null>
|
||||
*/
|
||||
private function teamPivot(): array
|
||||
{
|
||||
if (! Config::teamsEnabled()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [Config::teamForeignKey() => getPermissionsTeamId()];
|
||||
}
|
||||
|
||||
private function newPivotQueryForRole(): Builder
|
||||
{
|
||||
return $this->getConnection()
|
||||
->table(Config::modelHasRolesTable())
|
||||
->where(app(PermissionRegistrar::class)->pivotRole, $this->getKey());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,577 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Permission\Traits;
|
||||
|
||||
use BackedEnum;
|
||||
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;
|
||||
use Spatie\Permission\Contracts\Wildcard;
|
||||
use Spatie\Permission\Events\PermissionAttachedEvent;
|
||||
use Spatie\Permission\Events\PermissionDetachedEvent;
|
||||
use Spatie\Permission\Exceptions\GuardDoesNotMatch;
|
||||
use Spatie\Permission\Exceptions\PermissionDoesNotExist;
|
||||
use Spatie\Permission\Exceptions\WildcardPermissionInvalidArgument;
|
||||
use Spatie\Permission\Exceptions\WildcardPermissionNotImplementsContract;
|
||||
use Spatie\Permission\Guard;
|
||||
use Spatie\Permission\PermissionRegistrar;
|
||||
use Spatie\Permission\Support\Config;
|
||||
|
||||
use function Illuminate\Support\enum_value;
|
||||
|
||||
trait HasPermissions
|
||||
{
|
||||
private ?string $permissionClass = null;
|
||||
|
||||
private ?string $wildcardClass = null;
|
||||
|
||||
private array $wildcardPermissionsIndex;
|
||||
|
||||
public static function bootHasPermissions(): void
|
||||
{
|
||||
static::deleting(function ($model) {
|
||||
if (method_exists($model, 'isForceDeleting') && ! $model->isForceDeleting()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$teams = app(PermissionRegistrar::class)->teams;
|
||||
app(PermissionRegistrar::class)->teams = false;
|
||||
if (! $model instanceof Permission) {
|
||||
$model->permissions()->detach();
|
||||
}
|
||||
if ($model instanceof Role) {
|
||||
$model->users()->detach();
|
||||
}
|
||||
app(PermissionRegistrar::class)->teams = $teams;
|
||||
});
|
||||
}
|
||||
|
||||
public function getPermissionClass(): string
|
||||
{
|
||||
if (! $this->permissionClass) {
|
||||
$this->permissionClass = app(PermissionRegistrar::class)->getPermissionClass();
|
||||
}
|
||||
|
||||
return $this->permissionClass;
|
||||
}
|
||||
|
||||
public function getWildcardClass(): string
|
||||
{
|
||||
if (! is_null($this->wildcardClass)) {
|
||||
return $this->wildcardClass;
|
||||
}
|
||||
|
||||
$this->wildcardClass = '';
|
||||
|
||||
if (Config::wildcardPermissionsEnabled()) {
|
||||
$this->wildcardClass = Config::wildcardPermissionClass();
|
||||
|
||||
if (! is_subclass_of($this->wildcardClass, Wildcard::class)) {
|
||||
throw WildcardPermissionNotImplementsContract::create();
|
||||
}
|
||||
}
|
||||
|
||||
return $this->wildcardClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* A model may have multiple direct permissions.
|
||||
*/
|
||||
public function permissions(): BelongsToMany
|
||||
{
|
||||
$relation = $this->morphToMany(
|
||||
Config::permissionModel(),
|
||||
'model',
|
||||
Config::modelHasPermissionsTable(),
|
||||
Config::morphKey(),
|
||||
app(PermissionRegistrar::class)->pivotPermission
|
||||
);
|
||||
|
||||
if (! Config::teamsEnabled()) {
|
||||
return $relation;
|
||||
}
|
||||
|
||||
$teamsKey = Config::teamForeignKey();
|
||||
$relation->withPivot($teamsKey);
|
||||
|
||||
return $relation->wherePivot($teamsKey, getPermissionsTeamId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope the model query to certain permissions only.
|
||||
*
|
||||
* @param string|int|array|Permission|Collection|BackedEnum $permissions
|
||||
*/
|
||||
public function scopePermission(Builder $query, $permissions, bool $without = false): Builder
|
||||
{
|
||||
$permissions = $this->convertToPermissionModels($permissions);
|
||||
|
||||
$permissionKey = (new ($this->getPermissionClass())())->getKeyName();
|
||||
$roleKey = (new ($this instanceof Role ? static::class : $this->getRoleClass())())->getKeyName();
|
||||
|
||||
$rolesWithPermissions = $this instanceof Role ? [] : array_unique(
|
||||
array_reduce($permissions, fn ($result, $permission) => array_merge($result, $permission->roles->all()), [])
|
||||
);
|
||||
|
||||
return $query->where(fn (Builder $query) => $query
|
||||
->{! $without ? 'whereHas' : 'whereDoesntHave'}('permissions', fn (Builder $subQuery) => $subQuery
|
||||
->whereIn(Config::permissionsTable().".$permissionKey", array_column($permissions, $permissionKey))
|
||||
)
|
||||
->when(count($rolesWithPermissions), fn ($whenQuery) => $whenQuery
|
||||
->{! $without ? 'orWhereHas' : 'whereDoesntHave'}('roles', fn (Builder $subQuery) => $subQuery
|
||||
->whereIn(Config::rolesTable().".$roleKey", array_column($rolesWithPermissions, $roleKey))
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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): Builder
|
||||
{
|
||||
return $this->scopePermission($query, $permissions, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|int|array|Permission|Collection|BackedEnum $permissions
|
||||
*
|
||||
* @throws PermissionDoesNotExist
|
||||
*/
|
||||
protected function convertToPermissionModels($permissions): array
|
||||
{
|
||||
if ($permissions instanceof Collection) {
|
||||
$permissions = $permissions->all();
|
||||
}
|
||||
|
||||
return array_map(function ($permission) {
|
||||
if ($permission instanceof Permission) {
|
||||
return $permission;
|
||||
}
|
||||
|
||||
$permission = enum_value($permission);
|
||||
|
||||
$method = is_int($permission) || PermissionRegistrar::isUid($permission) ? 'findById' : 'findByName';
|
||||
|
||||
return $this->getPermissionClass()::{$method}($permission, $this->getDefaultGuardName());
|
||||
}, Arr::wrap($permissions));
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a permission.
|
||||
*
|
||||
* @param string|int|Permission|BackedEnum $permission
|
||||
*
|
||||
* @throws PermissionDoesNotExist
|
||||
*/
|
||||
public function filterPermission($permission, ?string $guardName = null): Permission
|
||||
{
|
||||
$permission = enum_value($permission);
|
||||
|
||||
if (is_int($permission) || PermissionRegistrar::isUid($permission)) {
|
||||
$permission = $this->getPermissionClass()::findById(
|
||||
$permission,
|
||||
$guardName ?? $this->getDefaultGuardName()
|
||||
);
|
||||
}
|
||||
|
||||
if (is_string($permission)) {
|
||||
$permission = $this->getPermissionClass()::findByName(
|
||||
$permission,
|
||||
$guardName ?? $this->getDefaultGuardName()
|
||||
);
|
||||
}
|
||||
|
||||
if (! $permission instanceof Permission) {
|
||||
throw new PermissionDoesNotExist;
|
||||
}
|
||||
|
||||
return $permission;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the model may perform the given permission.
|
||||
*
|
||||
* @param string|int|Permission|BackedEnum $permission
|
||||
*
|
||||
* @throws PermissionDoesNotExist
|
||||
*/
|
||||
public function hasPermissionTo($permission, ?string $guardName = null): bool
|
||||
{
|
||||
if ($this->getWildcardClass()) {
|
||||
return $this->hasWildcardPermission($permission, $guardName);
|
||||
}
|
||||
|
||||
$permission = $this->filterPermission($permission, $guardName);
|
||||
|
||||
return $this->hasDirectPermission($permission) || $this->hasPermissionViaRole($permission);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a wildcard permission against all permissions of a user.
|
||||
*
|
||||
* @param string|int|Permission|BackedEnum $permission
|
||||
*/
|
||||
protected function hasWildcardPermission($permission, ?string $guardName = null): bool
|
||||
{
|
||||
$guardName = $guardName ?? $this->getDefaultGuardName();
|
||||
|
||||
$permission = enum_value($permission);
|
||||
|
||||
if (is_int($permission) || PermissionRegistrar::isUid($permission)) {
|
||||
$permission = $this->getPermissionClass()::findById($permission, $guardName);
|
||||
}
|
||||
|
||||
if ($permission instanceof Permission) {
|
||||
$guardName = $permission->guard_name ?? $guardName;
|
||||
$permission = $permission->name;
|
||||
}
|
||||
|
||||
if (! is_string($permission)) {
|
||||
throw WildcardPermissionInvalidArgument::create();
|
||||
}
|
||||
|
||||
return app($this->getWildcardClass(), ['record' => $this])->implies(
|
||||
$permission,
|
||||
$guardName,
|
||||
app(PermissionRegistrar::class)->getWildcardPermissionIndex($this),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* An alias to hasPermissionTo(), but avoids throwing an exception.
|
||||
*
|
||||
* @param string|int|Permission|BackedEnum $permission
|
||||
*/
|
||||
public function checkPermissionTo($permission, ?string $guardName = null): bool
|
||||
{
|
||||
try {
|
||||
return $this->hasPermissionTo($permission, $guardName);
|
||||
} catch (PermissionDoesNotExist $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the model has any of the given permissions.
|
||||
*
|
||||
* @param string|int|array|Permission|Collection|BackedEnum ...$permissions
|
||||
*/
|
||||
public function hasAnyPermission(...$permissions): bool
|
||||
{
|
||||
$permissions = collect($permissions)->flatten();
|
||||
|
||||
foreach ($permissions as $permission) {
|
||||
if ($this->checkPermissionTo($permission)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the model has all of the given permissions.
|
||||
*
|
||||
* @param string|int|array|Permission|Collection|BackedEnum ...$permissions
|
||||
*/
|
||||
public function hasAllPermissions(...$permissions): bool
|
||||
{
|
||||
$permissions = collect($permissions)->flatten();
|
||||
|
||||
foreach ($permissions as $permission) {
|
||||
if (! $this->checkPermissionTo($permission)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the model has, via roles, the given permission.
|
||||
*/
|
||||
protected function hasPermissionViaRole(Permission $permission): bool
|
||||
{
|
||||
if ($this instanceof Role) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->hasRole($permission->roles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the model has the given permission.
|
||||
*
|
||||
* @param string|int|Permission|BackedEnum $permission
|
||||
*
|
||||
* @throws PermissionDoesNotExist
|
||||
*/
|
||||
public function hasDirectPermission($permission): bool
|
||||
{
|
||||
$permission = $this->filterPermission($permission);
|
||||
|
||||
return $this->loadMissing('permissions')->permissions
|
||||
->contains($permission->getKeyName(), $permission->getKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all the permissions the model has via roles.
|
||||
*/
|
||||
public function getPermissionsViaRoles(): Collection
|
||||
{
|
||||
if ($this instanceof Role || $this instanceof Permission) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
return $this->loadMissing('roles', 'roles.permissions')
|
||||
->roles->flatMap(fn ($role) => $role->permissions)
|
||||
->sort()->values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all the permissions the model has, both directly and via roles.
|
||||
*/
|
||||
public function getAllPermissions(): Collection
|
||||
{
|
||||
/** @var Collection $permissions */
|
||||
$permissions = $this->permissions;
|
||||
|
||||
if (! $this instanceof Permission) {
|
||||
$permissions = $permissions->merge($this->getPermissionsViaRoles());
|
||||
}
|
||||
|
||||
return $permissions->sort()->values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns array of permissions ids
|
||||
*
|
||||
* @param string|int|array|Permission|Collection|BackedEnum $permissions
|
||||
*/
|
||||
private function collectPermissions(...$permissions): array
|
||||
{
|
||||
return collect($permissions)
|
||||
->flatten()
|
||||
->reduce(function ($array, $permission) {
|
||||
if ($permission === null || $permission === '') {
|
||||
return $array;
|
||||
}
|
||||
|
||||
$permission = $this->getStoredPermission($permission);
|
||||
if (! $permission instanceof Permission) {
|
||||
return $array;
|
||||
}
|
||||
|
||||
if (! in_array($permission->getKey(), $array)) {
|
||||
$this->ensureModelSharesGuard($permission);
|
||||
$array[] = $permission->getKey();
|
||||
}
|
||||
|
||||
return $array;
|
||||
}, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Grant the given permission(s) to a role.
|
||||
*
|
||||
* @param string|int|array|Permission|Collection|BackedEnum $permissions
|
||||
* @return $this
|
||||
*/
|
||||
public function givePermissionTo(...$permissions): static
|
||||
{
|
||||
$permissions = $this->collectPermissions($permissions);
|
||||
|
||||
$model = $this->getModel();
|
||||
$teamPivot = app(PermissionRegistrar::class)->teams && ! $this instanceof Role ?
|
||||
[app(PermissionRegistrar::class)->teamsKey => getPermissionsTeamId()] : [];
|
||||
|
||||
if ($model->exists) {
|
||||
$currentPermissions = $this->permissions->map(fn ($permission) => $permission->getKey())->toArray();
|
||||
|
||||
$this->permissions()->attach(array_diff($permissions, $currentPermissions), $teamPivot);
|
||||
$model->unsetRelation('permissions');
|
||||
} else {
|
||||
$class = $model::class;
|
||||
$saved = false;
|
||||
|
||||
$class::saved(
|
||||
function ($object) use ($permissions, $model, $teamPivot, &$saved) {
|
||||
if ($saved || $model->getKey() != $object->getKey()) {
|
||||
return;
|
||||
}
|
||||
$model->permissions()->attach($permissions, $teamPivot);
|
||||
$model->unsetRelation('permissions');
|
||||
$saved = true;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if ($this instanceof Role) {
|
||||
$this->forgetCachedPermissions();
|
||||
}
|
||||
|
||||
if (Config::eventsEnabled()) {
|
||||
event(new PermissionAttachedEvent($this->getModel(), $permissions));
|
||||
}
|
||||
|
||||
$this->forgetWildcardPermissionIndex();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function forgetWildcardPermissionIndex(): void
|
||||
{
|
||||
app(PermissionRegistrar::class)->forgetWildcardPermissionIndex(
|
||||
$this instanceof Role ? null : $this,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all current permissions and set the given ones.
|
||||
*
|
||||
* @param string|int|array|Permission|Collection|BackedEnum $permissions
|
||||
* @return $this
|
||||
*/
|
||||
public function syncPermissions(...$permissions): static
|
||||
{
|
||||
if ($this->getModel()->exists) {
|
||||
$this->collectPermissions($permissions);
|
||||
$this->permissions()->detach();
|
||||
$this->setRelation('permissions', collect());
|
||||
}
|
||||
|
||||
return $this->givePermissionTo($permissions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke the given permission(s).
|
||||
*
|
||||
* @param Permission|Permission[]|string|string[]|BackedEnum $permission
|
||||
* @return $this
|
||||
*/
|
||||
public function revokePermissionTo($permission): static
|
||||
{
|
||||
$storedPermission = $this->getStoredPermission($permission);
|
||||
|
||||
$this->permissions()->detach($storedPermission);
|
||||
|
||||
if ($this instanceof Role) {
|
||||
$this->forgetCachedPermissions();
|
||||
}
|
||||
|
||||
if (Config::eventsEnabled()) {
|
||||
event(new PermissionDetachedEvent($this->getModel(), $storedPermission));
|
||||
}
|
||||
|
||||
$this->forgetWildcardPermissionIndex();
|
||||
|
||||
$this->unsetRelation('permissions');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPermissionNames(): Collection
|
||||
{
|
||||
return $this->permissions->pluck('name');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|int|array|Permission|Collection|BackedEnum $permissions
|
||||
* @return Permission|Permission[]|Collection
|
||||
*/
|
||||
protected function getStoredPermission($permissions)
|
||||
{
|
||||
$permissions = enum_value($permissions);
|
||||
|
||||
if (is_int($permissions) || PermissionRegistrar::isUid($permissions)) {
|
||||
return $this->getPermissionClass()::findById($permissions, $this->getDefaultGuardName());
|
||||
}
|
||||
|
||||
if (is_string($permissions)) {
|
||||
return $this->getPermissionClass()::findByName($permissions, $this->getDefaultGuardName());
|
||||
}
|
||||
|
||||
if (is_array($permissions)) {
|
||||
$permissions = array_map(fn ($permission) => $permission instanceof Permission ? $permission->name : enum_value($permission), $permissions);
|
||||
|
||||
return $this->getPermissionClass()::whereIn('name', $permissions)
|
||||
->whereIn('guard_name', $this->getGuardNames())
|
||||
->get();
|
||||
}
|
||||
|
||||
return $permissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Permission|Role $roleOrPermission
|
||||
*
|
||||
* @throws GuardDoesNotMatch
|
||||
*/
|
||||
protected function ensureModelSharesGuard($roleOrPermission): void
|
||||
{
|
||||
if (! $this->getGuardNames()->contains($roleOrPermission->guard_name)) {
|
||||
throw GuardDoesNotMatch::create($roleOrPermission->guard_name, $this->getGuardNames());
|
||||
}
|
||||
}
|
||||
|
||||
protected function getGuardNames(): Collection
|
||||
{
|
||||
return Guard::getNames($this);
|
||||
}
|
||||
|
||||
protected function getDefaultGuardName(): string
|
||||
{
|
||||
return Guard::getDefaultName($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forget the cached permissions.
|
||||
*/
|
||||
public function forgetCachedPermissions(): void
|
||||
{
|
||||
app(PermissionRegistrar::class)->forgetCachedPermissions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the model has All of the requested Direct permissions.
|
||||
*
|
||||
* @param string|int|array|Permission|Collection|BackedEnum ...$permissions
|
||||
*/
|
||||
public function hasAllDirectPermissions(...$permissions): bool
|
||||
{
|
||||
$permissions = collect($permissions)->flatten();
|
||||
|
||||
foreach ($permissions as $permission) {
|
||||
if (! $this->hasDirectPermission($permission)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the model has Any of the requested Direct permissions.
|
||||
*
|
||||
* @param string|int|array|Permission|Collection|BackedEnum ...$permissions
|
||||
*/
|
||||
public function hasAnyDirectPermission(...$permissions): bool
|
||||
{
|
||||
$permissions = collect($permissions)->flatten();
|
||||
|
||||
foreach ($permissions as $permission) {
|
||||
if ($this->hasDirectPermission($permission)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,497 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Permission\Traits;
|
||||
|
||||
use BackedEnum;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Spatie\Permission\Contracts\Permission;
|
||||
use Spatie\Permission\Contracts\Role;
|
||||
use Spatie\Permission\Events\RoleAttachedEvent;
|
||||
use Spatie\Permission\Events\RoleDetachedEvent;
|
||||
use Spatie\Permission\PermissionRegistrar;
|
||||
use Spatie\Permission\Support\Config;
|
||||
use TypeError;
|
||||
|
||||
use function Illuminate\Support\enum_value;
|
||||
|
||||
trait HasRoles
|
||||
{
|
||||
use HasPermissions;
|
||||
|
||||
private ?string $roleClass = null;
|
||||
|
||||
public static function bootHasRoles(): void
|
||||
{
|
||||
static::deleting(function ($model) {
|
||||
if (method_exists($model, 'isForceDeleting') && ! $model->isForceDeleting()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$teams = app(PermissionRegistrar::class)->teams;
|
||||
app(PermissionRegistrar::class)->teams = false;
|
||||
$model->roles()->detach();
|
||||
if ($model instanceof Permission) {
|
||||
$model->users()->detach();
|
||||
}
|
||||
app(PermissionRegistrar::class)->teams = $teams;
|
||||
});
|
||||
}
|
||||
|
||||
public function getRoleClass(): string
|
||||
{
|
||||
if (! $this->roleClass) {
|
||||
$this->roleClass = app(PermissionRegistrar::class)->getRoleClass();
|
||||
}
|
||||
|
||||
return $this->roleClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* A model may have multiple roles.
|
||||
*/
|
||||
public function roles(): BelongsToMany
|
||||
{
|
||||
$relation = $this->morphToMany(
|
||||
Config::roleModel(),
|
||||
'model',
|
||||
Config::modelHasRolesTable(),
|
||||
Config::morphKey(),
|
||||
app(PermissionRegistrar::class)->pivotRole
|
||||
);
|
||||
|
||||
if (! Config::teamsEnabled()) {
|
||||
return $relation;
|
||||
}
|
||||
|
||||
$teamsKey = Config::teamForeignKey();
|
||||
$relation->withPivot($teamsKey);
|
||||
$teamField = Config::rolesTable().'.'.$teamsKey;
|
||||
|
||||
return $relation->wherePivot($teamsKey, getPermissionsTeamId())
|
||||
->where(fn ($q) => $q->whereNull($teamField)->orWhere($teamField, getPermissionsTeamId()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope the model query to certain roles only.
|
||||
*
|
||||
* @param string|int|array|Role|Collection|BackedEnum $roles
|
||||
*/
|
||||
public function scopeRole(Builder $query, $roles, ?string $guard = null, bool $without = false): Builder
|
||||
{
|
||||
if ($roles instanceof Collection) {
|
||||
$roles = $roles->all();
|
||||
}
|
||||
|
||||
$roles = array_map(function ($role) use ($guard) {
|
||||
if ($role instanceof Role) {
|
||||
return $role;
|
||||
}
|
||||
|
||||
$role = enum_value($role);
|
||||
|
||||
$method = is_int($role) || PermissionRegistrar::isUid($role) ? 'findById' : 'findByName';
|
||||
|
||||
return $this->getRoleClass()::{$method}($role, $guard ?: $this->getDefaultGuardName());
|
||||
}, Arr::wrap($roles));
|
||||
|
||||
$key = (new ($this->getRoleClass())())->getKeyName();
|
||||
|
||||
return $query->{! $without ? 'whereHas' : 'whereDoesntHave'}('roles', fn (Builder $subQuery) => $subQuery
|
||||
->whereIn(Config::rolesTable().".$key", array_column($roles, $key))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope the model query to only those without certain roles.
|
||||
*
|
||||
* @param string|int|array|Role|Collection|BackedEnum $roles
|
||||
*/
|
||||
public function scopeWithoutRole(Builder $query, $roles, ?string $guard = null): Builder
|
||||
{
|
||||
return $this->scopeRole($query, $roles, $guard, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* A model may be part of multiple teams.
|
||||
*
|
||||
* When the teams feature is disabled this returns an empty BelongsToMany so
|
||||
* tooling that introspects model relations (e.g. ide-helper:models) does not
|
||||
* break. Querying it is a no-op and produces no rows.
|
||||
*/
|
||||
public function teams(): BelongsToMany
|
||||
{
|
||||
if (! Config::teamsEnabled()) {
|
||||
return $this->morphToMany(
|
||||
Config::permissionModel(),
|
||||
'model',
|
||||
Config::modelHasRolesTable(),
|
||||
Config::morphKey(),
|
||||
Config::teamForeignKey()
|
||||
)->whereRaw('1 = 0');
|
||||
}
|
||||
|
||||
return $this->morphToMany(
|
||||
Config::teamModel(),
|
||||
'model',
|
||||
Config::modelHasRolesTable(),
|
||||
Config::morphKey(),
|
||||
Config::teamForeignKey()
|
||||
)->distinct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope the model query to certain teams only.
|
||||
*
|
||||
* @param int|string|array|Model|Collection $teams
|
||||
*/
|
||||
public function scopeTeam(Builder $query, $teams, bool $without = false): Builder
|
||||
{
|
||||
$teamModel = Config::teamModel();
|
||||
|
||||
if ($teams instanceof Collection) {
|
||||
$teams = $teams->all();
|
||||
}
|
||||
|
||||
$teamIds = array_map(
|
||||
fn ($team) => $team instanceof $teamModel ? $team->getKey() : $team,
|
||||
Arr::wrap($teams),
|
||||
);
|
||||
|
||||
$pivotTable = Config::modelHasRolesTable();
|
||||
$morphKey = Config::morphKey();
|
||||
$teamsKey = Config::teamForeignKey();
|
||||
|
||||
return $query->{! $without ? 'whereExists' : 'whereNotExists'}(
|
||||
fn ($subQuery) => $subQuery
|
||||
->from($pivotTable)
|
||||
->whereColumn($morphKey, $query->getModel()->getQualifiedKeyName())
|
||||
->where('model_type', $query->getModel()->getMorphClass())
|
||||
->whereIn($teamsKey, $teamIds)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope the model query to those without certain teams.
|
||||
*
|
||||
* @param int|string|array|Model|Collection $teams
|
||||
*/
|
||||
public function scopeWithoutTeam(Builder $query, $teams): Builder
|
||||
{
|
||||
return $this->scopeTeam($query, $teams, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns array of role ids
|
||||
*
|
||||
* @param string|int|array|Role|Collection|BackedEnum $roles
|
||||
*/
|
||||
private function collectRoles(...$roles): array
|
||||
{
|
||||
return collect($roles)
|
||||
->flatten()
|
||||
->reduce(function ($array, $role) {
|
||||
if ($role === null || $role === '') {
|
||||
return $array;
|
||||
}
|
||||
|
||||
$role = $this->getStoredRole($role);
|
||||
|
||||
if (! in_array($role->getKey(), $array)) {
|
||||
$this->ensureModelSharesGuard($role);
|
||||
$array[] = $role->getKey();
|
||||
}
|
||||
|
||||
return $array;
|
||||
}, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign the given role to the model.
|
||||
*
|
||||
* @param string|int|array|Role|Collection|BackedEnum ...$roles
|
||||
* @return $this
|
||||
*/
|
||||
public function assignRole(...$roles): static
|
||||
{
|
||||
$roles = $this->collectRoles($roles);
|
||||
|
||||
$model = $this->getModel();
|
||||
$teamPivot = app(PermissionRegistrar::class)->teams && ! $this instanceof Permission ?
|
||||
[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);
|
||||
$model->unsetRelation('roles');
|
||||
} else {
|
||||
$class = $model::class;
|
||||
$saved = false;
|
||||
|
||||
$class::saved(
|
||||
function ($object) use ($roles, $model, $teamPivot, &$saved) {
|
||||
if ($saved || $model->getKey() != $object->getKey()) {
|
||||
return;
|
||||
}
|
||||
$model->roles()->attach($roles, $teamPivot);
|
||||
$model->unsetRelation('roles');
|
||||
$saved = true;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if ($this instanceof Permission) {
|
||||
$this->forgetCachedPermissions();
|
||||
}
|
||||
|
||||
$this->forgetWildcardPermissionIndex();
|
||||
|
||||
if (Config::eventsEnabled()) {
|
||||
event(new RoleAttachedEvent($this->getModel(), $roles));
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke the given role from the model.
|
||||
*
|
||||
* @param string|int|array|Role|Collection|BackedEnum ...$role
|
||||
* @return $this
|
||||
*/
|
||||
public function removeRole(...$role): static
|
||||
{
|
||||
$roles = $this->collectRoles($role);
|
||||
|
||||
$this->roles()->detach($roles);
|
||||
|
||||
$this->unsetRelation('roles');
|
||||
|
||||
if ($this instanceof Permission) {
|
||||
$this->forgetCachedPermissions();
|
||||
}
|
||||
|
||||
$this->forgetWildcardPermissionIndex();
|
||||
|
||||
if (Config::eventsEnabled()) {
|
||||
event(new RoleDetachedEvent($this->getModel(), $roles));
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all current roles and set the given ones.
|
||||
*
|
||||
* @param string|int|array|Role|Collection|BackedEnum ...$roles
|
||||
* @return $this
|
||||
*/
|
||||
public function syncRoles(...$roles): static
|
||||
{
|
||||
if ($this->getModel()->exists) {
|
||||
$this->collectRoles($roles);
|
||||
if (Config::eventsEnabled()) {
|
||||
$currentRoles = $this->roles()->get();
|
||||
if ($currentRoles->isNotEmpty()) {
|
||||
$this->removeRole($currentRoles);
|
||||
}
|
||||
} else {
|
||||
$this->roles()->detach();
|
||||
$this->setRelation('roles', collect());
|
||||
}
|
||||
}
|
||||
|
||||
return $this->assignRole($roles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the model has (one of) the given role(s).
|
||||
*
|
||||
* @param string|int|array|Role|Collection|BackedEnum $roles
|
||||
*/
|
||||
public function hasRole($roles, ?string $guard = null): bool
|
||||
{
|
||||
$this->loadMissing('roles');
|
||||
|
||||
if (is_string($roles) && str_contains($roles, '|')) {
|
||||
$roles = $this->convertPipeToArray($roles);
|
||||
}
|
||||
|
||||
if ($roles instanceof BackedEnum) {
|
||||
$roles = $roles->value;
|
||||
|
||||
return $this->roles
|
||||
->when($guard, fn ($q) => $q->where('guard_name', $guard))
|
||||
->pluck('name')
|
||||
->contains(fn ($name) => enum_value($name) == $roles);
|
||||
}
|
||||
|
||||
if (is_int($roles) || PermissionRegistrar::isUid($roles)) {
|
||||
$key = (new ($this->getRoleClass())())->getKeyName();
|
||||
|
||||
return $guard
|
||||
? $this->roles->where('guard_name', $guard)->contains($key, $roles)
|
||||
: $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());
|
||||
}
|
||||
|
||||
if (is_array($roles)) {
|
||||
foreach ($roles as $role) {
|
||||
if ($this->hasRole($role, $guard)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
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().');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the model has any of the given role(s).
|
||||
*
|
||||
* Alias to hasRole() but without Guard controls
|
||||
*
|
||||
* @param string|int|array|Role|Collection|BackedEnum $roles
|
||||
*/
|
||||
public function hasAnyRole(...$roles): bool
|
||||
{
|
||||
return $this->hasRole($roles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the model has all of the given role(s).
|
||||
*
|
||||
* @param string|array|Role|Collection|BackedEnum $roles
|
||||
*/
|
||||
public function hasAllRoles($roles, ?string $guard = null): bool
|
||||
{
|
||||
$this->loadMissing('roles');
|
||||
|
||||
$roles = enum_value($roles);
|
||||
|
||||
if (is_string($roles) && str_contains($roles, '|')) {
|
||||
$roles = $this->convertPipeToArray($roles);
|
||||
}
|
||||
|
||||
if (is_string($roles)) {
|
||||
return $this->hasRole($roles, $guard);
|
||||
}
|
||||
|
||||
if ($roles instanceof Role) {
|
||||
return $this->roles->contains($roles->getKeyName(), $roles->getKey());
|
||||
}
|
||||
|
||||
$roles = collect()->make($roles)->map(fn ($role) => $role instanceof Role ? $role->name : enum_value($role));
|
||||
|
||||
$roleNames = $guard
|
||||
? $this->roles->where('guard_name', $guard)->pluck('name')
|
||||
: $this->getRoleNames();
|
||||
|
||||
$roleNames = $roleNames->transform(fn ($roleName) => enum_value($roleName));
|
||||
|
||||
return $roles->intersect($roleNames) == $roles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the model has exactly all of the given role(s).
|
||||
*
|
||||
* @param string|array|Role|Collection|BackedEnum $roles
|
||||
*/
|
||||
public function hasExactRoles($roles, ?string $guard = null): bool
|
||||
{
|
||||
$this->loadMissing('roles');
|
||||
|
||||
if (is_string($roles) && str_contains($roles, '|')) {
|
||||
$roles = $this->convertPipeToArray($roles);
|
||||
}
|
||||
|
||||
if (is_string($roles)) {
|
||||
$roles = [$roles];
|
||||
}
|
||||
|
||||
if ($roles instanceof Role) {
|
||||
$roles = [$roles->name];
|
||||
}
|
||||
|
||||
$roles = collect()->make($roles)->map(fn ($role) => $role instanceof Role ? $role->name : $role
|
||||
);
|
||||
|
||||
return $this->roles->count() == $roles->count() && $this->hasAllRoles($roles, $guard);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all permissions directly coupled to the model.
|
||||
*/
|
||||
public function getDirectPermissions(): Collection
|
||||
{
|
||||
return $this->permissions;
|
||||
}
|
||||
|
||||
public function getRoleNames(): Collection
|
||||
{
|
||||
$this->loadMissing('roles');
|
||||
|
||||
return $this->roles->pluck('name');
|
||||
}
|
||||
|
||||
protected function getStoredRole($role): Role
|
||||
{
|
||||
$role = enum_value($role);
|
||||
|
||||
if (is_int($role) || PermissionRegistrar::isUid($role)) {
|
||||
return $this->getRoleClass()::findById($role, $this->getDefaultGuardName());
|
||||
}
|
||||
|
||||
if (is_string($role)) {
|
||||
return $this->getRoleClass()::findByName($role, $this->getDefaultGuardName());
|
||||
}
|
||||
|
||||
return $role;
|
||||
}
|
||||
|
||||
protected function convertPipeToArray(string $pipeString): array
|
||||
{
|
||||
$pipeString = trim($pipeString);
|
||||
|
||||
if (strlen($pipeString) <= 2) {
|
||||
return [str_replace('|', '', $pipeString)];
|
||||
}
|
||||
|
||||
$quoteCharacter = substr($pipeString, 0, 1);
|
||||
$endCharacter = substr($quoteCharacter, -1, 1);
|
||||
|
||||
if ($quoteCharacter !== $endCharacter) {
|
||||
return explode('|', $pipeString);
|
||||
}
|
||||
|
||||
if (! in_array($quoteCharacter, ["'", '"'])) {
|
||||
return explode('|', $pipeString);
|
||||
}
|
||||
|
||||
return explode('|', trim($pipeString, $quoteCharacter));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Permission\Traits;
|
||||
|
||||
use Spatie\Permission\PermissionRegistrar;
|
||||
|
||||
trait RefreshesPermissionCache
|
||||
{
|
||||
public static function bootRefreshesPermissionCache()
|
||||
{
|
||||
static::saved(function () {
|
||||
app(PermissionRegistrar::class)->forgetCachedPermissions();
|
||||
});
|
||||
|
||||
static::deleted(function () {
|
||||
app(PermissionRegistrar::class)->forgetCachedPermissions();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Permission;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Str;
|
||||
use Spatie\Permission\Contracts\Wildcard;
|
||||
use Spatie\Permission\Exceptions\WildcardPermissionNotProperlyFormatted;
|
||||
|
||||
class WildcardPermission implements Wildcard
|
||||
{
|
||||
/** @var string */
|
||||
public const WILDCARD_TOKEN = '*';
|
||||
|
||||
/** @var non-empty-string */
|
||||
public const PART_DELIMITER = '.';
|
||||
|
||||
/** @var non-empty-string */
|
||||
public const SUBPART_DELIMITER = ',';
|
||||
|
||||
public function __construct(protected Model $record) {}
|
||||
|
||||
public function getIndex(): array
|
||||
{
|
||||
$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,
|
||||
);
|
||||
}
|
||||
|
||||
return $index;
|
||||
}
|
||||
|
||||
protected function buildIndex(array $index, array $parts, string $permission): array
|
||||
{
|
||||
if (empty($parts)) {
|
||||
$index[''] = true;
|
||||
|
||||
return $index;
|
||||
}
|
||||
|
||||
$part = array_shift($parts);
|
||||
|
||||
if (blank($part)) {
|
||||
throw WildcardPermissionNotProperlyFormatted::create($permission);
|
||||
}
|
||||
|
||||
if (! Str::contains($part, static::SUBPART_DELIMITER)) {
|
||||
$index[$part] = $this->buildIndex(
|
||||
$index[$part] ?? [],
|
||||
$parts,
|
||||
$permission,
|
||||
);
|
||||
}
|
||||
|
||||
$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 $index;
|
||||
}
|
||||
|
||||
public function implies(string $permission, string $guardName, array $index): bool
|
||||
{
|
||||
if (! array_key_exists($guardName, $index)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$permission = explode(static::PART_DELIMITER, $permission);
|
||||
|
||||
return $this->checkIndex($permission, $index[$guardName]);
|
||||
}
|
||||
|
||||
protected function checkIndex(array $permission, array $index): bool
|
||||
{
|
||||
if (array_key_exists(strval(null), $index)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (empty($permission)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$firstPermission = array_shift($permission);
|
||||
|
||||
if (
|
||||
array_key_exists($firstPermission, $index) &&
|
||||
$this->checkIndex($permission, $index[$firstPermission])
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (array_key_exists(static::WILDCARD_TOKEN, $index)) {
|
||||
return $this->checkIndex($permission, $index[static::WILDCARD_TOKEN]);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Spatie\Permission\Guard;
|
||||
use Spatie\Permission\PermissionRegistrar;
|
||||
|
||||
if (! function_exists('getModelForGuard')) {
|
||||
function getModelForGuard(string $guard): ?string
|
||||
{
|
||||
return Guard::getModelForGuard($guard);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('setPermissionsTeamId')) {
|
||||
function setPermissionsTeamId(int|string|Model|null $id): void
|
||||
{
|
||||
app(PermissionRegistrar::class)->setPermissionsTeamId($id);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('getPermissionsTeamId')) {
|
||||
function getPermissionsTeamId(): int|string|null
|
||||
{
|
||||
return app(PermissionRegistrar::class)->getPermissionsTeamId();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user