From 41d71503d73b0c690763cec3fdadcb2c4277554c Mon Sep 17 00:00:00 2001 From: phenaproxima <phenaproxima@205645.no-reply.drupal.org> Date: Tue, 9 Nov 2021 15:26:08 +0000 Subject: [PATCH] Issue #3226570 by phenaproxima, tedbow: Ensure that SQLite databases are excluded from the staging area --- package_manager/package_manager.services.yml | 1 + .../ExcludedPathsSubscriber.php | 26 ++++- .../fake_site/sites/example.com/db.sqlite | 1 + .../fake_site/sites/example.com/db.sqlite-shm | 1 + .../fake_site/sites/example.com/db.sqlite-wal | 1 + .../src/Functional/ExcludedPathsTest.php | 31 ++++-- .../Kernel/ExcludedPathsSubscriberTest.php | 99 +++++++++++++++++++ .../Kernel/PackageManagerKernelTestBase.php | 26 +++-- 8 files changed, 170 insertions(+), 16 deletions(-) create mode 100644 package_manager/tests/fixtures/fake_site/sites/example.com/db.sqlite create mode 100644 package_manager/tests/fixtures/fake_site/sites/example.com/db.sqlite-shm create mode 100644 package_manager/tests/fixtures/fake_site/sites/example.com/db.sqlite-wal create mode 100644 package_manager/tests/src/Kernel/ExcludedPathsSubscriberTest.php diff --git a/package_manager/package_manager.services.yml b/package_manager/package_manager.services.yml index 59bfe79c0e..63da64d195 100644 --- a/package_manager/package_manager.services.yml +++ b/package_manager/package_manager.services.yml @@ -131,5 +131,6 @@ services: - '%site.path%' - '@file_system' - '@stream_wrapper_manager' + - '@database' tags: - { name: event_subscriber } diff --git a/package_manager/src/EventSubscriber/ExcludedPathsSubscriber.php b/package_manager/src/EventSubscriber/ExcludedPathsSubscriber.php index f7e601c3f0..4663284c79 100644 --- a/package_manager/src/EventSubscriber/ExcludedPathsSubscriber.php +++ b/package_manager/src/EventSubscriber/ExcludedPathsSubscriber.php @@ -2,6 +2,7 @@ namespace Drupal\package_manager\EventSubscriber; +use Drupal\Core\Database\Connection; use Drupal\Core\File\FileSystemInterface; use Drupal\Core\StreamWrapper\LocalStream; use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface; @@ -43,7 +44,14 @@ class ExcludedPathsSubscriber implements EventSubscriberInterface { protected $streamWrapperManager; /** - * Constructs an UpdateSubscriber. + * The database connection. + * + * @var \Drupal\Core\Database\Connection + */ + protected $database; + + /** + * Constructs an ExcludedPathsSubscriber. * * @param string $app_root * The Drupal root. @@ -53,12 +61,15 @@ class ExcludedPathsSubscriber implements EventSubscriberInterface { * The file system service. * @param \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $stream_wrapper_manager * The stream wrapper manager service. + * @param \Drupal\Core\Database\Connection $database + * The database connection. */ - public function __construct(string $app_root, string $site_path, FileSystemInterface $file_system, StreamWrapperManagerInterface $stream_wrapper_manager) { + public function __construct(string $app_root, string $site_path, FileSystemInterface $file_system, StreamWrapperManagerInterface $stream_wrapper_manager, Connection $database) { $this->appRoot = $app_root; $this->sitePath = $site_path; $this->fileSystem = $file_system; $this->streamWrapperManager = $stream_wrapper_manager; + $this->database = $database; } /** @@ -113,6 +124,17 @@ class ExcludedPathsSubscriber implements EventSubscriberInterface { $event->excludePath($this->sitePath . DIRECTORY_SEPARATOR . $settings_file); $event->excludePath($default_site . DIRECTORY_SEPARATOR . $settings_file); } + + // If the database is SQLite, it might be located in the active directory + // and we should not stage it. + if ($this->database->driver() === 'sqlite') { + $options = $this->database->getConnectionOptions(); + $database = str_replace($this->appRoot, NULL, $options['database']); + $database = ltrim($database, '/'); + $event->excludePath($database); + $event->excludePath("$database-shm"); + $event->excludePath("$database-wal"); + } } /** diff --git a/package_manager/tests/fixtures/fake_site/sites/example.com/db.sqlite b/package_manager/tests/fixtures/fake_site/sites/example.com/db.sqlite new file mode 100644 index 0000000000..08874eba8b --- /dev/null +++ b/package_manager/tests/fixtures/fake_site/sites/example.com/db.sqlite @@ -0,0 +1 @@ +This file should never be staged. diff --git a/package_manager/tests/fixtures/fake_site/sites/example.com/db.sqlite-shm b/package_manager/tests/fixtures/fake_site/sites/example.com/db.sqlite-shm new file mode 100644 index 0000000000..08874eba8b --- /dev/null +++ b/package_manager/tests/fixtures/fake_site/sites/example.com/db.sqlite-shm @@ -0,0 +1 @@ +This file should never be staged. diff --git a/package_manager/tests/fixtures/fake_site/sites/example.com/db.sqlite-wal b/package_manager/tests/fixtures/fake_site/sites/example.com/db.sqlite-wal new file mode 100644 index 0000000000..08874eba8b --- /dev/null +++ b/package_manager/tests/fixtures/fake_site/sites/example.com/db.sqlite-wal @@ -0,0 +1 @@ +This file should never be staged. diff --git a/package_manager/tests/src/Functional/ExcludedPathsTest.php b/package_manager/tests/src/Functional/ExcludedPathsTest.php index 3dae832352..fd28b082ab 100644 --- a/package_manager/tests/src/Functional/ExcludedPathsTest.php +++ b/package_manager/tests/src/Functional/ExcludedPathsTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\package_manager\Functional; +use Drupal\Core\Database\Driver\sqlite\Connection; use Drupal\Core\Site\Settings; use Drupal\package_manager\PathLocator; use Drupal\package_manager\Stage; @@ -64,10 +65,12 @@ class ExcludedPathsTest extends BrowserTestBase { $path_locator->getActiveDirectory()->willReturn($active_dir); $path_locator->getStageDirectory()->willReturn($stage_dir); + $site_path = 'sites/example.com'; + // Ensure that we are using directories within the fake site fixture for // public and private files. $settings = Settings::getAll(); - $settings['file_public_path'] = 'sites/example.com/files'; + $settings['file_public_path'] = "$site_path/files"; $settings['file_private_path'] = 'private'; new Settings($settings); @@ -76,7 +79,18 @@ class ExcludedPathsTest extends BrowserTestBase { $reflector = new \ReflectionObject($subscriber); $property = $reflector->getProperty('sitePath'); $property->setAccessible(TRUE); - $property->setValue($subscriber, 'sites/example.com'); + $property->setValue($subscriber, $site_path); + + // Mock a SQLite database connection to a file in the active directory. The + // file should not be staged. + $database = $this->prophesize(Connection::class); + $database->driver()->willReturn('sqlite'); + $database->getConnectionOptions()->willReturn([ + 'database' => $site_path . '/db.sqlite', + ]); + $property = $reflector->getProperty('database'); + $property->setAccessible(TRUE); + $property->setValue($subscriber, $database->reveal()); $stage = new Stage( $path_locator->reveal(), @@ -91,11 +105,16 @@ class ExcludedPathsTest extends BrowserTestBase { $this->assertDirectoryExists($stage_dir); $this->assertDirectoryNotExists("$stage_dir/sites/simpletest"); $this->assertFileNotExists("$stage_dir/vendor/web.config"); - $this->assertDirectoryNotExists("$stage_dir/sites/example.com/files"); + $this->assertDirectoryNotExists("$stage_dir/$site_path/files"); $this->assertDirectoryNotExists("$stage_dir/private"); - $this->assertFileNotExists("$stage_dir/sites/example.com/settings.php"); - $this->assertFileNotExists("$stage_dir/sites/example.com/settings.local.php"); - $this->assertFileNotExists("$stage_dir/sites/example.com/services.yml"); + $this->assertFileNotExists("$stage_dir/$site_path/settings.php"); + $this->assertFileNotExists("$stage_dir/$site_path/settings.local.php"); + $this->assertFileNotExists("$stage_dir/$site_path/services.yml"); + // SQLite databases and their support files should never be staged. + $this->assertFileNotExists("$stage_dir/$site_path/db.sqlite"); + $this->assertFileNotExists("$stage_dir/$site_path/db.sqlite-shm"); + $this->assertFileNotExists("$stage_dir/$site_path/db.sqlite-wal"); + // Default site-specific settings files should never be staged. $this->assertFileNotExists("$stage_dir/sites/default/settings.php"); $this->assertFileNotExists("$stage_dir/sites/default/settings.local.php"); $this->assertFileNotExists("$stage_dir/sites/default/services.yml"); diff --git a/package_manager/tests/src/Kernel/ExcludedPathsSubscriberTest.php b/package_manager/tests/src/Kernel/ExcludedPathsSubscriberTest.php new file mode 100644 index 0000000000..b44f7811d6 --- /dev/null +++ b/package_manager/tests/src/Kernel/ExcludedPathsSubscriberTest.php @@ -0,0 +1,99 @@ +<?php + +namespace Drupal\Tests\package_manager\Kernel; + +use Drupal\Core\Database\Driver\sqlite\Connection; +use Drupal\package_manager\Event\PreCreateEvent; +use Drupal\package_manager\EventSubscriber\ExcludedPathsSubscriber; + +/** + * @covers \Drupal\package_manager\EventSubscriber\ExcludedPathsSubscriber + * + * @group package_manager + */ +class ExcludedPathsSubscriberTest extends PackageManagerKernelTestBase { + + /** + * Data provider for ::testSqliteDatabaseExcluded(). + * + * @return array[] + * Sets of arguments to pass to the test method. + */ + public function providerSqliteDatabaseExcluded(): array { + $drupal_root = $this->getDrupalRoot(); + + return [ + 'relative path, in site directory' => [ + 'sites/example.com/db.sqlite', + [ + 'sites/example.com/db.sqlite', + 'sites/example.com/db.sqlite-shm', + 'sites/example.com/db.sqlite-wal', + ], + ], + 'relative path, at root' => [ + 'db.sqlite', + [ + 'db.sqlite', + 'db.sqlite-shm', + 'db.sqlite-wal', + ], + ], + 'absolute path, in site directory' => [ + $drupal_root . '/sites/example.com/db.sqlite', + [ + 'sites/example.com/db.sqlite', + 'sites/example.com/db.sqlite-shm', + 'sites/example.com/db.sqlite-wal', + ], + ], + 'absolute path, at root' => [ + $drupal_root . '/db.sqlite', + [ + 'db.sqlite', + 'db.sqlite-shm', + 'db.sqlite-wal', + ], + ], + ]; + } + + /** + * Tests that SQLite database paths are excluded from the staging area. + * + * The exclusion of SQLite databases from the staging area is functionally + * tested by \Drupal\Tests\package_manager\Functional\ExcludedPathsTest. The + * purpose of this test is to ensure that SQLite database paths are processed + * properly (e.g., converting an absolute path to a relative path) before + * being flagged for exclusion. + * + * @param string $database + * The path of the SQLite database, as set in the database connection + * options. + * @param string[] $expected_exclusions + * The database paths which should be flagged for exclusion. + * + * @dataProvider providerSqliteDatabaseExcluded + * + * @see \Drupal\Tests\package_manager\Functional\ExcludedPathsTest + */ + public function testSqliteDatabaseExcluded(string $database, array $expected_exclusions): void { + $connection = $this->prophesize(Connection::class); + $connection->driver()->willReturn('sqlite'); + $connection->getConnectionOptions()->willReturn(['database' => $database]); + + $subscriber = new ExcludedPathsSubscriber( + $this->getDrupalRoot(), + 'sites/default', + $this->container->get('file_system'), + $this->container->get('stream_wrapper_manager'), + $connection->reveal() + ); + + $event = new PreCreateEvent($this->createStage()); + $subscriber->preCreate($event); + // All of the expected exclusions should be flagged. + $this->assertEmpty(array_diff($expected_exclusions, $event->getExcludedPaths())); + } + +} diff --git a/package_manager/tests/src/Kernel/PackageManagerKernelTestBase.php b/package_manager/tests/src/Kernel/PackageManagerKernelTestBase.php index 431220461f..846426f14f 100644 --- a/package_manager/tests/src/Kernel/PackageManagerKernelTestBase.php +++ b/package_manager/tests/src/Kernel/PackageManagerKernelTestBase.php @@ -46,16 +46,13 @@ abstract class PackageManagerKernelTestBase extends KernelTestBase { } /** - * Asserts validation results are returned from a stage life cycle event. + * Creates a stage object for testing purposes. * - * @param \Drupal\package_manager\ValidationResult[] $expected_results - * The expected validation results. - * @param string|null $event_class - * (optional) The class of the event which should return the results. Must - * be passed if $expected_results is not empty. + * @return \Drupal\Tests\package_manager\Kernel\TestStage + * A stage object, with test-only modifications. */ - protected function assertResults(array $expected_results, string $event_class = NULL): void { - $stage = new TestStage( + protected function createStage(): TestStage { + return new TestStage( $this->container->get('package_manager.path_locator'), $this->container->get('package_manager.beginner'), $this->container->get('package_manager.stager'), @@ -63,6 +60,19 @@ abstract class PackageManagerKernelTestBase extends KernelTestBase { $this->container->get('package_manager.cleaner'), $this->container->get('event_dispatcher'), ); + } + + /** + * Asserts validation results are returned from a stage life cycle event. + * + * @param \Drupal\package_manager\ValidationResult[] $expected_results + * The expected validation results. + * @param string|null $event_class + * (optional) The class of the event which should return the results. Must + * be passed if $expected_results is not empty. + */ + protected function assertResults(array $expected_results, string $event_class = NULL): void { + $stage = $this->createStage(); try { $stage->create(); -- GitLab