From 42ad7d56c13a68edede12ea0ec47dff1db497989 Mon Sep 17 00:00:00 2001 From: tedbow <tedbow@240860.no-reply.drupal.org> Date: Thu, 7 Apr 2022 17:53:23 +0000 Subject: [PATCH] Issue #3271468 by tedbow, phenaproxima: Validate the target version is secure and supported --- automatic_updates.services.yml | 4 ++ src/Validator/UpdateReleaseValidator.php | 56 +++++++++++++++++++ .../ReadinessValidationManagerTest.php | 6 +- .../SettingsValidatorTest.php | 2 +- .../StagedProjectsValidatorTest.php | 2 +- .../UpdateReleaseValidatorTest.php | 40 +++++++++++++ tests/src/Kernel/UpdaterTest.php | 3 + 7 files changed, 106 insertions(+), 7 deletions(-) create mode 100644 src/Validator/UpdateReleaseValidator.php create mode 100644 tests/src/Kernel/ReadinessValidation/UpdateReleaseValidatorTest.php diff --git a/automatic_updates.services.yml b/automatic_updates.services.yml index ce546a1feb..67437b6e00 100644 --- a/automatic_updates.services.yml +++ b/automatic_updates.services.yml @@ -129,3 +129,7 @@ services: - '@string_translation' tags: - { name: event_subscriber } + automatic_updates.validator.target_release: + class: \Drupal\automatic_updates\Validator\UpdateReleaseValidator + tags: + - { name: event_subscriber } diff --git a/src/Validator/UpdateReleaseValidator.php b/src/Validator/UpdateReleaseValidator.php new file mode 100644 index 0000000000..d501d1cda7 --- /dev/null +++ b/src/Validator/UpdateReleaseValidator.php @@ -0,0 +1,56 @@ +<?php + +namespace Drupal\automatic_updates\Validator; + +use Drupal\automatic_updates\ProjectInfo; +use Drupal\automatic_updates\Updater; +use Drupal\Core\StringTranslation\StringTranslationTrait; +use Drupal\package_manager\Event\PreCreateEvent; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * Validates that the target release of Drupal core is secure and supported. + */ +class UpdateReleaseValidator implements EventSubscriberInterface { + + use StringTranslationTrait; + + /** + * Checks that the target version of Drupal core is secure and supported. + * + * @param \Drupal\package_manager\Event\PreCreateEvent $event + * The event object. + */ + public function checkRelease(PreCreateEvent $event): void { + $stage = $event->getStage(); + // This check only works with Automatic Updates. + if (!$stage instanceof Updater) { + return; + } + + $package_versions = $stage->getPackageVersions(); + // The updater will only update Drupal core, so all production dependencies + // will be Drupal core packages. + $target_version = reset($package_versions['production']); + + // If the target version isn't in the list of installable releases, then it + // isn't secure and supported and we should flag an error. + $releases = (new ProjectInfo('drupal'))->getInstallableReleases(); + if (empty($releases) || !array_key_exists($target_version, $releases)) { + $message = $this->t('Cannot update Drupal core to @target_version because it is not in the list of installable releases.', [ + '@target_version' => $target_version, + ]); + $event->addError([$message]); + } + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + return [ + PreCreateEvent::class => 'checkRelease', + ]; + } + +} diff --git a/tests/src/Kernel/ReadinessValidation/ReadinessValidationManagerTest.php b/tests/src/Kernel/ReadinessValidation/ReadinessValidationManagerTest.php index c9a09cb821..add1626a4e 100644 --- a/tests/src/Kernel/ReadinessValidation/ReadinessValidationManagerTest.php +++ b/tests/src/Kernel/ReadinessValidation/ReadinessValidationManagerTest.php @@ -219,12 +219,8 @@ class ReadinessValidationManagerTest extends AutomaticUpdatesKernelTestBase { * Tests that stored validation results are deleted after an update. */ public function testStoredResultsDeletedPostApply(): void { - $this->container->get('module_installer') - ->install(['automatic_updates']); - - // Ensure there's a simulated core release to update to. + $this->enableModules(['automatic_updates']); $this->setCoreVersion('9.8.1'); - $this->setReleaseMetadata(__DIR__ . '/../../../fixtures/release-history/drupal.9.8.2.xml'); // The readiness checker should raise a warning, so that the update is not // blocked or aborted. diff --git a/tests/src/Kernel/ReadinessValidation/SettingsValidatorTest.php b/tests/src/Kernel/ReadinessValidation/SettingsValidatorTest.php index 30493cecf7..dff44ec010 100644 --- a/tests/src/Kernel/ReadinessValidation/SettingsValidatorTest.php +++ b/tests/src/Kernel/ReadinessValidation/SettingsValidatorTest.php @@ -51,7 +51,7 @@ class SettingsValidatorTest extends AutomaticUpdatesKernelTestBase { $this->assertCheckerResultsFromManager($expected_results, TRUE); try { $this->container->get('automatic_updates.updater')->begin([ - 'drupal' => '9.8.1', + 'drupal' => '9.8.2', ]); // If there was no exception, ensure we're not expecting any errors. $this->assertSame([], $expected_results); diff --git a/tests/src/Kernel/ReadinessValidation/StagedProjectsValidatorTest.php b/tests/src/Kernel/ReadinessValidation/StagedProjectsValidatorTest.php index edc1146f48..eef8b02e2e 100644 --- a/tests/src/Kernel/ReadinessValidation/StagedProjectsValidatorTest.php +++ b/tests/src/Kernel/ReadinessValidation/StagedProjectsValidatorTest.php @@ -61,7 +61,7 @@ class StagedProjectsValidatorTest extends AutomaticUpdatesKernelTestBase { } $updater = $this->container->get('automatic_updates.updater'); - $stage_id = $updater->begin(['drupal' => '9.8.1']); + $stage_id = $updater->begin(['drupal' => '9.8.2']); if ($stage_dir_exists) { // Copy the fixture's staging directory into a subdirectory using the // stage ID as the directory name. diff --git a/tests/src/Kernel/ReadinessValidation/UpdateReleaseValidatorTest.php b/tests/src/Kernel/ReadinessValidation/UpdateReleaseValidatorTest.php new file mode 100644 index 0000000000..4234d103dc --- /dev/null +++ b/tests/src/Kernel/ReadinessValidation/UpdateReleaseValidatorTest.php @@ -0,0 +1,40 @@ +<?php + +namespace Drupal\Tests\automatic_updates\Kernel\ReadinessValidation; + +use Drupal\automatic_updates\Exception\UpdateException; +use Drupal\package_manager\ValidationResult; +use Drupal\Tests\automatic_updates\Kernel\AutomaticUpdatesKernelTestBase; + +/** + * @covers \Drupal\automatic_updates\Validator\UpdateReleaseValidator + * + * @group automatic_updates + */ +class UpdateReleaseValidatorTest extends AutomaticUpdatesKernelTestBase { + + /** + * {@inheritdoc} + */ + protected static $modules = ['automatic_updates']; + + /** + * Tests that an error is raised when trying to update to an unknown release. + */ + public function testUnknownReleaseRaisesError(): void { + $result = ValidationResult::createError([ + 'Cannot update Drupal core to 9.8.99 because it is not in the list of installable releases.', + ]); + + try { + $this->container->get('automatic_updates.updater')->begin([ + 'drupal' => '9.8.99', + ]); + $this->fail('Expected an exception to be thrown, but it was not.'); + } + catch (UpdateException $e) { + $this->assertValidationResultsEqual([$result], $e->getResults()); + } + } + +} diff --git a/tests/src/Kernel/UpdaterTest.php b/tests/src/Kernel/UpdaterTest.php index f48cb59281..8dbaeaafb6 100644 --- a/tests/src/Kernel/UpdaterTest.php +++ b/tests/src/Kernel/UpdaterTest.php @@ -34,6 +34,9 @@ class UpdaterTest extends AutomaticUpdatesKernelTestBase { * Tests that correct versions are staged after calling ::begin(). */ public function testCorrectVersionsStaged(): void { + // Simulate that we're running Drupal 9.8.0 and a security update to 9.8.1 + // is available. + $this->setCoreVersion('9.8.0'); $this->setReleaseMetadata(__DIR__ . '/../../fixtures/release-history/drupal.9.8.1-security.xml'); // Create a user who will own the stage even after the container is rebuilt. -- GitLab