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

Commit e2979aa

Browse files
authored
fixes #63 (#64)
| Q | A | --------------- | --- | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Related tickets | fixes #63 | License | MIT | Doc PR | -
1 parent a266c7e commit e2979aa

24 files changed

+3197
-48
lines changed

composer.json

+1-2
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,7 @@
3535
"narrowspark/coding-standard": "^1.2.0",
3636
"narrowspark/testing-helper": "^7.0.0",
3737
"nyholm/nsa": "^1.1.0",
38-
"phpunit/phpunit": "^7.2.0",
39-
"symfony/phpunit-bridge": "^4.0.8"
38+
"phpunit/phpunit": "^7.2.0"
4039
},
4140
"config": {
4241
"optimize-autoloader": true,

phpunit.xml.dist

-5
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,4 @@
3434
</whitelist>
3535
</filter>
3636

37-
<listeners>
38-
<listener class="Symfony\Bridge\PhpUnit\SymfonyTestsListener" />
39-
<listener class="Symfony\Bridge\PhpUnit\CoverageListener" />
40-
</listeners>
41-
4237
</phpunit>

src/Automatic/Automatic.php

+123-11
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
use Composer\Json\JsonFile;
2424
use Composer\Package\BasePackage;
2525
use Composer\Package\Locker;
26+
use Composer\Plugin\Capability\CommandProvider as CommandProviderContract;
27+
use Composer\Plugin\Capable;
2628
use Composer\Plugin\PluginEvents;
2729
use Composer\Plugin\PluginInterface;
2830
use Composer\Plugin\PreFileDownloadEvent;
@@ -46,17 +48,22 @@
4648
use Narrowspark\Automatic\Prefetcher\ParallelDownloader;
4749
use Narrowspark\Automatic\Prefetcher\Prefetcher;
4850
use Narrowspark\Automatic\Prefetcher\TruncatedComposerRepository;
51+
use Narrowspark\Automatic\Security\Audit;
52+
use Narrowspark\Automatic\Security\Command\CommandProvider;
53+
use Narrowspark\Automatic\Security\Downloader;
4954
use RecursiveDirectoryIterator;
5055
use RecursiveIteratorIterator;
5156
use ReflectionClass;
5257
use Symfony\Component\Console\Input\ArgvInput;
5358
use Symfony\Component\Console\Input\InputInterface;
5459

55-
class Automatic implements PluginInterface, EventSubscriberInterface
60+
class Automatic implements PluginInterface, EventSubscriberInterface, Capable
5661
{
5762
use ExpandTargetDirTrait;
5863
use GetGenericPropertyReaderTrait;
5964

65+
public const VERSION = '0.5.0';
66+
6067
/**
6168
* @var string
6269
*/
@@ -101,10 +108,26 @@ class Automatic implements PluginInterface, EventSubscriberInterface
101108
private $operations = [];
102109

103110
/**
104-
* @var array
111+
* List of package messages.
112+
*
113+
* @var string[]
105114
*/
106115
private $postInstallOutput = [''];
107116

117+
/**
118+
* The SecurityAdvisories database.
119+
*
120+
* @var array<string, array>
121+
*/
122+
private $securityAdvisories;
123+
124+
/**
125+
* Found package vulnerabilities.
126+
*
127+
* @var array[]
128+
*/
129+
private $foundVulnerabilities = [];
130+
108131
/**
109132
* Get the Container instance.
110133
*
@@ -131,16 +154,26 @@ public static function getSubscribedEvents(): array
131154
InstallerEvents::POST_DEPENDENCIES_SOLVING => [['populateFilesCacheDir', \PHP_INT_MAX]],
132155
PackageEvents::PRE_PACKAGE_INSTALL => [['populateFilesCacheDir', ~\PHP_INT_MAX]],
133156
PackageEvents::PRE_PACKAGE_UPDATE => [['populateFilesCacheDir', ~\PHP_INT_MAX]],
134-
PackageEvents::POST_PACKAGE_INSTALL => 'record',
135-
PackageEvents::POST_PACKAGE_UPDATE => 'record',
157+
PackageEvents::POST_PACKAGE_INSTALL => [['record'], ['auditPackage']],
158+
PackageEvents::POST_PACKAGE_UPDATE => [['record'], ['auditPackage']],
136159
PackageEvents::POST_PACKAGE_UNINSTALL => 'record',
137160
PluginEvents::PRE_FILE_DOWNLOAD => 'onFileDownload',
138-
ScriptEvents::POST_INSTALL_CMD => 'onPostInstall',
139-
ScriptEvents::POST_UPDATE_CMD => 'onPostUpdate',
161+
ScriptEvents::POST_INSTALL_CMD => [['onPostInstall'], ['auditComposerLock']],
162+
ScriptEvents::POST_UPDATE_CMD => [['onPostUpdate'], ['auditComposerLock']],
140163
ScriptEvents::POST_CREATE_PROJECT_CMD => [['onPostCreateProject', \PHP_INT_MAX], ['runSkeletonGenerator']],
141164
];
142165
}
143166

167+
/**
168+
* {@inheritdoc}
169+
*/
170+
public function getCapabilities(): array
171+
{
172+
return [
173+
CommandProviderContract::class => CommandProvider::class,
174+
];
175+
}
176+
144177
/**
145178
* {@inheritdoc}
146179
*/
@@ -165,6 +198,16 @@ public function activate(Composer $composer, IOInterface $io): void
165198

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

201+
$extra = $this->container->get('composer-extra');
202+
$downloader = new Downloader();
203+
204+
if (isset($extra[Util::COMPOSER_EXTRA_KEY]['audit']['timeout'])) {
205+
$downloader->setTimeout($extra[Util::COMPOSER_EXTRA_KEY]['audit']['timeout']);
206+
}
207+
$this->container->set(Audit::class, static function (Container $container) use ($downloader) {
208+
return new Audit($container->get('vendor-dir'), $downloader);
209+
});
210+
168211
/** @var \Composer\Installer\InstallationManager $installationManager */
169212
$installationManager = $this->container->get(Composer::class)->getInstallationManager();
170213
$installationManager->addInstaller($this->container->get(ConfiguratorInstaller::class));
@@ -173,7 +216,7 @@ public function activate(Composer $composer, IOInterface $io): void
173216
/** @var \Narrowspark\Automatic\LegacyTagsManager $tagsManager */
174217
$tagsManager = $this->container->get(LegacyTagsManager::class);
175218

176-
$this->configureLegacyTagsManager($io, $tagsManager);
219+
$this->configureLegacyTagsManager($io, $tagsManager, $extra);
177220

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

@@ -191,6 +234,8 @@ public function activate(Composer $composer, IOInterface $io): void
191234
$container->get(InputInterface::class)
192235
);
193236
});
237+
238+
$this->securityAdvisories = $this->container->get(Audit::class)->getSecurityAdvisories($this->container->get(IOInterface::class));
194239
}
195240

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

