diff --git a/core/modules/update/images/shield-icon.svg b/core/modules/update/images/shield-icon.svg
new file mode 100644
index 0000000..9a7c56d
--- /dev/null
+++ b/core/modules/update/images/shield-icon.svg
@@ -0,0 +1,35 @@
+
+
diff --git a/core/modules/update/src/Tests/UpdateContribTest.php b/core/modules/update/src/Tests/UpdateContribTest.php
index 81f7117..bf657da 100644
--- a/core/modules/update/src/Tests/UpdateContribTest.php
+++ b/core/modules/update/src/Tests/UpdateContribTest.php
@@ -84,6 +84,7 @@ public function testUpdateContribBasic() {
$this->assertRaw('
' . t('Modules') . '
');
$this->assertNoText(t('Update available'));
$this->assertRaw($project_link, 'Link to aaa_update_test project appears.');
+ $this->assertText(t('AAA is not covered!'));
// Since aaa_update_test is installed the fact it is hidden and in the
// Testing package means it should not appear.
diff --git a/core/modules/update/src/UpdateProcessor.php b/core/modules/update/src/UpdateProcessor.php
index 25dfc7a..9115357 100644
--- a/core/modules/update/src/UpdateProcessor.php
+++ b/core/modules/update/src/UpdateProcessor.php
@@ -231,6 +231,9 @@ protected function parseXml($raw_xml) {
foreach ($release->children() as $k => $v) {
$data['releases'][$version][$k] = (string) $v;
}
+ if ($release->security['covered']) {
+ $data['releases'][$version]['security_covered'] = TRUE;
+ }
$data['releases'][$version]['terms'] = [];
if ($release->terms) {
foreach ($release->terms->children() as $term) {
diff --git a/core/modules/update/tests/modules/update_test/aaa_update_test.1_0.xml b/core/modules/update/tests/modules/update_test/aaa_update_test.1_0.xml
index 82362fe..9405cb3 100644
--- a/core/modules/update/tests/modules/update_test/aaa_update_test.1_0.xml
+++ b/core/modules/update/tests/modules/update_test/aaa_update_test.1_0.xml
@@ -29,6 +29,7 @@
Release typeNew featuresRelease typeBug fixes
+ AAA is not covered!
diff --git a/core/modules/update/tests/modules/update_test/bbb_update_test.1_0.xml b/core/modules/update/tests/modules/update_test/bbb_update_test.1_0.xml
index 8d705b5..2142c04 100644
--- a/core/modules/update/tests/modules/update_test/bbb_update_test.1_0.xml
+++ b/core/modules/update/tests/modules/update_test/bbb_update_test.1_0.xml
@@ -16,11 +16,11 @@
bbb_update_test 8.x-1.08.x-1.0
- DRUPAL-7--1-0
+ 8.x-1.010published
- http://example.com/bbb_update_test-7-x-1-0-release
+ http://example.com/bbb_update_test-8-x-1-0-releasehttp://example.com/bbb_update_test-8.x-1.0.tar.gz1250424521b966255555d9c9b86d480ca08cfaa98e
@@ -29,6 +29,26 @@
Release typeNew featuresRelease typeBug fixes
+
+
+
+ bbb_update_test 8.x-1.0-beta1
+ 8.x-1.0-beta1
+ 8.x-1.0-beta1
+ 1
+ 0
+ beta1
+ published
+ http://example.com/bbb_update_test-8-x-1-0-beta1
+ http://example.com/bbb_update_test-8.x-1.0-beta1.tar.gz
+ 1250424521
+ 7da7b18ce17cef2122f5cbca1bfe626a
+ 1073751331
+
+ Release typeNew features
+ Release typeBug fixes
+
+ Not covered!
diff --git a/core/modules/update/update.compare.inc b/core/modules/update/update.compare.inc
index 2ba5fdc..c16a6f2 100644
--- a/core/modules/update/update.compare.inc
+++ b/core/modules/update/update.compare.inc
@@ -177,6 +177,14 @@ function update_calculate_project_update_status(&$project_data, $available) {
}
}
+ // For dev releases, existing_version looks like '8.4.0-dev', while
+ // updates.drupal.org provides '8.4.x-dev'.
+ $version_normalized = preg_replace('/0-dev$/', 'x-dev', $project_data['existing_version']);
+ if (isset($available['releases'][$version_normalized])) {
+ $project_data['security'] = isset($available['releases'][$version_normalized]['security']) ? $available['releases'][$version_normalized]['security'] : '';
+ $project_data['security_covered'] = !empty($available['releases'][$version_normalized]['security_covered']);
+ }
+
// If the project status is marked as something bad, there's nothing else
// to consider.
if (isset($available['project_status'])) {
diff --git a/core/modules/update/update.install b/core/modules/update/update.install
index c616412..6079a0b 100644
--- a/core/modules/update/update.install
+++ b/core/modules/update/update.install
@@ -33,8 +33,27 @@ function update_requirements($phase) {
if ($available = update_get_available(FALSE)) {
module_load_include('inc', 'update', 'update.compare');
$data = update_calculate_project_data($available);
- // First, populate the requirements for core:
+
+ // Check if all projects have security advisory coverage.
+ $requirements['update_covered'] = [
+ 'title' => t('Drupal.org security advisory coverage'),
+ 'value' => t('Currently installed modules and themes from Drupal.org receive coverage.'),
+ 'description' => t('Learn more about Drupal.org security advisory coverage.'),
+ ];
+ foreach ($data as $project) {
+ // 'security_covered' boolean makes a positive assertion of coverage.
+ // 'security' string confirms there is no coverage. Check both so
+ // non-www.drupal.org projects are not false positives.
+ if (empty($project['security_covered']) && !empty($project['security'])) {
+ $requirements['update_covered']['value'] = \Drupal::l(t('Not all Drupal.org modules and themes receive security advisory coverage'), new Url('update.status'));
+ $requirements['update_covered']['severity'] = REQUIREMENT_WARNING;
+ break;
+ }
+ }
+
+ // Populate the requirements for core:
$requirements['update_core'] = _update_requirement_check($data['drupal'], 'core');
+
// We don't want to check drupal a second time.
unset($data['drupal']);
if (!empty($data)) {
diff --git a/core/modules/update/update.module b/core/modules/update/update.module
index 32b5b8c..d2a18bc 100644
--- a/core/modules/update/update.module
+++ b/core/modules/update/update.module
@@ -118,7 +118,17 @@ function update_help($route_name, RouteMatchInterface $route_match) {
return $output;
case 'update.status':
- return '
' . t('Here you can find information about available updates for your installed modules and themes. Note that each module or theme is part of a "project", which may or may not have the same name, and might include multiple modules or themes within it.') . '
' . t('Here you can find information about available updates for your installed modules and themes. Note that each module or theme is part of a "project", which may or may not have the same name, and might include multiple modules or themes within it.') . '
';
+ $output .= '
' . t('Modules and themes with a shield icon @icon are covered by the Drupal Security Team’s advisory policy. Vulnerabilities reported to the Security Team will be responsibly disclosed.', ['@icon' => \Drupal::service('renderer')->render($icon)]) . '
';
+ return $output;
case 'system.modules_list':
if (_update_manager_access()) {
diff --git a/core/modules/update/update.report.inc b/core/modules/update/update.report.inc
index 7b14a05..81a3823 100644
--- a/core/modules/update/update.report.inc
+++ b/core/modules/update/update.report.inc
@@ -260,6 +260,35 @@ function template_preprocess_update_project_status(&$variables) {
$variables['status']['attributes'] = new Attribute();
$variables['status']['reason'] = (isset($project['reason'])) ? $project['reason'] : NULL;
+ if (isset($project['security'])) {
+ $variables['status']['security'] = $project['security'];
+ }
+ if (!empty($project['security_covered'])) {
+ // The update server confirms this release is covered for security
+ // announcements.
+ $variables['status']['security_icon'] = [
+ '#theme' => 'image',
+ '#width' => 18,
+ '#height' => 18,
+ '#uri' => 'core/modules/update/images/shield-icon.svg',
+ '#alt' => t('Shield'),
+ '#title' => t('Shield'),
+ ];
+ }
+ elseif (!empty($project['security'])) {
+ // The update server confirms this release is NOT covered for security
+ // announcements.
+ $variables['status']['security_icon'] = [
+ '#theme' => 'image',
+ '#width' => 18,
+ '#height' => 18,
+ '#uri' => 'core/misc/icons/e29700/warning.svg',
+ '#alt' => t('Warning'),
+ '#title' => t('Warning'),
+ ];
+ drupal_set_message(t('Your site uses modules and/or themes that are not subject to the Drupal Security Team’s advisory policy. When vulnerabilities are discovered, they may be disclosed publicly without a fix, and will not have security announcements.'), 'warning');
+ }
+
switch ($project['status']) {
case UPDATE_CURRENT:
$uri = 'core/misc/icons/73b355/check.svg';
diff --git a/core/themes/stable/css/update/update.admin.theme.css b/core/themes/stable/css/update/update.admin.theme.css
index abf0a88..c3711f4 100644
--- a/core/themes/stable/css/update/update.admin.theme.css
+++ b/core/themes/stable/css/update/update.admin.theme.css
@@ -8,10 +8,12 @@
font-size: 110%;
}
.project-update__status {
+ text-align: right; /* LTR */
float: right; /* LTR */
font-size: 110%;
}
[dir="rtl"] .project-update__status {
+ text-align: left;
float: left;
}
.project-update__status--not-supported {
@@ -32,6 +34,9 @@
padding-left: 0;
padding-right: 0.5em;
}
+.project-update__status-icon img {
+ vertical-align: top;
+}
.project-update__details {
padding: 1em 1em 0.25em 1em;
}
diff --git a/core/themes/stable/templates/admin/update-project-status.html.twig b/core/themes/stable/templates/admin/update-project-status.html.twig
index 5a6d2ec..8a7b775 100644
--- a/core/themes/stable/templates/admin/update-project-status.html.twig
+++ b/core/themes/stable/templates/admin/update-project-status.html.twig
@@ -43,9 +43,15 @@
{{ status.icon }}
+