Skip to content

Commit

Permalink
Merge pull request #10898 from Seldaek/meta_advisories
Browse files Browse the repository at this point in the history
Move security advisory loading to repositories
  • Loading branch information
Seldaek committed Jun 28, 2022
2 parents d880ab6 + 0196690 commit f9db69a
Show file tree
Hide file tree
Showing 19 changed files with 839 additions and 802 deletions.
2 changes: 1 addition & 1 deletion doc/03-cli.md
Expand Up @@ -109,7 +109,7 @@ resolution.
* **--no-autoloader:** Skips autoloader generation.
* **--no-progress:** Removes the progress display that can mess with some
terminals or scripts which don't handle backspace characters.
* **--no-audit:** Does not run the audit step after installation is complete.
* **--audit:** Run an audit after installation is complete.
* **--audit-format:** Audit output format. Must be "table", "plain", or "summary" (default).
* **--optimize-autoloader (-o):** Convert PSR-0/4 autoloading to classmap to get a faster
autoloader. This is recommended especially for production, but can take
Expand Down
122 changes: 6 additions & 116 deletions phpstan/baseline.neon
@@ -1,13 +1,13 @@
parameters:
ignoreErrors:
-
message: "#^Binary operation \"\\.\" between non\\-empty\\-string and array\\|string\\|null results in an error\\.$#"
message: "#^Parameter \\#2 \\$advisories of method Composer\\\\Advisory\\\\Auditor\\:\\:outputAdvisories\\(\\) expects array\\<string, array\\<Composer\\\\Advisory\\\\SecurityAdvisory\\>\\>, array\\<string, array\\<Composer\\\\Advisory\\\\PartialSecurityAdvisory\\>\\> given\\.$#"
count: 1
path: ../src/Composer/Autoload/AutoloadGenerator.php
path: ../src/Composer/Advisory/Auditor.php

-
message: "#^Cannot call method writeError\\(\\) on Composer\\\\IO\\\\IOInterface\\|null\\.$#"
count: 2
message: "#^Binary operation \"\\.\" between non\\-empty\\-string and array\\|string\\|null results in an error\\.$#"
count: 1
path: ../src/Composer/Autoload/AutoloadGenerator.php

-
Expand All @@ -20,11 +20,6 @@ parameters:
count: 1
path: ../src/Composer/Autoload/AutoloadGenerator.php

-
message: "#^Only booleans are allowed in &&, Composer\\\\IO\\\\IOInterface\\|null given on the left side\\.$#"
count: 1
path: ../src/Composer/Autoload/AutoloadGenerator.php

-
message: "#^Only booleans are allowed in &&, array\\<string, array\\<int\\|string, array\\<string\\>\\|int\\|string\\>\\|bool\\|string\\>\\|bool\\|string\\|null given on the left side\\.$#"
count: 1
Expand Down Expand Up @@ -55,21 +50,11 @@ parameters:
count: 3
path: ../src/Composer/Autoload/AutoloadGenerator.php

-
message: "#^Only booleans are allowed in a ternary operator condition, array\\<int, string\\> given\\.$#"
count: 1
path: ../src/Composer/Autoload/AutoloadGenerator.php

-
message: "#^Only booleans are allowed in a ternary operator condition, mixed given\\.$#"
count: 1
path: ../src/Composer/Autoload/AutoloadGenerator.php

-
message: "#^Only booleans are allowed in an if condition, array\\<int, string\\>\\|null given\\.$#"
count: 1
path: ../src/Composer/Autoload/AutoloadGenerator.php

-
message: "#^Only booleans are allowed in an if condition, mixed given\\.$#"
count: 1
Expand Down Expand Up @@ -115,11 +100,6 @@ parameters:
count: 1
path: ../src/Composer/Autoload/AutoloadGenerator.php

