function ComposerPluginsValidator::validate

Validates the allowed Composer plugins, both in active and stage.

File

core/modules/package_manager/src/Validator/ComposerPluginsValidator.php, line 131

Class

ComposerPluginsValidator
Validates the allowed Composer plugins, both in active and stage.

Namespace

Drupal\package_manager\Validator

Code

public function validate(PreOperationStageEvent $event) : void {
    $stage = $event->stage;
    // When about to copy the changes from the stage directory to the active
    // directory, use the stage directory's composer instead of the active.
    // Because composer plugins may be added or removed; the only thing that
    // matters is the set of composer plugins that *will* apply — if a composer
    // plugin is being removed, that's fine.
    $dir = $event instanceof PreApplyEvent ? $stage->getStageDirectory() : $this->pathLocator
        ->getProjectRoot();
    try {
        $allowed_plugins = $this->inspector
            ->getAllowPluginsConfig($dir);
    } catch (RuntimeException $exception) {
        $event->addErrorFromThrowable($exception, $this->t('Unable to determine Composer <code>allow-plugins</code> setting.'));
        return;
    }
    if ($allowed_plugins === TRUE) {
        $event->addError([
            $this->t('All composer plugins are allowed because <code>config.allow-plugins</code> is configured to <code>true</code>. This is an unacceptable security risk.'),
        ]);
        return;
    }
    // TRICKY: additional trusted Composer plugins is listed first, to allow
    // site owners who know what they're doing to use unsupported versions of
    // supported Composer plugins.
    $trusted_plugins = $this->additionalTrustedComposerPlugins + self::SUPPORTED_PLUGINS_THAT_DO_MODIFY + self::SUPPORTED_PLUGINS_THAT_DO_NOT_MODIFY;
    assert(is_array($allowed_plugins));
    // Only packages with `true` as a value are actually executed by Composer.
    $allowed_plugins = array_keys(array_filter($allowed_plugins));
    // The keys are normalized package names, and the values are the original,
    // non-normalized package names.
    $allowed_plugins = array_combine(array_map([
        __CLASS__,
        'normalizePackageName',
    ], $allowed_plugins), $allowed_plugins);
    $installed_packages = $this->inspector
        ->getInstalledPackagesList($dir);
    // Determine which plugins are both trusted by us, AND allowed by Composer's
    // configuration.
    $supported_plugins = array_intersect_key($allowed_plugins, $trusted_plugins);
    // Create an array whose keys are the names of those plugins, and the values
    // are their installed versions.
    $supported_plugins_installed_versions = array_combine($supported_plugins, array_map(fn(string $name): ?string => $installed_packages[$name]?->version, $supported_plugins));
    // Find the plugins whose installed versions aren't in the supported range.
    $unsupported_installed_versions = array_filter($supported_plugins_installed_versions, fn(?string $version, string $name): bool => $version && !Semver::satisfies($version, $trusted_plugins[$name]), ARRAY_FILTER_USE_BOTH);
    $untrusted_plugins = array_diff_key($allowed_plugins, $trusted_plugins);
    $messages = array_map(fn(string $raw_name) => $this->t('<code>@name</code>', [
        '@name' => $raw_name,
    ]), $untrusted_plugins);
    foreach ($unsupported_installed_versions as $name => $installed_version) {
        $messages[] = $this->t("<code>@name</code> is supported, but only version <code>@supported_version</code>, found <code>@installed_version</code>.", [
            '@name' => $name,
            '@supported_version' => $trusted_plugins[$name],
            '@installed_version' => $installed_version,
        ]);
    }
    if ($messages) {
        $summary = $this->formatPlural(count($messages), 'An unsupported Composer plugin was detected.', 'Unsupported Composer plugins were detected.');
        $event->addError($messages, $summary);
    }
}

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