glastree_on_gitea

This commit is contained in:
2026-05-26 08:14:29 +02:00
commit 0bed099d05
9556 changed files with 1186307 additions and 0 deletions
+21
View File
@@ -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
View File
@@ -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>
[![Latest Version on Packagist](https://img.shields.io/packagist/v/spatie/laravel-permission.svg?style=flat-square)](https://packagist.org/packages/spatie/laravel-permission)
[![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/spatie/laravel-permission/run-tests.yml?branch=main&label=Tests)](https://github.com/spatie/laravel-permission/actions?query=workflow%3ATests+branch%3Amain)
[![Total Downloads](https://img.shields.io/packagist/dt/spatie/laravel-permission.svg?style=flat-square)](https://packagist.org/packages/spatie/laravel-permission)
</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
View File
@@ -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"
}
}
+219
View File
@@ -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',
],
];
@@ -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 {}
};
@@ -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
View File
@@ -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
View File
@@ -0,0 +1,6 @@
{
"preset": "laravel",
"rules": {
"php_unit_method_casing": false
}
}
@@ -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;
}
+50
View File
@@ -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;
}
}
@@ -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'));
}
}
@@ -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'));
}
}
@@ -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
View File
@@ -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;
}
}
@@ -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();
}
}
+221
View File
@@ -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'),
]);
}
}
+125
View File
@@ -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;
}
}
+497
View File
@@ -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;
}
}
+26
View File
@@ -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();
}
}