-
message: "#^Parameter \\#2 \\$excluded of static method Composer\\\\Autoload\\\\ClassMapGenerator\\:\\:createMap\\(\\) expects string\\|null, array\\|string\\|null given\\.$#"
count: 1
path: ../src/Composer/Autoload/AutoloadGenerator.php

-
message: "#^Parameter \\#2 \\$subject of static method Composer\\\\Pcre\\\\Preg\\:\\:isMatch\\(\\) expects string, string\\|false given\\.$#"
count: 1
Expand Down Expand Up @@ -165,66 +145,6 @@ parameters:
count: 1
path: ../src/Composer/Autoload/ClassLoader.php

-
message: "#^Call to function in_array\\(\\) requires parameter \\#3 to be set\\.$#"
count: 1
path: ../src/Composer/Autoload/ClassMapGenerator.php

-
message: "#^Cannot call method getPathname\\(\\) on SplFileInfo\\|string\\.$#"
count: 1
path: ../src/Composer/Autoload/ClassMapGenerator.php

-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 2
path: ../src/Composer/Autoload/ClassMapGenerator.php

-
message: "#^Method Composer\\\\Autoload\\\\ClassMapGenerator\\:\\:findClasses\\(\\) should return array\\<int, class\\-string\\> but returns array\\<int, string\\>\\.$#"
count: 1
path: ../src/Composer/Autoload/ClassMapGenerator.php

-
message: "#^Only booleans are allowed in &&, Composer\\\\IO\\\\IOInterface\\|null given on the left side\\.$#"
count: 1
path: ../src/Composer/Autoload/ClassMapGenerator.php

-
message: "#^Only booleans are allowed in &&, string\\|null given on the left side\\.$#"
count: 2
path: ../src/Composer/Autoload/ClassMapGenerator.php

-
message: "#^Only booleans are allowed in a negated boolean, string given\\.$#"
count: 1
path: ../src/Composer/Autoload/ClassMapGenerator.php

-
message: "#^Only booleans are allowed in an if condition, Composer\\\\IO\\\\IOInterface\\|null given\\.$#"
count: 1
path: ../src/Composer/Autoload/ClassMapGenerator.php

-
message: "#^Only booleans are allowed in an if condition, array\\<int, class\\-string\\> given\\.$#"
count: 1
path: ../src/Composer/Autoload/ClassMapGenerator.php

-
message: "#^Parameter \\#1 \\$str of function strtr expects string, string\\|false given\\.$#"
count: 1
path: ../src/Composer/Autoload/ClassMapGenerator.php

-
message: "#^Parameter \\#3 \\$baseNamespace of static method Composer\\\\Autoload\\\\ClassMapGenerator\\:\\:filterByNamespace\\(\\) expects string, string\\|null given\\.$#"
count: 1
path: ../src/Composer/Autoload/ClassMapGenerator.php

-
message: "#^Parameter \\#5 \\$basePath of static method Composer\\\\Autoload\\\\ClassMapGenerator\\:\\:filterByNamespace\\(\\) expects string, array\\<string\\>\\|string\\|Traversable\\<mixed, SplFileInfo\\> given\\.$#"
count: 1
path: ../src/Composer/Autoload/ClassMapGenerator.php

-
message: "#^Casting to bool something that's already bool\\.$#"
count: 2
Expand Down Expand Up @@ -717,7 +637,7 @@ parameters:

-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 3
count: 2
path: ../src/Composer/Command/RequireCommand.php

-
Expand All @@ -735,11 +655,6 @@ parameters:
count: 1
path: ../src/Composer/Command/RequireCommand.php

-
message: "#^Only booleans are allowed in a negated boolean, mixed given\\.$#"
count: 1
path: ../src/Composer/Command/RequireCommand.php

-
message: "#^Only booleans are allowed in an if condition, string\\|null given\\.$#"
count: 2
Expand Down Expand Up @@ -1842,7 +1757,7 @@ parameters:

-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 2
count: 1
path: ../src/Composer/DependencyResolver/SolverProblemsException.php

