Skip to content

Commit

Permalink
[SPN-1440] New Guardrail Check to ensure documentation is present and…
Browse files Browse the repository at this point in the history
… report on deprecated web APIs
  • Loading branch information
bknutson123 committed Jan 15, 2025
1 parent b704af0 commit f4798fc
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/Checks/ErrorConstants.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ class ErrorConstants {
const TYPE_VARIABLE_FUNCTION_NAME = 'Standard.VariableFunctionCall';
const TYPE_VARIABLE_VARIABLE = 'Standard.VariableVariable';
const TYPE_COUNTABLE_EMPTINESS_CHECK = 'Standard.Countable.Emptiness';
const TYPE_WEB_API_DOCUMENTATION_CHECK = 'Standard.WebApi.Documentation';


/**
Expand Down
66 changes: 66 additions & 0 deletions src/Checks/WebApiDocumentationCheck.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

namespace BambooHR\Guardrail\Checks;

use BambooHR\Guardrail\Metrics\Metric;
use BambooHR\Guardrail\Metrics\MetricOutputInterface;
use BambooHR\Guardrail\Scope;
use PhpParser\Node;
use PhpParser\Node\Stmt\ClassLike;

class WebApiDocumentationCheck extends BaseCheck {
function __construct($index, $output, private readonly MetricOutputInterface $metricOutput) {
parent::__construct($index, $output);
}

/**
* getCheckNodeTypes
*
* @return string[]
*/
function getCheckNodeTypes() {
return [Node\Stmt\ClassMethod::class];
}

/**
* @param string $fileName The name of the file we are parsing
* @param Node $node Instance of the Node
* @param ClassLike|null $inside Instance of the ClassLike (the class we are parsing) [optional]
* @param Scope|null $scope Instance of the Scope (all variables in the current state) [optional]
*
* @return void
*/
public function run($fileName, Node $node, ClassLike $inside = null, Scope $scope = null) {
if ($node instanceof Node\Stmt\ClassMethod && $node->isPublic()) {
foreach ($node->attrGroups as $attrGroup) {
foreach ($attrGroup->attrs as $attribute) {
$attributeName = $attribute->name->toString();
if (str_starts_with($attributeName, 'OpenApi\Attributes')) {
foreach ($attribute->args as $arg) {
if ($arg->name->name === 'deprecated' && $arg->value->name->toString() == 'true') {
$this->metricOutput->emitMetric(new Metric(
$fileName,
$node->getLine(),
ErrorConstants::TYPE_METRICS_DEPRECATED_FUNCTIONS,
[]
));
break;
}
}

return;
}
}
}

$className = $inside->namespacedName->toString();
$this->emitErrorOnLine(
$fileName,
$node->getLine(),
ErrorConstants::TYPE_WEB_API_DOCUMENTATION_CHECK,
"All public controller methods should be associated with a route and must have
documentation through an OpenAPI Attribute. Method: {$node->name->name}, Class: $className"
);
}
}
}
2 changes: 2 additions & 0 deletions src/NodeVisitors/StaticAnalyzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
use BambooHR\Guardrail\Checks\UnsafeSuperGlobalCheck;
use BambooHR\Guardrail\Checks\UnusedPrivateMemberVariableCheck;
use BambooHR\Guardrail\Checks\UseStatementCaseCheck;
use BambooHR\Guardrail\Checks\WebApiDocumentationCheck;
use BambooHR\Guardrail\Config;
use BambooHR\Guardrail\Evaluators as Ev;
use BambooHR\Guardrail\Evaluators\ExpressionInterface;
Expand Down Expand Up @@ -157,6 +158,7 @@ function __construct(SymbolTable $index, OutputInterface $output, MetricOutputIn
new ThrowsCheck($this->index, $output),
new CountableEmptinessCheck($this->index, $output),
new DependenciesOnVendorCheck($this->index, $output, $metricOutput),
new WebApiDocumentationCheck($this->index, $output, $metricOutput),
//new ClassStoredAsVariableCheck($this->index, $output)
];

Expand Down
29 changes: 29 additions & 0 deletions tests/units/Checks/TestData/TestWebApiDocumentationCheck.1.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php
use OpenApi\Attributes as OA;


class MyController {
/**
* @return bool
*/
#[\Onsen\SecurityAudit\Sensitivity\Low]
#[OA\Get(path: "/test")]
public function hasAttribute() {
return false;
}

/**
* @return int
*/
public function doesNotHaveAttribute() {
return 123;
}

/**
* @return int
*/
#[\Onsen\SecurityAudit\Sensitivity\Low]
public function hasFakeAttribute() {
return 456;
}
}
17 changes: 17 additions & 0 deletions tests/units/Checks/TestData/TestWebApiDocumentationCheck.2.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php
use OpenApi\Attributes as OA;

class MyController {
function undefinedVisibilityMethod() {
return false;
}
public function publicMethod() {
return false;
}
protected function protectedMethod() {
return false;
}
private function privateMethod() {
return false;
}
}
18 changes: 18 additions & 0 deletions tests/units/Checks/TestData/TestWebApiDocumentationCheck.3.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php
use OpenApi\Attributes as OA;


class MyController {
/**
* @return bool
*/
#[OA\Get(path: "/test", deprecated: true)]
public function hasDeprecatedAttribute() {
return false;
}

#[OA\Get(path: "/test")]
public function doesNotHaveDeprecatedAttribute() {
return false;
}
}
29 changes: 29 additions & 0 deletions tests/units/Checks/TestWebApiDocumentationCheck.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace BambooHR\Guardrail\Tests\units\Checks;

use BambooHR\Guardrail\Checks\ErrorConstants;
use BambooHR\Guardrail\Tests\TestSuiteSetup;

class TestWebApiDocumentationCheck extends TestSuiteSetup {
/**
* testApiAttributeIsPresent
*
* @return void
*/
public function testApiAttributeIsPresent() {
$this->assertEquals(2, $this->runAnalyzerOnFile('.1.inc', ErrorConstants::TYPE_WEB_API_DOCUMENTATION_CHECK,), "");
}

/**
* @return void
*/
public function testOnlyErrorsOnPublicMethods() {
$this->assertEquals(2, $this->runAnalyzerOnFile('.2.inc', ErrorConstants::TYPE_WEB_API_DOCUMENTATION_CHECK,), "");
}

public function testMethodWithDeprecatedAttribute() {
$output = $this->getOutputFromAnalyzer('.3.inc', ErrorConstants::TYPE_METRICS_DEPRECATED_FUNCTIONS);
$this->assertEquals(1, $this->getMetricCountByName($output, ErrorConstants::TYPE_METRICS_DEPRECATED_FUNCTIONS));
}
}

0 comments on commit f4798fc

Please sign in to comment.