From 406a607aecbe4b3c10445955a93f8e3be62d3fe8 Mon Sep 17 00:00:00 2001
From: phenaproxima <phenaproxima@205645.no-reply.drupal.org>
Date: Thu, 9 Dec 2021 22:10:48 +0000
Subject: [PATCH] Issue #3253647 by phenaproxima, tedbow: Validate some
 Composer settings

---
 automatic_updates.services.yml                |   6 ++
 package_manager/package_manager.services.yml  |   6 ++
 package_manager/src/ComposerUtility.php       |  10 ++
 .../ComposerSettingsValidator.php             |  54 ++++++++++
 .../Kernel/ComposerSettingsValidatorTest.php  | 100 ++++++++++++++++++
 .../src/Kernel/DiskSpaceValidatorTest.php     |   6 +-
 .../src/Kernel/LockFileValidatorTest.php      |   4 +
 .../WritableFileSystemValidatorTest.php       |   6 +-
 .../PackageManagerReadinessChecksTest.php     |   1 +
 9 files changed, 189 insertions(+), 4 deletions(-)
 create mode 100644 package_manager/src/EventSubscriber/ComposerSettingsValidator.php
 create mode 100644 package_manager/tests/src/Kernel/ComposerSettingsValidatorTest.php

diff --git a/automatic_updates.services.yml b/automatic_updates.services.yml
index d648287bc9..a4cc7ecd94 100644
--- a/automatic_updates.services.yml
+++ b/automatic_updates.services.yml
@@ -48,6 +48,12 @@ services:
       - '@package_manager.validator.composer_executable'
     tags:
       - { name: event_subscriber }
+  automatic_updates.validator.composer_settings:
+    class: Drupal\automatic_updates\Validator\PackageManagerReadinessCheck
+    arguments:
+      - '@package_manager.validator.composer_settings'
+    tags:
+      - { name: event_subscriber }
   automatic_updates.disk_space_validator:
     class: Drupal\automatic_updates\Validator\PackageManagerReadinessCheck
     arguments:
diff --git a/package_manager/package_manager.services.yml b/package_manager/package_manager.services.yml
index 803c02b7c6..d2b01442f1 100644
--- a/package_manager/package_manager.services.yml
+++ b/package_manager/package_manager.services.yml
@@ -118,6 +118,12 @@ services:
       - '@string_translation'
     tags:
       - { name: event_subscriber }
+  package_manager.validator.composer_settings:
+    class: Drupal\package_manager\EventSubscriber\ComposerSettingsValidator
+    arguments:
+      - '@string_translation'
+    tags:
+      - { name: event_subscriber }
   package_manager.excluded_paths_subscriber:
     class: Drupal\package_manager\EventSubscriber\ExcludedPathsSubscriber
     arguments:
diff --git a/package_manager/src/ComposerUtility.php b/package_manager/src/ComposerUtility.php
index 6460b9e13c..6a111b74a9 100644
--- a/package_manager/src/ComposerUtility.php
+++ b/package_manager/src/ComposerUtility.php
@@ -37,6 +37,16 @@ class ComposerUtility {
     $this->composer = $composer;
   }
 
