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
Extra checks #60
Merged
NightJar
merged 9 commits into
silverstripe:master
from
silverstripe-terraformers:feature/extra-checks
Mar 19, 2019
Merged
Extra checks #60
Changes from 6 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
e323674
NEW: Adding Guzzle dependency.
frankmullenger df59195
NEW: Session check.
frankmullenger c1bfd56
NEW: Cache headers check.
frankmullenger 27994e5
FIX: Removing return type declarations.
frankmullenger 90f0556
FIX: Removal of optional param.
frankmullenger 2674beb
FIX: Line length.
frankmullenger 578c73e
NEW: Refactor to provide unit tests for session check.
frankmullenger 8f7f857
NEW: Refactor to provide unit tests for cache headers check.
frankmullenger da9eddc
Merge branch 'master' into feature/extra-checks
frankmullenger File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,220 @@ | ||
<?php | ||
|
||
namespace SilverStripe\EnvironmentCheck\Checks; | ||
|
||
use SilverStripe\Control\Director; | ||
use SilverStripe\Control\Controller; | ||
use SilverStripe\ORM\ValidationResult; | ||
use Psr\Http\Message\ResponseInterface; | ||
use SilverStripe\Core\Config\Configurable; | ||
use SilverStripe\EnvironmentCheck\Traits\Fetcher; | ||
use SilverStripe\EnvironmentCheck\EnvironmentCheck; | ||
|
||
/** | ||
* Check cache headers for any response, can specify directives that must be included and | ||
* also must be excluded from Cache-Control headers in response. Also checks for | ||
* existence of ETag. | ||
* | ||
* @example SilverStripe\EnvironmentCheck\Checks\CacheHeadersCheck("/",["must-revalidate", "max-age=120"],["no-store"]) | ||
* @package environmentcheck | ||
*/ | ||
class CacheHeadersCheck implements EnvironmentCheck | ||
{ | ||
use Configurable; | ||
use Fetcher; | ||
|
||
/** | ||
* URL to check | ||
* | ||
* @var string | ||
*/ | ||
protected $url; | ||
|
||
/** | ||
* Settings that must be included in the Cache-Control header | ||
* | ||
* @var array | ||
*/ | ||
protected $mustInclude = []; | ||
|
||
/** | ||
* Settings that must be excluded in the Cache-Control header | ||
* | ||
* @var array | ||
*/ | ||
protected $mustExclude = []; | ||
|
||
/** | ||
* Result to keep track of status and messages for all checks, reuses | ||
* ValidationResult for convenience. | ||
* | ||
* @var ValidationResult | ||
*/ | ||
protected $result; | ||
|
||
/** | ||
* Set up with URL, arrays of header settings to check. | ||
* | ||
* @param string $url | ||
* @param array $mustInclude Settings that must be included in Cache-Control | ||
* @param array $mustExclude Settings that must be excluded in Cache-Control | ||
*/ | ||
public function __construct($url = '', $mustInclude = [], $mustExclude = []) | ||
{ | ||
$this->url = $url; | ||
$this->mustInclude = $mustInclude; | ||
$this->mustExclude = $mustExclude; | ||
|
||
$this->clientConfig = [ | ||
'base_uri' => Director::absoluteBaseURL(), | ||
'timeout' => 10.0, | ||
]; | ||
|
||
// Using a validation result to capture messages | ||
$this->result = new ValidationResult(); | ||
} | ||
|
||
/** | ||
* Check that correct caching headers are present. | ||
* | ||
* @return void | ||
*/ | ||
public function check() | ||
{ | ||
$response = $this->fetchResponse($this->url); | ||
$fullURL = Controller::join_links(Director::absoluteBaseURL(), $this->url); | ||
if ($response === null) { | ||
return [ | ||
EnvironmentCheck::ERROR, | ||
"Cache headers check request failed for $fullURL", | ||
]; | ||
} | ||
|
||
//Check that Etag exists | ||
$this->checkEtag($response); | ||
|
||
// Check Cache-Control settings | ||
$this->checkCacheControl($response); | ||
|
||
if ($this->result->isValid()) { | ||
return [ | ||
EnvironmentCheck::OK, | ||
$this->getMessage(), | ||
]; | ||
} else { | ||
// @todo Ability to return a warning | ||
return [ | ||
EnvironmentCheck::ERROR, | ||
$this->getMessage(), | ||
]; | ||
} | ||
} | ||
|
||
/** | ||
* Collate messages from ValidationResult so that it is clear which parts | ||
* of the check passed and which failed. | ||
* | ||
* @return string | ||
*/ | ||
private function getMessage() | ||
{ | ||
$ret = ''; | ||
// Filter good messages | ||
$goodTypes = [ValidationResult::TYPE_GOOD, ValidationResult::TYPE_INFO]; | ||
$good = array_filter( | ||
$this->result->getMessages(), | ||
function ($val, $key) use ($goodTypes) { | ||
if (in_array($val['messageType'], $goodTypes)) { | ||
return true; | ||
} | ||
return false; | ||
}, | ||
ARRAY_FILTER_USE_BOTH | ||
); | ||
if (!empty($good)) { | ||
$ret .= "GOOD: " . implode('; ', array_column($good, 'message')) . " "; | ||
} | ||
|
||
// Filter bad messages | ||
$badTypes = [ValidationResult::TYPE_ERROR, ValidationResult::TYPE_WARNING]; | ||
$bad = array_filter( | ||
$this->result->getMessages(), | ||
function ($val, $key) use ($badTypes) { | ||
if (in_array($val['messageType'], $badTypes)) { | ||
return true; | ||
} | ||
return false; | ||
}, | ||
ARRAY_FILTER_USE_BOTH | ||
); | ||
if (!empty($bad)) { | ||
$ret .= "BAD: " . implode('; ', array_column($bad, 'message')); | ||
} | ||
return $ret; | ||
} | ||
|
||
/** | ||
* Check that ETag header exists | ||
* | ||
* @param ResponseInterface $response | ||
* @return void | ||
*/ | ||
private function checkEtag(ResponseInterface $response) | ||
{ | ||
$eTag = $response->getHeaderLine('ETag'); | ||
$fullURL = Controller::join_links(Director::absoluteBaseURL(), $this->url); | ||
|
||
if ($eTag) { | ||
$this->result->addMessage( | ||
"$fullURL includes an Etag header in response", | ||
ValidationResult::TYPE_GOOD | ||
); | ||
return; | ||
} | ||
$this->result->addError( | ||
"$fullURL is missing an Etag header", | ||
ValidationResult::TYPE_WARNING | ||
); | ||
} | ||
|
||
/** | ||
* Check that the correct header settings are either included or excluded. | ||
* | ||
* @param ResponseInterface $response | ||
* @return void | ||
*/ | ||
private function checkCacheControl(ResponseInterface $response) | ||
{ | ||
$cacheControl = $response->getHeaderLine('Cache-Control'); | ||
$vals = array_map('trim', explode(',', $cacheControl)); | ||
$fullURL = Controller::join_links(Director::absoluteBaseURL(), $this->url); | ||
|
||
// All entries from must contain should be present | ||
if ($this->mustInclude == array_intersect($this->mustInclude, $vals)) { | ||
$matched = implode(",", $this->mustInclude); | ||
$this->result->addMessage( | ||
"$fullURL includes all settings: {$matched}", | ||
ValidationResult::TYPE_GOOD | ||
); | ||
} else { | ||
$missing = implode(",", array_diff($this->mustInclude, $vals)); | ||
$this->result->addError( | ||
"$fullURL is excluding some settings: {$missing}" | ||
); | ||
} | ||
|
||
// All entries from must exclude should not be present | ||
if (empty(array_intersect($this->mustExclude, $vals))) { | ||
$missing = implode(",", $this->mustExclude); | ||
$this->result->addMessage( | ||
"$fullURL excludes all settings: {$missing}", | ||
ValidationResult::TYPE_GOOD | ||
); | ||
} else { | ||
$matched = implode(",", array_intersect($this->mustExclude, $vals)); | ||
$this->result->addError( | ||
"$fullURL is including some settings: {$matched}" | ||
); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
<?php | ||
|
||
namespace SilverStripe\EnvironmentCheck\Checks; | ||
|
||
use SilverStripe\Control\Director; | ||
use SilverStripe\Control\Controller; | ||
use Psr\Http\Message\ResponseInterface; | ||
use SilverStripe\Core\Config\Configurable; | ||
use SilverStripe\EnvironmentCheck\Traits\Fetcher; | ||
use SilverStripe\EnvironmentCheck\EnvironmentCheck; | ||
|
||
/** | ||
* Check that a given URL does not generate a session. | ||
* | ||
* @author Adrian Humphreys | ||
* @package environmentcheck | ||
*/ | ||
class SessionCheck implements EnvironmentCheck | ||
{ | ||
use Configurable; | ||
use Fetcher; | ||
|
||
/** | ||
* URL to check | ||
* | ||
* @var string | ||
*/ | ||
protected $url; | ||
|
||
/** | ||
* Set up check with URL | ||
* | ||
* @param string $url The route, excluding the domain | ||
* @inheritdoc | ||
*/ | ||
public function __construct($url = '') | ||
{ | ||
$this->url = $url; | ||
$this->clientConfig = [ | ||
'base_uri' => Director::absoluteBaseURL(), | ||
'timeout' => 10.0, | ||
]; | ||
} | ||
|
||
/** | ||
* Check that the response for URL does not create a session | ||
* | ||
* @return array | ||
*/ | ||
public function check() | ||
{ | ||
$response = $this->fetchResponse($this->url); | ||
$cookie = $this->getCookie($response); | ||
$fullURL = Controller::join_links(Director::absoluteBaseURL(), $this->url); | ||
|
||
if ($cookie) { | ||
return [ | ||
EnvironmentCheck::ERROR, | ||
"Sessions are being set for {$fullURL} : Set-Cookie => " . $cookie, | ||
]; | ||
} | ||
return [ | ||
EnvironmentCheck::OK, | ||
"Sessions are not being created for {$fullURL} 👍", | ||
]; | ||
} | ||
|
||
/** | ||
* Get PHPSESSID or SECSESSID cookie set from the response if it exists. | ||
* | ||
* @param ResponseInterface $response | ||
* @return string|null Cookie contents or null if it doesn't exist | ||
*/ | ||
public function getCookie(ResponseInterface $response) | ||
{ | ||
$result = null; | ||
$cookies = $response->getHeader('Set-Cookie'); | ||
|
||
foreach ($cookies as $cookie) { | ||
if (strpos($cookie, 'SESSID') !== false) { | ||
$result = $cookie; | ||
} | ||
} | ||
return $result; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
<?php | ||
|
||
namespace SilverStripe\EnvironmentCheck\Traits; | ||
|
||
use GuzzleHttp\Client; | ||
use Psr\Http\Message\ResponseInterface; | ||
|
||
/** | ||
* Simple helper for fetching responses using Guzzle client. | ||
* | ||
* @package environmentcheck | ||
*/ | ||
trait Fetcher | ||
{ | ||
/** | ||
* Configuration for the Guzzle client | ||
* | ||
* @var array | ||
*/ | ||
protected $clientConfig = []; | ||
|
||
/** | ||
* Merges configuration arrays and returns the result | ||
* | ||
* @param array $extraConfig | ||
* @return array | ||
*/ | ||
private function getClientConfig(array $extraConfig = []) | ||
{ | ||
return array_merge($this->clientConfig, $extraConfig); | ||
} | ||
|
||
/** | ||
* Fetch a response for a URL using Guzzle client. | ||
* | ||
* @param string $url | ||
* @param array|null $extraConfig Extra configuration | ||
* @return ResponseInterface | ||
*/ | ||
public function fetchResponse(string $url, array $extraConfig = []) | ||
{ | ||
$config = $this->getClientConfig($extraConfig); | ||
$client = new Client($config); | ||
return $client->get($url); | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Scalar typehints aren't PHP 5.6 friendly =)