├── .gitignore ├── LICENSE.txt ├── README.md ├── composer.json ├── resources ├── lang │ └── en │ │ ├── navigation.php │ │ ├── permissions.php │ │ ├── resources.php │ │ └── roles.php └── views │ └── navigation.blade.php └── src ├── ForgetCachedPermissions.php ├── LaravelNovaPermission.php ├── NovaPermissionServiceProvider.php ├── Permission.php ├── PermissionResourceTrait.php ├── PermissionsBasedAuthTrait.php ├── Role.php └── RoleResourceTrait.php /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /vendor 3 | /node_modules 4 | package-lock.json 5 | composer.phar 6 | composer.lock 7 | phpunit.xml 8 | .phpunit.result.cache 9 | .DS_Store 10 | Thumbs.db 11 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Insense Private Limited 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A Laravel Nova tool for the Spatie Permission package 2 | 3 | [![License](https://poser.pugx.org/insenseanalytics/laravel-nova-permission/license)](https://packagist.org/packages/insenseanalytics/laravel-nova-permission) 4 | [![Latest Stable Version](https://poser.pugx.org/insenseanalytics/laravel-nova-permission/v/stable)](https://packagist.org/packages/insenseanalytics/laravel-nova-permission) 5 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/insenseanalytics/laravel-nova-permission/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/insenseanalytics/laravel-nova-permission/?branch=master) 6 | [![Total Downloads](https://poser.pugx.org/insenseanalytics/laravel-nova-permission/downloads)](https://packagist.org/packages/insenseanalytics/laravel-nova-permission) 7 | 8 | This [Nova](https://nova.laravel.com) tool lets you: 9 | - manage roles and permissions on the Nova dashboard 10 | - use permissions based authorization for Nova resources 11 | 12 | ## Screenshots 13 | screenshot of the backup tool 14 | 15 | ## Requirements & Dependencies 16 | There are no PHP dependencies except the [Laravel Nova](https://nova.laravel.com) package and the [Spatie Permission](https://github.com/spatie/laravel-permission) package. 17 | 18 | ## Installation 19 | You can install this tool into a Laravel app that uses [Nova](https://nova.laravel.com) via composer: 20 | 21 | ```bash 22 | composer require insenseanalytics/laravel-nova-permission 23 | ``` 24 | 25 | Next, if you do not have package discovery enabled, you need to register the provider in the `config/app.php` file. 26 | ```php 27 | 'providers' => [ 28 | ..., 29 | Insenseanalytics\LaravelNovaPermission\NovaPermissionServiceProvider::class, 30 | ] 31 | ``` 32 | 33 | Next, you must register the tool with Nova. This is typically done in the `tools` method of the `NovaServiceProvider`. 34 | 35 | ```php 36 | // in app/Providers/NovaServiceProvider.php 37 | 38 | public function tools() 39 | { 40 | return [ 41 | // ... 42 | \Insenseanalytics\LaravelNovaPermission\LaravelNovaPermission::make(), 43 | ]; 44 | } 45 | ``` 46 | 47 | Next, add `MorphToMany` fields to your `app/Nova/User` resource: 48 | 49 | ```php 50 | use Laravel\Nova\Fields\MorphToMany; 51 | 52 | public function fields(Request $request) 53 | { 54 | return [ 55 | // ... 56 | MorphToMany::make('Roles', 'roles', \Insenseanalytics\LaravelNovaPermission\Role::class), 57 | MorphToMany::make('Permissions', 'permissions', \Insenseanalytics\LaravelNovaPermission\Permission::class), 58 | ]; 59 | } 60 | ``` 61 | 62 | Finally, add the `ForgetCachedPermissions` class to your `config/nova.php` middleware like so: 63 | 64 | ```php 65 | // in config/nova.php 66 | 'middleware' => [ 67 | 'web', 68 | Authenticate::class, 69 | DispatchServingNovaEvent::class, 70 | BootTools::class, 71 | Authorize::class, 72 | \Insenseanalytics\LaravelNovaPermission\ForgetCachedPermissions::class, 73 | ], 74 | ``` 75 | 76 | ## Localization 77 | 78 | You can use the artisan command line tool to publish localization files: 79 | 80 | ```php 81 | php artisan vendor:publish --provider="Insenseanalytics\LaravelNovaPermission\NovaPermissionServiceProvider" 82 | ``` 83 | 84 | ## Using Custom Role/Permission Resource Classes 85 | 86 | If you want to use custom resource classes you can define them when you register a tool: 87 | 88 | ```php 89 | // in app/Providers/NovaServiceProvider.php 90 | 91 | public function tools() 92 | { 93 | return [ 94 | // ... 95 | \Insenseanalytics\LaravelNovaPermission\LaravelNovaPermission::make() 96 | ->roleResource(CustomRole::class) 97 | ->permissionResource(CustomPermission::class), 98 | ]; 99 | } 100 | ``` 101 | 102 | ## Permissions Based Authorization for Nova Resources 103 | By default, Laravel Nova uses Policy based authorization for Nova resources. If you are using the Spatie Permission library, it is very likely that you would want to swap this out to permission based authorization without the need to define Authorization policies. 104 | 105 | To do so, you can use the `PermissionsBasedAuthTrait` and define a `permissionsForAbilities` static array property in your Nova resource class like so: 106 | 107 | ```php 108 | // in app/Nova/YourNovaResource.php 109 | 110 | class YourNovaResource extends Resource 111 | { 112 | use \Insenseanalytics\LaravelNovaPermission\PermissionsBasedAuthTrait; 113 | 114 | public static $permissionsForAbilities = [ 115 | 'all' => 'manage products', 116 | ]; 117 | } 118 | ``` 119 | 120 | The example above means that all actions on this resource can be performed by users who have the "manage products" permission. You can also define separate permissions for each action like so: 121 | 122 | ```php 123 | public static $permissionsForAbilities = [ 124 | 'viewAny' => 'view products', 125 | 'view' => 'view products', 126 | 'create' => 'create products', 127 | 'update' => 'update products', 128 | 'delete' => 'delete products', 129 | 'restore' => 'restore products', 130 | 'forceDelete' => 'forceDelete products', 131 | 'addAttribute' => 'add product attributes', 132 | 'attachAttribute' => 'attach product attributes', 133 | 'detachAttribute' => 'detach product attributes', 134 | ]; 135 | ``` 136 | 137 | ### Relationships 138 | To allow your users to specify a relationship on your model, you will need to add another permission on the Model. 139 | For example, if your `Product` belongs to `User`, add the following permission on `app/Nova/User.php`. : 140 | 141 | ```php 142 | public static $permissionsForAbilities = [ 143 | 'addProduct' => 'add user on products' 144 | ]; 145 | ``` 146 | 147 | ## Contributing 148 | 149 | Contributions are welcome and will be fully credited as long as you use PSR-2, explain the issue/feature that you want to solve/add and back your code up with tests. Happy coding! 150 | 151 | ## License 152 | 153 | The MIT License (MIT). Please see [License File](LICENSE.txt) for more information. 154 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "insenseanalytics/laravel-nova-permission", 3 | "description": "A Laravel Nova tool for Spatie's Permission library.", 4 | "keywords": [ 5 | "laravel", 6 | "nova", 7 | "spatie", 8 | "laravel-permission" 9 | ], 10 | "license": "MIT", 11 | "require": { 12 | "php": ">=7.1.0", 13 | "spatie/laravel-permission": "^2.16|^3.0" 14 | }, 15 | "autoload": { 16 | "psr-4": { 17 | "Insenseanalytics\\LaravelNovaPermission\\": "src/" 18 | } 19 | }, 20 | "extra": { 21 | "laravel": { 22 | "providers": [ 23 | "Insenseanalytics\\LaravelNovaPermission\\NovaPermissionServiceProvider" 24 | ] 25 | } 26 | }, 27 | "config": { 28 | "sort-packages": true 29 | }, 30 | "minimum-stability": "dev", 31 | "prefer-stable": true 32 | } -------------------------------------------------------------------------------- /resources/lang/en/navigation.php: -------------------------------------------------------------------------------- 1 | 'Roles & Permissions', 5 | ]; -------------------------------------------------------------------------------- /resources/lang/en/permissions.php: -------------------------------------------------------------------------------- 1 | 'Name', 5 | 'display_name' => 'Display Name', 6 | 'guard_name' => 'Guard Name', 7 | 'created_at' => 'Created at', 8 | 'updated_at' => 'Updated at', 9 | ]; -------------------------------------------------------------------------------- /resources/lang/en/resources.php: -------------------------------------------------------------------------------- 1 | 'Roles', 5 | 'Role' => 'Role', 6 | 'Permissions' => 'Permissions', 7 | 'Permission' => 'Permission', 8 | ]; -------------------------------------------------------------------------------- /resources/lang/en/roles.php: -------------------------------------------------------------------------------- 1 | 'Name', 5 | 'guard_name' => 'Guard Name', 6 | 'created_at' => 'Created at', 7 | 'updated_at' => 'Updated at', 8 | ]; -------------------------------------------------------------------------------- /resources/views/navigation.blade.php: -------------------------------------------------------------------------------- 1 | 5 | 6 | @if ((Nova::resourceForModel(app(PermissionRegistrar::class)->getRoleClass()))::authorizedToViewAny(request()) || (Nova::resourceForModel(app(PermissionRegistrar::class)->getPermissionClass()))::authorizedToViewAny(request())) 7 |

8 | 9 | 11 | 12 | 13 | @lang('laravel-nova-permission::navigation.sidebar-label') 14 | 15 |

16 | 17 | 46 | @endif -------------------------------------------------------------------------------- /src/ForgetCachedPermissions.php: -------------------------------------------------------------------------------- 1 | is('nova-api/*/detach') || 23 | $request->is('nova-api/*/*/attach/*')) { 24 | $permissionKey = (Nova::resourceForModel(app(PermissionRegistrar::class)->getPermissionClass()))::uriKey(); 25 | 26 | if ($request->viaRelationship === $permissionKey) { 27 | app(PermissionRegistrar::class)->forgetCachedPermissions(); 28 | } 29 | } 30 | 31 | return $response; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/LaravelNovaPermission.php: -------------------------------------------------------------------------------- 1 | roleResource && Permission::class === $this->permissionResource) 21 | || $this->registerCustomResources) { 22 | Nova::resources([ 23 | $this->roleResource, 24 | $this->permissionResource, 25 | ]); 26 | } 27 | } 28 | 29 | /** 30 | * Set a custom Role resource class. 31 | * 32 | * @param Role resource class 33 | * 34 | * @return $this 35 | */ 36 | public function roleResource(string $roleResource) 37 | { 38 | $this->roleResource = $roleResource; 39 | 40 | return $this; 41 | } 42 | 43 | /** 44 | * Set a custom Permission resource class. 45 | * 46 | * @param Permission resource class 47 | * 48 | * @return $this 49 | */ 50 | public function permissionResource(string $permissionResource) 51 | { 52 | $this->permissionResource = $permissionResource; 53 | 54 | return $this; 55 | } 56 | 57 | /** 58 | * Register the custom resource classes. 59 | * 60 | * @param bool 61 | * 62 | * @return $this 63 | */ 64 | public function withRegistration() 65 | { 66 | $this->registerCustomResources = true; 67 | 68 | return $this; 69 | } 70 | 71 | /** 72 | * Build the view that renders the navigation links for the tool. 73 | * 74 | * @return \Illuminate\View\View 75 | */ 76 | public function renderNavigation() 77 | { 78 | return view('laravel-nova-permission::navigation'); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/NovaPermissionServiceProvider.php: -------------------------------------------------------------------------------- 1 | loadViewsFrom(__DIR__ . '/../resources/views', 'laravel-nova-permission'); 15 | $this->loadTranslationsFrom(__DIR__.'/../resources/lang', 'laravel-nova-permission'); 16 | 17 | $this->publishes([ 18 | __DIR__.'/../resources/lang' => resource_path('lang/vendor/laravel-nova-permission'), 19 | ], 'laravel-nova-permission-lang'); 20 | } 21 | 22 | /** 23 | * Register any application services. 24 | */ 25 | public function register() 26 | { 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Permission.php: -------------------------------------------------------------------------------- 1 | getPermissionClass(); 22 | } 23 | 24 | /** 25 | * Get the fields displayed by the resource. 26 | * 27 | * @param \Illuminate\Http\Request $request 28 | * 29 | * @return array 30 | */ 31 | public function fields(Request $request) 32 | { 33 | $guardOptions = collect(config('auth.guards'))->mapWithKeys(function ($value, $key) { 34 | return [$key => $key]; 35 | }); 36 | 37 | $userResource = Nova::resourceForModel(getModelForGuard($this->guard_name)); 38 | 39 | $roleResource = Nova::resourceForModel(app(PermissionRegistrar::class)->getRoleClass()); 40 | 41 | return [ 42 | ID::make()->sortable(), 43 | 44 | Text::make(__('laravel-nova-permission::permissions.name'), 'name') 45 | ->rules(['required', 'string', 'max:255']) 46 | ->creationRules('unique:' . config('permission.table_names.permissions')) 47 | ->updateRules('unique:' . config('permission.table_names.permissions') . ',name,{{resourceId}}'), 48 | 49 | Text::make(__('laravel-nova-permission::permissions.display_name'),function (){ 50 | return __('laravel-nova-permission::permissions.display_names.'.$this->name); 51 | })->canSee(function (){ 52 | return is_array(__('laravel-nova-permission::permissions.display_names')); 53 | }), 54 | 55 | Select::make(__('laravel-nova-permission::permissions.guard_name'), 'guard_name') 56 | ->options($guardOptions->toArray()) 57 | ->rules(['required', Rule::in($guardOptions)]), 58 | 59 | DateTime::make(__('laravel-nova-permission::permissions.created_at'), 'created_at')->exceptOnForms(), 60 | 61 | DateTime::make(__('laravel-nova-permission::permissions.updated_at'), 'updated_at')->exceptOnForms(), 62 | 63 | BelongsToMany::make($roleResource::label(), 'roles', $roleResource)->searchable(), 64 | 65 | MorphToMany::make($userResource::label(), 'users', $userResource)->searchable(), 66 | ]; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/PermissionsBasedAuthTrait.php: -------------------------------------------------------------------------------- 1 | authorizeTo($request, 'viewAny'); 36 | } 37 | 38 | /** 39 | * Determine if the resource should be available for the given request. 40 | * 41 | * @param \Illuminate\Http\Request $request 42 | * 43 | * @return bool 44 | */ 45 | public static function authorizedToViewAny(Request $request) 46 | { 47 | if (!static::authorizable()) { 48 | return true; 49 | } 50 | 51 | return static::hasPermissionsTo($request, 'viewAny'); 52 | } 53 | 54 | /** 55 | * Determine if the current user can view the given resource or throw an exception. 56 | * 57 | * @param \Illuminate\Http\Request $request 58 | * 59 | * @throws \Illuminate\Auth\Access\AuthorizationException 60 | */ 61 | public function authorizeToView(Request $request) 62 | { 63 | return $this->authorizeTo($request, 'view') && $this->authorizeToViewAny($request); 64 | } 65 | 66 | /** 67 | * Determine if the current user can create new resources. 68 | * 69 | * @param \Illuminate\Http\Request $request 70 | * 71 | * @return bool 72 | */ 73 | public static function authorizedToCreate(Request $request) 74 | { 75 | return static::hasPermissionsTo($request, 'create'); 76 | } 77 | 78 | /** 79 | * Determine if the user can add / associate models of the given type to the resource. 80 | * 81 | * @param \Laravel\Nova\Http\Requests\NovaRequest $request 82 | * @param \Illuminate\Database\Eloquent\Model|string $model 83 | * 84 | * @return bool 85 | */ 86 | public function authorizedToAdd(NovaRequest $request, $model) 87 | { 88 | if (!static::authorizable()) { 89 | return true; 90 | } 91 | 92 | $method = 'add' . class_basename($model); 93 | 94 | return $this->authorizedTo($request, $method); 95 | } 96 | 97 | /** 98 | * Determine if the user can attach any models of the given type to the resource. 99 | * 100 | * @param \Laravel\Nova\Http\Requests\NovaRequest $request 101 | * @param \Illuminate\Database\Eloquent\Model|string $model 102 | * 103 | * @return bool 104 | */ 105 | public function authorizedToAttachAny(NovaRequest $request, $model) 106 | { 107 | if (!static::authorizable()) { 108 | return true; 109 | } 110 | 111 | $method = 'attachAny' . Str::singular(class_basename($model)); 112 | 113 | return $this->authorizedTo($request, $method); 114 | } 115 | 116 | /** 117 | * Determine if the user can attach models of the given type to the resource. 118 | * 119 | * @param \Laravel\Nova\Http\Requests\NovaRequest $request 120 | * @param \Illuminate\Database\Eloquent\Model|string $model 121 | * 122 | * @return bool 123 | */ 124 | public function authorizedToAttach(NovaRequest $request, $model) 125 | { 126 | if (!static::authorizable()) { 127 | return true; 128 | } 129 | 130 | $method = 'attach' . Str::singular(class_basename($model)); 131 | 132 | return $this->authorizedTo($request, $method); 133 | } 134 | 135 | /** 136 | * Determine if the user can detach models of the given type to the resource. 137 | * 138 | * @param \Laravel\Nova\Http\Requests\NovaRequest $request 139 | * @param \Illuminate\Database\Eloquent\Model|string $model 140 | * @param string $relationship 141 | * 142 | * @return bool 143 | */ 144 | public function authorizedToDetach(NovaRequest $request, $model, $relationship) 145 | { 146 | if (!static::authorizable()) { 147 | return true; 148 | } 149 | 150 | $method = 'detach' . Str::singular(class_basename($model)); 151 | 152 | return $this->authorizedTo($request, $method); 153 | } 154 | 155 | /** 156 | * Determine if the current user has a given ability. 157 | * 158 | * @param \Illuminate\Http\Request $request 159 | * @param string $ability 160 | * 161 | * @throws \Illuminate\Auth\Access\AuthorizationException 162 | */ 163 | public function authorizeTo(Request $request, $ability) 164 | { 165 | throw_unless($this->authorizedTo($request, $ability), AuthorizationException::class); 166 | } 167 | 168 | /** 169 | * Determine if the current user can view the given resource. 170 | * 171 | * @param \Illuminate\Http\Request $request 172 | * @param string $ability 173 | * 174 | * @return bool 175 | */ 176 | public function authorizedTo(Request $request, $ability) 177 | { 178 | return static::authorizable() ? static::hasPermissionsTo($request, $ability) : true; 179 | } 180 | 181 | public static function hasPermissionsTo(Request $request, $ability) 182 | { 183 | if (isset(static::$permissionsForAbilities[$ability])) { 184 | return $request->user()->can(static::$permissionsForAbilities[$ability]); 185 | } 186 | 187 | if (isset(static::$permissionsForAbilities['all'])) { 188 | return $request->user()->can(static::$permissionsForAbilities['all']); 189 | } 190 | 191 | return false; 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/Role.php: -------------------------------------------------------------------------------- 1 | getRoleClass(); 21 | } 22 | 23 | /** 24 | * Get the fields displayed by the resource. 25 | * 26 | * @param \Illuminate\Http\Request $request 27 | * 28 | * @return array 29 | */ 30 | public function fields(Request $request) 31 | { 32 | $guardOptions = collect(config('auth.guards'))->mapWithKeys(function ($value, $key) { 33 | return [$key => $key]; 34 | }); 35 | 36 | $userResource = Nova::resourceForModel(getModelForGuard($this->guard_name)); 37 | 38 | $permissionResource = Nova::resourceForModel(app(PermissionRegistrar::class)->getPermissionClass()); 39 | 40 | return [ 41 | ID::make()->sortable(), 42 | 43 | Text::make(__('laravel-nova-permission::roles.name'), 'name') 44 | ->rules(['required', 'string', 'max:255']) 45 | ->creationRules('unique:' . config('permission.table_names.roles')) 46 | ->updateRules('unique:' . config('permission.table_names.roles') . ',name,{{resourceId}}'), 47 | 48 | Select::make(__('laravel-nova-permission::roles.guard_name'), 'guard_name') 49 | ->options($guardOptions->toArray()) 50 | ->rules(['required', Rule::in($guardOptions)]), 51 | 52 | DateTime::make(__('laravel-nova-permission::roles.created_at'), 'created_at')->exceptOnForms(), 53 | 54 | DateTime::make(__('laravel-nova-permission::roles.updated_at'), 'updated_at')->exceptOnForms(), 55 | 56 | BelongsToMany::make($permissionResource::label(), 'permissions', $permissionResource)->searchable(), 57 | 58 | MorphToMany::make($userResource::label(), 'users', $userResource)->searchable(), 59 | ]; 60 | } 61 | } 62 | --------------------------------------------------------------------------------