Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add rule suppression annotations #52

Merged
merged 34 commits into from
Jul 3, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
a1f9733
First pass at annotation handling
redbeardcreator Jan 15, 2015
0d55069
Add annotation sample files
redbeardcreator Jan 15, 2015
9aa32a7
Convert annoatations to enable/disable
redbeardcreator Jan 16, 2015
024c009
Add and implement --disable-annotations
redbeardcreator Jan 16, 2015
e213cc2
Move examples to the examples directory
redbeardcreator Jan 16, 2015
a5f06f4
Update README with rule specification docs
redbeardcreator Jan 16, 2015
9bb02e9
Add single letter aliases for most scan options
redbeardcreator Jan 16, 2015
b6f09e7
Merge branch 'master' into issue-10-annotations
redbeardcreator Jan 17, 2015
710ed8d
Switch to eloquent/blox for parsing DocBlocks
redbeardcreator Jan 18, 2015
7bf0a00
Merge branch 'master' into issue-10-annotations
redbeardcreator Jan 19, 2015
f769453
Use @hanneskod's DocComment
redbeardcreator Jan 21, 2015
e7db279
Merge branch 'issue-10-annotations' of github.com:redbeardcreator/par…
redbeardcreator Jan 21, 2015
83fb594
Add and use get[I]MatchingTags() to DocComment
redbeardcreator Jan 21, 2015
e45582e
Move DocComment into DocComment namespace
redbeardcreator Jan 22, 2015
c459d99
Add DocCommentFactory and friends
redbeardcreator Jan 22, 2015
e7d0f65
Improve coverage of DocCommentFactory
redbeardcreator Jan 22, 2015
c16251e
Remove Eloquent/Blox
redbeardcreator Jan 22, 2015
4550ad5
Change CallbackVisitor constructor signature
redbeardcreator Jan 22, 2015
8817c73
Merge branch 'master' into issue-10-annotations
redbeardcreator Jan 23, 2015
cbe27df
Add short options to those missing them
redbeardcreator Jan 23, 2015
844511f
Fix and add annotation tests
redbeardcreator Jan 29, 2015
032a2d1
Refactor CallbackVisitor
redbeardcreator Jan 29, 2015
cf326cc
Fix indentation
redbeardcreator Jan 29, 2015
105f56b
Rename getMockNodeWithRule() to ...Annotation()
redbeardcreator Jan 29, 2015
7ce6f69
Make sure nodes with empty DocBlocks are tested
redbeardcreator Jan 29, 2015
de68c1f
Put quotes around path in File exceptions
Feb 4, 2015
bcb831c
Use DocCommentFactory and switch to fakes
Feb 4, 2015
15895f2
Merge branch 'master' into issue-10-annotations
redbeardcreator Feb 20, 2015
052888f
Add a note about annotation comments
redbeardcreator Feb 20, 2015
ac1780e
Merge branch 'master' into issue-10-annotations
redbeardcreator Feb 20, 2015
42181b3
Fix some EOL spaces and add some docs
redbeardcreator Feb 20, 2015
bea3f2b
Add & use assertFailureCallled() in callback tests
redbeardcreator Mar 10, 2015
e47d286
Add // comments to @annotations
redbeardcreator Mar 10, 2015
0b94df9
Merge branch 'master' into issue-10-annotations
redbeardcreator Mar 10, 2015
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
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,48 @@ You can also get a listing of the current checks being done with the `rules` com

psecio-parse rules

### Managing 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
off on a case-by-case basis using annotations, and disable annotations using
`--disable-annotations`.

#### Excluding and Including rules

By default, `psecio-parse scan` includes all available rules in its scan. By using
`--exclude-rules` and `--include-rules`, the rules included can be reduced.

Any rules specified by `--exclude-rules` are explicity excluded from the scan, regardless of any
other options selected. These rules cannot be added back to the scan, short of re-running the scan
with different options. Invalid rules are silently ignored.

