Skip to content
This repository has been archived by the owner on Mar 1, 2023. It is now read-only.

fixes #63 #64

Merged
merged 7 commits into from
Sep 7, 2018
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@
"narrowspark/coding-standard": "^1.2.0",
"narrowspark/testing-helper": "^7.0.0",
"nyholm/nsa": "^1.1.0",
"phpunit/phpunit": "^7.2.0",
"symfony/phpunit-bridge": "^4.0.8"
"phpunit/phpunit": "^7.2.0"
},
"config": {
"optimize-autoloader": true,
Expand Down
5 changes: 0 additions & 5 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,4 @@
</whitelist>
</filter>

<listeners>
<listener class="Symfony\Bridge\PhpUnit\SymfonyTestsListener" />
<listener class="Symfony\Bridge\PhpUnit\CoverageListener" />
</listeners>

</phpunit>
134 changes: 123 additions & 11 deletions src/Automatic/Automatic.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
use Composer\Json\JsonFile;
use Composer\Package\BasePackage;
use Composer\Package\Locker;
use Composer\Plugin\Capability\CommandProvider as CommandProviderContract;
use Composer\Plugin\Capable;
use Composer\Plugin\PluginEvents;
use Composer\Plugin\PluginInterface;
use Composer\Plugin\PreFileDownloadEvent;
Expand All @@ -46,17 +48,22 @@
use Narrowspark\Automatic\Prefetcher\ParallelDownloader;
use Narrowspark\Automatic\Prefetcher\Prefetcher;
use Narrowspark\Automatic\Prefetcher\TruncatedComposerRepository;
use Narrowspark\Automatic\Security\Audit;
use Narrowspark\Automatic\Security\Command\CommandProvider;
use Narrowspark\Automatic\Security\Downloader;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use ReflectionClass;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\InputInterface;

class Automatic implements PluginInterface, EventSubscriberInterface
class Automatic implements PluginInterface, EventSubscriberInterface, Capable
{
use ExpandTargetDirTrait;
use GetGenericPropertyReaderTrait;

public const VERSION = '0.5.0';

/**
* @var string
*/
Expand Down Expand Up @@ -101,10 +108,26 @@ class Automatic implements PluginInterface, EventSubscriberInterface
private $operations = [];

/**
* @var array
* List of package messages.
*
* @var string[]
*/
private $postInstallOutput = [''];

/**
* The SecurityAdvisories database.
*
* @var array<string, array>
*/
private $securityAdvisories;

/**
* Found package vulnerabilities.
*
* @var array[]
*/
private $foundVulnerabilities = [];

/**
* Get the Container instance.
*
Expand All @@ -131,16 +154,26 @@ public static function getSubscribedEvents(): array
InstallerEvents::POST_DEPENDENCIES_SOLVING => [['populateFilesCacheDir', \PHP_INT_MAX]],
PackageEvents::PRE_PACKAGE_INSTALL => [['populateFilesCacheDir', ~\PHP_INT_MAX]],
PackageEvents::PRE_PACKAGE_UPDATE => [['populateFilesCacheDir', ~\PHP_INT_MAX]],
PackageEvents::POST_PACKAGE_INSTALL => 'record',
PackageEvents::POST_PACKAGE_UPDATE => 'record',
PackageEvents::POST_PACKAGE_INSTALL => [['record'], ['auditPackage']],
PackageEvents::POST_PACKAGE_UPDATE => [['record'], ['auditPackage']],
PackageEvents::POST_PACKAGE_UNINSTALL => 'record',
PluginEvents::PRE_FILE_DOWNLOAD => 'onFileDownload',
ScriptEvents::POST_INSTALL_CMD => 'onPostInstall',
ScriptEvents::POST_UPDATE_CMD => 'onPostUpdate',
ScriptEvents::POST_INSTALL_CMD => [['onPostInstall'], ['auditComposerLock']],
ScriptEvents::POST_UPDATE_CMD => [['onPostUpdate'], ['auditComposerLock']],
ScriptEvents::POST_CREATE_PROJECT_CMD => [['onPostCreateProject', \PHP_INT_MAX], ['runSkeletonGenerator']],
];
}

/**
* {@inheritdoc}
*/
public function getCapabilities(): array
{
return [
CommandProviderContract::class => CommandProvider::class,
];
}

/**
* {@inheritdoc}
*/
Expand All @@ -165,6 +198,16 @@ public function activate(Composer $composer, IOInterface $io): void

$this->container = new Container($composer, $io);

$extra = $this->container->get('composer-extra');
$downloader = new Downloader();

if (isset($extra[Util::COMPOSER_EXTRA_KEY]['audit']['timeout'])) {
$downloader->setTimeout($extra[Util::COMPOSER_EXTRA_KEY]['audit']['timeout']);
}
$this->container->set(Audit::class, static function (Container $container) use ($downloader) {
return new Audit($container->get('vendor-dir'), $downloader);
});

/** @var \Composer\Installer\InstallationManager $installationManager */
$installationManager = $this->container->get(Composer::class)->getInstallationManager();
$installationManager->addInstaller($this->container->get(ConfiguratorInstaller::class));
Expand All @@ -173,7 +216,7 @@ public function activate(Composer $composer, IOInterface $io): void
/** @var \Narrowspark\Automatic\LegacyTagsManager $tagsManager */
$tagsManager = $this->container->get(LegacyTagsManager::class);

$this->configureLegacyTagsManager($io, $tagsManager);
$this->configureLegacyTagsManager($io, $tagsManager, $extra);

$composer->setRepositoryManager($this->extendRepositoryManager($composer, $io, $tagsManager));

Expand All @@ -191,6 +234,8 @@ public function activate(Composer $composer, IOInterface $io): void
$container->get(InputInterface::class)
);
});

$this->securityAdvisories = $this->container->get(Audit::class)->getSecurityAdvisories($this->container->get(IOInterface::class));
}

/**
Expand All @@ -204,7 +249,40 @@ public function postInstallOut(Event $event): void
{
$event->stopPropagation();

$this->container->get(IOInterface::class)->write($this->postInstallOutput);
/** @var \Composer\IO\IOInterface $io */
$io = $this->container->get(IOInterface::class);

$io->write($this->postInstallOutput);

$count = \count(\array_filter($this->foundVulnerabilities));

if ($count !== 0) {
$io->write('<error>[!]</> Audit Security Report: ' . \sprintf('%s vulnerabilit%s found - run "composer audit" for more information', $count, $count === 1 ? 'y' : 'ies'));
} else {
$io->write('<fg=black;bg=green>[+]</> Audit Security Report: No known vulnerabilities found');
}
}

/**
* Audit composer.lock.
*
* @param \Composer\Script\Event $event
*
* @return void
*/
public function auditComposerLock(Event $event): void
{
if (\count($this->foundVulnerabilities) !== 0) {
return;
}

$data = $this->container->get(Audit::class)->checkLock(Util::getComposerLockFile());

if (\count($data) === 0) {
return;
}

$this->foundVulnerabilities += $data[0];
}

/**
Expand All @@ -229,6 +307,40 @@ public function record(PackageEvent $event): void
}
}

/**
* Audit composer package operations.
*
* @param \Composer\Installer\PackageEvent $event
*
* @return void
*/
public function auditPackage(PackageEvent $event): void
{
$operation = $event->getOperation();

if ($operation instanceof UninstallOperation) {
return;
}

if ($operation instanceof UpdateOperation) {
$composerPackage = $operation->getTargetPackage();
} else {
$composerPackage = $operation->getPackage();
}

$data = $this->container->get(Audit::class)->checkPackage(
$composerPackage->getName(),
$composerPackage->getVersion(),
$this->securityAdvisories
);

if (\count($data) === 0) {
return;
}

$this->foundVulnerabilities += $data[0];
}

/**
* Execute on composer create project event.
*
Expand Down Expand Up @@ -288,7 +400,7 @@ public function runSkeletonGenerator(Event $event): void
$lock->read();

if ($lock->has(SkeletonInstaller::LOCK_KEY)) {
$this->operations = [];
$this->operations = $this->foundVulnerabilities = [];

$skeletonGenerator = new SkeletonGenerator(
$this->container->get(IOInterface::class),
Expand Down Expand Up @@ -862,12 +974,12 @@ private static function getComposerVersion(): string
*
* @param \Composer\IO\IOInterface $io
* @param \Narrowspark\Automatic\LegacyTagsManager $tagsManager
* @param array $extra
*
* @return void
*/
private function configureLegacyTagsManager(IOInterface $io, LegacyTagsManager $tagsManager): void
private function configureLegacyTagsManager(IOInterface $io, LegacyTagsManager $tagsManager, array $extra): void
{
$extra = $this->container->get('composer-extra');
$envRequire = \getenv('AUTOMATIC_REQUIRE');

if ($envRequire !== false) {
Expand Down
19 changes: 19 additions & 0 deletions src/Automatic/CommandProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace Narrowspark\Automatic\Security\Command;

use Composer\Plugin\Capability\CommandProvider as CommandProviderContract;

/**
* @internal
*/
final class CommandProvider implements CommandProviderContract
{
/**
* {@inheritdoc}
*/
public function getCommands(): array
{
return [new AuditCommand()];
}
}
7 changes: 1 addition & 6 deletions src/Automatic/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -164,12 +164,7 @@ public function __construct(Composer $composer, IOInterface $io)
}

/**
* Set a new entry to the container.
*
* @param string $id
* @param callable $callback
*
* @return void
* {@inheritdoc}
*/
public function set(string $id, callable $callback): void
{
Expand Down
10 changes: 10 additions & 0 deletions src/Automatic/Contract/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@ interface Container
*/
public function get(string $id);

/**
* Set a new entry to the container.
*
* @param string $id
* @param callable $callback
*
* @return void
*/
public function set(string $id, callable $callback): void;

/**
* Returns all container entries.
*
Expand Down
18 changes: 18 additions & 0 deletions src/Automatic/Contract/Security/Formatter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace Narrowspark\Automatic\Contract\Security;

use Symfony\Component\Console\Style\SymfonyStyle;

interface Formatter
{
/**
* Displays a security report.
*
* @param \Symfony\Component\Console\Style\SymfonyStyle $output
* @param array $vulnerabilities An array of vulnerabilities
*
* @return void
*/
public function displayResults(SymfonyStyle $output, array $vulnerabilities): void;
}
Loading