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'
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
......
......@@ -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.'),
];
......
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
......@@ -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);
}
......
......@@ -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()]),
];
......
......@@ -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,
]);
}
}
......@@ -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 %}
......
......@@ -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);
......
......@@ -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();
......
......@@ -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', []);
......
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