From 562b4a220d3f8b37834e5955ec36221c942cd7ad Mon Sep 17 00:00:00 2001 From: = Date: Wed, 20 May 2026 09:06:55 +0000 Subject: [PATCH] Fix: Associate App Permission with Roles - 'Use' permission of apps now synced with Role - Added `PermissionData` DTO for standardized permission transformation. - Expanded `AppsPermissionService` with `getAllPermissions` method for retrieving connected app permissions. - Updated `AppRoleService` with methods for assigning and revoking app permissions to roles (`assignAppPermissionsRole`, `removeAppPermissionsRole`). - Introduced `AppPermission::type` method for extracting permission type from names. - Refined role and app interaction logic, including proper slug generation and app-role association. --- app/Data/Permissions/PermissionData.php | 29 ++++++++++++ app/Helpers/AppPermission.php | 10 ++++ app/Services/AppRoleService.php | 46 +++++++++++++++++-- app/Services/AppsPermissionService.php | 18 ++++++++ app/Services/ConnectedAppService.php | 1 + config/permission.php | 2 +- .../roles-and-apps/⚡show.blade.php | 8 ++-- 7 files changed, 106 insertions(+), 8 deletions(-) create mode 100644 app/Data/Permissions/PermissionData.php diff --git a/app/Data/Permissions/PermissionData.php b/app/Data/Permissions/PermissionData.php new file mode 100644 index 0000000..7ed32be --- /dev/null +++ b/app/Data/Permissions/PermissionData.php @@ -0,0 +1,29 @@ +id, + name: $permission->name, + type: AppPermission::type($permission->name), + ); + + } +} diff --git a/app/Helpers/AppPermission.php b/app/Helpers/AppPermission.php index fc218ba..4ac0c67 100644 --- a/app/Helpers/AppPermission.php +++ b/app/Helpers/AppPermission.php @@ -18,4 +18,14 @@ public static function name(ConnectedAppPermissonEnum $permission, string|Connec return "{$permission->value}-{$slug}"; } + + /** + * @param string $permissionName This should be the name constructed by the {@see self::name()} method. + */ + public static function type(string $permissionName): ConnectedAppPermissonEnum + { + $permission = explode('-', $permissionName); + + return ConnectedAppPermissonEnum::from($permission[0]); + } } diff --git a/app/Services/AppRoleService.php b/app/Services/AppRoleService.php index aa547b8..2be1084 100644 --- a/app/Services/AppRoleService.php +++ b/app/Services/AppRoleService.php @@ -13,8 +13,13 @@ use Spatie\LaravelData\DataCollection; use Throwable; -final class AppRoleService +final readonly class AppRoleService { + public function __construct( + private ConnectedAppService $connectedAppService, + private AppsPermissionService $appsPermissionService, + ) {} + /** * Assign connected apps to a role, updating the duration if already assigned. * @@ -25,6 +30,9 @@ public function assign(AssignAppsToRoleData $data): void if (empty($data->appIds)) { return; } + $role = Role::query()->findOrFail($data->roleId); + + $this->assignAppPermissionsRole($role, $data->appIds); $records = array_map(fn (int $appId) => [ 'role_id' => $data->roleId, @@ -37,6 +45,21 @@ public function assign(AssignAppsToRoleData $data): void ['role_id', 'app_id'], ['duration'] ); + + } + + /** + * @param array $appIds Apps, of which all permissions will be assigned to the role. + */ + private function assignAppPermissionsRole(Role $role, array $appIds): void + { + array_map(function ($appId) use ($role): void { + $app = $this->connectedAppService->getConnectedApp($appId); + $permissions = $this->appsPermissionService->getAllPermissions($app); + $permissions + ->toCollection() + ->map(fn ($permission) => $role->givePermissionTo($permission->id)); + }, $appIds); } /** @@ -52,10 +75,27 @@ public function detach(int $id): void } DB::transaction(function () use ($id): void { - AppRole::query()->findOrFail($id)->delete(); + $appRole = AppRole::query()->findOrFail($id); + $role = Role::query()->findOrFail($appRole->role_id); + $this->removeAppPermissionsRole($role, $appRole->app_id); + $appRole->delete(); }); } + /** + * @param array|int $appIds + */ + private function removeAppPermissionsRole(Role $role, ...$appIds): void + { + array_map(function ($appId) use ($role): void { + $app = $this->connectedAppService->getConnectedApp($appId); + $permissions = $this->appsPermissionService->getAllPermissions($app); + $permissions + ->toCollection() + ->map(fn ($permission) => $role->revokePermissionTo($permission->name)); + }, $appIds); + } + /** * Get all apps assigned to a given role. * @@ -74,7 +114,7 @@ public function getAppsForRole(int $roleId): DataCollection $assignments->map(fn (AppRole $row): array => [ 'id' => $row->id, 'appId' => $row->app_id, - 'appName' => $row->app->name, + 'appName' => $row->app->name ?? '', 'duration' => $row->duration, ])->all(), DataCollection::class, diff --git a/app/Services/AppsPermissionService.php b/app/Services/AppsPermissionService.php index ad412bc..f55ff59 100644 --- a/app/Services/AppsPermissionService.php +++ b/app/Services/AppsPermissionService.php @@ -5,10 +5,12 @@ namespace App\Services; use App\Data\ConnectedApp\ConnectedAppData; +use App\Data\Permissions\PermissionData; use App\Enums\ConnectedAppPermissonEnum; use App\Helpers\AppPermission; use Illuminate\Support\Facades\DB; use Illuminate\Support\Str; +use Spatie\LaravelData\DataCollection; use Spatie\Permission\Models\Permission; use Throwable; @@ -29,4 +31,20 @@ public function add(ConnectedAppData $connectedApp, ConnectedAppPermissonEnum $p Permission::firstOrCreate(['name' => $permissionName]); }); } + + /** + * Returns all permissions for the connected app. + * + * @return DataCollection + */ + public function getAllPermissions(ConnectedAppData $connectedApp): DataCollection + { + if (! $connectedApp->slug) { + $connectedApp->slug = Str::slug($connectedApp->name); + } + $permissionName = AppPermission::name(ConnectedAppPermissonEnum::Use, $connectedApp); + $permissions = Permission::where('name', 'like', $permissionName)->get(); + + return PermissionData::collect($permissions, DataCollection::class); + } } diff --git a/app/Services/ConnectedAppService.php b/app/Services/ConnectedAppService.php index 65e5775..cf93b46 100644 --- a/app/Services/ConnectedAppService.php +++ b/app/Services/ConnectedAppService.php @@ -22,6 +22,7 @@ public function __construct( /** * Create a new connected app. + * By Default creates a use permission for the app. * * @throws Throwable when an error occurs during the creation. */ diff --git a/config/permission.php b/config/permission.php index d08cef3..4f2808e 100644 --- a/config/permission.php +++ b/config/permission.php @@ -2,7 +2,7 @@ declare(strict_types=1); -use app\Models\Role; +use App\Models\Role; use Spatie\Permission\DefaultTeamResolver; use Spatie\Permission\Models\Permission; diff --git a/resources/views/pages/access-manager/roles-and-apps/⚡show.blade.php b/resources/views/pages/access-manager/roles-and-apps/⚡show.blade.php index a18b663..3a087b4 100644 --- a/resources/views/pages/access-manager/roles-and-apps/⚡show.blade.php +++ b/resources/views/pages/access-manager/roles-and-apps/⚡show.blade.php @@ -64,7 +64,7 @@ public function mount(int $id): void $this->roleName = $role->name; }, onError: function (): void { - $this->redirectRoute('access-manager.roles-and-apps', navigate: true); + $this->redirectRoute('access-manager.roles-and-apps.index', navigate: true); }, showSuccess: false, ); @@ -192,11 +192,11 @@ public function updateApp(): void ); } - public function detachApp(int $assignmentId): void + public function detachApp(int $appId): void { $this->attempt( - action: function () use ($assignmentId): void { - $this->appRoleService->detach($assignmentId); + action: function () use ($appId): void { + $this->appRoleService->detach($appId); unset($this->apps); }, successMessage: 'App removed from role.',