From 27f141fb701d3db661875b719933a426d6fc5a24 Mon Sep 17 00:00:00 2001 From: hsbmaulana Date: Sat, 21 Jan 2023 03:23:23 +0700 Subject: [PATCH] build(core): initial --- .github/workflows/cd.yml | 17 ++ LICENSE | 21 ++ README.md | 141 +++++++++++ composer.json | 63 +++++ config/acl.php | 74 ++++++ ..._000000_create_roles_permissions_table.php | 170 +++++++++++++ ...023_01_20_000001_add_teams_fields.php.stub | 100 ++++++++ src/Console/Commands/InstallCommand.php | 81 ++++++ .../Admin/IACLPermissionRepository.php | 13 + .../Repository/Admin/IACLRoleRepository.php | 13 + src/Contracts/Repository/IACLRepository.php | 10 + src/Events/Created.php | 32 +++ src/Events/Deleted.php | 32 +++ src/Events/Granted.php | 24 ++ src/Events/Granting.php | 24 ++ src/Events/Revoked.php | 24 ++ src/Events/Revoking.php | 24 ++ src/Helpers/Accesses.php | 41 ++++ src/Helpers/IACL.php | 18 ++ src/Http/Middleware/PermissionMiddleware.php | 10 + src/Http/Middleware/RoleMiddleware.php | 10 + .../Middleware/RoleOrPermissionMiddleware.php | 10 + src/Http/Resources/AccessResource.php | 37 +++ src/Listeners/RevokeUnruleListener.php | 75 ++++++ src/Listeners/RuleGrantListener.php | 75 ++++++ src/Models/Admin/Permission.php | 11 + src/Models/Admin/Role.php | 11 + src/Providers/ACLServiceProvider.php | 202 +++++++++++++++ src/Repositories/Eloquent/ACLRepository.php | 203 +++++++++++++++ .../Admin/ACLPermissionRepository.php | 127 ++++++++++ .../Eloquent/Admin/ACLRoleRepository.php | 231 ++++++++++++++++++ src/Repositories/QueryBuilder/.gitkeep | 0 src/Scopes/OwnScope.php | 49 ++++ src/Traits/RolePermissionTrait.php | 10 + stubs/app/Exports/ACLs/.gitkeep | 0 .../Http/Controllers/ACL/ACLController.php | 36 +++ .../Admin/ACL/ACLAdminController.php | 134 ++++++++++ .../Admin/ACL/PermissionAdminController.php | 204 ++++++++++++++++ .../Admin/ACL/RoleAdminController.php | 204 ++++++++++++++++ .../Admin/ACL/UserAdminController.php | 68 ++++++ stubs/app/Http/Requests/ACLs/.gitkeep | 0 .../Requests/Admin/ACLs/ACLValidation.php | 87 +++++++ .../PermissionDestroyValidation.php | 38 +++ .../Permissions/PermissionShowValidation.php | 38 +++ .../Permissions/PermissionStoreValidation.php | 27 ++ .../ACLs/Roles/RoleDestroyValidation.php | 38 +++ .../Admin/ACLs/Roles/RoleShowValidation.php | 38 +++ .../Admin/ACLs/Roles/RoleStoreValidation.php | 27 ++ .../Admin/ACLs/Users/UserShowValidation.php | 41 ++++ stubs/app/Http/Responses/.gitkeep | 0 stubs/app/Imports/ACLs/.gitkeep | 0 stubs/routes/admin/acl.php | 22 ++ stubs/routes/user/acl.php | 12 + stubs/tests/Feature/ACL/ACLTest.stub | 24 ++ 54 files changed, 3021 insertions(+) create mode 100644 .github/workflows/cd.yml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 composer.json create mode 100644 config/acl.php create mode 100644 database/migrations/2023_01_20_000000_create_roles_permissions_table.php create mode 100644 database/migrations/2023_01_20_000001_add_teams_fields.php.stub create mode 100644 src/Console/Commands/InstallCommand.php create mode 100644 src/Contracts/Repository/Admin/IACLPermissionRepository.php create mode 100644 src/Contracts/Repository/Admin/IACLRoleRepository.php create mode 100644 src/Contracts/Repository/IACLRepository.php create mode 100644 src/Events/Created.php create mode 100644 src/Events/Deleted.php create mode 100644 src/Events/Granted.php create mode 100644 src/Events/Granting.php create mode 100644 src/Events/Revoked.php create mode 100644 src/Events/Revoking.php create mode 100644 src/Helpers/Accesses.php create mode 100644 src/Helpers/IACL.php create mode 100644 src/Http/Middleware/PermissionMiddleware.php create mode 100644 src/Http/Middleware/RoleMiddleware.php create mode 100644 src/Http/Middleware/RoleOrPermissionMiddleware.php create mode 100644 src/Http/Resources/AccessResource.php create mode 100644 src/Listeners/RevokeUnruleListener.php create mode 100644 src/Listeners/RuleGrantListener.php create mode 100644 src/Models/Admin/Permission.php create mode 100644 src/Models/Admin/Role.php create mode 100644 src/Providers/ACLServiceProvider.php create mode 100644 src/Repositories/Eloquent/ACLRepository.php create mode 100644 src/Repositories/Eloquent/Admin/ACLPermissionRepository.php create mode 100644 src/Repositories/Eloquent/Admin/ACLRoleRepository.php create mode 100644 src/Repositories/QueryBuilder/.gitkeep create mode 100644 src/Scopes/OwnScope.php create mode 100644 src/Traits/RolePermissionTrait.php create mode 100644 stubs/app/Exports/ACLs/.gitkeep create mode 100644 stubs/app/Http/Controllers/ACL/ACLController.php create mode 100644 stubs/app/Http/Controllers/Admin/ACL/ACLAdminController.php create mode 100644 stubs/app/Http/Controllers/Admin/ACL/PermissionAdminController.php create mode 100644 stubs/app/Http/Controllers/Admin/ACL/RoleAdminController.php create mode 100644 stubs/app/Http/Controllers/Admin/ACL/UserAdminController.php create mode 100644 stubs/app/Http/Requests/ACLs/.gitkeep create mode 100644 stubs/app/Http/Requests/Admin/ACLs/ACLValidation.php create mode 100644 stubs/app/Http/Requests/Admin/ACLs/Permissions/PermissionDestroyValidation.php create mode 100644 stubs/app/Http/Requests/Admin/ACLs/Permissions/PermissionShowValidation.php create mode 100644 stubs/app/Http/Requests/Admin/ACLs/Permissions/PermissionStoreValidation.php create mode 100644 stubs/app/Http/Requests/Admin/ACLs/Roles/RoleDestroyValidation.php create mode 100644 stubs/app/Http/Requests/Admin/ACLs/Roles/RoleShowValidation.php create mode 100644 stubs/app/Http/Requests/Admin/ACLs/Roles/RoleStoreValidation.php create mode 100644 stubs/app/Http/Requests/Admin/ACLs/Users/UserShowValidation.php create mode 100644 stubs/app/Http/Responses/.gitkeep create mode 100644 stubs/app/Imports/ACLs/.gitkeep create mode 100644 stubs/routes/admin/acl.php create mode 100644 stubs/routes/user/acl.php create mode 100644 stubs/tests/Feature/ACL/ACLTest.stub diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml new file mode 100644 index 0000000..3c99342 --- /dev/null +++ b/.github/workflows/cd.yml @@ -0,0 +1,17 @@ +name: Laravel.php ACL + +on: push + +jobs: + cd: + runs-on: ubuntu-latest + steps: + - name: cd + uses: tripteki/cd-package@1.0.0 + with: + token: ${{ secrets.GITHUB_TOKEN }} + repotoken: ${{ secrets.REPOSITORY_TOKEN }} + repouser: tripteki + repository: https://packagist.org + language: php + artifact: composer.json \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..061b3a5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Trip Teknologi + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..d65cae5 --- /dev/null +++ b/README.md @@ -0,0 +1,141 @@ +

ACL

