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,40 +38,34 @@ trait ValidatesDatabaseParameters
public static string $allowedPasswordCharacters = ' !#$%&()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_abcdefghijklmnopqrstuvwxyz{|}~';
/**
* Ensure that parameters (database names, usernames, etc.)
* only contain allowed characters before used in SQL statements
* Ensure that parameter (database name, username, etc.)
* only contains allowed characters before being used in SQL statements
* (or paths in the case of SQLiteDatabaseManager).
*
* By default, only the characters in allowedParameterCharacters() are allowed.
*
* @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) {
if (is_null($parameter)) {
throw new InvalidArgumentException('Parameter cannot be null.');
}
if (is_numeric($parameter)) {
$parameter = (string) $parameter;
}
if (! is_string($parameter)) {
// E.g. if a parameter is retrieved from the config, it isn't necessarily a string
throw new InvalidArgumentException('Parameter has to be a string.');
}
$allowedCharacters ??= static::$allowedParameterCharacters;
foreach (Arr::wrap($parameters) as $parameter) {
if (is_null($parameter)) {
throw new InvalidArgumentException('Parameter cannot be null.');
}
if (is_numeric($parameter)) {
$parameter = (string) $parameter;
}
if (! is_string($parameter)) {
// E.g. if a parameter is retrieved from the config, it isn't necessarily a string
throw new InvalidArgumentException('Parameter has to be a string.');
}
foreach (str_split($parameter) as $character) {
if (! str_contains($allowedCharacters, $character)) {
throw new InvalidArgumentException("Forbidden character '{$character}' in parameter.");
}
foreach (str_split($parameter) as $character) {
if (! str_contains($allowedCharacters, $character)) {
throw new InvalidArgumentException("Forbidden character '{$character}' in parameter.");
}
}
}
@ -87,6 +81,10 @@ trait ValidatesDatabaseParameters
*/
protected function validatePassword(string|null $password): void
{
if (is_null($password)) {
throw new InvalidArgumentException('Parameter cannot be null.');
}
$this->validateParameter($password, allowedCharacters: static::$allowedPasswordCharacters);
}
}

View file

@ -14,7 +14,7 @@ class MySQLDatabaseManager extends TenantDatabaseManager
$charset = $this->connection()->getConfig('charset');
$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
// if charset and collation are not specified.
@ -23,10 +23,12 @@ class MySQLDatabaseManager extends TenantDatabaseManager
$statement = "CREATE DATABASE `{$database}`";
if ($charset !== null) {
$this->validateParameter($charset);
$statement .= " CHARACTER SET `{$charset}`";
}
if ($collation !== null) {
$this->validateParameter($collation);
$statement .= " COLLATE `{$collation}`";
}

View file

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

View file

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

View file

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

View file

@ -23,7 +23,9 @@ class PermissionControlledPostgreSQLSchemaManager extends PostgreSQLSchemaManage
// Central database name
$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 USAGE, CREATE ON SCHEMA \"{$schema}\" TO \"{$username}\"");