From 76c324d758539e1d55f25a8b2ac143d740b8138d Mon Sep 17 00:00:00 2001 From: lukinovec Date: Fri, 1 May 2026 09:03:50 +0200 Subject: [PATCH] Add `validateFilename()` Use validateFilename instead of validateParameter in SQLiteDatabaseManager. Directories are no longer considered valid SQLite database names. --- .../Concerns/ValidatesDatabaseParameters.php | 32 +++++++++++++++++-- .../SQLiteDatabaseManager.php | 13 ++------ tests/TenantDatabaseManagerTest.php | 10 +++--- 3 files changed, 37 insertions(+), 18 deletions(-) diff --git a/src/Database/Concerns/ValidatesDatabaseParameters.php b/src/Database/Concerns/ValidatesDatabaseParameters.php index 360f12a6..b261cb4b 100644 --- a/src/Database/Concerns/ValidatesDatabaseParameters.php +++ b/src/Database/Concerns/ValidatesDatabaseParameters.php @@ -28,6 +28,16 @@ trait ValidatesDatabaseParameters return 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-'; } + /** + * Characters allowed in filenames (SQLite databases). + * + * Allows dots to support file extensions (e.g. '.sqlite'). + */ + protected static function filenameAllowlist(): string + { + return 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-.'; + } + /** * Characters allowed in database user passwords. * @@ -66,7 +76,7 @@ trait ValidatesDatabaseParameters foreach (str_split($parameter) as $char) { if (! str_contains($allowlist, $char)) { - throw new InvalidArgumentException("Forbidden character '{$char}' in database parameter."); + throw new InvalidArgumentException("Forbidden character '{$char}' in parameter."); } } } @@ -75,8 +85,8 @@ trait ValidatesDatabaseParameters /** * Ensure password only contains allowed characters before used in SQL statements. * - * Used as a shorthand for calling validateParameter() with the less strict allowlist - * to validate database user passwords. + * Used in permission controlled managers as a shorthand for calling validateParameter() + * with the less strict allowlist to validate database user passwords. * * @throws InvalidArgumentException */ @@ -84,4 +94,20 @@ trait ValidatesDatabaseParameters { $this->validateParameter($password, static::passwordAllowlist()); } + + /** + * Ensure filename only contains allowed characters and is not a directory name + * before used in file paths (e.g. SQLite databases). + * + * @throws InvalidArgumentException + * @see Stancl\Tenancy\Database\TenantDatabaseManagers\SQLiteDatabaseManager + */ + protected function validateFilename(string|null $filename): void + { + if (is_dir($filename)) { + throw new InvalidArgumentException("Filename '{$filename}' is a directory."); + } + + $this->validateParameter($filename, static::filenameAllowlist()); + } } diff --git a/src/Database/TenantDatabaseManagers/SQLiteDatabaseManager.php b/src/Database/TenantDatabaseManagers/SQLiteDatabaseManager.php index 64fb603a..c2c55d87 100644 --- a/src/Database/TenantDatabaseManagers/SQLiteDatabaseManager.php +++ b/src/Database/TenantDatabaseManagers/SQLiteDatabaseManager.php @@ -60,11 +60,6 @@ class SQLiteDatabaseManager implements TenantDatabaseManager */ public static Closure|null $closeInMemoryConnectionUsing = null; - protected static function parameterAllowlist(): string - { - return 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-.'; - } - public function createDatabase(TenantWithDatabase $tenant): bool { /** @var TenantWithDatabase&Model $tenant */ @@ -92,8 +87,6 @@ class SQLiteDatabaseManager implements TenantDatabaseManager return true; } - $this->validateParameter($name); - return file_put_contents($this->getPath($name), '') !== false; } @@ -109,8 +102,6 @@ class SQLiteDatabaseManager implements TenantDatabaseManager return true; } - $this->validateParameter($name); - $path = $this->getPath($name); try { @@ -132,8 +123,6 @@ class SQLiteDatabaseManager implements TenantDatabaseManager return true; } - $this->validateParameter($name); - return file_exists($this->getPath($name)); } @@ -159,6 +148,8 @@ class SQLiteDatabaseManager implements TenantDatabaseManager return rtrim(static::$path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $name; } + $this->validateFilename($name); + return database_path($name); } diff --git a/tests/TenantDatabaseManagerTest.php b/tests/TenantDatabaseManagerTest.php index b5c7f7d7..f28ad8c4 100644 --- a/tests/TenantDatabaseManagerTest.php +++ b/tests/TenantDatabaseManagerTest.php @@ -615,15 +615,17 @@ test('database managers validate parameters that cannot be bound', function ($dr } })->with('database_managers'); -test('sqlite database manager validates the name in databaseExists', function () { +test('sqlite database manager validates database filenames', function () { $manager = app(SQLiteDatabaseManager::class); - expect(fn () => $manager->databaseExists("../invalid-db-name.sqlite")) - ->toThrow(InvalidArgumentException::class); - + // Dots are allowed in database names expect(fn () => $manager->databaseExists('valid-db_name.sqlite')) ->not()->toThrow(InvalidArgumentException::class); + // Directories are not allowed as database names + expect(fn () => $manager->databaseExists("..")) + ->toThrow(InvalidArgumentException::class); + // In-memory database names aren't validated expect(fn () => $manager->databaseExists('../_tenancy_inmemory_')) ->not()->toThrow(InvalidArgumentException::class);