mirror of
https://github.com/archtechx/tenancy.git
synced 2025-12-12 21:54:03 +00:00
Set $forceRls in tests where scoping is tested, add non-superuser, non-bypassrls table owner test
This commit is contained in:
parent
a7f0c83f8f
commit
1ea1dff504
3 changed files with 93 additions and 12 deletions
|
|
@ -19,6 +19,7 @@ use Stancl\Tenancy\RLS\PolicyManagers\TraitRLSManager;
|
||||||
use Stancl\Tenancy\Bootstrappers\PostgresRLSBootstrapper;
|
use Stancl\Tenancy\Bootstrappers\PostgresRLSBootstrapper;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
|
CreateUserWithRLSPolicies::$forceRls = true;
|
||||||
TraitRLSManager::$excludedModels = [Article::class];
|
TraitRLSManager::$excludedModels = [Article::class];
|
||||||
TraitRLSManager::$modelDirectories = [__DIR__ . '/Etc'];
|
TraitRLSManager::$modelDirectories = [__DIR__ . '/Etc'];
|
||||||
|
|
||||||
|
|
@ -183,7 +184,9 @@ test('rls command recreates policies if the force option is passed', function (s
|
||||||
TraitRLSManager::class,
|
TraitRLSManager::class,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
test('queries will stop working when the tenant session variable is not set', function(string $manager) {
|
test('queries will stop working when the tenant session variable is not set', function(string $manager, bool $forceRls) {
|
||||||
|
CreateUserWithRLSPolicies::$forceRls = $forceRls;
|
||||||
|
|
||||||
config(['tenancy.rls.manager' => $manager]);
|
config(['tenancy.rls.manager' => $manager]);
|
||||||
|
|
||||||
$sessionVariableName = config('tenancy.rls.session_variable_name');
|
$sessionVariableName = config('tenancy.rls.session_variable_name');
|
||||||
|
|
@ -215,7 +218,7 @@ test('queries will stop working when the tenant session variable is not set', fu
|
||||||
INSERT INTO posts (text, tenant_id, author_id)
|
INSERT INTO posts (text, tenant_id, author_id)
|
||||||
VALUES ('post2', ?, ?)
|
VALUES ('post2', ?, ?)
|
||||||
SQL, [$tenant->id, $authorId]))->toThrow(QueryException::class);
|
SQL, [$tenant->id, $authorId]))->toThrow(QueryException::class);
|
||||||
})->with([
|
})->with(
|
||||||
TableRLSManager::class,
|
[TableRLSManager::class, TraitRLSManager::class],
|
||||||
TraitRLSManager::class,
|
[true, false]
|
||||||
]);
|
);
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ use Stancl\Tenancy\Bootstrappers\PostgresRLSBootstrapper;
|
||||||
use Stancl\Tenancy\Database\Exceptions\RecursiveRelationshipException;
|
use Stancl\Tenancy\Database\Exceptions\RecursiveRelationshipException;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
|
CreateUserWithRLSPolicies::$forceRls = true;
|
||||||
TableRLSManager::$scopeByDefault = true;
|
TableRLSManager::$scopeByDefault = true;
|
||||||
|
|
||||||
Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
|
Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
|
||||||
|
|
@ -158,7 +159,9 @@ test('correct rls policies get created with the correct hash using table manager
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('queries are correctly scoped using RLS', function() {
|
test('queries are correctly scoped using RLS', function(bool $forceRls) {
|
||||||
|
CreateUserWithRLSPolicies::$forceRls = $forceRls;
|
||||||
|
|
||||||
// 3-levels deep relationship
|
// 3-levels deep relationship
|
||||||
Schema::create('notes', function (Blueprint $table) {
|
Schema::create('notes', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
|
|
@ -319,7 +322,7 @@ test('queries are correctly scoped using RLS', function() {
|
||||||
|
|
||||||
expect(fn () => DB::statement("INSERT INTO notes (text, comment_id) VALUES ('baz', {$post1Comment->id})"))
|
expect(fn () => DB::statement("INSERT INTO notes (text, comment_id) VALUES ('baz', {$post1Comment->id})"))
|
||||||
->toThrow(QueryException::class);
|
->toThrow(QueryException::class);
|
||||||
});
|
})->with([true, false]);
|
||||||
|
|
||||||
test('table rls manager generates relationship trees with tables related to the tenants table', function (bool $scopeByDefault) {
|
test('table rls manager generates relationship trees with tables related to the tenants table', function (bool $scopeByDefault) {
|
||||||
TableRLSManager::$scopeByDefault = $scopeByDefault;
|
TableRLSManager::$scopeByDefault = $scopeByDefault;
|
||||||
|
|
@ -534,6 +537,74 @@ test('table rls manager generates relationship trees with tables related to the
|
||||||
]);
|
]);
|
||||||
})->with([true, false]);
|
})->with([true, false]);
|
||||||
|
|
||||||
|
test('user without BYPASSRLS can only query owned tables if forceRls is true', function(bool $forceRls) {
|
||||||
|
CreateUserWithRLSPolicies::$forceRls = $forceRls;
|
||||||
|
|
||||||
|
try {
|
||||||
|
DB::statement("DROP OWNED BY administrator;");
|
||||||
|
DB::statement("DROP USER IF EXISTS administrator;");
|
||||||
|
|
||||||
|
// Drop all tables created in beforeEach
|
||||||
|
DB::statement("DROP TABLE authors, categories, posts, comments, reactions;");
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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");
|
||||||
|
|
||||||
|
config(['database.connections.central' => array_merge(
|
||||||
|
config('database.connections.pgsql'),
|
||||||
|
['username' => 'administrator', '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();
|
||||||
|
$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 not be able to query the table protected by the RLS policy
|
||||||
|
// "unrecognized configuration parameter" = the my.current_tenant session variable isn't set -- the RLS policy is working
|
||||||
|
expect(fn () => Order::first())->toThrow(QueryException::class, 'unrecognized configuration parameter');
|
||||||
|
} else {
|
||||||
|
// RLS is not forced, so the table owner should be able to query the table, bypassing the RLS policy
|
||||||
|
expect(Order::first())->not()->toBeNull();
|
||||||
|
}
|
||||||
|
})->with([true, false]);
|
||||||
|
|
||||||
test('table rls manager generates queries correctly', function() {
|
test('table rls manager generates queries correctly', function() {
|
||||||
expect(array_values(app(TableRLSManager::class)->generateQueries()))->toEqualCanonicalizing([
|
expect(array_values(app(TableRLSManager::class)->generateQueries()))->toEqualCanonicalizing([
|
||||||
<<<SQL
|
<<<SQL
|
||||||
|
|
@ -701,3 +772,8 @@ class Author extends Model
|
||||||
{
|
{
|
||||||
protected $guarded = [];
|
protected $guarded = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Order extends Model
|
||||||
|
{
|
||||||
|
protected $guarded = [];
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ use Stancl\Tenancy\Bootstrappers\PostgresRLSBootstrapper;
|
||||||
use Stancl\Tenancy\Database\Concerns\BelongsToPrimaryModel;
|
use Stancl\Tenancy\Database\Concerns\BelongsToPrimaryModel;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
|
CreateUserWithRLSPolicies::$forceRls = true;
|
||||||
TraitRLSManager::$implicitRLS = true;
|
TraitRLSManager::$implicitRLS = true;
|
||||||
TraitRLSManager::$modelDirectories = [__DIR__ . '/Etc'];
|
TraitRLSManager::$modelDirectories = [__DIR__ . '/Etc'];
|
||||||
TraitRLSManager::$excludedModels = [Article::class];
|
TraitRLSManager::$excludedModels = [Article::class];
|
||||||
|
|
@ -148,7 +149,8 @@ test('global scope is not applied when using rls with single db traits', functio
|
||||||
expect(NonRLSComment::make()->hasGlobalScope(ParentModelScope::class))->toBeFalse();
|
expect(NonRLSComment::make()->hasGlobalScope(ParentModelScope::class))->toBeFalse();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('queries are correctly scoped using RLS with trait rls manager', function (bool $implicitRLS) {
|
test('queries are correctly scoped using RLS with trait rls manager', function (bool $implicitRLS, bool $forceRls) {
|
||||||
|
CreateUserWithRLSPolicies::$forceRls = $forceRls;
|
||||||
TraitRLSManager::$implicitRLS = $implicitRLS;
|
TraitRLSManager::$implicitRLS = $implicitRLS;
|
||||||
|
|
||||||
$postModel = $implicitRLS ? NonRLSPost::class : Post::class;
|
$postModel = $implicitRLS ? NonRLSPost::class : Post::class;
|
||||||
|
|
@ -262,10 +264,10 @@ test('queries are correctly scoped using RLS with trait rls manager', function (
|
||||||
|
|
||||||
expect(fn () => DB::statement("INSERT INTO comments (text, post_id) VALUES ('third comment', {$post1->id})"))
|
expect(fn () => DB::statement("INSERT INTO comments (text, post_id) VALUES ('third comment', {$post1->id})"))
|
||||||
->toThrow(QueryException::class);
|
->toThrow(QueryException::class);
|
||||||
})->with([
|
})->with(
|
||||||
true,
|
[true, false],
|
||||||
false
|
[true, false],
|
||||||
]);
|
);
|
||||||
|
|
||||||
test('trait rls manager generates queries correctly', function() {
|
test('trait rls manager generates queries correctly', function() {
|
||||||
/** @var TraitRLSManager $manager */
|
/** @var TraitRLSManager $manager */
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue