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