class PhpTufValidator

Validates that PHP-TUF is installed and correctly configured.

In both the active and stage directories, this checks for the following conditions:

  • The PHP-TUF plugin is installed.
  • The plugin is not explicitly blocked by Composer's `allow-plugins` configuration.
  • Composer is aware of at least one repository that has TUF support explicitly enabled.

Until it's more real world-tested, TUF protection is bypassed by default. Ultimately, though, Package Manager will not treat TUF as optional.

@internal This is an internal part of Package Manager and may be changed or removed at any time without warning. External code should not interact with this class.

Hierarchy

Expanded class hierarchy of PhpTufValidator

1 file declares its use of PhpTufValidator
PhpTufValidatorTest.php in core/modules/package_manager/tests/src/Kernel/PhpTufValidatorTest.php

File

core/modules/package_manager/src/Validator/PhpTufValidator.php, line 41

Namespace

Drupal\package_manager\Validator
View source
final class PhpTufValidator implements EventSubscriberInterface {
    use StringTranslationTrait;
    
    /**
     * The name of the PHP-TUF Composer integration plugin.
     *
     * @var string
     */
    public const PLUGIN_NAME = 'php-tuf/composer-integration';
    public function __construct(PathLocator $pathLocator, ComposerInspector $composerInspector, ModuleHandlerInterface $moduleHandler, Settings $settings, array $repositories) {
        assert(Inspector::assertAllStrings($repositories));
    }
    
    /**
     * {@inheritdoc}
     */
    public static function getSubscribedEvents() : array {
        return [
            StatusCheckEvent::class => 'validate',
            PreCreateEvent::class => 'validate',
            PreRequireEvent::class => 'validate',
            PreApplyEvent::class => 'validate',
        ];
    }
    
    /**
     * Reacts to a stage event by validating PHP-TUF configuration as needed.
     *
     * @param \Drupal\package_manager\Event\PreOperationStageEvent $event
     *   The event object.
     */
    public function validate(PreOperationStageEvent $event) : void {
        $messages = $this->validateTuf($this->pathLocator
            ->getProjectRoot());
        if ($messages) {
            $event->addError($messages, $this->t('The active directory is not protected by PHP-TUF, which is required to use Package Manager securely.'));
        }
        $stage = $event->stage;
        if ($stage->stageDirectoryExists()) {
            $messages = $this->validateTuf($stage->getStageDirectory());
            if ($messages) {
                $event->addError($messages, $this->t('The stage directory is not protected by PHP-TUF, which is required to use Package Manager securely.'));
            }
        }
    }
    
    /**
     * Flags messages if PHP-TUF is not installed and configured properly.
     *
     * @param string $dir
     *   The directory to examine.
     *
     * @return \Drupal\Core\StringTranslation\TranslatableMarkup[]
     *   The error messages, if any.
     */
    private function validateTuf(string $dir) : array {
        $messages = [];
        // This setting will be removed without warning when no longer need.
        if ($this->settings
            ->get('package_manager_bypass_tuf', TRUE)) {
            return $messages;
        }
        if ($this->moduleHandler
            ->moduleExists('help')) {
            $help_url = Url::fromRoute('help.page', [
                'name' => 'package_manager',
            ])->setOption('fragment', 'package-manager-tuf-info')
                ->toString();
        }
        // The Composer plugin must be installed.
        $installed_packages = $this->composerInspector
            ->getInstalledPackagesList($dir);
        if (!isset($installed_packages[static::PLUGIN_NAME])) {
            $message = $this->t('The <code>@plugin</code> plugin is not installed.', [
                '@plugin' => static::PLUGIN_NAME,
            ]);
            if (isset($help_url)) {
                $message = $this->t('@message See <a href=":url">the help page</a> for more information on how to install the plugin.', [
                    '@message' => $message,
                    ':url' => $help_url,
                ]);
            }
            $messages[] = $message;
        }
        // And it has to be explicitly enabled.
        $allowed_plugins = $this->composerInspector
            ->getAllowPluginsConfig($dir);
        if ($allowed_plugins !== TRUE && empty($allowed_plugins[static::PLUGIN_NAME])) {
            $message = $this->t('The <code>@plugin</code> plugin is not listed as an allowed plugin.', [
                '@plugin' => static::PLUGIN_NAME,
            ]);
            if (isset($help_url)) {
                $message = $this->t('@message See <a href=":url">the help page</a> for more information on how to configure the plugin.', [
                    '@message' => $message,
                    ':url' => $help_url,
                ]);
            }
            $messages[] = $message;
        }
        // Confirm that all repositories we're configured to look at have opted into
        // TUF protection.
        foreach ($this->getRepositoryStatus($dir) as $url => $is_protected) {
            if ($is_protected) {
                continue;
            }
            $message = $this->t('TUF is not enabled for the <code>@url</code> repository.', [
                '@url' => $url,
            ]);
            if (isset($help_url)) {
                $message = $this->t('@message See <a href=":url">the help page</a> for more information on how to set up this repository.', [
                    '@message' => $message,
                    ':url' => $help_url,
                ]);
            }
            $messages[] = $message;
        }
        return $messages;
    }
    
    /**
     * Gets the TUF protection status of Composer repositories.
     *
     * @param string $dir
     *   The directory in which to run Composer.
     *
     * @return bool[]
     *   An array of booleans, keyed by repository URL, indicating whether TUF
     *   protection is enabled for that repository.
     */
    private function getRepositoryStatus(string $dir) : array {
        $status = [];
        $repositories = $this->composerInspector
            ->getConfig('repositories', $dir);
        $repositories = Json::decode($repositories);
        foreach ($repositories as $repository) {
            // Only Composer repositories can have TUF protection.
            if ($repository['type'] === 'composer') {
                $url = $repository['url'];
                $status[$url] = !empty($repository['tuf']);
            }
        }
        return array_intersect_key($status, array_flip($this->repositories));
    }

}

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.