From d531c972b6819608d05e26e9466442967cfac2b7 Mon Sep 17 00:00:00 2001 From: lukinovec Date: Wed, 5 Feb 2025 10:08:55 +0100 Subject: [PATCH] Add test for `$forceRls = false`, refactor BYPASSRLS test --- tests/RLS/TableManagerTest.php | 102 ++++++++++++++++++++++++--------- 1 file changed, 74 insertions(+), 28 deletions(-) diff --git a/tests/RLS/TableManagerTest.php b/tests/RLS/TableManagerTest.php index c4bf6658..815ee6fd 100644 --- a/tests/RLS/TableManagerTest.php +++ b/tests/RLS/TableManagerTest.php @@ -541,33 +541,67 @@ test('table rls manager generates relationship trees with tables related to the ]); })->with([true, false]); +test('table owner sees all the records when forceRls is false while other users only see records scoped to them', function() { + CreateUserWithRLSPolicies::$forceRls = false; + + // Drop all tables created in beforeEach + DB::statement("DROP TABLE authors, categories, posts, comments, reactions, articles;"); + + [$username, $password] = createPostgresUser('central_user'); + + 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, $tenant2] = [Tenant::create(), Tenant::create()]; + + pest()->artisan('tenants:rls'); + + [$order1, $order2] = [ + Order::create(['name' => 'order1', 'tenant_id' => $tenant1->getTenantKey()]), + Order::create(['name' => 'order2', 'tenant_id' => $tenant2->getTenantKey()]), + ]; + + // The table owner should see all the records + expect(Order::all())->toHaveCount(2); + + tenancy()->initialize($tenant1); + + // The tenant users should only see their records + expect(Order::count())->toBe(1); + expect(Order::first()->name)->toBe($order1->name); + + tenancy()->initialize($tenant2); + + expect(Order::count())->toBe(1); + expect(Order::first()->name)->toBe($order2->name); +}); + +// https://github.com/tenancy-for-laravel/v4/issues/63 test('user without BYPASSRLS can only query owned tables if forceRls is true', function(bool $forceRls) { CreateUserWithRLSPolicies::$forceRls = $forceRls; // Drop all tables created in beforeEach DB::statement("DROP TABLE authors, categories, posts, comments, reactions, articles;"); - try { - DB::statement("DROP OWNED BY administrator;"); - } catch (\Throwable $th) {} - - DB::statement("DROP USER IF EXISTS administrator;"); - - // Create new central user (without superuser and bypassrls privileges) - DB::statement("CREATE USER administrator WITH ENCRYPTED PASSWORD 'password'"); - DB::statement("ALTER USER administrator CREATEDB"); - DB::statement("ALTER USER administrator CREATEROLE"); - - // Grant privileges to the new central user - DB::statement("GRANT ALL PRIVILEGES ON DATABASE main to administrator"); - DB::statement("GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO administrator"); - DB::statement("GRANT ALL ON SCHEMA public TO administrator"); - DB::statement("ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL PRIVILEGES ON TABLES TO administrator"); - DB::statement("GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO administrator"); + [$username, $password] = createPostgresUser('administrator'); config(['database.connections.central' => array_merge( config('database.connections.pgsql'), - ['username' => 'administrator', 'password' => 'password'] + ['username' => $username, 'password' => $password] )]); DB::reconnect(); @@ -583,21 +617,11 @@ test('user without BYPASSRLS can only query owned tables if forceRls is true', f }); $tenant1 = Tenant::create(); - $tenant2 = Tenant::create(); // Create RLS policy for the orders table pest()->artisan('tenants:rls'); - tenancy()->initialize($tenant1); - Order::create(['name' => 'order1', 'tenant_id' => $tenant1->getTenantKey()]); - expect(Order::first())->not()->toBeNull(); - - tenancy()->initialize($tenant2); - - expect(Order::first())->toBeNull(); // RLS works - - tenancy()->end(); if ($forceRls) { // RLS is forced, so by default, not even the table owner should be able to query the table protected by the RLS policy. @@ -725,6 +749,28 @@ test('table manager throws an exception when encountering a recursive relationsh expect(fn () => app(TableRLSManager::class)->generateTrees())->toThrow(RecursiveRelationshipException::class); }); +function createPostgresUser(string $username, string $password = 'password'): array +{ + try { + DB::statement("DROP OWNED BY {$username};"); + } catch (\Throwable $th) {} + + DB::statement("DROP USER IF EXISTS {$username};"); + + DB::statement("CREATE USER {$username} WITH ENCRYPTED PASSWORD '{$password}'"); + DB::statement("ALTER USER {$username} CREATEDB"); + DB::statement("ALTER USER {$username} CREATEROLE"); + + // 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}"); + DB::statement("GRANT ALL ON SCHEMA public TO {$username}"); + DB::statement("ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL PRIVILEGES ON TABLES TO {$username}"); + DB::statement("GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO {$username}"); + + return [$username, $password]; +} + class Post extends Model { protected $guarded = [];