From b48826cc3348b2c402e4db933d49a2be774fe85c Mon Sep 17 00:00:00 2001 From: lukinovec Date: Wed, 4 Jun 2025 13:16:48 +0200 Subject: [PATCH] Add test that makes the bypassrls/forceRls behavior clear --- tests/RLS/TableManagerTest.php | 59 +++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/tests/RLS/TableManagerTest.php b/tests/RLS/TableManagerTest.php index 46c1d81b..4a29d7fc 100644 --- a/tests/RLS/TableManagerTest.php +++ b/tests/RLS/TableManagerTest.php @@ -768,7 +768,59 @@ test('table manager ignores recursive relationship if the foreign key responsibl expect(fn () => app(TableRLSManager::class)->generateTrees())->not()->toThrow(RecursiveRelationshipException::class); }); -function createPostgresUser(string $username, string $password = 'password'): array +test('users with BYPASSRLS privilege can bypass RLS regardless of forceRls setting', function(bool $forceRls, bool $bypassRls) { + CreateUserWithRLSPolicies::$forceRls = $forceRls; + + // Drop all tables created in beforeEach + DB::statement("DROP TABLE authors, categories, posts, comments, reactions, articles;"); + + [$username, $password] = createPostgresUser('administrator', 'password', $bypassRls); + + config(['database.connections.central' => array_merge( + config('database.connections.pgsql'), + ['username' => $username, 'password' => $password] + )]); + + DB::reconnect(); + + Schema::create('orders', function (Blueprint $table) { + $table->id(); + $table->string('name'); + + $table->string('tenant_id')->comment('rls'); + $table->foreign('tenant_id')->references('id')->on('tenants')->onUpdate('cascade')->onDelete('cascade'); + + $table->timestamps(); + }); + + $tenant1 = Tenant::create(); + + // Create RLS policy for the orders table + pest()->artisan('tenants:rls'); + + $tenant1->run(fn () => Order::create(['name' => 'order1', 'tenant_id' => $tenant1->getTenantKey()])); + + if ($bypassRls) { + // Users with BYPASSRLS can always query tables regardless of forceRls setting + // This is the normal production setup behavior + expect(Order::first())->not()->toBeNull(); + expect(Order::first()->name)->toBe('order1'); + } else { + // Users without BYPASSRLS are subject to RLS policies even if they're table owners when forceRls is true + // OR they can bypass as table owners (when forceRls=false) + if ($forceRls) { + // Even table owners need session variable + expect(fn () => Order::first())->toThrow(QueryException::class, 'unrecognized configuration parameter'); + } else { + // Table owners can bypass RLS automatically + expect(Order::first())->not()->toBeNull(); + expect(Order::first()->name)->toBe('order1'); + } + } +})->with([true, false]) + ->with([true, false]); + +function createPostgresUser(string $username, string $password = 'password', bool $bypassRls = false): array { try { DB::statement("DROP OWNED BY {$username};"); @@ -780,6 +832,11 @@ function createPostgresUser(string $username, string $password = 'password'): ar DB::statement("ALTER USER {$username} CREATEDB"); DB::statement("ALTER USER {$username} CREATEROLE"); + // Grant BYPASSRLS privilege if requested + if ($bypassRls) { + DB::statement("ALTER USER {$username} BYPASSRLS"); + } + // Grant privileges to the new central user DB::statement("GRANT ALL PRIVILEGES ON DATABASE main to {$username}"); DB::statement("GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO {$username}");