-
Expand Down Expand Up @@ -3725,11 +3640,6 @@ parameters:
count: 2
path: ../src/Composer/Repository/ComposerRepository.php

-
message: "#^Only booleans are allowed in a negated boolean, string\\|null given\\.$#"
count: 2
path: ../src/Composer/Repository/ComposerRepository.php

-
message: "#^Only booleans are allowed in a ternary operator condition, string\\|null given\\.$#"
count: 1
Expand Down Expand Up @@ -4518,11 +4428,6 @@ parameters:
count: 1
path: ../src/Composer/SelfUpdate/Versions.php

-
message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#"
count: 1
path: src/Composer/Util/Auditor.php

-
message: "#^Call to function in_array\\(\\) requires parameter \\#3 to be set\\.$#"
count: 1
Expand Down Expand Up @@ -5598,21 +5503,6 @@ parameters:
count: 2
path: ../tests/Composer/Test/Autoload/AutoloadGeneratorTest.php

-
message: "#^Dynamic call to static method Composer\\\\Test\\\\TestCase\\:\\:ensureDirectoryExistsAndClear\\(\\)\\.$#"
count: 1
path: ../tests/Composer/Test/Autoload/ClassMapGeneratorTest.php

-
message: "#^Parameter \\#1 \\$expected of method Composer\\\\Test\\\\Autoload\\\\ClassMapGeneratorTest\\:\\:assertEqualsNormalized\\(\\) expects array\\<class\\-string\\>, array\\<string, string\\> given\\.$#"
count: 3
path: ../tests/Composer/Test/Autoload/ClassMapGeneratorTest.php

-
message: "#^Parameter \\#2 \\$actual of method Composer\\\\Test\\\\Autoload\\\\ClassMapGeneratorTest\\:\\:assertEqualsNormalized\\(\\) expects array\\<class\\-string\\>, array\\<class\\-string, string\\> given\\.$#"
count: 3
path: ../tests/Composer/Test/Autoload/ClassMapGeneratorTest.php

-
message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#"
count: 1
Expand Down
174 changes: 174 additions & 0 deletions src/Composer/Advisory/Auditor.php
@@ -0,0 +1,174 @@
<?php declare(strict_types=1);

namespace Composer\Advisory;

use Composer\IO\ConsoleIO;
use Composer\IO\IOInterface;
use Composer\Package\PackageInterface;
use Composer\Repository\RepositorySet;
use InvalidArgumentException;
use Symfony\Component\Console\Formatter\OutputFormatter;

