├── LICENSE.md ├── README.md ├── composer.json ├── config └── acl.php ├── migrations ├── 2015_12_20_100001_create_permissions_table.php ├── 2015_12_20_100002_create_roles_table.php ├── 2015_12_20_100003_create_permission_role_table.php ├── 2015_12_20_100004_create_role_user_table.php └── 2015_12_20_100005_create_permission_user_table.php └── src ├── AclServiceProvider.php ├── Directives ├── CanAtLeastDirective.php ├── DirectiveAbstract.php └── RoleDirective.php ├── GateRegistrar.php ├── Middleware ├── CanAtLeastMiddleware.php ├── PermissionMiddleware.php └── RoleMiddleware.php ├── Models ├── Permission.php └── Role.php └── Traits ├── AuthorizesPermissionResources.php ├── HasPermission.php ├── HasRole.php ├── HasRoleAndPermission.php ├── InteractsWithPermission.php ├── InteractsWithRole.php └── RefreshPermissionsCache.php /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright (c) 2015 - 2022 Arjay Angeles 4 | 5 | > Permission is hereby granted, free of charge, to any person obtaining a copy 6 | > of this software and associated documentation files (the "Software"), to deal 7 | > in the Software without restriction, including without limitation the rights 8 | > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | > copies of the Software, and to permit persons to whom the Software is 10 | > furnished to do so, subject to the following conditions: 11 | > 12 | > The above copyright notice and this permission notice shall be included in 13 | > all copies or substantial portions of the Software. 14 | > 15 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | > THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel ACL 2 | 3 | [![Latest Version on Packagist][ico-version]][link-packagist] 4 | [![Software License][ico-license]](LICENSE.md) 5 | 6 | [![Continuous Integration](https://github.com/yajra/laravel-acl/actions/workflows/continuous-integration.yml/badge.svg)](https://github.com/yajra/laravel-acl/actions/workflows/continuous-integration.yml) 7 | [![Static Analysis](https://github.com/yajra/laravel-acl/actions/workflows/static-analysis.yml/badge.svg)](https://github.com/yajra/laravel-acl/actions/workflows/static-analysis.yml) 8 | [![Total Downloads][ico-downloads]][link-downloads] 9 | 10 | Laravel ACL (Access Control List) is a simple role-permission ACL for the Laravel Framework. 11 | This package was based on the great package [Caffeinated/Shinobi](https://github.com/caffeinated/shinobi) but is fully compatible with Laravel's built-in Gate/Authorization system. 12 | 13 | ## Documentations 14 | - [Laravel ACL][link-docs] 15 | 16 | ## Laravel Version Compatibility 17 | 18 | | Laravel | Package | 19 | |:--------------|:--------| 20 | | 8.x and below | 6.x | 21 | | 9.x | 9.x | 22 | | 10.x | 10.x | 23 | | 11.x | 11.x | 24 | | 12.x | 12.x | 25 | 26 | ## Installation 27 | 28 | Via Composer 29 | 30 | ``` bash 31 | $ composer require yajra/laravel-acl:^12 32 | ``` 33 | 34 | ## Configuration 35 | Register service provider (Optional on Laravel 5.5+). 36 | ``` php 37 | Yajra\Acl\AclServiceProvider::class 38 | ``` 39 | 40 | Publish assets (Optional): 41 | ```php 42 | $ php artisan vendor:publish --tag=laravel-acl 43 | ``` 44 | 45 | Run migrations: 46 | ```php 47 | php artisan migrate 48 | ``` 49 | 50 | ## Change log 51 | 52 | Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently. 53 | 54 | ## Contributing 55 | 56 | Please see [CONTRIBUTING](CONTRIBUTING.md) and [CONDUCT](CONDUCT.md) for details. 57 | 58 | ## Security 59 | 60 | If you discover any security related issues, please email aqangeles@gmail.com instead of using the issue tracker. 61 | 62 | ## Credits 63 | 64 | - [Arjay Angeles][link-author] 65 | - [Caffeinated/Shinobi](https://github.com/caffeinated/shinobi) 66 | - [All Contributors][link-contributors] 67 | 68 | ## License 69 | 70 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 71 | 72 | [ico-version]: https://img.shields.io/packagist/v/yajra/laravel-acl.svg?style=flat-square 73 | [ico-license]: https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square 74 | [ico-downloads]: https://img.shields.io/packagist/dt/yajra/laravel-acl.svg?style=flat-square 75 | 76 | [link-packagist]: https://packagist.org/packages/yajra/laravel-acl 77 | [link-downloads]: https://packagist.org/packages/yajra/laravel-acl 78 | [link-author]: https://github.com/yajra 79 | [link-contributors]: ../../contributors 80 | [link-docs]: https://yajrabox.com/docs/laravel-acl/master 81 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yajra/laravel-acl", 3 | "description": "Laravel ACL is a simple role, permission ACL for Laravel Framework.", 4 | "keywords": [ 5 | "yajra", 6 | "laravel", 7 | "acl", 8 | "user", 9 | "role", 10 | "permissions" 11 | ], 12 | "homepage": "https://github.com/yajra/laravel-acl", 13 | "license": "MIT", 14 | "authors": [ 15 | { 16 | "name": "Arjay Angeles", 17 | "email": "aqangeles@gmail.com", 18 | "homepage": "https://yajrabox.com", 19 | "role": "Developer" 20 | } 21 | ], 22 | "require": { 23 | "illuminate/support": "^12.0", 24 | "illuminate/database": "^12.0" 25 | }, 26 | "require-dev": { 27 | "larastan/larastan": "^3.1", 28 | "laravel/pint": "^1.21.0", 29 | "rector/rector": "^2.0.9", 30 | "orchestra/testbench": "^10" 31 | }, 32 | "autoload": { 33 | "psr-4": { 34 | "Yajra\\Acl\\": "src/" 35 | } 36 | }, 37 | "autoload-dev": { 38 | "psr-4": { 39 | "Yajra\\Acl\\Tests\\": "tests/" 40 | } 41 | }, 42 | "extra": { 43 | "branch-alias": { 44 | "dev-master": "12.x-dev" 45 | }, 46 | "laravel": { 47 | "providers": [ 48 | "Yajra\\Acl\\AclServiceProvider" 49 | ] 50 | } 51 | }, 52 | "config": { 53 | "sort-packages": true, 54 | "allow-plugins": { 55 | "pestphp/pest-plugin": false 56 | } 57 | }, 58 | "minimum-stability": "dev", 59 | "prefer-stable": true, 60 | "scripts": { 61 | "test": "./vendor/bin/phpunit", 62 | "pint": "./vendor/bin/pint", 63 | "rector": "./vendor/bin/rector", 64 | "stan": "./vendor/bin/phpstan analyse --memory-limit=2G --ansi --no-progress --no-interaction --configuration=phpstan.neon.dist", 65 | "pr": [ 66 | "@rector", 67 | "@pint", 68 | "@stan", 69 | "@test" 70 | ] 71 | }, 72 | "funding": [ 73 | { 74 | "type": "github", 75 | "url": "https://github.com/sponsors/yajra" 76 | } 77 | ] 78 | } 79 | -------------------------------------------------------------------------------- /config/acl.php: -------------------------------------------------------------------------------- 1 | App\Models\User::class, 8 | 9 | /** 10 | * Role class used for ACL. 11 | */ 12 | 'role' => Yajra\Acl\Models\Role::class, 13 | 14 | /** 15 | * Permission class used for ACL. 16 | */ 17 | 'permission' => Yajra\Acl\Models\Permission::class, 18 | 19 | /** 20 | * Cache config. 21 | */ 22 | 'cache' => [ 23 | 'enabled' => true, 24 | 'key' => 'permissions.policies', 25 | ], 26 | ]; 27 | -------------------------------------------------------------------------------- /migrations/2015_12_20_100001_create_permissions_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->string('name', 50); 17 | $table->string('slug')->unique(); 18 | $table->string('resource', 20)->default('System'); 19 | $table->boolean('system')->default(0); 20 | $table->timestamps(); 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migration. 26 | */ 27 | public function down(): void 28 | { 29 | Schema::drop('permissions'); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /migrations/2015_12_20_100002_create_roles_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->string('name'); 17 | $table->string('slug')->unique(); 18 | $table->text('description')->nullable(); 19 | $table->boolean('system')->default(0); 20 | $table->timestamps(); 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migration. 26 | */ 27 | public function down(): void 28 | { 29 | Schema::drop('roles'); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /migrations/2015_12_20_100003_create_permission_role_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->foreignId('permission_id')->constrained()->cascadeOnDelete(); 17 | $table->foreignId('role_id')->constrained()->cascadeOnDelete(); 18 | $table->timestamps(); 19 | }); 20 | } 21 | 22 | /** 23 | * Reverse the migration. 24 | */ 25 | public function down(): void 26 | { 27 | Schema::drop('permission_role'); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /migrations/2015_12_20_100004_create_role_user_table.php: -------------------------------------------------------------------------------- 1 | id('id'); 16 | $table->foreignId('role_id')->constrained()->cascadeOnDelete(); 17 | $table->foreignId('user_id')->constrained()->cascadeOnDelete(); 18 | $table->timestamps(); 19 | }); 20 | } 21 | 22 | /** 23 | * Reverse the migration. 24 | */ 25 | public function down(): void 26 | { 27 | Schema::drop('role_user'); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /migrations/2015_12_20_100005_create_permission_user_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->foreignId('permission_id')->constrained()->cascadeOnDelete(); 17 | $table->foreignId('user_id')->constrained()->cascadeOnDelete(); 18 | $table->timestamps(); 19 | }); 20 | } 21 | 22 | /** 23 | * Reverse the migrations. 24 | */ 25 | public function down(): void 26 | { 27 | Schema::dropIfExists('permission_user'); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /src/AclServiceProvider.php: -------------------------------------------------------------------------------- 1 | register(); 15 | 16 | $this->publishConfig(); 17 | $this->publishMigrations(); 18 | $this->registerPolicies(); 19 | $this->registerBladeDirectives(); 20 | } 21 | 22 | protected function publishConfig(): void 23 | { 24 | $path = __DIR__.'/../config/acl.php'; 25 | 26 | $this->publishes([$path => config_path('acl.php')], 'laravel-acl'); 27 | 28 | $this->mergeConfigFrom($path, 'acl'); 29 | } 30 | 31 | protected function publishMigrations(): void 32 | { 33 | $this->loadMigrationsFrom(__DIR__.'/../migrations'); 34 | $this->publishes([ 35 | __DIR__.'/../migrations' => database_path('migrations'), 36 | ], 'laravel-acl'); 37 | } 38 | 39 | protected function registerBladeDirectives(): void 40 | { 41 | /** @var BladeCompiler $blade */ 42 | $blade = resolve('blade.compiler'); 43 | $blade->directive('canAtLeast', fn (string|array $expression) => "handle({$expression})): ?>"); 44 | $blade->directive('endCanAtLeast', fn (string|array $expression) => ''); 45 | 46 | $blade->directive('role', fn ($expression) => "handle({$expression})): ?>"); 47 | $blade->directive('endRole', fn ($expression) => ''); 48 | } 49 | 50 | public function register(): void 51 | { 52 | $this->app->singleton('laravel-acl.directives.canAtLeast', CanAtLeastDirective::class); 53 | $this->app->singleton('laravel-acl.directives.role', RoleDirective::class); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Directives/CanAtLeastDirective.php: -------------------------------------------------------------------------------- 1 | auth->user() && method_exists($this->auth->user(), 'canAtLeast')) { 17 | return $this->auth->user()->canAtLeast((array) $permissions); 18 | } 19 | 20 | $guest = Role::whereSlug('guest')->first(); 21 | if ($guest) { 22 | return $guest->canAtLeast((array) $permissions); 23 | } 24 | 25 | return false; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Directives/DirectiveAbstract.php: -------------------------------------------------------------------------------- 1 | auth->user() && method_exists($this->auth->user(), 'hasRole')) { 15 | return $this->auth->user()->hasRole($role); 16 | } 17 | 18 | return $role === 'guest'; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/GateRegistrar.php: -------------------------------------------------------------------------------- 1 | getPermissions()->each(function (Permission $permission) { 19 | $ability = $permission->slug; 20 | $policy = function (User $user) use ($permission) { 21 | if (method_exists($user, 'getPermissions')) { 22 | // @phpstan-ignore-next-line 23 | return collect($user->getPermissions())->contains($permission->slug); 24 | } 25 | 26 | return false; 27 | }; 28 | 29 | if (Str::contains($permission->slug, '@')) { 30 | $policy = $permission->slug; 31 | $ability = $permission->name; 32 | } 33 | 34 | $this->gate->define($ability, $policy); 35 | }); 36 | } 37 | 38 | /** 39 | * Get all permissions. 40 | * 41 | * @return \Illuminate\Support\Collection 42 | */ 43 | protected function getPermissions(): Collection 44 | { 45 | /** @var string $key */ 46 | $key = config('acl.cache.key', 'permissions.policies'); 47 | 48 | try { 49 | return config('acl.cache.enabled', true) 50 | ? $this->cache->rememberForever($key, fn () => $this->getPermissionsFromQuery()) 51 | : $this->getPermissionsFromQuery(); 52 | } catch (\Throwable) { 53 | $this->cache->forget($key); 54 | 55 | return collect(); 56 | } 57 | } 58 | 59 | /** 60 | * @return \Illuminate\Support\Collection 61 | */ 62 | public function getPermissionsFromQuery(): Collection 63 | { 64 | return $this->getPermissionClass() 65 | ->with('roles') 66 | ->get(); 67 | } 68 | 69 | protected function getPermissionClass(): Permission 70 | { 71 | /** @var class-string $class */ 72 | $class = config('acl.permission', Permission::class); 73 | 74 | return resolve($class); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Middleware/CanAtLeastMiddleware.php: -------------------------------------------------------------------------------- 1 | deniesAccessUsing($abilities)) { 20 | abort(403, 'You are not allowed to view this content!'); 21 | } 22 | 23 | return $next($request); 24 | } 25 | 26 | /** 27 | * @param string[] $abilities 28 | */ 29 | protected function deniesAccessUsing(array $abilities): bool 30 | { 31 | return ! auth()->user() 32 | || (method_exists(auth()->user(), 'canAtLeast') && ! auth()->user()->canAtLeast($abilities)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Middleware/PermissionMiddleware.php: -------------------------------------------------------------------------------- 1 | user() || ! auth()->user()->can($permission)) { 18 | if ($request->ajax()) { 19 | return response()->json([ 20 | 'error' => [ 21 | 'status_code' => 401, 22 | 'code' => 'INSUFFICIENT_PERMISSIONS', 23 | 'description' => 'You are not authorized to access this resource.', 24 | ], 25 | ], 401); 26 | } 27 | 28 | abort(401, 'You are not authorized to access this resource.'); 29 | } 30 | 31 | return $next($request); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Middleware/RoleMiddleware.php: -------------------------------------------------------------------------------- 1 | split('/[|,]/')->toArray(); 21 | } 22 | 23 | if ($this->deniesAccessUsing($role)) { 24 | if ($request->ajax()) { 25 | return response()->json([ 26 | 'error' => [ 27 | 'status_code' => 401, 28 | 'code' => 'INSUFFICIENT_PERMISSIONS', 29 | 'description' => 'You are not authorized to access this resource.', 30 | ], 31 | ], 401); 32 | } 33 | 34 | abort(401, 'You are not authorized to access this resource.'); 35 | } 36 | 37 | return $next($request); 38 | } 39 | 40 | /** 41 | * @param string[] $role 42 | */ 43 | protected function deniesAccessUsing(array $role): bool 44 | { 45 | return ! auth()->user() 46 | || (method_exists(auth()->user(), 'hasRole') && ! auth()->user()->hasRole($role)); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Models/Permission.php: -------------------------------------------------------------------------------- 1 | 'bool', 34 | ]; 35 | 36 | /** 37 | * Find a permission by slug. 38 | */ 39 | public static function findBySlug(string $slug): Permission 40 | { 41 | return static::query()->where('slug', $slug)->firstOrFail(); 42 | } 43 | 44 | /** 45 | * Create a permissions for a resource. 46 | * 47 | * @return \Illuminate\Support\Collection 48 | */ 49 | public static function createResource(string $resource, bool $system = false): Collection 50 | { 51 | $group = Str::title($resource); 52 | $slug = Str::slug($group); 53 | $permissions = [ 54 | [ 55 | 'slug' => 'viewAny-'.$slug, 56 | 'resource' => $group, 57 | 'name' => 'View Any '.$group, 58 | 'system' => $system, 59 | ], 60 | [ 61 | 'slug' => 'view-'.$slug, 62 | 'resource' => $group, 63 | 'name' => 'View '.$group, 64 | 'system' => $system, 65 | ], 66 | [ 67 | 'slug' => 'create-'.$slug, 68 | 'resource' => $group, 69 | 'name' => 'Create '.$group, 70 | 'system' => $system, 71 | ], 72 | [ 73 | 'slug' => 'update-'.$slug, 74 | 'resource' => $group, 75 | 'name' => 'Update '.$group, 76 | 'system' => $system, 77 | ], 78 | [ 79 | 'slug' => 'delete-'.$slug, 80 | 'resource' => $group, 81 | 'name' => 'Delete '.$group, 82 | 'system' => $system, 83 | ], 84 | ]; 85 | 86 | $collection = new Collection; 87 | foreach ($permissions as $permission) { 88 | try { 89 | $collection->push(static::create($permission)); 90 | } catch (Exception) { 91 | // permission already exists. 92 | } 93 | } 94 | 95 | return $collection; 96 | } 97 | 98 | /** 99 | * Permission can belong to many users. 100 | * 101 | * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany<\Illuminate\Foundation\Auth\User> 102 | */ 103 | public function users(): BelongsToMany 104 | { 105 | /** @var class-string<\Illuminate\Foundation\Auth\User> $model */ 106 | $model = config('acl.user', config('auth.providers.users.model')); 107 | 108 | // @phpstan-ignore-next-line 109 | return $this->belongsToMany($model)->withTimestamps(); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/Models/Role.php: -------------------------------------------------------------------------------- 1 | 'bool', 31 | ]; 32 | 33 | /** 34 | * Find a role by slug. 35 | */ 36 | public static function findBySlug(string $slug): Role 37 | { 38 | return static::query()->where('slug', $slug)->firstOrFail(); 39 | } 40 | 41 | /** 42 | * Roles can belong to many users. 43 | * 44 | * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany<\Illuminate\Foundation\Auth\User> 45 | */ 46 | public function users(): BelongsToMany 47 | { 48 | /** @var class-string<\Illuminate\Foundation\Auth\User> $model */ 49 | $model = config('acl.user', config('auth.providers.users.model')); 50 | 51 | // @phpstan-ignore-next-line 52 | return $this->belongsToMany($model)->withTimestamps(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Traits/AuthorizesPermissionResources.php: -------------------------------------------------------------------------------- 1 | 'viewAny', 20 | 'create' => 'create', 21 | 'store' => 'create', 22 | 'show' => 'view', 23 | 'edit' => 'update', 24 | 'update' => 'update', 25 | 'destroy' => 'delete', 26 | ]; 27 | 28 | /** 29 | * Authorize a permission resource action based on the incoming request. 30 | * 31 | * @return void 32 | */ 33 | public function authorizePermissionResource(string $resource, array $options = []) 34 | { 35 | $permissions = $this->resourcePermissionMap(); 36 | $collection = new Collection; 37 | foreach ($permissions as $method => $ability) { 38 | $collection->push(new Fluent([ 39 | 'ability' => $ability, 40 | 'method' => $method, 41 | ])); 42 | } 43 | 44 | $collection->groupBy('ability')->each(function ($permission, $ability) use ($resource) { 45 | $this->middleware("can:{$resource}.{$ability}") 46 | ->only($permission->pluck('method')->toArray()); 47 | }); 48 | } 49 | 50 | /** 51 | * Get the map of permission resource methods to ability names. 52 | */ 53 | protected function resourcePermissionMap(): array 54 | { 55 | if (property_exists($this, 'customPermissionMap') && is_array($this->customPermissionMap)) { 56 | return array_merge($this->resourcePermissionMap, $this->customPermissionMap); 57 | } 58 | 59 | return $this->resourcePermissionMap; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Traits/HasPermission.php: -------------------------------------------------------------------------------- 1 | can($permission); 17 | } 18 | 19 | /** 20 | * Checks if the role has the given permission. 21 | * 22 | * @param string|string[] $permission 23 | */ 24 | public function can(array|string $permission): bool 25 | { 26 | $permissions = $this->getPermissions(); 27 | 28 | if (is_array($permission)) { 29 | $permissionCount = count($permission); 30 | $intersection = array_intersect($permissions, $permission); 31 | $intersectionCount = count($intersection); 32 | 33 | return $permissionCount == $intersectionCount; 34 | } 35 | 36 | return in_array($permission, $permissions); 37 | } 38 | 39 | /** 40 | * Check if the role has at least one of the given permissions. 41 | * 42 | * @param string|string[] $permission 43 | */ 44 | public function canAtLeast(string|array $permission): bool 45 | { 46 | $permissions = $this->getPermissions(); 47 | 48 | $intersection = array_intersect($permissions, (array) $permission); 49 | $intersectionCount = count($intersection); 50 | 51 | return $intersectionCount > 0; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Traits/HasRole.php: -------------------------------------------------------------------------------- 1 | canAtLeast($acl) || $this->hasRole($acl); 19 | } 20 | 21 | /** 22 | * Check if user has at least one of the given permissions 23 | */ 24 | public function canAtLeast(string|array $permissions): bool 25 | { 26 | $can = false; 27 | 28 | if (auth()->check()) { 29 | /** @var Role $role */ 30 | foreach ($this->roles as $role) { 31 | if ($role->canAtLeast($permissions)) { 32 | $can = true; 33 | } 34 | } 35 | } else { 36 | try { 37 | $guest = $this->findRoleBySlug('guest'); 38 | 39 | return $guest->canAtLeast($permissions); 40 | } catch (ModelNotFoundException) { 41 | // 42 | } 43 | } 44 | 45 | return $can; 46 | } 47 | 48 | /** 49 | * Get all user role permissions. 50 | */ 51 | public function getPermissions(): array 52 | { 53 | $permissions = [[], []]; 54 | 55 | foreach ($this->roles as $role) { 56 | $permissions[] = $role->getPermissions(); 57 | } 58 | 59 | return call_user_func_array('array_merge', $permissions); 60 | } 61 | 62 | /** 63 | * Check if the given entity/model is owned by the user. 64 | * 65 | * @param string $relation 66 | */ 67 | public function owns(Model $entity, $relation = 'user_id'): bool 68 | { 69 | return $this->getKeyName() === $entity->{$relation}; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Traits/HasRoleAndPermission.php: -------------------------------------------------------------------------------- 1 | getRolePermissions(); 20 | $userPermissions = $this->permissions->pluck('slug')->toArray(); 21 | 22 | return collect($userPermissions)->merge($rolePermissions)->unique()->toArray(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Traits/InteractsWithPermission.php: -------------------------------------------------------------------------------- 1 | $permissions 12 | * 13 | * @mixin \Illuminate\Database\Eloquent\Model 14 | */ 15 | trait InteractsWithPermission 16 | { 17 | /** 18 | * @var class-string|Permission 19 | */ 20 | public $permissionClass; 21 | 22 | /** 23 | * Grant permissions by slug(/s). 24 | * 25 | * @param string|string[] $slug 26 | */ 27 | public function grantPermissionBySlug(array|string $slug): void 28 | { 29 | $this->getPermissionClass() 30 | ->newQuery() 31 | ->whereIn('slug', (array) $slug) 32 | ->each(function ($permission) { 33 | $this->grantPermission($permission); 34 | }); 35 | 36 | $this->load('permissions'); 37 | } 38 | 39 | /** 40 | * Get Permission class. 41 | */ 42 | public function getPermissionClass(): Permission 43 | { 44 | if (! $this->permissionClass instanceof Permission) { 45 | /** @var class-string $model */ 46 | $model = config('acl.permission'); 47 | $this->permissionClass = resolve($model); 48 | } 49 | 50 | return $this->permissionClass; 51 | } 52 | 53 | /** 54 | * Grant the given permission. 55 | * 56 | * @param array $attributes 57 | */ 58 | public function grantPermission(mixed $ids, array $attributes = [], bool $touch = true): void 59 | { 60 | $this->permissions()->attach($ids, $attributes, $touch); 61 | 62 | $this->load('permissions'); 63 | } 64 | 65 | /** 66 | * Get related permissions. 67 | * 68 | * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany 69 | */ 70 | public function permissions(): BelongsToMany 71 | { 72 | /** @var class-string $model */ 73 | $model = config('acl.permission', Permission::class); 74 | 75 | // @phpstan-ignore-next-line 76 | return $this->belongsToMany($model)->withTimestamps(); 77 | } 78 | 79 | /** 80 | * Grant permissions by resource. 81 | * 82 | * @param string|string[] $resource 83 | */ 84 | public function grantPermissionByResource(array|string $resource): void 85 | { 86 | $this->getPermissionClass() 87 | ->newQuery() 88 | ->whereIn('resource', (array) $resource) 89 | ->each(function ($permission) { 90 | $this->grantPermission($permission); 91 | }); 92 | 93 | $this->load('permissions'); 94 | } 95 | 96 | /** 97 | * Revoke permissions by the given slug(/s). 98 | * 99 | * @param string|string[] $slug 100 | */ 101 | public function revokePermissionBySlug(array|string $slug): void 102 | { 103 | $this->getPermissionClass() 104 | ->newQuery() 105 | ->whereIn('slug', (array) $slug) 106 | ->each(function ($permission) { 107 | $this->revokePermission($permission); 108 | }); 109 | 110 | $this->load('permissions'); 111 | } 112 | 113 | /** 114 | * Revokes the given permission. 115 | */ 116 | public function revokePermission(mixed $ids = null, bool $touch = true): int 117 | { 118 | $detached = $this->permissions()->detach($ids, $touch); 119 | 120 | $this->load('permissions'); 121 | 122 | return $detached; 123 | } 124 | 125 | /** 126 | * Revoke permissions by resource. 127 | * 128 | * @param string|string[] $resource 129 | */ 130 | public function revokePermissionByResource(array|string $resource): void 131 | { 132 | $this->getPermissionClass() 133 | ->newQuery() 134 | ->whereIn('resource', (array) $resource) 135 | ->each(function ($permission) { 136 | $this->revokePermission($permission); 137 | }); 138 | 139 | $this->load('permissions'); 140 | } 141 | 142 | /** 143 | * Syncs the given permission. 144 | * 145 | * @param Collection|Model|array $ids 146 | * @return array 147 | */ 148 | public function syncPermissions(Collection|Model|array $ids, bool $detaching = true): array 149 | { 150 | $synced = $this->permissions()->sync($ids, $detaching); 151 | 152 | $this->load('permissions'); 153 | 154 | return $synced; 155 | } 156 | 157 | /** 158 | * Revokes all permissions. 159 | */ 160 | public function revokeAllPermissions(): int 161 | { 162 | $detached = $this->permissions()->detach(); 163 | 164 | $this->load('permissions'); 165 | 166 | return $detached; 167 | } 168 | 169 | /** 170 | * Get list of permissions slug. 171 | * 172 | * @return array 173 | */ 174 | public function getPermissions(): array 175 | { 176 | $this->loadMissing('permissions'); 177 | 178 | return $this->permissions->pluck('slug')->toArray(); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/Traits/InteractsWithRole.php: -------------------------------------------------------------------------------- 1 | $roles 13 | * 14 | * @method static Builder havingRoles($roleIds) 15 | * @method static Builder havingRolesBySlugs($slugs) 16 | */ 17 | trait InteractsWithRole 18 | { 19 | /** 20 | * @var class-string|Role 21 | */ 22 | public $roleClass; 23 | 24 | /** 25 | * Check if user has the given role. 26 | * 27 | * @param string|string[] $role 28 | */ 29 | public function hasRole(string|array $role): bool 30 | { 31 | if (is_array($role)) { 32 | $roles = $this->getRoleSlugs(); 33 | 34 | $intersection = array_intersect($roles, $role); 35 | $intersectionCount = count($intersection); 36 | 37 | return $intersectionCount > 0; 38 | } 39 | 40 | return $this->roles->contains('slug', $role); 41 | } 42 | 43 | /** 44 | * Get all user roles. 45 | * 46 | * @return array 47 | */ 48 | public function getRoleSlugs(): array 49 | { 50 | return $this->roles->pluck('slug')->toArray(); 51 | } 52 | 53 | /** 54 | * Attach a role to user using slug. 55 | */ 56 | public function attachRoleBySlug(string $slug): void 57 | { 58 | $this->attachRole($this->findRoleBySlug($slug)); 59 | 60 | $this->load('roles'); 61 | } 62 | 63 | /** 64 | * Attach a role to user. 65 | * 66 | * @param array $attributes 67 | */ 68 | public function attachRole(mixed $role, array $attributes = [], bool $touch = true): void 69 | { 70 | $this->roles()->attach($role, $attributes, $touch); 71 | 72 | $this->load('roles'); 73 | } 74 | 75 | /** 76 | * Model can have many roles. 77 | * 78 | * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany 79 | */ 80 | public function roles(): BelongsToMany 81 | { 82 | /** @var class-string $model */ 83 | $model = config('acl.role', Role::class); 84 | 85 | // @phpstan-ignore-next-line 86 | return $this->belongsToMany($model)->withTimestamps(); 87 | } 88 | 89 | /** 90 | * Find a role by slug. 91 | * 92 | * @throws \Illuminate\Database\Eloquent\ModelNotFoundException 93 | */ 94 | protected function findRoleBySlug(string $slug): Model|static 95 | { 96 | return $this->getRoleClass()->newQuery()->where('slug', $slug)->firstOrFail(); 97 | } 98 | 99 | /** 100 | * Get Role class. 101 | */ 102 | public function getRoleClass(): Role 103 | { 104 | if (! $this->roleClass instanceof Role) { 105 | /** @var class-string $role */ 106 | $role = config('acl.role'); 107 | 108 | $this->roleClass = resolve($role); 109 | } 110 | 111 | return $this->roleClass; 112 | } 113 | 114 | /** 115 | * Query scope for user having the given roles. 116 | * 117 | * @param \Illuminate\Database\Eloquent\Builder<\Yajra\Acl\Models\Permission> $query 118 | * @param array $roles 119 | * @return \Illuminate\Database\Eloquent\Builder<\Yajra\Acl\Models\Permission> 120 | */ 121 | public function scopeHavingRoles(Builder $query, array $roles): Builder 122 | { 123 | return $query->whereExists(function ($query) use ($roles) { 124 | $query->selectRaw('1') 125 | ->from('role_user') 126 | ->whereRaw('role_user.user_id = users.id') 127 | ->whereIn('role_id', $roles); 128 | }); 129 | } 130 | 131 | /** 132 | * Query scope for user having the given roles by slugs. 133 | * 134 | * @param \Illuminate\Database\Eloquent\Builder<\Yajra\Acl\Models\Permission> $query 135 | * @param array $slugs 136 | * @return \Illuminate\Database\Eloquent\Builder<\Yajra\Acl\Models\Permission> 137 | */ 138 | public function scopeHavingRolesBySlugs(Builder $query, array $slugs): Builder 139 | { 140 | return $query->whereHas('roles', function ($query) use ($slugs) { 141 | $query->whereIn('roles.slug', $slugs); 142 | }); 143 | } 144 | 145 | /** 146 | * Revokes the given role from the user using slug. 147 | * 148 | * @param string|string[] $slug 149 | * @param bool $touch 150 | */ 151 | public function revokeRoleBySlug(string|array $slug, $touch = true): int 152 | { 153 | $roles = $this->getRoleClass() 154 | ->newQuery() 155 | ->whereIn('slug', (array) $slug) 156 | ->get(); 157 | 158 | $detached = $this->roles()->detach($roles, $touch); 159 | 160 | $this->load('roles'); 161 | 162 | return $detached; 163 | } 164 | 165 | /** 166 | * Revokes the given role from the user. 167 | * 168 | * @param bool $touch 169 | */ 170 | public function revokeRole(mixed $role, $touch = true): int 171 | { 172 | $detached = $this->roles()->detach($role, $touch); 173 | 174 | $this->load('roles'); 175 | 176 | return $detached; 177 | } 178 | 179 | /** 180 | * Syncs the given role(s) with the user. 181 | * 182 | * @param Collection|Model|array $roles 183 | * @return array 184 | */ 185 | public function syncRoles(Collection|Model|array $roles, bool $detaching = true): array 186 | { 187 | $synced = $this->roles()->sync($roles, $detaching); 188 | 189 | $this->load('roles'); 190 | 191 | return $synced; 192 | } 193 | 194 | /** 195 | * Revokes all roles from the user. 196 | */ 197 | public function revokeAllRoles(): int 198 | { 199 | $detached = $this->roles()->detach(); 200 | 201 | $this->load('roles'); 202 | 203 | return $detached; 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /src/Traits/RefreshPermissionsCache.php: -------------------------------------------------------------------------------- 1 | check() && auth()->user() instanceof Model) { 14 | auth()->user()->load('roles'); 15 | } 16 | 17 | /** @var string $key */ 18 | $key = config('acl.cache.key', 'permissions.policies'); 19 | 20 | app('cache.store')->forget($key); 21 | app(GateRegistrar::class)->register(); 22 | }); 23 | 24 | static::deleted(function () { 25 | if (auth()->check() && auth()->user() instanceof Model) { 26 | auth()->user()->load('roles'); 27 | } 28 | 29 | /** @var string $key */ 30 | $key = config('acl.cache.key', 'permissions.policies'); 31 | 32 | app('cache.store')->forget($key); 33 | app(GateRegistrar::class)->register(); 34 | }); 35 | } 36 | } 37 | --------------------------------------------------------------------------------