From e45f7b1225f8647dc712a673d9e56dd3627fb946 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Forsg=C3=A5rd?= Date: Wed, 28 Jan 2015 01:48:30 +0100 Subject: [PATCH 01/16] Prepare for a configuration file implementation --- src/Command/ScanCommand.php | 118 +++++++++++++----------------- src/Conf/Configuration.php | 58 +++++++++++++++ src/Conf/DefaultConf.php | 44 +++++++++++ src/Conf/DualConf.php | 103 ++++++++++++++++++++++++++ src/Conf/UserConf.php | 75 +++++++++++++++++++ src/RuleCollection.php | 15 ++++ tests/Command/ScanCommandTest.php | 19 ----- tests/Conf/DefaultConfTest.php | 18 +++++ tests/Conf/DualConfTest.php | 41 +++++++++++ tests/Conf/UserConfTest.php | 92 +++++++++++++++++++++++ tests/RuleCollectionTest.php | 10 +++ 11 files changed, 507 insertions(+), 86 deletions(-) create mode 100644 src/Conf/Configuration.php create mode 100644 src/Conf/DefaultConf.php create mode 100644 src/Conf/DualConf.php create mode 100644 src/Conf/UserConf.php create mode 100644 tests/Conf/DefaultConfTest.php create mode 100644 tests/Conf/DualConfTest.php create mode 100644 tests/Conf/UserConfTest.php diff --git a/src/Command/ScanCommand.php b/src/Command/ScanCommand.php index 3688375..a08097f 100644 --- a/src/Command/ScanCommand.php +++ b/src/Command/ScanCommand.php @@ -4,11 +4,17 @@ use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputArgument; -use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Helper\ProgressBar; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Psecio\Parse\RuleFactory; +use Psecio\Parse\FileIterator; +use Psecio\Parse\CallbackVisitor; +use Psecio\Parse\Scanner; +use Psecio\Parse\Conf\DualConf; +use Psecio\Parse\Conf\UserConf; use Psecio\Parse\Subscriber\ExitCodeCatcher; use Psecio\Parse\Subscriber\ConsoleDots; use Psecio\Parse\Subscriber\ConsoleProgressBar; @@ -18,11 +24,6 @@ use Psecio\Parse\Subscriber\Xml; use Psecio\Parse\Event\Events; use Psecio\Parse\Event\MessageEvent; -use Psecio\Parse\RuleFactory; -use Psecio\Parse\RuleInterface; -use Psecio\Parse\Scanner; -use Psecio\Parse\CallbackVisitor; -use Psecio\Parse\FileIterator; use RuntimeException; /** @@ -40,43 +41,56 @@ protected function configure() ->addArgument( 'path', InputArgument::OPTIONAL|InputArgument::IS_ARRAY, - 'Paths to scan', - [] + 'Path to scan' ) ->addOption( 'format', - null, + 'f', InputOption::VALUE_REQUIRED, - 'Output format (progress, dots or xml)', - 'progress' + 'Output format (progress, dots or xml)' ) ->addOption( 'ignore-paths', - null, + 'i', InputOption::VALUE_REQUIRED, - 'Comma-separated list of paths to ignore', - '' + 'Comma-separated list of paths to ignore' ) ->addOption( 'extensions', - null, + 'x', InputOption::VALUE_REQUIRED, - 'Comma-separated list of file extensions to parse', - 'php,phps,phtml,php5' + 'Comma-separated list of file extensions to parse (default: php,phps,phtml,php5)' ) ->addOption( 'whitelist-rules', - null, + 'w', InputOption::VALUE_REQUIRED, - 'Comma-separated list of rules to use', - '' + 'Comma-separated list of rules to whitelist' ) ->addOption( 'blacklist-rules', - null, + 'b', InputOption::VALUE_REQUIRED, - 'Comma-separated list of rules to skip', - '' + 'Comma-separated list of rules to blacklist' + ) + ->addOption( + 'disable-annotations', + 'd', + InputOption::VALUE_NONE, + 'Skip all annotation-based rule toggles' + ) + ->addOption( + 'configuration', + 'c', + InputOption::VALUE_REQUIRED, + 'Read configuration from file', + '.psecio-parse.json' + ) + ->addOption( + 'no-configuration', + null, + InputOption::VALUE_NONE, + 'Ignore default configuration file' ) ->setHelp( "Scan paths for possible security issues:\n\n psecio-parse %command.name% /path/to/src\n" @@ -88,24 +102,22 @@ protected function configure() * * @param InputInterface $input Input object * @param OutputInterface $output Output object - * @throws RuntimeException If output format is not valid * @return void */ protected function execute(InputInterface $input, OutputInterface $output) { + $conf = new DualConf(new UserConf($input)); + + $rules = (new RuleFactory($conf->getRuleWhitelist(), $conf->getRuleBlacklist()))->createRuleCollection(); + + $files = new FileIterator($conf->getPaths(), $conf->getIgnorePaths(), $conf->getExtensions()); + $dispatcher = new EventDispatcher; $exitCode = new ExitCodeCatcher; $dispatcher->addSubscriber($exitCode); - $fileIterator = new FileIterator( - $input->getArgument('path'), - $this->parseCsv($input->getOption('ignore-paths')), - $this->parseCsv($input->getOption('extensions')) - ); - - $format = strtolower($input->getOption('format')); - switch ($format) { + switch ($conf->getFormat()) { case 'dots': case 'progress': $output->writeln("Parse: A PHP Security Scanner\n"); @@ -117,9 +129,9 @@ protected function execute(InputInterface $input, OutputInterface $output) $dispatcher->addSubscriber( new ConsoleLines($output) ); - } elseif ('progress' == $format && $output->isDecorated()) { + } elseif ('progress' == $conf->getFormat() && $output->isDecorated()) { $dispatcher->addSubscriber( - new ConsoleProgressBar(new ProgressBar($output, count($fileIterator))) + new ConsoleProgressBar(new ProgressBar($output, count($files))) ); } else { $dispatcher->addSubscriber( @@ -132,42 +144,14 @@ protected function execute(InputInterface $input, OutputInterface $output) $dispatcher->addSubscriber(new Xml($output)); break; default: - throw new RuntimeException("Unknown output format '{$input->getOption('format')}'"); + throw new RuntimeException("Unknown output format '{$conf->getFormat()}'"); } - $ruleFactory = new RuleFactory( - $this->parseCsv($input->getOption('whitelist-rules')), - $this->parseCsv($input->getOption('blacklist-rules')) - ); - - $ruleCollection = $ruleFactory->createRuleCollection(); + $dispatcher->dispatch(Events::DEBUG, new MessageEvent("Using ruleset: $rules")); - $ruleNames = implode(',', array_map( - function (RuleInterface $rule) { - return $rule->getName(); - }, - $ruleCollection->toArray() - )); - - $dispatcher->dispatch(Events::DEBUG, new MessageEvent("Using ruleset $ruleNames")); - - $scanner = new Scanner($dispatcher, new CallbackVisitor($ruleCollection)); - $scanner->scan($fileIterator); + $scanner = new Scanner($dispatcher, new CallbackVisitor($rules)); + $scanner->scan($files); return $exitCode->getExitCode(); } - - /** - * Parse comma-separated values from string - * - * Using array_filter ensures that an empty array is returned when an empty - * string is parsed. - * - * @param string $string - * @return array - */ - public function parseCsv($string) - { - return array_filter(explode(',', $string)); - } } diff --git a/src/Conf/Configuration.php b/src/Conf/Configuration.php new file mode 100644 index 0000000..e536b70 --- /dev/null +++ b/src/Conf/Configuration.php @@ -0,0 +1,58 @@ +primary = $primary; + $this->secondary = $secondary ?: new DefaultConf; + } + + /** + * Get output format identifier + * + * @return string + */ + public function getFormat() + { + return strtolower($this->primary->getFormat() ?: $this->secondary->getFormat()); + } + + /** + * Get list of paths to scan + * + * @return string[] + */ + public function getPaths() + { + return $this->primary->getPaths() ?: $this->secondary->getPaths(); + } + + /** + * Get list of paths to ignore + * + * @return string[] + */ + public function getIgnorePaths() + { + return $this->primary->getIgnorePaths() ?: $this->secondary->getIgnorePaths(); + } + + /** + * Get list of extensions to scan + * + * @return string[] + */ + public function getExtensions() + { + return $this->primary->getExtensions() ?: $this->secondary->getExtensions(); + } + + /** + * Get list of whitelisted rules + * + * @return string[] + */ + public function getRuleWhitelist() + { + return $this->primary->getRuleWhitelist() ?: $this->secondary->getRuleWhitelist(); + } + + /** + * Get list of blacklisted rules + * + * @return string[] + */ + public function getRuleBlacklist() + { + return $this->primary->getRuleBlacklist() ?: $this->secondary->getRuleBlacklist(); + } + + /** + * Check if annotations should be disabled + * + * @return boolean + */ + public function disableAnnotations() + { + return $this->primary->disableAnnotations() || $this->secondary->disableAnnotations(); + } +} diff --git a/src/Conf/UserConf.php b/src/Conf/UserConf.php new file mode 100644 index 0000000..6517cec --- /dev/null +++ b/src/Conf/UserConf.php @@ -0,0 +1,75 @@ +input = $input; + } + + public function getFormat() + { + return $this->input->getOption('format'); + } + + public function getPaths() + { + return $this->input->getArgument('path'); + } + + public function getIgnorePaths() + { + return $this->parseCsv($this->input->getOption('ignore-paths')); + } + + public function getExtensions() + { + return $this->parseCsv($this->input->getOption('extensions')); + } + + public function getRuleWhitelist() + { + return $this->parseCsv($this->input->getOption('whitelist-rules')); + } + + public function getRuleBlacklist() + { + return $this->parseCsv($this->input->getOption('blacklist-rules')); + } + + public function disableAnnotations() + { + return $this->input->getOption('disable-annotations'); + } + + /** + * Parse comma-separated values from string + * + * Using array_filter ensures that an empty array is returned when an empty + * string is parsed. + * + * @param string $string + * @return array + */ + private function parseCsv($string) + { + return array_filter(explode(',', $string)); + } +} diff --git a/src/RuleCollection.php b/src/RuleCollection.php index 3844d80..40bcaab 100644 --- a/src/RuleCollection.php +++ b/src/RuleCollection.php @@ -111,4 +111,19 @@ public function toArray() { return $this->rules; } + + /** + * Get a comma separated list of rules in collection + * + * @return string + */ + public function __toString() + { + return implode(',', array_map( + function (RuleInterface $rule) { + return $rule->getName(); + }, + $this->toArray() + )); + } } diff --git a/tests/Command/ScanCommandTest.php b/tests/Command/ScanCommandTest.php index a1359ac..6d384a6 100644 --- a/tests/Command/ScanCommandTest.php +++ b/tests/Command/ScanCommandTest.php @@ -78,25 +78,6 @@ public function testExceptionOnUnknownFormat() $this->executeCommand(['--format' => 'this-format-does-not-exist']); } - public function testParseCsv() - { - $this->assertSame( - ['php', 'phps'], - (new ScanCommand)->parseCsv('php,phps'), - 'parsing comma separated values should work' - ); - $this->assertSame( - ['php', 'phps'], - array_values((new ScanCommand)->parseCsv('php,,phps')), - 'multiple commas should be skipped while parsing csv' - ); - $this->assertSame( - [], - (new ScanCommand)->parseCsv(''), - 'parsing an empty string should return an empty array' - ); - } - private function executeCommand(array $input, array $options = array()) { $application = new Application; diff --git a/tests/Conf/DefaultConfTest.php b/tests/Conf/DefaultConfTest.php new file mode 100644 index 0000000..209f477 --- /dev/null +++ b/tests/Conf/DefaultConfTest.php @@ -0,0 +1,18 @@ +assertSame('progress', $conf->getFormat()); + $this->assertSame([], $conf->getPaths()); + $this->assertSame([], $conf->getIgnorePaths()); + $this->assertSame(['php', 'phps', 'phtml', 'php5'], $conf->getExtensions()); + $this->assertSame([], $conf->getRuleWhitelist()); + $this->assertSame([], $conf->getRuleBlacklist()); + $this->assertFalse($conf->disableAnnotations()); + } +} diff --git a/tests/Conf/DualConfTest.php b/tests/Conf/DualConfTest.php new file mode 100644 index 0000000..6b22850 --- /dev/null +++ b/tests/Conf/DualConfTest.php @@ -0,0 +1,41 @@ +shouldReceive($method)->andReturn($first)->mock(), + m::mock('Psecio\Parse\Conf\Configuration')->shouldReceive($method)->andReturn($second)->mock() + ); + $this->assertSame($expected, $conf->$method()); + } +} diff --git a/tests/Conf/UserConfTest.php b/tests/Conf/UserConfTest.php new file mode 100644 index 0000000..135d929 --- /dev/null +++ b/tests/Conf/UserConfTest.php @@ -0,0 +1,92 @@ +shouldReceive('getOption') + ->with('format') + ->andReturn('foobar') + ->mock() + ); + $this->assertSame('foobar', $conf->getFormat()); + } + + public function testPaths() + { + $conf = new UserConf( + m::mock('Symfony\Component\Console\Input\InputInterface') + ->shouldReceive('getArgument') + ->with('path') + ->andReturn(['path']) + ->mock() + ); + $this->assertSame(['path'], $conf->getPaths()); + } + + public function testIgnorePaths() + { + $conf = new UserConf( + m::mock('Symfony\Component\Console\Input\InputInterface') + ->shouldReceive('getOption') + ->with('ignore-paths') + ->andReturn('path1,path2') + ->mock() + ); + $this->assertSame(['path1', 'path2'], $conf->getIgnorePaths()); + } + + public function testExtensions() + { + $conf = new UserConf( + m::mock('Symfony\Component\Console\Input\InputInterface') + ->shouldReceive('getOption') + ->with('extensions') + ->andReturn('php,,phps') + ->mock() + ); + $this->assertSame(['php', 'phps'], array_values($conf->getExtensions())); + } + + public function testRuleWhitelist() + { + $conf = new UserConf( + m::mock('Symfony\Component\Console\Input\InputInterface') + ->shouldReceive('getOption') + ->with('whitelist-rules') + ->andReturn('') + ->mock() + ); + $this->assertSame([], $conf->getRuleWhitelist()); + } + + public function testRuleBlacklist() + { + $conf = new UserConf( + m::mock('Symfony\Component\Console\Input\InputInterface') + ->shouldReceive('getOption') + ->with('blacklist-rules') + ->andReturn('rule') + ->mock() + ); + $this->assertSame(['rule'], $conf->getRuleBlacklist()); + } + + public function testDisableAnnotations() + { + $conf = new UserConf( + m::mock('Symfony\Component\Console\Input\InputInterface') + ->shouldReceive('getOption') + ->with('disable-annotations') + ->andReturn(true) + ->mock() + ); + $this->assertTrue($conf->disableAnnotations()); + } +} diff --git a/tests/RuleCollectionTest.php b/tests/RuleCollectionTest.php index fb18c80..fbc9111 100644 --- a/tests/RuleCollectionTest.php +++ b/tests/RuleCollectionTest.php @@ -116,4 +116,14 @@ public function testExceptionInRemove() $this->setExpectedException('RuntimeException'); (new RuleCollection)->remove('does-not-exist'); } + + public function testTostring() + { + $ruleA = m::mock('\Psecio\Parse\RuleInterface')->shouldReceive('getName')->andReturn('a')->mock(); + $ruleB = m::mock('\Psecio\Parse\RuleInterface')->shouldReceive('getName')->andReturn('b')->mock(); + $this->assertSame( + 'a,b', + (string)(new RuleCollection([$ruleA, $ruleB])) + ); + } } From 37bc910880c4a3d5464085a600d57b497c896d09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Forsg=C3=A5rd?= Date: Thu, 29 Jan 2015 23:30:40 +0100 Subject: [PATCH 02/16] Add support for json configuration files --- .gitignore | 1 + .psecio-parse.json | 8 +++ .travis.yml | 11 ++++- composer.json | 7 +-- src/Command/ScanCommand.php | 14 ++++-- src/Conf/ConfFactory.php | 52 ++++++++++++++++++++ src/Conf/DualConf.php | 10 ++-- src/Conf/JsonConf.php | 89 ++++++++++++++++++++++++++++++++++ src/Conf/schema.json | 64 ++++++++++++++++++++++++ src/File.php | 2 +- tests/Conf/ConfFactoryTest.php | 79 ++++++++++++++++++++++++++++++ tests/Conf/JsonConfTest.php | 49 +++++++++++++++++++ 12 files changed, 371 insertions(+), 15 deletions(-) create mode 100644 .psecio-parse.json create mode 100644 src/Conf/ConfFactory.php create mode 100644 src/Conf/JsonConf.php create mode 100644 src/Conf/schema.json create mode 100644 tests/Conf/ConfFactoryTest.php create mode 100644 tests/Conf/JsonConfTest.php diff --git a/.gitignore b/.gitignore index 3c28a27..d29deb5 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ /bin/coveralls /bin/phpunit /bin/test-reporter +/bin/validate-json diff --git a/.psecio-parse.json b/.psecio-parse.json new file mode 100644 index 0000000..6adba54 --- /dev/null +++ b/.psecio-parse.json @@ -0,0 +1,8 @@ +{ + "paths": [ + "src", + "tests", + "bin/psecio-parse" + ], + "format": "progress" +} diff --git a/.travis.yml b/.travis.yml index 954d3ef..8f26c55 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,9 +6,18 @@ php: - 5.5 - 5.4 +env: + matrix: + - PREFER_LOWEST="--prefer-lowest --prefer-stable" + - PREFER_LOWEST="" + install: - composer install --dev --prefer-source +before_script: + - composer self-update + - composer update --prefer-source $PREFER_LOWEST + script: - bin/phpunit - - bin/psecio-parse scan src tests + - bin/psecio-parse scan diff --git a/composer.json b/composer.json index 94511b9..ca8a540 100644 --- a/composer.json +++ b/composer.json @@ -17,12 +17,13 @@ "nikic/php-parser": "~1.0", "symfony/console": "~2.5", "symfony/event-dispatcher": "~2.4", - "eloquent/blox": "~3.0" + "eloquent/blox": "~3.0", + "justinrainbow/json-schema": ">=1.3.2,<1.4" }, "require-dev": { - "phpunit/phpunit": "4.2.*", + "phpunit/phpunit": "~4.2", "codeclimate/php-test-reporter": "dev-master", - "mockery/mockery": "0.9.*", + "mockery/mockery": ">=0.9.3", "akamon/mockery-callable-mock": "~1.0" }, "autoload": { diff --git a/src/Command/ScanCommand.php b/src/Command/ScanCommand.php index a08097f..34b41e7 100644 --- a/src/Command/ScanCommand.php +++ b/src/Command/ScanCommand.php @@ -13,8 +13,8 @@ use Psecio\Parse\FileIterator; use Psecio\Parse\CallbackVisitor; use Psecio\Parse\Scanner; -use Psecio\Parse\Conf\DualConf; -use Psecio\Parse\Conf\UserConf; +use Psecio\Parse\File; +use Psecio\Parse\Conf\ConfFactory; use Psecio\Parse\Subscriber\ExitCodeCatcher; use Psecio\Parse\Subscriber\ConsoleDots; use Psecio\Parse\Subscriber\ConsoleProgressBar; @@ -25,6 +25,7 @@ use Psecio\Parse\Event\Events; use Psecio\Parse\Event\MessageEvent; use RuntimeException; +use SplFileInfo; /** * The main command, scan paths for possible security issues @@ -83,8 +84,7 @@ protected function configure() 'configuration', 'c', InputOption::VALUE_REQUIRED, - 'Read configuration from file', - '.psecio-parse.json' + 'Read configuration from file' ) ->addOption( 'no-configuration', @@ -106,7 +106,7 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { - $conf = new DualConf(new UserConf($input)); + $conf = (new ConfFactory)->createConf($input, $confFileName); $rules = (new RuleFactory($conf->getRuleWhitelist(), $conf->getRuleBlacklist()))->createRuleCollection(); @@ -147,6 +147,10 @@ protected function execute(InputInterface $input, OutputInterface $output) throw new RuntimeException("Unknown output format '{$conf->getFormat()}'"); } + if ($confFileName) { + $dispatcher->dispatch(Events::DEBUG, new MessageEvent("Reading configurations from $confFileName")); + } + $dispatcher->dispatch(Events::DEBUG, new MessageEvent("Using ruleset: $rules")); $scanner = new Scanner($dispatcher, new CallbackVisitor($rules)); diff --git a/src/Conf/ConfFactory.php b/src/Conf/ConfFactory.php new file mode 100644 index 0000000..29a6dfa --- /dev/null +++ b/src/Conf/ConfFactory.php @@ -0,0 +1,52 @@ +getConfFileInfo($input)) { + $confFileName = $confFileInfo->getFilename(); + $conf = new DualConf($conf, new JsonConf((new File($confFileInfo))->getContents())); + } + + return new DualConf($conf, new DefaultConf); + } + + /** + * Get info on configuration file to use + * + * @param InputInterface $input + * @return SplFileInfo|void + */ + private function getConfFileInfo(InputInterface $input) + { + if ($filename = $input->getOption('configuration')) { + return new SplFileInfo($filename); + } + + if (!$input->getOption('no-configuration')) { + $confFileInfo = new SplFileInfo('.psecio-parse.json'); + if ($confFileInfo->isReadable()) { + return $confFileInfo; + } + } + } +} diff --git a/src/Conf/DualConf.php b/src/Conf/DualConf.php index 14f8d02..0becb53 100644 --- a/src/Conf/DualConf.php +++ b/src/Conf/DualConf.php @@ -3,7 +3,7 @@ namespace Psecio\Parse\Conf; /** - * Read configurations from dual sources + * Merge configurations from dual sources */ class DualConf implements Configuration { @@ -22,13 +22,13 @@ class DualConf implements Configuration * * Primary configurations take precedence if specified * - * @param Configuration $primary - * @param Configuration|null $secondary + * @param Configuration $primary + * @param Configuration $secondary */ - public function __construct(Configuration $primary, Configuration $secondary = null) + public function __construct(Configuration $primary, Configuration $secondary) { $this->primary = $primary; - $this->secondary = $secondary ?: new DefaultConf; + $this->secondary = $secondary; } /** diff --git a/src/Conf/JsonConf.php b/src/Conf/JsonConf.php new file mode 100644 index 0000000..2df0adf --- /dev/null +++ b/src/Conf/JsonConf.php @@ -0,0 +1,89 @@ +data = json_decode($json); + + $retriever = new UriRetriever; + $schema = $retriever->retrieve('file://' . realpath(__DIR__ . '/schema.json')); + + $validator = new Validator; + $validator->check($this->data, $schema); + + foreach ($validator->getErrors() as $error) { + throw new RuntimeException("Invalid configuration for {$error['property']}\n{$error['message']}"); + } + } + + public function getFormat() + { + return $this->read('format', ''); + } + + public function getPaths() + { + return $this->read('paths', []); + } + + public function getIgnorePaths() + { + return $this->read('ignore-paths', []); + } + + public function getExtensions() + { + return $this->read('extensions', []); + } + + public function getRuleWhitelist() + { + return $this->read('whitelist-rules', []); + } + + public function getRuleBlacklist() + { + return $this->read('blacklist-rules', []); + } + + public function disableAnnotations() + { + return $this->read('disable-annotations', false); + } + + /** + * Read configuration options + * + * @param string $option Name of config to read + * @param mixed $default Returned if otion is not set + * @return mixed + */ + private function read($property, $option) + { + if (property_exists($this->data, $property)) { + return $this->data->$property; + } + return $option; + } +} diff --git a/src/Conf/schema.json b/src/Conf/schema.json new file mode 100644 index 0000000..f5c6664 --- /dev/null +++ b/src/Conf/schema.json @@ -0,0 +1,64 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Configurations", + "description": "Configurations for the psecio-parse security scanner", + "type": "object", + "properties": { + "paths": { + "description": "List of paths to scan", + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "ignore-paths": { + "description": "List of paths to ignore", + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "extensions": { + "description": "List of file extensions to scan", + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "whitelist-rules": { + "description": "List of rules to whitelist", + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "blacklist-rules": { + "description": "List of rules to blacklist", + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "disable-annotations": { + "description": "Flag if all annotation-based rule toggles should be ignored", + "type": "boolean" + }, + "format": { + "description": "Output format", + "type": "string", + "enum": [ + "progress", + "dots", + "lines", + "debug", + "xml" + ] + } + }, + "additionalProperties": false +} diff --git a/src/File.php b/src/File.php index 20c4d38..dbf7385 100644 --- a/src/File.php +++ b/src/File.php @@ -30,7 +30,7 @@ class File public function __construct(SplFileInfo $splFileInfo) { if (!$splFileInfo->isReadable()) { - throw new RuntimeException("Failed to open file {$splFileInfo->getRealPath()}"); + throw new RuntimeException("Failed to open file {$splFileInfo->getFilename()}"); } $this->splFileInfo = $splFileInfo; $this->lines = file($splFileInfo->getRealPath(), FILE_IGNORE_NEW_LINES); diff --git a/tests/Conf/ConfFactoryTest.php b/tests/Conf/ConfFactoryTest.php new file mode 100644 index 0000000..c233928 --- /dev/null +++ b/tests/Conf/ConfFactoryTest.php @@ -0,0 +1,79 @@ +setExpectedException('RuntimeException'); + (new ConfFactory)->createConf( + m::mock('Symfony\Component\Console\Input\InputInterface') + ->shouldReceive('getOption') + ->with('configuration') + ->andReturn('this-file-does-not-exist') + ->mock() + ); + } + + public function testCustomConfFile() + { + $filename = sys_get_temp_dir() . '/' . uniqid('psecio-parse') . '.json'; + file_put_contents($filename, '{"extensions": ["foobar"]}'); + + $conf = (new ConfFactory)->createConf( + m::mock('Symfony\Component\Console\Input\InputInterface') + ->shouldReceive('getOption')->with('configuration')->andReturn($filename) + ->shouldReceive('getOption')->with('extensions')->andReturn('') + ->mock() + ); + $this->assertSame(['foobar'], $conf->getExtensions()); + + unlink($filename); + } + + public function testDefaultConfFile() + { + $cwd = getcwd(); + chdir(sys_get_temp_dir()); + + $filename = sys_get_temp_dir() . '/.psecio-parse.json'; + file_put_contents($filename, '{"extensions": ["foobar"]}'); + + $confUsingDefaultFile = (new ConfFactory)->createConf( + m::mock('Symfony\Component\Console\Input\InputInterface') + ->shouldReceive('getOption')->with('configuration')->andReturn('') + ->shouldReceive('getOption')->with('no-configuration')->andReturn(false) + ->shouldReceive('getOption')->with('extensions')->andReturn('') + ->mock() + ); + $this->assertSame(['foobar'], $confUsingDefaultFile->getExtensions()); + + $confIgnoringDefaultFile = (new ConfFactory)->createConf( + m::mock('Symfony\Component\Console\Input\InputInterface') + ->shouldReceive('getOption')->with('configuration')->andReturn('') + ->shouldReceive('getOption')->with('no-configuration')->andReturn(true) + ->shouldReceive('getOption')->with('extensions')->andReturn('') + ->mock() + ); + $this->assertTrue(in_array('php', $confIgnoringDefaultFile->getExtensions())); + + unlink($filename); + + $confDefaultFileMissing = (new ConfFactory)->createConf( + m::mock('Symfony\Component\Console\Input\InputInterface') + ->shouldReceive('getOption')->with('configuration')->andReturn('') + ->shouldReceive('getOption')->with('no-configuration')->andReturn(false) + ->shouldReceive('getOption')->with('extensions')->andReturn('') + ->mock() + ); + $this->assertTrue(in_array('php', $confDefaultFileMissing->getExtensions())); + + chdir($cwd); + } +} diff --git a/tests/Conf/JsonConfTest.php b/tests/Conf/JsonConfTest.php new file mode 100644 index 0000000..c61c35f --- /dev/null +++ b/tests/Conf/JsonConfTest.php @@ -0,0 +1,49 @@ +setExpectedException('RuntimeException'); + new JsonConf('this-is-not-a-valid-json-string'); + } + + public function testExceptionSchemaViolation() + { + $this->setExpectedException('RuntimeException'); + new JsonConf('{"undefined": "this property is not definied in the schema"}'); + } + + public function configurationProvider() + { + return [ + ['{}', 'getFormat', ''], + ['{}', 'getPaths', []], + ['{}', 'getIgnorePaths', []], + ['{}', 'getExtensions', []], + ['{}', 'getRuleWhitelist', []], + ['{}', 'getRuleBlacklist', []], + ['{}', 'disableAnnotations', false], + ['{"format":"dots"}', 'getFormat', 'dots'], + ['{"paths":["path"]}', 'getPaths', ['path']], + ['{"ignore-paths":["path"]}', 'getIgnorePaths', ['path']], + ['{"extensions":["php"]}', 'getExtensions', ['php']], + ['{"whitelist-rules":["rule"]}', 'getRuleWhitelist', ['rule']], + ['{"blacklist-rules":["rule"]}', 'getRuleBlacklist', ['rule']], + ['{"disable-annotations": true}', 'disableAnnotations', true], + ]; + } + + /** + * @dataProvider configurationProvider + */ + public function testConfiguration($json, $method, $expected) + { + $this->assertSame( + $expected, + (new JsonConf($json))->$method() + ); + } +} From 1392c2a057aea947084e63b500b4d5f71de512fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Forsg=C3=A5rd?= Date: Thu, 29 Jan 2015 23:35:36 +0100 Subject: [PATCH 03/16] Removing prefer-lowest from travis build as there is an issue with coveralls --- .travis.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8f26c55..38e1ad9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,18 +6,9 @@ php: - 5.5 - 5.4 -env: - matrix: - - PREFER_LOWEST="--prefer-lowest --prefer-stable" - - PREFER_LOWEST="" - install: - composer install --dev --prefer-source -before_script: - - composer self-update - - composer update --prefer-source $PREFER_LOWEST - script: - bin/phpunit - bin/psecio-parse scan From 660336de89f43e6135330eae1e72915c232852a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Forsg=C3=A5rd?= Date: Fri, 30 Jan 2015 01:03:49 +0100 Subject: [PATCH 04/16] Some renaming in the subscriber namespace --- src/Command/ScanCommand.php | 22 +++++++++---------- .../{Subscriber.php => BaseSubscriber.php} | 2 +- .../{ConsoleDebug.php => Console/Debug.php} | 4 ++-- .../{ConsoleDots.php => Console/Dots.php} | 6 +++-- .../{ConsoleLines.php => Console/Lines.php} | 6 +++-- .../Progress.php} | 5 +++-- .../{ConsoleReport.php => Console/Report.php} | 6 +++-- src/Subscriber/ExitCodeCatcher.php | 2 +- src/Subscriber/Xml.php | 2 +- ...scriberTest.php => BaseSubscriberTest.php} | 6 ++--- .../DebugTest.php} | 6 ++--- .../DotsTest.php} | 6 ++--- .../Lines.php} | 6 ++--- .../ProgressTest.php} | 6 ++--- .../ReportTest.php} | 8 +++---- 15 files changed, 50 insertions(+), 43 deletions(-) rename src/Subscriber/{Subscriber.php => BaseSubscriber.php} (96%) rename src/Subscriber/{ConsoleDebug.php => Console/Debug.php} (93%) rename src/Subscriber/{ConsoleDots.php => Console/Dots.php} (91%) rename src/Subscriber/{ConsoleLines.php => Console/Lines.php} (87%) rename src/Subscriber/{ConsoleProgressBar.php => Console/Progress.php} (92%) rename src/Subscriber/{ConsoleReport.php => Console/Report.php} (96%) rename tests/Subscriber/{SubscriberTest.php => BaseSubscriberTest.php} (80%) rename tests/Subscriber/{ConsoleDebugTest.php => Console/DebugTest.php} (87%) rename tests/Subscriber/{ConsoleDotsTest.php => Console/DotsTest.php} (91%) rename tests/Subscriber/{ConsoleLinesTest.php => Console/Lines.php} (93%) rename tests/Subscriber/{ConsoleProgressBarTest.php => Console/ProgressTest.php} (81%) rename tests/Subscriber/{ConsoleReportTest.php => Console/ReportTest.php} (93%) diff --git a/src/Command/ScanCommand.php b/src/Command/ScanCommand.php index 34b41e7..28914e4 100644 --- a/src/Command/ScanCommand.php +++ b/src/Command/ScanCommand.php @@ -16,11 +16,11 @@ use Psecio\Parse\File; use Psecio\Parse\Conf\ConfFactory; use Psecio\Parse\Subscriber\ExitCodeCatcher; -use Psecio\Parse\Subscriber\ConsoleDots; -use Psecio\Parse\Subscriber\ConsoleProgressBar; -use Psecio\Parse\Subscriber\ConsoleLines; -use Psecio\Parse\Subscriber\ConsoleDebug; -use Psecio\Parse\Subscriber\ConsoleReport; +use Psecio\Parse\Subscriber\Console\Dots; +use Psecio\Parse\Subscriber\Console\Progress; +use Psecio\Parse\Subscriber\Console\Lines; +use Psecio\Parse\Subscriber\Console\Debug; +use Psecio\Parse\Subscriber\Console\Report; use Psecio\Parse\Subscriber\Xml; use Psecio\Parse\Event\Events; use Psecio\Parse\Event\MessageEvent; @@ -48,7 +48,7 @@ protected function configure() 'format', 'f', InputOption::VALUE_REQUIRED, - 'Output format (progress, dots or xml)' + 'Output format (progress, dots, lines, debug or xml)' ) ->addOption( 'ignore-paths', @@ -123,22 +123,22 @@ protected function execute(InputInterface $input, OutputInterface $output) $output->writeln("Parse: A PHP Security Scanner\n"); if ($output->isVeryVerbose()) { $dispatcher->addSubscriber( - new ConsoleDebug($output) + new Debug($output) ); } elseif ($output->isVerbose()) { $dispatcher->addSubscriber( - new ConsoleLines($output) + new Lines($output) ); } elseif ('progress' == $conf->getFormat() && $output->isDecorated()) { $dispatcher->addSubscriber( - new ConsoleProgressBar(new ProgressBar($output, count($files))) + new Progress(new ProgressBar($output, count($files))) ); } else { $dispatcher->addSubscriber( - new ConsoleDots($output) + new Dots($output) ); } - $dispatcher->addSubscriber(new ConsoleReport($output)); + $dispatcher->addSubscriber(new Report($output)); break; case 'xml': $dispatcher->addSubscriber(new Xml($output)); diff --git a/src/Subscriber/Subscriber.php b/src/Subscriber/BaseSubscriber.php similarity index 96% rename from src/Subscriber/Subscriber.php rename to src/Subscriber/BaseSubscriber.php index 793f772..b80d2d6 100644 --- a/src/Subscriber/Subscriber.php +++ b/src/Subscriber/BaseSubscriber.php @@ -14,7 +14,7 @@ * * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ -class Subscriber implements EventSubscriberInterface, Events +abstract class BaseSubscriber implements EventSubscriberInterface, Events { /** * Returns an array of event names this subscriber wants to listen to diff --git a/src/Subscriber/ConsoleDebug.php b/src/Subscriber/Console/Debug.php similarity index 93% rename from src/Subscriber/ConsoleDebug.php rename to src/Subscriber/Console/Debug.php index 7949d6e..1919306 100644 --- a/src/Subscriber/ConsoleDebug.php +++ b/src/Subscriber/Console/Debug.php @@ -1,13 +1,13 @@ assertInternalType( 'array', - Subscriber::getSubscribedEvents() + BaseSubscriber::getSubscribedEvents() ); } public function testEmptyMethods() { - $subscriber = new Subscriber; + $subscriber = m::mock('\Psecio\Parse\Subscriber\BaseSubscriber[]'); $this->assertNull($subscriber->onScanStart()); $this->assertNull($subscriber->onScanComplete()); $this->assertNull($subscriber->onFileOpen(m::mock('\Psecio\Parse\Event\FileEvent'))); diff --git a/tests/Subscriber/ConsoleDebugTest.php b/tests/Subscriber/Console/DebugTest.php similarity index 87% rename from tests/Subscriber/ConsoleDebugTest.php rename to tests/Subscriber/Console/DebugTest.php index 104168c..a62a81c 100644 --- a/tests/Subscriber/ConsoleDebugTest.php +++ b/tests/Subscriber/Console/DebugTest.php @@ -1,10 +1,10 @@ shouldReceive('getMessage')->andReturn('debug message'); - $console = new ConsoleDebug($output); + $console = new Debug($output); // Should write debug start $console->onScanStart(); diff --git a/tests/Subscriber/ConsoleDotsTest.php b/tests/Subscriber/Console/DotsTest.php similarity index 91% rename from tests/Subscriber/ConsoleDotsTest.php rename to tests/Subscriber/Console/DotsTest.php index 298d973..bc1b0ef 100644 --- a/tests/Subscriber/ConsoleDotsTest.php +++ b/tests/Subscriber/Console/DotsTest.php @@ -1,10 +1,10 @@ shouldReceive('write')->ordered()->once()->with("\n"); $output->shouldReceive('write')->ordered()->once()->with("I"); - $console = new ConsoleDots($output); + $console = new Dots($output); $console->setLineLength(2); $console->onScanStart(); diff --git a/tests/Subscriber/ConsoleLinesTest.php b/tests/Subscriber/Console/Lines.php similarity index 93% rename from tests/Subscriber/ConsoleLinesTest.php rename to tests/Subscriber/Console/Lines.php index 14e8243..db3ea82 100644 --- a/tests/Subscriber/ConsoleLinesTest.php +++ b/tests/Subscriber/Console/Lines.php @@ -1,10 +1,10 @@ shouldReceive('getRule->getName')->andReturn('Rule'); $issueEvent->shouldReceive('getFile->getPath')->andReturn('path'); - $console = new ConsoleLines($output); + $console = new Lines($output); $console->onScanStart(); diff --git a/tests/Subscriber/ConsoleProgressBarTest.php b/tests/Subscriber/Console/ProgressTest.php similarity index 81% rename from tests/Subscriber/ConsoleProgressBarTest.php rename to tests/Subscriber/Console/ProgressTest.php index 7b05714..91e98a6 100644 --- a/tests/Subscriber/ConsoleProgressBarTest.php +++ b/tests/Subscriber/Console/ProgressTest.php @@ -1,10 +1,10 @@ shouldReceive('advance')->ordered()->once(); $bar->shouldReceive('finish')->ordered()->once(); - $console = new ConsoleProgressBar($bar); + $console = new Progress($bar); $console->onScanStart(); $console->onFileClose(); $console->onScanComplete(); diff --git a/tests/Subscriber/ConsoleReportTest.php b/tests/Subscriber/Console/ReportTest.php similarity index 93% rename from tests/Subscriber/ConsoleReportTest.php rename to tests/Subscriber/Console/ReportTest.php index 62362ec..b5eb347 100644 --- a/tests/Subscriber/ConsoleReportTest.php +++ b/tests/Subscriber/Console/ReportTest.php @@ -1,14 +1,14 @@ shouldReceive('writeln') ->once() @@ -41,7 +41,7 @@ public function testFailureReport() FAILURES! Scanned: 0, Errors: 1, Issues: 1."; - $report = new ConsoleReport( + $report = new Report( m::mock('\Symfony\Component\Console\Output\OutputInterface') ->shouldReceive('writeln') ->once() From 8adc2f7843f57c3fdbaeed75f639fa955791c82b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Forsg=C3=A5rd?= Date: Fri, 30 Jan 2015 22:57:05 +0100 Subject: [PATCH 05/16] Implement SubscriberFactory --- src/Command/ScanCommand.php | 62 ++----- src/Conf/schema.json | 9 +- src/Event/Events.php | 3 + src/Scanner.php | 2 +- src/Subscriber/BaseSubscriber.php | 3 +- src/Subscriber/Console/Debug.php | 5 +- src/Subscriber/Console/Dots.php | 10 +- src/Subscriber/Console/Header.php | 26 +++ src/Subscriber/Console/Lines.php | 6 +- src/Subscriber/Console/Progress.php | 18 ++- src/Subscriber/Console/Report.php | 4 +- src/Subscriber/OutputTrait.php | 10 ++ src/Subscriber/SubscriberFactory.php | 152 ++++++++++++++++++ src/Subscriber/Xml.php | 4 +- tests/ScannerTest.php | 29 ++-- tests/Subscriber/BaseSubscriberTest.php | 2 +- tests/Subscriber/Console/DebugTest.php | 3 +- tests/Subscriber/Console/DotsTest.php | 3 +- .../Console/{Lines.php => LinesTest.php} | 3 +- tests/Subscriber/Console/ProgressTest.php | 9 +- tests/Subscriber/Console/ReportTest.php | 4 +- tests/Subscriber/XmlTest.php | 2 +- 22 files changed, 264 insertions(+), 105 deletions(-) create mode 100644 src/Subscriber/Console/Header.php create mode 100644 src/Subscriber/SubscriberFactory.php rename tests/Subscriber/Console/{Lines.php => LinesTest.php} (93%) diff --git a/src/Command/ScanCommand.php b/src/Command/ScanCommand.php index 28914e4..e4c720e 100644 --- a/src/Command/ScanCommand.php +++ b/src/Command/ScanCommand.php @@ -4,28 +4,19 @@ use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputArgument; -use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Helper\ProgressBar; use Symfony\Component\EventDispatcher\EventDispatcher; -use Psecio\Parse\RuleFactory; -use Psecio\Parse\FileIterator; -use Psecio\Parse\CallbackVisitor; -use Psecio\Parse\Scanner; -use Psecio\Parse\File; use Psecio\Parse\Conf\ConfFactory; +use Psecio\Parse\Subscriber\SubscriberFactory; use Psecio\Parse\Subscriber\ExitCodeCatcher; -use Psecio\Parse\Subscriber\Console\Dots; -use Psecio\Parse\Subscriber\Console\Progress; -use Psecio\Parse\Subscriber\Console\Lines; -use Psecio\Parse\Subscriber\Console\Debug; -use Psecio\Parse\Subscriber\Console\Report; -use Psecio\Parse\Subscriber\Xml; use Psecio\Parse\Event\Events; use Psecio\Parse\Event\MessageEvent; -use RuntimeException; -use SplFileInfo; +use Psecio\Parse\RuleFactory; +use Psecio\Parse\CallbackVisitor; +use Psecio\Parse\Scanner; +use Psecio\Parse\FileIterator; /** * The main command, scan paths for possible security issues @@ -107,54 +98,23 @@ protected function configure() protected function execute(InputInterface $input, OutputInterface $output) { $conf = (new ConfFactory)->createConf($input, $confFileName); - - $rules = (new RuleFactory($conf->getRuleWhitelist(), $conf->getRuleBlacklist()))->createRuleCollection(); - - $files = new FileIterator($conf->getPaths(), $conf->getIgnorePaths(), $conf->getExtensions()); - $dispatcher = new EventDispatcher; + (new SubscriberFactory($conf->getFormat(), $output))->addSubscribersTo($dispatcher); + $exitCode = new ExitCodeCatcher; $dispatcher->addSubscriber($exitCode); - switch ($conf->getFormat()) { - case 'dots': - case 'progress': - $output->writeln("Parse: A PHP Security Scanner\n"); - if ($output->isVeryVerbose()) { - $dispatcher->addSubscriber( - new Debug($output) - ); - } elseif ($output->isVerbose()) { - $dispatcher->addSubscriber( - new Lines($output) - ); - } elseif ('progress' == $conf->getFormat() && $output->isDecorated()) { - $dispatcher->addSubscriber( - new Progress(new ProgressBar($output, count($files))) - ); - } else { - $dispatcher->addSubscriber( - new Dots($output) - ); - } - $dispatcher->addSubscriber(new Report($output)); - break; - case 'xml': - $dispatcher->addSubscriber(new Xml($output)); - break; - default: - throw new RuntimeException("Unknown output format '{$conf->getFormat()}'"); - } - if ($confFileName) { $dispatcher->dispatch(Events::DEBUG, new MessageEvent("Reading configurations from $confFileName")); } + $rules = (new RuleFactory($conf->getRuleWhitelist(), $conf->getRuleBlacklist()))->createRuleCollection(); + $dispatcher->dispatch(Events::DEBUG, new MessageEvent("Using ruleset: $rules")); $scanner = new Scanner($dispatcher, new CallbackVisitor($rules)); - $scanner->scan($files); + $scanner->scan(new FileIterator($conf->getPaths(), $conf->getIgnorePaths(), $conf->getExtensions())); return $exitCode->getExitCode(); } diff --git a/src/Conf/schema.json b/src/Conf/schema.json index f5c6664..e7279cc 100644 --- a/src/Conf/schema.json +++ b/src/Conf/schema.json @@ -50,14 +50,7 @@ }, "format": { "description": "Output format", - "type": "string", - "enum": [ - "progress", - "dots", - "lines", - "debug", - "xml" - ] + "type": "string" } }, "additionalProperties": false diff --git a/src/Event/Events.php b/src/Event/Events.php index 406a5f7..1d53625 100644 --- a/src/Event/Events.php +++ b/src/Event/Events.php @@ -9,6 +9,9 @@ interface Events { /** * A scan.start event is fired when the scan starts + * + * The event listener receives an \Psecio\Parse\Event\MessageEvent instance + * with the number of files to scan as message */ const SCAN_START = 'scan.start'; diff --git a/src/Scanner.php b/src/Scanner.php index 1903e54..2b3f960 100644 --- a/src/Scanner.php +++ b/src/Scanner.php @@ -76,7 +76,7 @@ public function onNodeFailure(RuleInterface $rule, Node $node, File $file) */ public function scan(FileIterator $fileIterator) { - $this->dispatcher->dispatch(self::SCAN_START); + $this->dispatcher->dispatch(self::SCAN_START, new Event\MessageEvent(count($fileIterator))); foreach ($fileIterator as $file) { $this->dispatcher->dispatch(self::FILE_OPEN, new Event\FileEvent($file)); diff --git a/src/Subscriber/BaseSubscriber.php b/src/Subscriber/BaseSubscriber.php index b80d2d6..0c211ea 100644 --- a/src/Subscriber/BaseSubscriber.php +++ b/src/Subscriber/BaseSubscriber.php @@ -37,9 +37,10 @@ public static function getSubscribedEvents() /** * Empty on scan start method * + * @param MessageEvent $event * @return void */ - public function onScanStart() + public function onScanStart(MessageEvent $event) { } diff --git a/src/Subscriber/Console/Debug.php b/src/Subscriber/Console/Debug.php index 1919306..cc00dbb 100644 --- a/src/Subscriber/Console/Debug.php +++ b/src/Subscriber/Console/Debug.php @@ -17,11 +17,12 @@ class Debug extends Lines /** * Save timestamp at scan start * + * @param MessageEvent $event * @return void */ - public function onScanStart() + public function onScanStart(MessageEvent $event) { - parent::onScanStart(); + parent::onScanStart($event); $this->write("[DEBUG] Starting scan\n"); $this->startTime = microtime(true); } diff --git a/src/Subscriber/Console/Dots.php b/src/Subscriber/Console/Dots.php index 652c513..031cfb4 100644 --- a/src/Subscriber/Console/Dots.php +++ b/src/Subscriber/Console/Dots.php @@ -2,19 +2,16 @@ namespace Psecio\Parse\Subscriber\Console; -use Psecio\Parse\Subscriber\BaseSubscriber; -use Psecio\Parse\Subscriber\OutputTrait; use Psecio\Parse\Event\FileEvent; use Psecio\Parse\Event\IssueEvent; use Psecio\Parse\Event\ErrorEvent; +use Psecio\Parse\Event\MessageEvent; /** * Display phpunit style dots to visualize scan progression */ -class Dots extends BaseSubscriber +class Dots extends Header { - use OutputTrait; - /** * @var string One charactes status descriptor */ @@ -44,9 +41,10 @@ public function setLineLength($lineLength) /** * Write header on scan start * + * @param MessageEvent $event * @return void */ - public function onScanStart() + public function onScanStart(MessageEvent $event) { $this->fileCount = 0; } diff --git a/src/Subscriber/Console/Header.php b/src/Subscriber/Console/Header.php new file mode 100644 index 0000000..4b60b0f --- /dev/null +++ b/src/Subscriber/Console/Header.php @@ -0,0 +1,26 @@ +writeln("Parse: A PHP Security Scanner\n"); + $this->setOutput($output); + } +} diff --git a/src/Subscriber/Console/Lines.php b/src/Subscriber/Console/Lines.php index 08e4880..1005d1d 100644 --- a/src/Subscriber/Console/Lines.php +++ b/src/Subscriber/Console/Lines.php @@ -2,8 +2,6 @@ namespace Psecio\Parse\Subscriber\Console; -use Psecio\Parse\Subscriber\BaseSubscriber; -use Psecio\Parse\Subscriber\OutputTrait; use Psecio\Parse\Event\FileEvent; use Psecio\Parse\Event\IssueEvent; use Psecio\Parse\Event\ErrorEvent; @@ -11,10 +9,8 @@ /** * Display descriptive lines to visualize scan progression */ -class Lines extends BaseSubscriber +class Lines extends Header { - use OutputTrait; - /** * Write path on file open * diff --git a/src/Subscriber/Console/Progress.php b/src/Subscriber/Console/Progress.php index 5d0e8e4..b29b37a 100644 --- a/src/Subscriber/Console/Progress.php +++ b/src/Subscriber/Console/Progress.php @@ -2,13 +2,14 @@ namespace Psecio\Parse\Subscriber\Console; -use Psecio\Parse\Subscriber\BaseSubscriber; +use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Helper\ProgressBar; +use Psecio\Parse\Event\MessageEvent; /** * Display a progress bar to visualize scan progression */ -class Progress extends BaseSubscriber +class Progress extends Header { /** * The progress bar format used of the number if steps is known @@ -28,11 +29,13 @@ class Progress extends BaseSubscriber /** * Inject progress bar * - * @param ProgressBar $progressBar + * @param OutputInterface $output + * @param ProgressBar|null $progressBar */ - public function __construct(ProgressBar $progressBar) + public function __construct(OutputInterface $output, ProgressBar $progressBar = null) { - $this->progressBar = $progressBar; + parent::__construct($output); + $this->progressBar = $progressBar ?: new ProgressBar($output); $this->progressBar->setFormat( $this->progressBar->getMaxSteps() ? self::FORMAT_STEPS_KNOWN : self::FORMAT_STEPS_UNKNOWN ); @@ -41,11 +44,12 @@ public function __construct(ProgressBar $progressBar) /** * Reset progress bar on scan start * + * @param MessageEvent $event * @return void */ - public function onScanStart() + public function onScanStart(MessageEvent $event) { - $this->progressBar->start(); + $this->progressBar->start((int)$event->getMessage()); } /** diff --git a/src/Subscriber/Console/Report.php b/src/Subscriber/Console/Report.php index 610b37a..4c43856 100644 --- a/src/Subscriber/Console/Report.php +++ b/src/Subscriber/Console/Report.php @@ -7,6 +7,7 @@ use Psecio\Parse\Event\FileEvent; use Psecio\Parse\Event\IssueEvent; use Psecio\Parse\Event\ErrorEvent; +use Psecio\Parse\Event\MessageEvent; /** * Print report at scan complete @@ -33,9 +34,10 @@ class Report extends BaseSubscriber /** * Reset values on scan start * + * @param MessageEvent $event * @return void */ - public function onScanStart() + public function onScanStart(MessageEvent $event) { $this->fileCount = 0; $this->issues = []; diff --git a/src/Subscriber/OutputTrait.php b/src/Subscriber/OutputTrait.php index 30fcfa0..4c23fa7 100644 --- a/src/Subscriber/OutputTrait.php +++ b/src/Subscriber/OutputTrait.php @@ -20,6 +20,16 @@ trait OutputTrait * @param OutputInterface $output */ public function __construct(OutputInterface $output) + { + $this->setOutput($output); + } + + /** + * Register output interface + * + * @param OutputInterface $output + */ + public function setOutput(OutputInterface $output) { $this->output = $output; } diff --git a/src/Subscriber/SubscriberFactory.php b/src/Subscriber/SubscriberFactory.php new file mode 100644 index 0000000..5779b7a --- /dev/null +++ b/src/Subscriber/SubscriberFactory.php @@ -0,0 +1,152 @@ + [ + self::VERBOSITY_VERBOSE => self::FORMAT_LINES, + self::VERBOSITY_DEBUG => self::FORMAT_DEBUG + ], + self::FORMAT_DOTS => [ + self::VERBOSITY_VERBOSE => self::FORMAT_LINES, + self::VERBOSITY_DEBUG => self::FORMAT_DEBUG + ], + self::FORMAT_LINES => [ + self::VERBOSITY_VERBOSE => self::FORMAT_LINES, + self::VERBOSITY_DEBUG => self::FORMAT_DEBUG + ], + self::FORMAT_DEBUG => [ + self::VERBOSITY_VERBOSE => self::FORMAT_DEBUG, + self::VERBOSITY_DEBUG => self::FORMAT_DEBUG + ], + self::FORMAT_XML => [ + self::VERBOSITY_VERBOSE => self::FORMAT_XML, + self::VERBOSITY_DEBUG => self::FORMAT_XML + ] + ]; + + /** + * @var string Format identifier + */ + private $format; + + /** + * @var OutputInterface Output object + */ + private $output; + + /** + * Set requested format and output object + * + * @param string $format Requested format + * @param OutputInterface $output Output object + * @throws RuntimeException If format is not valid + */ + public function __construct($format, OutputInterface $output) + { + if (!isset($this->formatTransitions[$format])) { + throw new RuntimeException("Unknown output format '{$format}'"); + } + + if ($output->isVeryVerbose()) { + $format = $this->formatTransitions[$format][self::VERBOSITY_DEBUG]; + } elseif ($output->isVerbose()) { + $format = $this->formatTransitions[$format][self::VERBOSITY_VERBOSE]; + } + + if (self::FORMAT_PROGRESS == $format && !$output->isDecorated()) { + $format = self::FORMAT_DOTS; + } + + $this->format = $format; + $this->output = $output; + } + + /** + * Get format identifier + * + * @return string + */ + public function getFormat() + { + return $this->format; + } + + /** + * Add subscribers to event dispatcher + * + * @param EventDispatcherInterface $dispatcher + * @return void + */ + public function addSubscribersTo(EventDispatcherInterface $dispatcher) + { + switch ($this->getFormat()) { + case self::FORMAT_PROGRESS: + $dispatcher->addSubscriber(new Console\Progress($this->output)); + $dispatcher->addSubscriber(new Console\Report($this->output)); + return; + case self::FORMAT_DOTS: + $dispatcher->addSubscriber(new Console\Dots($this->output)); + $dispatcher->addSubscriber(new Console\Report($this->output)); + return; + case self::FORMAT_LINES: + $dispatcher->addSubscriber(new Console\Lines($this->output)); + $dispatcher->addSubscriber(new Console\Report($this->output)); + return; + case self::FORMAT_DEBUG: + $dispatcher->addSubscriber(new Console\Debug($this->output)); + $dispatcher->addSubscriber(new Console\Report($this->output)); + return; + case self::FORMAT_XML: + $dispatcher->addSubscriber(new Xml($this->output)); + return; + } + } +} diff --git a/src/Subscriber/Xml.php b/src/Subscriber/Xml.php index d345729..8b719ce 100644 --- a/src/Subscriber/Xml.php +++ b/src/Subscriber/Xml.php @@ -6,6 +6,7 @@ use XMLWriter; use Psecio\Parse\Event\IssueEvent; use Psecio\Parse\Event\ErrorEvent; +use Psecio\Parse\Event\MessageEvent; /** * Xml generating event subscriber @@ -22,9 +23,10 @@ class Xml extends BaseSubscriber /** * Create document at scan start * + * @param MessageEvent $event * @return void */ - public function onScanStart() + public function onScanStart(MessageEvent $event) { $this->xmlWriter = new XMLWriter; $this->xmlWriter->openMemory(); diff --git a/tests/ScannerTest.php b/tests/ScannerTest.php index 048b342..dd87392 100644 --- a/tests/ScannerTest.php +++ b/tests/ScannerTest.php @@ -41,12 +41,12 @@ public function testErrorOnPhpsFile() m::mock('\PhpParser\NodeTraverser')->shouldReceive('traverse', 'addVisitor')->mock() ); - $scanner->scan( - m::mock('\Psecio\Parse\FileIterator') - ->shouldReceive('getIterator') - ->andReturn(new \ArrayIterator([$file])) - ->mock() - ); + $fileIterator = m::mock('\Psecio\Parse\FileIterator') + ->shouldReceive('getIterator')->andReturn(new \ArrayIterator([$file])) + ->shouldReceive('count')->andReturn(1) + ->mock(); + + $scanner->scan($fileIterator); } public function testErrorOnParseException() @@ -64,18 +64,21 @@ public function testErrorOnParseException() m::mock('\PhpParser\NodeTraverser')->shouldReceive('addVisitor')->mock() ); - $scanner->scan( - m::mock('\Psecio\Parse\FileIterator') - ->shouldReceive('getIterator') - ->andReturn(new \ArrayIterator([$file])) - ->mock() - ); + $fileIterator = m::mock('\Psecio\Parse\FileIterator') + ->shouldReceive('getIterator')->andReturn(new \ArrayIterator([$file])) + ->shouldReceive('count')->andReturn(1) + ->mock(); + + $scanner->scan($fileIterator); } private function createErrorDispatcherMock() { $dispatcher = m::mock('\Symfony\Component\EventDispatcher\EventDispatcherInterface'); - $dispatcher->shouldReceive('dispatch')->ordered()->once()->with(Scanner::SCAN_START); + $dispatcher->shouldReceive('dispatch')->ordered()->once()->with( + Scanner::SCAN_START, + m::type('\Psecio\Parse\Event\MessageEvent') + ); $dispatcher->shouldReceive('dispatch')->ordered()->once()->with( Scanner::FILE_OPEN, m::type('\Psecio\Parse\Event\FileEvent') diff --git a/tests/Subscriber/BaseSubscriberTest.php b/tests/Subscriber/BaseSubscriberTest.php index b69a7dc..15ec30d 100644 --- a/tests/Subscriber/BaseSubscriberTest.php +++ b/tests/Subscriber/BaseSubscriberTest.php @@ -17,7 +17,7 @@ public function testSubscription() public function testEmptyMethods() { $subscriber = m::mock('\Psecio\Parse\Subscriber\BaseSubscriber[]'); - $this->assertNull($subscriber->onScanStart()); + $this->assertNull($subscriber->onScanStart(m::mock('\Psecio\Parse\Event\MessageEvent'))); $this->assertNull($subscriber->onScanComplete()); $this->assertNull($subscriber->onFileOpen(m::mock('\Psecio\Parse\Event\FileEvent'))); $this->assertNull($subscriber->onFileClose()); diff --git a/tests/Subscriber/Console/DebugTest.php b/tests/Subscriber/Console/DebugTest.php index a62a81c..014b4de 100644 --- a/tests/Subscriber/Console/DebugTest.php +++ b/tests/Subscriber/Console/DebugTest.php @@ -11,6 +11,7 @@ public function testOutput() $output = m::mock('\Symfony\Component\Console\Output\OutputInterface'); // The order of the calls to write should match the order of the events fired on $console + $output->shouldReceive('writeln')->once()->with("/Parse/"); $output->shouldReceive('write')->ordered()->once()->with("[DEBUG] Starting scan\n"); $output->shouldReceive('write')->ordered()->once()->with("[DEBUG] debug message\n"); $output->shouldReceive('write')->ordered()->once()->with("/\[DEBUG\] Scan completed in \d+\.\d+ seconds/"); @@ -22,7 +23,7 @@ public function testOutput() $console = new Debug($output); // Should write debug start - $console->onScanStart(); + $console->onScanStart(m::mock('\Psecio\Parse\Event\MessageEvent')); // Writes debug message $console->onDebug($messageEvent); diff --git a/tests/Subscriber/Console/DotsTest.php b/tests/Subscriber/Console/DotsTest.php index bc1b0ef..58d412e 100644 --- a/tests/Subscriber/Console/DotsTest.php +++ b/tests/Subscriber/Console/DotsTest.php @@ -11,6 +11,7 @@ public function testOutput() $output = m::mock('\Symfony\Component\Console\Output\OutputInterface'); // The order of the calls to write should match the order of the events fired on $console + $output->shouldReceive('writeln')->once()->with("/Parse/"); $output->shouldReceive('write')->ordered()->once()->with("."); $output->shouldReceive('write')->ordered()->once()->with("E"); $output->shouldReceive('write')->ordered()->once()->with("\n"); @@ -19,7 +20,7 @@ public function testOutput() $console = new Dots($output); $console->setLineLength(2); - $console->onScanStart(); + $console->onScanStart(m::mock('\Psecio\Parse\Event\MessageEvent')); // Writes a dot as a file is scanned $console->onFileOpen(m::mock('\Psecio\Parse\Event\FileEvent')); diff --git a/tests/Subscriber/Console/Lines.php b/tests/Subscriber/Console/LinesTest.php similarity index 93% rename from tests/Subscriber/Console/Lines.php rename to tests/Subscriber/Console/LinesTest.php index db3ea82..bfb98f7 100644 --- a/tests/Subscriber/Console/Lines.php +++ b/tests/Subscriber/Console/LinesTest.php @@ -11,6 +11,7 @@ public function testOutput() $output = m::mock('\Symfony\Component\Console\Output\OutputInterface'); // The order of the calls to write should match the order of the events fired on $console + $output->shouldReceive('writeln')->once()->with("/Parse/"); $output->shouldReceive('write')->ordered()->once()->with("[PARSE] /path/to/file\n"); $output->shouldReceive('write')->ordered()->once()->with("[PARSE] /path/to/file\n"); $output->shouldReceive('write')->ordered()->once()->with("[ERROR] message in /path/to/file\n"); @@ -34,7 +35,7 @@ public function testOutput() $console = new Lines($output); - $console->onScanStart(); + $console->onScanStart(m::mock('\Psecio\Parse\Event\MessageEvent')); // File open writes [PARSE] line $console->onFileOpen($fileEvent); diff --git a/tests/Subscriber/Console/ProgressTest.php b/tests/Subscriber/Console/ProgressTest.php index 91e98a6..de9b85b 100644 --- a/tests/Subscriber/Console/ProgressTest.php +++ b/tests/Subscriber/Console/ProgressTest.php @@ -17,8 +17,13 @@ public function testOutput() $bar->shouldReceive('advance')->ordered()->once(); $bar->shouldReceive('finish')->ordered()->once(); - $console = new Progress($bar); - $console->onScanStart(); + $output = m::mock('\Symfony\Component\Console\Output\OutputInterface'); + $output->shouldReceive('writeln')->once()->with("/Parse/"); + + $console = new Progress($output, $bar); + $console->onScanStart( + m::mock('\Psecio\Parse\Event\MessageEvent')->shouldReceive('getMessage')->mock() + ); $console->onFileClose(); $console->onScanComplete(); } diff --git a/tests/Subscriber/Console/ReportTest.php b/tests/Subscriber/Console/ReportTest.php index b5eb347..4c58cc4 100644 --- a/tests/Subscriber/Console/ReportTest.php +++ b/tests/Subscriber/Console/ReportTest.php @@ -16,7 +16,7 @@ public function testPassReport() ->mock() ); - $report->onScanStart(); + $report->onScanStart(m::mock('\Psecio\Parse\Event\MessageEvent')); $report->onFileOpen(m::mock('\Psecio\Parse\Event\FileEvent')); $report->onFileOpen(m::mock('\Psecio\Parse\Event\FileEvent')); $report->onScanComplete(); @@ -49,7 +49,7 @@ public function testFailureReport() ->mock() ); - $report->onScanStart(); + $report->onScanStart(m::mock('\Psecio\Parse\Event\MessageEvent')); $errorEvent = m::mock('\Psecio\Parse\Event\ErrorEvent'); $errorEvent->shouldReceive('getMessage')->once()->andReturn('error description'); diff --git a/tests/Subscriber/XmlTest.php b/tests/Subscriber/XmlTest.php index e76bbb3..53d8f01 100644 --- a/tests/Subscriber/XmlTest.php +++ b/tests/Subscriber/XmlTest.php @@ -33,7 +33,7 @@ public function testGenerateXml() $xml = new Xml($output); - $xml->onScanStart(); + $xml->onScanStart(m::mock('\Psecio\Parse\Event\MessageEvent')); $errorEvent = m::mock('\Psecio\Parse\Event\ErrorEvent'); $errorEvent->shouldReceive('getMessage')->once()->andReturn('error description'); From 847b143785da5b83048bcac335ae2c85f153d82f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Forsg=C3=A5rd?= Date: Sat, 31 Jan 2015 14:38:38 +0100 Subject: [PATCH 06/16] Add tests for SubscriberFactory --- src/Subscriber/SubscriberFactory.php | 47 +++++++----- tests/Subscriber/SubscriberFactoryTest.php | 85 ++++++++++++++++++++++ 2 files changed, 112 insertions(+), 20 deletions(-) create mode 100644 tests/Subscriber/SubscriberFactoryTest.php diff --git a/src/Subscriber/SubscriberFactory.php b/src/Subscriber/SubscriberFactory.php index 5779b7a..1afa86c 100644 --- a/src/Subscriber/SubscriberFactory.php +++ b/src/Subscriber/SubscriberFactory.php @@ -72,6 +72,31 @@ class SubscriberFactory ] ]; + /** + * @var array Maps subscribers to format identifiers + */ + private $subscriberMap = [ + self::FORMAT_PROGRESS => [ + '\Psecio\Parse\Subscriber\Console\Progress', + '\Psecio\Parse\Subscriber\Console\Report' + ], + self::FORMAT_DOTS => [ + '\Psecio\Parse\Subscriber\Console\Dots', + '\Psecio\Parse\Subscriber\Console\Report' + ], + self::FORMAT_LINES => [ + '\Psecio\Parse\Subscriber\Console\Lines', + '\Psecio\Parse\Subscriber\Console\Report' + ], + self::FORMAT_DEBUG => [ + '\Psecio\Parse\Subscriber\Console\Debug', + '\Psecio\Parse\Subscriber\Console\Report' + ], + self::FORMAT_XML => [ + '\Psecio\Parse\Subscriber\Xml' + ] + ]; + /** * @var string Format identifier */ @@ -127,26 +152,8 @@ public function getFormat() */ public function addSubscribersTo(EventDispatcherInterface $dispatcher) { - switch ($this->getFormat()) { - case self::FORMAT_PROGRESS: - $dispatcher->addSubscriber(new Console\Progress($this->output)); - $dispatcher->addSubscriber(new Console\Report($this->output)); - return; - case self::FORMAT_DOTS: - $dispatcher->addSubscriber(new Console\Dots($this->output)); - $dispatcher->addSubscriber(new Console\Report($this->output)); - return; - case self::FORMAT_LINES: - $dispatcher->addSubscriber(new Console\Lines($this->output)); - $dispatcher->addSubscriber(new Console\Report($this->output)); - return; - case self::FORMAT_DEBUG: - $dispatcher->addSubscriber(new Console\Debug($this->output)); - $dispatcher->addSubscriber(new Console\Report($this->output)); - return; - case self::FORMAT_XML: - $dispatcher->addSubscriber(new Xml($this->output)); - return; + foreach ($this->subscriberMap[$this->getFormat()] as $classname) { + $dispatcher->addSubscriber(new $classname($this->output)); } } } diff --git a/tests/Subscriber/SubscriberFactoryTest.php b/tests/Subscriber/SubscriberFactoryTest.php new file mode 100644 index 0000000..ef3272f --- /dev/null +++ b/tests/Subscriber/SubscriberFactoryTest.php @@ -0,0 +1,85 @@ +setExpectedException('RuntimeException'); + new SubscriberFactory( + 'invalid-format-identifier', + m::mock('Symfony\Component\Console\Output\OutputInterface') + ); + } + + public function testDebugTransition() + { + $factory = new SubscriberFactory( + SubscriberFactory::FORMAT_DOTS, + m::mock('Symfony\Component\Console\Output\OutputInterface') + ->shouldReceive('isVeryVerbose') + ->andReturn(true) + ->mock() + ); + $this->assertSame( + SubscriberFactory::FORMAT_DEBUG, + $factory->getFormat() + ); + } + + public function testVerboseTransition() + { + $factory = new SubscriberFactory( + SubscriberFactory::FORMAT_DOTS, + m::mock('Symfony\Component\Console\Output\OutputInterface') + ->shouldReceive('isVeryVerbose')->andReturn(false) + ->shouldReceive('isVerbose')->andReturn(true) + ->mock() + ); + $this->assertSame( + SubscriberFactory::FORMAT_LINES, + $factory->getFormat() + ); + } + + public function testNoAnsiTransition() + { + $factory = new SubscriberFactory( + SubscriberFactory::FORMAT_PROGRESS, + m::mock('Symfony\Component\Console\Output\OutputInterface') + ->shouldReceive('isVeryVerbose')->andReturn(false) + ->shouldReceive('isVerbose')->andReturn(false) + ->shouldReceive('isDecorated')->andReturn(false) + ->mock() + ); + $this->assertSame( + SubscriberFactory::FORMAT_DOTS, + $factory->getFormat() + ); + } + + public function testAddSubscribersToDispatcher() + { + $factory = new SubscriberFactory( + SubscriberFactory::FORMAT_XML, + m::mock('Symfony\Component\Console\Output\OutputInterface') + ->shouldReceive('isVeryVerbose')->andReturn(false) + ->shouldReceive('isVerbose')->andReturn(false) + ->shouldReceive('isDecorated')->andReturn(true) + ->mock() + ); + + $factory->addSubscribersTo( + m::mock('Symfony\Component\EventDispatcher\EventDispatcherInterface') + ->shouldReceive('addSubscriber') + ->once() + ->mock() + ); + } +} From 850244f5b5235dd2a1538d6c8baf34324173dc00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Forsg=C3=A5rd?= Date: Sat, 31 Jan 2015 14:56:41 +0100 Subject: [PATCH 07/16] Added documentation --- README.md | 35 ++++++++++++++++++++++ tests/Subscriber/SubscriberFactoryTest.php | 1 - 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0999f59..3866b4a 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,41 @@ You can also get a listing of the current checks being done with the `rules` com psecio-parse rules +### Using a configuraion file + +Specify the name of your configuration file with the `--configuration` option. If no +filename is given the default `.psecio-parse.json` is used. To ignore the default configuration +file use the `--no-configuration` option. + +#### Configuration file format +```json +{ + "paths": [ + "path/to/scan" + ], + "ignore-paths": [ + "path/to/ignore" + ], + "extensions": [ + "php", + "phps", + "phtml", + "php5" + ], + "whitelist-rules": [ + "rule-name" + ], + "blacklist-rules": [ + "rule-name" + ], + "disable-annotations": false, + "format": "dots|progress|lines|debug|xml" +} +``` + +See the configurations for scanning parse itself [here](.psecio-parse.json). See the +json schema used to validate configuration files [here](src/Conf/schema.json). + The Checks ---------- diff --git a/tests/Subscriber/SubscriberFactoryTest.php b/tests/Subscriber/SubscriberFactoryTest.php index ef3272f..84cbd66 100644 --- a/tests/Subscriber/SubscriberFactoryTest.php +++ b/tests/Subscriber/SubscriberFactoryTest.php @@ -74,7 +74,6 @@ public function testAddSubscribersToDispatcher() ->shouldReceive('isDecorated')->andReturn(true) ->mock() ); - $factory->addSubscribersTo( m::mock('Symfony\Component\EventDispatcher\EventDispatcherInterface') ->shouldReceive('addSubscriber') From 6386f780fb73f3cc1303f69a5773b8a4b4209b04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Forsg=C3=A5rd?= Date: Sat, 31 Jan 2015 15:00:40 +0100 Subject: [PATCH 08/16] Fixing some scrutinizr issues --- src/Conf/JsonConf.php | 8 ++++---- src/Subscriber/Console/Header.php | 2 -- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Conf/JsonConf.php b/src/Conf/JsonConf.php index 2df0adf..7c39e68 100644 --- a/src/Conf/JsonConf.php +++ b/src/Conf/JsonConf.php @@ -75,15 +75,15 @@ public function disableAnnotations() /** * Read configuration options * - * @param string $option Name of config to read - * @param mixed $default Returned if otion is not set + * @param string $property Name of property to read + * @param mixed $default Returned if otion is not set * @return mixed */ - private function read($property, $option) + private function read($property, $default) { if (property_exists($this->data, $property)) { return $this->data->$property; } - return $option; + return $default; } } diff --git a/src/Subscriber/Console/Header.php b/src/Subscriber/Console/Header.php index 4b60b0f..d3bf399 100644 --- a/src/Subscriber/Console/Header.php +++ b/src/Subscriber/Console/Header.php @@ -15,8 +15,6 @@ class Header extends BaseSubscriber /** * Print header - * - * @return void */ public function __construct(OutputInterface $output) { From 0625fb1c2639df68a23b9c4d81426b687db81653 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Forsg=C3=A5rd?= Date: Sat, 31 Jan 2015 15:03:58 +0100 Subject: [PATCH 09/16] Correct spelling.. [ci skip] --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3866b4a..b8c14d1 100644 --- a/README.md +++ b/README.md @@ -71,11 +71,12 @@ You can also get a listing of the current checks being done with the `rules` com psecio-parse rules -### Using a configuraion file +### Using a configuration file Specify the name of your configuration file with the `--configuration` option. If no -filename is given the default `.psecio-parse.json` is used. To ignore the default configuration -file use the `--no-configuration` option. +filename is given the default `.psecio-parse.json` is used. + +To ignore the default configuration file use the `--no-configuration` option. #### Configuration file format ```json From ca0a1aecc6d7aeb43277bb52afda6df3694c95d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Forsg=C3=A5rd?= Date: Sun, 8 Feb 2015 18:51:03 +0100 Subject: [PATCH 10/16] Use a .dist file --- .psecio-parse.json => .psecio-parse.json.dist | 0 .travis.yml | 2 +- README.md | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename .psecio-parse.json => .psecio-parse.json.dist (100%) diff --git a/.psecio-parse.json b/.psecio-parse.json.dist similarity index 100% rename from .psecio-parse.json rename to .psecio-parse.json.dist diff --git a/.travis.yml b/.travis.yml index 38e1ad9..c888b5a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,4 +11,4 @@ install: script: - bin/phpunit - - bin/psecio-parse scan + - bin/psecio-parse scan src tests bin/psecio-parse diff --git a/README.md b/README.md index b8c14d1..bb5e0b0 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,7 @@ To ignore the default configuration file use the `--no-configuration` option. } ``` -See the configurations for scanning parse itself [here](.psecio-parse.json). See the +See example configurations for scanning parse itself [here](.psecio-parse.json.dist). See the json schema used to validate configuration files [here](src/Conf/schema.json). From becb3b67623eae9a772f16a99646ed6b8b3c27ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Forsg=C3=A5rd?= Date: Sun, 8 Feb 2015 20:51:17 +0100 Subject: [PATCH 11/16] Allow hhvm failure in travis builds --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index c888b5a..311da73 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,11 @@ php: - 5.5 - 5.4 +matrix: + allow_failures: + - php: hhvm + fast_finish: true + install: - composer install --dev --prefer-source From c9406605f257933b266f333ec96e58854948a0c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Forsg=C3=A5rd?= Date: Fri, 13 Feb 2015 15:52:33 +0100 Subject: [PATCH 12/16] Renaming the default config file psecio-parse.json --- README.md | 4 ++-- .psecio-parse.json.dist => psecio-parse.json.dist | 0 src/Conf/ConfFactory.php | 2 +- tests/Conf/ConfFactoryTest.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename .psecio-parse.json.dist => psecio-parse.json.dist (100%) diff --git a/README.md b/README.md index bb5e0b0..d3fff22 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ You can also get a listing of the current checks being done with the `rules` com ### Using a configuration file Specify the name of your configuration file with the `--configuration` option. If no -filename is given the default `.psecio-parse.json` is used. +filename is given the default `psecio-parse.json` is used. To ignore the default configuration file use the `--no-configuration` option. @@ -104,7 +104,7 @@ To ignore the default configuration file use the `--no-configuration` option. } ``` -See example configurations for scanning parse itself [here](.psecio-parse.json.dist). See the +See example configurations for scanning parse itself [here](psecio-parse.json.dist). See the json schema used to validate configuration files [here](src/Conf/schema.json). diff --git a/.psecio-parse.json.dist b/psecio-parse.json.dist similarity index 100% rename from .psecio-parse.json.dist rename to psecio-parse.json.dist diff --git a/src/Conf/ConfFactory.php b/src/Conf/ConfFactory.php index 29a6dfa..1347e7f 100644 --- a/src/Conf/ConfFactory.php +++ b/src/Conf/ConfFactory.php @@ -43,7 +43,7 @@ private function getConfFileInfo(InputInterface $input) } if (!$input->getOption('no-configuration')) { - $confFileInfo = new SplFileInfo('.psecio-parse.json'); + $confFileInfo = new SplFileInfo('psecio-parse.json'); if ($confFileInfo->isReadable()) { return $confFileInfo; } diff --git a/tests/Conf/ConfFactoryTest.php b/tests/Conf/ConfFactoryTest.php index c233928..3167253 100644 --- a/tests/Conf/ConfFactoryTest.php +++ b/tests/Conf/ConfFactoryTest.php @@ -42,7 +42,7 @@ public function testDefaultConfFile() $cwd = getcwd(); chdir(sys_get_temp_dir()); - $filename = sys_get_temp_dir() . '/.psecio-parse.json'; + $filename = sys_get_temp_dir() . '/psecio-parse.json'; file_put_contents($filename, '{"extensions": ["foobar"]}'); $confUsingDefaultFile = (new ConfFactory)->createConf( From ba1231f2043fa836675624b6ad0f26ad381b9fc6 Mon Sep 17 00:00:00 2001 From: Michael Johnson Date: Mon, 6 Jul 2015 14:55:10 -0500 Subject: [PATCH 13/16] Check for missing name property The `IsBoolLiteral` trait checks that the `name` property of the Node. In the current version of PhpParser, the `name` property *may* not be present. So check for it first. --- src/Rule/Helper/IsBoolLiteralTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Rule/Helper/IsBoolLiteralTrait.php b/src/Rule/Helper/IsBoolLiteralTrait.php index 85f6988..66d192f 100644 --- a/src/Rule/Helper/IsBoolLiteralTrait.php +++ b/src/Rule/Helper/IsBoolLiteralTrait.php @@ -20,7 +20,7 @@ trait IsBoolLiteralTrait */ protected function isBoolLiteral(Node $node, $value = null) { - if ($node->name instanceof \PhpParser\Node\Name) { + if (isset($node->name) && $node->name instanceof \PhpParser\Node\Name) { $name = strtolower($node->name); if ($name === 'true' || $name === 'false') { if ($value === true) { From cc7f123933eac4d020fab31f53cb602ae62de489 Mon Sep 17 00:00:00 2001 From: Marv Date: Wed, 30 Nov 2016 11:52:42 +0000 Subject: [PATCH 14/16] updating justinrainbow/json-schema --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index f1346de..f042f00 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ "nikic/php-parser": "^1.0|^2.0", "symfony/console": "~2.5|~3.1", "symfony/event-dispatcher": "~2.4|~3.1", - "justinrainbow/json-schema": ">=1.3.2,<1.4" + "justinrainbow/json-schema": "^4.0" }, "require-dev": { "phpunit/phpunit": "~4.2", From 82eb763122239d1b4012b72529c38c398f53791e Mon Sep 17 00:00:00 2001 From: Marv Date: Wed, 30 Nov 2016 12:27:36 +0000 Subject: [PATCH 15/16] adding symfony/finder to enable wildcards in json.paths --- composer.json | 3 ++- src/Conf/JsonConf.php | 11 ++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index f042f00..3ec8f5e 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,8 @@ "nikic/php-parser": "^1.0|^2.0", "symfony/console": "~2.5|~3.1", "symfony/event-dispatcher": "~2.4|~3.1", - "justinrainbow/json-schema": "^4.0" + "justinrainbow/json-schema": "^4.0", + "symfony/finder": "^3.1" }, "require-dev": { "phpunit/phpunit": "~4.2", diff --git a/src/Conf/JsonConf.php b/src/Conf/JsonConf.php index 7c39e68..3ef9793 100644 --- a/src/Conf/JsonConf.php +++ b/src/Conf/JsonConf.php @@ -5,6 +5,7 @@ use JsonSchema\Uri\UriRetriever; use JsonSchema\Validator; use RuntimeException; +use Symfony\Component\Finder\Finder; /** * Json configuration wrapper @@ -44,7 +45,15 @@ public function getFormat() public function getPaths() { - return $this->read('paths', []); + $finder = (new Finder())->directories(); + foreach ($this->read('paths', []) as $path) { + $finder->in($path); + } + $out = []; + foreach ($finder as $path) { + $out[] = $path; + } + return $out; } public function getIgnorePaths() From 1f9e68b6f7a3990ca61d288dbb4a68ea4a2d96af Mon Sep 17 00:00:00 2001 From: Marv Date: Wed, 30 Nov 2016 12:28:17 +0000 Subject: [PATCH 16/16] sorting packages --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 3ec8f5e..9dffcc3 100644 --- a/composer.json +++ b/composer.json @@ -14,10 +14,10 @@ ], "require":{ "php": ">=5.4", + "justinrainbow/json-schema": "^4.0", "nikic/php-parser": "^1.0|^2.0", "symfony/console": "~2.5|~3.1", "symfony/event-dispatcher": "~2.4|~3.1", - "justinrainbow/json-schema": "^4.0", "symfony/finder": "^3.1" }, "require-dev": {