/**
* @internal
*/
class Auditor
{
public const FORMAT_TABLE = 'table';

public const FORMAT_PLAIN = 'plain';

public const FORMAT_SUMMARY = 'summary';

public const FORMATS = [
self::FORMAT_TABLE,
self::FORMAT_PLAIN,
self::FORMAT_SUMMARY,
];

/**
* @param IOInterface $io
* @param PackageInterface[] $packages
* @param self::FORMAT_* $format The format that will be used to output audit results.
* @param bool $warningOnly If true, outputs a warning. If false, outputs an error.
* @return int Amount of packages with vulnerabilities found
* @throws InvalidArgumentException If no packages are passed in
*/
public function audit(IOInterface $io, RepositorySet $repoSet, array $packages, string $format, bool $warningOnly = true): int
{
$advisories = $repoSet->getMatchingSecurityAdvisories($packages, $format === self::FORMAT_SUMMARY);
$errorOrWarn = $warningOnly ? 'warning' : 'error';
if (count($advisories) > 0) {
[$affectedPackages, $totalAdvisories] = $this->countAdvisories($advisories);
$plurality = $totalAdvisories === 1 ? 'y' : 'ies';
$pkgPlurality = $affectedPackages === 1 ? '' : 's';
$punctuation = $format === 'summary' ? '.' : ':';
$io->writeError("<$errorOrWarn>Found $totalAdvisories security vulnerability advisor{$plurality} affecting $affectedPackages package{$pkgPlurality}{$punctuation}</$errorOrWarn>");
$this->outputAdvisories($io, $advisories, $format);

return $affectedPackages;
}

$io->writeError('<info>No security vulnerability advisories found</info>');

return 0;
}

/**
* @param array<string, array<PartialSecurityAdvisory>> $advisories
* @return array{int, int} Count of affected packages and total count of advisories
*/
private function countAdvisories(array $advisories): array
{
$count = 0;
foreach ($advisories as $packageAdvisories) {
$count += count($packageAdvisories);
}
return [count($advisories), $count];
}

/**
* @param IOInterface $io
* @param array<string, array<SecurityAdvisory>> $advisories
* @param self::FORMAT_* $format The format that will be used to output audit results.
* @return void
*/
private function outputAdvisories(IOInterface $io, array $advisories, string $format): void
{
switch ($format) {
case self::FORMAT_TABLE:
if (!($io instanceof ConsoleIO)) {
throw new InvalidArgumentException('Cannot use table format with ' . get_class($io));
}
$this->outputAvisoriesTable($io, $advisories);
return;
case self::FORMAT_PLAIN:
$this->outputAdvisoriesPlain($io, $advisories);
return;
case self::FORMAT_SUMMARY:
// We've already output the number of advisories in audit()
$io->writeError('Run composer audit for a full list of advisories.');
return;
default:
throw new InvalidArgumentException('Invalid format "'.$format.'".');
}
}

/**
* @param ConsoleIO $io
* @param array<string, array<SecurityAdvisory>> $advisories
* @return void
*/
private function outputAvisoriesTable(ConsoleIO $io, array $advisories): void
{
foreach ($advisories as $packageAdvisories) {
foreach ($packageAdvisories as $advisory) {
$io->getTable()
->setHorizontal()
->setHeaders([
'Package',
'CVE',
'Title',
'URL',
'Affected versions',
'Reported at',
])
->addRow([
$advisory->packageName,
$this->getCVE($advisory),
$advisory->title,
$this->getURL($advisory),
$advisory->affectedVersions->getPrettyString(),
$advisory->reportedAt->format(DATE_ATOM),
])
->setColumnWidth(1, 80)
->setColumnMaxWidth(1, 80)
->render();
}
}
}

/**
* @param IOInterface $io
* @param array<string, array<SecurityAdvisory>> $advisories
* @return void
*/
private function outputAdvisoriesPlain(IOInterface $io, array $advisories): void
{
$error = [];
$firstAdvisory = true;
foreach ($advisories as $packageAdvisories) {
foreach ($packageAdvisories as $advisory) {
if (!$firstAdvisory) {
$error[] = '--------';
}
$error[] = "Package: ".$advisory->packageName;
$error[] = "CVE: ".$this->getCVE($advisory);
$error[] = "Title: ".OutputFormatter::escape($advisory->title);
$error[] = "URL: ".$this->getURL($advisory);
$error[] = "Affected versions: ".OutputFormatter::escape($advisory->affectedVersions->getPrettyString());
$error[] = "Reported at: ".$advisory->reportedAt->format(DATE_ATOM);
$firstAdvisory = false;
}
}
$io->writeError($error);
}

private function getCVE(SecurityAdvisory $advisory): string
{
if ($advisory->cve === null) {
return 'NO CVE';
}

return '<href=https://cve.mitre.org/cgi-bin/cvename.cgi?name='.$advisory->cve.'>'.$advisory->cve.'</>';
}

private function getURL(SecurityAdvisory $advisory): string
{
if ($advisory->link === null) {
return '';
}

return '<href='.OutputFormatter::escape($advisory->link).'>'.OutputFormatter::escape($advisory->link).'</>';
}
}

0 comments on commit f9db69a

Please sign in to comment.