From 1cd38891ca7be9f548a291fd99ce8b91580f49bb Mon Sep 17 00:00:00 2001 From: Holger Schmermbeck Date: Sat, 15 Nov 2025 13:26:58 +0100 Subject: [PATCH] fix: add route-level authorization middleware to Phase 4 RBAC endpoints - Add permission middleware to Role Management CRUD (5 routes) - Add permission middleware to Permission Management CRUD (5 routes) - Add permission middleware to User Permission Assignment (4 routes) - Follow Phase 3 pattern: route-level auth + policy (defense-in-depth) - Consistent with Laravel best practices (early authorization) - Use correct permission names from seeder (permissions.assign_direct, permissions.revoke_direct) Fixes #161 --- routes/api.php | 44 ++++++++++++------- .../UserPermissionAssignmentApiTest.php | 8 ++++ 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/routes/api.php b/routes/api.php index 611f85b..2e73686 100644 --- a/routes/api.php +++ b/routes/api.php @@ -47,20 +47,30 @@ Route::get('/me', [AuthController::class, 'me']); // Role Management CRUD API - // Authorization handled by RoleManagementPolicy - Route::get('/roles', [RoleManagementController::class, 'index']); - Route::post('/roles', [RoleManagementController::class, 'store']); - Route::get('/roles/{id}', [RoleManagementController::class, 'show']); - Route::patch('/roles/{id}', [RoleManagementController::class, 'update']); - Route::delete('/roles/{id}', [RoleManagementController::class, 'destroy']); + // Authorization: Route-level permission middleware + Policy (defense-in-depth) + Route::get('/roles', [RoleManagementController::class, 'index']) + ->middleware('permission:roles.read'); + Route::post('/roles', [RoleManagementController::class, 'store']) + ->middleware('permission:roles.create'); + Route::get('/roles/{id}', [RoleManagementController::class, 'show']) + ->middleware('permission:roles.read'); + Route::patch('/roles/{id}', [RoleManagementController::class, 'update']) + ->middleware('permission:roles.update'); + Route::delete('/roles/{id}', [RoleManagementController::class, 'destroy']) + ->middleware('permission:roles.delete'); // Permission Management CRUD API - // Authorization handled by PermissionManagementPolicy - Route::get('/permissions', [PermissionManagementController::class, 'index']); - Route::post('/permissions', [PermissionManagementController::class, 'store']); - Route::get('/permissions/{id}', [PermissionManagementController::class, 'show']); - Route::patch('/permissions/{id}', [PermissionManagementController::class, 'update']); - Route::delete('/permissions/{id}', [PermissionManagementController::class, 'destroy']); + // Authorization: Route-level permission middleware + Policy (defense-in-depth) + Route::get('/permissions', [PermissionManagementController::class, 'index']) + ->middleware('permission:permissions.read'); + Route::post('/permissions', [PermissionManagementController::class, 'store']) + ->middleware('permission:permissions.create'); + Route::get('/permissions/{id}', [PermissionManagementController::class, 'show']) + ->middleware('permission:permissions.read'); + Route::patch('/permissions/{id}', [PermissionManagementController::class, 'update']) + ->middleware('permission:permissions.update'); + Route::delete('/permissions/{id}', [PermissionManagementController::class, 'destroy']) + ->middleware('permission:permissions.delete'); // Role management endpoints Route::post('/users/{user}/roles', [RoleController::class, 'store']) @@ -73,10 +83,14 @@ ->middleware('permission:role.assign'); // User Direct Permission Assignment API (RBAC Phase 4) - // Authorization handled by UserPermissionPolicy (viewPermissions, assignPermission, revokePermission) + // Authorization: Policy-based (users can view own, Admin can view all/modify) Route::get('/users/{user}/permissions', [UserPermissionController::class, 'index']); - Route::post('/users/{user}/permissions', [UserPermissionController::class, 'store']); - Route::delete('/users/{user}/permissions/{permission}', [UserPermissionController::class, 'destroy']); + // Authorization: Route-level permission middleware + Policy (Admin only) + Route::post('/users/{user}/permissions', [UserPermissionController::class, 'store']) + ->middleware('permission:permissions.assign_direct'); + Route::delete('/users/{user}/permissions/{permission}', [UserPermissionController::class, 'destroy']) + ->middleware('permission:permissions.revoke_direct'); + // Authorization: Policy-based (users can view own, Admin can view all) Route::get('/users/{user}/permissions/direct', [UserPermissionController::class, 'direct']); // Tenant-scoped Person endpoints diff --git a/tests/Feature/UserPermissionAssignmentApiTest.php b/tests/Feature/UserPermissionAssignmentApiTest.php index e040098..1e8cd2c 100644 --- a/tests/Feature/UserPermissionAssignmentApiTest.php +++ b/tests/Feature/UserPermissionAssignmentApiTest.php @@ -28,12 +28,20 @@ Permission::create(['name' => 'employees.export', 'guard_name' => 'sanctum']); Permission::create(['name' => 'reports.generate', 'guard_name' => 'sanctum']); Permission::create(['name' => 'shifts.read', 'guard_name' => 'sanctum']); + Permission::create(['name' => 'permissions.read', 'guard_name' => 'sanctum']); + Permission::create(['name' => 'permissions.assign_direct', 'guard_name' => 'sanctum']); + Permission::create(['name' => 'permissions.revoke_direct', 'guard_name' => 'sanctum']); // Create roles with permissions $managerRole = Role::create(['name' => 'Manager', 'guard_name' => 'sanctum']); $managerRole->givePermissionTo(['employees.read', 'shifts.read']); $adminRole = Role::create(['name' => 'Admin', 'guard_name' => 'sanctum']); + $adminRole->givePermissionTo([ + 'permissions.read', + 'permissions.assign_direct', + 'permissions.revoke_direct', + ]); }); afterEach(function (): void {