diff --git a/composer.json b/composer.json
index c37e842b..37856b99 100644
--- a/composer.json
+++ b/composer.json
@@ -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,
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 760cfdbb..84b2dc5a 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -34,9 +34,4 @@
-
-
-
-
-
diff --git a/src/Automatic/Automatic.php b/src/Automatic/Automatic.php
index 182b8639..12e9dcb6 100644
--- a/src/Automatic/Automatic.php
+++ b/src/Automatic/Automatic.php
@@ -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;
@@ -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
*/
@@ -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
+ */
+ private $securityAdvisories;
+
+ /**
+ * Found package vulnerabilities.
+ *
+ * @var array[]
+ */
+ private $foundVulnerabilities = [];
+
/**
* Get the Container instance.
*
@@ -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}
*/
@@ -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));
@@ -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));
@@ -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));
}
/**
@@ -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('[!]> Audit Security Report: ' . \sprintf('%s vulnerabilit%s found - run "composer audit" for more information', $count, $count === 1 ? 'y' : 'ies'));
+ } else {
+ $io->write('[+]> 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];
}
/**
@@ -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.
*
@@ -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),
@@ -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) {
diff --git a/src/Automatic/CommandProvider.php b/src/Automatic/CommandProvider.php
new file mode 100644
index 00000000..26fc41b5
--- /dev/null
+++ b/src/Automatic/CommandProvider.php
@@ -0,0 +1,19 @@
+composerVendorPath = $composerVendorPath;
+ $this->downloader = $downloader;
+ $this->versionParser = new VersionParser();
+ $this->filesystem = new Filesystem();
+ }
+
+ /**
+ * Checks a package on name and version.
+ *
+ * @param string $name
+ * @param string $version
+ * @param array $securityAdvisories
+ *
+ * @return array[]
+ */
+ public function checkPackage(string $name, string $version, array $securityAdvisories): array
+ {
+ if (! isset($securityAdvisories[$name])) {
+ return [];
+ }
+
+ $package = new Package($name, $this->versionParser->normalize($version), $version);
+
+ [$messages, $vulnerabilities] = $this->checkPackageAgainstSecurityAdvisories($securityAdvisories, $package);
+
+ \ksort($vulnerabilities);
+
+ return [$vulnerabilities, $messages];
+ }
+
+ /**
+ * Checks a composer lock file.
+ *
+ * @param string $lock The path to the composer.lock file
+ *
+ * @throws \Narrowspark\Automatic\Common\Contract\Exception\RuntimeException When the lock file does not exist
+ *
+ * @return array[]
+ */
+ public function checkLock(string $lock): array
+ {
+ if (! \file_exists($lock)) {
+ throw new RuntimeException('Lock file does not exist.');
+ }
+
+ $lockContents = $this->getLockContents($lock);
+
+ /** @var \Composer\Package\Package[] $packages */
+ $packages = [];
+
+ foreach (['packages', 'packages-dev'] as $key) {
+ $data = $lockContents[$key];
+
+ foreach ($data as $pkgData) {
+ $packages[] = new Package($pkgData['name'], $this->versionParser->normalize($pkgData['version']), $pkgData['version']);
+ }
+ }
+
+ $securityAdvisories = $this->getSecurityAdvisories();
+ $vulnerabilities = [];
+ $messages = [];
+
+ foreach ($packages as $package) {
+ if (! isset($securityAdvisories[$package->getName()])) {
+ continue;
+ }
+
+ [$messages, $vulnerabilities] = $this->checkPackageAgainstSecurityAdvisories($securityAdvisories, $package, $messages, $vulnerabilities);
+ }
+
+ \ksort($vulnerabilities);
+
+ return [$vulnerabilities, $messages];
+ }
+
+ /**
+ * Get the news security advisories from narrowspark/security-advisories.
+ *
+ * @param null|\Composer\IO\IOInterface $io
+ *
+ * @return array
+ */
+ public function getSecurityAdvisories(?IOInterface $io = null): array
+ {
+ if (! \extension_loaded('curl')) {
+ $sha = $this->downloader->downloadWithComposer(self::SECURITY_ADVISORIES_BASE_URL . self::SECURITY_ADVISORIES_SHA);
+ } else {
+ $sha = $this->downloader->downloadWithCurl(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)) {
+ $this->filesystem->mkdir($narrowsparkAutomaticPath);
+ }
+
+ $securityAdvisoriesShaPath = $narrowsparkAutomaticPath . self::SECURITY_ADVISORIES_SHA;
+ $securityAdvisoriesPath = $narrowsparkAutomaticPath . self::SECURITY_ADVISORIES;
+
+ if ($this->filesystem->exists($securityAdvisoriesShaPath)) {
+ $oldSha = \file_get_contents($securityAdvisoriesShaPath);
+
+ if ($oldSha === $sha) {
+ return \json_decode((string) \file_get_contents($securityAdvisoriesPath), true);
+ }
+ }
+
+ if ($io !== null) {
+ $io->writeError('Downloading the Security Advisories database');
+ }
+
+ if (! \extension_loaded('curl')) {
+ $securityAdvisories = $this->downloader->downloadWithComposer(self::SECURITY_ADVISORIES_BASE_URL . self::SECURITY_ADVISORIES);
+ } else {
+ $securityAdvisories = $this->downloader->downloadWithCurl(self::SECURITY_ADVISORIES_BASE_URL . self::SECURITY_ADVISORIES);
+ }
+
+ $this->filesystem->dumpFile($securityAdvisoriesShaPath, $sha);
+ $this->filesystem->dumpFile($securityAdvisoriesPath, $securityAdvisories);
+
+ return \json_decode((string) \file_get_contents($securityAdvisoriesPath), true);
+ }
+
+ /**
+ * @param string $lock
+ *
+ * @return array
+ */
+ private function getLockContents(string $lock): array
+ {
+ $contents = \json_decode((string) \file_get_contents($lock), true);
+ $packages = ['packages' => [], 'packages-dev' => []];
+
+ foreach (['packages', 'packages-dev'] as $key) {
+ if (! \is_array($contents[$key])) {
+ continue;
+ }
+
+ foreach ($contents[$key] as $package) {
+ $data = [
+ 'name' => $package['name'],
+ 'version' => $package['version'],
+ ];
+
+ if (isset($package['time']) && false !== \mb_strpos($package['version'], 'dev')) {
+ $data['time'] = $package['time'];
+ }
+
+ $packages[$key][] = $data;
+ }
+ }
+
+ return $packages;
+ }
+
+ /**
+ * Check if a package has some security issues.
+ *
+ * @param array $securityAdvisories
+ * @param \Composer\Package\Package $package
+ * @param array $messages
+ * @param array $vulnerabilities
+ *
+ * @return array[]
+ */
+ private function checkPackageAgainstSecurityAdvisories(
+ array $securityAdvisories,
+ Package $package,
+ array $messages = [],
+ array $vulnerabilities = []
+ ): array {
+ $name = $package->getName();
+
+ foreach ($securityAdvisories[$name] as $key => $advisoryData) {
+ if (! \is_array($advisoryData['branches'])) {
+ $messages[$name][] = '"branches" is expected to be an array.';
+
+ continue;
+ }
+
+ foreach ($advisoryData['branches'] as $name => $branch) {
+ if (! isset($branch['versions'])) {
+ $messages[$name][] = \sprintf('Key [versions] is not set for branch [%s].', $key);
+ } elseif (! \is_array($branch['versions'])) {
+ $messages[$name][] = \sprintf('Key [versions] is expected to be an array for branch [%s].', $key);
+ } else {
+ $constraints = [];
+
+ foreach ($branch['versions'] as $version) {
+ $op = null;
+
+ foreach (Constraint::getSupportedOperators() as $operators) {
+ if (\mb_strpos($version, $operators) === 0) {
+ $op = $operators;
+
+ break;
+ }
+ }
+
+ if (null === $op) {
+ $messages[$name][] = \sprintf('Version [%s] does not contain a supported operator.', $version);
+
+ continue;
+ }
+
+ $constraints[] = new Constraint($op, \mb_substr($version, \mb_strlen($op)));
+ }
+
+ $affectedConstraint = new MultiConstraint($constraints);
+ $affectedPackage = $affectedConstraint->matches(new Constraint('==', $package->getVersion()));
+
+ if ($affectedPackage) {
+ $composerPackage = \mb_substr($advisoryData['reference'], 11);
+
+ $vulnerabilities[$composerPackage] = $vulnerabilities[$composerPackage] ?? [
+ 'version' => $package->getPrettyVersion(),
+ 'advisories' => [],
+ ];
+
+ $vulnerabilities[$composerPackage]['advisories'][$key] = [
+ 'title' => $advisoryData['title'] ?? '',
+ 'link' => $advisoryData['link'] ?? '',
+ 'cve' => $advisoryData['cve'] ?? '',
+ ];
+ }
+ }
+ }
+ }
+
+ return [$messages, $vulnerabilities];
+ }
+}
diff --git a/src/Automatic/Security/Command/AuditCommand.php b/src/Automatic/Security/Command/AuditCommand.php
new file mode 100644
index 00000000..7895fa5d
--- /dev/null
+++ b/src/Automatic/Security/Command/AuditCommand.php
@@ -0,0 +1,120 @@
+setName('audit')
+ ->setDefinition([
+ new InputOption('composer-lock', '', InputOption::VALUE_REQUIRED, 'Path to a composer.lock'),
+ new InputOption('format', '', InputOption::VALUE_REQUIRED, 'The output format', 'txt'),
+ new InputOption('timeout', '', InputOption::VALUE_REQUIRED, 'The HTTP timeout in seconds'),
+ ])
+ ->setDescription('Checks security issues in your project dependencies')
+ ->setHelp(
+ <<<'EOF'
+The %command.name% command looks for security issues in the
+project dependencies:
+%command.full_name%
+EOF
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function execute(InputInterface $input, OutputInterface $output): int
+ {
+ $downloader = new Downloader();
+
+ if (($timeout = $input->getOption('timeout')) !== null) {
+ $downloader->setTimeout((int) $timeout);
+ }
+
+ $config = Factory::createConfig(new NullIO());
+ $audit = new Audit(\rtrim($config->get('vendor-dir'), '/'), $downloader);
+
+ if ($input->getOption('composer-lock') !== null) {
+ $composerFile = $input->getOption('composer-lock');
+ } else {
+ $composerFile = Util::getComposerLockFile();
+ }
+
+ $output = new SymfonyStyle($input, $output);
+ $output->writeln('=== Audit Security Report ===');
+
+ try {
+ [$vulnerabilities, $messages] = $audit->checkLock($composerFile);
+ } catch (RuntimeException $exception) {
+ /** @var \Symfony\Component\Console\Helper\FormatterHelper $formatter */
+ $formatter = $this->getHelperSet()->get('formatter');
+
+ $output->writeln($formatter->formatBlock($exception->getMessage(), 'error', true));
+
+ return 1;
+ }
+ $output->comment('This checker can only detect vulnerabilities that are referenced in the SensioLabs security advisories database.');
+
+ if (\count($messages) !== 0) {
+ $output->note('Please report this found messages to https://github.com/narrowspark/security-advisories.');
+
+ foreach ($messages as $key => $message) {
+ $output->writeln($key . ': ' . $message);
+ }
+ }
+
+ $count = \count($vulnerabilities);
+
+ if (\count($vulnerabilities) !== 0) {
+ switch ($input->getOption('format')) {
+ case 'json':
+ $formatter = new JsonFormatter();
+
+ break;
+ case 'simple':
+ $formatter = new SimpleFormatter();
+
+ break;
+ case 'txt':
+ default:
+ $formatter = new TextFormatter();
+ }
+
+ $formatter->displayResults($output, $vulnerabilities);
+ $output->writeln('[!]> ' . \sprintf('%s vulnerabilit%s found - ', $count, $count === 1 ? 'y' : 'ies') .
+ 'We recommend you to check the related security advisories and upgrade these dependencies.');
+
+ return 1;
+ }
+
+ $output->writeln('[+]> No known vulnerabilities found');
+
+ return 0;
+ }
+}
diff --git a/src/Automatic/Security/Command/Formatter/JsonFormatter.php b/src/Automatic/Security/Command/Formatter/JsonFormatter.php
new file mode 100644
index 00000000..c76b25a7
--- /dev/null
+++ b/src/Automatic/Security/Command/Formatter/JsonFormatter.php
@@ -0,0 +1,17 @@
+writeln((string) \json_encode($vulnerabilities, \JSON_PRETTY_PRINT));
+ }
+}
diff --git a/src/Automatic/Security/Command/Formatter/SimpleFormatter.php b/src/Automatic/Security/Command/Formatter/SimpleFormatter.php
new file mode 100644
index 00000000..a08e974a
--- /dev/null
+++ b/src/Automatic/Security/Command/Formatter/SimpleFormatter.php
@@ -0,0 +1,38 @@
+ $issues) {
+ $dependencyFullName = $dependency . ' (' . $issues['version'] . ')';
+ $output->writeln('' . $dependencyFullName . "\n" . \str_repeat('-', \mb_strlen($dependencyFullName)) . ">\n");
+
+ foreach ($issues['advisories'] as $issue => $details) {
+ $output->write(' * ');
+
+ if ($details['cve']) {
+ $output->write('' . $details['cve'] . ': ');
+ }
+
+ $output->writeln($details['title']);
+
+ if ('' !== $details['link']) {
+ $output->writeln(' ' . $details['link']);
+ }
+
+ $output->writeln('');
+ }
+ }
+ }
+ }
+}
diff --git a/src/Automatic/Security/Command/Formatter/TextFormatter.php b/src/Automatic/Security/Command/Formatter/TextFormatter.php
new file mode 100644
index 00000000..6f3225a4
--- /dev/null
+++ b/src/Automatic/Security/Command/Formatter/TextFormatter.php
@@ -0,0 +1,27 @@
+ $issues) {
+ $output->section(\sprintf('%s (%s)', $dependency, $issues['version']));
+
+ $details = \array_map(function ($value) {
+ return \sprintf("%s>: %s\n %s", $value['cve'] ?: '(no CVE ID)', $value['title'], $value['link']);
+ }, $issues['advisories']);
+
+ $output->listing($details);
+ }
+ }
+ }
+}
diff --git a/src/Automatic/Security/Downloader.php b/src/Automatic/Security/Downloader.php
new file mode 100644
index 00000000..f75676ec
--- /dev/null
+++ b/src/Automatic/Security/Downloader.php
@@ -0,0 +1,177 @@
+timeout = $timeout;
+ }
+
+ /**
+ * Download a file from a url with composer's StreamContextFactory class.
+ *
+ * @param string $url
+ *
+ * @return string
+ */
+ public function downloadWithComposer(string $url): string
+ {
+ $opts = [
+ 'http' => [
+ 'method' => 'GET',
+ 'ignore_errors' => true,
+ 'follow_location' => true,
+ 'max_redirects' => 3,
+ 'timeout' => $this->timeout,
+ 'user_agent' => $this->getUserAgent(),
+ ],
+ 'ssl' => [
+ 'verify_peer' => 1,
+ 'verify_host' => 2,
+ ],
+ ];
+
+ $caPathOrFile = CaBundle::getSystemCaRootBundlePath();
+
+ if (\is_dir($caPathOrFile) || (\is_link($caPathOrFile) && \is_dir(\readlink($caPathOrFile)))) {
+ $opts['ssl']['capath'] = $caPathOrFile;
+ } else {
+ $opts['ssl']['cafile'] = $caPathOrFile;
+ }
+
+ $context = StreamContextFactory::getContext($url, $opts);
+ $level = \error_reporting(0);
+ $body = \file_get_contents($url, false, $context);
+
+ \error_reporting($level);
+
+ if ($body === false) {
+ $error = \error_get_last();
+
+ throw new RuntimeException(\sprintf('An error occurred: %s.', $error['message']));
+ }
+
+ // status code
+ if ((bool) \preg_match('{HTTP/\d\.\d (\d+) }i', $http_response_header[0], $match) === false) {
+ throw new RuntimeException('An unknown error occurred.');
+ }
+
+ $this->checkStatus((int) $match[1], $body);
+
+ return \trim($body);
+ }
+
+ /**
+ * Download a file with the curl extension.
+ *
+ * @param string $url
+ *
+ * @throws \Narrowspark\Automatic\Common\Contract\Exception\RuntimeException
+ *
+ * @return string
+ */
+ public function downloadWithCurl(string $url): string
+ {
+ $curl = \curl_init();
+
+ \curl_setopt($curl, \CURLOPT_RETURNTRANSFER, true);
+ \curl_setopt($curl, \CURLOPT_HEADER, true);
+ \curl_setopt($curl, \CURLOPT_URL, $url);
+ \curl_setopt($curl, \CURLOPT_HTTPHEADER, ['Accept: application/text']);
+ \curl_setopt($curl, \CURLOPT_CONNECTTIMEOUT, $this->timeout);
+ \curl_setopt($curl, \CURLOPT_TIMEOUT, 10);
+ \curl_setopt($curl, \CURLOPT_FOLLOWLOCATION, \is_string(\ini_get('open_basedir')) ? 0 : 1);
+ \curl_setopt($curl, \CURLOPT_MAXREDIRS, 3);
+ \curl_setopt($curl, \CURLOPT_FAILONERROR, false);
+ \curl_setopt($curl, \CURLOPT_SSL_VERIFYPEER, 1);
+ \curl_setopt($curl, \CURLOPT_SSL_VERIFYHOST, 2);
+ \curl_setopt($curl, \CURLOPT_USERAGENT, $this->getUserAgent());
+
+ $caPathOrFile = CaBundle::getSystemCaRootBundlePath();
+
+ if (\is_dir($caPathOrFile) || (\is_link($caPathOrFile) && \is_dir(\readlink($caPathOrFile)))) {
+ \curl_setopt($curl, \CURLOPT_CAPATH, $caPathOrFile);
+ } else {
+ \curl_setopt($curl, \CURLOPT_CAINFO, $caPathOrFile);
+ }
+
+ $response = \curl_exec($curl);
+
+ if ($response === false) {
+ $error = \curl_error($curl);
+
+ \curl_close($curl);
+
+ throw new RuntimeException(\sprintf('An error occurred: %s.', $error));
+ }
+
+ $body = \mb_substr((string) $response, \curl_getinfo($curl, \CURLINFO_HEADER_SIZE));
+ $statusCode = (int) \curl_getinfo($curl, \CURLINFO_HTTP_CODE);
+
+ \curl_close($curl);
+
+ $this->checkStatus($statusCode, $body);
+
+ return \trim($body);
+ }
+
+ /**
+ * @return string
+ */
+ private function getUserAgent(): string
+ {
+ return \sprintf(
+ 'Narrowspark-Automatic/%s (%s; %s; %s%s)',
+ Automatic::VERSION,
+ \function_exists('php_uname') ? \php_uname('s') : 'Unknown',
+ \function_exists('php_uname') ? \php_uname('r') : 'Unknown',
+ 'PHP ' . \PHP_MAJOR_VERSION . '.' . \PHP_MINOR_VERSION . '.' . \PHP_RELEASE_VERSION,
+ \getenv('CI') !== false ? '; CI' : ''
+ );
+ }
+
+ /**
+ * Check response status.
+ *
+ * @param int $statusCode
+ * @param string $body
+ *
+ * @throws \Narrowspark\Automatic\Common\Contract\Exception\RuntimeException
+ *
+ * @return void
+ */
+ private function checkStatus(int $statusCode, string $body): void
+ {
+ if ($statusCode === 400) {
+ throw new RuntimeException($body);
+ }
+
+ if ($statusCode !== 200) {
+ throw new RuntimeException(\sprintf('The web service failed for an unknown reason (HTTP %s).', $statusCode), $statusCode);
+ }
+ }
+}
diff --git a/src/Automatic/SkeletonGenerator.php b/src/Automatic/SkeletonGenerator.php
index e4468ac9..715934fc 100644
--- a/src/Automatic/SkeletonGenerator.php
+++ b/src/Automatic/SkeletonGenerator.php
@@ -113,10 +113,11 @@ public function run(): void
$status = $this->installationManager->run();
+ // @codeCoverageIgnoreStart
if ($status !== 0) {
exit($status);
}
-
+ // @codeCoverageIgnoreEnd
$generator->generate();
}
diff --git a/src/Common/Package.php b/src/Common/Package.php
index 91f8642b..b6e0e26e 100644
--- a/src/Common/Package.php
+++ b/src/Common/Package.php
@@ -28,7 +28,7 @@ final class Package implements PackageContract
private $parentName;
/**
- * The package version.
+ * The package pretty version.
*
* @var null|string
*/
@@ -290,9 +290,9 @@ public static function createFromLock(string $name, array $packageData): Package
$package = new static($name, $packageData['version']);
- foreach ($packageData as $key => $date) {
- if ($date !== null && isset($keyToFunctionMappers[$key])) {
- $package->{$keyToFunctionMappers[$key]}($date);
+ foreach ($packageData as $key => $data) {
+ if ($data !== null && isset($keyToFunctionMappers[$key])) {
+ $package->{$keyToFunctionMappers[$key]}($data);
}
}
diff --git a/tests/Automatic/AutomaticTest.php b/tests/Automatic/AutomaticTest.php
index b97b1644..40b28349 100644
--- a/tests/Automatic/AutomaticTest.php
+++ b/tests/Automatic/AutomaticTest.php
@@ -74,7 +74,7 @@ protected function tearDown(): void
\putenv('COMPOSER_CACHE_DIR=');
\putenv('COMPOSER_CACHE_DIR');
- (new Filesystem())->remove($this->composerCachePath);
+ (new Filesystem())->remove([$this->composerCachePath, __DIR__ . \DIRECTORY_SEPARATOR . 'narrowspark']);
}
public function testGetSubscribedEvents(): void
@@ -139,6 +139,9 @@ public function testActivate(): void
$this->ioMock->shouldReceive('isInteractive')
->once()
->andReturn(true);
+ $this->ioMock->shouldReceive('writeError')
+ ->once()
+ ->with('Downloading the Security Advisories database');
$this->automatic->activate($this->composerMock, $this->ioMock);
@@ -218,6 +221,8 @@ public function testRecordWithUninstallRecord(): void
public function testRecordWithInstallRecord(): void
{
+ \putenv('COMPOSER_VENDOR_DIR=' . __DIR__);
+
$automatic = new Automatic();
$packageEventMock = $this->mock(PackageEvent::class);
@@ -269,10 +274,15 @@ public function isInteractive(): bool
);
$automatic->record($packageEventMock);
+
+ \putenv('COMPOSER_VENDOR_DIR=');
+ \putenv('COMPOSER_VENDOR_DIR');
}
public function testRecordWithInstallRecordAndAutomaticPackage(): void
{
+ \putenv('COMPOSER_VENDOR_DIR=' . __DIR__);
+
$automatic = new Automatic();
$packageEventMock = $this->mock(PackageEvent::class);
@@ -344,6 +354,9 @@ public function isInteractive(): bool
$automatic->record($packageEventMock);
$automatic->record($automaticPackageEventMock);
+
+ \putenv('COMPOSER_VENDOR_DIR=');
+ \putenv('COMPOSER_VENDOR_DIR');
}
public function testExecuteAutoScripts(): void
@@ -420,6 +433,9 @@ public function testPostInstallOut(): void
$this->ioMock->shouldReceive('write')
->once()
->with(['']);
+ $this->ioMock->shouldReceive('write')
+ ->once()
+ ->with('[+]> Audit Security Report: No known vulnerabilities found');
$containerMock = $this->mock(ContainerContract::class);
$containerMock->shouldReceive('get')
diff --git a/tests/Automatic/CommandProviderTest.php b/tests/Automatic/CommandProviderTest.php
new file mode 100644
index 00000000..20c71656
--- /dev/null
+++ b/tests/Automatic/CommandProviderTest.php
@@ -0,0 +1,22 @@
+getCommands();
+
+ static::assertInstanceOf(AuditCommand::class, $commands[0]);
+ }
+}
diff --git a/tests/Automatic/Fixture/composer_1.7.1_composer.lock b/tests/Automatic/Fixture/composer_1.7.1_composer.lock
new file mode 100644
index 00000000..278fa805
--- /dev/null
+++ b/tests/Automatic/Fixture/composer_1.7.1_composer.lock
@@ -0,0 +1,860 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+ "This file is @generated automatically"
+ ],
+ "content-hash": "f96b35b2deaec90bb30cceddb419accc",
+ "packages": [
+ {
+ "name": "composer/ca-bundle",
+ "version": "1.1.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/composer/ca-bundle.git",
+ "reference": "46afded9720f40b9dc63542af4e3e43a1177acb0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/composer/ca-bundle/zipball/46afded9720f40b9dc63542af4e3e43a1177acb0",
+ "reference": "46afded9720f40b9dc63542af4e3e43a1177acb0",
+ "shasum": ""
+ },
+ "require": {
+ "ext-openssl": "*",
+ "ext-pcre": "*",
+ "php": "^5.3.2 || ^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5",
+ "psr/log": "^1.0",
+ "symfony/process": "^2.5 || ^3.0 || ^4.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Composer\\CaBundle\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jordi Boggiano",
+ "email": "j.boggiano@seld.be",
+ "homepage": "http://seld.be"
+ }
+ ],
+ "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.",
+ "keywords": [
+ "cabundle",
+ "cacert",
+ "certificate",
+ "ssl",
+ "tls"
+ ],
+ "time": "2018-08-08T08:57:40+00:00"
+ },
+ {
+ "name": "composer/composer",
+ "version": "1.7.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/composer/composer.git",
+ "reference": "576aab9b5abb2ed11a1c52353a759363216a4ad2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/composer/composer/zipball/576aab9b5abb2ed11a1c52353a759363216a4ad2",
+ "reference": "576aab9b5abb2ed11a1c52353a759363216a4ad2",
+ "shasum": ""
+ },
+ "require": {
+ "composer/ca-bundle": "^1.0",
+ "composer/semver": "^1.0",
+ "composer/spdx-licenses": "^1.2",
+ "composer/xdebug-handler": "^1.1",
+ "justinrainbow/json-schema": "^3.0 || ^4.0 || ^5.0",
+ "php": "^5.3.2 || ^7.0",
+ "psr/log": "^1.0",
+ "seld/jsonlint": "^1.4",
+ "seld/phar-utils": "^1.0",
+ "symfony/console": "^2.7 || ^3.0 || ^4.0",
+ "symfony/filesystem": "^2.7 || ^3.0 || ^4.0",
+ "symfony/finder": "^2.7 || ^3.0 || ^4.0",
+ "symfony/process": "^2.7 || ^3.0 || ^4.0"
+ },
+ "conflict": {
+ "symfony/console": "2.8.38"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.8.35 || ^5.7",
+ "phpunit/phpunit-mock-objects": "^2.3 || ^3.0"
+ },
+ "suggest": {
+ "ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages",
+ "ext-zip": "Enabling the zip extension allows you to unzip archives",
+ "ext-zlib": "Allow gzip compression of HTTP requests"
+ },
+ "bin": [
+ "bin/composer"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.7-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Composer\\": "src/Composer"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nils Adermann",
+ "email": "naderman@naderman.de",
+ "homepage": "http://www.naderman.de"
+ },
+ {
+ "name": "Jordi Boggiano",
+ "email": "j.boggiano@seld.be",
+ "homepage": "http://seld.be"
+ }
+ ],
+ "description": "Composer helps you declare, manage and install dependencies of PHP projects, ensuring you have the right stack everywhere.",
+ "homepage": "https://getcomposer.org/",
+ "keywords": [
+ "autoload",
+ "dependency",
+ "package"
+ ],
+ "time": "2018-08-16T14:57:12+00:00"
+ },
+ {
+ "name": "composer/semver",
+ "version": "1.4.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/composer/semver.git",
+ "reference": "c7cb9a2095a074d131b65a8a0cd294479d785573"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/composer/semver/zipball/c7cb9a2095a074d131b65a8a0cd294479d785573",
+ "reference": "c7cb9a2095a074d131b65a8a0cd294479d785573",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.3.2 || ^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.5 || ^5.0.5",
+ "phpunit/phpunit-mock-objects": "2.3.0 || ^3.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Composer\\Semver\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nils Adermann",
+ "email": "naderman@naderman.de",
+ "homepage": "http://www.naderman.de"
+ },
+ {
+ "name": "Jordi Boggiano",
+ "email": "j.boggiano@seld.be",
+ "homepage": "http://seld.be"
+ },
+ {
+ "name": "Rob Bast",
+ "email": "rob.bast@gmail.com",
+ "homepage": "http://robbast.nl"
+ }
+ ],
+ "description": "Semver library that offers utilities, version constraint parsing and validation.",
+ "keywords": [
+ "semantic",
+ "semver",
+ "validation",
+ "versioning"
+ ],
+ "time": "2016-08-30T16:08:34+00:00"
+ },
+ {
+ "name": "composer/spdx-licenses",
+ "version": "1.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/composer/spdx-licenses.git",
+ "reference": "cb17687e9f936acd7e7245ad3890f953770dec1b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/cb17687e9f936acd7e7245ad3890f953770dec1b",
+ "reference": "cb17687e9f936acd7e7245ad3890f953770dec1b",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.3.2 || ^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5",
+ "phpunit/phpunit-mock-objects": "2.3.0 || ^3.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Composer\\Spdx\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nils Adermann",
+ "email": "naderman@naderman.de",
+ "homepage": "http://www.naderman.de"
+ },
+ {
+ "name": "Jordi Boggiano",
+ "email": "j.boggiano@seld.be",
+ "homepage": "http://seld.be"
+ },
+ {
+ "name": "Rob Bast",
+ "email": "rob.bast@gmail.com",
+ "homepage": "http://robbast.nl"
+ }
+ ],
+ "description": "SPDX licenses list and validation library.",
+ "keywords": [
+ "license",
+ "spdx",
+ "validator"
+ ],
+ "time": "2018-04-30T10:33:04+00:00"
+ },
+ {
+ "name": "composer/xdebug-handler",
+ "version": "1.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/composer/xdebug-handler.git",
+ "reference": "b8e9745fb9b06ea6664d8872c4505fb16df4611c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/b8e9745fb9b06ea6664d8872c4505fb16df4611c",
+ "reference": "b8e9745fb9b06ea6664d8872c4505fb16df4611c",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.3.2 || ^7.0",
+ "psr/log": "^1.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Composer\\XdebugHandler\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "John Stevenson",
+ "email": "john-stevenson@blueyonder.co.uk"
+ }
+ ],
+ "description": "Restarts a process without xdebug.",
+ "keywords": [
+ "Xdebug",
+ "performance"
+ ],
+ "time": "2018-08-31T19:07:57+00:00"
+ },
+ {
+ "name": "justinrainbow/json-schema",
+ "version": "5.2.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/justinrainbow/json-schema.git",
+ "reference": "8560d4314577199ba51bf2032f02cd1315587c23"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/8560d4314577199ba51bf2032f02cd1315587c23",
+ "reference": "8560d4314577199ba51bf2032f02cd1315587c23",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^2.1",
+ "json-schema/json-schema-test-suite": "1.2.0",
+ "phpunit/phpunit": "^4.8.35"
+ },
+ "bin": [
+ "bin/validate-json"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "JsonSchema\\": "src/JsonSchema/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Bruno Prieto Reis",
+ "email": "bruno.p.reis@gmail.com"
+ },
+ {
+ "name": "Justin Rainbow",
+ "email": "justin.rainbow@gmail.com"
+ },
+ {
+ "name": "Igor Wiedler",
+ "email": "igor@wiedler.ch"
+ },
+ {
+ "name": "Robert Schönthal",
+ "email": "seroscho@googlemail.com"
+ }
+ ],
+ "description": "A library to validate a json schema.",
+ "homepage": "https://github.com/justinrainbow/json-schema",
+ "keywords": [
+ "json",
+ "schema"
+ ],
+ "time": "2018-02-14T22:26:30+00:00"
+ },
+ {
+ "name": "psr/log",
+ "version": "1.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/log.git",
+ "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
+ "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Log\\": "Psr/Log/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for logging libraries",
+ "homepage": "https://github.com/php-fig/log",
+ "keywords": [
+ "log",
+ "psr",
+ "psr-3"
+ ],
+ "time": "2016-10-10T12:19:37+00:00"
+ },
+ {
+ "name": "seld/jsonlint",
+ "version": "1.7.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Seldaek/jsonlint.git",
+ "reference": "d15f59a67ff805a44c50ea0516d2341740f81a38"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/d15f59a67ff805a44c50ea0516d2341740f81a38",
+ "reference": "d15f59a67ff805a44c50ea0516d2341740f81a38",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.3 || ^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0"
+ },
+ "bin": [
+ "bin/jsonlint"
+ ],
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Seld\\JsonLint\\": "src/Seld/JsonLint/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jordi Boggiano",
+ "email": "j.boggiano@seld.be",
+ "homepage": "http://seld.be"
+ }
+ ],
+ "description": "JSON Linter",
+ "keywords": [
+ "json",
+ "linter",
+ "parser",
+ "validator"
+ ],
+ "time": "2018-01-24T12:46:19+00:00"
+ },
+ {
+ "name": "seld/phar-utils",
+ "version": "1.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Seldaek/phar-utils.git",
+ "reference": "7009b5139491975ef6486545a39f3e6dad5ac30a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/7009b5139491975ef6486545a39f3e6dad5ac30a",
+ "reference": "7009b5139491975ef6486545a39f3e6dad5ac30a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Seld\\PharUtils\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jordi Boggiano",
+ "email": "j.boggiano@seld.be"
+ }
+ ],
+ "description": "PHAR file format utilities, for when PHP phars you up",
+ "keywords": [
+ "phra"
+ ],
+ "time": "2015-10-13T18:44:15+00:00"
+ },
+ {
+ "name": "symfony/console",
+ "version": "v4.1.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/console.git",
+ "reference": "ca80b8ced97cf07390078b29773dc384c39eee1f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/console/zipball/ca80b8ced97cf07390078b29773dc384c39eee1f",
+ "reference": "ca80b8ced97cf07390078b29773dc384c39eee1f",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1.3",
+ "symfony/polyfill-mbstring": "~1.0"
+ },
+ "conflict": {
+ "symfony/dependency-injection": "<3.4",
+ "symfony/process": "<3.3"
+ },
+ "require-dev": {
+ "psr/log": "~1.0",
+ "symfony/config": "~3.4|~4.0",
+ "symfony/dependency-injection": "~3.4|~4.0",
+ "symfony/event-dispatcher": "~3.4|~4.0",
+ "symfony/lock": "~3.4|~4.0",
+ "symfony/process": "~3.4|~4.0"
+ },
+ "suggest": {
+ "psr/log-implementation": "For using the console logger",
+ "symfony/event-dispatcher": "",
+ "symfony/lock": "",
+ "symfony/process": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.1-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Console\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Console Component",
+ "homepage": "https://symfony.com",
+ "time": "2018-07-26T11:24:31+00:00"
+ },
+ {
+ "name": "symfony/filesystem",
+ "version": "v4.1.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/filesystem.git",
+ "reference": "c0f5f62db218fa72195b8b8700e4b9b9cf52eb5e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/filesystem/zipball/c0f5f62db218fa72195b8b8700e4b9b9cf52eb5e",
+ "reference": "c0f5f62db218fa72195b8b8700e4b9b9cf52eb5e",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1.3",
+ "symfony/polyfill-ctype": "~1.8"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.1-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Filesystem\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Filesystem Component",
+ "homepage": "https://symfony.com",
+ "time": "2018-08-18T16:52:46+00:00"
+ },
+ {
+ "name": "symfony/finder",
+ "version": "v4.1.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/finder.git",
+ "reference": "e162f1df3102d0b7472805a5a9d5db9fcf0a8068"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/finder/zipball/e162f1df3102d0b7472805a5a9d5db9fcf0a8068",
+ "reference": "e162f1df3102d0b7472805a5a9d5db9fcf0a8068",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.1-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Finder\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Finder Component",
+ "homepage": "https://symfony.com",
+ "time": "2018-07-26T11:24:31+00:00"
+ },
+ {
+ "name": "symfony/polyfill-ctype",
+ "version": "v1.9.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-ctype.git",
+ "reference": "e3d826245268269cd66f8326bd8bc066687b4a19"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19",
+ "reference": "e3d826245268269cd66f8326bd8bc066687b4a19",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "suggest": {
+ "ext-ctype": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.9-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Ctype\\": ""
+ },
+ "files": [
+ "bootstrap.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ },
+ {
+ "name": "Gert de Pagter",
+ "email": "BackEndTea@gmail.com"
+ }
+ ],
+ "description": "Symfony polyfill for ctype functions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "ctype",
+ "polyfill",
+ "portable"
+ ],
+ "time": "2018-08-06T14:22:27+00:00"
+ },
+ {
+ "name": "symfony/polyfill-mbstring",
+ "version": "v1.9.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-mbstring.git",
+ "reference": "d0cd638f4634c16d8df4508e847f14e9e43168b8"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d0cd638f4634c16d8df4508e847f14e9e43168b8",
+ "reference": "d0cd638f4634c16d8df4508e847f14e9e43168b8",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "suggest": {
+ "ext-mbstring": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.9-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Mbstring\\": ""
+ },
+ "files": [
+ "bootstrap.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for the Mbstring extension",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "mbstring",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "time": "2018-08-06T14:22:27+00:00"
+ },
+ {
+ "name": "symfony/process",
+ "version": "v4.1.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/process.git",
+ "reference": "86cdb930a6a855b0ab35fb60c1504cb36184f843"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/process/zipball/86cdb930a6a855b0ab35fb60c1504cb36184f843",
+ "reference": "86cdb930a6a855b0ab35fb60c1504cb36184f843",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.1-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Process\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Process Component",
+ "homepage": "https://symfony.com",
+ "time": "2018-08-03T11:13:38+00:00"
+ }
+ ],
+ "packages-dev": [],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": [],
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": [],
+ "platform-dev": []
+}
diff --git a/tests/Automatic/Fixture/symfony_2.5.2_composer.lock b/tests/Automatic/Fixture/symfony_2.5.2_composer.lock
new file mode 100644
index 00000000..3d2a17f3
--- /dev/null
+++ b/tests/Automatic/Fixture/symfony_2.5.2_composer.lock
@@ -0,0 +1,992 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+ "This file is @generated automatically"
+ ],
+ "content-hash": "f4984f8e9643d2860a86d45a862b64a4",
+ "packages": [
+ {
+ "name": "doctrine/annotations",
+ "version": "v1.6.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/annotations.git",
+ "reference": "c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/annotations/zipball/c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5",
+ "reference": "c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/lexer": "1.*",
+ "php": "^7.1"
+ },
+ "require-dev": {
+ "doctrine/cache": "1.*",
+ "phpunit/phpunit": "^6.4"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.6.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Roman Borschel",
+ "email": "roman@code-factory.org"
+ },
+ {
+ "name": "Benjamin Eberlei",
+ "email": "kontakt@beberlei.de"
+ },
+ {
+ "name": "Guilherme Blanco",
+ "email": "guilhermeblanco@gmail.com"
+ },
+ {
+ "name": "Jonathan Wage",
+ "email": "jonwage@gmail.com"
+ },
+ {
+ "name": "Johannes Schmitt",
+ "email": "schmittjoh@gmail.com"
+ }
+ ],
+ "description": "Docblock Annotations Parser",
+ "homepage": "http://www.doctrine-project.org",
+ "keywords": [
+ "annotations",
+ "docblock",
+ "parser"
+ ],
+ "time": "2017-12-06T07:11:42+00:00"
+ },
+ {
+ "name": "doctrine/cache",
+ "version": "v1.8.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/cache.git",
+ "reference": "d768d58baee9a4862ca783840eca1b9add7a7f57"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/cache/zipball/d768d58baee9a4862ca783840eca1b9add7a7f57",
+ "reference": "d768d58baee9a4862ca783840eca1b9add7a7f57",
+ "shasum": ""
+ },
+ "require": {
+ "php": "~7.1"
+ },
+ "conflict": {
+ "doctrine/common": ">2.2,<2.4"
+ },
+ "require-dev": {
+ "alcaeus/mongo-php-adapter": "^1.1",
+ "doctrine/coding-standard": "^4.0",
+ "mongodb/mongodb": "^1.1",
+ "phpunit/phpunit": "^7.0",
+ "predis/predis": "~1.0"
+ },
+ "suggest": {
+ "alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.8.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Roman Borschel",
+ "email": "roman@code-factory.org"
+ },
+ {
+ "name": "Benjamin Eberlei",
+ "email": "kontakt@beberlei.de"
+ },
+ {
+ "name": "Guilherme Blanco",
+ "email": "guilhermeblanco@gmail.com"
+ },
+ {
+ "name": "Jonathan Wage",
+ "email": "jonwage@gmail.com"
+ },
+ {
+ "name": "Johannes Schmitt",
+ "email": "schmittjoh@gmail.com"
+ }
+ ],
+ "description": "Caching library offering an object-oriented API for many cache backends",
+ "homepage": "https://www.doctrine-project.org",
+ "keywords": [
+ "cache",
+ "caching"
+ ],
+ "time": "2018-08-21T18:01:43+00:00"
+ },
+ {
+ "name": "doctrine/collections",
+ "version": "v1.5.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/collections.git",
+ "reference": "a01ee38fcd999f34d9bfbcee59dbda5105449cbf"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/collections/zipball/a01ee38fcd999f34d9bfbcee59dbda5105449cbf",
+ "reference": "a01ee38fcd999f34d9bfbcee59dbda5105449cbf",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1"
+ },
+ "require-dev": {
+ "doctrine/coding-standard": "~0.1@dev",
+ "phpunit/phpunit": "^5.7"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.3.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Doctrine\\Common\\Collections\\": "lib/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Roman Borschel",
+ "email": "roman@code-factory.org"
+ },
+ {
+ "name": "Benjamin Eberlei",
+ "email": "kontakt@beberlei.de"
+ },
+ {
+ "name": "Guilherme Blanco",
+ "email": "guilhermeblanco@gmail.com"
+ },
+ {
+ "name": "Jonathan Wage",
+ "email": "jonwage@gmail.com"
+ },
+ {
+ "name": "Johannes Schmitt",
+ "email": "schmittjoh@gmail.com"
+ }
+ ],
+ "description": "Collections Abstraction library",
+ "homepage": "http://www.doctrine-project.org",
+ "keywords": [
+ "array",
+ "collections",
+ "iterator"
+ ],
+ "time": "2017-07-22T10:37:32+00:00"
+ },
+ {
+ "name": "doctrine/common",
+ "version": "v2.9.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/common.git",
+ "reference": "a210246d286c77d2b89040f8691ba7b3a713d2c1"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/common/zipball/a210246d286c77d2b89040f8691ba7b3a713d2c1",
+ "reference": "a210246d286c77d2b89040f8691ba7b3a713d2c1",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/annotations": "^1.0",
+ "doctrine/cache": "^1.0",
+ "doctrine/collections": "^1.0",
+ "doctrine/event-manager": "^1.0",
+ "doctrine/inflector": "^1.0",
+ "doctrine/lexer": "^1.0",
+ "doctrine/persistence": "^1.0",
+ "doctrine/reflection": "^1.0",
+ "php": "^7.1"
+ },
+ "require-dev": {
+ "doctrine/coding-standard": "^1.0",
+ "phpunit/phpunit": "^6.3",
+ "squizlabs/php_codesniffer": "^3.0",
+ "symfony/phpunit-bridge": "^4.0.5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.9.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Common\\": "lib/Doctrine/Common"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Roman Borschel",
+ "email": "roman@code-factory.org"
+ },
+ {
+ "name": "Benjamin Eberlei",
+ "email": "kontakt@beberlei.de"
+ },
+ {
+ "name": "Guilherme Blanco",
+ "email": "guilhermeblanco@gmail.com"
+ },
+ {
+ "name": "Jonathan Wage",
+ "email": "jonwage@gmail.com"
+ },
+ {
+ "name": "Johannes Schmitt",
+ "email": "schmittjoh@gmail.com"
+ },
+ {
+ "name": "Marco Pivetta",
+ "email": "ocramius@gmail.com"
+ }
+ ],
+ "description": "Common Library for Doctrine projects",
+ "homepage": "https://www.doctrine-project.org",
+ "keywords": [
+ "annotations",
+ "collections",
+ "eventmanager",
+ "persistence",
+ "spl"
+ ],
+ "time": "2018-07-12T21:16:12+00:00"
+ },
+ {
+ "name": "doctrine/event-manager",
+ "version": "v1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/event-manager.git",
+ "reference": "a520bc093a0170feeb6b14e9d83f3a14452e64b3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/event-manager/zipball/a520bc093a0170feeb6b14e9d83f3a14452e64b3",
+ "reference": "a520bc093a0170feeb6b14e9d83f3a14452e64b3",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1"
+ },
+ "conflict": {
+ "doctrine/common": "<2.9@dev"
+ },
+ "require-dev": {
+ "doctrine/coding-standard": "^4.0",
+ "phpunit/phpunit": "^7.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Common\\": "lib/Doctrine/Common"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Roman Borschel",
+ "email": "roman@code-factory.org"
+ },
+ {
+ "name": "Benjamin Eberlei",
+ "email": "kontakt@beberlei.de"
+ },
+ {
+ "name": "Guilherme Blanco",
+ "email": "guilhermeblanco@gmail.com"
+ },
+ {
+ "name": "Jonathan Wage",
+ "email": "jonwage@gmail.com"
+ },
+ {
+ "name": "Johannes Schmitt",
+ "email": "schmittjoh@gmail.com"
+ },
+ {
+ "name": "Marco Pivetta",
+ "email": "ocramius@gmail.com"
+ }
+ ],
+ "description": "Doctrine Event Manager component",
+ "homepage": "https://www.doctrine-project.org/projects/event-manager.html",
+ "keywords": [
+ "event",
+ "eventdispatcher",
+ "eventmanager"
+ ],
+ "time": "2018-06-11T11:59:03+00:00"
+ },
+ {
+ "name": "doctrine/inflector",
+ "version": "v1.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/inflector.git",
+ "reference": "5527a48b7313d15261292c149e55e26eae771b0a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/inflector/zipball/5527a48b7313d15261292c149e55e26eae771b0a",
+ "reference": "5527a48b7313d15261292c149e55e26eae771b0a",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.3.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Common\\Inflector\\": "lib/Doctrine/Common/Inflector"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Roman Borschel",
+ "email": "roman@code-factory.org"
+ },
+ {
+ "name": "Benjamin Eberlei",
+ "email": "kontakt@beberlei.de"
+ },
+ {
+ "name": "Guilherme Blanco",
+ "email": "guilhermeblanco@gmail.com"
+ },
+ {
+ "name": "Jonathan Wage",
+ "email": "jonwage@gmail.com"
+ },
+ {
+ "name": "Johannes Schmitt",
+ "email": "schmittjoh@gmail.com"
+ }
+ ],
+ "description": "Common String Manipulations with regard to casing and singular/plural rules.",
+ "homepage": "http://www.doctrine-project.org",
+ "keywords": [
+ "inflection",
+ "pluralize",
+ "singularize",
+ "string"
+ ],
+ "time": "2018-01-09T20:05:19+00:00"
+ },
+ {
+ "name": "doctrine/lexer",
+ "version": "v1.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/lexer.git",
+ "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/lexer/zipball/83893c552fd2045dd78aef794c31e694c37c0b8c",
+ "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Doctrine\\Common\\Lexer\\": "lib/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Roman Borschel",
+ "email": "roman@code-factory.org"
+ },
+ {
+ "name": "Guilherme Blanco",
+ "email": "guilhermeblanco@gmail.com"
+ },
+ {
+ "name": "Johannes Schmitt",
+ "email": "schmittjoh@gmail.com"
+ }
+ ],
+ "description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.",
+ "homepage": "http://www.doctrine-project.org",
+ "keywords": [
+ "lexer",
+ "parser"
+ ],
+ "time": "2014-09-09T13:34:57+00:00"
+ },
+ {
+ "name": "doctrine/persistence",
+ "version": "v1.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/persistence.git",
+ "reference": "af1ec238659a83e320f03e0e454e200f689b4b97"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/persistence/zipball/af1ec238659a83e320f03e0e454e200f689b4b97",
+ "reference": "af1ec238659a83e320f03e0e454e200f689b4b97",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/annotations": "^1.0",
+ "doctrine/cache": "^1.0",
+ "doctrine/collections": "^1.0",
+ "doctrine/event-manager": "^1.0",
+ "doctrine/reflection": "^1.0",
+ "php": "^7.1"
+ },
+ "conflict": {
+ "doctrine/common": "<2.9@dev"
+ },
+ "require-dev": {
+ "doctrine/coding-standard": "^4.0",
+ "phpstan/phpstan": "^0.8",
+ "phpunit/phpunit": "^7.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Common\\": "lib/Doctrine/Common"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Roman Borschel",
+ "email": "roman@code-factory.org"
+ },
+ {
+ "name": "Benjamin Eberlei",
+ "email": "kontakt@beberlei.de"
+ },
+ {
+ "name": "Guilherme Blanco",
+ "email": "guilhermeblanco@gmail.com"
+ },
+ {
+ "name": "Jonathan Wage",
+ "email": "jonwage@gmail.com"
+ },
+ {
+ "name": "Johannes Schmitt",
+ "email": "schmittjoh@gmail.com"
+ },
+ {
+ "name": "Marco Pivetta",
+ "email": "ocramius@gmail.com"
+ }
+ ],
+ "description": "Doctrine Persistence abstractions.",
+ "homepage": "https://doctrine-project.org/projects/persistence.html",
+ "keywords": [
+ "persistence"
+ ],
+ "time": "2018-07-12T12:37:50+00:00"
+ },
+ {
+ "name": "doctrine/reflection",
+ "version": "v1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/reflection.git",
+ "reference": "02538d3f95e88eb397a5f86274deb2c6175c2ab6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/reflection/zipball/02538d3f95e88eb397a5f86274deb2c6175c2ab6",
+ "reference": "02538d3f95e88eb397a5f86274deb2c6175c2ab6",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/annotations": "^1.0",
+ "ext-tokenizer": "*",
+ "php": "^7.1"
+ },
+ "require-dev": {
+ "doctrine/coding-standard": "^4.0",
+ "doctrine/common": "^2.8",
+ "phpstan/phpstan": "^0.9.2",
+ "phpstan/phpstan-phpunit": "^0.9.4",
+ "phpunit/phpunit": "^7.0",
+ "squizlabs/php_codesniffer": "^3.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Common\\": "lib/Doctrine/Common"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Roman Borschel",
+ "email": "roman@code-factory.org"
+ },
+ {
+ "name": "Benjamin Eberlei",
+ "email": "kontakt@beberlei.de"
+ },
+ {
+ "name": "Guilherme Blanco",
+ "email": "guilhermeblanco@gmail.com"
+ },
+ {
+ "name": "Jonathan Wage",
+ "email": "jonwage@gmail.com"
+ },
+ {
+ "name": "Johannes Schmitt",
+ "email": "schmittjoh@gmail.com"
+ },
+ {
+ "name": "Marco Pivetta",
+ "email": "ocramius@gmail.com"
+ }
+ ],
+ "description": "Doctrine Reflection component",
+ "homepage": "https://www.doctrine-project.org/projects/reflection.html",
+ "keywords": [
+ "reflection"
+ ],
+ "time": "2018-06-14T14:45:07+00:00"
+ },
+ {
+ "name": "psr/log",
+ "version": "1.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/log.git",
+ "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
+ "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Log\\": "Psr/Log/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for logging libraries",
+ "homepage": "https://github.com/php-fig/log",
+ "keywords": [
+ "log",
+ "psr",
+ "psr-3"
+ ],
+ "time": "2016-10-10T12:19:37+00:00"
+ },
+ {
+ "name": "symfony/icu",
+ "version": "v1.0.1",
+ "target-dir": "Symfony/Component/Icu",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/icu.git",
+ "reference": "fdba214b1e087c149843bde976263c53ac10c975"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/icu/zipball/fdba214b1e087c149843bde976263c53ac10c975",
+ "reference": "fdba214b1e087c149843bde976263c53ac10c975",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3",
+ "symfony/intl": "~2.3"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-0": {
+ "Symfony\\Component\\Icu\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Symfony Community",
+ "homepage": "http://symfony.com/contributors"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@gmail.com"
+ }
+ ],
+ "description": "Contains an excerpt of the ICU data and classes to load it.",
+ "homepage": "http://symfony.com",
+ "keywords": [
+ "icu",
+ "intl"
+ ],
+ "abandoned": "symfony/intl",
+ "time": "2013-10-04T09:12:07+00:00"
+ },
+ {
+ "name": "symfony/polyfill-ctype",
+ "version": "v1.9.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-ctype.git",
+ "reference": "e3d826245268269cd66f8326bd8bc066687b4a19"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19",
+ "reference": "e3d826245268269cd66f8326bd8bc066687b4a19",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "suggest": {
+ "ext-ctype": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.9-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Ctype\\": ""
+ },
+ "files": [
+ "bootstrap.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ },
+ {
+ "name": "Gert de Pagter",
+ "email": "BackEndTea@gmail.com"
+ }
+ ],
+ "description": "Symfony polyfill for ctype functions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "ctype",
+ "polyfill",
+ "portable"
+ ],
+ "time": "2018-08-06T14:22:27+00:00"
+ },
+ {
+ "name": "symfony/symfony",
+ "version": "v2.5.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/symfony.git",
+ "reference": "e66ee967571b89234c90946fe0d50dad195ad29c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/symfony/zipball/e66ee967571b89234c90946fe0d50dad195ad29c",
+ "reference": "e66ee967571b89234c90946fe0d50dad195ad29c",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/common": "~2.2",
+ "php": ">=5.3.3",
+ "psr/log": "~1.0",
+ "symfony/icu": "~1.0",
+ "twig/twig": "~1.12"
+ },
+ "replace": {
+ "symfony/browser-kit": "self.version",
+ "symfony/class-loader": "self.version",
+ "symfony/config": "self.version",
+ "symfony/console": "self.version",
+ "symfony/css-selector": "self.version",
+ "symfony/debug": "self.version",
+ "symfony/dependency-injection": "self.version",
+ "symfony/doctrine-bridge": "self.version",
+ "symfony/dom-crawler": "self.version",
+ "symfony/event-dispatcher": "self.version",
+ "symfony/expression-language": "self.version",
+ "symfony/filesystem": "self.version",
+ "symfony/finder": "self.version",
+ "symfony/form": "self.version",
+ "symfony/framework-bundle": "self.version",
+ "symfony/http-foundation": "self.version",
+ "symfony/http-kernel": "self.version",
+ "symfony/intl": "self.version",
+ "symfony/locale": "self.version",
+ "symfony/monolog-bridge": "self.version",
+ "symfony/options-resolver": "self.version",
+ "symfony/process": "self.version",
+ "symfony/propel1-bridge": "self.version",
+ "symfony/property-access": "self.version",
+ "symfony/proxy-manager-bridge": "self.version",
+ "symfony/routing": "self.version",
+ "symfony/security": "self.version",
+ "symfony/security-acl": "self.version",
+ "symfony/security-bundle": "self.version",
+ "symfony/security-core": "self.version",
+ "symfony/security-csrf": "self.version",
+ "symfony/security-http": "self.version",
+ "symfony/serializer": "self.version",
+ "symfony/stopwatch": "self.version",
+ "symfony/swiftmailer-bridge": "self.version",
+ "symfony/templating": "self.version",
+ "symfony/translation": "self.version",
+ "symfony/twig-bridge": "self.version",
+ "symfony/twig-bundle": "self.version",
+ "symfony/validator": "self.version",
+ "symfony/web-profiler-bundle": "self.version",
+ "symfony/yaml": "self.version"
+ },
+ "require-dev": {
+ "doctrine/data-fixtures": "1.0.*",
+ "doctrine/dbal": "~2.2",
+ "doctrine/orm": "~2.2,>=2.2.3",
+ "egulias/email-validator": "1.1.0",
+ "ircmaxell/password-compat": "1.0.*",
+ "monolog/monolog": "~1.3",
+ "ocramius/proxy-manager": ">=0.3.1,<0.6-dev",
+ "propel/propel1": "1.6.*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.5-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Symfony\\": "src/"
+ },
+ "classmap": [
+ "src/Symfony/Component/HttpFoundation/Resources/stubs",
+ "src/Symfony/Component/Intl/Resources/stubs"
+ ],
+ "files": [
+ "src/Symfony/Component/Intl/Resources/stubs/functions.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Symfony Community",
+ "homepage": "http://symfony.com/contributors"
+ },
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ }
+ ],
+ "description": "The Symfony PHP framework",
+ "homepage": "http://symfony.com",
+ "keywords": [
+ "framework"
+ ],
+ "time": "2014-07-15T15:39:46+00:00"
+ },
+ {
+ "name": "twig/twig",
+ "version": "v1.35.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/twigphp/Twig.git",
+ "reference": "7e081e98378a1e78c29cc9eba4aefa5d78a05d2a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/twigphp/Twig/zipball/7e081e98378a1e78c29cc9eba4aefa5d78a05d2a",
+ "reference": "7e081e98378a1e78c29cc9eba4aefa5d78a05d2a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3",
+ "symfony/polyfill-ctype": "^1.8"
+ },
+ "require-dev": {
+ "psr/container": "^1.0",
+ "symfony/debug": "^2.7",
+ "symfony/phpunit-bridge": "^3.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.35-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Twig_": "lib/"
+ },
+ "psr-4": {
+ "Twig\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com",
+ "homepage": "http://fabien.potencier.org",
+ "role": "Lead Developer"
+ },
+ {
+ "name": "Armin Ronacher",
+ "email": "armin.ronacher@active-4.com",
+ "role": "Project Founder"
+ },
+ {
+ "name": "Twig Team",
+ "homepage": "https://twig.symfony.com/contributors",
+ "role": "Contributors"
+ }
+ ],
+ "description": "Twig, the flexible, fast, and secure template language for PHP",
+ "homepage": "https://twig.symfony.com",
+ "keywords": [
+ "templating"
+ ],
+ "time": "2018-07-13T07:12:17+00:00"
+ }
+ ],
+ "packages-dev": [],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": [],
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": [],
+ "platform-dev": []
+}
diff --git a/tests/Automatic/OperationsResolverTest.php b/tests/Automatic/OperationsResolverTest.php
index cad3623d..f2a7c8a2 100644
--- a/tests/Automatic/OperationsResolverTest.php
+++ b/tests/Automatic/OperationsResolverTest.php
@@ -234,14 +234,14 @@ public function testResolveWithRemoveAndLock(): void
->once()
->with(Automatic::LOCK_PACKAGES, $name)
->andReturn([
- 'pretty-name' => $name,
- 'version' => '1.0-dev',
- 'parent' => null,
- 'is-dev' => false,
- 'url' => null,
- 'operation' => 'install',
- 'type' => 'library',
- 'requires' => [
+ 'pretty-name' => $name,
+ 'version' => '1.0-dev',
+ 'parent' => null,
+ 'is-dev' => false,
+ 'url' => null,
+ 'operation' => 'install',
+ 'type' => 'library',
+ 'requires' => [
'viserio/contract',
],
'autoload' => [],
diff --git a/tests/Automatic/Security/AuditTest.php b/tests/Automatic/Security/AuditTest.php
new file mode 100644
index 00000000..c79fe71d
--- /dev/null
+++ b/tests/Automatic/Security/AuditTest.php
@@ -0,0 +1,234 @@
+audit = new Audit(__DIR__, new Downloader());
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function tearDown()
+ {
+ parent::tearDown();
+
+ (new Filesystem())->remove(__DIR__ . \DIRECTORY_SEPARATOR . 'narrowspark');
+ }
+
+ public function testCheckPackageWithSymfony(): void
+ {
+ [$vulnerabilities, $messages] = $this->audit->checkPackage('symfony/symfony', 'v2.5.2', $this->audit->getSecurityAdvisories());
+
+ $this->assertSymfonySecurity(\count($vulnerabilities), $vulnerabilities);
+ static::assertCount(0, $messages);
+ }
+
+ public function testCheckPackageWithSymfonyAndCache(): void
+ {
+ [$vulnerabilities, $messages] = $this->audit->checkPackage('symfony/symfony', 'v2.5.2', $this->audit->getSecurityAdvisories());
+
+ $this->assertSymfonySecurity(\count($vulnerabilities), $vulnerabilities);
+ static::assertCount(0, $messages);
+
+ [$vulnerabilities, $messages] = $this->audit->checkPackage('symfony/symfony', 'v2.5.2', $this->audit->getSecurityAdvisories());
+
+ $this->assertSymfonySecurity(\count($vulnerabilities), $vulnerabilities);
+ }
+
+ public function testCheckLockWithSymfony252(): void
+ {
+ [$vulnerabilities, $messages] = $this->audit->checkLock(
+ \dirname(__DIR__) . \DIRECTORY_SEPARATOR . 'Fixture' . \DIRECTORY_SEPARATOR . 'symfony_2.5.2_composer.lock'
+ );
+
+ $this->assertSymfonySecurity(\count($vulnerabilities), $vulnerabilities);
+ static::assertCount(0, $messages);
+ }
+
+ public function testCheckLockWithComposer171(): void
+ {
+ [$vulnerabilities, $messages] = $this->audit->checkLock(
+ \dirname(__DIR__) . \DIRECTORY_SEPARATOR . 'Fixture' . \DIRECTORY_SEPARATOR . 'composer_1.7.1_composer.lock'
+ );
+
+ static::assertCount(0, $vulnerabilities);
+ static::assertCount(0, $messages);
+ }
+
+ public function testCheckLockThrowsException(): void
+ {
+ $this->expectException(RuntimeException::class);
+ $this->expectExceptionMessage('Lock file does not exist.');
+
+ $this->audit->checkLock('');
+ }
+
+ public function testCheckPackageWithCustomPackage(): void
+ {
+ static::assertCount(0, $this->audit->checkPackage('fooa/fooa', 'v2.5.2', $this->audit->getSecurityAdvisories()));
+ }
+
+ /**
+ * @param int $vulnerabilitiesCount
+ * @param array $vulnerabilities
+ *
+ * @return void
+ */
+ private function assertSymfonySecurity(int $vulnerabilitiesCount, array $vulnerabilities): void
+ {
+ static::assertSame(1, $vulnerabilitiesCount);
+ static::assertEquals(
+ [
+ 'symfony/symfony' => [
+ 'version' => 'v2.5.2',
+ 'advisories' => [
+ 'CVE-2016-4423' => [
+ 'title' => 'CVE-2016-4423: Large username storage in session',
+ 'link' => 'https://symfony.com/cve-2016-4423',
+ 'cve' => 'CVE-2016-4423',
+ ],
+ 'CVE-2017-16654' => [
+ 'title' => 'CVE-2017-16654: Intl bundle readers breaking out of paths',
+ 'link' => 'https://symfony.com/cve-2017-16654',
+ 'cve' => 'CVE-2017-16654',
+ ],
+ 'CVE-2017-16652' => [
+ 'title' => 'CVE-2017-16652: Open redirect vulnerability on security handlers',
+ 'link' => 'https://symfony.com/cve-2017-16652',
+ 'cve' => 'CVE-2017-16652',
+ ],
+ 'CVE-2014-6061' => [
+ 'title' => 'Security issue when parsing the Authorization header',
+ 'link' => 'https://symfony.com/cve-2014-6061',
+ 'cve' => 'CVE-2014-6061',
+ ],
+ 'CVE-2015-4050' => [
+ 'title' => 'CVE-2015-4050: ESI unauthorized access',
+ 'link' => 'https://symfony.com/cve-2015-4050',
+ 'cve' => 'CVE-2015-4050',
+ ],
+ 'CVE-2018-11408' => [
+ 'title' => 'CVE-2018-11408: Open redirect vulnerability on security handlers',
+ 'link' => 'https://symfony.com/cve-2018-11408',
+ 'cve' => 'CVE-2018-11408',
+ ],
+ 'CVE-2018-11385' => [
+ 'title' => 'CVE-2018-11385: Session Fixation Issue for Guard Authentication',
+ 'link' => 'https://symfony.com/cve-2018-11385',
+ 'cve' => 'CVE-2018-11385',
+ ],
+ 'CVE-2014-4931' => [
+ 'title' => 'Code injection in the way Symfony implements translation caching in FrameworkBundle',
+ 'link' => 'https://symfony.com/blog/security-releases-cve-2014-4931-symfony-2-3-18-2-4-8-and-2-5-2-released',
+ 'cve' => 'CVE-2014-4931',
+ ],
+ 'CVE-2016-1902' => [
+ 'title' => 'CVE-2016-1902: SecureRandom\'s fallback not secure when OpenSSL fails ',
+ 'link' => 'https://symfony.com/cve-2016-1902',
+ 'cve' => 'CVE-2016-1902',
+ ],
+ 'CVE-2018-14773' => [
+ 'title' => 'CVE-2018-14773: Remove support for legacy and risky HTTP headers',
+ 'link' => 'https://symfony.com/blog/cve-2018-14773-remove-support-for-legacy-and-risky-http-headers',
+ 'cve' => 'CVE-2018-14773',
+ ],
+ 'CVE-2015-8124' => [
+ 'title' => 'CVE-2015-8124: Session Fixation in the "Remember Me" Login Feature',
+ 'link' => 'https://symfony.com/cve-2015-8124',
+ 'cve' => 'CVE-2015-8124',
+ ],
+ 'CVE-2015-2309' => [
+ 'title' => 'Unsafe methods in the Request class',
+ 'link' => 'https://symfony.com/cve-2015-2309',
+ 'cve' => 'CVE-2015-2309',
+ ],
+ 'CVE-2017-16653' => [
+ 'title' => 'CVE-2017-16653: CSRF protection does not use different tokens for HTTP and HTTPS',
+ 'link' => 'https://symfony.com/cve-2017-16653',
+ 'cve' => 'CVE-2017-16653',
+ ],
+ 'CVE-2017-11365' => [
+ 'title' => 'CVE-2017-11365: Empty passwords validation issue',
+ 'link' => 'https://symfony.com/cve-2017-11365',
+ 'cve' => 'CVE-2017-11365',
+ ],
+ 'CVE-2018-11386' => [
+ 'title' => 'CVE-2018-11386: Denial of service when using PDOSessionHandler',
+ 'link' => 'https://symfony.com/cve-2018-11386',
+ 'cve' => 'CVE-2018-11386',
+ ],
+ 'CVE-2018-11406' => [
+ 'title' => 'CVE-2018-11406: CSRF Token Fixation',
+ 'link' => 'https://symfony.com/cve-2018-11406',
+ 'cve' => 'CVE-2018-11406',
+ ],
+ 'CVE-2014-6072' => [
+ 'title' => 'CSRF vulnerability in the Web Profiler',
+ 'link' => 'https://symfony.com/cve-2014-6072',
+ 'cve' => 'CVE-2014-6072',
+ ],
+ 'CVE-2018-11407' => [
+ 'title' => 'CVE-2018-11407: Unauthorized access on a misconfigured LDAP server when using an empty password',
+ 'link' => 'https://symfony.com/cve-2018-11407',
+ 'cve' => 'CVE-2018-11407',
+ ],
+ 'CVE-2015-8125' => [
+ 'title' => 'CVE-2015-8125: Potential Remote Timing Attack Vulnerability in Security Remember-Me Service',
+ 'link' => 'https://symfony.com/cve-2015-8125',
+ 'cve' => 'CVE-2015-8125',
+ ],
+ 'CVE-2015-2308' => [
+ 'title' => 'Esi Code Injection',
+ 'link' => 'https://symfony.com/cve-2015-2308',
+ 'cve' => 'CVE-2015-2308',
+ ],
+ 'CVE-2016-2403' => [
+ 'title' => 'CVE-2016-2403: Unauthorized access on a misconfigured Ldap server when using an empty password',
+ 'link' => 'https://symfony.com/cve-2016-2403',
+ 'cve' => 'CVE-2016-2403',
+ ],
+ 'CVE-2014-5244' => [
+ 'title' => 'Denial of service with a malicious HTTP Host header',
+ 'link' => 'https://symfony.com/cve-2014-5244',
+ 'cve' => 'CVE-2014-5244',
+ ],
+ 'CVE-2014-5245' => [
+ 'title' => 'Direct access of ESI URLs behind a trusted proxy',
+ 'link' => 'https://symfony.com/cve-2014-5245',
+ 'cve' => 'CVE-2014-5245',
+ ],
+ 'CVE-2017-16790' => [
+ 'title' => 'CVE-2017-16790: Ensure that submitted data are uploaded files',
+ 'link' => 'https://symfony.com/cve-2017-16790',
+ 'cve' => 'CVE-2017-16790',
+ ],
+ ],
+ ],
+ ],
+ $vulnerabilities
+ );
+ }
+}
diff --git a/tests/Automatic/Security/Command/AuditCommandTest.php b/tests/Automatic/Security/Command/AuditCommandTest.php
new file mode 100644
index 00000000..c90f3555
--- /dev/null
+++ b/tests/Automatic/Security/Command/AuditCommandTest.php
@@ -0,0 +1,158 @@
+application = new Application();
+ }
+
+ public function testAuditCommand(): void
+ {
+ \putenv('COMPOSER=' . \dirname(__DIR__, 2) . \DIRECTORY_SEPARATOR . 'Fixture' . \DIRECTORY_SEPARATOR . 'composer_1.7.1_composer.lock');
+
+ $commandTester = $this->executeCommand(new AuditCommand());
+
+ static::assertContains('[+] No known vulnerabilities found', \trim($commandTester->getDisplay(true)));
+
+ \putenv('COMPOSER=');
+ \putenv('COMPOSER');
+ }
+
+ public function testAuditCommandWithComposerLockOption(): void
+ {
+ $commandTester = $this->executeCommand(
+ new AuditCommand(),
+ ['--composer-lock' => \dirname(__DIR__, 2) . \DIRECTORY_SEPARATOR . 'Fixture' . \DIRECTORY_SEPARATOR . 'composer_1.7.1_composer.lock']
+ );
+
+ $output = \trim($commandTester->getDisplay(true));
+
+ static::assertContains('=== Audit Security Report ===', $output);
+ static::assertContains('This checker can only detect vulnerabilities that are referenced', $output);
+ static::assertContains('[+] No known vulnerabilities found', $output);
+ }
+
+ public function testAuditCommandWithEmptyComposerLockPath(): void
+ {
+ $commandTester = $this->executeCommand(
+ new AuditCommand(),
+ ['--composer-lock' => 'composer_1.7.1_composer.lock']
+ );
+
+ $output = \trim($commandTester->getDisplay(true));
+
+ static::assertContains(\trim('=== Audit Security Report ==='), $output);
+ static::assertContains(\trim('Lock file does not exist.'), $output);
+ }
+
+ public function testAuditCommandWithError(): void
+ {
+ $commandTester = $this->executeCommand(
+ new AuditCommand(),
+ ['--composer-lock' => \dirname(__DIR__, 2) . \DIRECTORY_SEPARATOR . 'Fixture' . \DIRECTORY_SEPARATOR . 'symfony_2.5.2_composer.lock']
+ );
+
+ $output = \trim($commandTester->getDisplay(true));
+
+ static::assertContains('=== Audit Security Report ===', $output);
+ static::assertContains('This checker can only detect vulnerabilities that are referenced', $output);
+ static::assertContains('symfony/symfony (v2.5.2)', $output);
+ static::assertContains('[!] 1 vulnerability found - We recommend you to check the related security advisories and upgrade these dependencies.', $output);
+ }
+
+ public function testAuditCommandWithErrorAndJsonFormat(): void
+ {
+ $commandTester = $this->executeCommand(
+ new AuditCommand(),
+ [
+ '--composer-lock' => \dirname(__DIR__, 2) . \DIRECTORY_SEPARATOR . 'Fixture' . \DIRECTORY_SEPARATOR . 'symfony_2.5.2_composer.lock',
+ '--format' => 'json',
+ '--timeout' => '20',
+ ]
+ );
+
+ $output = \trim($commandTester->getDisplay(true));
+
+ $jsonOutput = \str_replace(
+ [
+ '=== Audit Security Report ===',
+ '//',
+ 'This checker can only detect vulnerabilities that are referenced',
+ 'in the',
+ 'SensioLabs security advisories database.',
+ '[!] 1 vulnerability found - We recommend you to check the related security advisories and upgrade these dependencies.',
+ ],
+ '',
+ $output
+ );
+
+ static::assertJson($jsonOutput);
+ static::assertContains('=== Audit Security Report ===', $output);
+ static::assertContains('This checker can only detect vulnerabilities that are referenced', $output);
+ static::assertContains('[!] 1 vulnerability found - We recommend you to check the related security advisories and upgrade these dependencies.', $output);
+ }
+
+ public function testAuditCommandWithErrorAndSimpleFormat(): void
+ {
+ $commandTester = $this->executeCommand(
+ new AuditCommand(),
+ [
+ '--composer-lock' => \dirname(__DIR__, 2) . \DIRECTORY_SEPARATOR . 'Fixture' . \DIRECTORY_SEPARATOR . 'symfony_2.5.2_composer.lock',
+ '--format' => 'simple',
+ ]
+ );
+
+ $output = \trim($commandTester->getDisplay(true));
+
+ static::assertContains('=== Audit Security Report ===', $output);
+ static::assertContains(\trim('symfony/symfony (v2.5.2)
+------------------------
+'), $output);
+ static::assertContains('This checker can only detect vulnerabilities that are referenced', $output);
+ static::assertContains('[!] 1 vulnerability found - We recommend you to check the related security advisories and upgrade these dependencies.', $output);
+ }
+
+ /**
+ * @param \Symfony\Component\Console\Command\Command $command
+ * @param array $input
+ * @param array $options
+ *
+ * @return \Symfony\Component\Console\Tester\CommandTester
+ */
+ protected function executeCommand(Command $command, array $input = [], array $options = []): CommandTester
+ {
+ $this->application->add($command);
+
+ $reflectionProperty = (new \ReflectionClass($command))->getProperty('defaultName');
+ $reflectionProperty->setAccessible(true);
+
+ $command = $this->application->find($reflectionProperty->getValue($command));
+
+ $commandTester = new CommandTester($command);
+ $commandTester->execute(['command' => $command->getName()] + $input, $options);
+
+ return $commandTester;
+ }
+}
diff --git a/tests/Automatic/Security/DownloaderTest.php b/tests/Automatic/Security/DownloaderTest.php
new file mode 100644
index 00000000..d35e1ed7
--- /dev/null
+++ b/tests/Automatic/Security/DownloaderTest.php
@@ -0,0 +1,42 @@
+downloader = new Downloader();
+ }
+
+ public function testDownloadWithComposer(): void
+ {
+ static::assertNotEmpty($this->downloader->downloadWithComposer(self::SECURITY_ADVISORIES_SHA));
+ }
+
+ public function testDownloadWithCurl(): void
+ {
+ static::assertNotEmpty($this->downloader->downloadWithCurl(self::SECURITY_ADVISORIES_SHA));
+ }
+}
diff --git a/tests/Common/PackageTest.php b/tests/Common/PackageTest.php
index cf662214..7a0496bd 100644
--- a/tests/Common/PackageTest.php
+++ b/tests/Common/PackageTest.php
@@ -178,16 +178,16 @@ public function testToArray(): void
public function testCreateFromLock(): void
{
$lockdata = [
- 'pretty-name' => 'test/Test',
- 'version' => '1',
- 'parent' => null,
- 'is-dev' => false,
- 'url' => null,
- 'operation' => null,
- 'type' => null,
- 'requires' => [],
- 'automatic-extra' => [],
- 'created' => $this->package->getTime(),
+ 'pretty-name' => 'test/Test',
+ 'version' => '1',
+ 'parent' => null,
+ 'is-dev' => false,
+ 'url' => null,
+ 'operation' => null,
+ 'type' => null,
+ 'requires' => [],
+ 'automatic-extra' => [],
+ 'created' => $this->package->getTime(),
];
static::assertInstanceOf(ContractPackage::class, Package::createFromLock('test/test', $lockdata));