Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move security advisory loading to repositories #10898

Merged
merged 2 commits into from Jun 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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).'</>';
}
}