Skip to content
Snippets Groups Projects
Commit 650696ac authored by Balazs Janos Tatar's avatar Balazs Janos Tatar Committed by Lucas Hedding
Browse files

Issue #3045273 by tatarbj, heddn, mlhess, catch: Add real endpoint after...

Issue #3045273 by tatarbj, heddn, mlhess, catch: Add real endpoint after drupal.org provides a live feed
parent 39bf8c10
No related branches found
No related tags found
No related merge requests found
name: 'Automatic Updates' name: 'Automatic Updates'
type: module 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 core: 8.x
package: 'Security' package: 'Security'
configure: automatic_updates.settings configure: automatic_updates.settings
......
...@@ -76,7 +76,7 @@ function _automatic_updates_checker_requirements(array &$requirements) { ...@@ -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 * @param array $requirements
* The requirements array. * The requirements array.
...@@ -89,7 +89,7 @@ function _automatic_updates_psa_requirements(array &$requirements) { ...@@ -89,7 +89,7 @@ function _automatic_updates_psa_requirements(array &$requirements) {
$psa = \Drupal::service('automatic_updates.psa'); $psa = \Drupal::service('automatic_updates.psa');
$messages = $psa->getPublicServiceMessages(); $messages = $psa->getPublicServiceMessages();
$requirements['automatic_updates_psa'] = [ $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, 'severity' => REQUIREMENT_OK,
'value' => t('No announcements requiring attention.'), 'value' => t('No announcements requiring attention.'),
]; ];
......
automatic_updates.settings: automatic_updates.settings:
title: 'Automatic updates' title: 'Automatic updates'
route_name: automatic_updates.settings route_name: automatic_updates.settings
description: 'Configure public service announcement notifications' description: 'Configure Public service announcement notifications'
parent: system.admin_config_system parent: system.admin_config_system
...@@ -35,7 +35,7 @@ function automatic_updates_page_top(array &$page_top) { ...@@ -35,7 +35,7 @@ function automatic_updates_page_top(array &$page_top) {
$psa = \Drupal::service('automatic_updates.psa'); $psa = \Drupal::service('automatic_updates.psa');
$messages = $psa->getPublicServiceMessages(); $messages = $psa->getPublicServiceMessages();
if ($messages) { if ($messages) {
\Drupal::messenger()->addError(t('Drupal public service announcements:')); \Drupal::messenger()->addError(t('Public service announcements:'));
foreach ($messages as $message) { foreach ($messages as $message) {
\Drupal::messenger()->addError($message); \Drupal::messenger()->addError($message);
} }
......
...@@ -71,12 +71,12 @@ class SettingsForm extends ConfigFormBase { ...@@ -71,12 +71,12 @@ class SettingsForm extends ConfigFormBase {
]; ];
$form['enable_psa'] = [ $form['enable_psa'] = [
'#type' => 'checkbox', '#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'), '#default_value' => $config->get('enable_psa'),
]; ];
$form['notify'] = [ $form['notify'] = [
'#type' => 'checkbox', '#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'), '#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()]), '#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()]),
]; ];
......
...@@ -5,7 +5,6 @@ namespace Drupal\automatic_updates\Services; ...@@ -5,7 +5,6 @@ namespace Drupal\automatic_updates\Services;
use Composer\Semver\VersionParser; use Composer\Semver\VersionParser;
use Drupal\Component\Datetime\TimeInterface; use Drupal\Component\Datetime\TimeInterface;
use Drupal\Component\Render\FormattableMarkup; use Drupal\Component\Render\FormattableMarkup;
use Drupal\Component\Version\Constraint;
use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\DependencyInjection\DependencySerializationTrait; use Drupal\Core\DependencyInjection\DependencySerializationTrait;
...@@ -114,7 +113,6 @@ class AutomaticUpdatesPsa implements AutomaticUpdatesPsaInterface { ...@@ -114,7 +113,6 @@ class AutomaticUpdatesPsa implements AutomaticUpdatesPsaInterface {
*/ */
public function getPublicServiceMessages() { public function getPublicServiceMessages() {
$messages = []; $messages = [];
if (!$this->config->get('enable_psa')) { if (!$this->config->get('enable_psa')) {
return $messages; return $messages;
} }
...@@ -140,10 +138,13 @@ class AutomaticUpdatesPsa implements AutomaticUpdatesPsaInterface { ...@@ -140,10 +138,13 @@ class AutomaticUpdatesPsa implements AutomaticUpdatesPsaInterface {
$json_payload = json_decode($response); $json_payload = json_decode($response);
if ($json_payload) { if ($json_payload) {
foreach ($json_payload as $json) { foreach ($json_payload as $json) {
if ($json->project === 'core') { if ($json->is_psa && ($json->type === 'core' || $this->isValidExtension($json->type, $json->project))) {
$this->coreParser($messages, $json); $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); $this->contribParser($messages, $json);
} }
} }
...@@ -163,27 +164,22 @@ class AutomaticUpdatesPsa implements AutomaticUpdatesPsaInterface { ...@@ -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 * @param string $extension_type
* The messages array. * The extension type i.e. module, theme, profile.
* @param object $json * @param string $project_name
* The JSON object. * The project.
*
* @return bool
* TRUE if extension exists, else FALSE.
*/ */
protected function coreParser(array &$messages, $json) { protected function isValidExtension($extension_type, $project_name) {
$parser = new VersionParser(); if (!property_exists($this, $extension_type)) {
array_walk($json->secure_versions, function (&$version) { $this->logger->error('Extension list of type "%extension" does not exist.', ['%extension' => $extension_type]);
$version = '<' . $version; return FALSE;
});
$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,
]);
} }
return $this->{$extension_type}->exists($project_name) && !empty($this->{$extension_type}->getAllAvailableInfo()[$project_name]['version']);
} }
/** /**
...@@ -195,46 +191,59 @@ class AutomaticUpdatesPsa implements AutomaticUpdatesPsaInterface { ...@@ -195,46 +191,59 @@ class AutomaticUpdatesPsa implements AutomaticUpdatesPsaInterface {
* The JSON object. * The JSON object.
*/ */
protected function contribParser(array &$messages, $json) { protected function contribParser(array &$messages, $json) {
$extension_list = $json->type; $extension_version = $this->{$json->type}->getAllAvailableInfo()[$json->project]['version'];
if (!property_exists($this, $extension_list)) { $json->insecure = array_filter(array_map(function ($version) {
$this->logger->error('Extension list of type "%extension" does not exist.', ['%extension' => $extension_list]);
return;
}
array_walk($json->secure_versions, function (&$version) {
if (substr($version, 0, 4) === \Drupal::CORE_COMPATIBILITY . '-') { if (substr($version, 0, 4) === \Drupal::CORE_COMPATIBILITY . '-') {
$version = substr($version, 4); return 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']);
} }
}, $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 * @param array $messages
* The messages array. * The messages array.
* @param object $json * @param object $json
* The JSON object. * The JSON object.
* @param string $extension_version * @param string $current_version
* The extension version. * The current extension version.
*
* @throws \UnexpectedValueException
*/ */
protected function contribMessage(array &$messages, $json, $extension_version) { protected function parseConstraints(array &$messages, $json, $current_version) {
$version_string = implode('||', $json->secure_versions); $version_string = implode('||', $json->insecure);
$constraint = new Constraint("<=$extension_version", \Drupal::CORE_COMPATIBILITY); if (empty($version_string)) {
if (!$constraint->isCompatible($version_string)) { return;
$messages[] = new FormattableMarkup('<a href=":url">:message</a>', [ }
':message' => $json->title, $parser = new VersionParser();
':url' => $json->link, $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,
]);
}
} }
...@@ -21,12 +21,13 @@ ...@@ -21,12 +21,13 @@
See the <a href="{{ status_report }}">site status report page</a> for more information. See the <a href="{{ status_report }}">site status report page</a> for more information.
{% endtrans %} {% endtrans %}
</p> </p>
<p>{{ 'Drupal public service announcements:'|t }}</p> <p>{{ 'Public service announcements:'|t }}</p>
<ul> <ul>
{% for message in messages %} {% for message in messages %}
<li>{{ message }}</li> <li>{{ message }}</li>
{% endfor %} {% endfor %}
</ul> </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> <p>
{% set settings_link = path('automatic_updates.settings') %} {% set settings_link = path('automatic_updates.settings') %}
{% trans %} {% trans %}
......
...@@ -19,77 +19,98 @@ class JsonTestController extends ControllerBase { ...@@ -19,77 +19,98 @@ class JsonTestController extends ControllerBase {
public function json() { public function json() {
$feed = []; $feed = [];
$feed[] = [ $feed[] = [
'title' => 'Critical Release - PSA-2019-02-19', 'title' => 'Critical Release - SA-2019-02-19',
'link' => 'https://www.drupal.org/psa-2019-02-19', 'link' => 'https://www.drupal.org/sa-2019-02-19',
'project' => 'core', 'project' => 'drupal',
'extensions' => [], 'type' => 'core',
'type' => 'module', 'insecure' => [
'secure_versions' => [ '7.65',
'7.99', '8.5.14',
'8.10.99', '8.5.14',
'8.9.99', '8.6.13',
'8.8.99', '8.7.0-alpha2',
'8.7.99', '8.7.0-beta1',
'8.6.99', '8.7.0-beta2',
'8.5.99', '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', 'pubDate' => 'Tue, 19 Feb 2019 14:11:01 +0000',
]; ];
$feed[] = [ $feed[] = [
'title' => 'Critical Release - PSA-Really Old', 'title' => 'Critical Release - PSA-Really Old',
'link' => 'https://www.drupal.org/psa', 'link' => 'https://www.drupal.org/psa',
'project' => 'core', 'project' => 'drupal',
'extensions' => [], 'type' => 'core',
'type' => 'module', 'is_psa' => '1',
'secure_versions' => [ 'insecure' => [],
'7.0',
'8.4.0',
],
'pubDate' => 'Tue, 19 Feb 2019 14:11:01 +0000', 'pubDate' => 'Tue, 19 Feb 2019 14:11:01 +0000',
]; ];
$feed[] = [ $feed[] = [
'title' => 'Node - Moderately critical - Access bypass - SA-CONTRIB-2019', 'title' => 'Node - Moderately critical - Access bypass - SA-CONTRIB-2019',
'link' => 'https://www.drupal.org/sa-contrib-2019', 'link' => 'https://www.drupal.org/sa-contrib-2019',
'project' => 'node', 'project' => 'node',
'extensions' => ['node'],
'type' => 'module', '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', 'pubDate' => 'Tue, 19 Mar 2019 12:50:00 +0000',
]; ];
$feed[] = [ $feed[] = [
'title' => 'Standard - Moderately critical - Access bypass - SA-CONTRIB-2019', 'title' => 'Standard - Moderately critical - Access bypass - SA-CONTRIB-2019',
'link' => 'https://www.drupal.org/sa-contrib-2019', 'link' => 'https://www.drupal.org/sa-contrib-2019',
'project' => 'Standard Install Profile', 'project' => 'standard',
'extensions' => ['standard'],
'type' => 'profile', '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', 'pubDate' => 'Tue, 19 Mar 2019 12:50:00 +0000',
]; ];
$feed[] = [ $feed[] = [
'title' => 'Seven - Moderately critical - Access bypass - SA-CONTRIB-2019', 'title' => 'Seven - Moderately critical - Access bypass - SA-CONTRIB-2019',
'link' => 'https://www.drupal.org/sa-contrib-2019', 'link' => 'https://www.drupal.org/sa-contrib-2019',
'project' => 'seven', 'project' => 'seven',
'extensions' => ['seven'],
'type' => 'theme', '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', 'pubDate' => 'Tue, 19 Mar 2019 12:50:00 +0000',
]; ];
$feed[] = [ $feed[] = [
'title' => 'Foobar - Moderately critical - Access bypass - SA-CONTRIB-2019', 'title' => 'Foobar - Moderately critical - Access bypass - SA-CONTRIB-2019',
'link' => 'https://www.drupal.org/sa-contrib-2019', 'link' => 'https://www.drupal.org/sa-contrib-2019',
'project' => 'foobar', 'project' => 'foobar',
'extensions' => ['foobar'],
'type' => 'foobar', 'type' => 'foobar',
'secure_versions' => ['8.x-1.2'], 'is_psa' => '1',
'insecure' => [],
'pubDate' => 'Tue, 19 Mar 2019 12:50:00 +0000', 'pubDate' => 'Tue, 19 Mar 2019 12:50:00 +0000',
]; ];
$feed[] = [ $feed[] = [
'title' => 'Token - Moderately critical - Access bypass - SA-CONTRIB-2019', 'title' => 'Token - Moderately critical - Access bypass - SA-CONTRIB-2019',
'link' => 'https://www.drupal.org/sa-contrib-2019', 'link' => 'https://www.drupal.org/sa-contrib-2019',
'project' => 'token', 'project' => 'token',
'extensions' => ['token'],
'type' => 'module', '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', 'pubDate' => 'Tue, 19 Mar 2019 12:50:00 +0000',
]; ];
return new JsonResponse($feed); return new JsonResponse($feed);
......
...@@ -51,15 +51,16 @@ class AutomaticUpdatesTest extends BrowserTestBase { ...@@ -51,15 +51,16 @@ class AutomaticUpdatesTest extends BrowserTestBase {
->set('psa_endpoint', $end_point) ->set('psa_endpoint', $end_point)
->save(); ->save();
$this->drupalGet(Url::fromRoute('system.admin')); $this->drupalGet(Url::fromRoute('system.admin'));
$this->assertSession()->pageTextContains('Critical Release - PSA-2019-02-19'); $this->assertSession()->pageTextContains('Critical Release - SA-2019-02-19');
$this->assertSession()->pageTextNotContains('Critical Release - PSA-Really Old'); $this->assertSession()->pageTextContains('Critical Release - PSA-Really Old');
$this->assertSession()->pageTextNotContains('Node - Moderately critical - Access bypass - SA-CONTRIB-2019');
$this->assertSession()->pageTextContains('Seven - Moderately critical - Access bypass - SA-CONTRIB-2019'); $this->assertSession()->pageTextContains('Seven - Moderately critical - Access bypass - SA-CONTRIB-2019');
$this->assertSession()->pageTextContains('Standard - 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. // Test site status report.
$this->drupalGet(Url::fromRoute('system.status')); $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. // Test cache.
$end_point = $this->buildUrl(Url::fromRoute('test_automatic_updates.json_test_denied_controller')); $end_point = $this->buildUrl(Url::fromRoute('test_automatic_updates.json_test_denied_controller'));
...@@ -67,7 +68,7 @@ class AutomaticUpdatesTest extends BrowserTestBase { ...@@ -67,7 +68,7 @@ class AutomaticUpdatesTest extends BrowserTestBase {
->set('psa_endpoint', $end_point) ->set('psa_endpoint', $end_point)
->save(); ->save();
$this->drupalGet(Url::fromRoute('system.admin')); $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. // Test transmit errors with JSON endpoint.
drupal_flush_all_caches(); drupal_flush_all_caches();
......
...@@ -60,14 +60,14 @@ class NotifyTest extends BrowserTestBase { ...@@ -60,14 +60,14 @@ class NotifyTest extends BrowserTestBase {
public function testSendMail() { public function testSendMail() {
// Test PSAs on admin pages. // Test PSAs on admin pages.
$this->drupalGet(Url::fromRoute('system.admin')); $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. // Email should be sent.
$notify = $this->container->get('automatic_updates.psa_notify'); $notify = $this->container->get('automatic_updates.psa_notify');
$notify->send(); $notify->send();
$this->assertCount(1, $this->getMails()); $this->assertCount(1, $this->getMails());
$this->assertMailString('subject', '3 urgent Drupal announcements require your attention', 1); $this->assertMailString('subject', '4 urgent Drupal announcements require your attention', 1);
$this->assertMailString('body', 'Critical Release - PSA-2019-02-19', 1); $this->assertMailString('body', 'Critical Release - SA-2019-02-19', 1);
// No email should be sent if PSA's are disabled. // No email should be sent if PSA's are disabled.
$this->container->get('state')->set('system.test_mail_collector', []); $this->container->get('state')->set('system.test_mail_collector', []);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment