From 0d177c7e9bcf09a2d7025ffb863575fe01340f47 Mon Sep 17 00:00:00 2001 From: lukinovec Date: Fri, 12 Jun 2026 15:34:39 +0200 Subject: [PATCH] Use simple tenants table check during hardening For a niche opt-in feature, checking if the tenants table is in the current schema is good enough for determining if the current DB is central -- the solution where we added getCurrentDatabaseName to each manager was overly complicated for this (though a bit more correct, not worth the added complexity). --- .../DatabaseTenancyBootstrapper.php | 15 +++-------- .../Contracts/TenantDatabaseManager.php | 10 ------- .../PostgreSQLSchemaManager.php | 7 ----- .../SQLiteDatabaseManager.php | 6 ----- .../TenantDatabaseManager.php | 5 ---- .../DatabaseTenancyBootstrapperTest.php | 26 ++++++++++++++----- 6 files changed, 24 insertions(+), 45 deletions(-) diff --git a/src/Bootstrappers/DatabaseTenancyBootstrapper.php b/src/Bootstrappers/DatabaseTenancyBootstrapper.php index c09803c6..08b36f1d 100644 --- a/src/Bootstrappers/DatabaseTenancyBootstrapper.php +++ b/src/Bootstrappers/DatabaseTenancyBootstrapper.php @@ -5,7 +5,7 @@ declare(strict_types=1); namespace Stancl\Tenancy\Bootstrappers; use Exception; -use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Schema; use RuntimeException; use Stancl\Tenancy\Contracts\TenancyBootstrapper; use Stancl\Tenancy\Contracts\Tenant; @@ -90,8 +90,7 @@ class DatabaseTenancyBootstrapper implements TenancyBootstrapper protected function verifyTenantCanUseDatabase(Tenant $tenant): void { /** @var \Stancl\Tenancy\Database\Models\Tenant&TenantWithDatabase $tenant */ - $tenantDbConfig = $tenant->database(); - $tenantDbName = $tenantDbConfig->getName(); + $tenantDbName = $tenant->database()->getName(); // Check that no other tenant uses this tenant's database if ($tenant::where($tenant->getTenantKeyName(), '!=', $tenant->getTenantKey()) @@ -100,14 +99,8 @@ class DatabaseTenancyBootstrapper implements TenancyBootstrapper throw new RuntimeException('Tenant cannot use a database of another tenant.'); } - $manager = $tenantDbConfig->manager(); - - $centralConnection = DB::connection(config('tenancy.database.central_connection', 'central')); - $currentConnection = DB::connection(); - - // Throw if the current database/schema is central. - // At this point the connection should be the tenant's, so it should not match central. - if ($manager->getCurrentDatabaseName($currentConnection) === $manager->getCurrentDatabaseName($centralConnection)) { + if (Schema::hasTable($tenant->getTable())) { + // Throw if the current database/schema has the tenants table (i.e. it's not central) throw new RuntimeException('Tenant cannot use the central database.'); } } diff --git a/src/Database/Contracts/TenantDatabaseManager.php b/src/Database/Contracts/TenantDatabaseManager.php index f1ac6d5f..8b5007b9 100644 --- a/src/Database/Contracts/TenantDatabaseManager.php +++ b/src/Database/Contracts/TenantDatabaseManager.php @@ -4,8 +4,6 @@ declare(strict_types=1); namespace Stancl\Tenancy\Database\Contracts; -use Illuminate\Database\Connection; - interface TenantDatabaseManager { /** Create a database. */ @@ -19,12 +17,4 @@ interface TenantDatabaseManager /** Construct a DB connection config array. */ public function makeConnectionConfig(array $baseConfig, string $databaseName): array; - - /** - * Get the schema/database name that the given connection points to. - * - * In database managers, this should return the *database* name of the passed connection, - * while in schema managers, this should return the *schema* name of the passed connection. - */ - public function getCurrentDatabaseName(Connection $connection): string; } diff --git a/src/Database/TenantDatabaseManagers/PostgreSQLSchemaManager.php b/src/Database/TenantDatabaseManagers/PostgreSQLSchemaManager.php index fdb294a2..354eb768 100644 --- a/src/Database/TenantDatabaseManagers/PostgreSQLSchemaManager.php +++ b/src/Database/TenantDatabaseManagers/PostgreSQLSchemaManager.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace Stancl\Tenancy\Database\TenantDatabaseManagers; -use Illuminate\Database\Connection; use Stancl\Tenancy\Database\Contracts\TenantWithDatabase; class PostgreSQLSchemaManager extends TenantDatabaseManager @@ -38,10 +37,4 @@ class PostgreSQLSchemaManager extends TenantDatabaseManager return $baseConfig; } - - public function getCurrentDatabaseName(Connection $connection): string - { - // Get the *schema* name (not the database name) - return $connection->selectOne('SELECT current_schema() AS schema')->schema; - } } diff --git a/src/Database/TenantDatabaseManagers/SQLiteDatabaseManager.php b/src/Database/TenantDatabaseManagers/SQLiteDatabaseManager.php index 9d749044..ce3582c0 100644 --- a/src/Database/TenantDatabaseManagers/SQLiteDatabaseManager.php +++ b/src/Database/TenantDatabaseManagers/SQLiteDatabaseManager.php @@ -5,7 +5,6 @@ declare(strict_types=1); namespace Stancl\Tenancy\Database\TenantDatabaseManagers; use Closure; -use Illuminate\Database\Connection; use Illuminate\Database\Eloquent\Model; use InvalidArgumentException; use PDO; @@ -150,11 +149,6 @@ class SQLiteDatabaseManager implements TenantDatabaseManager return $baseConfig; } - public function getCurrentDatabaseName(Connection $connection): string - { - return $connection->getDatabaseName(); - } - public function getPath(string $name): string { $this->validateDatabaseName($name); diff --git a/src/Database/TenantDatabaseManagers/TenantDatabaseManager.php b/src/Database/TenantDatabaseManagers/TenantDatabaseManager.php index 0dc4e642..a0822615 100644 --- a/src/Database/TenantDatabaseManagers/TenantDatabaseManager.php +++ b/src/Database/TenantDatabaseManagers/TenantDatabaseManager.php @@ -37,9 +37,4 @@ abstract class TenantDatabaseManager implements StatefulTenantDatabaseManager return $baseConfig; } - - public function getCurrentDatabaseName(Connection $connection): string - { - return $connection->getDatabaseName(); - } } diff --git a/tests/Bootstrappers/DatabaseTenancyBootstrapperTest.php b/tests/Bootstrappers/DatabaseTenancyBootstrapperTest.php index 8fd123b7..fe09fca6 100644 --- a/tests/Bootstrappers/DatabaseTenancyBootstrapperTest.php +++ b/tests/Bootstrappers/DatabaseTenancyBootstrapperTest.php @@ -39,10 +39,17 @@ test('harden prevents tenants from using the central database', function (bool $ "tenancy.database.managers.{$connection}" => $manager, ]); - // Set up and migrate the central database + // Point the central connection at the tested connection's config and migrate it + // (so that the central database/schema contains the tenants table). $centralConnection = config('tenancy.database.central_connection'); + $centralConfig = config("database.connections.{$connection}"); + + if ($connection === 'sqlite') { + $centralConfig['database'] = database_path($sqliteCentralDb = 'central.sqlite'); + } + DB::purge($centralConnection); - config(["database.connections.{$centralConnection}" => config("database.connections.{$connection}")]); + config(["database.connections.{$centralConnection}" => $centralConfig]); pest()->artisan('migrate:fresh', [ '--force' => true, @@ -56,11 +63,18 @@ test('harden prevents tenants from using the central database', function (bool $ return $event->tenant; })->toListener()); - // Create the tenant with its own database, then repoint it at the central database/schema. + // Create the tenant with its own database, then repoint it at the central database/schema + // (which contains the tenants table that the hardening check looks for). $tenant = Tenant::create(['tenancy_db_connection' => $connection]); - $tenant->update([ - 'tenancy_db_name' => $tenant->database()->manager()->getCurrentDatabaseName(DB::connection($centralConnection)), - ]); + + $central = DB::connection($centralConnection); + $centralName = match (true) { + $manager === PostgreSQLSchemaManager::class => $central->selectOne('SELECT current_schema() AS schema')->schema, // Central schema name + $connection === 'sqlite' => $sqliteCentralDb, // Central SQLite DB name + default => $central->getDatabaseName(), // Central DB name + }; + + $tenant->update(['tenancy_db_name' => $centralName]); if ($harden) { // Harden blocks initialization for tenants that use the central database