If `--include-rules` is provided, only those rules specified can be used. No other rules are
checked. Note that rules that aren't available (whether they do not exist or `--excluded-rules` is
used to exclude them) cannot be included. Invalid rules are silently ignored.

#### Annotations

Rules can be enabled and disabled using DocBlock annotations. These are comments in the code being
scanned that tells *Parse* to specifically enable or disable a rule for the block of code the
DocBlock applies to.

* `@psecio\parse\disable <rule>`: Tells *Parse* to ignore the given rule for the scope of the
DocBlock.
* `@psecio\parse\enable <rule>`: Tells *Parse* to enable the given rule for the scope of the
DocBlock. This can be used to re-enable a particular rule when `@psecio\parse\disable` has been
applied to the containing scope.

Note that annotations cannot enable tests that have been omitted via the command line options. If
a test is disabled at the command line, it is disabled for the entire scan, regardless of any
annotations.

Comments can be added after `<rule>` following a dobule-slash (`//`) comment separator. It is
recommended that comments be used to indicate why the rule has been disabled or enabled.

To disable the use of annotations, use the `--disable-annotations` option.

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

The Checks
----------
Expand Down
3 changes: 1 addition & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@
"php": ">=5.4",
"nikic/php-parser": "~1.0",
"symfony/console": "~2.5",
"symfony/event-dispatcher": "~2.4",
"eloquent/blox": "~3.0"
"symfony/event-dispatcher": "~2.4"
},
"require-dev": {
"phpunit/phpunit": "4.2.*",
Expand Down
19 changes: 19 additions & 0 deletions examples/annotations/turnOffRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

/**
* Turn off the rule for the following function
*
* @psecio\parse\disable EvalFunction
*/
function needsEval()
{
eval('echo "I need eval()\n"');
}

/**
* Previous rule should not affect this one.
*/
function evalIsBad()
{
eval('echo "Eval is Evil!"');
}
35 changes: 35 additions & 0 deletions examples/annotations/turnOnRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

/**
* This class turns off BooleanIdentity.
*
* @psecio\parse\disable BooleanIdentity // This class has lots of brokeness here to be fixed later
*/
class testTurnOnRule
{
public function shouldError()
{
if ($ignoreMe == true) {
echo "Check your bools!\n";
}
}

/**
* @psecio\parse\enable BooleanIdentity Got the problems solved in this method
*/
public function shouldNotError()
{
if ($respectMyAuthority == true) {
echo "I'm not responsible for your bools.!\n";
}

/** @psecio\parse\disable booleanidentity But there is a reason for this */
if ($butThisCanBeIgnored == false) {
echo "But..yeah. Huh.\n";
}

if ($dontIgnoreThis == true) {
echo "Now we do the dance of joy!\n";
}
}
}
94 changes: 90 additions & 4 deletions src/CallbackVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,26 @@
use PhpParser\NodeVisitorAbstract;
use PhpParser\Node;

use Psecio\Parse\DocComment\DocCommentFactoryInterface;

/**
* Evaluate rules and call callback on failure
*/
class CallbackVisitor extends NodeVisitorAbstract
{
const ENABLE_TAG = 'psecio\parse\enable';
const DISABLE_TAG = 'psecio\parse\disable';

/**
* @var RuleCollection Rules to evaluate
*/
private $ruleCollection;

/**
* @var array List of enabled rules, stored as ruleName => bool
*/
private $enabledRules;

/**
* @var callable Fail callback
*/
Expand All @@ -25,14 +35,35 @@ class CallbackVisitor extends NodeVisitorAbstract
*/
private $file;

/**
* @var bool If false, ignore all annotations
*/
private $useAnnotations;

/**
* @var DocCommentFactoryInterface
*/
private $docCommentFactory;

/**
* Inject rule collection
*
* @param RuleCollection $ruleCollection
* @param bool $useAnnotations If false, ignore all annotations
*/
public function __construct(RuleCollection $ruleCollection)
public function __construct(RuleCollection $ruleCollection,
DocCommentFactoryInterface $docCommentFactory,
$useAnnotations)
{
$this->ruleCollection = $ruleCollection;
$this->enabledRules = [];

foreach ($this->ruleCollection as $rule) {
$this->enabledRules[strtolower($rule->getName())] = true;
}

$this->useAnnotations = $useAnnotations;
$this->docCommentFactory = $docCommentFactory;
}

/**
Expand Down Expand Up @@ -64,10 +95,65 @@ public function setFile(File $file)
*/
public function enterNode(Node $node)
{
if ($this->useAnnotations) {
$this->updateRuleFilters($node);
}

foreach ($this->ruleCollection as $rule) {
if (!$rule->isValid($node)) {
call_user_func($this->callback, $rule, $node, $this->file);
}
$this->evaluateRule($node, $rule);
}
}

protected function evaluateRule($node, $rule)
{
if (!$this->enabledRules[strtolower($rule->getName())]) {
return;
}

if (!$rule->isValid($node)) {
call_user_func($this->callback, $rule, $node, $this->file);
}
}

public function leaveNode(Node $node)
{
if (!$node->hasAttribute('oldEnabledRules')) {
return;
}

// Restore rules as they were before this node
$this->enabledRules = $node->getAttribute('oldEnabledRules');
}

private function updateRuleFilters($node)
{
$docBlock = $node->getDocComment();
if (empty($docBlock)) {
return;
}

$node->setAttribute('oldEnabledRules', $this->enabledRules);
$this->enabledRules = $this->evaluateDocBlock($docBlock, $this->enabledRules);
}

private function evaluateDocBlock($docBlock, $rules)
{
$comment = $this->docCommentFactory->createDocComment($docBlock);

$this->checkTags($comment, $rules, self::ENABLE_TAG, true);
$this->checkTags($comment, $rules, self::DISABLE_TAG, false);

return $rules;
}

private function checkTags(DocComment\DocCommentInterface $comment, &$rules, $tag, $value)
{
$tags = $comment->getIMatchingTags($tag);

foreach ($tags as $rule) {
// Get the first word from content. This allows you to add comments to rules.
$ruleName = trim(strtolower(strtok($rule, '//')));
$rules[$ruleName] = $value;
}
}
}
28 changes: 22 additions & 6 deletions src/Command/ScanCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
use Psecio\Parse\Scanner;
use Psecio\Parse\CallbackVisitor;
use Psecio\Parse\FileIterator;
use Psecio\Parse\DocComment\DocCommentFactory;
use RuntimeException;

/**
Expand All @@ -45,39 +46,45 @@ protected function configure()
)
->addOption(
'format',
null,
'f',
InputOption::VALUE_REQUIRED,
'Output format (progress, dots or xml)',
'progress'
)
->addOption(
'ignore-paths',
null,
'i',
InputOption::VALUE_REQUIRED,
'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'
)
->addOption(
'whitelist-rules',
null,
'w',
InputOption::VALUE_REQUIRED,
'Comma-separated list of rules to use',
''
)
->addOption(
'blacklist-rules',
null,
'b',
InputOption::VALUE_REQUIRED,
'Comma-separated list of rules to skip',
''
)
->addOption(
'disable-annotations',
'd',
InputOption::VALUE_NONE,
'Skip all annotation-based rule toggles.'
)
->setHelp(
"Scan paths for possible security issues:\n\n <info>psecio-parse %command.name% /path/to/src</info>\n"
);
Expand Down Expand Up @@ -151,7 +158,16 @@ function (RuleInterface $rule) {

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

$scanner = new Scanner($dispatcher, new CallbackVisitor($ruleCollection));
$docCommentFactory = new DocCommentFactory();

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

return $exitCode->getExitCode();
Expand Down
Loading