Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
/bin/coveralls
/bin/phpunit
/bin/test-reporter
/bin/validate-json
7 changes: 6 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,14 @@ php:
- 5.5
- 5.4

matrix:
allow_failures:
- php: hhvm
fast_finish: true

install:
- composer install --dev --prefer-source

script:
- bin/phpunit
- bin/psecio-parse scan src tests
- bin/psecio-parse scan src tests bin/psecio-parse
38 changes: 37 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ You can also get a listing of the current checks being done with the `rules` com

psecio-parse rules

### Managing rules to run
### Managing the rules to run

There are several ways to control which rules are run. You can specifically include rules using
the `--include-rules` option, specifically exclude them with `--exclude-rules`, turn them on and
Expand Down Expand Up @@ -114,6 +114,42 @@ To disable the use of annotations, use the `--disable-annotations` option.

See the `examples` directory for some examples of the use of annotations for *Parse*.

### 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.

#### 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 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).

The Checks
----------
Here's the current list of checks:
Expand Down
8 changes: 5 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,16 @@
],
"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"
"symfony/event-dispatcher": "~2.4|~3.1",
"symfony/finder": "^3.1"
},
"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": {
Expand Down
8 changes: 8 additions & 0 deletions psecio-parse.json.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"paths": [
"src",
"tests",
"bin/psecio-parse"
],
"format": "progress"
}
124 changes: 37 additions & 87 deletions src/Command/ScanCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,14 @@
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\Console\Helper\ProgressBar;
use Psecio\Parse\Conf\ConfFactory;
use Psecio\Parse\Subscriber\SubscriberFactory;
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\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\Scanner;
use Psecio\Parse\FileIterator;
use Psecio\Parse\DocComment\DocCommentFactory;
use RuntimeException;
Expand All @@ -41,43 +35,55 @@ protected function configure()
->addArgument(
'path',
InputArgument::OPTIONAL|InputArgument::IS_ARRAY,
'Paths to scan',
[]
'Path to scan'
)
->addOption(
'format',
'f',
InputOption::VALUE_REQUIRED,
'Output format (progress, dots or xml)',
'progress'
'Output format (progress, dots, lines, debug or xml)'
)
->addOption(
'ignore-paths',
'i',
InputOption::VALUE_REQUIRED,
'Comma-separated list of paths to ignore',
''
'Comma-separated list of paths to ignore'
)
->addOption(
'extensions',
'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',
'w',
InputOption::VALUE_REQUIRED,
'Comma-separated list of rules to use',
''
'Comma-separated list of rules to whitelist'
)
->addOption(
'blacklist-rules',
'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'
)
->addOption(
'no-configuration',
null,
InputOption::VALUE_NONE,
'Ignore default configuration file'
)
->addOption(
'disable-annotations',
Expand All @@ -95,95 +101,39 @@ 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 ConfFactory)->createConf($input, $confFileName);
$dispatcher = new EventDispatcher;

(new SubscriberFactory($conf->getFormat(), $output))->addSubscribersTo($dispatcher);

$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) {
case 'dots':
case 'progress':
$output->writeln("<info>Parse: A PHP Security Scanner</info>\n");
if ($output->isVeryVerbose()) {
$dispatcher->addSubscriber(
new ConsoleDebug($output)
);
} elseif ($output->isVerbose()) {
$dispatcher->addSubscriber(
new ConsoleLines($output)
);
} elseif ('progress' == $format && $output->isDecorated()) {
$dispatcher->addSubscriber(
new ConsoleProgressBar(new ProgressBar($output, count($fileIterator)))
);
} else {
$dispatcher->addSubscriber(
new ConsoleDots($output)
);
}
$dispatcher->addSubscriber(new ConsoleReport($output));
break;
case 'xml':
$dispatcher->addSubscriber(new Xml($output));
break;
default:
throw new RuntimeException("Unknown output format '{$input->getOption('format')}'");
if ($confFileName) {
$dispatcher->dispatch(Events::DEBUG, new MessageEvent("Reading configurations from $confFileName"));
}

$ruleFactory = new RuleFactory(
$this->parseCsv($input->getOption('whitelist-rules')),
$this->parseCsv($input->getOption('blacklist-rules'))
);

$ruleCollection = $ruleFactory->createRuleCollection();
$rules = (new RuleFactory($conf->getRuleWhitelist(), $conf->getRuleBlacklist()))->createRuleCollection();

$ruleNames = implode(',', array_map(
function (RuleInterface $rule) {
return $rule->getName();
},
$ruleCollection->toArray()
));

$dispatcher->dispatch(Events::DEBUG, new MessageEvent("Using ruleset $ruleNames"));
$dispatcher->dispatch(Events::DEBUG, new MessageEvent("Using ruleset: $rules"));

$docCommentFactory = new DocCommentFactory();

$scanner = new Scanner(
$dispatcher,
new CallbackVisitor(
$ruleCollection,
$rules,
$docCommentFactory,
!$input->getOption('disable-annotations')
)
);
$scanner->scan($fileIterator);

return $exitCode->getExitCode();
}
$scanner->scan(new FileIterator($conf->getPaths(), $conf->getIgnorePaths(), $conf->getExtensions()));

/**
* 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));
return $exitCode->getExitCode();
}
}
52 changes: 52 additions & 0 deletions src/Conf/ConfFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

namespace Psecio\Parse\Conf;

use Symfony\Component\Console\Input\InputInterface;
use Psecio\Parse\File;
use SplFileInfo;

/**
* Manage the configuration cascade
*/
class ConfFactory
{
/**
* Create configuration cascade
*
* @param InputInterface $input Input object
* @param string &$confFileName Will contain name of used config file
* @return Configuration
*/
public function createConf(InputInterface $input, &$confFileName = '')
{
$conf = new UserConf($input);

if ($confFileInfo = $this->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;
}
}
}
}
Loading