Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[4.x] RLS without central user (role) having bypassrls #1288

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified extensions/lib/noattach.dll
Binary file not shown.
50 changes: 26 additions & 24 deletions src/Commands/CreateUserWithRLSPolicies.php
Original file line number Diff line number Diff line change
Expand Up @@ -105,34 +105,36 @@

$createdPolicies = [];

foreach ($rlsQueries as $table => $query) {
[$hash, $policyQuery] = $this->hashPolicy($query);
$expectedName = $table . '_rls_policy_' . $hash;
foreach ($rlsQueries as $table => $queries) {
foreach ($queries as $type => $query) {

Check failure on line 109 in src/Commands/CreateUserWithRLSPolicies.php

View workflow job for this annotation

GitHub Actions / Static analysis (PHPStan)

Argument of an invalid type string supplied for foreach, only iterables are supported.
[$hash, $policyQuery] = $this->hashPolicy($query);
$expectedName = $table . '_' . $type . '_rls_policy_' . $hash;

$tableRLSPolicy = $this->findTableRLSPolicy($table);
$olderPolicyExists = $tableRLSPolicy && $tableRLSPolicy->policyname !== $expectedName;
$tableRLSPolicy = $this->findTableRLSPolicy($table, $type);
$olderPolicyExists = $tableRLSPolicy && $tableRLSPolicy->policyname !== $expectedName;

// Drop the policy if an outdated version exists
// or if it exists (even in the current form) and the --force option is used
$dropPolicy = $olderPolicyExists || ($tableRLSPolicy && $this->option('force'));
// Drop the policy if an outdated version exists
// or if it exists (even in the current form) and the --force option is used
$dropPolicy = $olderPolicyExists || ($tableRLSPolicy && $this->option('force'));

if ($tableRLSPolicy && $dropPolicy) {
DB::statement("DROP POLICY {$tableRLSPolicy->policyname} ON {$table}");
if ($tableRLSPolicy && $dropPolicy) {
DB::statement("DROP POLICY {$tableRLSPolicy->policyname} ON {$table}");

$this->components->info("RLS policy for table '{$table}' dropped.");
}
$this->components->info("RLS policy for table '{$table}' for '{$type}' dropped.");
}

// Create RLS policy if the table doesn't have it or if the --force option is used
$createPolicy = $dropPolicy || ! $tableRLSPolicy || $this->option('force');
// Create RLS policy if the table doesn't have it or if the --force option is used
$createPolicy = $dropPolicy || ! $tableRLSPolicy || $this->option('force');

if ($createPolicy) {
DB::statement($policyQuery);
if ($createPolicy) {
DB::statement($policyQuery);

$this->enableRLS($table);
$this->enableRLS($table);

$createdPolicies[] = $table . " ($hash)";
} else {
$this->components->info("Table '{$table}' already has an up to date RLS policy.");
$createdPolicies[] = $table . " ($hash)";
} else {
$this->components->info("Table '{$table}' for '{$type}' already has an up to date RLS policy.");
}
}
}

Expand All @@ -143,19 +145,19 @@

$this->components->bulletList($createdPolicies);

$this->components->success('RLS policies updated successfully.');
$this->components->info('RLS policies updated successfully.');
} else {
$this->components->success('All RLS policies are up to date.');
$this->components->info('All RLS policies are up to date.');
}
}

/** @return \stdClass|null */
protected function findTableRLSPolicy(string $table): object|null
protected function findTableRLSPolicy(string $table, string $type): object|null
{
return DB::selectOne(<<<SQL
SELECT * FROM pg_policies
WHERE tablename = '{$table}'
AND policyname LIKE '{$table}_rls_policy%';
AND policyname LIKE '{$table}_{$type}_rls_policy%';
SQL);
}

Expand Down
13 changes: 10 additions & 3 deletions src/RLS/PolicyManagers/TableRLSManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Stancl\Tenancy\RLS\PolicyManagers;

use Illuminate\Contracts\Config\Repository;
use Illuminate\Database\DatabaseManager;
use Stancl\Tenancy\Database\Exceptions\RecursiveRelationshipException;

Expand All @@ -13,18 +14,23 @@
public static bool $scopeByDefault = true;

public function __construct(
protected DatabaseManager $database
protected DatabaseManager $database,
protected Repository $config
) {}

public function generateQueries(array $trees = []): array
{
$queries = [];
$centralUserName = $this->database->getConfig('username');

foreach ($trees ?: $this->shortestPaths() as $table => $path) {
$queries[$table] = $this->generateQuery($table, $path);
$queries[$table] = [
'tenant' => $this->generateQuery($table, $path),
'central' => "CREATE POLICY {$table}_central_rls_policy ON {$table} AS PERMISSIVE TO {$centralUserName} USING (true);",
];
}

return $queries;

Check failure on line 33 in src/RLS/PolicyManagers/TableRLSManager.php

View workflow job for this annotation

GitHub Actions / Static analysis (PHPStan)

Method Stancl\Tenancy\RLS\PolicyManagers\TableRLSManager::generateQueries() should return array<string> but returns array<array<string, string>>.
}

/**
Expand Down Expand Up @@ -209,8 +215,9 @@
/** Generates a query that creates a row-level security policy for the passed table. */
protected function generateQuery(string $table, array $path): string
{
$role = $this->config->get('tenancy.rls.user.username');
// Generate the SQL conditions recursively
$query = "CREATE POLICY {$table}_rls_policy ON {$table} USING (\n";
$query = "CREATE POLICY {$table}_tenant_rls_policy ON {$table} TO {$role} USING (\n";
$sessionTenantKey = config('tenancy.rls.session_variable_name');

foreach ($path as $index => $relation) {
Expand Down
Loading