diff --git a/core/modules/update/css/update.admin.theme.css b/core/modules/update/css/update.admin.theme.css index abf0a88..c3711f4 100644 --- a/core/modules/update/css/update.admin.theme.css +++ b/core/modules/update/css/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/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 @@ + + + + + + + image/svg+xml + + + + + + + + + diff --git a/core/modules/update/src/Tests/UpdateContribTest.php b/core/modules/update/src/Tests/UpdateContribTest.php index 81f7117..7434c3c 100644 --- a/core/modules/update/src/Tests/UpdateContribTest.php +++ b/core/modules/update/src/Tests/UpdateContribTest.php @@ -84,6 +84,8 @@ public function testUpdateContribBasic() { $this->assertRaw('

' . t('Modules') . '

'); $this->assertNoText(t('Update available')); $this->assertRaw($project_link, 'Link to aaa_update_test project appears.'); + // Check for security advisory coverage information. + $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. @@ -176,8 +178,8 @@ public function testUpdateContribOrder() { // Instead of just searching for 'BBB Update test' or something, we want // to use the full markup that starts the project entry itself, so that // we're really testing that the project listings are in the right order. - $bbb_project_link = '
BBB Update test'; - $ccc_project_link = '
CCC Update test'; + $bbb_project_link = '
BBB Update test'; + $ccc_project_link = '
CCC Update test'; $this->assertTrue(strpos($this->getRawContent(), $bbb_project_link) < strpos($this->getRawContent(), $ccc_project_link), "'BBB Update test' project is listed before the 'CCC Update test' project"); } @@ -375,7 +377,18 @@ public function testUpdateBrokenFetchURL() { // The other two should be listed as projects. $this->assertRaw(\Drupal::l(t('AAA Update test'), Url::fromUri('http://example.com/project/aaa_update_test')), 'Link to aaa_update_test project appears.'); $this->assertNoRaw(\Drupal::l(t('BBB Update test'), Url::fromUri('http://example.com/project/bbb_update_test')), 'Link to bbb_update_test project does not appear.'); - $this->assertRaw(\Drupal::l(t('CCC Update test'), Url::fromUri('http://example.com/project/ccc_update_test')), 'Link to bbb_update_test project appears.'); + $this->assertRaw(\Drupal::l(t('CCC Update test'), Url::fromUri('http://example.com/project/ccc_update_test')), 'Link to ccc_update_test project appears.'); + + // Check for security coverage shield for ccc_update_test. + $icon = [ + '#theme' => 'image', + '#width' => 18, + '#height' => 18, + '#uri' => 'core/modules/update/images/shield-icon.svg', + '#alt' => t('Shield'), + '#title' => t('Shield'), + ]; + $this->assertRaw(\Drupal::service('renderer')->render($icon), 'Security coverage shield appears.'); } /** 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/templates/update-project-status.html.twig b/core/modules/update/templates/update-project-status.html.twig index 4cc9a19..3c48d7a 100644 --- a/core/modules/update/templates/update-project-status.html.twig +++ b/core/modules/update/templates/update-project-status.html.twig @@ -45,9 +45,15 @@ {{ status.icon }} +
+ {{ status.security }} + + {{ status.security_icon }} + +
-
+
{%- if url -%} {{ title }} {%- else -%} 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 features Release 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.0 8.x-1.0 - DRUPAL-7--1-0 + 8.x-1.0 1 0 published - http://example.com/bbb_update_test-7-x-1-0-release + http://example.com/bbb_update_test-8-x-1-0-release http://example.com/bbb_update_test-8.x-1.0.tar.gz 1250424521 b966255555d9c9b86d480ca08cfaa98e @@ -29,6 +29,26 @@ Release typeNew features Release 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/tests/modules/update_test/ccc_update_test.1_0.xml b/core/modules/update/tests/modules/update_test/ccc_update_test.1_0.xml index 82764c2..7e6c2f1 100644 --- a/core/modules/update/tests/modules/update_test/ccc_update_test.1_0.xml +++ b/core/modules/update/tests/modules/update_test/ccc_update_test.1_0.xml @@ -30,5 +30,6 @@ Release typeBug fixes + 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.') . '

'; + $icon = [ + '#theme' => 'image', + '#width' => 18, + '#height' => 18, + '#uri' => 'core/modules/update/images/shield-icon.svg', + '#alt' => t('Shield'), + '#title' => t('Shield'), + ]; + $output = '

' . 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 }} +
+ {{ status.security }} + + {{ status.security_icon }} + +
-
+
{%- if url -%} {{ title }} {%- else -%}