Upstream composer/composer state of the touched files before 062bfdce1b
This commit is contained in:
1 parent
1a98d2c35a
commit
12931dd03f
2 files changed
+325
No files matched your search
@@ -0,0 +1,136 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of Composer.
|
||||
*
|
||||
* (c) Nils Adermann <naderman@naderman.de>
|
||||
* Jordi Boggiano <j.boggiano@seld.be>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Composer\Command;
|
||||
|
||||
use Composer\Composer;
|
||||
use Composer\FilterList\FilterListProvider\FilterListProviderSet;
|
||||
use Composer\Policy\ListPolicyConfig;
|
||||
use Composer\Policy\PolicyConfig;
|
||||
use Composer\Repository\RepositorySet;
|
||||
use Composer\Repository\RepositoryUtils;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Composer\Package\PackageInterface;
|
||||
use Composer\Repository\InstalledRepository;
|
||||
use Composer\Advisory\Auditor;
|
||||
use Composer\Console\Input\InputOption;
|
||||
|
||||
class AuditCommand extends BaseCommand
|
||||
{
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setName('audit')
|
||||
->setDescription('Checks for security vulnerability advisories for installed packages')
|
||||
->setDefinition([
|
||||
new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables auditing of require-dev packages.'),
|
||||
new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Output format. Must be "table", "plain", "json", or "summary".', Auditor::FORMAT_TABLE, Auditor::FORMATS),
|
||||
new InputOption('locked', null, InputOption::VALUE_NONE, 'Audit based on the lock file instead of the installed packages.'),
|
||||
new InputOption('abandoned', null, InputOption::VALUE_REQUIRED, 'Behavior on abandoned packages. Must be "ignore", "report", or "fail".', null, ListPolicyConfig::AUDITS),
|
||||
new InputOption('ignore-severity', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Ignore advisories of a certain severity level.', [], ['low', 'medium', 'high', 'critical']),
|
||||
new InputOption('ignore-unreachable', null, InputOption::VALUE_NONE, 'Ignore repositories that are unreachable or return a non-200 status code.'),
|
||||
])
|
||||
->setHelp(
|
||||
<<<EOT
|
||||
The <info>audit</info> command checks for security vulnerability advisories for installed packages.
|
||||
|
||||
If you do not want to include dev dependencies in the audit you can omit them with --no-dev
|
||||
|
||||
If you want to ignore repositories that are unreachable or return a non-200 status code, use --ignore-unreachable
|
||||
|
||||
Read more at https://getcomposer.org/doc/03-cli.md#audit
|
||||
EOT
|
||||
)
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$composer = $this->requireComposer();
|
||||
$packages = $this->getPackages($composer, $input);
|
||||
|
||||
if (count($packages) === 0) {
|
||||
if ($composer->getPackage()->getRequires() !== []
|
||||
|| (!$input->getOption('no-dev') && $composer->getPackage()->getDevRequires() !== [])) {
|
||||
$this->getIO()->writeError('No installed packages found. Please run "composer install" before running "audit" or pass "--locked" to audit the lock file.');
|
||||
|
||||
return Auditor::STATUS_FAILED;
|
||||
}
|
||||
|
||||
$this->getIO()->writeError('No packages - skipping audit.');
|
||||
|
||||
return Auditor::STATUS_OK;
|
||||
}
|
||||
|
||||
$auditor = new Auditor();
|
||||
$repoSet = new RepositorySet();
|
||||
foreach ($composer->getRepositoryManager()->getRepositories() as $repo) {
|
||||
$repoSet->addRepository($repo);
|
||||
}
|
||||
|
||||
$abandoned = $input->getOption('abandoned');
|
||||
if ($abandoned !== null && !in_array($abandoned, ListPolicyConfig::AUDITS, true)) {
|
||||
throw new \InvalidArgumentException('--abandoned must be one of '.implode(', ', ListPolicyConfig::AUDITS).'.');
|
||||
}
|
||||
|
||||
$policyConfig = $this->createPolicyConfig($composer->getConfig(), $input);
|
||||
if ($abandoned !== null) {
|
||||
$policyConfig = $policyConfig->withAudit($abandoned);
|
||||
}
|
||||
|
||||
$ignoreSeverities = $input->getOption('ignore-severity');
|
||||
if (count($ignoreSeverities) > 0) {
|
||||
$policyConfig = $policyConfig->withIgnoreSeverity(array_values($ignoreSeverities));
|
||||
}
|
||||
if ($input->getOption('ignore-unreachable')) {
|
||||
$policyConfig = $policyConfig->withIgnoreUnreachable('audit');
|
||||
}
|
||||
|
||||
$filterListProviderSet = $policyConfig->enabled ? FilterListProviderSet::create($policyConfig, $composer->getRepositoryManager()->getRepositories(), $composer->getLoop()->getHttpDownloader()) : null;
|
||||
|
||||
return min(255, $auditor->audit(
|
||||
$this->getIO(),
|
||||
$repoSet,
|
||||
$policyConfig,
|
||||
$packages,
|
||||
$this->getAuditFormat($input, 'format'),
|
||||
false,
|
||||
$filterListProviderSet
|
||||
));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return PackageInterface[]
|
||||
*/
|
||||
private function getPackages(Composer $composer, InputInterface $input): array
|
||||
{
|
||||
if ($input->getOption('locked')) {
|
||||
if (!$composer->getLocker()->isLocked()) {
|
||||
throw new \UnexpectedValueException('Valid composer.json and composer.lock files are required to run this command with --locked');
|
||||
}
|
||||
$locker = $composer->getLocker();
|
||||
|
||||
return $locker->getLockedRepository(!$input->getOption('no-dev'))->getPackages();
|
||||
}
|
||||
|
||||
$rootPkg = $composer->getPackage();
|
||||
$installedRepo = new InstalledRepository([$composer->getRepositoryManager()->getLocalRepository()]);
|
||||
|
||||
if ($input->getOption('no-dev')) {
|
||||
return RepositoryUtils::filterRequiredPackages($installedRepo->getPackages(), $rootPkg);
|
||||
}
|
||||
|
||||
return $installedRepo->getPackages();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of Composer.
|
||||
*
|
||||
* (c) Nils Adermann <naderman@naderman.de>
|
||||
* Jordi Boggiano <j.boggiano@seld.be>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Composer\Test\Command;
|
||||
|
||||
use Composer\Advisory\Auditor;
|
||||
use Composer\Policy\ListPolicyConfig;
|
||||
use Composer\Test\TestCase;
|
||||
use UnexpectedValueException;
|
||||
|
||||
class AuditCommandTest extends TestCase
|
||||
{
|
||||
public function testSuccessfulResponseCodeWhenNoPackagesAreRequired(): void
|
||||
{
|
||||
$this->initTempComposer();
|
||||
|
||||
$appTester = $this->getApplicationTester();
|
||||
$appTester->run(['command' => 'audit']);
|
||||
|
||||
$appTester->assertCommandIsSuccessful();
|
||||
self::assertEquals('No packages - skipping audit.', trim($appTester->getDisplay(true)));
|
||||
}
|
||||
|
||||
public function testErrorAuditingLockFileWhenItIsMissing(): void
|
||||
{
|
||||
$this->initTempComposer();
|
||||
$this->createInstalledJson([self::getPackage()]);
|
||||
|
||||
$this->expectException(UnexpectedValueException::class);
|
||||
$this->expectExceptionMessage(
|
||||
"Valid composer.json and composer.lock files are required to run this command with --locked"
|
||||
);
|
||||
|
||||
$appTester = $this->getApplicationTester();
|
||||
$appTester->run(['command' => 'audit', '--locked' => true]);
|
||||
}
|
||||
|
||||
public function testAuditPackageWithNoSecurityVulnerabilities(): void
|
||||
{
|
||||
$this->initTempComposer();
|
||||
$packages = [self::getPackage()];
|
||||
$this->createInstalledJson($packages);
|
||||
$this->createComposerLock($packages);
|
||||
|
||||
$appTester = $this->getApplicationTester();
|
||||
$appTester->run(['command' => 'audit', '--locked' => true]);
|
||||
|
||||
self::assertStringContainsString(
|
||||
'No security vulnerability advisories found.',
|
||||
trim($appTester->getDisplay(true))
|
||||
);
|
||||
}
|
||||
|
||||
public function testErrorAuditWithNoInstalledPackages(): void
|
||||
{
|
||||
$this->initTempComposer(['require' => ['dummy/pkg' => '1.0.0']]);
|
||||
|
||||
$appTester = $this->getApplicationTester();
|
||||
$appTester->run(['command' => 'audit']);
|
||||
|
||||
self::assertSame(Auditor::STATUS_FAILED, $appTester->getStatusCode());
|
||||
self::assertStringContainsString(
|
||||
'No installed packages found. Please run "composer install" before running "audit"',
|
||||
trim($appTester->getDisplay(true))
|
||||
);
|
||||
}
|
||||
|
||||
public function testAuditPackageWithNoDevOptionPassed(): void
|
||||
{
|
||||
$this->initTempComposer();
|
||||
$devPackage = [self::getPackage()];
|
||||
$this->createInstalledJson([], $devPackage);
|
||||
$this->createComposerLock([], $devPackage);
|
||||
|
||||
$appTester = $this->getApplicationTester();
|
||||
$appTester->run(['command' => 'audit', '--no-dev' => true]);
|
||||
|
||||
self::assertStringContainsString(
|
||||
'No packages - skipping audit.',
|
||||
trim($appTester->getDisplay(true))
|
||||
);
|
||||
}
|
||||
|
||||
public function testAuditWithMalwareAndCustomListBothFail(): void
|
||||
{
|
||||
$packages = [
|
||||
['name' => 'safe/pkg', 'version' => '1.0.0'],
|
||||
['name' => 'malicious/pkg', 'version' => '1.0.0'],
|
||||
['name' => 'banned/pkg', 'version' => '1.0.0'],
|
||||
];
|
||||
$this->initTempComposerWithFilterLists($packages);
|
||||
$this->createComposerLockWithPackages($packages);
|
||||
|
||||
$appTester = $this->getApplicationTester();
|
||||
$exitCode = $appTester->run(['command' => 'audit', '--locked' => true]);
|
||||
|
||||
$display = $appTester->getDisplay(true);
|
||||
self::assertStringContainsString('Found 2 packages matching filters', $display);
|
||||
self::assertStringContainsString('malicious/pkg', $display);
|
||||
self::assertStringContainsString('banned/pkg', $display);
|
||||
|
||||
self::assertSame(Auditor::STATUS_FAILED, $exitCode);
|
||||
}
|
||||
|
||||
public function testAuditWithCustomListAuditReportDoesNotFail(): void
|
||||
{
|
||||
$this->initTempComposer([
|
||||
'repositories' => [
|
||||
'packages' => [
|
||||
'type' => 'package',
|
||||
'package' => $packages = [
|
||||
['name' => 'safe/pkg', 'version' => '1.0.0'],
|
||||
['name' => 'banned/pkg', 'version' => '1.0.0'],
|
||||
],
|
||||
'filter' => [
|
||||
'company-banned' => [
|
||||
['package' => 'banned/pkg', 'constraint' => '*', 'reason' => 'company policy'],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'config' => [
|
||||
'policy' => [
|
||||
'company-banned' => ['audit' => ListPolicyConfig::AUDIT_REPORT],
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$this->createComposerLockWithPackages($packages);
|
||||
|
||||
$appTester = $this->getApplicationTester();
|
||||
$exitCode = $appTester->run(['command' => 'audit', '--locked' => true]);
|
||||
|
||||
$display = $appTester->getDisplay(true);
|
||||
self::assertStringContainsString('Found 1 package matching filters', $display);
|
||||
self::assertStringContainsString('banned/pkg', $display);
|
||||
self::assertSame(Auditor::STATUS_OK, $exitCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<array{name: string, version: string}> $packages
|
||||
*/
|
||||
private function initTempComposerWithFilterLists(array $packages): void
|
||||
{
|
||||
$this->initTempComposer([
|
||||
'repositories' => [
|
||||
'packages' => [
|
||||
'type' => 'package',
|
||||
'package' => $packages,
|
||||
'filter' => [
|
||||
'malware' => [
|
||||
['package' => 'malicious/pkg', 'constraint' => '*', 'reason' => 'malware sample'],
|
||||
],
|
||||
'company-banned' => [
|
||||
['package' => 'banned/pkg', 'constraint' => '*', 'reason' => 'company policy'],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'config' => [
|
||||
'policy' => [
|
||||
'company-banned' => true,
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<array{name: string, version: string}> $packages
|
||||
*/
|
||||
private function createComposerLockWithPackages(array $packages): void
|
||||
{
|
||||
$packages = array_map(function (array $package) {
|
||||
return self::getPackage($package['name'], $package['version']);
|
||||
}, $packages);
|
||||
|
||||
$this->createInstalledJson($packages);
|
||||
$this->createComposerLock($packages);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user