+  /**
+   * Returns the underlying Composer instance.
+   *
+   * @return \Composer\Composer
+   *   The Composer instance.
+   */
+  public function getComposer(): Composer {
+    return $this->composer;
+  }
+
   /**
    * Creates a utility object using the files in a given directory.
    *
diff --git a/package_manager/src/EventSubscriber/ComposerSettingsValidator.php b/package_manager/src/EventSubscriber/ComposerSettingsValidator.php
new file mode 100644
index 0000000000..64c7249564
--- /dev/null
+++ b/package_manager/src/EventSubscriber/ComposerSettingsValidator.php
@@ -0,0 +1,54 @@
+<?php
+
+namespace Drupal\package_manager\EventSubscriber;
+
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Drupal\package_manager\Event\PreCreateEvent;
+use Drupal\package_manager\Event\PreOperationStageEvent;
+
+/**
+ * Validates certain Composer settings.
+ */
+class ComposerSettingsValidator implements PreOperationStageValidatorInterface {
+
+  use StringTranslationTrait;
+
+  /**
+   * Constructs a ComposerSettingsValidator object.
+   *
+   * @param \Drupal\Core\StringTranslation\TranslationInterface $translation
+   *   The string translation service.
+   */
+  public function __construct(TranslationInterface $translation) {
+    $this->setStringTranslation($translation);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateStagePreOperation(PreOperationStageEvent $event): void {
+    $config = $event->getStage()
+      ->getActiveComposer()
+      ->getComposer()
+      ->getConfig();
+
+    if ($config->get('secure-http') !== TRUE) {
+      $event->addError([
+        $this->t('HTTPS must be enabled for Composer downloads. See <a href=":url">the Composer documentation</a> for more information.', [
+          ':url' => 'https://getcomposer.org/doc/06-config.md#secure-http',
+        ]),
+      ]);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents() {
+    return [
+      PreCreateEvent::class => 'validateStagePreOperation',
+    ];
+  }
+
+}
diff --git a/package_manager/tests/src/Kernel/ComposerSettingsValidatorTest.php b/package_manager/tests/src/Kernel/ComposerSettingsValidatorTest.php
new file mode 100644
index 0000000000..fae703f6e0
--- /dev/null
+++ b/package_manager/tests/src/Kernel/ComposerSettingsValidatorTest.php
@@ -0,0 +1,100 @@
+<?php
+
+namespace Drupal\Tests\package_manager\Kernel;
+
+use Drupal\Component\Serialization\Json;
+use Drupal\Core\DependencyInjection\ContainerBuilder;
+use Drupal\package_manager\Exception\StageValidationException;
+use Drupal\package_manager\PathLocator;
+use Drupal\package_manager\ValidationResult;
+use org\bovigo\vfs\vfsStream;
+
+/**
+ * @covers \Drupal\package_manager\EventSubscriber\ComposerSettingsValidator
+ *
+ * @group package_manager
+ */
+class ComposerSettingsValidatorTest extends PackageManagerKernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function disableValidators(ContainerBuilder $container): void {
+    parent::disableValidators($container);
+
+    // Disable the disk space validator, since it tries to inspect the file
+    // system in ways that vfsStream doesn't support, like calling stat() and
+    // disk_free_space().
+    $container->removeDefinition('package_manager.validator.disk_space');
+
+    // Disable the lock file validator, since the mock file system we create in
+    // this test doesn't have any lock files to validate.
+    $container->removeDefinition('package_manager.validator.lock_file');
+  }
+
+  /**
+   * Data provider for ::testSecureHttpValidation().
+   *
+   * @return array[]
+   *   Sets of arguments to pass to the test method.
+   */
+  public function providerSecureHttpValidation(): array {
+    $error = ValidationResult::createError([
+      'HTTPS must be enabled for Composer downloads. See <a href="https://getcomposer.org/doc/06-config.md#secure-http">the Composer documentation</a> for more information.',
+    ]);
+
+    return [
+      'disabled' => [
+        Json::encode([
+          'config' => [
+            'secure-http' => FALSE,
+          ],
+        ]),
+        [$error],
+      ],
+      'explicitly enabled' => [
+        Json::encode([
+          'config' => [
+            'secure-http' => TRUE,
+          ],
+        ]),
+        [],
+      ],
+      'implicitly enabled' => [
+        '{}',
+        [],
+      ],
+    ];
+  }
+
+  /**
+   * Tests that Composer's secure-http setting is validated.
+   *
+   * @param string $contents
+   *   The contents of the composer.json file.
+   * @param \Drupal\package_manager\ValidationResult[] $expected_results
+   *   The expected validation results, if any.
+   *
+   * @dataProvider providerSecureHttpValidation
+   */
+  public function testSecureHttpValidation(string $contents, array $expected_results): void {
+    $file = vfsStream::newFile('composer.json')->setContent($contents);
+    $this->vfsRoot->addChild($file);
+
+    $active_dir = $this->vfsRoot->url();
+    $locator = $this->prophesize(PathLocator::class);
+    $locator->getActiveDirectory()->willReturn($active_dir);
+    $locator->getProjectRoot()->willReturn($active_dir);
+    $locator->getVendorDirectory()->willReturn($active_dir);
+    $this->container->set('package_manager.path_locator', $locator->reveal());
+
+    try {
+      $this->createStage()->create();
+      $this->assertSame([], $expected_results);
+    }
+    catch (StageValidationException $e) {
+      $this->assertValidationResultsEqual($expected_results, $e->getResults());
+    }
+  }
+
+}
diff --git a/package_manager/tests/src/Kernel/DiskSpaceValidatorTest.php b/package_manager/tests/src/Kernel/DiskSpaceValidatorTest.php
index c4ee293ee0..ea15bb086e 100644
--- a/package_manager/tests/src/Kernel/DiskSpaceValidatorTest.php
+++ b/package_manager/tests/src/Kernel/DiskSpaceValidatorTest.php
@@ -33,9 +33,11 @@ class DiskSpaceValidatorTest extends PackageManagerKernelTestBase {
   protected function disableValidators(ContainerBuilder $container): void {
     parent::disableValidators($container);
 
-    // Disable the lock file validator, since in this test we are validating an
-    // imaginary file system which doesn't have any lock files.
+    // Disable the lock file and Composer settings validators, since in this
+    // test we are validating an imaginary file system which doesn't have any
+    // Composer files.
     $container->removeDefinition('package_manager.validator.lock_file');
+    $container->removeDefinition('package_manager.validator.composer_settings');
   }
 
   /**
diff --git a/package_manager/tests/src/Kernel/LockFileValidatorTest.php b/package_manager/tests/src/Kernel/LockFileValidatorTest.php
index 62c0142462..c71a6d27e4 100644
--- a/package_manager/tests/src/Kernel/LockFileValidatorTest.php
+++ b/package_manager/tests/src/Kernel/LockFileValidatorTest.php
@@ -45,6 +45,10 @@ class LockFileValidatorTest extends PackageManagerKernelTestBase {
     // system in ways that vfsStream doesn't support, like calling stat() and
     // disk_free_space().
     $container->removeDefinition('package_manager.validator.disk_space');
+
+    // Disable the Composer settings validator, since it tries to read Composer
+    // files that may not exist in this test.
+    $container->removeDefinition('package_manager.validator.composer_settings');
   }
 
   /**
diff --git a/package_manager/tests/src/Kernel/WritableFileSystemValidatorTest.php b/package_manager/tests/src/Kernel/WritableFileSystemValidatorTest.php
index cae0606c83..136d6b37cb 100644
--- a/package_manager/tests/src/Kernel/WritableFileSystemValidatorTest.php
+++ b/package_manager/tests/src/Kernel/WritableFileSystemValidatorTest.php
@@ -44,9 +44,11 @@ class WritableFileSystemValidatorTest extends PackageManagerKernelTestBase {
     // disk_free_space().
     $container->removeDefinition('package_manager.validator.disk_space');
 
-    // Disable the lock file validator, since the mock file system we create in
-    // this test doesn't have any lock files to validate.
+    // Disable the lock file and Composer settings validators, since in this
+    // test we are validating an imaginary file system which doesn't have any
+    // Composer files.
     $container->removeDefinition('package_manager.validator.lock_file');
+    $container->removeDefinition('package_manager.validator.composer_settings');
   }
 
   /**
diff --git a/tests/src/Kernel/ReadinessValidation/PackageManagerReadinessChecksTest.php b/tests/src/Kernel/ReadinessValidation/PackageManagerReadinessChecksTest.php
index f9b7a7bc4c..19dff1be7c 100644
--- a/tests/src/Kernel/ReadinessValidation/PackageManagerReadinessChecksTest.php
+++ b/tests/src/Kernel/ReadinessValidation/PackageManagerReadinessChecksTest.php
@@ -49,6 +49,7 @@ class PackageManagerReadinessChecksTest extends AutomaticUpdatesKernelTestBase {
       ['package_manager.validator.disk_space'],
       ['package_manager.validator.pending_updates'],
       ['package_manager.validator.file_system'],
+      ['package_manager.validator.composer_settings'],
     ];
   }
 
-- 
GitLab