23
23
use Composer \Json \JsonFile ;
24
24
use Composer \Package \BasePackage ;
25
25
use Composer \Package \Locker ;
26
+ use Composer \Plugin \Capability \CommandProvider as CommandProviderContract ;
27
+ use Composer \Plugin \Capable ;
26
28
use Composer \Plugin \PluginEvents ;
27
29
use Composer \Plugin \PluginInterface ;
28
30
use Composer \Plugin \PreFileDownloadEvent ;
46
48
use Narrowspark \Automatic \Prefetcher \ParallelDownloader ;
47
49
use Narrowspark \Automatic \Prefetcher \Prefetcher ;
48
50
use Narrowspark \Automatic \Prefetcher \TruncatedComposerRepository ;
51
+ use Narrowspark \Automatic \Security \Audit ;
52
+ use Narrowspark \Automatic \Security \Command \CommandProvider ;
53
+ use Narrowspark \Automatic \Security \Downloader ;
49
54
use RecursiveDirectoryIterator ;
50
55
use RecursiveIteratorIterator ;
51
56
use ReflectionClass ;
52
57
use Symfony \Component \Console \Input \ArgvInput ;
53
58
use Symfony \Component \Console \Input \InputInterface ;
54
59
55
- class Automatic implements PluginInterface, EventSubscriberInterface
60
+ class Automatic implements PluginInterface, EventSubscriberInterface, Capable
56
61
{
57
62
use ExpandTargetDirTrait;
58
63
use GetGenericPropertyReaderTrait;
59
64
65
+ public const VERSION = '0.5.0 ' ;
66
+
60
67
/**
61
68
* @var string
62
69
*/
@@ -101,10 +108,26 @@ class Automatic implements PluginInterface, EventSubscriberInterface
101
108
private $ operations = [];
102
109
103
110
/**
104
- * @var array
111
+ * List of package messages.
112
+ *
113
+ * @var string[]
105
114
*/
106
115
private $ postInstallOutput = ['' ];
107
116
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
+
108
131
/**
109
132
* Get the Container instance.
110
133
*
@@ -131,16 +154,26 @@ public static function getSubscribedEvents(): array
131
154
InstallerEvents::POST_DEPENDENCIES_SOLVING => [['populateFilesCacheDir ' , \PHP_INT_MAX ]],
132
155
PackageEvents::PRE_PACKAGE_INSTALL => [['populateFilesCacheDir ' , ~\PHP_INT_MAX ]],
133
156
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 ' ]] ,
136
159
PackageEvents::POST_PACKAGE_UNINSTALL => 'record ' ,
137
160
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 ' ]] ,
140
163
ScriptEvents::POST_CREATE_PROJECT_CMD => [['onPostCreateProject ' , \PHP_INT_MAX ], ['runSkeletonGenerator ' ]],
141
164
];
142
165
}
143
166
167
+ /**
168
+ * {@inheritdoc}
169
+ */
170
+ public function getCapabilities (): array
171
+ {
172
+ return [
173
+ CommandProviderContract::class => CommandProvider::class,
174
+ ];
175
+ }
176
+
144
177
/**
145
178
* {@inheritdoc}
146
179
*/
@@ -165,6 +198,16 @@ public function activate(Composer $composer, IOInterface $io): void
165
198
166
199
$ this ->container = new Container ($ composer , $ io );
167
200
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
+
168
211
/** @var \Composer\Installer\InstallationManager $installationManager */
169
212
$ installationManager = $ this ->container ->get (Composer::class)->getInstallationManager ();
170
213
$ installationManager ->addInstaller ($ this ->container ->get (ConfiguratorInstaller::class));
@@ -173,7 +216,7 @@ public function activate(Composer $composer, IOInterface $io): void
173
216
/** @var \Narrowspark\Automatic\LegacyTagsManager $tagsManager */
174
217
$ tagsManager = $ this ->container ->get (LegacyTagsManager::class);
175
218
176
- $ this ->configureLegacyTagsManager ($ io , $ tagsManager );
219
+ $ this ->configureLegacyTagsManager ($ io , $ tagsManager, $ extra );
177
220
178
221
$ composer ->setRepositoryManager ($ this ->extendRepositoryManager ($ composer , $ io , $ tagsManager ));
179
222
@@ -191,6 +234,8 @@ public function activate(Composer $composer, IOInterface $io): void
191
234
$ container ->get (InputInterface::class)
192
235
);
193
236
});
237
+
238
+ $ this ->securityAdvisories = $ this ->container ->get (Audit::class)->getSecurityAdvisories ($ this ->container ->get (IOInterface::class));
194
239
}
195
240
196
241
/**
@@ -204,7 +249,40 @@ public function postInstallOut(Event $event): void
204
249
{
205
250
$ event ->stopPropagation ();
206
251
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 ];
208
286
}
209
287
210
288
/**
@@ -229,6 +307,40 @@ public function record(PackageEvent $event): void
229
307
}
230
308
}
231
309
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
+
232
344
/**
233
345
* Execute on composer create project event.
234
346
*
@@ -288,7 +400,7 @@ public function runSkeletonGenerator(Event $event): void
288
400
$ lock ->read ();
289
401
290
402
if ($ lock ->has (SkeletonInstaller::LOCK_KEY )) {
291
- $ this ->operations = [];
403
+ $ this ->operations = $ this -> foundVulnerabilities = [];
292
404
293
405
$ skeletonGenerator = new SkeletonGenerator (
294
406
$ this ->container ->get (IOInterface::class),
@@ -862,12 +974,12 @@ private static function getComposerVersion(): string
862
974
*
863
975
* @param \Composer\IO\IOInterface $io
864
976
* @param \Narrowspark\Automatic\LegacyTagsManager $tagsManager
977
+ * @param array $extra
865
978
*
866
979
* @return void
867
980
*/
868
- private function configureLegacyTagsManager (IOInterface $ io , LegacyTagsManager $ tagsManager ): void
981
+ private function configureLegacyTagsManager (IOInterface $ io , LegacyTagsManager $ tagsManager, array $ extra ): void
869
982
{
870
- $ extra = $ this ->container ->get ('composer-extra ' );
871
983
$ envRequire = \getenv ('AUTOMATIC_REQUIRE ' );
872
984
873
985
if ($ envRequire !== false ) {
0 commit comments