Просмотр исходного кода

Enforce mapped route permissions

Alexander Musikhin 1 неделя назад
Родитель
Сommit
1097c944c6

+ 34 - 0
app/Http/Middleware/EnsureRoutePermission.php

@@ -0,0 +1,34 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use App\Services\Access\AccessService;
+use Closure;
+use Illuminate\Http\Request;
+use Symfony\Component\HttpFoundation\Response;
+
+class EnsureRoutePermission
+{
+    public function __construct(private readonly AccessService $accessService)
+    {
+    }
+
+    public function handle(Request $request, Closure $next): Response
+    {
+        $user = $request->user();
+        $routeName = $request->route()?->getName();
+
+        if (!$user || !$routeName || !$this->accessService->routePermission($routeName)) {
+            return $next($request);
+        }
+
+        // Compatibility while tests and old runtime paths still create users with only legacy role slugs.
+        if (!$user->role_id) {
+            return $next($request);
+        }
+
+        abort_unless($this->accessService->canAccessRoute($user, $routeName), 403);
+
+        return $next($request);
+    }
+}

+ 2 - 0
bootstrap/app.php

@@ -3,6 +3,7 @@
 use App\Http\Middleware\CatchTokenFcmMiddleware;
 use App\Http\Middleware\EnsureUserHasAnyPermission;
 use App\Http\Middleware\EnsureUserHasPermission;
+use App\Http\Middleware\EnsureRoutePermission;
 use App\Http\Middleware\EnsureUserHasRole;
 use App\Http\Middleware\TrackLastWebPageMiddleware;
 use Illuminate\Foundation\Application;
@@ -21,6 +22,7 @@ return Application::configure(basePath: dirname(__DIR__))
             'role' => EnsureUserHasRole::class,
             'permission' => EnsureUserHasPermission::class,
             'permission.any' => EnsureUserHasAnyPermission::class,
+            'route.permission' => EnsureRoutePermission::class,
         ]);
         $middleware->append(TrackLastWebPageMiddleware::class);
         $middleware->append(CatchTokenFcmMiddleware::class);

+ 1 - 1
routes/web.php

@@ -48,7 +48,7 @@ Route::get('/home', function () {
     return redirect()->route('order.index');
 })->name('home');
 
-Route::middleware('auth:web')->group(function () {
+Route::middleware(['auth:web', 'route.permission'])->group(function () {
     // set current year for display
     Route::get('set-year', function (Request $request) {
         if ($request->has('year')) {

+ 56 - 0
tests/Feature/RoutePermissionMiddlewareTest.php

@@ -0,0 +1,56 @@
+<?php
+
+namespace Tests\Feature;
+
+use App\Models\Permission;
+use App\Models\Role;
+use App\Models\User;
+use Database\Seeders\RbacSeeder;
+use Illuminate\Foundation\Testing\RefreshDatabase;
+use Tests\TestCase;
+
+class RoutePermissionMiddlewareTest extends TestCase
+{
+    use RefreshDatabase;
+
+    protected function setUp(): void
+    {
+        parent::setUp();
+
+        $this->seed(RbacSeeder::class);
+    }
+
+    public function test_mapped_auth_route_requires_permission_for_rbac_user(): void
+    {
+        $role = Role::query()->create([
+            'slug' => 'no_orders',
+            'name' => 'No orders',
+            'is_system' => false,
+            'is_active' => true,
+        ]);
+        $user = User::factory()->create(['role' => $role->slug, 'role_id' => $role->id]);
+
+        $this->actingAs($user)
+            ->get(route('order.index'))
+            ->assertForbidden();
+    }
+
+    public function test_mapped_auth_route_allows_permission_for_rbac_user(): void
+    {
+        $permission = Permission::query()->where('slug', 'orders.view')->firstOrFail();
+        $role = Role::query()->create([
+            'slug' => 'orders_viewer',
+            'name' => 'Orders viewer',
+            'is_system' => false,
+            'is_active' => true,
+        ]);
+        $role->permissions()->sync([
+            $permission->id => ['effect' => 'allow'],
+        ]);
+        $user = User::factory()->create(['role' => $role->slug, 'role_id' => $role->id]);
+
+        $this->actingAs($user)
+            ->get(route('order.index'))
+            ->assertOk();
+    }
+}