mirror of
https://github.com/archtechx/tenancy.git
synced 2026-05-06 16:24:03 +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:
parent
ad7d229daf
commit
bdf592c0ff
10 changed files with 67 additions and 11 deletions
|
|
@ -28,6 +28,9 @@ trait ManagesPostgresUsers
|
||||||
$username = $databaseConfig->getUsername();
|
$username = $databaseConfig->getUsername();
|
||||||
$password = $databaseConfig->getPassword();
|
$password = $databaseConfig->getPassword();
|
||||||
|
|
||||||
|
// todo@validation password
|
||||||
|
$this->validateParameter($username);
|
||||||
|
|
||||||
$createUser = ! $this->userExists($username);
|
$createUser = ! $this->userExists($username);
|
||||||
|
|
||||||
if ($createUser) {
|
if ($createUser) {
|
||||||
|
|
@ -42,7 +45,7 @@ trait ManagesPostgresUsers
|
||||||
public function deleteUser(DatabaseConfig $databaseConfig): bool
|
public function deleteUser(DatabaseConfig $databaseConfig): bool
|
||||||
{
|
{
|
||||||
// Tenant DB username
|
// Tenant DB username
|
||||||
$username = $databaseConfig->getUsername();
|
$username = $this->validateParameter($databaseConfig->getUsername());
|
||||||
|
|
||||||
// Tenant host connection config
|
// Tenant host connection config
|
||||||
$connectionName = $this->connection()->getConfig('name');
|
$connectionName = $this->connection()->getConfig('name');
|
||||||
|
|
|
||||||
|
|
@ -10,14 +10,16 @@ class MicrosoftSQLDatabaseManager extends TenantDatabaseManager
|
||||||
{
|
{
|
||||||
public function createDatabase(TenantWithDatabase $tenant): bool
|
public function createDatabase(TenantWithDatabase $tenant): bool
|
||||||
{
|
{
|
||||||
$database = $tenant->database()->getName();
|
$database = $this->validateParameter($tenant->database()->getName());
|
||||||
|
|
||||||
return $this->connection()->statement("CREATE DATABASE [{$database}]");
|
return $this->connection()->statement("CREATE DATABASE [{$database}]");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function deleteDatabase(TenantWithDatabase $tenant): bool
|
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
|
public function databaseExists(string $name): bool
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,16 @@ class MySQLDatabaseManager extends TenantDatabaseManager
|
||||||
$charset = $this->connection()->getConfig('charset');
|
$charset = $this->connection()->getConfig('charset');
|
||||||
$collation = $this->connection()->getConfig('collation');
|
$collation = $this->connection()->getConfig('collation');
|
||||||
|
|
||||||
|
$this->validateParameter([$database, $charset, $collation]);
|
||||||
|
|
||||||
return $this->connection()->statement("CREATE DATABASE `{$database}` CHARACTER SET `$charset` COLLATE `$collation`");
|
return $this->connection()->statement("CREATE DATABASE `{$database}` CHARACTER SET `$charset` COLLATE `$collation`");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function deleteDatabase(TenantWithDatabase $tenant): bool
|
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
|
public function databaseExists(string $name): bool
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,9 @@ class PermissionControlledMicrosoftSQLServerDatabaseManager extends MicrosoftSQL
|
||||||
$username = $databaseConfig->getUsername();
|
$username = $databaseConfig->getUsername();
|
||||||
$password = $databaseConfig->getPassword();
|
$password = $databaseConfig->getPassword();
|
||||||
|
|
||||||
|
// todo@validation password
|
||||||
|
$this->validateParameter([$database, $username]);
|
||||||
|
|
||||||
// Create login
|
// Create login
|
||||||
$this->connection()->statement("CREATE LOGIN [$username] WITH PASSWORD = '$password'");
|
$this->connection()->statement("CREATE LOGIN [$username] WITH PASSWORD = '$password'");
|
||||||
|
|
||||||
|
|
@ -37,7 +40,9 @@ class PermissionControlledMicrosoftSQLServerDatabaseManager extends MicrosoftSQL
|
||||||
|
|
||||||
public function deleteUser(DatabaseConfig $databaseConfig): bool
|
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
|
public function userExists(string $username): bool
|
||||||
|
|
@ -54,11 +59,13 @@ class PermissionControlledMicrosoftSQLServerDatabaseManager extends MicrosoftSQL
|
||||||
|
|
||||||
public function deleteDatabase(TenantWithDatabase $tenant): bool
|
public function deleteDatabase(TenantWithDatabase $tenant): bool
|
||||||
{
|
{
|
||||||
|
$name = $this->validateParameter($tenant->database()->getName());
|
||||||
|
|
||||||
// Close all connections to the database before deleting it
|
// Close all connections to the database before deleting it
|
||||||
// Set the database to SINGLE_USER mode to ensure that
|
// Set the database to SINGLE_USER mode to ensure that
|
||||||
// No other connections are using the database while we're trying to delete it
|
// No other connections are using the database while we're trying to delete it
|
||||||
// Rollback all active transactions
|
// 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);
|
return parent::deleteDatabase($tenant);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,9 @@ class PermissionControlledMySQLDatabaseManager extends MySQLDatabaseManager impl
|
||||||
$username = $databaseConfig->getUsername();
|
$username = $databaseConfig->getUsername();
|
||||||
$password = $databaseConfig->getPassword();
|
$password = $databaseConfig->getPassword();
|
||||||
|
|
||||||
|
//todo@validation password
|
||||||
|
$this->validateParameter([$database, $username]);
|
||||||
|
|
||||||
$this->connection()->statement("CREATE USER `{$username}`@`%` IDENTIFIED BY '{$password}'");
|
$this->connection()->statement("CREATE USER `{$username}`@`%` IDENTIFIED BY '{$password}'");
|
||||||
|
|
||||||
$grants = implode(', ', static::$grants);
|
$grants = implode(', ', static::$grants);
|
||||||
|
|
@ -48,7 +51,9 @@ class PermissionControlledMySQLDatabaseManager extends MySQLDatabaseManager impl
|
||||||
|
|
||||||
public function deleteUser(DatabaseConfig $databaseConfig): bool
|
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
|
public function userExists(string $username): bool
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@ class PermissionControlledPostgreSQLDatabaseManager extends PostgreSQLDatabaseMa
|
||||||
$username = $databaseConfig->getUsername();
|
$username = $databaseConfig->getUsername();
|
||||||
$schema = $databaseConfig->connection()['search_path'];
|
$schema = $databaseConfig->connection()['search_path'];
|
||||||
|
|
||||||
|
$this->validateParameter([$database, $username, $schema]);
|
||||||
|
|
||||||
// Host config
|
// Host config
|
||||||
$connectionName = $this->connection()->getConfig('name');
|
$connectionName = $this->connection()->getConfig('name');
|
||||||
$centralDatabase = $this->connection()->getConfig('database');
|
$centralDatabase = $this->connection()->getConfig('database');
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ class PermissionControlledPostgreSQLSchemaManager extends PostgreSQLSchemaManage
|
||||||
// Central database name
|
// Central database name
|
||||||
$database = DB::connection(config('tenancy.database.central_connection'))->getDatabaseName();
|
$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 CONNECT ON DATABASE {$database} TO \"{$username}\"");
|
||||||
$this->connection()->statement("GRANT USAGE, CREATE ON SCHEMA \"{$schema}\" 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}\"");
|
$this->connection()->statement("GRANT USAGE ON ALL SEQUENCES IN SCHEMA \"{$schema}\" TO \"{$username}\"");
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,16 @@ class PostgreSQLDatabaseManager extends TenantDatabaseManager
|
||||||
{
|
{
|
||||||
public function createDatabase(TenantWithDatabase $tenant): bool
|
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
|
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
|
public function databaseExists(string $name): bool
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,16 @@ class PostgreSQLSchemaManager extends TenantDatabaseManager
|
||||||
{
|
{
|
||||||
public function createDatabase(TenantWithDatabase $tenant): bool
|
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
|
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
|
public function databaseExists(string $name): bool
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,15 @@ namespace Stancl\Tenancy\Database\TenantDatabaseManagers;
|
||||||
|
|
||||||
use Illuminate\Database\Connection;
|
use Illuminate\Database\Connection;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use InvalidArgumentException;
|
||||||
use Stancl\Tenancy\Database\Contracts\StatefulTenantDatabaseManager;
|
use Stancl\Tenancy\Database\Contracts\StatefulTenantDatabaseManager;
|
||||||
use Stancl\Tenancy\Database\Exceptions\NoConnectionSetException;
|
use Stancl\Tenancy\Database\Exceptions\NoConnectionSetException;
|
||||||
|
|
||||||
abstract class TenantDatabaseManager implements StatefulTenantDatabaseManager
|
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. */
|
/** The database connection to the server. */
|
||||||
protected string $connection;
|
protected string $connection;
|
||||||
|
|
||||||
|
|
@ -34,4 +38,23 @@ abstract class TenantDatabaseManager implements StatefulTenantDatabaseManager
|
||||||
|
|
||||||
return $baseConfig;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue