├── .github └── issue_template.md ├── .styleci.yml ├── LICENSE ├── README.md ├── codesize.xml ├── composer.json ├── database ├── factories │ └── PermissionFactory.php └── migrations │ ├── 2017_01_01_101000_create_permissions_table.php │ └── 2017_01_01_124000_create_structure_for_permissions.php ├── routes └── api.php ├── src ├── AppServiceProvider.php ├── AuthServiceProvider.php ├── EnumServiceProvider.php ├── Enums │ ├── Types.php │ └── Verbs.php ├── Exceptions │ └── Permission.php ├── Forms │ ├── Builders │ │ └── Permission.php │ └── Templates │ │ └── permission.json ├── Http │ ├── Controllers │ │ ├── Create.php │ │ ├── Destroy.php │ │ ├── Edit.php │ │ ├── ExportExcel.php │ │ ├── InitTable.php │ │ ├── Options.php │ │ ├── Store.php │ │ ├── TableData.php │ │ └── Update.php │ ├── Middleware │ │ └── VerifyRouteAccess.php │ ├── Requests │ │ └── ValidatePermission.php │ └── Resources │ │ └── Permission.php ├── Models │ └── Permission.php ├── State │ ├── DefaultRoute.php │ └── Routes.php └── Tables │ ├── Builders │ └── Permission.php │ └── Templates │ └── permissions.json └── tests └── features └── PermissionTest.php /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | 2 | This is a **bug | feature request**. 3 | 4 | 5 | ### Prerequisites 6 | * [ ] Are you running the latest version? 7 | * [ ] Are you reporting to the correct repository? 8 | * [ ] Did you check the documentation? 9 | * [ ] Did you perform a cursory search? 10 | 11 | ### Description 12 | 13 | 14 | ### Steps to Reproduce 15 | 20 | 21 | ### Expected behavior 22 | 23 | 24 | ### Actual behavior 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.styleci.yml: -------------------------------------------------------------------------------- 1 | risky: true 2 | 3 | preset: laravel 4 | 5 | enabled: 6 | - strict 7 | - unalign_double_arrow 8 | 9 | disabled: 10 | - short_array_syntax 11 | 12 | finder: 13 | exclude: 14 | - "public" 15 | - "resources" 16 | - "tests" 17 | name: 18 | - "*.php" 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 laravel-enso 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 | # Permissions 2 | 3 | [![Codacy Badge](https://app.codacy.com/project/badge/Grade/9e6c16aa475d4504b2ebfb64063c1782)](https://www.codacy.com/gh/laravel-enso/permissions?utm_source=github.com&utm_medium=referral&utm_content=laravel-enso/permissions&utm_campaign=Badge_Grade) 4 | [![StyleCI](https://github.styleci.io/repos/94779938/shield?branch=master)](https://github.styleci.io/repos/94779938) 5 | [![License](https://poser.pugx.org/laravel-enso/permissions/license)](https://packagist.org/packages/laravel-enso/permissions) 6 | [![Total Downloads](https://poser.pugx.org/laravel-enso/permissions/downloads)](https://packagist.org/packages/laravel-enso/permissions) 7 | [![Latest Stable Version](https://poser.pugx.org/laravel-enso/permissions/version)](https://packagist.org/packages/laravel-enso/permissions) 8 | 9 | Permissions dependency for [Laravel Enso](https://github.com/laravel-enso/Enso) 10 | 11 | This package works exclusively within the [Enso](https://github.com/laravel-enso/Enso) ecosystem. 12 | 13 | The front end assets that utilize this api are present in the [ui](https://github.com/enso-ui/ui) package. 14 | 15 | For live examples and demos, you may visit [laravel-enso.com](https://www.laravel-enso.com) 16 | 17 | [![Screenshot](https://laravel-enso.github.io/permissions/screenshots/bulma_029_thumb.png)](https://laravel-enso.github.io/permissions/screenshots/bulma_029.png) 18 | 19 | ### Installation, Configuration & Usage 20 | 21 | Be sure to check out the full documentation for this package available at [docs.laravel-enso.com](https://docs.laravel-enso.com/backend/permissions.html) 22 | 23 | ## Contributions 24 | 25 | are welcome. Pull requests are great, but issues are good too. 26 | 27 | ## License 28 | 29 | This package is released under the MIT license. 30 | -------------------------------------------------------------------------------- /codesize.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | custom rules 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "laravel-enso/permissions", 3 | "description": "Permissions dependency for Laravel Enso", 4 | "keywords": [ 5 | "laravel-enso", 6 | "permission-manager", 7 | "laravel-permissions" 8 | ], 9 | "homepage": "https://github.com/laravel-enso/permissions", 10 | "type": "library", 11 | "license": "MIT", 12 | "authors": [ 13 | { 14 | "name": "Adrian Ocneanu", 15 | "email": "aocneanu@gmail.com", 16 | "homepage": "https://laravel-enso.com", 17 | "role": "Developer" 18 | } 19 | ], 20 | "require": { 21 | "laravel-enso/core": "^10.0", 22 | "laravel-enso/dynamic-methods": "^3.0", 23 | "laravel-enso/enums": "^2.0", 24 | "laravel-enso/forms": "^4.0", 25 | "laravel-enso/helpers": "^3.0", 26 | "laravel-enso/migrator": "^2.0", 27 | "laravel-enso/roles": "^5.0", 28 | "laravel-enso/tables": "^4.0" 29 | }, 30 | "autoload": { 31 | "psr-4": { 32 | "LaravelEnso\\Permissions\\": "src/", 33 | "LaravelEnso\\Permissions\\Database\\Factories\\": "database/factories/" 34 | } 35 | }, 36 | "extra": { 37 | "laravel": { 38 | "providers": [ 39 | "LaravelEnso\\Permissions\\AppServiceProvider", 40 | "LaravelEnso\\Permissions\\AuthServiceProvider", 41 | "LaravelEnso\\Permissions\\EnumServiceProvider" 42 | ], 43 | "aliases": [] 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /database/factories/PermissionFactory.php: -------------------------------------------------------------------------------- 1 | $this->faker->unique()->word, 16 | 'description' => $this->faker->sentence, 17 | 'is_default' => $this->faker->boolean, 18 | ]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /database/migrations/2017_01_01_101000_create_permissions_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 13 | 14 | $table->string('name')->unique()->index(); 15 | $table->string('description')->nullable(); 16 | 17 | $table->boolean('is_default'); 18 | 19 | $table->timestamps(); 20 | }); 21 | } 22 | 23 | public function down() 24 | { 25 | Schema::dropIfExists('permissions'); 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /database/migrations/2017_01_01_124000_create_structure_for_permissions.php: -------------------------------------------------------------------------------- 1 | 'system.permissions.index', 'description' => 'Permissions index', 'is_default' => false], 9 | ['name' => 'system.permissions.create', 'description' => 'Create a new permission', 'is_default' => false], 10 | ['name' => 'system.permissions.edit', 'description' => 'Edit existing permissions', 'is_default' => false], 11 | ['name' => 'system.permissions.store', 'description' => 'Save edited permission', 'is_default' => false], 12 | ['name' => 'system.permissions.update', 'description' => 'Update permission', 'is_default' => false], 13 | ['name' => 'system.permissions.destroy', 'description' => 'Delete permission', 'is_default' => false], 14 | ['name' => 'system.permissions.tableData', 'description' => 'Get table data for permissions', 'is_default' => false], 15 | ['name' => 'system.permissions.exportExcel', 'description' => 'Export excel for permissions', 'is_default' => false], 16 | ['name' => 'system.permissions.initTable', 'description' => 'Init table data for permissions', 'is_default' => false], 17 | ['name' => 'system.permissions.options', 'description' => 'Get options for select', 'is_default' => false], 18 | ]; 19 | 20 | protected array $menu = [ 21 | 'name' => 'Permissions', 'icon' => 'exclamation-triangle', 'route' => 'system.permissions.index', 'order_index' => 999, 'has_children' => false, 22 | ]; 23 | 24 | protected ?string $parentMenu = 'System'; 25 | }; 26 | -------------------------------------------------------------------------------- /routes/api.php: -------------------------------------------------------------------------------- 1 | prefix('api/system/permissions')->as('system.permissions.') 16 | ->group(function () { 17 | Route::get('create', Create::class)->name('create'); 18 | Route::post('', Store::class)->name('store'); 19 | Route::get('{permission}/edit', Edit::class)->name('edit'); 20 | Route::patch('{permission}', Update::class)->name('update'); 21 | Route::delete('{permission}', Destroy::class)->name('destroy'); 22 | 23 | Route::get('initTable', InitTable::class)->name('initTable'); 24 | Route::get('tableData', TableData::class)->name('tableData'); 25 | Route::get('exportExcel', ExportExcel::class)->name('exportExcel'); 26 | Route::get('options', Options::class)->name('options'); 27 | }); 28 | -------------------------------------------------------------------------------- /src/AppServiceProvider.php: -------------------------------------------------------------------------------- 1 | app['router'] 13 | ->aliasMiddleware('verify-route-access', VerifyRouteAccess::class); 14 | 15 | $this->load() 16 | ->publish(); 17 | } 18 | 19 | private function load() 20 | { 21 | $this->loadRoutesFrom(__DIR__.'/../routes/api.php'); 22 | 23 | $this->loadMigrationsFrom(__DIR__.'/../database/migrations'); 24 | 25 | return $this; 26 | } 27 | 28 | private function publish() 29 | { 30 | $this->publishes([ 31 | __DIR__.'/../database/factories' => database_path('factories'), 32 | ], ['permissions-factories', 'enso-factories']); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/AuthServiceProvider.php: -------------------------------------------------------------------------------- 1 | $user->canAccess($route) 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/EnumServiceProvider.php: -------------------------------------------------------------------------------- 1 | Types::class, 12 | ]; 13 | } 14 | -------------------------------------------------------------------------------- /src/Enums/Types.php: -------------------------------------------------------------------------------- 1 | Types::Read, 13 | 'OPTIONS' => Types::Read, 14 | 'GET' => Types::Read, 15 | 'POST' => Types::Write, 16 | 'PATCH' => Types::Write, 17 | 'PUT' => Types::Write, 18 | 'DELETE' => Types::Delete, 19 | ]; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Exceptions/Permission.php: -------------------------------------------------------------------------------- 1 | form = (new Form($this->templatePath())) 18 | ->options('roles', Role::get(['name', 'id'])); 19 | } 20 | 21 | public function create() 22 | { 23 | return $this->form->create(); 24 | } 25 | 26 | public function edit(Model $permission) 27 | { 28 | return $this->form->edit($permission); 29 | } 30 | 31 | protected function templatePath(): string 32 | { 33 | return self::TemplatePath; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Forms/Templates/permission.json: -------------------------------------------------------------------------------- 1 | { 2 | "routePrefix": "system.permissions", 3 | "sections": [ 4 | { 5 | "columns": 2, 6 | "fields": [ 7 | { 8 | "label": "Name", 9 | "name": "name", 10 | "value": "", 11 | "meta": { 12 | "type": "input", 13 | "content": "text" 14 | } 15 | }, 16 | { 17 | "label": "Description", 18 | "name": "description", 19 | "value": "", 20 | "meta": { 21 | "type": "input", 22 | "content": "text" 23 | } 24 | }, 25 | { 26 | "label": "Roles", 27 | "name": "roles", 28 | "value": [], 29 | "meta": { 30 | "type": "select", 31 | "multiple": true, 32 | "options": [] 33 | } 34 | }, 35 | { 36 | "label": "Default", 37 | "name": "is_default", 38 | "value": false, 39 | "meta": { 40 | "type": "input", 41 | "content": "checkbox" 42 | } 43 | } 44 | ] 45 | } 46 | ] 47 | } -------------------------------------------------------------------------------- /src/Http/Controllers/Create.php: -------------------------------------------------------------------------------- 1 | $form->create()]; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Http/Controllers/Destroy.php: -------------------------------------------------------------------------------- 1 | delete(); 13 | 14 | return [ 15 | 'message' => __('The permission was successfully deleted'), 16 | 'redirect' => 'system.permissions.index', 17 | ]; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Http/Controllers/Edit.php: -------------------------------------------------------------------------------- 1 | $form->edit($permission)]; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Http/Controllers/ExportExcel.php: -------------------------------------------------------------------------------- 1 | storeWithRoles( 14 | $request->validatedExcept('roles'), 15 | $request->get('roles'), 16 | ); 17 | 18 | return [ 19 | 'message' => __('The permission was created!'), 20 | 'redirect' => 'system.permissions.edit', 21 | 'param' => ['permission' => $permission->id], 22 | ]; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Http/Controllers/TableData.php: -------------------------------------------------------------------------------- 1 | updateWithRoles( 14 | $request->validatedExcept('roles'), 15 | $request->get('roles') 16 | ); 17 | 18 | return ['message' => __('The permission was successfully updated')]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Http/Middleware/VerifyRouteAccess.php: -------------------------------------------------------------------------------- 1 | user()->cannot('access-route', $request->route()->getName())) { 13 | throw new AuthorizationException( 14 | __('You are not authorized to perform this action') 15 | ); 16 | } 17 | 18 | return $next($request); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Http/Requests/ValidatePermission.php: -------------------------------------------------------------------------------- 1 | ['required', $this->nameUnique()], 22 | 'description' => 'required', 23 | 'is_default' => 'required', 24 | 'roles' => 'array', 25 | ]; 26 | } 27 | 28 | protected function nameUnique() 29 | { 30 | return Rule::unique('permissions', 'name') 31 | ->ignore($this->route('permission')?->id); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Http/Resources/Permission.php: -------------------------------------------------------------------------------- 1 | $this->id, 14 | 'name' => $this->name, 15 | 'shortName' => Str::afterLast($this->name, '.'), 16 | 'description' => $this->description, 17 | 'isDefault' => $this->is_default, 18 | 'type' => $this->type(), 19 | ]; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Models/Permission.php: -------------------------------------------------------------------------------- 1 | hasOne(Menu::class); 25 | } 26 | 27 | public function getTypeAttribute() 28 | { 29 | return $this->type(); 30 | } 31 | 32 | public function type(): string 33 | { 34 | if ($this->relationLoaded('menu') && $this->menu !== null) { 35 | return __(Types::Menu); 36 | } 37 | 38 | return Verbs::get($this->method()) ?? __(Types::Link); 39 | } 40 | 41 | public function method() 42 | { 43 | $methods = Route::getRoutes()->getByName($this->name)?->methods(); 44 | 45 | return $methods[0] ?? null; 46 | } 47 | 48 | public function scopeImplicit($query) 49 | { 50 | return $query->whereIsDefault(true); 51 | } 52 | 53 | public function delete() 54 | { 55 | if ($this->roles()->exists()) { 56 | throw Exception::roles(); 57 | } 58 | 59 | if ($this->menu()->exists()) { 60 | throw Exception::menu(); 61 | } 62 | 63 | parent::delete(); 64 | } 65 | 66 | protected function casts(): array 67 | { 68 | return [ 69 | 'is_default' => 'boolean', 70 | ]; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/State/DefaultRoute.php: -------------------------------------------------------------------------------- 1 | role->menu->permission->name; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/State/Routes.php: -------------------------------------------------------------------------------- 1 | routes(); 20 | } 21 | 22 | protected function routes(): Collection 23 | { 24 | return Auth::user()->role->permissions 25 | ->mapWithKeys(fn ($permission) => [ 26 | $permission->name => $this->route($permission->name), 27 | ])->merge(['login' => $this->route('login')]); 28 | } 29 | 30 | protected function route(string $name): ?array 31 | { 32 | $route = Route::getRoutes()->getByName($name); 33 | 34 | return $route 35 | ? (new Collection($route))->only(['uri', 'methods']) 36 | ->put('domain', $route->domain()) 37 | ->toArray() 38 | : null; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Tables/Builders/Permission.php: -------------------------------------------------------------------------------- 1 | selectRaw(' 16 | permissions.id, permissions.name, permissions.description, 17 | permissions.created_at, permissions.is_default 18 | '); 19 | } 20 | 21 | public function templatePath(): string 22 | { 23 | return self::TemplatePath; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Tables/Templates/permissions.json: -------------------------------------------------------------------------------- 1 | { 2 | "routePrefix": "system.permissions", 3 | "crtNo": true, 4 | "buttons": [ 5 | "excel", 6 | "create", 7 | "edit", 8 | "destroy" 9 | ], 10 | "appends": [ 11 | "type" 12 | ], 13 | "columns": [{ 14 | "label": "Name", 15 | "name": "name", 16 | "data": "permissions.name", 17 | "meta": [ 18 | "searchable", 19 | "sortable" 20 | ] 21 | }, 22 | { 23 | "label": "Description", 24 | "name": "description", 25 | "data": "permissions.description", 26 | "meta": [ 27 | "searchable", 28 | "sortable" 29 | ] 30 | }, 31 | { 32 | "label": "Type", 33 | "name": "type", 34 | "data": "type", 35 | "enum": "LaravelEnso\\Permissions\\Enums\\Types", 36 | "meta": [ 37 | "slot" 38 | ] 39 | }, 40 | { 41 | "label": "Default", 42 | "name": "is_default", 43 | "data": "permissions.is_default", 44 | "meta": [ 45 | "boolean", 46 | "sortable" 47 | ] 48 | }, 49 | { 50 | "label": "Created At", 51 | "name": "created_at", 52 | "data": "permissions.created_at", 53 | "meta": [ 54 | "sortable", 55 | "date" 56 | ] 57 | } 58 | ] 59 | } 60 | -------------------------------------------------------------------------------- /tests/features/PermissionTest.php: -------------------------------------------------------------------------------- 1 | seed() 25 | ->actingAs(User::first()); 26 | 27 | $this->testModel = Permission::factory() 28 | ->make(); 29 | } 30 | 31 | /** @test */ 32 | public function can_store_permission() 33 | { 34 | $response = $this->post( 35 | route('system.permissions.store'), 36 | $this->testModel->toArray() + ['roles' => []] 37 | ); 38 | 39 | $permission = Permission::whereName($this->testModel->name) 40 | ->first(); 41 | 42 | $response->assertStatus(200) 43 | ->assertJsonFragment([ 44 | 'redirect' => 'system.permissions.edit', 45 | 'param' => ['permission' => $permission->id], 46 | ])->assertJsonStructure(['message']); 47 | } 48 | 49 | /** @test */ 50 | public function can_update_permission() 51 | { 52 | $this->testModel->save(); 53 | 54 | $this->testModel->description = 'edited'; 55 | 56 | $this->patch( 57 | route('system.permissions.update', $this->testModel->id, false), 58 | $this->testModel->toArray() + ['roles' => []] 59 | )->assertStatus(200) 60 | ->assertJsonStructure(['message']); 61 | 62 | $this->assertEquals($this->testModel->description, $this->testModel->fresh()->description); 63 | } 64 | 65 | /** @test */ 66 | public function cant_destroy_if_has_roles() 67 | { 68 | $this->testModel->save(); 69 | 70 | $this->testModel->roles()->attach(Role::first(['id'])); 71 | 72 | $this->delete(route('system.permissions.destroy', $this->testModel->id, false)) 73 | ->assertStatus(409); 74 | 75 | $this->assertNotNull($this->testModel->fresh()); 76 | } 77 | } 78 | --------------------------------------------------------------------------------