├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json ├── config └── permission.php ├── database └── migrations │ └── create_permission_collections.php.stub ├── renovate.json └── src ├── Commands ├── CreatePermission.php └── CreateRole.php ├── Contracts ├── PermissionInterface.php └── RoleInterface.php ├── Directives └── PermissionDirectives.php ├── Exceptions ├── GuardDoesNotMatch.php ├── MakladException.php ├── PermissionAlreadyExists.php ├── PermissionDoesNotExist.php ├── RoleAlreadyExists.php ├── RoleDoesNotExist.php ├── UnauthorizedException.php ├── UnauthorizedPermission.php ├── UnauthorizedRole.php └── UserNotLoggedIn.php ├── Guard.php ├── Helpers.php ├── Middlewares ├── PermissionMiddleware.php └── RoleMiddleware.php ├── Models ├── Permission.php └── Role.php ├── PermissionRegistrar.php ├── PermissionServiceProvider.php └── Traits ├── HasPermissions.php ├── HasRoles.php └── RefreshesPermissionCache.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All Notable changes to `laravel-permission-mongodb` will be documented in this file. 4 | 5 | ## 5.0.0-alpha - 2022-07-01 6 | 7 | ### Added 8 | - Support of PHP 8.0 9 | - Update Relations between models 10 | 11 | 12 | ## 4.0.0 - 2022-05-15 13 | 14 | ### Added 15 | - Support of Laravel 9.x 16 | - Added some return toward PHP 8 transitioning to require return types 17 | - Use of DatabaseMigration and Seeder in tests 18 | - Fix some tests (api guard is no more in auth.php by default) 19 | 20 | 21 | ## 3.1.0 - 2020-10-04 22 | 23 | ### Added 24 | - Support of Laravel 8.x 25 | 26 | ## 3.0.0 - 2020-09-27 27 | 28 | ### Added 29 | - Support of Laravel 7.x 30 | 31 | ## 2.0.1 - 2020-02-23 32 | 33 | ### Changed 34 | - Defer registering permissions on the Gate instance until it's resolved 35 | 36 | ## 2.0.0 - 2020-02-20 37 | 38 | ### Added 39 | - Support of Laravel 6.x 40 | 41 | ## 1.10.1 - 2018-09-16 42 | 43 | ### Fixed 44 | - Fix test coverage 45 | 46 | ## 1.10.0 - 2018-09-15 47 | 48 | ### Added 49 | - Add migration files 50 | 51 | ### Changed 52 | - Update PermissionRegistrar to use Authorizable 53 | - Improve readme description of how defaults work with multiple guards 54 | - Replacing static Permission::class and Role::class with dynamic value 55 | - Improve speed of findByName 56 | 57 | ## 1.9.0 - 2018-09-14 58 | 59 | ### Fixed 60 | - Fix wrong BelongsTo relationship 61 | - Config cleanup 62 | - Fixes for Lumen 5.6 compatibility 63 | - Fix classes resolution to config values 64 | - Fix permissions via roles 65 | - Fixed detection of Lumen 66 | 67 | ### Added 68 | - Add scrutinizer code intelligence 69 | 70 | ### Changed 71 | - Loose typing definitions for BelongsToMany 72 | 73 | ## 1.8.2 - 2018-08-14 74 | 75 | ### Changed 76 | - Exclude yml files from export 77 | 78 | ## 1.8.1 - 2018-06-24 79 | 80 | ### Changed 81 | - Move permission functionality from HasRoles Trait to HasPermissions Trait 82 | 83 | ## 1.8.0 - 2018-04-15 84 | 85 | ### Added 86 | - Allow assign/sync/remove Roles from Permission model 87 | 88 | ## 1.7.1 - 2018-04-09 89 | 90 | ### Added 91 | - Allow missing guard driver param (Spark compatibility) 92 | 93 | ## 1.7.0 - 2018-03-21 94 | 95 | ### Added 96 | - Support getting guard_name from extended model 97 | - Add required permissions and roles in exception object 98 | - Add the option to hide and show permissions in exceptions 99 | 100 | ## 1.6.0 - 2018-02-17 101 | 102 | ### Added 103 | - Officially support `laravel 5.6` 104 | - Improve Lumen support 105 | 106 | ## 1.5.3 - 2018-02-07 107 | 108 | ### Added 109 | - add findOrCreate to Permissions 110 | - add findOrCreate to Roles 111 | 112 | ### Fixed 113 | - use sync([]) instead of detach() 114 | - fix soft deleting in laravel 5.2 and 5.3 115 | 116 | ## 1.5.2 - 2018-01-25 117 | 118 | ### Added 119 | - Added multiple Revoke Permissions 120 | - Added multiple Remove Roles 121 | - Remove SensioLabsInsight badge 122 | 123 | 124 | ## 1.5.1 - 2018-01-22 125 | 126 | ### Added 127 | - Added Lumen support 128 | 129 | ## 1.5.0 - 2018-01-08 130 | 131 | ### Added 132 | - Handle Http Exceptions as Unauthorized Exception 133 | 134 | ## 1.4.0 - 2018-01-01 135 | 136 | ### Added 137 | - Officially Support `laravel 5.5` 138 | 139 | ## 1.3.5 - 2017-10-18 140 | 141 | ### Added 142 | - Give Permissions to roles in Command Line 143 | 144 | ### Fixed 145 | - Fixed a bug where `Role`s and `Permission`s got detached when soft deleting a model 146 | 147 | 148 | ## 1.3.4 - 2017-09-28 149 | 150 | ### Added 151 | - Add the support of `laravel 5.2` 152 | 153 | ## 1.3.3 - 2017-09-27 154 | 155 | ### Added 156 | - Add the support of `laravel 5.3` 157 | 158 | 159 | ## 1.4.0-alpha - 2017-09-19 160 | 161 | ### Added 162 | - Add the support of `laravel 5.5` 163 | 164 | 165 | ## 1.3.2 - 2017-09-12 166 | 167 | ### Removed 168 | - Remove the support of `laravel 5.5` till `jenssegers/laravel-mongodb` supports it 169 | 170 | 171 | ## 1.3.1 - 2017-09-11 172 | 173 | ### Added 174 | - Add convertToRoleModels and convertToPermissionModels 175 | 176 | ### Fixed 177 | - Register Blade extensions 178 | 179 | 180 | ## 1.3.0 - 2017-09-09 181 | 182 | ### Added 183 | - Added permission scope to HasRoles trait 184 | - Update dependencies 185 | 186 | ### Changed 187 | - Register Blade extensions in boot instead of register 188 | 189 | 190 | ## 1.2.2 - 2017-09-07 191 | 192 | ### Fixed 193 | - Recreate Exceptions 194 | - Fix most PHP Code Sniffer errors 195 | - Fix some PHP Mess Detector errors 196 | 197 | 198 | ## 1.2.1 - 2017-09-05 199 | 200 | ### Added 201 | - Let middleware use caching 202 | - Allow logging while exceptions 203 | 204 | 205 | ## 1.2.0 - 2017-09-03 206 | 207 | ### Added 208 | - Add getRoleNames() method to return a collection of assigned roles 209 | - Add getPermissionNames() method to return a collection of all assigned permissions 210 | 211 | 212 | ## 1.1.0 - 2017-09-01 213 | 214 | ### Added 215 | - Adding support of `Laravel 5.5` 216 | 217 | ### Fixed 218 | - Remove the role and permission relation when delete user 219 | - Code quality enhancements 220 | 221 | 222 | ## 1.0.0 - 2017-08-21 223 | 224 | ### Added 225 | - Everything, initial release 226 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Mostafa Abd El-Salam Maklad 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![ReadMeSupportPalestine](https://raw.githubusercontent.com/Safouene1/support-palestine-banner/master/banner-support.svg)](https://sahem.ksrelief.org/Pages/ProgramDetails/1ca8852b-9e6d-ee11-b83f-005056ac5498) 2 | # laravel-permission-mongodb 3 | 4 | [![Latest Version on Packagist][ico-version]][link-releases] 5 | [![Software License][ico-license]](LICENSE.md) 6 | [![Build Status][ico-travis]][link-travis] 7 | [![Scrutinizer][ico-scrutinizer]][link-scrutinizer] 8 | [![Maintainability][ico-codeclimate-maintainability]][link-codeclimate-maintainability] 9 | [![Codacy Badge][ico-codacy]][link-codacy] 10 | [![StyleCI][ico-styleci]][link-styleci] 11 | [![Coverage Status][ico-coveralls]][link-coveralls] 12 | [![Total Downloads][ico-downloads]][link-packagist] 13 | [![StandWithPalestine](https://raw.githubusercontent.com/Safouene1/support-palestine-banner/master/StandWithPalestine.svg)](https://sahem.ksrelief.org/Pages/ProgramDetails/1ca8852b-9e6d-ee11-b83f-005056ac5498) 14 | 15 | This package allows you to manage user permissions and roles in a database. 16 | It is inspired from [laravel-permission][link-laravel-permission]. Same code same every thing but it is compatible with [laravel-mongodb][link-laravel-mongodb] 17 | 18 | Once installed you can do stuff like this: 19 | 20 | ```php 21 | // Adding permissions to a user 22 | $user->givePermissionTo('edit articles'); 23 | 24 | // Adding permissions via a role 25 | $user->assignRole('writer'); 26 | 27 | $role->givePermissionTo('edit articles'); 28 | ``` 29 | 30 | If you're using multiple guards we've got you covered as well. Every guard will have its own set of permissions and roles that can be assigned to the guard's users. Read about it in the [using multiple guards](#using-multiple-guards) section of the readme. 31 | 32 | Because all permissions will be registered on [Laravel's gate](https://laravel.com/docs/5.5/authorization), you can test if a user has a permission with Laravel's default `can` function: 33 | 34 | ```php 35 | $user->can('edit articles'); 36 | ``` 37 | 38 | ## Table of contents 39 | * [Installation](#installation) 40 | * [Laravel Compatibility](#laravel-compatibility) 41 | * [Laravel](#laravel) 42 | * [Lumen](#lumen) 43 | * [Usage](#usage) 44 | * [Using "direct" permissions](#using-direct-permissions) 45 | * [Using permissions via roles](#using-permissions-via-roles) 46 | * [Using Blade directives](#using-blade-directives) 47 | * [Using multiple guards](#using-multiple-guards) 48 | * [Using permissions and roles with multiple guards](#using-permissions-and-roles-with-multiple-guards) 49 | * [Assigning permissions and roles to guard users](#assigning-permissions-and-roles-to-guard-users) 50 | * [Using blade directives with multiple guards](#using-blade-directives-with-multiple-guards) 51 | * [Using a middleware](#using-a-middleware) 52 | * [Using artisan commands](#using-artisan-commands) 53 | * [Unit Testing](#unit-testing) 54 | * [Database Seeding](#database-seeding) 55 | * [Extending](#extending) 56 | * [Cache](#cache) 57 | * [Manual cache reset](#manual-cache-reset) 58 | * [Cache Identifier](#cache-identifier) 59 | * [Need a UI?](#need-a-ui) 60 | * [Change log](#change-log) 61 | * [Testing](#testing) 62 | * [Contributing](#contributing) 63 | * [Security](#security) 64 | * [Credits](#credits) 65 | * [License](#license) 66 | 67 | ## Installation 68 | 69 | ### Laravel Compatibility 70 | 71 | Laravel | Package 72 | :---------|:---------- 73 | 5.x | 1.x or 2.x or 3.x 74 | 6.x | 2.x or 3.x 75 | 7.x | 3.x 76 | 8.x | 3.1.x 77 | 9.x | 4.x 78 | 79 | ### Laravel 80 | 81 | You can install the package via composer: 82 | 83 | For laravel 9.x use 84 | 85 | ``` bash 86 | composer require mostafamaklad/laravel-permission-mongodb 87 | ``` 88 | 89 | For laravel 8.x and older use 90 | 91 | ``` bash 92 | composer require mostafamaklad/laravel-permission-mongodb:"^3.1" 93 | ``` 94 | 95 | You can publish [the migration](database/migrations/create_permission_collections.php.stub) with: 96 | 97 | ```bash 98 | php artisan vendor:publish --provider="Maklad\Permission\PermissionServiceProvider" --tag="migrations" 99 | ``` 100 | 101 | ```bash 102 | php artisan migrate 103 | ``` 104 | 105 | You can publish the config file with: 106 | 107 | ```bash 108 | php artisan vendor:publish --provider="Maklad\Permission\PermissionServiceProvider" --tag="config" 109 | ``` 110 | 111 | When published, the [`config/permission.php`](config/permission.php) config file contains: 112 | 113 | ```php 114 | return [ 115 | 116 | 'models' => [ 117 | 118 | /* 119 | * When using the "HasRoles" trait from this package, we need to know which 120 | * Moloquent model should be used to retrieve your permissions. Of course, it 121 | * is often just the "Permission" model but you may use whatever you like. 122 | * 123 | * The model you want to use as a Permission model needs to implement the 124 | * `Maklad\Permission\Contracts\Permission` contract. 125 | */ 126 | 127 | 'permission' => Maklad\Permission\Models\Permission::class, 128 | 129 | /* 130 | * When using the "HasRoles" trait from this package, we need to know which 131 | * Moloquent model should be used to retrieve your roles. Of course, it 132 | * is often just the "Role" model but you may use whatever you like. 133 | * 134 | * The model you want to use as a Role model needs to implement the 135 | * `Maklad\Permission\Contracts\Role` contract. 136 | */ 137 | 138 | 'role' => Maklad\Permission\Models\Role::class, 139 | 140 | ], 141 | 142 | 'collection_names' => [ 143 | 144 | /* 145 | * When using the "HasRoles" trait from this package, we need to know which 146 | * table should be used to retrieve your roles. We have chosen a basic 147 | * default value but you may easily change it to any table you like. 148 | */ 149 | 150 | 'roles' => 'roles', 151 | 152 | /* 153 | * When using the "HasRoles" trait from this package, we need to know which 154 | * table should be used to retrieve your permissions. We have chosen a basic 155 | * default value but you may easily change it to any table you like. 156 | */ 157 | 158 | 'permissions' => 'permissions', 159 | ], 160 | 161 | /* 162 | * By default all permissions will be cached for 24 hours unless a permission or 163 | * role is updated. Then the cache will be flushed immediately. 164 | */ 165 | 166 | 'cache_expiration_time' => 60 * 24, 167 | 168 | /* 169 | * By default we'll make an entry in the application log when the permissions 170 | * could not be loaded. Normally this only occurs while installing the packages. 171 | * 172 | * If for some reason you want to disable that logging, set this value to false. 173 | */ 174 | 175 | 'log_registration_exception' => true, 176 | 177 | /* 178 | * When set to true, the required permission/role names are added to the exception 179 | * message. This could be considered an information leak in some contexts, so 180 | * the default setting is false here for optimum safety. 181 | */ 182 | 183 | 'display_permission_in_exception' => false, 184 | ]; 185 | ``` 186 | 187 | ### Lumen 188 | 189 | You can install the package via Composer: 190 | 191 | ``` bash 192 | composer require mostafamaklad/laravel-permission-mongodb 193 | ``` 194 | 195 | Copy the required files: 196 | 197 | ```bash 198 | cp vendor/mostafamaklad/laravel-permission-mongodb/config/permission.php config/permission.php 199 | cp vendor/mostafamaklad/laravel-permission-mongodb/database/migrations/create_permission_collections.php.stub database/migrations/2018_01_01_000000_create_permission_collections.php 200 | ``` 201 | 202 | You will also need to create another configuration file at `config/auth.php`. Get it on the Laravel repository or just run the following command: 203 | 204 | ```bash 205 | curl -Ls https://raw.githubusercontent.com/laravel/lumen-framework/5.5/config/auth.php -o config/auth.php 206 | ``` 207 | 208 | Then, in `bootstrap/app.php`, register the middlewares: 209 | 210 | ```php 211 | $app->routeMiddleware([ 212 | 'auth' => App\Http\Middleware\Authenticate::class, 213 | 'permission' => Maklad\Permission\Middlewares\PermissionMiddleware::class, 214 | 'role' => Maklad\Permission\Middlewares\RoleMiddleware::class, 215 | ]); 216 | ``` 217 | 218 | As well as the configuration and the service provider: 219 | 220 | ```php 221 | $app->configure('permission'); 222 | $app->register(Maklad\Permission\PermissionServiceProvider::class); 223 | ``` 224 | 225 | Now, run your migrations: 226 | 227 | ```bash 228 | php artisan migrate 229 | ``` 230 | 231 | ## Usage 232 | 233 | First, add the `Maklad\Permission\Traits\HasRoles` trait to your `User` model(s): 234 | 235 | ```php 236 | use Illuminate\Auth\Authenticatable; 237 | use Jenssegers\Mongodb\Eloquent\Model as Model; 238 | use Illuminate\Foundation\Auth\Access\Authorizable; 239 | use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract; 240 | use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract; 241 | use Maklad\Permission\Traits\HasRoles; 242 | 243 | class User extends Model implements AuthenticatableContract, AuthorizableContract 244 | { 245 | use Authenticatable, Authorizable, HasRoles; 246 | 247 | // ... 248 | } 249 | ``` 250 | 251 | > Note: that if you need to use `HasRoles` trait with another model ex.`Page` you will also need to add `protected $guard_name = 'web';` as well to that model or you would get an error 252 | 253 | ```php 254 | use Jenssegers\Mongodb\Eloquent\Model as Model; 255 | use Maklad\Permission\Traits\HasRoles; 256 | 257 | class Page extends Model 258 | { 259 | use HasRoles; 260 | 261 | protected $guard_name = 'web'; // or whatever guard you want to use 262 | 263 | // ... 264 | } 265 | ``` 266 | 267 | This package allows for users to be associated with permissions and roles. Every role is associated with multiple permissions. 268 | A `Role` and a `Permission` are regular Moloquent models. They require a `name` and can be created like this: 269 | 270 | ```php 271 | use Maklad\Permission\Models\Role; 272 | use Maklad\Permission\Models\Permission; 273 | 274 | $role = Role::create(['name' => 'writer']); 275 | $permission = Permission::create(['name' => 'edit articles']); 276 | ``` 277 | 278 | A permission can be assigned to a role using 1 of these methods: 279 | 280 | ```php 281 | $role->givePermissionTo($permission); 282 | $permission->assignRole($role); 283 | ``` 284 | 285 | Multiple permissions can be synced to a role using 1 of these methods: 286 | 287 | ```php 288 | $role->syncPermissions($permissions); 289 | $permission->syncRoles($roles); 290 | ``` 291 | 292 | A permission can be removed from a role using 1 of these methods: 293 | 294 | ```php 295 | $role->revokePermissionTo($permission); 296 | $permission->removeRole($role); 297 | ``` 298 | 299 | If you're using multiple guards the `guard_name` attribute needs to be set as well. Read about it in the [using multiple guards](#using-multiple-guards) section of the readme. 300 | 301 | The `HasRoles` trait adds Moloquent relationships to your models, which can be accessed directly or used as a base query: 302 | 303 | ```php 304 | // get a list of all permissions directly assigned to the user 305 | $permissions = $user->permissions; // Returns a collection 306 | 307 | // get all permissions inherited by the user via roles 308 | $permissions = $user->getAllPermissions(); // Returns a collection 309 | 310 | // get all permissions names 311 | $permissions = $user->getPermissionNames(); // Returns a collection 312 | 313 | // get a collection of all defined roles 314 | $roles = $user->roles->pluck('name'); // Returns a collection 315 | 316 | // get all role names 317 | $roles = $user->getRoleNames() // Returns a collection; 318 | ``` 319 | 320 | The `HasRoles` trait also adds a `role` scope to your models to scope the query to certain roles or permissions: 321 | 322 | ```php 323 | $users = User::role('writer')->get(); // Returns only users with the role 'writer' 324 | $users = User::permission('edit articles')->get(); // Returns only users with the permission 'edit articles' 325 | ``` 326 | 327 | The scope can accept a string, a `\Maklad\Permission\Models\Role` object, a `\Maklad\Permission\Models\Permission` object or an `\Illuminate\Support\Collection` object. 328 | 329 | 330 | ### Using "direct" permissions 331 | 332 | A permission can be given to any user with the `HasRoles` trait: 333 | 334 | ```php 335 | $user->givePermissionTo('edit articles'); 336 | 337 | // You can also give multiple permission at once 338 | $user->givePermissionTo('edit articles', 'delete articles'); 339 | 340 | // You may also pass an array 341 | $user->givePermissionTo(['edit articles', 'delete articles']); 342 | ``` 343 | 344 | A permission can be revoked from a user: 345 | 346 | ```php 347 | $user->revokePermissionTo('edit articles'); 348 | ``` 349 | 350 | Or revoke & add new permissions in one go: 351 | 352 | ```php 353 | $user->syncPermissions(['edit articles', 'delete articles']); 354 | ``` 355 | 356 | You can test if a user has a permission: 357 | 358 | ```php 359 | $user->hasPermissionTo('edit articles'); 360 | ``` 361 | 362 | ...or if a user has multiple permissions: 363 | 364 | ```php 365 | $user->hasAnyPermission(['edit articles', 'publish articles', 'unpublish articles']); 366 | ``` 367 | 368 | Saved permissions will be registered with the `Illuminate\Auth\Access\Gate` class for the default guard. So you can 369 | test if a user has a permission with Laravel's default `can` function: 370 | 371 | ```php 372 | $user->can('edit articles'); 373 | ``` 374 | 375 | ### Using permissions via roles 376 | 377 | A role can be assigned to any user: 378 | 379 | ```php 380 | $user->assignRole('writer'); 381 | 382 | // You can also assign multiple roles at once 383 | $user->assignRole('writer', 'admin'); 384 | // or as an array 385 | $user->assignRole(['writer', 'admin']); 386 | ``` 387 | 388 | A role can be removed from a user: 389 | 390 | ```php 391 | $user->removeRole('writer'); 392 | ``` 393 | 394 | Roles can also be synced: 395 | 396 | ```php 397 | // All current roles will be removed from the user and replaced by the array given 398 | $user->syncRoles(['writer', 'admin']); 399 | ``` 400 | 401 | You can determine if a user has a certain role: 402 | 403 | ```php 404 | $user->hasRole('writer'); 405 | ``` 406 | 407 | You can also determine if a user has any of a given list of roles: 408 | 409 | ```php 410 | $user->hasAnyRole(Role::all()); 411 | ``` 412 | 413 | You can also determine if a user has all of a given list of roles: 414 | 415 | ```php 416 | $user->hasAllRoles(Role::all()); 417 | ``` 418 | 419 | The `assignRole`, `hasRole`, `hasAnyRole`, `hasAllRoles` and `removeRole` functions can accept a 420 | string, a `\Maklad\Permission\Models\Role` object or an `\Illuminate\Support\Collection` object. 421 | 422 | A permission can be given to a role: 423 | 424 | ```php 425 | $role->givePermissionTo('edit articles'); 426 | ``` 427 | 428 | You can determine if a role has a certain permission: 429 | 430 | ```php 431 | $role->hasPermissionTo('edit articles'); 432 | ``` 433 | 434 | A permission can be revoked from a role: 435 | 436 | ```php 437 | $role->revokePermissionTo('edit articles'); 438 | ``` 439 | 440 | The `givePermissionTo` and `revokePermissionTo` functions can accept a 441 | string or a `Maklad\Permission\Models\Permission` object. 442 | 443 | Permissions are inherited from roles automatically. 444 | Additionally, individual permissions can be assigned to the user too. 445 | 446 | For instance: 447 | 448 | ```php 449 | $role = Role::findByName('writer'); 450 | $role->givePermissionTo('edit articles'); 451 | 452 | $user->assignRole('writer'); 453 | 454 | $user->givePermissionTo('delete articles'); 455 | ``` 456 | 457 | In the above example, a role is given permission to edit articles and this role is assigned to a user. 458 | Now the user can edit articles and additionally delete articles. The permission of `delete articles` is the user's direct permission because it is assigned directly to them. 459 | When we call `$user->hasDirectPermission('delete articles')` it returns `true`, but `false` for `$user->hasDirectPermission('edit articles')`. 460 | 461 | This method is useful if one builds a form for setting permissions for roles and users in an application and wants to restrict or change inherited permissions of roles of the user, i.e. allowing to change only direct permissions of the user. 462 | 463 | You can list all of these permissions: 464 | 465 | ```php 466 | // Direct permissions 467 | $user->getDirectPermissions() // Or $user->permissions; 468 | 469 | // Permissions inherited from the user's roles 470 | $user->getPermissionsViaRoles(); 471 | 472 | // All permissions which apply on the user (inherited and direct) 473 | $user->getAllPermissions(); 474 | ``` 475 | 476 | All these responses are collections of `Maklad\Permission\Models\Permission` objects. 477 | 478 | If we follow the previous example, the first response will be a collection with the `delete article` permission, the 479 | second will be a collection with the `edit article` permission and the third will contain both. 480 | 481 | ### Using Blade directives 482 | This package also adds Blade directives to verify whether the currently logged in user has all or any of a given list of roles. 483 | 484 | Optionally you can pass in the `guard` that the check will be performed on as a second argument. 485 | #### Blade and Roles 486 | Test for a specific role: 487 | ```php 488 | @role('writer') 489 | I am a writer! 490 | @else 491 | I am not a writer... 492 | @endrole 493 | ``` 494 | is the same as 495 | ```php 496 | @hasrole('writer') 497 | I am a writer! 498 | @else 499 | I am not a writer... 500 | @endhasrole 501 | ``` 502 | Test for any role in a list: 503 | ```php 504 | @hasanyrole(Role::all()) 505 | I have one or more of these roles! 506 | @else 507 | I have none of these roles... 508 | @endhasanyrole 509 | // or 510 | @hasanyrole('writer|admin') 511 | I am either a writer or an admin or both! 512 | @else 513 | I have none of these roles... 514 | @endhasanyrole 515 | ``` 516 | Test for all roles: 517 | ```php 518 | @hasallroles(Role::all()) 519 | I have all of these roles! 520 | @else 521 | I do not have all of these roles... 522 | @endhasallroles 523 | // or 524 | @hasallroles('writer|admin') 525 | I am both a writer and an admin! 526 | @else 527 | I do not have all of these roles... 528 | @endhasallroles 529 | ``` 530 | 531 | #### Blade and Permissions 532 | This package doesn't add any permission-specific Blade directives. Instead, use Laravel's native `@can` directive to check if a user has a certain permission. 533 | 534 | ```php 535 | @can('edit articles') 536 | // 537 | @endcan 538 | ``` 539 | or 540 | ```php 541 | @if(auth()->user()->can('edit articles') && $some_other_condition) 542 | // 543 | @endif 544 | ``` 545 | 546 | ## Using multiple guards 547 | 548 | When using the default Laravel auth configuration all of the above methods will work out of the box, no extra configuration required. 549 | 550 | However when using multiple guards they will act like namespaces for your permissions and roles. Meaning every guard has its own set of permissions and roles that can be assigned to their user model. 551 | 552 | ### Using permissions and roles with multiple guards 553 | 554 | When creating new permissions and roles, if no guard is specified, then the **first** defined guard in `auth.guards` config array will be used. When creating permissions and roles for specific guards you'll have to specify their `guard_name` on the model: 555 | 556 | ```php 557 | // Create a superadmin role for the admin users 558 | 559 | $user->hasPermissionTo('publish articles', 'admin'); 560 | ``` 561 | 562 | > **Note**: When determining whether a role/permission is valid on a given model, it chooses the guard in this order: first the `$guard_name` property of the model; then the guard in the config (through a provider); then the first-defined guard in the `auth.guards` config array; then the `auth.defaults.guard` config. 563 | 564 | ### Assigning permissions and roles to guard users 565 | 566 | You can use the same methods to assign permissions and roles to users as described above in [using permissions via roles](#using-permissions-via-roles). Just make sure the `guard_name` on the permission or role matches the guard of the user, otherwise a `GuardDoesNotMatch` exception will be thrown. 567 | 568 | ### Using blade directives with multiple guards 569 | 570 | You can use all of the blade directives listed in [using blade directives](#using-blade-directives) by passing in the guard you wish to use as the second argument to the directive: 571 | 572 | ```php 573 | @role('super-admin', 'admin') 574 | I am a super-admin! 575 | @else 576 | I am not a super-admin... 577 | @endrole 578 | ``` 579 | 580 | ## Using a middleware 581 | 582 | This package comes with `RoleMiddleware` and `PermissionMiddleware` middleware. You can add them inside your `app/Http/Kernel.php` file. 583 | 584 | ```php 585 | protected $routeMiddleware = [ 586 | // ... 587 | 'role' => \Maklad\Permission\Middlewares\RoleMiddleware::class, 588 | 'permission' => \Maklad\Permission\Middlewares\PermissionMiddleware::class, 589 | ]; 590 | ``` 591 | 592 | Then you can protect your routes using middleware rules: 593 | 594 | ```php 595 | Route::group(['middleware' => ['role:super-admin']], function () { 596 | // 597 | }); 598 | 599 | Route::group(['middleware' => ['permission:publish articles']], function () { 600 | // 601 | }); 602 | 603 | Route::group(['middleware' => ['role:super-admin','permission:publish articles']], function () { 604 | // 605 | }); 606 | ``` 607 | You can protect your controllers similarly, by setting desired middleware in the constructor: 608 | 609 | ```php 610 | public function __construct() 611 | { 612 | $this->middleware(['role:super-admin','permission:publish articles|edit articles']); 613 | } 614 | ``` 615 | 616 | You can add something in Laravel exception handler: 617 | 618 | ```php 619 | public function render($request, Exception $exception) 620 | { 621 | if ($exception instanceof \Maklad\Permission\Exceptions\UnauthorizedException) { 622 | // Code here ... 623 | } 624 | 625 | return parent::render($request, $exception); 626 | } 627 | 628 | ``` 629 | 630 | ## Using artisan commands 631 | 632 | You can create a role or permission from a console with artisan commands. 633 | 634 | ```bash 635 | php artisan permission:create-role writer 636 | ``` 637 | 638 | ```bash 639 | php artisan permission:create-permission 'edit articles' 640 | ``` 641 | 642 | When creating permissions and roles for specific guards you can specify the guard names as a second argument: 643 | 644 | ```bash 645 | php artisan permission:create-role writer web 646 | ``` 647 | 648 | ```bash 649 | php artisan permission:create-permission 'edit articles' web 650 | ``` 651 | 652 | ## Unit Testing 653 | 654 | In your application's tests, if you are not seeding roles and permissions as part of your test `setUp()` then you may run into a chicken/egg situation where roles and permissions aren't registered with the gate (because your tests create them after that gate registration is done). Working around this is simple: In your tests simply add a `setUp()` instruction to re-register the permissions, like this: 655 | 656 | ```php 657 | public function setUp() 658 | { 659 | // first include all the normal setUp operations 660 | parent::setUp(); 661 | 662 | // now re-register all the roles and permissions 663 | $this->app->make(\Maklad\Permission\PermissionRegistrar::class)->registerPermissions(); 664 | } 665 | ``` 666 | 667 | ## Database Seeding 668 | 669 | Two notes about Database Seeding: 670 | 671 | 1. It is best to flush the `maklad.permission.cache` before seeding, to avoid cache conflict errors. This can be done from an Artisan command (see Troubleshooting: Cache section, later) or directly in a seeder class (see example below). 672 | 673 | 2. Here's a sample seeder, which clears the cache, creates permissions, and then assigns permissions to roles: 674 | ```php 675 | use Illuminate\Database\Seeder; 676 | use Maklad\Permission\Models\Role; 677 | use Maklad\Permission\Models\Permission; 678 | 679 | class RolesAndPermissionsSeeder extends Seeder 680 | { 681 | public function run() 682 | { 683 | // Reset cached roles and permissions 684 | app()['cache']->forget('maklad.permission.cache'); 685 | 686 | // create permissions 687 | Permission::firstOrCreate(['name' => 'edit articles']); 688 | Permission::firstOrCreate(['name' => 'delete articles']); 689 | Permission::firstOrCreate(['name' => 'publish articles']); 690 | Permission::firstOrCreate(['name' => 'unpublish articles']); 691 | 692 | // create roles and assign existing permissions 693 | $role = Role::firstOrCreate(['name' => 'writer']); 694 | $role->givePermissionTo('edit articles'); 695 | $role->givePermissionTo('delete articles'); 696 | 697 | $role = Role::firstOrCreate(['name' => 'admin']); 698 | $role->givePermissionTo(['publish articles', 'unpublish articles']); 699 | } 700 | } 701 | ``` 702 | 703 | ## Extending 704 | If you need to EXTEND the existing `Role` or `Permission` models note that: 705 | 706 | - Your `Role` model needs to extend the `Maklad\Permission\Models\Role` model 707 | - Your `Permission` model needs to extend the `Maklad\Permission\Models\Permission` model 708 | 709 | If you need to extend or replace the existing `Role` or `Permission` models you just need to 710 | keep the following things in mind: 711 | 712 | - Your `Role` model needs to implement the `Maklad\Permission\Contracts\Role` contract 713 | - Your `Permission` model needs to implement the `Maklad\Permission\Contracts\Permission` contract 714 | 715 | In BOTH cases, whether extending or replacing, you will need to specify your new models in the configuration. To do this you must update the `models.role` and `models.permission` values in the configuration file after publishing the configuration with this command: 716 | ```bash 717 | php artisan vendor:publish --provider="Maklad\Permission\PermissionServiceProvider" --tag="config" 718 | ``` 719 | 720 | ## Cache 721 | 722 | Role and Permission data are cached to speed up performance. 723 | 724 | When you use the supplied methods for manipulating roles and permissions, the cache is automatically reset for you: 725 | 726 | ```php 727 | $user->assignRole('writer'); 728 | $user->removeRole('writer'); 729 | $user->syncRoles(params); 730 | $role->givePermissionTo('edit articles'); 731 | $role->revokePermissionTo('edit articles'); 732 | $role->syncPermissions(params); 733 | $permission->assignRole('writer'); 734 | $permission->removeRole('writer'); 735 | $permission->syncRoles(params); 736 | ``` 737 | 738 | HOWEVER, if you manipulate permission/role data directly in the database instead of calling the supplied methods, then you will not see the changes reflected in the application unless you manually reset the cache. 739 | 740 | ### Manual cache reset 741 | To manually reset the cache for this package, run: 742 | ```bash 743 | php artisan cache:forget maklad.permission.cache 744 | ``` 745 | 746 | ### Cache Identifier 747 | 748 | > Note: If you are leveraging a caching service such as `redis` or `memcached` and there are other sites running on your server, you could run into cache clashes. It is prudent to set your own cache `prefix` in `/config/cache.php` for each application uniquely. This will prevent other applications from accidentally using/changing your cached data. 749 | 750 | ## Need a UI? 751 | 752 | As we are based on [laravel-permission][link-laravel-permission]. The package doesn't come with any screens out of the box, you should build that yourself. To get started check out [this extensive tutorial](https://scotch.io/tutorials/user-authorization-in-laravel-54-with-spatie-laravel-permission) by [Caleb Oki](http://www.caleboki.com/). 753 | 754 | ## Change log 755 | 756 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 757 | 758 | ## Testing 759 | 760 | ``` bash 761 | composer test 762 | ``` 763 | 764 | ## Contributing 765 | 766 | Please see [CONTRIBUTING](.github/CONTRIBUTING.md) and [CONDUCT](.github/CONDUCT.md) for details. 767 | 768 | ## Security 769 | 770 | If you discover any security-related issues, please email dev.mostafa.maklad@gmail.com instead of using the issue tracker. 771 | 772 | ## Credits 773 | 774 | - [Freek Van der Herten][link-freekmurze] 775 | - [Mostafa Maklad][link-author] 776 | - [All Contributors][link-contributors] 777 | 778 | ## License 779 | 780 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 781 | 782 | 783 | [link-packagist]: https://packagist.org/packages/mostafamaklad/laravel-permission-mongodb 784 | [ico-version]: https://img.shields.io/packagist/v/mostafamaklad/laravel-permission-mongodb.svg?style=flat-square 785 | [ico-license]: https://img.shields.io/packagist/l/mostafamaklad/laravel-permission-mongodb.svg?style=flat-square 786 | [ico-downloads]: https://img.shields.io/packagist/dt/mostafamaklad/laravel-permission-mongodb.svg?style=flat-square 787 | 788 | [link-laravel-5.2]: https://laravel.com/docs/5.2 789 | [ico-laravel-5.2]: https://img.shields.io/badge/Laravel-5.2.x-brightgreen.svg?style=flat-square 790 | [link-laravel-5.3]: https://laravel.com/docs/5.3 791 | [ico-laravel-5.3]: https://img.shields.io/badge/Laravel-5.3.x-brightgreen.svg?style=flat-square 792 | [link-laravel-5.4]: https://laravel.com/docs/5.4 793 | [ico-laravel-5.4]: https://img.shields.io/badge/Laravel-5.4.x-brightgreen.svg?style=flat-square 794 | [link-laravel-5.5]: https://laravel.com/docs/5.5 795 | [ico-laravel-5.5]: https://img.shields.io/badge/Laravel-5.5.x-brightgreen.svg?style=flat-square 796 | [link-laravel-5.6]: https://laravel.com/docs/5.6 797 | [ico-laravel-5.6]: https://img.shields.io/badge/Laravel-5.6.x-brightgreen.svg?style=flat-square 798 | 799 | [link-travis]: https://travis-ci.org/mostafamaklad/laravel-permission-mongodb 800 | [ico-travis]: https://img.shields.io/travis/mostafamaklad/laravel-permission-mongodb/master.svg?style=flat-square 801 | 802 | [link-scrutinizer]: https://scrutinizer-ci.com/g/mostafamaklad/laravel-permission-mongodb 803 | [link-scrutinizer-build]: https://scrutinizer-ci.com/g/mostafamaklad/laravel-permission-mongodb/build-status/master 804 | [link-scrutinizer-coverage]: https://scrutinizer-ci.com/g/mostafamaklad/laravel-permission-mongodb/code-structure 805 | [ico-scrutinizer]: https://img.shields.io/scrutinizer/g/mostafamaklad/laravel-permission-mongodb.svg?style=flat-square 806 | [ico-scrutinizer-build]: https://img.shields.io/scrutinizer/build/g/mostafamaklad/laravel-permission-mongodb.svg?style=flat-square 807 | [ico-scrutinizer-coverage]: https://img.shields.io/scrutinizer/coverage/g/mostafamaklad/laravel-permission-mongodb.svg?style=flat-square 808 | 809 | [link-coveralls]: https://coveralls.io/github/mostafamaklad/laravel-permission-mongodb 810 | [ico-coveralls]: https://img.shields.io/coveralls/mostafamaklad/laravel-permission-mongodb.svg?style=flat-square 811 | 812 | [link-styleci]: https://styleci.io/repos/100894062 813 | [ico-styleci]: https://styleci.io/repos/100894062/shield?style=flat-square 814 | 815 | [link-codeclimate]: https://codeclimate.com/github/mostafamaklad/laravel-permission-mongodb 816 | [link-codeclimate-coverage]: https://codeclimate.com/github/mostafamaklad/laravel-permission-mongodb/coverage 817 | [link-codeclimate-maintainability]: https://codeclimate.com/github/mostafamaklad/laravel-permission-mongodb/maintainability 818 | [ico-codeclimate]: https://img.shields.io/codeclimate/github/mostafamaklad/laravel-permission-mongodb.svg?style=flat-square 819 | [ico-codeclimate-coverage]: https://img.shields.io/codeclimate/coverage/github/mostafamaklad/laravel-permission-mongodb.svg?style=flat-square 820 | [ico-codeclimate-issue-count]: https://img.shields.io/codeclimate/issues/github/mostafamaklad/laravel-permission-mongodb.svg?style=flat-square 821 | [ico-codeclimate-maintainability]: https://api.codeclimate.com/v1/badges/005c3644a2db6b364514/maintainability 822 | 823 | [link-codacy]: https://www.codacy.com/app/mostafamaklad/laravel-permission-mongodb?utm_source=github.com&utm_medium=referral&utm_content=mostafamaklad/laravel-permission-mongodb&utm_campaign=Badge_Grade 824 | [ico-codacy]: https://api.codacy.com/project/badge/Grade/11620283b18945e2beb77e59ddc90624 825 | 826 | [link-sensiolabs]: https://insight.sensiolabs.com/projects/9a0d8b6f-1b6d-4f9f-ba87-ed9ab66b7707 827 | [ico-sensiolabs]: https://insight.sensiolabs.com/projects/9a0d8b6f-1b6d-4f9f-ba87-ed9ab66b7707/mini.png 828 | 829 | [link-author]: https://github.com/mostafamaklad 830 | [link-contributors]: ../../contributors 831 | [link-releases]: ../../releases 832 | [link-laravel-permission]: https://github.com/spatie/laravel-permission 833 | [link-laravel-mongodb]: https://github.com/jenssegers/laravel-mongodb 834 | [link-freekmurze]: https://github.com/freekmurze 835 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mostafamaklad/laravel-permission-mongodb", 3 | "description": "Permission handling for Laravel 5.2 and up using mongodb", 4 | "version": "dev-master", 5 | "keywords": [ 6 | "laravel", 7 | "security", 8 | "mongodb", 9 | "permission", 10 | "acl", 11 | "mostafamaklad", 12 | "maklad", 13 | "mostafa", 14 | "spatie", 15 | "jenssegers" 16 | ], 17 | "homepage": "https://github.com/mostafamaklad/laravel-permission-mongodb", 18 | "license": "MIT", 19 | "authors": [ 20 | { 21 | "name": "Mostafa Maklad", 22 | "email": "dev.mostafa.maklad@gmail.com", 23 | "homepage": "https://github.com/mostafamaklad", 24 | "role": "Developer" 25 | }, 26 | { 27 | "name": "Freek Van der Herten", 28 | "email": "freek@spatie.be", 29 | "homepage": "https://spatie.be", 30 | "role": "The original module" 31 | } 32 | ], 33 | "require": { 34 | "php": "^8.1", 35 | "illuminate/auth": "^11.0", 36 | "illuminate/container": "^11.0", 37 | "illuminate/contracts": "^11.0", 38 | "mongodb/laravel-mongodb": "^4.6" 39 | }, 40 | "require-dev": { 41 | "monolog/monolog": "^3.2", 42 | "orchestra/testbench": "^9.0", 43 | "phpunit/phpunit": "^10.0", 44 | "squizlabs/php_codesniffer": "^3.7" 45 | }, 46 | "autoload": { 47 | "psr-4": { 48 | "Maklad\\Permission\\": "src" 49 | } 50 | }, 51 | "autoload-dev": { 52 | "psr-4": { 53 | "Maklad\\Permission\\Test\\": "tests" 54 | } 55 | }, 56 | "scripts": { 57 | "test": "phpunit", 58 | "check-style": "phpcs --standard=psr2 src/", 59 | "fix-style": "phpcbf --standard=psr2 src/" 60 | }, 61 | "config": { 62 | "sort-packages": true 63 | }, 64 | "extra": { 65 | "laravel": { 66 | "providers": [ 67 | "Maklad\\Permission\\PermissionServiceProvider" 68 | ] 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /config/permission.php: -------------------------------------------------------------------------------- 1 | [ 6 | 7 | /* 8 | * When using the "HasRoles" trait from this package, we need to know which 9 | * Eloquent model should be used to retrieve your permissions. Of course, it 10 | * is often just the "Permission" model but you may use whatever you like. 11 | * 12 | * The model you want to use as a Permission model needs to implement the 13 | * `Maklad\Permission\Contracts\Permission` contract. 14 | */ 15 | 16 | 'permission' => Maklad\Permission\Models\Permission::class, 17 | 18 | /* 19 | * When using the "HasRoles" trait from this package, we need to know which 20 | * Eloquent model should be used to retrieve your roles. Of course, it 21 | * is often just the "Role" model but you may use whatever you like. 22 | * 23 | * The model you want to use as a Role model needs to implement the 24 | * `Maklad\Permission\Contracts\Role` contract. 25 | */ 26 | 27 | 'role' => Maklad\Permission\Models\Role::class, 28 | 29 | ], 30 | 31 | 'collection_names' => [ 32 | 33 | /* 34 | * When using the "HasRoles" trait from this package, we need to know which 35 | * table should be used to retrieve your roles. We have chosen a basic 36 | * default value but you may easily change it to any table you like. 37 | */ 38 | 39 | 'roles' => 'roles', 40 | 41 | /* 42 | * When using the "HasRoles" trait from this package, we need to know which 43 | * table should be used to retrieve your permissions. We have chosen a basic 44 | * default value but you may easily change it to any table you like. 45 | */ 46 | 47 | 'permissions' => 'permissions', 48 | ], 49 | 50 | /* 51 | * By default all permissions will be cached for 24 hours unless a permission or 52 | * role is updated. Then the cache will be flushed immediately. 53 | */ 54 | 55 | 'cache_expiration_time' => 60 * 24, 56 | 57 | /* 58 | * By default we'll make an entry in the application log when the permissions 59 | * could not be loaded. Normally this only occurs while installing the packages. 60 | * 61 | * If for some reason you want to disable that logging, set this value to false. 62 | */ 63 | 64 | 'log_registration_exception' => true, 65 | 66 | /* 67 | * When set to true, the required permission/role names are added to the exception 68 | * message. This could be considered an information leak in some contexts, so 69 | * the default setting is false here for optimum safety. 70 | */ 71 | 72 | 'display_permission_in_exception' => false, 73 | ]; 74 | -------------------------------------------------------------------------------- /database/migrations/create_permission_collections.php.stub: -------------------------------------------------------------------------------- 1 | unique(['name', 'guard_name']); 20 | }); 21 | 22 | Schema::table($collectionNames['permissions'], function (Blueprint $collection) { 23 | $collection->unique(['name', 'guard_name']); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | * 30 | * @return void 31 | */ 32 | public function down() 33 | { 34 | $collectionNames = config('permission.collection_names'); 35 | 36 | Schema::dropIfExists($collectionNames['roles']); 37 | Schema::dropIfExists($collectionNames['permissions']); 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /src/Commands/CreatePermission.php: -------------------------------------------------------------------------------- 1 | $this->argument('name'), 27 | 'guard_name' => $this->argument('guard') 28 | ]); 29 | 30 | $this->info("Permission `$permission->name` created"); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Commands/CreateRole.php: -------------------------------------------------------------------------------- 1 | argument('name'); 27 | $guard = $this->argument('guard'); 28 | $permissions = $this->option('permission'); 29 | 30 | $role = $roleClass::create([ 31 | 'name' => $name, 32 | 'guard_name' => $guard 33 | ]); 34 | 35 | $this->info("Role `$role->name` created"); 36 | 37 | $role->givePermissionTo($permissions); 38 | $permissionsStr = $role->permissions->implode('name', '`, `'); 39 | $this->info("Permissions `$permissionsStr` has been given to role `$role->name`"); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Contracts/PermissionInterface.php: -------------------------------------------------------------------------------- 1 | bladeCompiler = $bladeCompiler; 19 | } 20 | 21 | /** 22 | * Declare role directive 23 | */ 24 | public function roleDirective(): void 25 | { 26 | $this->bladeCompiler->directive('role', function ($arguments) { 27 | list($role, $guard) = $this->extractRoleGuard($arguments); 28 | 29 | return "check() && auth($guard)->user()->hasRole($role)): ?>"; 30 | }); 31 | 32 | $this->bladeCompiler->directive('endrole', function () { 33 | return ''; 34 | }); 35 | } 36 | 37 | /** 38 | * Declare hasrole directive 39 | */ 40 | public function hasroleDirective(): void 41 | { 42 | $this->bladeCompiler->directive('hasrole', function ($arguments) { 43 | list($role, $guard) = $this->extractRoleGuard($arguments); 44 | 45 | return "check() && auth($guard)->user()->hasRole($role)): ?>"; 46 | }); 47 | $this->bladeCompiler->directive('endhasrole', function () { 48 | return ''; 49 | }); 50 | } 51 | 52 | /** 53 | * Declare hasanyrole directive 54 | */ 55 | public function hasanyroleDirective(): void 56 | { 57 | $this->bladeCompiler->directive('hasanyrole', function ($arguments) { 58 | list($roles, $guard) = $this->extractRoleGuard($arguments); 59 | 60 | return "check() && auth($guard)->user()->hasAnyRole($roles)): ?>"; 61 | }); 62 | $this->bladeCompiler->directive('endhasanyrole', function () { 63 | return ''; 64 | }); 65 | } 66 | 67 | /** 68 | * Declare hasallroles directive 69 | */ 70 | public function hasallrolesDirective(): void 71 | { 72 | $this->bladeCompiler->directive('hasallroles', function ($arguments) { 73 | list($roles, $guard) = $this->extractRoleGuard($arguments); 74 | 75 | return "check() && auth($guard)->user()->hasAllRoles($roles)): ?>"; 76 | }); 77 | $this->bladeCompiler->directive('endhasallroles', function () { 78 | return ''; 79 | }); 80 | } 81 | 82 | /** 83 | * @param $arguments 84 | * 85 | * @return array 86 | */ 87 | private function extractRoleGuard($arguments): array 88 | { 89 | $arguments = preg_replace('([() ])', '', $arguments); 90 | 91 | return explode(',', $arguments . ','); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/Exceptions/GuardDoesNotMatch.php: -------------------------------------------------------------------------------- 1 | alert($message); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Exceptions/PermissionAlreadyExists.php: -------------------------------------------------------------------------------- 1 | alert($message); 37 | } 38 | 39 | $this->requiredRoles = $requiredRoles; 40 | $this->requiredPermissions = $requiredPermissions; 41 | } 42 | 43 | /** 44 | * Return Required Roles 45 | * 46 | * @return array 47 | */ 48 | public function getRequiredRoles(): array 49 | { 50 | return $this->requiredRoles; 51 | } 52 | 53 | /** 54 | * Return Required Permissions 55 | * 56 | * @return array 57 | */ 58 | public function getRequiredPermissions(): array 59 | { 60 | return $this->requiredPermissions; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Exceptions/UnauthorizedPermission.php: -------------------------------------------------------------------------------- 1 | guard_name ?? null; 33 | } 34 | 35 | if ($guardName === null) { 36 | $class = is_object($model) ? get_class($model) : $model; 37 | $guardName = (new ReflectionClass($class))->getDefaultProperties()['guard_name'] ?? null; 38 | } 39 | 40 | if ($guardName) { 41 | return collect($guardName); 42 | } 43 | 44 | return collect(config('auth.guards')) 45 | ->map(function ($guard) { 46 | if (! isset($guard['provider'])) { 47 | return; 48 | } 49 | return config("auth.providers.{$guard['provider']}.model"); 50 | }) 51 | ->filter(function ($model) use ($class) { 52 | return $class === $model; 53 | }) 54 | ->keys(); 55 | } 56 | 57 | /** 58 | * Return Default Guard name 59 | * 60 | * @param $class 61 | * 62 | * @return string 63 | * @throws ReflectionException 64 | */ 65 | public function getDefaultName($class): string 66 | { 67 | $default = config('auth.defaults.guard'); 68 | return $this->getNames($class)->first() ?: $default; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Helpers.php: -------------------------------------------------------------------------------- 1 | map(function ($guard) { 24 | return config("auth.providers.{$guard['provider']}.model"); 25 | })->get($guard); 26 | } 27 | 28 | /** 29 | * @param Collection $expected 30 | * @param string $given 31 | * 32 | * @return string 33 | */ 34 | public function getGuardDoesNotMatchMessage(Collection $expected, string $given): string 35 | { 36 | $expectedStr = $expected->implode(', '); 37 | return "The given role or permission should use guard `$expectedStr` instead of `$given`."; 38 | } 39 | 40 | /** 41 | * @param string $name 42 | * @param string $guardName 43 | * 44 | * @return string 45 | */ 46 | public function getPermissionAlreadyExistsMessage(string $name, string $guardName): string 47 | { 48 | return "A permission `$name` already exists for guard `$guardName`."; 49 | } 50 | 51 | /** 52 | * @param string $name 53 | * @param string $guardName 54 | * 55 | * @return string 56 | */ 57 | public function getPermissionDoesNotExistMessage(string $name, string $guardName): string 58 | { 59 | return "There is no permission named `$name` for guard `$guardName`."; 60 | } 61 | 62 | /** 63 | * @param string $name 64 | * @param string $guardName 65 | * 66 | * @return string 67 | */ 68 | public function getRoleAlreadyExistsMessage(string $name, string $guardName): string 69 | { 70 | return "A role `$name` already exists for guard `$guardName`."; 71 | } 72 | 73 | /** 74 | * @param string $name 75 | * 76 | * @param string $guardName 77 | * 78 | * @return string 79 | */ 80 | public function getRoleDoesNotExistMessage(string $name, string $guardName): string 81 | { 82 | return "There is no role named `$name` for guard `$guardName`."; 83 | } 84 | 85 | /** 86 | * @param string $roles 87 | * 88 | * @return string 89 | */ 90 | public function getUnauthorizedRoleMessage(string $roles): string 91 | { 92 | $message = "User does not have the right roles `$roles`."; 93 | if (! config('permission.display_permission_in_exception')) { 94 | $message = 'User does not have the right roles.'; 95 | } 96 | 97 | return $message; 98 | } 99 | 100 | /** 101 | * @param string $permissions 102 | * 103 | * @return string 104 | */ 105 | public function getUnauthorizedPermissionMessage(string $permissions): string 106 | { 107 | $message = "User does not have the right permissions `$permissions`."; 108 | if (! config('permission.display_permission_in_exception')) { 109 | $message = 'User does not have the right permissions.'; 110 | } 111 | 112 | return $message; 113 | } 114 | 115 | /** 116 | * @return string 117 | */ 118 | public function getUserNotLoggedINMessage(): string 119 | { 120 | return 'User is not logged in.'; 121 | } 122 | 123 | /** 124 | * @return bool 125 | */ 126 | public function isNotLumen(): bool 127 | { 128 | return ! (stripos(app()->version(), 'lumen') !== false); 129 | } 130 | 131 | /** 132 | * @return bool 133 | */ 134 | public function checkVersion(): bool 135 | { 136 | return ($this->isNotLumen() && app()::VERSION < '5.4'); 137 | } 138 | 139 | /** 140 | * @param array $items 141 | * @return array 142 | */ 143 | public function flattenArray(array $items): array 144 | { 145 | return collect($items)->map(function ($item) { 146 | return is_string($item) ? explode('|', $item): $item; 147 | })->flatten()->all(); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/Middlewares/PermissionMiddleware.php: -------------------------------------------------------------------------------- 1 | guest()) { 30 | $helpers = new Helpers(); 31 | throw new UserNotLoggedIn(403, $helpers->getUserNotLoggedINMessage()); 32 | } 33 | 34 | $permissions = is_array($permission) ? $permission : explode('|', $permission); 35 | 36 | 37 | if (! app('auth')->user()->hasAnyPermission($permissions)) { 38 | $helpers = new Helpers(); 39 | throw new UnauthorizedPermission( 40 | 403, 41 | $helpers->getUnauthorizedPermissionMessage(implode(', ', $permissions)), 42 | $permissions 43 | ); 44 | } 45 | 46 | return $next($request); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Middlewares/RoleMiddleware.php: -------------------------------------------------------------------------------- 1 | guest()) { 30 | $helpers = new Helpers(); 31 | throw new UserNotLoggedIn(403, $helpers->getUserNotLoggedINMessage()); 32 | } 33 | 34 | $roles = is_array($role) ? $role : explode('|', $role); 35 | 36 | if (! app('auth')->user()->hasAnyRole($roles)) { 37 | $helpers = new Helpers(); 38 | throw new UnauthorizedRole(403, $helpers->getUnauthorizedRoleMessage(implode(', ', $roles)), $roles); 39 | } 40 | 41 | return $next($request); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Models/Permission.php: -------------------------------------------------------------------------------- 1 | getDefaultName(static::class); 41 | 42 | parent::__construct($attributes); 43 | 44 | $this->helpers = new Helpers(); 45 | 46 | $this->setTable(config('permission.collection_names.permissions')); 47 | } 48 | 49 | /** 50 | * Create new Permission 51 | * 52 | * @param array $attributes 53 | * 54 | * @return $this|\Illuminate\Database\Eloquent\Model 55 | * @throws PermissionAlreadyExists 56 | * @throws ReflectionException 57 | */ 58 | public static function create(array $attributes = []): \Illuminate\Database\Eloquent\Model|static 59 | { 60 | $helpers = new Helpers(); 61 | $attributes['guard_name'] = $attributes['guard_name'] ?? (new Guard())->getDefaultName(static::class); 62 | 63 | if (static::getPermissions()->where('name', $attributes['name'])->where( 64 | 'guard_name', 65 | $attributes['guard_name'] 66 | )->first()) { 67 | $name = (string)$attributes['name']; 68 | $guardName = (string)$attributes['guard_name']; 69 | throw new PermissionAlreadyExists($helpers->getPermissionAlreadyExistsMessage($name, $guardName)); 70 | } 71 | 72 | return static::query()->create($attributes); 73 | } 74 | 75 | /** 76 | * Find or create permission by its name (and optionally guardName). 77 | * 78 | * @param string $name 79 | * @param string|null $guardName 80 | * 81 | * @return PermissionInterface 82 | * @throws ReflectionException 83 | */ 84 | public static function findOrCreate(string $name, string $guardName = null): PermissionInterface 85 | { 86 | $guardName = $guardName ?? (new Guard())->getDefaultName(static::class); 87 | 88 | $permission = static::getPermissions()->filter(function ($permission) use ($name, $guardName) { 89 | return $permission->name === $name && $permission->guard_name === $guardName; 90 | })->first(); 91 | 92 | if (!$permission) { 93 | $permission = static::create(['name' => $name, 'guard_name' => $guardName]); 94 | } 95 | 96 | return $permission; 97 | } 98 | 99 | /** 100 | * A permission can be applied to roles. 101 | * @return mixed 102 | */ 103 | public function rolesQuery(): mixed 104 | { 105 | $roleClass = $this->getRoleClass(); 106 | return $roleClass->query()->where('permission_ids', 'all', [$this->_id]); 107 | } 108 | 109 | /** 110 | * A permission can be applied to roles. 111 | * @return mixed 112 | */ 113 | public function getRolesAttribute(): mixed 114 | { 115 | return $this->rolesQuery()->get(); 116 | } 117 | 118 | /** 119 | * A permission belongs to some users of the model associated with its guard. 120 | * @return mixed 121 | */ 122 | public function usersQuery(): mixed 123 | { 124 | $usersClass = app($this->helpers->getModelForGuard($this->attributes['guard_name'])); 125 | return $usersClass->query()->where('permission_ids', 'all', [$this->_id]); 126 | } 127 | 128 | /** 129 | * A permission belongs to some users of the model associated with its guard. 130 | * @return mixed 131 | */ 132 | public function getUsersAttribute(): mixed 133 | { 134 | return $this->usersQuery()->get(); 135 | } 136 | 137 | /** 138 | * Find a permission by its name (and optionally guardName). 139 | * 140 | * @param string $name 141 | * @param string|null $guardName 142 | * 143 | * @return PermissionInterface 144 | * @throws ReflectionException 145 | */ 146 | public static function findByName(string $name, string $guardName = null): PermissionInterface 147 | { 148 | $guardName = $guardName ?? (new Guard())->getDefaultName(static::class); 149 | 150 | $permission = static::getPermissions()->filter(function ($permission) use ($name, $guardName) { 151 | return $permission->name === $name && $permission->guard_name === $guardName; 152 | })->first(); 153 | 154 | if (!$permission) { 155 | $helpers = new Helpers(); 156 | throw new PermissionDoesNotExist($helpers->getPermissionDoesNotExistMessage($name, $guardName)); 157 | } 158 | 159 | return $permission; 160 | } 161 | 162 | /** 163 | * Get the current cached permissions. 164 | * @return Collection 165 | */ 166 | protected static function getPermissions(): Collection 167 | { 168 | return app(PermissionRegistrar::class)->getPermissions(); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/Models/Role.php: -------------------------------------------------------------------------------- 1 | getDefaultName(static::class); 42 | 43 | parent::__construct($attributes); 44 | 45 | $this->helpers = new Helpers(); 46 | 47 | $this->setTable(config('permission.collection_names.roles')); 48 | } 49 | 50 | /** 51 | * @param array $attributes 52 | * 53 | * @return Builder|\Illuminate\Database\Eloquent\Model|RoleInterface 54 | * 55 | * @throws RoleAlreadyExists 56 | * @throws ReflectionException 57 | */ 58 | public static function create(array $attributes = []): \Illuminate\Database\Eloquent\Model|RoleInterface|Builder 59 | { 60 | $attributes['guard_name'] = $attributes['guard_name'] ?? (new Guard())->getDefaultName(static::class); 61 | $helpers = new Helpers(); 62 | 63 | if (static::query()->where('name', $attributes['name'])->where('guard_name', $attributes['guard_name'])->first()) { 64 | $name = (string)$attributes['name']; 65 | $guardName = (string)$attributes['guard_name']; 66 | throw new RoleAlreadyExists($helpers->getRoleAlreadyExistsMessage($name, $guardName)); 67 | } 68 | 69 | return static::query()->create($attributes); 70 | } 71 | 72 | /** 73 | * Find or create role by its name (and optionally guardName). 74 | * 75 | * @param string $name 76 | * @param string|null $guardName 77 | * 78 | * @return RoleInterface 79 | * @throws RoleAlreadyExists 80 | * @throws ReflectionException 81 | */ 82 | public static function findOrCreate(string $name, string $guardName = null): Role 83 | { 84 | $guardName = $guardName ?? (new Guard())->getDefaultName(static::class); 85 | 86 | $role = static::query() 87 | ->where('name', $name) 88 | ->where('guard_name', $guardName) 89 | ->first(); 90 | 91 | if (!$role) { 92 | $role = static::create(['name' => $name, 'guard_name' => $guardName]); 93 | } 94 | 95 | return $role; 96 | } 97 | 98 | /** 99 | * Find a role by its name and guard name. 100 | * 101 | * @param string $name 102 | * @param string|null $guardName 103 | * 104 | * @return RoleInterface 105 | * @throws RoleDoesNotExist 106 | * @throws ReflectionException 107 | */ 108 | public static function findByName(string $name, string $guardName = null): RoleInterface 109 | { 110 | $guardName = $guardName ?? (new Guard())->getDefaultName(static::class); 111 | 112 | $role = static::query() 113 | ->where('name', $name) 114 | ->where('guard_name', $guardName) 115 | ->first(); 116 | 117 | if (!$role) { 118 | $helpers = new Helpers(); 119 | throw new RoleDoesNotExist($helpers->getRoleDoesNotExistMessage($name, $guardName)); 120 | } 121 | 122 | return $role; 123 | } 124 | 125 | /** 126 | * A permission belongs to some users of the model associated with its guard. 127 | * @return mixed 128 | */ 129 | public function usersQuery(): mixed 130 | { 131 | $usersClass = app($this->helpers->getModelForGuard($this->attributes['guard_name'])); 132 | return $usersClass->query()->where('role_ids', 'all', [$this->_id]); 133 | } 134 | 135 | /** 136 | * A permission belongs to some users of the model associated with its guard. 137 | * @return mixed 138 | */ 139 | public function getUsersAttribute(): mixed 140 | { 141 | return $this->usersQuery()->get(); 142 | } 143 | 144 | /** 145 | * Determine if the user may perform the given permission. 146 | * 147 | * @param string|Permission $permission 148 | * 149 | * @return bool 150 | * 151 | * @throws GuardDoesNotMatch 152 | * @throws ReflectionException 153 | */ 154 | public function hasPermissionTo(string|PermissionInterface $permission): bool 155 | { 156 | if (is_string($permission)) { 157 | $permission = $this->getPermissionClass()->findByName($permission, $this->getDefaultGuardName()); 158 | } 159 | 160 | if (!$this->getGuardNames()->contains($permission->guard_name)) { 161 | $expected = $this->getGuardNames(); 162 | $given = $permission->guard_name; 163 | 164 | throw new GuardDoesNotMatch($this->helpers->getGuardDoesNotMatchMessage($expected, $given)); 165 | } 166 | 167 | return in_array($permission->_id, $this->permission_ids ?? [], true); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/PermissionRegistrar.php: -------------------------------------------------------------------------------- 1 | gate = $gate; 36 | $this->cache = $cache; 37 | $this->permissionClass = config('permission.models.permission'); 38 | $this->roleClass = config('permission.models.role'); 39 | } 40 | 41 | /** 42 | * Register Permissions 43 | * 44 | * @return bool 45 | */ 46 | public function registerPermissions(): bool 47 | { 48 | $this->getPermissions()->map(function (Permission $permission) { 49 | $this->gate->define($permission->name, function (Authorizable $user) use ($permission) { 50 | return $user->hasPermissionTo($permission) ?: null; 51 | }); 52 | }); 53 | 54 | return true; 55 | } 56 | 57 | /** 58 | * Forget cached permission 59 | */ 60 | public function forgetCachedPermissions(): void 61 | { 62 | $this->cache->forget($this->cacheKey); 63 | } 64 | 65 | /** 66 | * Get Permissions 67 | * 68 | * @return Collection 69 | */ 70 | public function getPermissions(): Collection 71 | { 72 | return $this->cache->remember($this->cacheKey, config('permission.cache_expiration_time'), function () { 73 | return $this->getPermissionClass()->get(); 74 | }); 75 | } 76 | 77 | /** 78 | * Get Permission class 79 | * 80 | * @return Application|mixed 81 | */ 82 | public function getPermissionClass(): mixed 83 | { 84 | return app($this->permissionClass); 85 | } 86 | 87 | /** 88 | * Get Role class 89 | * 90 | * @return Application|mixed 91 | */ 92 | public function getRoleClass(): mixed 93 | { 94 | return app($this->roleClass); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/PermissionServiceProvider.php: -------------------------------------------------------------------------------- 1 | isNotLumen()) { 22 | $this->publishes([ 23 | __DIR__ . '/../config/permission.php' => $this->app->configPath() . '/permission.php', 24 | ], 'config'); 25 | 26 | if (!class_exists('CreatePermissionTables')) { 27 | $timestamp = date('Y_m_d_His'); 28 | $mFilePath = $this->app->databasePath() . "/migrations/{$timestamp}_create_permission_collections.php"; 29 | $this->publishes([ 30 | __DIR__ . '/../database/migrations/create_permission_collections.php.stub' => $mFilePath, 31 | ], 'migrations'); 32 | } 33 | } 34 | 35 | if ($this->app->runningInConsole()) { 36 | $this->commands([ 37 | Commands\CreateRole::class, 38 | Commands\CreatePermission::class, 39 | ]); 40 | } 41 | 42 | $this->registerModelBindings(); 43 | 44 | DB::connection()->getPdo(); 45 | app(PermissionRegistrar::class)->registerPermissions(); 46 | } 47 | 48 | public function register() 49 | { 50 | $helpers = new Helpers(); 51 | if ($helpers->isNotLumen()) { 52 | $this->mergeConfigFrom( 53 | __DIR__ . '/../config/permission.php', 54 | 'permission' 55 | ); 56 | } 57 | 58 | $this->registerBladeExtensions(); 59 | } 60 | 61 | protected function registerModelBindings() 62 | { 63 | $config = $this->app->config['permission.models']; 64 | 65 | $this->app->bind(Permission::class, $config['permission']); 66 | $this->app->bind(Role::class, $config['role']); 67 | } 68 | 69 | protected function registerBladeExtensions() 70 | { 71 | $this->app->afterResolving('blade.compiler', function (BladeCompiler $bladeCompiler) { 72 | $permissionDirectives = new PermissionDirectives($bladeCompiler); 73 | 74 | $permissionDirectives->roleDirective(); 75 | $permissionDirectives->hasroleDirective(); 76 | $permissionDirectives->hasanyroleDirective(); 77 | $permissionDirectives->hasallrolesDirective(); 78 | }); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Traits/HasPermissions.php: -------------------------------------------------------------------------------- 1 | permissionClass === null) { 30 | $this->permissionClass = app(PermissionRegistrar::class)->getPermissionClass(); 31 | } 32 | return $this->permissionClass; 33 | } 34 | 35 | /** 36 | * Query the permissions 37 | */ 38 | public function permissionsQuery() 39 | { 40 | $permission = $this->getPermissionClass(); 41 | return $permission::whereIn('_id', $this->permission_ids ?? []); 42 | } 43 | 44 | /** 45 | * gets the permissions Attribute 46 | */ 47 | public function getPermissionsAttribute() 48 | { 49 | return $this->permissionsQuery()->get(); 50 | } 51 | 52 | /** 53 | * Grant the given permission(s) to a role. 54 | * 55 | * @param string|array|Permission|Collection $permissions 56 | * 57 | * @return $this 58 | * @throws GuardDoesNotMatch 59 | */ 60 | public function givePermissionTo(...$permissions): self 61 | { 62 | $this->permission_ids = collect($this->permission_ids ?? []) 63 | ->merge($this->getPermissionIds($permissions)) 64 | ->all(); 65 | 66 | $this->save(); 67 | 68 | $this->forgetCachedPermissions(); 69 | 70 | return $this; 71 | } 72 | 73 | /** 74 | * Remove all current permissions and set the given ones. 75 | * 76 | * @param string|array|Permission|Collection $permissions 77 | * 78 | * @return $this 79 | * @throws GuardDoesNotMatch 80 | */ 81 | public function syncPermissions(...$permissions): self 82 | { 83 | $this->permission_ids = $this->getPermissionIds($permissions); 84 | 85 | $this->save(); 86 | return $this->givePermissionTo($permissions); 87 | } 88 | 89 | /** 90 | * Revoke the given permission. 91 | * 92 | * @param string|array|Permission|Collection $permissions 93 | * 94 | * @return $this 95 | * @throws GuardDoesNotMatch 96 | */ 97 | public function revokePermissionTo(...$permissions): self 98 | { 99 | $permissions = $this->getPermissionIds($permissions); 100 | 101 | $this->permission_ids = collect($this->permission_ids ?? []) 102 | ->filter(function ($permission) use ($permissions) { 103 | return ! in_array($permission, $permissions, true); 104 | }) 105 | ->all(); 106 | 107 | $this->save(); 108 | 109 | $this->forgetCachedPermissions(); 110 | 111 | return $this; 112 | } 113 | 114 | /** 115 | * @param string|Permission $permission 116 | * 117 | * @return Permission 118 | * @throws ReflectionException 119 | */ 120 | protected function getStoredPermission($permission): Permission 121 | { 122 | if (is_string($permission)) { 123 | return $this->getPermissionClass()->findByName($permission, $this->getDefaultGuardName()); 124 | } 125 | 126 | return $permission; 127 | } 128 | 129 | /** 130 | * @param Model $roleOrPermission 131 | * 132 | * @throws GuardDoesNotMatch 133 | * @throws ReflectionException 134 | */ 135 | protected function ensureModelSharesGuard(Model $roleOrPermission): void 136 | { 137 | if (! $this->getGuardNames()->contains($roleOrPermission->guard_name)) { 138 | $expected = $this->getGuardNames(); 139 | $given = $roleOrPermission->guard_name; 140 | $helpers = new Helpers(); 141 | 142 | throw new GuardDoesNotMatch($helpers->getGuardDoesNotMatchMessage($expected, $given)); 143 | } 144 | } 145 | 146 | /** 147 | * @return Collection 148 | * @throws ReflectionException 149 | */ 150 | protected function getGuardNames(): Collection 151 | { 152 | return (new Guard())->getNames($this); 153 | } 154 | 155 | /** 156 | * @return string 157 | * @throws ReflectionException 158 | */ 159 | protected function getDefaultGuardName(): string 160 | { 161 | return (new Guard())->getDefaultName($this); 162 | } 163 | 164 | /** 165 | * Forget the cached permissions. 166 | */ 167 | public function forgetCachedPermissions(): void 168 | { 169 | app(PermissionRegistrar::class)->forgetCachedPermissions(); 170 | } 171 | 172 | /** 173 | * Convert to Permission Models 174 | * 175 | * @param array|string|Collection $permissions 176 | * 177 | * @return Collection 178 | */ 179 | private function convertToPermissionModels($permissions): Collection 180 | { 181 | if (is_array($permissions)) { 182 | $permissions = collect($permissions); 183 | } 184 | 185 | if (! $permissions instanceof Collection) { 186 | $permissions = collect([$permissions]); 187 | } 188 | 189 | return $permissions->map(function ($permission) { 190 | return $this->getStoredPermission($permission); 191 | }); 192 | } 193 | 194 | /** 195 | * Return a collection of permission names associated with this user. 196 | * 197 | * @return Collection 198 | */ 199 | public function getPermissionNames(): Collection 200 | { 201 | return $this->getAllPermissions()->pluck('name'); 202 | } 203 | 204 | /** 205 | * Return all the permissions the model has via roles. 206 | */ 207 | public function getPermissionsViaRoles(): Collection 208 | { 209 | $permissionIds = $this->roles->pluck('permission_ids')->flatten()->unique()->values(); 210 | return $this->getPermissionClass()->query()->whereIn('_id', $permissionIds)->get(); 211 | /*return $this->load('roles', 'roles.permissions') 212 | ->roles->flatMap(function (Role $role) { 213 | return $role->permissions; 214 | })->sort()->values();*/ 215 | } 216 | 217 | /** 218 | * Return all the permissions the model has, both directly and via roles. 219 | */ 220 | public function getAllPermissions(): Collection 221 | { 222 | return $this->permissions 223 | ->merge($this->getPermissionsViaRoles()) 224 | ->sort() 225 | ->values(); 226 | } 227 | 228 | /** 229 | * Determine if the model may perform the given permission. 230 | * 231 | * @param string|PermissionInterface $permission 232 | * @param string|null $guardName 233 | * @return bool 234 | * @throws ReflectionException 235 | */ 236 | public function hasPermissionTo($permission, string $guardName = null): bool 237 | { 238 | if (is_string($permission)) { 239 | $permission = $this->getPermissionClass()->findByName( 240 | $permission, 241 | $guardName ?? $this->getDefaultGuardName() 242 | ); 243 | } 244 | 245 | return $this->hasDirectPermission($permission) || $this->hasPermissionViaRole($permission); 246 | } 247 | 248 | /** 249 | * Determine if the model has any of the given permissions. 250 | * 251 | * @param array ...$permissions 252 | * 253 | * @return bool 254 | * @throws ReflectionException 255 | */ 256 | public function hasAnyPermission(...$permissions): bool 257 | { 258 | if (is_array($permissions[0])) { 259 | $permissions = $permissions[0]; 260 | } 261 | 262 | foreach ($permissions as $permission) { 263 | if ($this->hasPermissionTo($permission)) { 264 | return true; 265 | } 266 | } 267 | 268 | return false; 269 | } 270 | 271 | /** 272 | * Determine if the model has all the given permissions(s). 273 | * 274 | * @param $permissions 275 | * 276 | * @return bool 277 | * @throws ReflectionException 278 | */ 279 | public function hasAllPermissions(...$permissions): bool 280 | { 281 | $helpers = new Helpers(); 282 | $permissions = $helpers->flattenArray($permissions); 283 | 284 | foreach ($permissions as $permission) { 285 | if (!$this->hasPermissionTo($permission)) { 286 | return false; 287 | } 288 | } 289 | return true; 290 | } 291 | 292 | /** 293 | * Determine if the model has, via roles, the given permission. 294 | * 295 | * @param Permission $permission 296 | * 297 | * @return bool 298 | */ 299 | protected function hasPermissionViaRole(Permission $permission): bool 300 | { 301 | return $this->hasRole($permission->roles); 302 | } 303 | 304 | /** 305 | * Determine if the model has the given permission. 306 | * 307 | * @param string|Permission $permission 308 | * 309 | * @return bool 310 | * @throws ReflectionException 311 | */ 312 | public function hasDirectPermission($permission): bool 313 | { 314 | if (is_string($permission)) { 315 | $permission = $this->getPermissionClass()->findByName($permission, $this->getDefaultGuardName()); 316 | } 317 | 318 | return $this->permissions->contains('_id', $permission->_id); 319 | } 320 | 321 | /** 322 | * Return all permissions the directory coupled to the model. 323 | */ 324 | public function getDirectPermissions(): Collection 325 | { 326 | return $this->permissions; 327 | } 328 | 329 | /** 330 | * Scope the model query to certain permissions only. 331 | * 332 | * @param Builder $query 333 | * @param array|string|Permission|Collection $permissions 334 | * 335 | * @return Builder 336 | */ 337 | public function scopePermission(Builder $query, $permissions): Builder 338 | { 339 | $permissions = $this->convertToPermissionModels($permissions); 340 | 341 | $roles = collect([]); 342 | 343 | foreach ($permissions as $permission) { 344 | $roles = $roles->merge($permission->roles); 345 | } 346 | $roles = $roles->unique(); 347 | 348 | return $query->orWhereIn('permission_ids', $permissions->pluck('_id')) 349 | ->orWhereIn('role_ids', $roles->pluck('_id')); 350 | } 351 | 352 | /** 353 | * @param string|array|Permission|Collection $permissions 354 | * @return array 355 | */ 356 | protected function getPermissionIds(...$permissions): array 357 | { 358 | return collect($permissions) 359 | ->flatten() 360 | ->map(function ($permission) { 361 | $permission = $this->getStoredPermission($permission); 362 | $this->ensureModelSharesGuard($permission); 363 | return $permission->_id; 364 | }) 365 | ->all(); 366 | } 367 | } 368 | -------------------------------------------------------------------------------- /src/Traits/HasRoles.php: -------------------------------------------------------------------------------- 1 | forceDeleting) && !$model->forceDeleting) { 29 | return; 30 | } 31 | 32 | $model->roles()->sync([]); 33 | }); 34 | } 35 | 36 | public function getRoleClass() 37 | { 38 | if ($this->roleClass === null) { 39 | $this->roleClass = app(PermissionRegistrar::class)->getRoleClass(); 40 | } 41 | return $this->roleClass; 42 | } 43 | 44 | /** 45 | * A model may have multiple roles. 46 | */ 47 | public function roles(): BelongsToMany|\Illuminate\Database\Eloquent\Relations\BelongsToMany 48 | { 49 | return $this->belongsToMany(config('permission.models.role')); 50 | } 51 | 52 | /** 53 | * Scope the model query to certain roles only. 54 | * 55 | * @param Builder $query 56 | * @param string|array|Role|Collection $roles 57 | * 58 | * @return Builder 59 | */ 60 | public function scopeRole(Builder $query, $roles): Builder 61 | { 62 | $roles = $this->convertToRoleModels($roles); 63 | 64 | return $query->whereIn('role_ids', $roles->pluck('_id')); 65 | } 66 | 67 | /** 68 | * Assign the given role to the model. 69 | * 70 | * @param array|string|Role ...$roles 71 | * 72 | * @return array|Role|string 73 | * @throws ReflectionException 74 | */ 75 | public function assignRole(...$roles) 76 | { 77 | $roles = collect($roles) 78 | ->flatten() 79 | ->map(function ($role) { 80 | return $this->getStoredRole($role); 81 | }) 82 | ->each(function ($role) { 83 | $this->ensureModelSharesGuard($role); 84 | }) 85 | ->all(); 86 | 87 | $this->roles()->saveMany($roles); 88 | 89 | $this->forgetCachedPermissions(); 90 | 91 | return $roles; 92 | } 93 | 94 | /** 95 | * Revoke the given role from the model. 96 | * 97 | * @param array|string|Role ...$roles 98 | * 99 | * @return array|Role|string 100 | */ 101 | public function removeRole(...$roles) 102 | { 103 | collect($roles) 104 | ->flatten() 105 | ->map(function ($role) { 106 | $role = $this->getStoredRole($role); 107 | $this->roles()->detach($role); 108 | 109 | return $role; 110 | }); 111 | 112 | $this->forgetCachedPermissions(); 113 | 114 | return $roles; 115 | } 116 | 117 | /** 118 | * Remove all current roles and set the given ones. 119 | * 120 | * @param array ...$roles 121 | * 122 | * @return array|Role|string 123 | * @throws ReflectionException 124 | */ 125 | public function syncRoles(...$roles): Role|array|string 126 | { 127 | $this->roles()->sync([]); 128 | 129 | return $this->assignRole($roles); 130 | } 131 | 132 | /** 133 | * Determine if the model has (one of) the given role(s). 134 | * 135 | * @param string|array|Role|Collection $roles 136 | * 137 | * @return bool 138 | */ 139 | public function hasRole($roles): bool 140 | { 141 | if (\is_string($roles) && str_contains($roles, '|')) { 142 | $roles = \explode('|', $roles); 143 | } 144 | 145 | if (\is_string($roles) || $roles instanceof Role) { 146 | return $this->roles->contains('name', $roles->name ?? $roles); 147 | } 148 | 149 | $roles = collect()->make($roles)->map(function ($role) { 150 | return $role instanceof Role ? $role->name : $role; 151 | }); 152 | 153 | return !$roles->intersect($this->roles->pluck('name'))->isEmpty(); 154 | } 155 | 156 | /** 157 | * Determine if the model has any of the given role(s). 158 | * 159 | * @param string|array|Role|Collection $roles 160 | * 161 | * @return bool 162 | */ 163 | public function hasAnyRole($roles): bool 164 | { 165 | return $this->hasRole($roles); 166 | } 167 | 168 | /** 169 | * Determine if the model has all the given role(s). 170 | * 171 | * @param $roles 172 | * 173 | * @return bool 174 | */ 175 | public function hasAllRoles(...$roles): bool 176 | { 177 | $helpers = new Helpers(); 178 | $roles = $helpers->flattenArray($roles); 179 | 180 | foreach ($roles as $role) { 181 | if (!$this->hasRole($role)) { 182 | return false; 183 | } 184 | } 185 | return true; 186 | } 187 | 188 | /** 189 | * Return Role object 190 | * 191 | * @param String|Role $role role name 192 | * 193 | * @return Role 194 | * @throws ReflectionException 195 | */ 196 | protected function getStoredRole($role): Role 197 | { 198 | if (\is_string($role)) { 199 | return $this->getRoleClass()->findByName($role, $this->getDefaultGuardName()); 200 | } 201 | 202 | return $role; 203 | } 204 | 205 | /** 206 | * Return a collection of role names associated with this user. 207 | * 208 | * @return Collection 209 | */ 210 | public function getRoleNames(): Collection 211 | { 212 | return $this->roles()->pluck('name'); 213 | } 214 | 215 | /** 216 | * Convert to Role Models 217 | * 218 | * @param $roles 219 | * 220 | * @return Collection 221 | */ 222 | private function convertToRoleModels($roles): Collection 223 | { 224 | if (is_array($roles)) { 225 | $roles = collect($roles); 226 | } 227 | 228 | if (!$roles instanceof Collection) { 229 | $roles = collect([$roles]); 230 | } 231 | 232 | return $roles->map(function ($role) { 233 | return $this->getStoredRole($role); 234 | }); 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /src/Traits/RefreshesPermissionCache.php: -------------------------------------------------------------------------------- 1 | forgetCachedPermissions(); 23 | }); 24 | 25 | static::deleted(function () { 26 | app(config('permission.models.permission'))->forgetCachedPermissions(); 27 | }); 28 | } 29 | } 30 | --------------------------------------------------------------------------------