1
0
Fork 0
mirror of https://github.com/archtechx/tenancy.git synced 2026-05-06 20:34:04 +00:00

Add parameter validation to DB managers

DB manager methods validate the parameters they use in SQL statements using validateParameter() (excluding parameters passed via bindings in SELECT statements).
This commit is contained in:
lukinovec 2026-04-29 14:13:56 +02:00
parent ad7d229daf
commit bdf592c0ff
10 changed files with 67 additions and 11 deletions

View file

@ -28,6 +28,9 @@ trait ManagesPostgresUsers
$username = $databaseConfig->getUsername();
$password = $databaseConfig->getPassword();
// todo@validation password
$this->validateParameter($username);
$createUser = ! $this->userExists($username);
if ($createUser) {
@ -42,7 +45,7 @@ trait ManagesPostgresUsers
public function deleteUser(DatabaseConfig $databaseConfig): bool
{
// Tenant DB username
$username = $databaseConfig->getUsername();
$username = $this->validateParameter($databaseConfig->getUsername());
// Tenant host connection config
$connectionName = $this->connection()->getConfig('name');

View file

@ -10,14 +10,16 @@ class MicrosoftSQLDatabaseManager extends TenantDatabaseManager
{
public function createDatabase(TenantWithDatabase $tenant): bool
{
$database = $tenant->database()->getName();
$database = $this->validateParameter($tenant->database()->getName());
return $this->connection()->statement("CREATE DATABASE [{$database}]");
}
public function deleteDatabase(TenantWithDatabase $tenant): bool
{
return $this->connection()->statement("DROP DATABASE [{$tenant->database()->getName()}]");
$database = $this->validateParameter($tenant->database()->getName());
return $this->connection()->statement("DROP DATABASE [{$database}]");
}
public function databaseExists(string $name): bool

View file

@ -14,12 +14,16 @@ class MySQLDatabaseManager extends TenantDatabaseManager
$charset = $this->connection()->getConfig('charset');
$collation = $this->connection()->getConfig('collation');
$this->validateParameter([$database, $charset, $collation]);
return $this->connection()->statement("CREATE DATABASE `{$database}` CHARACTER SET `$charset` COLLATE `$collation`");
}
public function deleteDatabase(TenantWithDatabase $tenant): bool
{
return $this->connection()->statement("DROP DATABASE `{$tenant->database()->getName()}`");
$database = $this->validateParameter($tenant->database()->getName());
return $this->connection()->statement("DROP DATABASE `{$database}`");
}
public function databaseExists(string $name): bool

View file

@ -24,6 +24,9 @@ class PermissionControlledMicrosoftSQLServerDatabaseManager extends MicrosoftSQL
$username = $databaseConfig->getUsername();
$password = $databaseConfig->getPassword();
// todo@validation password
$this->validateParameter([$database, $username]);
// Create login
$this->connection()->statement("CREATE LOGIN [$username] WITH PASSWORD = '$password'");
@ -37,7 +40,9 @@ class PermissionControlledMicrosoftSQLServerDatabaseManager extends MicrosoftSQL
public function deleteUser(DatabaseConfig $databaseConfig): bool
{
return $this->connection()->statement("DROP LOGIN [{$databaseConfig->getUsername()}]");
$username = $this->validateParameter($databaseConfig->getUsername());
return $this->connection()->statement("DROP LOGIN [{$username}]");
}
public function userExists(string $username): bool
@ -54,11 +59,13 @@ class PermissionControlledMicrosoftSQLServerDatabaseManager extends MicrosoftSQL
public function deleteDatabase(TenantWithDatabase $tenant): bool
{
$name = $this->validateParameter($tenant->database()->getName());
// Close all connections to the database before deleting it
// Set the database to SINGLE_USER mode to ensure that
// No other connections are using the database while we're trying to delete it
// Rollback all active transactions
$this->connection()->statement("ALTER DATABASE [{$tenant->database()->getName()}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;");
$this->connection()->statement("ALTER DATABASE [{$name}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;");
return parent::deleteDatabase($tenant);
}

View file

@ -25,6 +25,9 @@ class PermissionControlledMySQLDatabaseManager extends MySQLDatabaseManager impl
$username = $databaseConfig->getUsername();
$password = $databaseConfig->getPassword();
//todo@validation password
$this->validateParameter([$database, $username]);
$this->connection()->statement("CREATE USER `{$username}`@`%` IDENTIFIED BY '{$password}'");
$grants = implode(', ', static::$grants);
@ -48,7 +51,9 @@ class PermissionControlledMySQLDatabaseManager extends MySQLDatabaseManager impl
public function deleteUser(DatabaseConfig $databaseConfig): bool
{
return $this->connection()->statement("DROP USER IF EXISTS '{$databaseConfig->getUsername()}'");
$username = $this->validateParameter($databaseConfig->getUsername());
return $this->connection()->statement("DROP USER IF EXISTS '{$username}'");
}
public function userExists(string $username): bool

View file

@ -20,6 +20,8 @@ class PermissionControlledPostgreSQLDatabaseManager extends PostgreSQLDatabaseMa
$username = $databaseConfig->getUsername();
$schema = $databaseConfig->connection()['search_path'];
$this->validateParameter([$database, $username, $schema]);
// Host config
$connectionName = $this->connection()->getConfig('name');
$centralDatabase = $this->connection()->getConfig('database');

View file

@ -23,6 +23,8 @@ class PermissionControlledPostgreSQLSchemaManager extends PostgreSQLSchemaManage
// Central database name
$database = DB::connection(config('tenancy.database.central_connection'))->getDatabaseName();
$this->validateParameter([$username, $schema, $database]);
$this->connection()->statement("GRANT CONNECT ON DATABASE {$database} TO \"{$username}\"");
$this->connection()->statement("GRANT USAGE, CREATE ON SCHEMA \"{$schema}\" TO \"{$username}\"");
$this->connection()->statement("GRANT USAGE ON ALL SEQUENCES IN SCHEMA \"{$schema}\" TO \"{$username}\"");

View file

@ -10,12 +10,16 @@ class PostgreSQLDatabaseManager extends TenantDatabaseManager
{
public function createDatabase(TenantWithDatabase $tenant): bool
{
return $this->connection()->statement("CREATE DATABASE \"{$tenant->database()->getName()}\" WITH TEMPLATE=template0");
$name = $this->validateParameter($tenant->database()->getName());
return $this->connection()->statement("CREATE DATABASE \"{$name}\" WITH TEMPLATE=template0");
}
public function deleteDatabase(TenantWithDatabase $tenant): bool
{
return $this->connection()->statement("DROP DATABASE \"{$tenant->database()->getName()}\"");
$name = $this->validateParameter($tenant->database()->getName());
return $this->connection()->statement("DROP DATABASE \"{$name}\"");
}
public function databaseExists(string $name): bool

View file

@ -10,12 +10,16 @@ class PostgreSQLSchemaManager extends TenantDatabaseManager
{
public function createDatabase(TenantWithDatabase $tenant): bool
{
return $this->connection()->statement("CREATE SCHEMA \"{$tenant->database()->getName()}\"");
$name = $this->validateParameter($tenant->database()->getName());
return $this->connection()->statement("CREATE SCHEMA \"{$name}\"");
}
public function deleteDatabase(TenantWithDatabase $tenant): bool
{
return $this->connection()->statement("DROP SCHEMA \"{$tenant->database()->getName()}\" CASCADE");
$name = $this->validateParameter($tenant->database()->getName());
return $this->connection()->statement("DROP SCHEMA \"{$name}\" CASCADE");
}
public function databaseExists(string $name): bool

View file

@ -6,11 +6,15 @@ namespace Stancl\Tenancy\Database\TenantDatabaseManagers;
use Illuminate\Database\Connection;
use Illuminate\Support\Facades\DB;
use InvalidArgumentException;
use Stancl\Tenancy\Database\Contracts\StatefulTenantDatabaseManager;
use Stancl\Tenancy\Database\Exceptions\NoConnectionSetException;
abstract class TenantDatabaseManager implements StatefulTenantDatabaseManager
{
/** Characters allowed in SQL identifiers (database names, usernames, schema names, etc.). */
public static string $allowlist = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-';
/** The database connection to the server. */
protected string $connection;
@ -34,4 +38,23 @@ abstract class TenantDatabaseManager implements StatefulTenantDatabaseManager
return $baseConfig;
}
/**
* Validate that parameters (database names, usernames, etc.)
* contain only allowed characters before used in SQL statements.
*
* @throws InvalidArgumentException
*/
protected function validateParameter(string|array $parameters): string|array
{
foreach ((array) $parameters as $parameter) {
foreach (str_split($parameter) as $char) {
if (! str_contains(static::$allowlist, $char)) {
throw new InvalidArgumentException("Invalid character '{$char}' in SQL parameter: {$parameter}");
}
}
}
return $parameters;
}
}