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