1
0
Fork 0
mirror of https://github.com/archtechx/tenancy.git synced 2026-06-21 18:44:06 +00:00

refactor: only accept single values in validateParameter()

this is to make handling null easier (previous Arr::wrap() approach
turned null into an empty array rather than [null] requiring two
separate null checks) and testing easier (we use empty arrays as
examples of invalid values in some tests which would previously be
accepted when passed individually as validateParameter([]) rather than
being part of a wider [something, [], ...] array)

also restrict passing null to validatePassword()

also minor grammar fix in the validateParameter() docblock
This commit is contained in:
Samuel Stancl 2026-06-09 18:38:56 -07:00
parent 0fdc59dc5d
commit a7aa03158d
No known key found for this signature in database
GPG key ID: BA146259A1E16C57
6 changed files with 33 additions and 27 deletions

View file

@ -38,23 +38,16 @@ trait ValidatesDatabaseParameters
public static string $allowedPasswordCharacters = ' !#$%&()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_abcdefghijklmnopqrstuvwxyz{|}~'; public static string $allowedPasswordCharacters = ' !#$%&()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_abcdefghijklmnopqrstuvwxyz{|}~';
/** /**
* Ensure that parameters (database names, usernames, etc.) * Ensure that parameter (database name, username, etc.)
* only contain allowed characters before used in SQL statements * only contains allowed characters before being used in SQL statements
* (or paths in the case of SQLiteDatabaseManager). * (or paths in the case of SQLiteDatabaseManager).
* *
* By default, only the characters in allowedParameterCharacters() are allowed. * By default, only the characters in allowedParameterCharacters() are allowed.
* *
* @throws InvalidArgumentException * @throws InvalidArgumentException
*/ */
protected function validateParameter(string|array|null $parameters, string|null $allowedCharacters = null): void protected function validateParameter(mixed $parameter, string|null $allowedCharacters = null): void
{ {
if ($parameters === null) {
throw new InvalidArgumentException('Parameter cannot be null.');
}
$allowedCharacters ??= static::$allowedParameterCharacters;
foreach (Arr::wrap($parameters) as $parameter) {
if (is_null($parameter)) { if (is_null($parameter)) {
throw new InvalidArgumentException('Parameter cannot be null.'); throw new InvalidArgumentException('Parameter cannot be null.');
} }
@ -68,13 +61,14 @@ trait ValidatesDatabaseParameters
throw new InvalidArgumentException('Parameter has to be a string.'); throw new InvalidArgumentException('Parameter has to be a string.');
} }
$allowedCharacters ??= static::$allowedParameterCharacters;
foreach (str_split($parameter) as $character) { foreach (str_split($parameter) as $character) {
if (! str_contains($allowedCharacters, $character)) { if (! str_contains($allowedCharacters, $character)) {
throw new InvalidArgumentException("Forbidden character '{$character}' in parameter."); throw new InvalidArgumentException("Forbidden character '{$character}' in parameter.");
} }
} }
} }
}
/** /**
* Ensure password only contains allowed characters (allowedPasswordCharacters()) * Ensure password only contains allowed characters (allowedPasswordCharacters())
@ -87,6 +81,10 @@ trait ValidatesDatabaseParameters
*/ */
protected function validatePassword(string|null $password): void protected function validatePassword(string|null $password): void
{ {
if (is_null($password)) {
throw new InvalidArgumentException('Parameter cannot be null.');
}
$this->validateParameter($password, allowedCharacters: static::$allowedPasswordCharacters); $this->validateParameter($password, allowedCharacters: static::$allowedPasswordCharacters);
} }
} }

View file

@ -14,7 +14,7 @@ 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(array_filter([$database, $charset, $collation], fn ($param) => $param !== null)); $this->validateParameter($database);
// MySQL defaults to the server's charset and collation // MySQL defaults to the server's charset and collation
// if charset and collation are not specified. // if charset and collation are not specified.
@ -23,10 +23,12 @@ class MySQLDatabaseManager extends TenantDatabaseManager
$statement = "CREATE DATABASE `{$database}`"; $statement = "CREATE DATABASE `{$database}`";
if ($charset !== null) { if ($charset !== null) {
$this->validateParameter($charset);
$statement .= " CHARACTER SET `{$charset}`"; $statement .= " CHARACTER SET `{$charset}`";
} }
if ($collation !== null) { if ($collation !== null) {
$this->validateParameter($collation);
$statement .= " COLLATE `{$collation}`"; $statement .= " COLLATE `{$collation}`";
} }

View file

@ -24,7 +24,8 @@ class PermissionControlledMicrosoftSQLServerDatabaseManager extends MicrosoftSQL
$username = $databaseConfig->getUsername(); $username = $databaseConfig->getUsername();
$password = $databaseConfig->getPassword(); $password = $databaseConfig->getPassword();
$this->validateParameter([$database, $username]); $this->validateParameter($database);
$this->validateParameter($username);
$this->validatePassword($password); $this->validatePassword($password);
// Create login // Create login

View file

@ -25,7 +25,8 @@ class PermissionControlledMySQLDatabaseManager extends MySQLDatabaseManager impl
$username = $databaseConfig->getUsername(); $username = $databaseConfig->getUsername();
$password = $databaseConfig->getPassword(); $password = $databaseConfig->getPassword();
$this->validateParameter([$database, $username]); $this->validateParameter($database);
$this->validateParameter($username);
$this->validatePassword($password); $this->validatePassword($password);
$this->connection()->statement("CREATE USER `{$username}`@`%` IDENTIFIED BY '{$password}'"); $this->connection()->statement("CREATE USER `{$username}`@`%` IDENTIFIED BY '{$password}'");

View file

@ -20,7 +20,9 @@ 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]); $this->validateParameter($database);
$this->validateParameter($username);
$this->validateParameter($schema);
// Host config // Host config
$connectionName = $this->connection()->getConfig('name'); $connectionName = $this->connection()->getConfig('name');

View file

@ -23,7 +23,9 @@ 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->validateParameter($username);
$this->validateParameter($schema);
$this->validateParameter($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}\"");