1
0
Fork 0
mirror of https://github.com/archtechx/tenancy.git synced 2025-12-13 21:04:03 +00:00

Postgres RLS + permission controlled database managers (#33)

This PR adds Postgres RLS (trait manager + table manager approach) and permission controlled managers for PostgreSQL.

---------

Co-authored-by: lukinovec <lukinovec@gmail.com>
Co-authored-by: PHP CS Fixer <phpcsfixer@example.com>
This commit is contained in:
Samuel Štancl 2024-04-24 22:32:49 +02:00 committed by GitHub
parent 34297d3e1a
commit 7317d2638a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
39 changed files with 2511 additions and 112 deletions

View file

@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace Stancl\Tenancy\Database\TenantDatabaseManagers;
use Stancl\Tenancy\Database\Concerns\CreatesDatabaseUsers;
use Stancl\Tenancy\Database\Concerns\ManagesPostgresUsers;
use Stancl\Tenancy\Database\Contracts\ManagesDatabaseUsers;
use Stancl\Tenancy\Database\DatabaseConfig;
class PermissionControlledPostgreSQLDatabaseManager extends PostgreSQLDatabaseManager implements ManagesDatabaseUsers
{
use CreatesDatabaseUsers, ManagesPostgresUsers;
protected function grantPermissions(DatabaseConfig $databaseConfig): bool
{
// Tenant DB config
$database = $databaseConfig->getName();
$username = $databaseConfig->getUsername();
$schema = $databaseConfig->connection()['search_path'];
// Host config
$connectionName = $this->database()->getConfig('name');
$centralDatabase = $this->database()->getConfig('database');
$this->database()->statement("GRANT CONNECT ON DATABASE \"{$database}\" TO \"{$username}\"");
// Connect to tenant database
config(["database.connections.{$connectionName}.database" => $database]);
$this->database()->reconnect();
// Grant permissions to create and use tables in the configured schema ("public" by default) to the user
$this->database()->statement("GRANT USAGE, CREATE ON SCHEMA {$schema} TO \"{$username}\"");
// Grant permissions to use sequences in the current schema to the user
$this->database()->statement("GRANT USAGE ON ALL SEQUENCES IN SCHEMA {$schema} TO \"{$username}\"");
// Reconnect to central database
config(["database.connections.{$connectionName}.database" => $centralDatabase]);
$this->database()->reconnect();
return true;
}
}

View file

@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace Stancl\Tenancy\Database\TenantDatabaseManagers;
use Illuminate\Support\Facades\DB;
use Stancl\Tenancy\Database\Concerns\CreatesDatabaseUsers;
use Stancl\Tenancy\Database\Concerns\ManagesPostgresUsers;
use Stancl\Tenancy\Database\Contracts\ManagesDatabaseUsers;
use Stancl\Tenancy\Database\DatabaseConfig;
class PermissionControlledPostgreSQLSchemaManager extends PostgreSQLSchemaManager implements ManagesDatabaseUsers
{
use CreatesDatabaseUsers, ManagesPostgresUsers;
protected function grantPermissions(DatabaseConfig $databaseConfig): bool
{
// Tenant DB config
$username = $databaseConfig->getUsername();
$schema = $databaseConfig->getName();
// Central database name
$database = DB::connection(config('tenancy.database.central_connection'))->getDatabaseName();
$this->database()->statement("GRANT CONNECT ON DATABASE {$database} TO \"{$username}\"");
$this->database()->statement("GRANT USAGE, CREATE ON SCHEMA \"{$schema}\" TO \"{$username}\"");
$this->database()->statement("GRANT USAGE ON ALL SEQUENCES IN SCHEMA \"{$schema}\" TO \"{$username}\"");
$tables = $this->database()->select("SELECT table_name FROM information_schema.tables WHERE table_schema = '{$schema}'");
// Grant permissions to any existing tables. This is used with RLS
// todo@samuel refactor this along with the todo in TenantDatabaseManager
// and move the grantPermissions() call inside the condition in `ManagesPostgresUsers::createUser()`
foreach ($tables as $table) {
$tableName = $table->table_name;
/** @var string $primaryKey */
$primaryKey = $this->database()->selectOne(<<<SQL
SELECT column_name
FROM information_schema.key_column_usage
WHERE table_name = '{$tableName}'
AND constraint_name LIKE '%_pkey'
SQL)->column_name;
// Grant all permissions for all existing tables
$this->database()->statement("GRANT ALL ON \"{$schema}\".\"{$tableName}\" TO \"{$username}\"");
// Grant permission to reference the primary key for the table
// The previous query doesn't grant the references privilege, so it has to be granted here
$this->database()->statement("GRANT REFERENCES (\"{$primaryKey}\") ON \"{$schema}\".\"{$tableName}\" TO \"{$username}\"");
}
return true;
}
}

View file

@ -20,6 +20,6 @@ class PostgreSQLDatabaseManager extends TenantDatabaseManager
public function databaseExists(string $name): bool
{
return (bool) $this->database()->select("SELECT datname FROM pg_database WHERE datname = '$name'");
return (bool) $this->database()->selectOne("SELECT datname FROM pg_database WHERE datname = '$name'");
}
}