+ +This package provides implementation of Access Control List (ACL) Roles-Permissions in repository pattern for Lumen and Laravel besides REST API starterpack of admin management with no intervention to codebase and keep clean. + +Getting Started +--- + +Installation : + +``` +$ composer require tripteki/laravelphp-acl +``` + +How to use it : + +- Read detail optional instruction here [Lumen](https://spatie.be/docs/laravel-permission/installation-lumen) or [Laravel](https://spatie.be/docs/laravel-permission/installation-laravel). + +- Put to any of your model ruleable. + +```php + +/** + * @var array + */ +protected $dispatchesEvents = [ + + "created" => \Tripteki\ACL\Events\Created::class, + "deleted" => \Tripteki\ACL\Events\Deleted::class, + "restored" => \Tripteki\ACL\Events\Created::class, + "forceDeleted" => \Tripteki\ACL\Events\Deleted::class, +]; +``` + +- Put `Tripteki\ACL\Providers\ACLServiceProvider` to service provider configuration list. + +- Put `Tripteki\ACL\Providers\ACLServiceProvider::ignoreConfig()` into `register` provider, then publish config file into your project's directory with running : + +``` +php artisan vendor:publish --tag=tripteki-laravelphp-acl +``` + +- Put `Tripteki\ACL\Providers\ACLServiceProvider::ignoreMigrations()` into `register` provider, then publish migrations file into your project's directory with running (optionally) : + +``` +php artisan vendor:publish --tag=tripteki-laravelphp-acl-migrations +``` + +- Migrate. + +``` +$ php artisan migrate +``` + +- Emit Event-Listener. + +``` +php artisan queue:work +``` + +- Publish tests file into your project's directory with running (optionally) : + +``` +php artisan vendor:publish --tag=tripteki-laravelphp-acl-tests +``` + +- Sample : + +```php +use Tripteki\ACL\Contracts\Repository\Admin\IACLRoleRepository; +use Tripteki\ACL\Contracts\Repository\Admin\IACLPermissionRepository; +use Tripteki\ACL\Contracts\Repository\IACLRepository; + +$roleRepository = app(IACLRoleRepository::class); +$permissionRepository = app(IACLPermissionRepository::class); + +/* + * As `{resource}`.`{action}`.`{target}` is representing : + * + * - {resource} : 'posts' = 'posts.*' = 'posts.*.*' + * - {action} : 'viewAny', 'view', 'create', 'update', 'delete' + * - {target} : '[identifier]' + */ + +// $permissionRepository->rule("posts.update.*"); // +// $permissionRepository->unrule("posts.update.*"); // +// $permissionRepository->get("posts.update.*"); // +// $permissionRepository->all(); // + +// $roleRepository->rule("admin"); // +// $roleRepository->rule("user"); // +// $roleRepository->unrule("admin"); // +// $roleRepository->unrule("user"); // +// $roleRepository->get("admin"); // +// $roleRepository->get("user"); // +// $roleRepository->all(); // + +// $roleRepository->forRole("admin"); // +// $roleRepository->grant("posts.update.*"); // +// $roleRepository->revoke("posts.update.*"); // +// $roleRepository->ability("posts.update.*"); // +// $roleRepository->permissions(); // + +$repository = app(IACLRepository::class); +// $repository->setUser(...); // +// $repository->getUser(); // + +// $repository->grantAs("admin"); // +// $repository->revokeAs("admin"); // +// $repository->is("admin"); // +// $repository->permissions(); // +// $repository->grant("posts.update.5"); // +// $repository->revoke("posts.update.5"); // +// $repository->owns(); // +// $repository->can(iacl(\App\Models\Post::class, "update", 5)); // +// $repository->can("posts.update.5"); // +// auth()->user()->can("posts.update.5"); // +// auth()->user()->canAny([ "posts.update.5", ]); // +// auth()->user()->cant("posts.update.5"); // +// auth()->user()->cantAny([ "posts.update.5", ]); // +``` + +- Generate swagger files into your project's directory with putting this into your annotation configuration (optionally) : + +``` +base_path("app/Http/Controllers/ACL") +``` + +``` +base_path("app/Http/Controllers/Admin/ACL") +``` + +Usage +--- + +`php artisan adminer:install:acl` + +Author +--- + +- Trip Teknologi ([@tripteki](https://linkedin.com/company/tripteki)) +- Hasby Maulana ([@hsbmaulana](https://linkedin.com/in/hsbmaulana)) diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..98a89fd --- /dev/null +++ b/composer.json @@ -0,0 +1,63 @@ +{ + "name": "tripteki/laravelphp-acl", + "version": "1.0.0", + "description": "Trip Teknologi's Laravel.php ACLs", + + "readme": "README.md", + "license": "MIT", + "authors": [ { "name": "Trip Teknologi", "email": "tripteki.company@gmail.com" } ], + "homepage": "https://github.com/tripteki/laravelphp-acl", + "support": { "issues": "https://github.com/tripteki/laravelphp-acl/issues" }, + + "require": { + + "php": "^8.0.2", + + "tripteki/laravelphp-repository": "^1.0.0", + "tripteki/laravelphp-helpers": "^1.0.0", + "tripteki/laravelphp-adminer": "^1.0.0", + "tripteki/laravelphp-import-export": "^1.0.0", + "tripteki/laravelphp-request-response-query": "^1.0.0", + + "spatie/laravel-permission": "^5.8.0" + }, + + "require-dev": {}, + + "suggest": { + + "laravel/lumen-framework": "Required when using lumen framework (^9.0).", + "laravel/framework": "Required when using laravel framework (^9.0)." + }, + + "autoload": { + + "files": [ + + "src/Helpers/IACL.php", + "src/Helpers/Accesses.php" + ], + + "psr-4": { + + "Tripteki\\ACL\\": "src/" + } + }, + + "autoload-dev": {}, + + "extra": { + + "laravel": { + + "dont-discover": [], + + "providers": [ + + "Tripteki\\ACL\\Providers\\ACLServiceProvider" + ], + + "aliases": [] + } + } +} diff --git a/config/acl.php b/config/acl.php new file mode 100644 index 0000000..88de699 --- /dev/null +++ b/config/acl.php @@ -0,0 +1,74 @@ + [ + + "permission" => Tripteki\ACL\Models\Admin\Permission::class, + "role" => Tripteki\ACL\Models\Admin\Role::class, + ], + + "table_names" => [ + + "permissions" => "acl_permissions", + "roles" => "acl_roles", + + "model_has_permissions" => "acl_user_has_permissions", + "model_has_roles" => "acl_user_has_roles", + "role_has_permissions" => "acl_role_has_permissions", + ], + + /** + * Owns : + * + * "index" => "viewAny" + * "show" => "view" + * "create" => "create" + * "store" => "create" + * "edit" => "update" + * "update" => "update" + * "destroy" => "delete" + */ + "own_resources" => [ + + "show", + "update", + "destroy", + ], + + + + "column_names" => [ + + "role_pivot_key" => null, + + "permission_pivot_key" => null, + + "model_morph_key" => "model_id", + + "team_foreign_key" => "team_id", + ], + + "teams" => false, + + + + "enable_wildcard_permission" => true, + + "register_permission_check_method" => true, + + "display_permission_in_exception" => false, + + "display_role_in_exception" => false, + + "cache" => [ + + "expiration_time" => \DateInterval::createFromDateString("24 hours"), + + "key" => Str::slug(env("APP_NAME"), "_")."_acl", + + "store" => "default", + ], +]; diff --git a/database/migrations/2023_01_20_000000_create_roles_permissions_table.php b/database/migrations/2023_01_20_000000_create_roles_permissions_table.php new file mode 100644 index 0000000..efa4694 --- /dev/null +++ b/database/migrations/2023_01_20_000000_create_roles_permissions_table.php @@ -0,0 +1,170 @@ +keytype = app(AuthModelContract::class)->getKeyType(); + $this->role = app(IACLRoleRepository::class); + $this->permission = app(IACLPermissionRepository::class); + } + + /** + * @return void + */ + public function up() + { + $keytype = $this->keytype; + $tableNames = config("permission.table_names"); + $columnNames = config("permission.column_names"); + $teams = config("permission.teams"); + + if (empty($tableNames)) { + + throw new \Exception("Error: config/permission.php not loaded. Run [php artisan config:clear] and try again."); + } + + if ($teams && empty($columnNames["team_foreign_key"] ?? null)) { + + throw new \Exception("Error: team_foreign_key on config/permission.php not loaded. Run [php artisan config:clear] and try again."); + } + + Schema::create($tableNames["permissions"], function (Blueprint $table) { + + $table->uuid("id"); + $table->string("name"); + $table->string("guard_name"); + $table->timestamps(); + $table->unique([ "name", "guard_name", ]); + + $table->primary("id"); + }); + + Schema::create($tableNames["roles"], function (Blueprint $table) use ($teams, $columnNames) { + + $table->uuid("id"); + + if ($teams || config("permission.testing")) { + + $table->unsignedBigInteger($columnNames["team_foreign_key"])->nullable(); + $table->index($columnNames["team_foreign_key"], "roles_team_foreign_key_index"); + } + + $table->string("name"); + $table->string("guard_name"); + $table->timestamps(); + + if ($teams || config("permission.testing")) { + + $table->unique([ $columnNames["team_foreign_key"], "name", "guard_name", ]); + + } else { + + $table->unique([ "name", "guard_name", ]); + } + + $table->primary("id"); + }); + + Schema::create($tableNames["model_has_permissions"], function (Blueprint $table) use ($keytype, $tableNames, $columnNames, $teams) { + + $table->string("model_type"); + + if ($keytype == "int") $table->unsignedBigInteger($columnNames["model_morph_key"]); + else if ($keytype == "string") $table->uuid($columnNames["model_morph_key"]); + + $table->index([ $columnNames["model_morph_key"], "model_type", ], "model_has_permissions_model_id_model_type_index"); + $table->foreignUuid(PermissionRegistrar::$pivotPermission)->references("id")->on($tableNames["permissions"])->onUpdate("cascade")->onDelete("cascade"); + + if ($teams) { + + $table->unsignedBigInteger($columnNames["team_foreign_key"]); + $table->index($columnNames["team_foreign_key"], "model_has_permissions_team_foreign_key_index"); + $table->primary([ $columnNames["team_foreign_key"], PermissionRegistrar::$pivotPermission, $columnNames["model_morph_key"], "model_type", ], "model_has_permissions_permission_model_type_primary"); + + } else { + + $table->primary([ PermissionRegistrar::$pivotPermission, $columnNames["model_morph_key"], "model_type", ], "model_has_permissions_permission_model_type_primary"); + } + }); + + Schema::create($tableNames["model_has_roles"], function (Blueprint $table) use ($keytype, $tableNames, $columnNames, $teams) { + + $table->string("model_type"); + + if ($keytype == "int") $table->unsignedBigInteger($columnNames["model_morph_key"]); + else if ($keytype == "string") $table->uuid($columnNames["model_morph_key"]); + + $table->index([ $columnNames["model_morph_key"], "model_type", ], "model_has_roles_model_id_model_type_index"); + $table->foreignUuid(PermissionRegistrar::$pivotRole)->references("id")->on($tableNames["roles"])->onUpdate("cascade")->onDelete("cascade"); + + if ($teams) { + + $table->unsignedBigInteger($columnNames["team_foreign_key"]); + $table->index($columnNames["team_foreign_key"], "model_has_roles_team_foreign_key_index"); + $table->primary([ $columnNames["team_foreign_key"], PermissionRegistrar::$pivotRole, $columnNames["model_morph_key"], "model_type", ], "model_has_roles_role_model_type_primary"); + + } else { + + $table->primary([ PermissionRegistrar::$pivotRole, $columnNames["model_morph_key"], "model_type", ], "model_has_roles_role_model_type_primary"); + } + }); + + Schema::create($tableNames["role_has_permissions"], function (Blueprint $table) use ($tableNames) { + + $table->foreignUuid(PermissionRegistrar::$pivotPermission)->references("id")->on($tableNames["permissions"])->onUpdate("cascade")->onDelete("cascade"); + $table->foreignUuid(PermissionRegistrar::$pivotRole)->references("id")->on($tableNames["roles"])->onUpdate("cascade")->onDelete("cascade"); + $table->primary([ PermissionRegistrar::$pivotPermission, PermissionRegistrar::$pivotRole, ], "role_has_permissions_permission_id_role_id_primary"); + }); + + app("cache")->store(config("permission.cache.store") != "default" ? config("permission.cache.store") : null)->forget(config("permission.cache.key")); + + $this->role->rule(ACLServiceProvider::SUPERUSER); + } + + /** + * @return void + */ + public function down() + { + $tableNames = config("permission.table_names"); + + if (empty($tableNames)) { + + throw new \Exception("Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually."); + } + + Schema::drop($tableNames["role_has_permissions"]); + Schema::drop($tableNames["model_has_roles"]); + Schema::drop($tableNames["model_has_permissions"]); + Schema::drop($tableNames["roles"]); + Schema::drop($tableNames["permissions"]); + } +}; diff --git a/database/migrations/2023_01_20_000001_add_teams_fields.php.stub b/database/migrations/2023_01_20_000001_add_teams_fields.php.stub new file mode 100644 index 0000000..4d6bf57 --- /dev/null +++ b/database/migrations/2023_01_20_000001_add_teams_fields.php.stub @@ -0,0 +1,100 @@ +unsignedBigInteger($columnNames["team_foreign_key"])->nullable()->after("id"); + $table->index($columnNames["team_foreign_key"], "roles_team_foreign_key_index"); + $table->dropUnique("roles_name_guard_name_unique"); + $table->unique([ $columnNames["team_foreign_key"], "name", "guard_name", ]); + }); + } + + if (! Schema::hasColumn($tableNames["model_has_permissions"], $columnNames["team_foreign_key"])) { + + Schema::table($tableNames["model_has_permissions"], function (Blueprint $table) use ($tableNames, $columnNames) { + + $table->unsignedBigInteger($columnNames["team_foreign_key"])->default("1"); + $table->index($columnNames["team_foreign_key"], "model_has_permissions_team_foreign_key_index"); + + if (DB::getDriverName() !== "sqlite") { + + $table->dropForeign([ PermissionRegistrar::$pivotPermission, ]); + } + + $table->dropPrimary(); + $table->primary([ $columnNames["team_foreign_key"], PermissionRegistrar::$pivotPermission, $columnNames["model_morph_key"], "model_type", ], "model_has_permissions_permission_model_type_primary"); + + if (DB::getDriverName() !== "sqlite") { + + $table->foreignUuid(PermissionRegistrar::$pivotPermission)->references("id")->on($tableNames["permissions"])->onUpdate("cascade")->onDelete("cascade"); + } + }); + } + + if (! Schema::hasColumn($tableNames["model_has_roles"], $columnNames["team_foreign_key"])) { + + Schema::table($tableNames["model_has_roles"], function (Blueprint $table) use ($tableNames, $columnNames) { + + $table->unsignedBigInteger($columnNames["team_foreign_key"])->default("1");; + $table->index($columnNames["team_foreign_key"], "model_has_roles_team_foreign_key_index"); + + if (DB::getDriverName() !== "sqlite") { + + $table->dropForeign([ PermissionRegistrar::$pivotRole, ]); + } + + $table->dropPrimary(); + $table->primary([ $columnNames["team_foreign_key"], PermissionRegistrar::$pivotRole, $columnNames["model_morph_key"], "model_type", ], "model_has_roles_role_model_type_primary"); + + if (DB::getDriverName() !== "sqlite") { + + $table->foreignUuid(PermissionRegistrar::$pivotRole)->references("id")->on($tableNames["roles"])->onDelete("cascade"); + } + }); + } + + app("cache")->store(config("permission.cache.store") != "default" ? config("permission.cache.store") : null)->forget(config("permission.cache.key")); + } + + /** + * @return void + */ + public function down() + { + // + } +}; diff --git a/src/Console/Commands/InstallCommand.php b/src/Console/Commands/InstallCommand.php new file mode 100644 index 0000000..5fe7a03 --- /dev/null +++ b/src/Console/Commands/InstallCommand.php @@ -0,0 +1,81 @@ +helper = $helper; + } + + /** + * @return int + */ + public function handle() + { + $this->installStack(); + + return 0; + } + + /** + * @return int|null + */ + protected function installStack() + { + (new Filesystem)->ensureDirectoryExists(base_path("routes/user")); + (new Filesystem)->ensureDirectoryExists(base_path("routes/admin")); + (new Filesystem)->copy(__DIR__."/../../../stubs/routes/user/acl.php", base_path("routes/user/acl.php")); + (new Filesystem)->copy(__DIR__."/../../../stubs/routes/admin/acl.php", base_path("routes/admin/acl.php")); + $this->helper->putRoute("api.php", "user/acl.php"); + $this->helper->putRoute("api.php", "admin/acl.php"); + + (new Filesystem)->ensureDirectoryExists(app_path("Http/Controllers/ACL")); + (new Filesystem)->copyDirectory(__DIR__."/../../../stubs/app/Http/Controllers/ACL", app_path("Http/Controllers/ACL")); + (new Filesystem)->ensureDirectoryExists(app_path("Http/Requests/ACLs")); + (new Filesystem)->copyDirectory(__DIR__."/../../../stubs/app/Http/Requests/ACLs", app_path("Http/Requests/ACLs")); + (new Filesystem)->ensureDirectoryExists(app_path("Http/Controllers/Admin/ACL")); + (new Filesystem)->copyDirectory(__DIR__."/../../../stubs/app/Http/Controllers/Admin/ACL", app_path("Http/Controllers/Admin/ACL")); + (new Filesystem)->ensureDirectoryExists(app_path("Imports/ACLs")); + (new Filesystem)->copyDirectory(__DIR__."/../../../stubs/app/Imports/ACLs", app_path("Imports/ACLs")); + (new Filesystem)->ensureDirectoryExists(app_path("Exports/ACLs")); + (new Filesystem)->copyDirectory(__DIR__."/../../../stubs/app/Exports/ACLs", app_path("Exports/ACLs")); + (new Filesystem)->ensureDirectoryExists(app_path("Http/Requests/Admin/ACLs")); + (new Filesystem)->copyDirectory(__DIR__."/../../../stubs/app/Http/Requests/Admin/ACLs", app_path("Http/Requests/Admin/ACLs")); + (new Filesystem)->ensureDirectoryExists(app_path("Http/Responses")); + + $this->helper->putTrait($this->helper->classToFile(get_class(app(AuthModelContract::class))), \Tripteki\ACL\Traits\RolePermissionTrait::class); + $this->helper->putMiddleware(null, "role", \Tripteki\ACL\Http\Middleware\RoleMiddleware::class); + $this->helper->putMiddleware(null, "permission", \Tripteki\ACL\Http\Middleware\PermissionMiddleware::class); + $this->helper->putMiddleware(null, "role_or_permission", \Tripteki\ACL\Http\Middleware\RoleOrPermissionMiddleware::class); + + $this->info("Adminer ACL scaffolding installed successfully."); + } +}; diff --git a/src/Contracts/Repository/Admin/IACLPermissionRepository.php b/src/Contracts/Repository/Admin/IACLPermissionRepository.php new file mode 100644 index 0000000..b7ca69b --- /dev/null +++ b/src/Contracts/Repository/Admin/IACLPermissionRepository.php @@ -0,0 +1,13 @@ +user = Auth::check() ? Auth::user() : null; + $this->model = $model; + } +}; diff --git a/src/Events/Deleted.php b/src/Events/Deleted.php new file mode 100644 index 0000000..3547679 --- /dev/null +++ b/src/Events/Deleted.php @@ -0,0 +1,32 @@ +user = Auth::check() ? Auth::user() : null; + $this->model = $model; + } +}; diff --git a/src/Events/Granted.php b/src/Events/Granted.php new file mode 100644 index 0000000..ad96cde --- /dev/null +++ b/src/Events/Granted.php @@ -0,0 +1,24 @@ +data = $data; + } +}; diff --git a/src/Events/Granting.php b/src/Events/Granting.php new file mode 100644 index 0000000..4147a4b --- /dev/null +++ b/src/Events/Granting.php @@ -0,0 +1,24 @@ +data = $data; + } +}; diff --git a/src/Events/Revoked.php b/src/Events/Revoked.php new file mode 100644 index 0000000..a91a8bb --- /dev/null +++ b/src/Events/Revoked.php @@ -0,0 +1,24 @@ +data = $data; + } +}; diff --git a/src/Events/Revoking.php b/src/Events/Revoking.php new file mode 100644 index 0000000..cd4968b --- /dev/null +++ b/src/Events/Revoking.php @@ -0,0 +1,24 @@ +data = $data; + } +}; diff --git a/src/Helpers/Accesses.php b/src/Helpers/Accesses.php new file mode 100644 index 0000000..9a6f84b --- /dev/null +++ b/src/Helpers/Accesses.php @@ -0,0 +1,41 @@ +setUser($user); + + } else { + + if (Auth::check()) { + + $repository->setUser(Auth::user()); + } + } + + if ($repository->getUser() instanceof $class && in_array(RolePermissionTrait::class, class_uses($class))) { + + $accesses = array_merge($repository->permissions()->toArray(), $repository->owns()->toArray()); + } + + return AccessResource::collection($accesses)->resolve(); + }; +} diff --git a/src/Helpers/IACL.php b/src/Helpers/IACL.php new file mode 100644 index 0000000..109d9a9 --- /dev/null +++ b/src/Helpers/IACL.php @@ -0,0 +1,18 @@ +resource["name"]); + $resources = explode(",", $accesses[0]); + $actions = explode(",", $accesses[1]); + $targets = explode(",", $accesses[2]); + + $ables = []; + + foreach ($resources as $resource) { + + foreach ($actions as $action) { + + foreach ($targets as $target) { + + $ables[$resource][$target][$this->resource["id"]] = $action; + } + } + } + + return $ables; + } +}; diff --git a/src/Listeners/RevokeUnruleListener.php b/src/Listeners/RevokeUnruleListener.php new file mode 100644 index 0000000..2019734 --- /dev/null +++ b/src/Listeners/RevokeUnruleListener.php @@ -0,0 +1,75 @@ +role = $role; + $this->permission = $permission; + $this->acl = $acl; + + $this->own = $own; + } + + /** + * @param \Tripteki\ACL\Events\Deleted $event + * @return void + */ + public function handle(Deleted $event) + { + $this->acl->setUser($event->user); + + if ($this->acl->getUser()) { + + foreach ($this->own->scope($event->model) as $able) { + + $this->acl->revoke($able); + $this->permission->unrule($able); + } + } + } +}; diff --git a/src/Listeners/RuleGrantListener.php b/src/Listeners/RuleGrantListener.php new file mode 100644 index 0000000..3190775 --- /dev/null +++ b/src/Listeners/RuleGrantListener.php @@ -0,0 +1,75 @@ +role = $role; + $this->permission = $permission; + $this->acl = $acl; + + $this->own = $own; + } + + /** + * @param \Tripteki\ACL\Events\Created $event + * @return void + */ + public function handle(Created $event) + { + $this->acl->setUser($event->user); + + if ($this->acl->getUser()) { + + foreach ($this->own->scope($event->model) as $able) { + + $this->permission->rule($able); + $this->acl->grant($able); + } + } + } +}; diff --git a/src/Models/Admin/Permission.php b/src/Models/Admin/Permission.php new file mode 100644 index 0000000..a57eeec --- /dev/null +++ b/src/Models/Admin/Permission.php @@ -0,0 +1,11 @@ + \Tripteki\ACL\Repositories\Eloquent\ACLRepository::class, + \Tripteki\ACL\Contracts\Repository\Admin\IACLRoleRepository::class => \Tripteki\ACL\Repositories\Eloquent\Admin\ACLRoleRepository::class, + \Tripteki\ACL\Contracts\Repository\Admin\IACLPermissionRepository::class => \Tripteki\ACL\Repositories\Eloquent\Admin\ACLPermissionRepository::class, + ]; + + /** + * @var string + */ + public const SUPERUSER = "superuser"; + + /** + * @var bool + */ + public static $loadConfig = true; + + /** + * @var bool + */ + public static $runsMigrations = true; + + /** + * @return bool + */ + public static function shouldLoadConfig() + { + return static::$loadConfig; + } + + /** + * @return bool + */ + public static function shouldRunMigrations() + { + return static::$runsMigrations; + } + + /** + * @return void + */ + public static function ignoreConfig() + { + static::$loadConfig = false; + } + + /** + * @return void + */ + public static function ignoreMigrations() + { + static::$runsMigrations = false; + } + + /** + * @return void + */ + public function boot() + { + parent::boot(); + + $this->dataEventListener(); + + $this->registerSuperuser(); + $this->registerPublishers(); + $this->registerConfigs(); + $this->registerCommands(); + $this->registerMigrations(); + } + + /** + * @return void + */ + protected function registerSuperuser() + { + Gate::before(function ($user, $ability) { + + return $user->hasRole(ACLServiceProvider::SUPERUSER) ? true : null; + }); + + // Gate::after(function ($user, $ability) { // + + // return $user->hasRole(ACLServiceProvider::SUPERUSER); // + // }); // + } + + /** + * @return void + */ + protected function registerConfigs() + { + if (static::shouldLoadConfig()) { + + $this->app["config"]->set("permission", []); + $this->mergeConfigFrom(__DIR__."/../../config/acl.php", "permission"); + } + + $this->app->bind(PermissionContract::class, function ($app) { + + $config = $app->config["permission.models"]; + + return $app->make($config["permission"]); + }); + + $this->app->bind(RoleContract::class, function ($app) { + + $config = $app->config["permission.models"]; + + return $app->make($config["role"]); + }); + } + + /** + * @return void + */ + protected function registerCommands() + { + if (! $this->app->isProduction() && $this->app->runningInConsole()) { + + $this->commands( + [ + InstallCommand::class, + ]); + } + } + + /** + * @return void + */ + protected function registerMigrations() + { + if ($this->app->runningInConsole() && static::shouldRunMigrations()) { + + $this->loadMigrationsFrom(__DIR__."/../../database/migrations"); + } + } + + /** + * @return void + */ + protected function registerPublishers() + { + $this->publishes( + [ + __DIR__."/../../config/acl.php" => config_path("permission.php"), + ], + + "tripteki-laravelphp-acl"); + + if (! static::shouldRunMigrations()) { + + $this->publishes( + [ + __DIR__."/../../database/migrations" => database_path("migrations"), + ], + + "tripteki-laravelphp-acl-migrations"); + } + + $this->publishes( + [ + __DIR__."/../../stubs/tests/Feature/ACL/ACLTest.stub" => base_path("tests/Feature/ACL/ACLTest.php"), + ], + + "tripteki-laravelphp-acl-tests"); + } + + /** + * @return void + */ + public function dataEventListener() + { + Permission::observe(UniqueIdObserver::class); + Role::observe(UniqueIdObserver::class); + + Event::listen(Created::class, [ RuleGrantListener::class, "handle", ]); + Event::listen(Deleted::class, [ RevokeUnruleListener::class, "handle", ]); + } +}; diff --git a/src/Repositories/Eloquent/ACLRepository.php b/src/Repositories/Eloquent/ACLRepository.php new file mode 100644 index 0000000..c5c4ce2 --- /dev/null +++ b/src/Repositories/Eloquent/ACLRepository.php @@ -0,0 +1,203 @@ +role = $role; + $this->permission = $permission; + } + + /** + * @param array $querystring|[] + * @return mixed + */ + public function all($querystring = []) + { + $querystringed = + [ + "limit" => $querystring["limit"] ?? request()->query("limit", 10), + "current_page" => $querystring["current_page"] ?? request()->query("current_page", 1), + ]; + extract($querystringed); + + $content = $this->user; + $content = $content->setRelation("roles", + QueryBuilder::for($content->roles())-> + defaultSort("name")-> + allowedSorts([ "name", "guard_name", ])-> + allowedFilters([ "name", "guard_name", ])-> + paginate($limit, [ "*", ], "current_page", $current_page)->appends(empty($querystring) ? request()->query() : $querystringed)); + $content = $content->loadCount("roles"); + + return collect($content)->only([ "roles_count", "roles", ]); + } + + /** + * @param string|int $role + * @return mixed + */ + public function grantAs($role) + { + $content = null; + + DB::beginTransaction(); + + try { + + $content = $this->user->assignRole($this->role->get($role)); + + DB::commit(); + + event(new Granted($content)); + + } catch (Exception $exception) { + + DB::rollback(); + } + + return $content; + } + + /** + * @param string|int $role + * @return mixed + */ + public function revokeAs($role) + { + $content = null; + + DB::beginTransaction(); + + try { + + $content = $this->user->removeRole($this->role->get($role)); + + DB::commit(); + + event(new Revoked($content)); + + } catch (Exception $exception) { + + DB::rollback(); + } + + return $content; + } + + /** + * @param string $role + * @return bool + */ + public function is($role) + { + return $this->user->hasRole($role); + } + + /** + * @param string|int $permission + * @return mixed + */ + public function grant($permission) + { + $content = null; + + DB::beginTransaction(); + + try { + + $content = $this->user->givePermissionTo($this->permission->get($permission)); + + DB::commit(); + + event(new Granted($content)); + + } catch (Exception $exception) { + + DB::rollback(); + } + + return $content; + } + + /** + * @param string|int $permission + * @return mixed + */ + public function revoke($permission) + { + $content = null; + + DB::beginTransaction(); + + try { + + $content = $this->user->revokePermissionTo($this->permission->get($permission)); + + DB::commit(); + + event(new Revoked($content)); + + } catch (Exception $exception) { + + DB::rollback(); + } + + return $content; + } + + /** + * @param string $permission + * @return bool + */ + public function can($permission) + { + return $this->user->can($permission); + } + + /** + * @return \Illuminate\Support\Collection + */ + public function permissions() + { + return $this->user->getPermissionsViaRoles(); + } + + /** + * @return \Illuminate\Support\Collection + */ + public function owns() + { + return $this->user->getDirectPermissions(); + } +}; diff --git a/src/Repositories/Eloquent/Admin/ACLPermissionRepository.php b/src/Repositories/Eloquent/Admin/ACLPermissionRepository.php new file mode 100644 index 0000000..c894368 --- /dev/null +++ b/src/Repositories/Eloquent/Admin/ACLPermissionRepository.php @@ -0,0 +1,127 @@ + $querystring["limit"] ?? request()->query("limit", 10), + "current_page" => $querystring["current_page"] ?? request()->query("current_page", 1), + ]; + extract($querystringed); + + $content = QueryBuilder::for(app(PermissionModel::class)->query())-> + defaultSort("name")-> + allowedSorts([ "name", "guard_name", ])-> + allowedFilters([ "name", "guard_name", ])-> + paginate($limit, [ "*", ], "current_page", $current_page)->appends(empty($querystring) ? request()->query() : $querystringed); + + return $content; + } + + /** + * @param int|string $identifier + * @param array $querystring|[] + * @return mixed + */ + public function get($identifier, $querystring = []) + { + $querystringed = + [ + "limit" => $querystring["limit"] ?? request()->query("limit", 10), + "current_page" => $querystring["current_page"] ?? request()->query("current_page", 1), + ]; + extract($querystringed); + + $content = app(PermissionModel::class)->findByName($identifier); + $content = $content->setRelation("roles", + QueryBuilder::for($content->roles())-> + defaultSort("name")-> + allowedSorts([ "name", "guard_name", ])-> + allowedFilters([ "name", "guard_name", ])-> + paginate($limit, [ "*", ], "current_page", $current_page)->appends(empty($querystring) ? request()->query() : $querystringed)); + $content = $content->loadCount("roles"); + + return $content; + } + + /** + * @param array $data + * @return mixed + */ + public function create($data) + { + $content = null; + + DB::beginTransaction(); + + try { + + $content = app(PermissionModel::class)->create($data); + + DB::commit(); + + } catch (Exception $exception) { + + DB::rollback(); + } + + return $content; + } + + /** + * @param int|string $identifier + * @return mixed + */ + public function delete($identifier) + { + $content = app(PermissionModel::class)->findByName($identifier); + + DB::beginTransaction(); + + try { + + $content->delete(); + + DB::commit(); + + } catch (Exception $exception) { + + DB::rollback(); + } + + return $content; + } + + /** + * @param string $permission + * @return mixed + */ + public function rule($permission) + { + return $this->create([ "name" => $permission, ]); + } + + /** + * @param string $permission + * @return mixed + */ + public function unrule($permission) + { + return $this->delete($permission); + } +}; diff --git a/src/Repositories/Eloquent/Admin/ACLRoleRepository.php b/src/Repositories/Eloquent/Admin/ACLRoleRepository.php new file mode 100644 index 0000000..2b09acb --- /dev/null +++ b/src/Repositories/Eloquent/Admin/ACLRoleRepository.php @@ -0,0 +1,231 @@ +setRole($this->get($role)); + } + + /** + * @param \Illuminate\Database\Eloquent\Model $role + * @return void + */ + protected function setRole(\Illuminate\Database\Eloquent\Model $role) + { + $this->role = $role; + } + + /** + * @return \Illuminate\Database\Eloquent\Model + */ + protected function getRole() + { + return $this->role; + } + + /** + * @param array $querystring|[] + * @return mixed + */ + public function all($querystring = []) + { + $querystringed = + [ + "limit" => $querystring["limit"] ?? request()->query("limit", 10), + "current_page" => $querystring["current_page"] ?? request()->query("current_page", 1), + ]; + extract($querystringed); + + $content = QueryBuilder::for(app(RoleModel::class)->query())-> + defaultSort("name")-> + allowedSorts([ "name", "guard_name", ])-> + allowedFilters([ "name", "guard_name", ])-> + paginate($limit, [ "*", ], "current_page", $current_page)->appends(empty($querystring) ? request()->query() : $querystringed); + + return $content; + } + + /** + * @param int|string $identifier + * @param array $querystring|[] + * @return mixed + */ + public function get($identifier, $querystring = []) + { + $querystringed = + [ + "limit" => $querystring["limit"] ?? request()->query("limit", 10), + "current_page" => $querystring["current_page"] ?? request()->query("current_page", 1), + ]; + extract($querystringed); + + $content = app(RoleModel::class)->findByName($identifier); + $content = $content->setRelation("permissions", + QueryBuilder::for($content->permissions())-> + defaultSort("name")-> + allowedSorts([ "name", "guard_name", ])-> + allowedFilters([ "name", "guard_name", ])-> + paginate($limit, [ "*", ], "current_page", $current_page)->appends(empty($querystring) ? request()->query() : $querystringed)); + $content = $content->loadCount("permissions"); + + return $content; + } + + /** + * @param array $data + * @return mixed + */ + public function create($data) + { + $content = null; + + DB::beginTransaction(); + + try { + + $content = app(RoleModel::class)->create($data); + + DB::commit(); + + } catch (Exception $exception) { + + DB::rollback(); + } + + return $content; + } + + /** + * @param int|string $identifier + * @return mixed + */ + public function delete($identifier) + { + $content = app(RoleModel::class)->findByName($identifier); + + DB::beginTransaction(); + + try { + + $content->delete(); + + DB::commit(); + + } catch (Exception $exception) { + + DB::rollback(); + } + + return $content; + } + + /** + * @param string $role + * @return mixed + */ + public function rule($role) + { + return $this->create([ "name" => $role, ]); + } + + /** + * @param string $role + * @return mixed + */ + public function unrule($role) + { + return $this->delete($role); + } + + /** + * @param string|int $permission + * @return mixed + */ + public function grant($permission) + { + $content = null; + + DB::beginTransaction(); + + try { + + $content = $this->role->givePermissionTo($permission); + + DB::commit(); + + event(new Granted($content)); + + } catch (Exception $exception) { + + DB::rollback(); + } + + return $content; + } + + /** + * @param string|int $permission + * @return mixed + */ + public function revoke($permission) + { + $content = null; + + DB::beginTransaction(); + + try { + + $content = $this->role->revokePermissionTo($permission); + + DB::commit(); + + event(new Revoked($content)); + + } catch (Exception $exception) { + + DB::rollback(); + } + + return $content; + } + + /** + * @param string $permission + * @return bool + */ + public function ability($permission) + { + return $this->role->hasPermissionTo($permission); + } + + /** + * @return \Illuminate\Support\Collection + */ + public function permissions() + { + return $this->role->permissions()->get(); + } +}; diff --git a/src/Repositories/QueryBuilder/.gitkeep b/src/Repositories/QueryBuilder/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/Scopes/OwnScope.php b/src/Scopes/OwnScope.php new file mode 100644 index 0000000..d6871b8 --- /dev/null +++ b/src/Scopes/OwnScope.php @@ -0,0 +1,49 @@ +resourceAbilityMap())->only(config("permission.own_resources")); + $target = $model->{$model->getKeyName()}; + + if (config("permission.enable_wildcard_permission")) { + + $able = $action->implode(","); + + return [ + + $resource.".".$able.".".$target, + ]; + + } else { + + return $action->map(function ($able) use ($resource, $target) { + + return $resource.".".$able.".".$target; + + })->toArray(); + } + } +}; diff --git a/src/Traits/RolePermissionTrait.php b/src/Traits/RolePermissionTrait.php new file mode 100644 index 0000000..1d18f89 --- /dev/null +++ b/src/Traits/RolePermissionTrait.php @@ -0,0 +1,10 @@ +user()); + + return iresponse($data, $statecode); + } +}; diff --git a/stubs/app/Http/Controllers/Admin/ACL/ACLAdminController.php b/stubs/app/Http/Controllers/Admin/ACL/ACLAdminController.php new file mode 100644 index 0000000..e4877f8 --- /dev/null +++ b/stubs/app/Http/Controllers/Admin/ACL/ACLAdminController.php @@ -0,0 +1,134 @@ +aclRoleAdminRepository = $aclRoleAdminRepository; + $this->aclUserAdminRepository = $aclUserAdminRepository; + } + + /** + * @OA\Put( + * path="/admin/acls/{context}/{object}", + * tags={"Admin ACL Rule"}, + * summary="rule", + * @OA\Parameter( + * required=true, + * in="path", + * name="context", + * schema={"type": "string", "enum": {"grant_permissions_to_role", "revoke_permissions_from_role", "grant_roles_to_user", "revoke_roles_from_user"}}, + * description="ACL's Context." + * ), + * @OA\Parameter( + * required=true, + * in="path", + * name="object", + * description="ACL's Object." + * ), + * @OA\RequestBody( + * @OA\MediaType( + * mediaType="application/x-www-form-urlencoded", + * @OA\Schema( + * @OA\Property( + * property="rules[]", + * type="array", + * collectionFormat="multi", + * @OA\Items(type="string"), + * description="ACL's Rules." + * ) + * ) + * ) + * ), + * @OA\Response( + * response=201, + * description="Created." + * ), + * @OA\Response( + * response=422, + * description="Unprocessable Entity." + * ), + * @OA\Response( + * response=404, + * description="Not Found." + * ) + * ) + * + * @param \App\Http\Requests\Admin\ACLs\ACLValidation $request + * @param string $context + * @param string $object + * @return \Illuminate\Http\JsonResponse + */ + public function rule(ACLValidation $request, $context, $object) + { + $form = $request->validated(); + $data = []; + $statecode = 202; + + if ($context == ACLValidation::GRANT_PERMISSIONS_TO_ROLE || $context == ACLValidation::REVOKE_PERMISSIONS_FROM_ROLE) { + + $this->aclRoleAdminRepository->forRole($object); + + foreach ($form["rules"] as $rule) { + + if ($context == ACLValidation::GRANT_PERMISSIONS_TO_ROLE) { + + $data[] = $this->aclRoleAdminRepository->grant($rule); + + } else if ($context == ACLValidation::REVOKE_PERMISSIONS_FROM_ROLE) { + + $data[] = $this->aclRoleAdminRepository->revoke($rule); + } + } + + } else if ($context == ACLValidation::GRANT_ROLES_TO_USER || $context == ACLValidation::REVOKE_ROLES_FROM_USER) { + + $this->aclUserAdminRepository->setUser(app(AuthModelContract::class)->findOrFail($object)); + + foreach ($form["rules"] as $rule) { + + if ($context == ACLValidation::GRANT_ROLES_TO_USER) { + + $data[] = $this->aclUserAdminRepository->grantAs($rule); + + } else if ($context == ACLValidation::REVOKE_ROLES_FROM_USER) { + + $data[] = $this->aclUserAdminRepository->revokeAs($rule); + } + } + } + + if ($data) { + + $statecode = 201; + } + + return iresponse($data, $statecode); + } +}; diff --git a/stubs/app/Http/Controllers/Admin/ACL/PermissionAdminController.php b/stubs/app/Http/Controllers/Admin/ACL/PermissionAdminController.php new file mode 100644 index 0000000..c2b8df3 --- /dev/null +++ b/stubs/app/Http/Controllers/Admin/ACL/PermissionAdminController.php @@ -0,0 +1,204 @@ +permissionAdminRepository = $permissionAdminRepository; + } + + /** + * @OA\Get( + * path="/admin/acls/permissions", + * tags={"Admin ACL Permission"}, + * summary="Index", + * @OA\Parameter( + * required=false, + * in="query", + * name="limit", + * description="ACL Permission's Pagination Limit." + * ), + * @OA\Parameter( + * required=false, + * in="query", + * name="current_page", + * description="ACL Permission's Pagination Current Page." + * ), + * @OA\Parameter( + * required=false, + * in="query", + * name="order", + * description="ACL Permission's Pagination Order." + * ), + * @OA\Parameter( + * required=false, + * in="query", + * name="filter[]", + * description="ACL Permission's Pagination Filter." + * ), + * @OA\Response( + * response=200, + * description="Success." + * ) + * ) + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\JsonResponse + */ + public function index(Request $request) + { + $data = []; + $statecode = 200; + + $data = $this->permissionAdminRepository->all(); + + return iresponse($data, $statecode); + } + + /** + * @OA\Get( + * path="/admin/acls/permissions/{permission}", + * tags={"Admin ACL Permission"}, + * summary="Show", + * @OA\Parameter( + * required=true, + * in="path", + * name="permission", + * description="ACL Permission's Permission." + * ), + * @OA\Response( + * response=200, + * description="Success." + * ), + * @OA\Response( + * response=404, + * description="Not Found." + * ) + * ) + * + * @param \App\Http\Requests\Admin\ACLs\Permissions\PermissionShowValidation $request + * @param string $permission + * @return \Illuminate\Http\JsonResponse + */ + public function show(PermissionShowValidation $request, $permission) + { + $form = $request->validated(); + $data = []; + $statecode = 200; + + $data = $this->permissionAdminRepository->get($permission); + + return iresponse($data, $statecode); + } + + /** + * @OA\Post( + * path="/admin/acls/permissions", + * tags={"Admin ACL Permission"}, + * summary="Store", + * @OA\RequestBody( + * @OA\MediaType( + * mediaType="application/x-www-form-urlencoded", + * @OA\Schema( + * @OA\Property( + * property="permission", + * type="string", + * description="ACL Permission's Permission." + * ) + * ) + * ) + * ), + * @OA\Response( + * response=201, + * description="Created." + * ), + * @OA\Response( + * response=422, + * description="Unprocessable Entity." + * ) + * ) + * + * @param \App\Http\Requests\Admin\ACLs\Permissions\PermissionStoreValidation $request + * @return \Illuminate\Http\JsonResponse + */ + public function store(PermissionStoreValidation $request) + { + $form = $request->validated(); + $data = []; + $statecode = 202; + + $data = $this->permissionAdminRepository->rule($form["permission"]); + + if ($data) { + + $statecode = 201; + } + + return iresponse($data, $statecode); + } + + /** + * @OA\Delete( + * path="/admin/acls/permissions/{permission}", + * tags={"Admin ACL Permission"}, + * summary="Destroy", + * @OA\Parameter( + * required=true, + * in="path", + * name="permission", + * description="ACL Permission's Permission." + * ), + * @OA\Response( + * response=200, + * description="Success." + * ), + * @OA\Response( + * response=422, + * description="Unprocessable Entity." + * ), + * @OA\Response( + * response=404, + * description="Not Found." + * ) + * ) + * + * @param \App\Http\Requests\Admin\ACLs\Permissions\PermissionDestroyValidation $request + * @param string $permission + * @return \Illuminate\Http\JsonResponse + */ + public function destroy(PermissionDestroyValidation $request, $permission) + { + $form = $request->validated(); + $data = []; + $statecode = 202; + + $data = $this->permissionAdminRepository->unrule($permission); + + if ($data) { + + $statecode = 200; + } + + return iresponse($data, $statecode); + } +}; diff --git a/stubs/app/Http/Controllers/Admin/ACL/RoleAdminController.php b/stubs/app/Http/Controllers/Admin/ACL/RoleAdminController.php new file mode 100644 index 0000000..324013d --- /dev/null +++ b/stubs/app/Http/Controllers/Admin/ACL/RoleAdminController.php @@ -0,0 +1,204 @@ +roleAdminRepository = $roleAdminRepository; + } + + /** + * @OA\Get( + * path="/admin/acls/roles", + * tags={"Admin ACL Role"}, + * summary="Index", + * @OA\Parameter( + * required=false, + * in="query", + * name="limit", + * description="ACL Role's Pagination Limit." + * ), + * @OA\Parameter( + * required=false, + * in="query", + * name="current_page", + * description="ACL Role's Pagination Current Page." + * ), + * @OA\Parameter( + * required=false, + * in="query", + * name="order", + * description="ACL Role's Pagination Order." + * ), + * @OA\Parameter( + * required=false, + * in="query", + * name="filter[]", + * description="ACL Role's Pagination Filter." + * ), + * @OA\Response( + * response=200, + * description="Success." + * ) + * ) + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\JsonResponse + */ + public function index(Request $request) + { + $data = []; + $statecode = 200; + + $data = $this->roleAdminRepository->all(); + + return iresponse($data, $statecode); + } + + /** + * @OA\Get( + * path="/admin/acls/roles/{role}", + * tags={"Admin ACL Role"}, + * summary="Show", + * @OA\Parameter( + * required=true, + * in="path", + * name="role", + * description="ACL Role's Role." + * ), + * @OA\Response( + * response=200, + * description="Success." + * ), + * @OA\Response( + * response=404, + * description="Not Found." + * ) + * ) + * + * @param \App\Http\Requests\Admin\ACLs\Roles\RoleShowValidation $request + * @param string $role + * @return \Illuminate\Http\JsonResponse + */ + public function show(RoleShowValidation $request, $role) + { + $form = $request->validated(); + $data = []; + $statecode = 200; + + $data = $this->roleAdminRepository->get($role); + + return iresponse($data, $statecode); + } + + /** + * @OA\Post( + * path="/admin/acls/roles", + * tags={"Admin ACL Role"}, + * summary="Store", + * @OA\RequestBody( + * @OA\MediaType( + * mediaType="application/x-www-form-urlencoded", + * @OA\Schema( + * @OA\Property( + * property="role", + * type="string", + * description="ACL Role's Role." + * ) + * ) + * ) + * ), + * @OA\Response( + * response=201, + * description="Created." + * ), + * @OA\Response( + * response=422, + * description="Unprocessable Entity." + * ) + * ) + * + * @param \App\Http\Requests\Admin\ACLs\Roles\RoleStoreValidation $request + * @return \Illuminate\Http\JsonResponse + */ + public function store(RoleStoreValidation $request) + { + $form = $request->validated(); + $data = []; + $statecode = 202; + + $data = $this->roleAdminRepository->rule($form["role"]); + + if ($data) { + + $statecode = 201; + } + + return iresponse($data, $statecode); + } + + /** + * @OA\Delete( + * path="/admin/acls/roles/{role}", + * tags={"Admin ACL Role"}, + * summary="Destroy", + * @OA\Parameter( + * required=true, + * in="path", + * name="role", + * description="ACL Role's Role." + * ), + * @OA\Response( + * response=200, + * description="Success." + * ), + * @OA\Response( + * response=422, + * description="Unprocessable Entity." + * ), + * @OA\Response( + * response=404, + * description="Not Found." + * ) + * ) + * + * @param \App\Http\Requests\Admin\ACLs\Roles\RoleDestroyValidation $request + * @param string $role + * @return \Illuminate\Http\JsonResponse + */ + public function destroy(RoleDestroyValidation $request, $role) + { + $form = $request->validated(); + $data = []; + $statecode = 202; + + $data = $this->roleAdminRepository->unrule($role); + + if ($data) { + + $statecode = 200; + } + + return iresponse($data, $statecode); + } +}; diff --git a/stubs/app/Http/Controllers/Admin/ACL/UserAdminController.php b/stubs/app/Http/Controllers/Admin/ACL/UserAdminController.php new file mode 100644 index 0000000..a032730 --- /dev/null +++ b/stubs/app/Http/Controllers/Admin/ACL/UserAdminController.php @@ -0,0 +1,68 @@ +userAdminRepository = $userAdminRepository; + } + + /** + * @OA\Get( + * path="/admin/acls/users/{user}", + * tags={"Admin ACL User"}, + * summary="Show", + * @OA\Parameter( + * required=true, + * in="path", + * name="user", + * description="ACL User's User." + * ), + * @OA\Response( + * response=200, + * description="Success." + * ), + * @OA\Response( + * response=404, + * description="Not Found." + * ) + * ) + * + * @param \App\Http\Requests\Admin\ACLs\Users\UserShowValidation $request + * @param string $user + * @return \Illuminate\Http\JsonResponse + */ + public function show(UserShowValidation $request, $user) + { + $form = $request->validated(); + $data = []; + $statecode = 200; + + $user = app(AuthModelContract::class)->findOrFail($user); + + $this->userAdminRepository->setUser($user); + + $data = $this->userAdminRepository->all(); + + return iresponse($data, $statecode); + } +}; diff --git a/stubs/app/Http/Requests/ACLs/.gitkeep b/stubs/app/Http/Requests/ACLs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/stubs/app/Http/Requests/Admin/ACLs/ACLValidation.php b/stubs/app/Http/Requests/Admin/ACLs/ACLValidation.php new file mode 100644 index 0000000..1fd8897 --- /dev/null +++ b/stubs/app/Http/Requests/Admin/ACLs/ACLValidation.php @@ -0,0 +1,87 @@ + $this->route("context"), + "object" => $this->route("object"), + ]; + } + + /** + * @return bool + */ + public function authorize() + { + return true; + } + + /** + * @return array + */ + public function rules() + { + $provider = app(AuthModelContract::class); + + $validator = [ + + "context" => "required|string|in:".self::GRANT_PERMISSIONS_TO_ROLE.",".self::REVOKE_PERMISSIONS_FROM_ROLE.",".self::GRANT_ROLES_TO_USER.",".self::REVOKE_ROLES_FROM_USER, + "rules" => "required|array", + "rules.*" => [], + "object" => [ "required", "string", ], + ]; + + if ($this->route("context") == self::GRANT_PERMISSIONS_TO_ROLE || $this->route("context") == self::REVOKE_PERMISSIONS_FROM_ROLE) { + + $validator["rules.*"] = [ + + Rule::exists(config("permission.models.permission"), "name"), + ]; + + $validator["object"][] = "exists:".config("permission.models.role").",name"; + + } else if ($this->route("context") == self::GRANT_ROLES_TO_USER || $this->route("context") == self::REVOKE_ROLES_FROM_USER) { + + $validator["rules.*"] = [ + + Rule::exists(config("permission.models.role"), "name"), + ]; + + $validator["object"][] = "exists:".get_class($provider).",".keyName($provider); + } + + return $validator; + } +}; diff --git a/stubs/app/Http/Requests/Admin/ACLs/Permissions/PermissionDestroyValidation.php b/stubs/app/Http/Requests/Admin/ACLs/Permissions/PermissionDestroyValidation.php new file mode 100644 index 0000000..7fd3c50 --- /dev/null +++ b/stubs/app/Http/Requests/Admin/ACLs/Permissions/PermissionDestroyValidation.php @@ -0,0 +1,38 @@ + $this->route("permission"), + ]; + } + + /** + * @return bool + */ + public function authorize() + { + return true; + } + + /** + * @return array + */ + public function rules() + { + return [ + + "permission" => "required|string|exists:".config("permission.models.permission").",name", + ]; + } +}; diff --git a/stubs/app/Http/Requests/Admin/ACLs/Permissions/PermissionShowValidation.php b/stubs/app/Http/Requests/Admin/ACLs/Permissions/PermissionShowValidation.php new file mode 100644 index 0000000..a89bd73 --- /dev/null +++ b/stubs/app/Http/Requests/Admin/ACLs/Permissions/PermissionShowValidation.php @@ -0,0 +1,38 @@ + $this->route("permission"), + ]; + } + + /** + * @return bool + */ + public function authorize() + { + return true; + } + + /** + * @return array + */ + public function rules() + { + return [ + + "permission" => "required|string|exists:".config("permission.models.permission").",name", + ]; + } +}; diff --git a/stubs/app/Http/Requests/Admin/ACLs/Permissions/PermissionStoreValidation.php b/stubs/app/Http/Requests/Admin/ACLs/Permissions/PermissionStoreValidation.php new file mode 100644 index 0000000..fbd4149 --- /dev/null +++ b/stubs/app/Http/Requests/Admin/ACLs/Permissions/PermissionStoreValidation.php @@ -0,0 +1,27 @@ + + */ + public function rules() + { + return [ + + "permission" => "required|string|max:127|regex:/^[a-zA-Z\._]+$/|unique:".config("permission.models.permission").",name", + ]; + } +}; diff --git a/stubs/app/Http/Requests/Admin/ACLs/Roles/RoleDestroyValidation.php b/stubs/app/Http/Requests/Admin/ACLs/Roles/RoleDestroyValidation.php new file mode 100644 index 0000000..5e497f5 --- /dev/null +++ b/stubs/app/Http/Requests/Admin/ACLs/Roles/RoleDestroyValidation.php @@ -0,0 +1,38 @@ + $this->route("role"), + ]; + } + + /** + * @return bool + */ + public function authorize() + { + return true; + } + + /** + * @return array + */ + public function rules() + { + return [ + + "role" => "required|string|exists:".config("permission.models.role").",name", + ]; + } +}; diff --git a/stubs/app/Http/Requests/Admin/ACLs/Roles/RoleShowValidation.php b/stubs/app/Http/Requests/Admin/ACLs/Roles/RoleShowValidation.php new file mode 100644 index 0000000..799ff5a --- /dev/null +++ b/stubs/app/Http/Requests/Admin/ACLs/Roles/RoleShowValidation.php @@ -0,0 +1,38 @@ + $this->route("role"), + ]; + } + + /** + * @return bool + */ + public function authorize() + { + return true; + } + + /** + * @return array + */ + public function rules() + { + return [ + + "role" => "required|string|exists:".config("permission.models.role").",name", + ]; + } +}; diff --git a/stubs/app/Http/Requests/Admin/ACLs/Roles/RoleStoreValidation.php b/stubs/app/Http/Requests/Admin/ACLs/Roles/RoleStoreValidation.php new file mode 100644 index 0000000..f6ad5fa --- /dev/null +++ b/stubs/app/Http/Requests/Admin/ACLs/Roles/RoleStoreValidation.php @@ -0,0 +1,27 @@ + + */ + public function rules() + { + return [ + + "role" => "required|string|max:127|regex:/^[a-zA-Z\._]+$/|unique:".config("permission.models.role").",name", + ]; + } +}; diff --git a/stubs/app/Http/Requests/Admin/ACLs/Users/UserShowValidation.php b/stubs/app/Http/Requests/Admin/ACLs/Users/UserShowValidation.php new file mode 100644 index 0000000..eee292c --- /dev/null +++ b/stubs/app/Http/Requests/Admin/ACLs/Users/UserShowValidation.php @@ -0,0 +1,41 @@ + $this->route("user"), + ]; + } + + /** + * @return bool + */ + public function authorize() + { + return true; + } + + /** + * @return array + */ + public function rules() + { + $provider = app(AuthModelContract::class); + + return [ + + "user" => "required|string|exists:".get_class($provider).",".keyName($provider), + ]; + } +}; diff --git a/stubs/app/Http/Responses/.gitkeep b/stubs/app/Http/Responses/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/stubs/app/Imports/ACLs/.gitkeep b/stubs/app/Imports/ACLs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/stubs/routes/admin/acl.php b/stubs/routes/admin/acl.php new file mode 100644 index 0000000..fb5f051 --- /dev/null +++ b/stubs/routes/admin/acl.php @@ -0,0 +1,22 @@ +middleware(config("adminer.middleware.admin"))->group(function () { + + /** + * ACLs. + */ + Route::prefix("acls")->group(function () { + + Route::apiResource("users", UserAdminController::class)->only("show")->parameters([ "users" => "user", ]); + Route::apiResource("roles", RoleAdminController::class)->except("update")->parameters([ "roles" => "role", ]); + Route::apiResource("permissions", PermissionAdminController::class)->except("update")->parameters([ "permissions" => "permission", ]); + + Route::put("/{context}/{object}", [ ACLAdminController::class, "rule", ]); + }); +}); diff --git a/stubs/routes/user/acl.php b/stubs/routes/user/acl.php new file mode 100644 index 0000000..649e5f4 --- /dev/null +++ b/stubs/routes/user/acl.php @@ -0,0 +1,12 @@ +middleware(config("adminer.middleware.user"))->group(function () { + + /** + * ACLs. + */ + Route::get("acls", [ ACLController::class, "index", ]); +}); diff --git a/stubs/tests/Feature/ACL/ACLTest.stub b/stubs/tests/Feature/ACL/ACLTest.stub new file mode 100644 index 0000000..35fea51 --- /dev/null +++ b/stubs/tests/Feature/ACL/ACLTest.stub @@ -0,0 +1,24 @@ +user(); + $this->actingAs($user); + + $data = accesses(); + $this->assertIsArray($data); + } +};