diff --git a/.github/workflows/coding-standard.yml b/.github/workflows/coding-standard.yml index 3b10a080..6f51014d 100644 --- a/.github/workflows/coding-standard.yml +++ b/.github/workflows/coding-standard.yml @@ -12,8 +12,8 @@ jobs: uses: shivammathur/setup-php@master with: php-version: 7.2 + - run: composer global require narrowspark/coding-standard:3.1.0 --no-interaction --prefer-dist --no-progress --profile --no-suggest --optimize-autoloader - name: 'lint php code' run: | - composer global require narrowspark/coding-standard:3.1.0 --no-interaction --prefer-dist --no-progress --profile --no-suggest --optimize-autoloader cd $GITHUB_WORKSPACE /home/runner/.composer/vendor/bin/php-cs-fixer fix -v --dry-run --stop-on-violation diff --git a/.github/workflows/static-analyze-phpstan.yml b/.github/workflows/static-analyze-phpstan.yml index 493538c3..889f719d 100644 --- a/.github/workflows/static-analyze-phpstan.yml +++ b/.github/workflows/static-analyze-phpstan.yml @@ -12,7 +12,6 @@ jobs: uses: shivammathur/setup-php@master with: php-version: 7.2 + - run: composer install - name: 'analyze php code' - run: | - composer install --no-interaction --prefer-dist --no-progress --profile --no-suggest - composer phpstan + run: composer phpstan diff --git a/composer.json b/composer.json index 8fafea9f..f52e3e7f 100644 --- a/composer.json +++ b/composer.json @@ -92,6 +92,8 @@ "infection": "infection", "test-common": "phpunit --testsuite=\"Narrowspark Automatic Common Test Suite\"", "test-automatic": "phpunit --testsuite=\"Narrowspark Automatic Test Suite\"", + "test-prefetcher": "phpunit --testsuite=\"Narrowspark Automatic Prefetcher Test Suite\"", + "test-security": "phpunit --testsuite=\"Narrowspark Automatic Security Test Suite\"", "changelog": "changelog-generator generate --config=\".changelog\" --file --prepend", "auto-scripts": { diff --git a/src/Prefetcher/Plugin.php b/src/Prefetcher/Plugin.php index 6ec8d530..d1941104 100644 --- a/src/Prefetcher/Plugin.php +++ b/src/Prefetcher/Plugin.php @@ -19,13 +19,10 @@ use Composer\Console\Application; use Composer\DependencyResolver\Pool; use Composer\EventDispatcher\EventSubscriberInterface; -use Composer\Installer; use Composer\Installer\InstallerEvent; use Composer\Installer\InstallerEvents; use Composer\Installer\PackageEvents; -use Composer\Installer\SuggestedPackagesReporter; use Composer\IO\IOInterface; -use Composer\IO\NullIO; use Composer\Package\BasePackage; use Composer\Plugin\PluginEvents; use Composer\Plugin\PluginInterface; @@ -379,7 +376,7 @@ private function getErrorMessage(): ?string } /** - * Extend the composer object with some automatic settings. + * Extend the composer object with some automatic prefetcher settings. * * @param array $backtrace * @param \Narrowspark\Automatic\Prefetcher\Contract\LegacyTagsManager $tagsManager @@ -388,16 +385,6 @@ private function getErrorMessage(): ?string */ private function extendComposer($backtrace, LegacyTagsManagerContract $tagsManager): void { - foreach ($backtrace as $trace) { - if (isset($trace['object']) && $trace['object'] instanceof Installer) { - /** @var \Composer\Installer $installer */ - $installer = $trace['object']; - $installer->setSuggestedPackagesReporter(new SuggestedPackagesReporter(new NullIO())); - - break; - } - } - foreach ($backtrace as $trace) { if (! isset($trace['object']) || ! isset($trace['args'][0])) { continue; diff --git a/src/Security/Audit.php b/src/Security/Audit.php index 53f9cd68..f8b58518 100644 --- a/src/Security/Audit.php +++ b/src/Security/Audit.php @@ -25,13 +25,13 @@ final class Audit { /** @var string */ - private const SECURITY_ADVISORIES_BASE_URL = 'https://raw.githubusercontent.com/narrowspark/security-advisories/master/'; + public const SECURITY_ADVISORIES_BASE_URL = 'https://raw.githubusercontent.com/narrowspark/security-advisories/master/'; /** @var string */ - private const SECURITY_ADVISORIES_SHA = 'security-advisories-sha'; + public const SECURITY_ADVISORIES_SHA = 'security-advisories-sha'; /** @var string */ - private const SECURITY_ADVISORIES = 'security-advisories.json'; + public const SECURITY_ADVISORIES = 'security-advisories.json'; /** * A Filesystem instance. @@ -61,18 +61,27 @@ final class Audit */ private $downloader; + /** + * Sha of the security security-advisories.json. + * + * @var string + */ + private $sha; + /** * Create a new Audit instance. * * @param string $composerVendorPath * @param \Narrowspark\Automatic\Security\Contract\Downloader $downloader + * @param string $sha */ - public function __construct(string $composerVendorPath, DownloaderContract $downloader) + public function __construct(string $composerVendorPath, DownloaderContract $downloader, string $sha) { $this->composerVendorPath = $composerVendorPath; $this->downloader = $downloader; $this->versionParser = new VersionParser(); $this->filesystem = new Filesystem(); + $this->sha = $sha; } /** @@ -153,8 +162,6 @@ public function checkLock(string $lock): array */ public function getSecurityAdvisories(?IOInterface $io = null): array { - $sha = $this->downloader->download(self::SECURITY_ADVISORIES_BASE_URL . self::SECURITY_ADVISORIES_SHA); - $narrowsparkAutomaticPath = $this->composerVendorPath . \DIRECTORY_SEPARATOR . 'narrowspark' . \DIRECTORY_SEPARATOR . 'automatic' . \DIRECTORY_SEPARATOR; if (! $this->filesystem->exists($narrowsparkAutomaticPath)) { @@ -167,7 +174,7 @@ public function getSecurityAdvisories(?IOInterface $io = null): array if ($this->filesystem->exists($securityAdvisoriesShaPath)) { $oldSha = \file_get_contents($securityAdvisoriesShaPath); - if ($oldSha === $sha) { + if ($oldSha === $this->sha) { return \json_decode((string) \file_get_contents($securityAdvisoriesPath), true); } } @@ -178,10 +185,10 @@ public function getSecurityAdvisories(?IOInterface $io = null): array $securityAdvisories = $this->downloader->download(self::SECURITY_ADVISORIES_BASE_URL . self::SECURITY_ADVISORIES); - $this->filesystem->dumpFile($securityAdvisoriesShaPath, $sha); + $this->filesystem->dumpFile($securityAdvisoriesShaPath, $this->sha); $this->filesystem->dumpFile($securityAdvisoriesPath, $securityAdvisories); - return \json_decode((string) \file_get_contents($securityAdvisoriesPath), true); + return \json_decode($securityAdvisories, true); } /** diff --git a/src/Security/Command/AuditCommand.php b/src/Security/Command/AuditCommand.php index 95301dbc..5c5bb745 100644 --- a/src/Security/Command/AuditCommand.php +++ b/src/Security/Command/AuditCommand.php @@ -74,8 +74,20 @@ protected function execute(InputInterface $input, OutputInterface $output): int $downloader->setTimeout((int) $timeout); } + try { + $output->write('Narrowspark Automatic Security Audit is checking for internet connection...', true, OutputInterface::VERBOSITY_VERBOSE); + + $sha = $downloader->download(Audit::SECURITY_ADVISORIES_BASE_URL . Audit::SECURITY_ADVISORIES_SHA); + } catch (RuntimeException $exception) { + $output->write('Connecting to github.com failed.'); + + $downloader = $timeout = null; + + return 1; + } + $config = Factory::createConfig(new NullIO()); - $audit = new Audit(\rtrim($config->get('vendor-dir'), '/'), $downloader); + $audit = new Audit(\rtrim($config->get('vendor-dir'), '/'), $downloader, $sha); /** @var null|string $composerFile */ $composerFile = $input->getOption('composer-lock'); diff --git a/src/Security/Contract/Downloader.php b/src/Security/Contract/Downloader.php index e34c707d..adcabeae 100644 --- a/src/Security/Contract/Downloader.php +++ b/src/Security/Contract/Downloader.php @@ -29,6 +29,8 @@ public function setTimeout(int $timeout): void; * * @param string $url * + * @throws \Narrowspark\Automatic\Security\Contract\Exception\RuntimeException + * * @return string */ public function download(string $url): string; diff --git a/src/Security/Plugin.php b/src/Security/Plugin.php index 2f5b88e7..6c6cba30 100644 --- a/src/Security/Plugin.php +++ b/src/Security/Plugin.php @@ -26,6 +26,7 @@ use Composer\Script\Event; use Composer\Script\ScriptEvents as ComposerScriptEvents; use FilesystemIterator; +use Narrowspark\Automatic\Security\Contract\Downloader; use Narrowspark\Automatic\Security\Contract\Exception\RuntimeException; use Narrowspark\Automatic\Security\Downloader\ComposerDownloader; use Narrowspark\Automatic\Security\Downloader\CurlDownloader; @@ -85,6 +86,13 @@ final class Plugin implements Capable, EventSubscriberInterface, PluginInterface */ private static $activated = true; + /** + * Sha of the security security-advisories.json. + * + * @var string + */ + private $securitySha; + /** * {@inheritdoc} */ @@ -107,11 +115,25 @@ public static function getSubscribedEvents(): array */ public function activate(Composer $composer, IOInterface $io): void { - if (($errorMessage = $this->getErrorMessage()) !== null) { + if (\extension_loaded('curl')) { + $downloader = new CurlDownloader(); + } else { + $downloader = new ComposerDownloader(); + } + + $extra = $composer->getPackage()->getExtra(); + + if (isset($extra[self::COMPOSER_EXTRA_KEY]['timeout'])) { + $downloader->setTimeout($extra[self::COMPOSER_EXTRA_KEY]['timeout']); + } + + if (($errorMessage = $this->getErrorMessage($io, $downloader)) !== null) { self::$activated = false; $io->writeError('Narrowspark Automatic Security Audit has been disabled. ' . $errorMessage . ''); + $downloader = $extra = null; + return; } @@ -127,19 +149,7 @@ public function activate(Composer $composer, IOInterface $io): void $this->composer = $composer; $this->io = $io; - if (\extension_loaded('curl')) { - $downloader = new CurlDownloader(); - } else { - $downloader = new ComposerDownloader(); - } - - $extra = $composer->getPackage()->getExtra(); - - if (isset($extra[self::COMPOSER_EXTRA_KEY]['timeout'])) { - $downloader->setTimeout($extra[self::COMPOSER_EXTRA_KEY]['timeout']); - } - - $this->audit = new Audit(\rtrim($composer->getConfig()->get('vendor-dir'), '/'), $downloader); + $this->audit = new Audit(\rtrim($composer->getConfig()->get('vendor-dir'), '/'), $downloader, $this->securitySha); $this->securityAdvisories = $this->audit->getSecurityAdvisories($io); } @@ -231,9 +241,12 @@ public function auditComposerLock(Event $event): void /** * Check if automatic can be activated. * + * @param \Composer\IO\IOInterface $io + * @param Downloader $downloader + * * @return null|string */ - private function getErrorMessage(): ?string + private function getErrorMessage(IOInterface $io, Downloader $downloader): ?string { // @codeCoverageIgnoreStart if (\version_compare(self::getComposerVersion(), '1.7.0', '<')) { @@ -241,6 +254,14 @@ private function getErrorMessage(): ?string } // @codeCoverageIgnoreEnd + try { + $io->writeError('Narrowspark Automatic Security Audit is checking for internet connection...', true, IOInterface::VERBOSE); + + $this->securitySha = $downloader->download(Audit::SECURITY_ADVISORIES_BASE_URL . Audit::SECURITY_ADVISORIES_SHA); + } catch (RuntimeException $exception) { + return 'Connecting to github.com failed.'; + } + return null; } diff --git a/tests/Security/AuditTest.php b/tests/Security/AuditTest.php index e8d19562..a68b7b00 100644 --- a/tests/Security/AuditTest.php +++ b/tests/Security/AuditTest.php @@ -40,7 +40,9 @@ protected function setUp(): void $this->path = __DIR__ . 'audit'; - $this->audit = new Audit($this->path, new ComposerDownloader()); + $downloader = new ComposerDownloader(); + + $this->audit = new Audit($this->path, $downloader, $downloader->download(Audit::SECURITY_ADVISORIES_BASE_URL . Audit::SECURITY_ADVISORIES_SHA)); } /** diff --git a/tests/Security/PluginTest.php b/tests/Security/PluginTest.php index f0cdeffb..52a28f09 100644 --- a/tests/Security/PluginTest.php +++ b/tests/Security/PluginTest.php @@ -84,6 +84,10 @@ public function testActivate(): void ->once() ->with('Downloading the Security Advisories database...', true, IOInterface::VERBOSE); + $this->ioMock->shouldReceive('writeError') + ->once() + ->with('Narrowspark Automatic Security Audit is checking for internet connection...', true, IOInterface::VERBOSE); + $this->securityPlugin->activate($this->composerMock, $this->ioMock); } @@ -149,7 +153,9 @@ public function testAuditPackage(): void ->once() ->andReturn($operationMock); - $audit = new Audit($this->tmpFolder, new ComposerDownloader()); + $downloader = new ComposerDownloader(); + + $audit = new Audit($this->tmpFolder, $downloader, $downloader->download(Audit::SECURITY_ADVISORIES_BASE_URL . Audit::SECURITY_ADVISORIES_SHA)); NSA::setProperty($this->securityPlugin, 'audit', $audit); NSA::setProperty($this->securityPlugin, 'securityAdvisories', $audit->getSecurityAdvisories()); @@ -193,7 +199,9 @@ public function testAuditPackageWithUpdate(): void ->once() ->andReturn($operationMock); - $audit = new Audit($this->tmpFolder, new ComposerDownloader()); + $downloader = new ComposerDownloader(); + + $audit = new Audit($this->tmpFolder, $downloader, $downloader->download(Audit::SECURITY_ADVISORIES_BASE_URL . Audit::SECURITY_ADVISORIES_SHA)); NSA::setProperty($this->securityPlugin, 'audit', $audit); NSA::setProperty($this->securityPlugin, 'securityAdvisories', $audit->getSecurityAdvisories()); @@ -207,7 +215,9 @@ public function testAuditComposerLock(): void { \putenv('COMPOSER=' . __DIR__ . \DIRECTORY_SEPARATOR . 'Fixture' . \DIRECTORY_SEPARATOR . 'symfony_2.5.2_composer.json'); - $audit = new Audit($this->tmpFolder, new ComposerDownloader()); + $downloader = new ComposerDownloader(); + + $audit = new Audit($this->tmpFolder, $downloader, $downloader->download(Audit::SECURITY_ADVISORIES_BASE_URL . Audit::SECURITY_ADVISORIES_SHA)); NSA::setProperty($this->securityPlugin, 'audit', $audit);