From b42ac5d936ebc3399cc23a583d00a4ae2304d977 Mon Sep 17 00:00:00 2001 From: Ben Croker Date: Sat, 7 Oct 2023 09:53:32 +0200 Subject: [PATCH] Add `TestsService::EVENT_BEFORE_RUN_TESTS` event --- .github/workflows/create-release.yml | 21 +++++++ CHANGELOG.md | 93 +++++++++++++++++++++++++--- composer.json | 90 +++++++++++++-------------- src/Sherlock.php | 28 +++++---- src/events/RunTestsEvent.php | 17 +++++ src/services/ScansService.php | 25 ++++---- src/services/TestsService.php | 60 +++++++++++------- 7 files changed, 231 insertions(+), 103 deletions(-) create mode 100644 .github/workflows/create-release.yml create mode 100644 src/events/RunTestsEvent.php diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml new file mode 100644 index 0000000..8e33492 --- /dev/null +++ b/.github/workflows/create-release.yml @@ -0,0 +1,21 @@ +name: Create Release +run-name: Create release for ${{ github.event.client_payload.version }} + +on: + repository_dispatch: + types: + - craftcms/new-release + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: ncipollo/release-action@v1 + with: + body: ${{ github.event.client_payload.notes }} + makeLatest: ${{ github.event.client_payload.latest }} + name: ${{ github.event.client_payload.version }} + prerelease: ${{ github.event.client_payload.prerelease }} + tag: ${{ github.event.client_payload.tag }} diff --git a/CHANGELOG.md b/CHANGELOG.md index caac38a..c9cae7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,82 +1,121 @@ # Changelog +## 4.4.0 - Unreleased + +### Added + +- Added the `TestsService::EVENT_BEFORE_RUN_TESTS` event that can be used to configure the Guzzle client ([#43](https://github.com/putyourlightson/craft-sherlock/issues/43)). + ## 4.3.0 - 2023-01-02 + ### Added -- Added the `BaseIntegration::BEFORE_RUN_INTEGRATION` event that can be used to modify the configuration or cancel the running of an integration. + +- Added the `BaseIntegration::BEFORE_RUN_INTEGRATION` event that can be used to modify the configuration or cancel the running of an integration. ## 4.2.3 - 2022-12-22 + ### Changed + - The front-end HTTPS redirect test no longer results in an error if the web server blocks insecure requests. ## 4.2.2 - 2022-12-15 + ### Changed + - Updated the supported PHP version test to include 8.2. ## 4.2.1 - 2022-12-07 + ### Changed + - The Control Panel test no longer results in an error if the web server blocks insecure requests. - Updated the supported PHP version test to list the most recent 8.x versions. - Changed the Rollbar integration to reference the config service environment instead of the environment constant. ### Fixed + - Fixed a broken changelog link in the Craft updates test. ## 4.2.0 - 2022-09-01 + ### Changed + - Changed the HTTP error code from `503` to `403` when access to the site is denied ([#36](https://github.com/putyourlightson/craft-sherlock/issues/36)). ### Removed + - Removed the test for `Expect-CT` headers, since it became obsolete in June 2021 ([source](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Expect-CT#browser_compatibility)). ## 4.1.0 - 2022-06-03 + ### Added + - Added a test for the `Send Powered By Header` config setting. ### Changed + - Improved the `Strict-Transport-Security Header` test. ## 4.0.1 - 2022-05-09 + ### Changed + - Element queries are now deferred, avoiding potential issues with element queries being executed before Craft has fully initialised. ## 4.0.0 - 2022-05-04 + ### Added + - Added compatibility with Craft 4. - Added `Referrer-Policy` to default headers. ## 3.1.4 - 2022-01-13 + ### Fixed + - Fixed a bug that was throwing an exception on the settings page in versions of Craft less than 3.6.0 ([#33](https://github.com/putyourlightson/craft-sherlock/issues/33)). ## 3.1.3 - 2021-06-08 + ### Fixed + - Fixed a bug in which the plugin migration that adds the site ID column could be ignored in rare cases ([#31](https://github.com/putyourlightson/craft-sherlock/issues/31)). ## 3.1.2 - 2021-03-30 + ### Changed + - Changed the PHP Composer Version test to only compare the minor version and not the patch version. ### Fixed + - Fixed a bug in which control panel alerts were being overwritten instead of merged ([#27](https://github.com/putyourlightson/craft-sherlock/issues/27)). ## 3.1.1 - 2021-02-03 + ### Fixed + - Fixed a bug in which the Content Security Policy meta tag was not being recognised if it contained line breaks ([#23](https://github.com/putyourlightson/craft-sherlock/issues/23)). - Fixed a bug in which the `sherlock/scans/run-scan` action was requiring the user to be logged in ([#24](https://github.com/putyourlightson/craft-sherlock/issues/24)). ## 3.1.0 - 2021-01-26 + ### Added + - Added the PHP Composer Version test. ### Fixed + - Fixed an exception that was being thrown when one of the files being checked did not exist ([#21](https://github.com/putyourlightson/craft-sherlock/issues/21)). ## 3.0.0 - 2021-01-20 + ### Added + - Added Lite, Plus and Pro editions. -- Added integration with [Bugsnag](https://bugsnag.com/). -- Added integration with [Rollbar](https://rollbar.com/). -- Added integration with [Sentry](https://sentry.io/). +- Added integration with [Bugsnag](https://bugsnag.com/). +- Added integration with [Rollbar](https://rollbar.com/). +- Added integration with [Sentry](https://sentry.io/). - Added multi site functionality for security scans. - Added the ability to add a Content Security Policy in the plugin settings. - Added the ability to add HTTP Headers in the plugin settings. @@ -94,109 +133,149 @@ - Added unit tests. ### Changed + - Changed the HTTPS tests to ensure that an encrypted HTTPS connection is required. - Changed the file and folder permissions test criteria. - Non-critical Craft and plugin updates now display warnings instead of failures in high security mode. -- The `X-XSS-Protection` header now only display a warning instead of a failure ([reasoning](https://scotthelme.co.uk/security-headers-updates/#removing-the-x-xss-protection-header)). +- The `X-XSS-Protection` header now only display a warning instead of a failure ([reasoning](https://scotthelme.co.uk/security-headers-updates/#removing-the-x-xss-protection-header)). - Renamed "Live Mode" to "Monitoring". - Improved test icons, explanations, thresholds and documentation links. ### Fixed + - Fixed wording of user session duration test. - Fixed output of default file permissions test. ### Removed + - Removed the plugin vulnerabilities JSON feed. - Removed the secret key. ## 2.3.0 - 2020-12-26 + ### Added + - Added prevent user enumeration test. - Added sanitize SVG uploads test. ### Changed + - Minor UI improvements. - Removed security key test. ## 2.2.5 - 2020-08-24 + ### Fixed + - Fixed `X-XSS-Protection` case issue ([#16](https://github.com/putyourlightson/craft-sherlock/issues/16)). ## 2.2.4 - 2020-07-02 + ### Changed + - Minor UI improvements. ### Fixed + - Fixed `X-XSS-Protection` test. ## 2.2.3 - 2020-07-02 + ### Changed + - Headers are now correctly detected regardless of whether in normal or lower case. - Headers are now stripped of tags to ensure they are safe to output to the browser. ## 2.2.2 - 2020-03-31 + ### Fixed + - Fixed a bug in which scans could throw an error with recent versions of Craft ([#13](https://github.com/putyourlightson/craft-sherlock/issues/13)). ## 2.2.1 - 2020-03-26 + ### Fixed + - Fixed a bug when running a scan and using Postgres ([#12](https://github.com/putyourlightson/craft-sherlock/issues/12)). ## 2.2.0 - 2019-09-30 + ### Added + - Added the ability to add `*` and `?` wildcards to restricted IP addresses ([#11](https://github.com/putyourlightson/craft-sherlock/issues/11)). ### Fixed + - Fixed a bug in the restriction of IP addresses on the front-end. ## 2.1.3 - 2019-09-02 + ### Fixed + - Fixed an error that could occur when running a scan using the API key ([#10](https://github.com/putyourlightson/craft-sherlock/issues/10)). ## 2.1.2 - 2019-06-19 + ### Fixed + - Fixed migration issue that could happen with project config ([#7](https://github.com/putyourlightson/craft-sherlock/issues/7)). - Fixed `defaultTokenDuration` test that was failing incorrectly ([#8](https://github.com/putyourlightson/craft-sherlock/issues/8)). ## 2.1.1 - 2019-06-14 + ### Changed + - Improved spacing and info tooltip sizing. -- Changed duration settings from intervals to seconds. +- Changed duration settings from intervals to seconds. ### Fixed + - Fixed duration tests that were failing incorrectly ([#7](https://github.com/putyourlightson/craft-sherlock/issues/7)). ## 2.1.0 - 2019-02-11 + ### Added + - Added welcome screen after the plugin is installed. - Added system email to default plugin settings. - Added environment variables to API settings. - Added config warnings to settings. ### Changed + - Changed minimum requirement of Craft to version 3.1.0. ### Fixed + - Fixed redirect to settings screen after the plugin is installed. ## 2.0.4 - 2019-02-07 + ### Fixed + - Fixed check for redirect of insecure front-end URL. ## 2.0.3 - 2019-02-07 + ### Changed -- Improved feedback for insecure front-end URL connection errors. + +- Improved feedback for insecure front-end URL connection errors. - Improved formatting of test results. ## 2.0.2 - 2019-01-03 + ### Fixed + - Fixed CMS and plugin update detection. - Fixed a bug where restricted IP addresses were not being parsed correctly in some server environments. ## 2.0.1 - 2018-07-12 + ### Changed + - Changed plugin icon. - Plugin does not interfere with console requests. ### Fixed + - Fixed a bug where restricted IP addresses were not being checked correctly on servers that use carriage returns in new lines. diff --git a/composer.json b/composer.json index 9225964..790e7fe 100644 --- a/composer.json +++ b/composer.json @@ -1,48 +1,48 @@ { - "name": "putyourlightson/craft-sherlock", - "description": "Security scanner and monitor to keep your site and CMS secure.", - "version": "4.3.0", - "type": "craft-plugin", - "homepage": "https://putyourlightson.com/plugins/sherlock", - "license": "proprietary", - "keywords": [ - "craft", - "cms", - "craftcms", - "security", - "scanner", - "secure" - ], - "require": { - "php": "^8.0.2", - "craftcms/cms": "^4.0.0" - }, - "require-dev": { - "codeception/codeception": "^4.1.29", - "codeception/module-asserts": "^1.3.1", - "codeception/module-yii2": "^1.1.5", - "craftcms/ecs": "dev-main", - "craftcms/phpstan": "dev-main", - "yiisoft/yii2-redis": "^2.0", - "vlucas/phpdotenv": "^5.4.1" - }, - "autoload": { - "psr-4": { - "putyourlightson\\sherlock\\": "src/" - } - }, - "support": { - "docs": "https://putyourlightson.com/plugins/sherlock", - "source": "https://github.com/putyourlightson/craft-sherlock", - "issues": "https://github.com/putyourlightson/craft-sherlock/issues" - }, - "extra": { - "name": "Sherlock", - "handle": "sherlock", - "developer": "PutYourLightsOn", - "developerUrl": "https://putyourlightson.com/", - "documentationUrl": "https://putyourlightson.com/plugins/sherlock", - "changelogUrl": "https://raw.githubusercontent.com/putyourlightson/craft-sherlock/v4/CHANGELOG.md", - "class": "putyourlightson\\sherlock\\Sherlock" + "name": "putyourlightson/craft-sherlock", + "description": "Security scanner and monitor to keep your site and CMS secure.", + "version": "4.4.0", + "type": "craft-plugin", + "homepage": "https://putyourlightson.com/plugins/sherlock", + "license": "proprietary", + "keywords": [ + "craft", + "cms", + "craftcms", + "security", + "scanner", + "secure" + ], + "require": { + "php": "^8.0.2", + "craftcms/cms": "^4.0.0" + }, + "require-dev": { + "codeception/codeception": "^4.1.29", + "codeception/module-asserts": "^1.3.1", + "codeception/module-yii2": "^1.1.5", + "craftcms/ecs": "dev-main", + "craftcms/phpstan": "dev-main", + "yiisoft/yii2-redis": "^2.0", + "vlucas/phpdotenv": "^5.4.1" + }, + "autoload": { + "psr-4": { + "putyourlightson\\sherlock\\": "src/" } + }, + "support": { + "docs": "https://putyourlightson.com/plugins/sherlock", + "source": "https://github.com/putyourlightson/craft-sherlock", + "issues": "https://github.com/putyourlightson/craft-sherlock/issues" + }, + "extra": { + "name": "Sherlock", + "handle": "sherlock", + "developer": "PutYourLightsOn", + "developerUrl": "https://putyourlightson.com/", + "documentationUrl": "https://putyourlightson.com/plugins/sherlock", + "changelogUrl": "https://raw.githubusercontent.com/putyourlightson/craft-sherlock/v4/CHANGELOG.md", + "class": "putyourlightson\\sherlock\\Sherlock" + } } diff --git a/src/Sherlock.php b/src/Sherlock.php index edded4a..5a309df 100755 --- a/src/Sherlock.php +++ b/src/Sherlock.php @@ -199,13 +199,15 @@ protected function createSettingsModel(): SettingsModel /** * Registers variables. */ - private function _registerVariables() + private function _registerVariables(): void { - Event::on(CraftVariable::class, CraftVariable::EVENT_INIT, function(Event $event) { - /** @var CraftVariable $variable */ - $variable = $event->sender; - $variable->set('sherlock', SherlockVariable::class); - }); + Event::on(CraftVariable::class, CraftVariable::EVENT_INIT, + function(Event $event) { + /** @var CraftVariable $variable */ + $variable = $event->sender; + $variable->set('sherlock', SherlockVariable::class); + } + ); } /** @@ -231,7 +233,7 @@ private function _registerLogTarget(): void /** * Registers Twig extensions. */ - private function _registerTwigExtensions() + private function _registerTwigExtensions(): void { Craft::$app->getView()->registerTwigExtension(new SherlockTwigExtension()); } @@ -239,7 +241,7 @@ private function _registerTwigExtensions() /** * Registers the content security policy. */ - private function _registerContentSecurityPolicy() + private function _registerContentSecurityPolicy(): void { Event::on(Application::class, Application::EVENT_INIT, function() { @@ -251,7 +253,7 @@ function() { /** * Registers CP URL rules event. */ - private function _registerCpUrlRules() + private function _registerCpUrlRules(): void { Event::on(UrlManager::class, UrlManager::EVENT_REGISTER_CP_URL_RULES, function(RegisterUrlRulesEvent $event) { @@ -260,8 +262,8 @@ function(RegisterUrlRulesEvent $event) { // Merge so that settings controller action comes first (important!) $event->rules = array_merge([ - 'settings/plugins/sherlock' => 'sherlock/settings/edit', - ], + 'settings/plugins/sherlock' => 'sherlock/settings/edit', + ], $event->rules ); } @@ -271,7 +273,7 @@ function(RegisterUrlRulesEvent $event) { /** * Registers CP alerts. */ - private function _registerCpAlerts() + private function _registerCpAlerts(): void { Event::on(Cp::class, Cp::EVENT_REGISTER_ALERTS, function(RegisterCpAlertsEvent $event) { @@ -283,7 +285,7 @@ function(RegisterCpAlertsEvent $event) { /** * Registers after install event. */ - private function _registerAfterInstallEvent() + private function _registerAfterInstallEvent(): void { Event::on(Plugins::class, Plugins::EVENT_AFTER_INSTALL_PLUGIN, function(PluginEvent $event) { diff --git a/src/events/RunTestsEvent.php b/src/events/RunTestsEvent.php new file mode 100644 index 0000000..d8b44fa --- /dev/null +++ b/src/events/RunTestsEvent.php @@ -0,0 +1,17 @@ +getSites()->getCurrentSite()->id; + /** @var ScanRecord|null $scanRecord */ $scanRecord = ScanRecord::find() ->where(['siteId' => $siteId]) ->orderBy('dateCreated desc') @@ -47,7 +47,8 @@ public function getAllScans(int $siteId = null, int $offsetId = 0): array $siteId = $siteId ?: Craft::$app->getSites()->getCurrentSite()->id; // Get records offset by ID - $records = ScanRecord::find() + /** @var ScanRecord[] $scanRecords */ + $scanRecords = ScanRecord::find() ->where(['siteId' => $siteId]) ->andWhere('id > :offsetId', [':offsetId' => $offsetId]) ->orderBy('dateCreated desc') @@ -55,9 +56,9 @@ public function getAllScans(int $siteId = null, int $offsetId = 0): array $scans = []; - foreach ($records as $record) { + foreach ($scanRecords as $scanRecord) { $scan = new ScanModel(); - $scan->setAttributes($record->getAttributes(), false); + $scan->setAttributes($scanRecord->getAttributes(), false); $scans[] = $scan; } @@ -67,7 +68,7 @@ public function getAllScans(int $siteId = null, int $offsetId = 0): array /** * Runs a scan. */ - public function runScan(int $siteId = null, callable $setProgressHandler = null) + public function runScan(int $siteId = null, callable $setProgressHandler = null): void { if ($siteId !== null) { Craft::$app->getSites()->setCurrentSite($siteId); @@ -155,7 +156,7 @@ public function runScan(int $siteId = null, callable $setProgressHandler = null) * * @param ScanModel $scanModel */ - private function _sendNotifications(ScanModel $scanModel) + private function _sendNotifications(ScanModel $scanModel): void { // Check failed scan against last scan $lastScan = $this->getLastScan(); @@ -191,17 +192,13 @@ private function _sendNotifications(ScanModel $scanModel) /** * Sends and logs notification email. */ - private function _sendLogNotificationEmail(string $subject, string $body, string $log) + private function _sendLogNotificationEmail(string $subject, string $body, string $log): void { - $mailer = Craft::$app->getMailer(); - - /** @var Message $message*/ - $message = $mailer->compose() + Craft::$app->mailer->compose() ->setTo(Sherlock::$plugin->settings->notificationEmailAddresses) ->setSubject(Craft::$app->getSites()->getCurrentSite()->name . ' - ' . $subject) - ->setHtmlBody($body . UrlHelper::cpUrl('sherlock')); - - $message->send(); + ->setHtmlBody($body . UrlHelper::cpUrl('sherlock')) + ->send(); Sherlock::$plugin->log($log . Sherlock::$plugin->settings->notificationEmailAddresses); } diff --git a/src/services/TestsService.php b/src/services/TestsService.php index 2b4a4dc..3ddc3e5 100755 --- a/src/services/TestsService.php +++ b/src/services/TestsService.php @@ -16,8 +16,10 @@ use GuzzleHttp\Client; use GuzzleHttp\Exception\GuzzleException; use GuzzleHttp\TransferStats; +use putyourlightson\sherlock\events\RunTestsEvent; use putyourlightson\sherlock\models\TestModel; use putyourlightson\sherlock\Sherlock; +use yii\base\Event; use yii\web\NotFoundHttpException; /** @@ -27,6 +29,11 @@ */ class TestsService extends Component { + /** + * @event RunTestsEvent + */ + public const EVENT_BEFORE_RUN_TESTS = 'beforeRunTests'; + /** * @var Client|null */ @@ -126,7 +133,7 @@ public function getTestNames(): array /** * Performs preps before running tests. */ - public function beforeRunTests() + public function beforeRunTests(): void { // Ensure we only run this method once if ($this->client !== null) { @@ -143,6 +150,11 @@ public function beforeRunTests() // Get the current site's base URL if not already set (by unit tests) $this->siteUrl = $this->siteUrl ?? Craft::$app->getSites()->getCurrentSite()->getBaseUrl(); + $event = new RunTestsEvent([ + 'client' => $this->client, + ]); + Event::trigger(static::class, self::EVENT_BEFORE_RUN_TESTS, $event); + try { $response = $this->client->get($this->siteUrl); $this->siteUrlResponse['headers'] = $response->getHeaders(); @@ -175,13 +187,13 @@ public function runTest(string $test): TestModel foreach ($this->updates->cms->releases as $release) { if ($release->critical) { $criticalCraftUpdates[] = Html::a( - $release->version, - 'https://github.com/craftcms/cms/blob/develop/CHANGELOG.md#' . str_replace('.', '-', $release->version) - ) . Html::tag( - 'span', - 'Version ' . $release->version . ' is a critical update, released on ' . $this->_formatDate($release->date), - ['class' => 'info'] - ); + $release->version, + 'https://github.com/craftcms/cms/blob/develop/CHANGELOG.md#' . str_replace('.', '-', $release->version) + ) . Html::tag( + 'span', + 'Version ' . $release->version . ' is a critical update, released on ' . $this->_formatDate($release->date), + ['class' => 'info'] + ); } } @@ -203,14 +215,14 @@ public function runTest(string $test): TestModel foreach ($update->releases as $release) { if ($release->critical) { $criticalPluginUpdates[] = Html::a( - $plugin->name, - $plugin->changelogUrl, - ['target' => '_blank'], - ) . Html::tag( - 'span', - 'Version ' . $release->version . ' is a critical update, released on ' . $this->_formatDate($release->date), - ['class' => 'info'] - ); + $plugin->name, + $plugin->changelogUrl, + ['target' => '_blank'], + ) . Html::tag( + 'span', + 'Version ' . $release->version . ' is a critical update, released on ' . $this->_formatDate($release->date), + ['class' => 'info'] + ); } } } @@ -244,14 +256,14 @@ public function runTest(string $test): TestModel if ($plugin !== null) { $pluginUpdates[] = Html::a( - $plugin->name, - $plugin->changelogUrl, - ['target' => '_blank'], - ) . Html::tag( - 'span', - 'Local version ' . $plugin->version . ' is ' . count($update->releases) . ' release' . (count($update->releases) != 1 ? 's' : '') . ' behind latest version ' . $latestRelease->version . ', released on ' . $this->_formatDate($latestRelease->date), - ['class' => 'info'] - ); + $plugin->name, + $plugin->changelogUrl, + ['target' => '_blank'], + ) . Html::tag( + 'span', + 'Local version ' . $plugin->version . ' is ' . count($update->releases) . ' release' . (count($update->releases) != 1 ? 's' : '') . ' behind latest version ' . $latestRelease->version . ', released on ' . $this->_formatDate($latestRelease->date), + ['class' => 'info'] + ); } } }