[DisallowSqliteAttach::class]]); config(['tenancy.bootstrappers' => [DatabaseTenancyBootstrapper::class]]); Event::listen(TenancyInitialized::class, BootstrapTenancy::class); Event::listen(TenancyEnded::class, RevertToCentralContext::class); Event::listen(TenantCreated::class, JobPipeline::make([ CreateDatabase::class, MigrateDatabase::class, ])->send(function (TenantCreated $event) { return $event->tenant; })->toListener()); $tempdb1 = tempnam(sys_get_temp_dir(), 'tenancy_attach_test'); $tempdb2 = tempnam(sys_get_temp_dir(), 'tenancy_attach_test'); register_shutdown_function(fn () => @unlink($tempdb1)); register_shutdown_function(fn () => @unlink($tempdb2)); config(['database.connections.foo' => ['driver' => 'sqlite', 'database' => $tempdb1]]); config(['database.connections.bar' => ['driver' => 'sqlite', 'database' => $tempdb2]]); DB::connection('bar')->statement('CREATE TABLE secrets (key, value)'); DB::connection('bar')->statement('INSERT INTO secrets (key, value) VALUES ("secret_foo", "secret_bar")'); Route::post('/central-sqli', function () { DB::connection('foo')->select(request('q1')); return json_encode(DB::connection('foo')->select(request('q2'))); }); Route::middleware(InitializeTenancyByPath::class)->post('/{tenant}/tenant-sqli', function () { DB::select(request('q1')); return json_encode(DB::select(request('q2'))); }); tenancy()->bootstrapFeatures(); if ($disallow) { expect(fn () => pest()->post('/central-sqli', [ 'q1' => 'ATTACH DATABASE "' . $tempdb2 . '" as bar', 'q2' => 'SELECT * from bar.secrets', ])->json())->toThrow(QueryException::class, 'not authorized'); } else { expect(pest()->post('/central-sqli', [ 'q1' => 'ATTACH DATABASE "' . $tempdb2 . '" as bar', 'q2' => 'SELECT * from bar.secrets', ])->json()[0])->toBe([ 'key' => 'secret_foo', 'value' => 'secret_bar', ]); } $tenant = Tenant::create([ 'tenancy_db_connection' => 'sqlite', ]); if ($disallow) { expect(fn () => pest()->post($tenant->id . '/tenant-sqli', [ 'q1' => 'ATTACH DATABASE "' . $tempdb2 . '" as baz', 'q2' => 'SELECT * from bar.secrets', ])->json())->toThrow(QueryException::class, 'not authorized'); } else { expect(pest()->post($tenant->id . '/tenant-sqli', [ 'q1' => 'ATTACH DATABASE "' . $tempdb2 . '" as baz', 'q2' => 'SELECT * from baz.secrets', ])->json()[0])->toBe([ 'key' => 'secret_foo', 'value' => 'secret_bar', ]); } })->with([true, false]);