From 650696ac148ce1be7d07ffeb455aadbdcbed5893 Mon Sep 17 00:00:00 2001 From: tatarbj <tatarbj@649590.no-reply.drupal.org> Date: Wed, 12 Jun 2019 08:30:37 -0500 Subject: [PATCH] Issue #3045273 by tatarbj, heddn, mlhess, catch: Add real endpoint after drupal.org provides a live feed --- automatic_updates.info.yml | 2 +- automatic_updates.install | 4 +- automatic_updates.links.menu.yml | 2 +- automatic_updates.module | 2 +- src/Form/SettingsForm.php | 4 +- src/Services/AutomaticUpdatesPsa.php | 109 ++++++++++-------- .../automatic-updates-psa-notify.html.twig | 3 +- .../src/Controller/JsonTestController.php | 83 ++++++++----- tests/src/Functional/AutomaticUpdatesTest.php | 11 +- tests/src/Functional/NotifyTest.php | 6 +- 10 files changed, 129 insertions(+), 97 deletions(-) diff --git a/automatic_updates.info.yml b/automatic_updates.info.yml index fdcd5bb618..79f8401cb5 100644 --- a/automatic_updates.info.yml +++ b/automatic_updates.info.yml @@ -1,6 +1,6 @@ name: 'Automatic Updates' type: module -description: 'Display public service announcements and verify readiness for applying automatic updates to the site.' +description: 'Display Public service announcements and verify readiness for applying automatic updates to the site.' core: 8.x package: 'Security' configure: automatic_updates.settings diff --git a/automatic_updates.install b/automatic_updates.install index 25965a7ca7..3c54d76b64 100644 --- a/automatic_updates.install +++ b/automatic_updates.install @@ -76,7 +76,7 @@ function _automatic_updates_checker_requirements(array &$requirements) { } /** - * Display requirements from public service announcements. + * Display requirements from Public service announcements. * * @param array $requirements * The requirements array. @@ -89,7 +89,7 @@ function _automatic_updates_psa_requirements(array &$requirements) { $psa = \Drupal::service('automatic_updates.psa'); $messages = $psa->getPublicServiceMessages(); $requirements['automatic_updates_psa'] = [ - 'title' => t('<a href="@link">Drupal public service announcements</a>', ['@link' => 'https://www.drupal.org/docs/8/update/automatic-updates#psas']), + 'title' => t('<a href="@link">Public service announcements</a>', ['@link' => 'https://www.drupal.org/docs/8/update/automatic-updates#psas']), 'severity' => REQUIREMENT_OK, 'value' => t('No announcements requiring attention.'), ]; diff --git a/automatic_updates.links.menu.yml b/automatic_updates.links.menu.yml index 4ebb874894..bc60db505b 100644 --- a/automatic_updates.links.menu.yml +++ b/automatic_updates.links.menu.yml @@ -1,5 +1,5 @@ automatic_updates.settings: title: 'Automatic updates' route_name: automatic_updates.settings - description: 'Configure public service announcement notifications' + description: 'Configure Public service announcement notifications' parent: system.admin_config_system diff --git a/automatic_updates.module b/automatic_updates.module index c95de6fb40..446d8e99d8 100644 --- a/automatic_updates.module +++ b/automatic_updates.module @@ -35,7 +35,7 @@ function automatic_updates_page_top(array &$page_top) { $psa = \Drupal::service('automatic_updates.psa'); $messages = $psa->getPublicServiceMessages(); if ($messages) { - \Drupal::messenger()->addError(t('Drupal public service announcements:')); + \Drupal::messenger()->addError(t('Public service announcements:')); foreach ($messages as $message) { \Drupal::messenger()->addError($message); } diff --git a/src/Form/SettingsForm.php b/src/Form/SettingsForm.php index 9f38e66a95..96b9a082d4 100644 --- a/src/Form/SettingsForm.php +++ b/src/Form/SettingsForm.php @@ -71,12 +71,12 @@ class SettingsForm extends ConfigFormBase { ]; $form['enable_psa'] = [ '#type' => 'checkbox', - '#title' => $this->t('Show public service announcements on administrative pages.'), + '#title' => $this->t('Show Public service announcements on administrative pages.'), '#default_value' => $config->get('enable_psa'), ]; $form['notify'] = [ '#type' => 'checkbox', - '#title' => $this->t('Send email notifications for public service announcements.'), + '#title' => $this->t('Send email notifications for Public service announcements.'), '#default_value' => $config->get('notify'), '#description' => $this->t('The email addresses listed in <a href="@update_manager">update manager settings</a> will be notified.', ['@update_manager' => Url::fromRoute('update.settings')->toString()]), ]; diff --git a/src/Services/AutomaticUpdatesPsa.php b/src/Services/AutomaticUpdatesPsa.php index 820bc9955b..e0e0d12f8b 100644 --- a/src/Services/AutomaticUpdatesPsa.php +++ b/src/Services/AutomaticUpdatesPsa.php @@ -5,7 +5,6 @@ namespace Drupal\automatic_updates\Services; use Composer\Semver\VersionParser; use Drupal\Component\Datetime\TimeInterface; use Drupal\Component\Render\FormattableMarkup; -use Drupal\Component\Version\Constraint; use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\DependencyInjection\DependencySerializationTrait; @@ -114,7 +113,6 @@ class AutomaticUpdatesPsa implements AutomaticUpdatesPsaInterface { */ public function getPublicServiceMessages() { $messages = []; - if (!$this->config->get('enable_psa')) { return $messages; } @@ -140,10 +138,13 @@ class AutomaticUpdatesPsa implements AutomaticUpdatesPsaInterface { $json_payload = json_decode($response); if ($json_payload) { foreach ($json_payload as $json) { - if ($json->project === 'core') { - $this->coreParser($messages, $json); + if ($json->is_psa && ($json->type === 'core' || $this->isValidExtension($json->type, $json->project))) { + $messages[] = $this->message($json->title, $json->link); + } + elseif ($json->type === 'core') { + $this->parseConstraints($messages, $json, \Drupal::VERSION); } - else { + elseif ($this->isValidExtension($json->type, $json->project)) { $this->contribParser($messages, $json); } } @@ -163,27 +164,22 @@ class AutomaticUpdatesPsa implements AutomaticUpdatesPsaInterface { } /** - * Parse core project JSON version strings. + * Determine if extension exists and has a version string. * - * @param array $messages - * The messages array. - * @param object $json - * The JSON object. + * @param string $extension_type + * The extension type i.e. module, theme, profile. + * @param string $project_name + * The project. + * + * @return bool + * TRUE if extension exists, else FALSE. */ - protected function coreParser(array &$messages, $json) { - $parser = new VersionParser(); - array_walk($json->secure_versions, function (&$version) { - $version = '<' . $version; - }); - $version_string = implode('||', $json->secure_versions); - $psa_constraint = $parser->parseConstraints($version_string); - $core_constraint = $parser->parseConstraints(\Drupal::VERSION); - if ($psa_constraint->matches($core_constraint)) { - $messages[] = new FormattableMarkup('<a href=":url">:message</a>', [ - ':message' => $json->title, - ':url' => $json->link, - ]); + protected function isValidExtension($extension_type, $project_name) { + if (!property_exists($this, $extension_type)) { + $this->logger->error('Extension list of type "%extension" does not exist.', ['%extension' => $extension_type]); + return FALSE; } + return $this->{$extension_type}->exists($project_name) && !empty($this->{$extension_type}->getAllAvailableInfo()[$project_name]['version']); } /** @@ -195,46 +191,59 @@ class AutomaticUpdatesPsa implements AutomaticUpdatesPsaInterface { * The JSON object. */ protected function contribParser(array &$messages, $json) { - $extension_list = $json->type; - if (!property_exists($this, $extension_list)) { - $this->logger->error('Extension list of type "%extension" does not exist.', ['%extension' => $extension_list]); - return; - } - array_walk($json->secure_versions, function (&$version) { + $extension_version = $this->{$json->type}->getAllAvailableInfo()[$json->project]['version']; + $json->insecure = array_filter(array_map(function ($version) { if (substr($version, 0, 4) === \Drupal::CORE_COMPATIBILITY . '-') { - $version = substr($version, 4); - } - }); - foreach ($json->extensions as $extension_name) { - if ($this->{$extension_list}->exists($extension_name)) { - $extension = $this->{$extension_list}->getAllAvailableInfo()[$extension_name]; - if (empty($extension['version'])) { - continue; - } - $this->contribMessage($messages, $json, $extension['version']); + return substr($version, 4); } + }, $json->insecure)); + if (substr($extension_version, 0, 4) === \Drupal::CORE_COMPATIBILITY . '-') { + $extension_version = substr($extension_version, 4); } + $this->parseConstraints($messages, $json, $extension_version); } /** - * Add a contrib message PSA, if appropriate. + * Compare versions and add a message, if appropriate. * * @param array $messages * The messages array. * @param object $json * The JSON object. - * @param string $extension_version - * The extension version. + * @param string $current_version + * The current extension version. + * + * @throws \UnexpectedValueException */ - protected function contribMessage(array &$messages, $json, $extension_version) { - $version_string = implode('||', $json->secure_versions); - $constraint = new Constraint("<=$extension_version", \Drupal::CORE_COMPATIBILITY); - if (!$constraint->isCompatible($version_string)) { - $messages[] = new FormattableMarkup('<a href=":url">:message</a>', [ - ':message' => $json->title, - ':url' => $json->link, - ]); + protected function parseConstraints(array &$messages, $json, $current_version) { + $version_string = implode('||', $json->insecure); + if (empty($version_string)) { + return; + } + $parser = new VersionParser(); + $psa_constraint = $parser->parseConstraints($version_string); + $contrib_constraint = $parser->parseConstraints($current_version); + if ($psa_constraint->matches($contrib_constraint)) { + $messages[] = $this->message($json->title, $json->link); } } + /** + * Return a message. + * + * @param string $title + * The title. + * @param string $link + * The link. + * + * @return \Drupal\Component\Render\FormattableMarkup + * The PSA or SA message. + */ + protected function message($title, $link) { + return new FormattableMarkup('<a href=":url">:message</a>', [ + ':message' => $title, + ':url' => $link, + ]); + } + } diff --git a/templates/automatic-updates-psa-notify.html.twig b/templates/automatic-updates-psa-notify.html.twig index dd49aac63d..853df06fd5 100644 --- a/templates/automatic-updates-psa-notify.html.twig +++ b/templates/automatic-updates-psa-notify.html.twig @@ -21,12 +21,13 @@ See the <a href="{{ status_report }}">site status report page</a> for more information. {% endtrans %} </p> -<p>{{ 'Drupal public service announcements:'|t }}</p> +<p>{{ 'Public service announcements:'|t }}</p> <ul> {% for message in messages %} <li>{{ message }}</li> {% endfor %} </ul> +<p>To see all PSAs, visit <a href="https://www.drupal.org/security/psa" target="_blank">https://www.drupal.org/security/psa</a>.</p> <p> {% set settings_link = path('automatic_updates.settings') %} {% trans %} diff --git a/tests/modules/test_automatic_updates/src/Controller/JsonTestController.php b/tests/modules/test_automatic_updates/src/Controller/JsonTestController.php index c84d11d9e4..b646680580 100644 --- a/tests/modules/test_automatic_updates/src/Controller/JsonTestController.php +++ b/tests/modules/test_automatic_updates/src/Controller/JsonTestController.php @@ -19,77 +19,98 @@ class JsonTestController extends ControllerBase { public function json() { $feed = []; $feed[] = [ - 'title' => 'Critical Release - PSA-2019-02-19', - 'link' => 'https://www.drupal.org/psa-2019-02-19', - 'project' => 'core', - 'extensions' => [], - 'type' => 'module', - 'secure_versions' => [ - '7.99', - '8.10.99', - '8.9.99', - '8.8.99', - '8.7.99', - '8.6.99', - '8.5.99', + 'title' => 'Critical Release - SA-2019-02-19', + 'link' => 'https://www.drupal.org/sa-2019-02-19', + 'project' => 'drupal', + 'type' => 'core', + 'insecure' => [ + '7.65', + '8.5.14', + '8.5.14', + '8.6.13', + '8.7.0-alpha2', + '8.7.0-beta1', + '8.7.0-beta2', + '8.6.14', + '8.6.15', + '8.6.15', + '8.5.15', + '8.5.15', + '7.66', + '8.7.0', + \Drupal::VERSION, ], + 'is_psa' => '0', 'pubDate' => 'Tue, 19 Feb 2019 14:11:01 +0000', ]; $feed[] = [ 'title' => 'Critical Release - PSA-Really Old', 'link' => 'https://www.drupal.org/psa', - 'project' => 'core', - 'extensions' => [], - 'type' => 'module', - 'secure_versions' => [ - '7.0', - '8.4.0', - ], + 'project' => 'drupal', + 'type' => 'core', + 'is_psa' => '1', + 'insecure' => [], 'pubDate' => 'Tue, 19 Feb 2019 14:11:01 +0000', ]; $feed[] = [ 'title' => 'Node - Moderately critical - Access bypass - SA-CONTRIB-2019', 'link' => 'https://www.drupal.org/sa-contrib-2019', 'project' => 'node', - 'extensions' => ['node'], 'type' => 'module', - 'secure_versions' => ['7.x-7.22', '8.x-8.2.0'], + 'is_psa' => '0', + 'insecure' => ['7.x-7.22', '8.x-8.2.0'], 'pubDate' => 'Tue, 19 Mar 2019 12:50:00 +0000', ]; $feed[] = [ 'title' => 'Standard - Moderately critical - Access bypass - SA-CONTRIB-2019', 'link' => 'https://www.drupal.org/sa-contrib-2019', - 'project' => 'Standard Install Profile', - 'extensions' => ['standard'], + 'project' => 'standard', 'type' => 'profile', - 'secure_versions' => ['8.x-8.10.99'], + 'insecure' => ['8.x-8.6.13', '8.x-' . \Drupal::VERSION], + 'is_psa' => '0', 'pubDate' => 'Tue, 19 Mar 2019 12:50:00 +0000', ]; $feed[] = [ 'title' => 'Seven - Moderately critical - Access bypass - SA-CONTRIB-2019', 'link' => 'https://www.drupal.org/sa-contrib-2019', 'project' => 'seven', - 'extensions' => ['seven'], 'type' => 'theme', - 'secure_versions' => ['8.x-8.10.99'], + 'is_psa' => '0', + 'insecure' => ['8.x-8.7.0', '8.x-' . \Drupal::VERSION], 'pubDate' => 'Tue, 19 Mar 2019 12:50:00 +0000', ]; $feed[] = [ 'title' => 'Foobar - Moderately critical - Access bypass - SA-CONTRIB-2019', 'link' => 'https://www.drupal.org/sa-contrib-2019', 'project' => 'foobar', - 'extensions' => ['foobar'], 'type' => 'foobar', - 'secure_versions' => ['8.x-1.2'], + 'is_psa' => '1', + 'insecure' => [], 'pubDate' => 'Tue, 19 Mar 2019 12:50:00 +0000', ]; $feed[] = [ 'title' => 'Token - Moderately critical - Access bypass - SA-CONTRIB-2019', 'link' => 'https://www.drupal.org/sa-contrib-2019', 'project' => 'token', - 'extensions' => ['token'], 'type' => 'module', - 'secure_versions' => ['7.x-1.7', '8.x-1.5'], + 'is_psa' => '0', + 'insecure' => ['7.x-1.7', '8.x-1.4'], + 'pubDate' => 'Tue, 19 Mar 2019 12:50:00 +0000', + ]; + $feed[] = [ + 'title' => 'Views - Moderately critical - Access bypass - SA-CONTRIB-2019', + 'link' => 'https://www.drupal.org/sa-contrib-2019', + 'project' => 'views', + 'type' => 'module', + 'insecure' => [ + '7.x-3.16', + '7.x-3.17', + '7.x-3.18', + '7.x-3.19', + '7.x-3.19', + '8.x-8.7.0', + ], + 'is_psa' => '0', 'pubDate' => 'Tue, 19 Mar 2019 12:50:00 +0000', ]; return new JsonResponse($feed); diff --git a/tests/src/Functional/AutomaticUpdatesTest.php b/tests/src/Functional/AutomaticUpdatesTest.php index cbd4cb924b..d01fc4c2db 100644 --- a/tests/src/Functional/AutomaticUpdatesTest.php +++ b/tests/src/Functional/AutomaticUpdatesTest.php @@ -51,15 +51,16 @@ class AutomaticUpdatesTest extends BrowserTestBase { ->set('psa_endpoint', $end_point) ->save(); $this->drupalGet(Url::fromRoute('system.admin')); - $this->assertSession()->pageTextContains('Critical Release - PSA-2019-02-19'); - $this->assertSession()->pageTextNotContains('Critical Release - PSA-Really Old'); - $this->assertSession()->pageTextNotContains('Node - Moderately critical - Access bypass - SA-CONTRIB-2019'); + $this->assertSession()->pageTextContains('Critical Release - SA-2019-02-19'); + $this->assertSession()->pageTextContains('Critical Release - PSA-Really Old'); $this->assertSession()->pageTextContains('Seven - Moderately critical - Access bypass - SA-CONTRIB-2019'); $this->assertSession()->pageTextContains('Standard - Moderately critical - Access bypass - SA-CONTRIB-2019'); + $this->assertSession()->pageTextNotContains('Node - Moderately critical - Access bypass - SA-CONTRIB-2019'); + $this->assertSession()->pageTextNotContains('Views - Moderately critical - Access bypass - SA-CONTRIB-2019'); // Test site status report. $this->drupalGet(Url::fromRoute('system.status')); - $this->assertSession()->pageTextContains('3 urgent announcements require your attention:'); + $this->assertSession()->pageTextContains('4 urgent announcements require your attention:'); // Test cache. $end_point = $this->buildUrl(Url::fromRoute('test_automatic_updates.json_test_denied_controller')); @@ -67,7 +68,7 @@ class AutomaticUpdatesTest extends BrowserTestBase { ->set('psa_endpoint', $end_point) ->save(); $this->drupalGet(Url::fromRoute('system.admin')); - $this->assertSession()->pageTextContains('Critical Release - PSA-2019-02-19'); + $this->assertSession()->pageTextContains('Critical Release - SA-2019-02-19'); // Test transmit errors with JSON endpoint. drupal_flush_all_caches(); diff --git a/tests/src/Functional/NotifyTest.php b/tests/src/Functional/NotifyTest.php index dfb394ccb8..77b63a6e49 100644 --- a/tests/src/Functional/NotifyTest.php +++ b/tests/src/Functional/NotifyTest.php @@ -60,14 +60,14 @@ class NotifyTest extends BrowserTestBase { public function testSendMail() { // Test PSAs on admin pages. $this->drupalGet(Url::fromRoute('system.admin')); - $this->assertSession()->pageTextContains('Critical Release - PSA-2019-02-19'); + $this->assertSession()->pageTextContains('Critical Release - SA-2019-02-19'); // Email should be sent. $notify = $this->container->get('automatic_updates.psa_notify'); $notify->send(); $this->assertCount(1, $this->getMails()); - $this->assertMailString('subject', '3 urgent Drupal announcements require your attention', 1); - $this->assertMailString('body', 'Critical Release - PSA-2019-02-19', 1); + $this->assertMailString('subject', '4 urgent Drupal announcements require your attention', 1); + $this->assertMailString('body', 'Critical Release - SA-2019-02-19', 1); // No email should be sent if PSA's are disabled. $this->container->get('state')->set('system.test_mail_collector', []); -- GitLab