207-
$this->container->get(IOInterface::class)->write($this->postInstallOutput);
252+
/** @var \Composer\IO\IOInterface $io */
253+
$io = $this->container->get(IOInterface::class);
254+
255+
$io->write($this->postInstallOutput);
256+
257+
$count = \count(\array_filter($this->foundVulnerabilities));
258+
259+
if ($count !== 0) {
260+
$io->write('<error>[!]</> Audit Security Report: ' . \sprintf('%s vulnerabilit%s found - run "composer audit" for more information', $count, $count === 1 ? 'y' : 'ies'));
261+
} else {
262+
$io->write('<fg=black;bg=green>[+]</> Audit Security Report: No known vulnerabilities found');
263+
}
264+
}
265+
266+
/**
267+
* Audit composer.lock.
268+
*
269+
* @param \Composer\Script\Event $event
270+
*
271+
* @return void
272+
*/
273+
public function auditComposerLock(Event $event): void
274+
{
275+
if (\count($this->foundVulnerabilities) !== 0) {
276+
return;
277+
}
278+
279+
$data = $this->container->get(Audit::class)->checkLock(Util::getComposerLockFile());
280+
281+
if (\count($data) === 0) {
282+
return;
283+
}
284+
285+
$this->foundVulnerabilities += $data[0];
208286
}
209287

210288
/**
@@ -229,6 +307,40 @@ public function record(PackageEvent $event): void
229307
}
230308
}
231309

310+
/**
311+
* Audit composer package operations.
312+
*
313+
* @param \Composer\Installer\PackageEvent $event
314+
*
315+
* @return void
316+
*/
317+
public function auditPackage(PackageEvent $event): void
318+
{
319+
$operation = $event->getOperation();
320+
321+
if ($operation instanceof UninstallOperation) {
322+
return;
323+
}
324+
325+
if ($operation instanceof UpdateOperation) {
326+
$composerPackage = $operation->getTargetPackage();
327+
} else {
328+
$composerPackage = $operation->getPackage();
329+
}
330+
331+
$data = $this->container->get(Audit::class)->checkPackage(
332+
$composerPackage->getName(),
333+
$composerPackage->getVersion(),
334+
$this->securityAdvisories
335+
);
336+
337+
if (\count($data) === 0) {
338+
return;
339+
}
340+
341+
$this->foundVulnerabilities += $data[0];
342+
}
343+
232344
/**
233345
* Execute on composer create project event.
234346
*
@@ -288,7 +400,7 @@ public function runSkeletonGenerator(Event $event): void
288400
$lock->read();
289401

290402
if ($lock->has(SkeletonInstaller::LOCK_KEY)) {
291-
$this->operations = [];
403+
$this->operations = $this->foundVulnerabilities = [];
292404

293405
$skeletonGenerator = new SkeletonGenerator(
294406
$this->container->get(IOInterface::class),
@@ -862,12 +974,12 @@ private static function getComposerVersion(): string
862974
*
863975
* @param \Composer\IO\IOInterface $io
864976
* @param \Narrowspark\Automatic\LegacyTagsManager $tagsManager
977+
* @param array $extra
865978
*
866979
* @return void
867980
*/
868-
private function configureLegacyTagsManager(IOInterface $io, LegacyTagsManager $tagsManager): void
981+
private function configureLegacyTagsManager(IOInterface $io, LegacyTagsManager $tagsManager, array $extra): void
869982
{
870-
$extra = $this->container->get('composer-extra');
871983
$envRequire = \getenv('AUTOMATIC_REQUIRE');
872984

873985
if ($envRequire !== false) {

src/Automatic/CommandProvider.php

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
declare(strict_types=1);
3+
namespace Narrowspark\Automatic\Security\Command;
4+
5+
use Composer\Plugin\Capability\CommandProvider as CommandProviderContract;
6+
7+
/**
8+
* @internal
9+
*/
10+
final class CommandProvider implements CommandProviderContract
11+
{
12+
/**
13+
* {@inheritdoc}
14+
*/
15+
public function getCommands(): array
16+
{
17+
return [new AuditCommand()];
18+
}
19+
}

src/Automatic/Container.php

+1-6
Original file line numberDiff line numberDiff line change
@@ -164,12 +164,7 @@ public function __construct(Composer $composer, IOInterface $io)
164164
}
165165

166166
/**
167-
* Set a new entry to the container.
168-
*
169-
* @param string $id
170-
* @param callable $callback
171-
*
172-
* @return void
167+
* {@inheritdoc}
173168
*/
174169
public function set(string $id, callable $callback): void
175170
{

src/Automatic/Contract/Container.php

+10
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,16 @@ interface Container
1313
*/
1414
public function get(string $id);
1515

16+
/**
17+
* Set a new entry to the container.
18+
*
19+
* @param string $id
20+
* @param callable $callback
21+
*
22+
* @return void
23+
*/
24+
public function set(string $id, callable $callback): void;
25+
1626
/**
1727
* Returns all container entries.
1828
*
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
declare(strict_types=1);
3+
namespace Narrowspark\Automatic\Contract\Security;
4+
5+
use Symfony\Component\Console\Style\SymfonyStyle;
6+
7+
interface Formatter
8+
{
9+
/**
10+
* Displays a security report.
11+
*
12+
* @param \Symfony\Component\Console\Style\SymfonyStyle $output
13+
* @param array $vulnerabilities An array of vulnerabilities
14+
*
15+
* @return void
16+
*/
17+
public function displayResults(SymfonyStyle $output, array $vulnerabilities): void;
18+
}

0 commit comments

Comments
 (0)