Issue #2991207 by tedbow, dww, samuel.mortenson, robpowell, Spokje, Gábor Hojtsy, bnjmnm, xjm, benjifisher, drumm, tim.plunkett, larowlan, mpdonadio, webchick, AaronMcHale, jibran, catch: Drupal core should inform the user of the security coverage for the site's installed minor version including final 8.x LTS releases

(cherry picked from commit 87df6a1d93)
merge-requests/64/head
Gábor Hojtsy 2020-02-06 10:55:41 +01:00
parent e31698a395
commit 1f9fb6a1e7
11 changed files with 1288 additions and 0 deletions

View File

@ -0,0 +1,211 @@
<?php
namespace Drupal\update;
/**
* Calculates a project's security coverage information.
*
* @internal
* This class implements logic to determine security coverage for Drupal core
* according to Drupal core security policy. It should not be called directly.
*/
final class ProjectSecurityData {
/**
* The number of minor versions of Drupal core that receive security coverage.
*
* For example, if this value is 2 and the existing version is 9.0.1, the
* 9.0.x branch will receive security coverage until the release of version
* 9.2.0.
*/
const CORE_MINORS_WITH_SECURITY_COVERAGE = 2;
/**
* Define constants for versions with security coverage end dates.
*
* Two types of constants are supported:
* - SECURITY_COVERAGE_END_DATE_[VERSION_MAJOR]_[VERSION_MINOR]: A date in
* 'Y-m-d' or 'Y-m' format.
* - SECURITY_COVERAGE_ENDING_WARN_DATE_[VERSION_MAJOR]_[VERSION_MINOR]: A
* date in 'Y-m-d' format.
*
* @see \Drupal\update\ProjectSecurityRequirement::getDateEndRequirement()
*/
const SECURITY_COVERAGE_END_DATE_8_8 = '2020-12-02';
const SECURITY_COVERAGE_ENDING_WARN_DATE_8_8 = '2020-06-02';
const SECURITY_COVERAGE_END_DATE_8_9 = '2021-11';
/**
* The existing (currently installed) version of the project.
*
* Because this class only handles the Drupal core project, values will be
* semantic version numbers such as 8.8.0, 8.8.0-alpha1, or 9.0.0.
*
* @var string|null
*/
protected $existingVersion;
/**
* Releases as returned by update_get_available().
*
* @var array
*
* Each release item in the array has metadata about that release. This class
* uses the keys:
* - status (string): The status of the release.
* - version (string): The version number of the release.
*
* @see update_get_available()
*/
protected $releases;
/**
* Constructs a ProjectSecurityData object.
*
* @param string $existing_version
* The existing (currently installed) version of the project.
* @param array $releases
* Project releases as returned by update_get_available().
*/
private function __construct($existing_version = NULL, array $releases = []) {
$this->existingVersion = $existing_version;
$this->releases = $releases;
}
/**
* Creates a ProjectSecurityData object from project data and releases.
*
* @param array $project_data
* Project data from Drupal\update\UpdateManagerInterface::getProjects() and
* processed by update_process_project_info().
* @param array $releases
* Project releases as returned by update_get_available().
*
* @return static
*/
public static function createFromProjectDataAndReleases(array $project_data, array $releases) {
if (!($project_data['project_type'] === 'core' && $project_data['name'] === 'drupal')) {
// Only Drupal core has an explicit coverage range.
return new static();
}
return new static($project_data['existing_version'], $releases);
}
/**
* Gets the security coverage information for a project.
*
* Currently only Drupal core is supported.
*
* @return array
* The security coverage information, or an empty array if no security
* information is available for the project. If security coverage is based
* on release of a specific version, the array will have the following
* keys:
* - security_coverage_end_version (string): The minor version the existing
* version will receive security coverage until.
* - additional_minors_coverage (int): The number of additional minor
* versions the existing version will receive security coverage.
* If the security coverage is based on a specific date, the array will have
* the following keys:
* - security_coverage_end_date (string): The month or date security
* coverage will end for the existing version. It can be in either
* 'YYYY-MM' or 'YYYY-MM-DD' format.
* - (optional) security_coverage_ending_warn_date (string): The date, in
* the format 'YYYY-MM-DD', after which a warning should be displayed
* about upgrading to another version.
*/
public function getCoverageInfo() {
if (empty($this->releases[$this->existingVersion])) {
// If the existing version does not have a release, we cannot get the
// security coverage information.
return [];
}
$info = [];
$existing_release_version = ModuleVersion::createFromVersionString($this->existingVersion);
// Check if the installed version has a specific end date defined.
$version_suffix = $existing_release_version->getMajorVersion() . '_' . $this->getSemanticMinorVersion($this->existingVersion);
if (defined("self::SECURITY_COVERAGE_END_DATE_$version_suffix")) {
$info['security_coverage_end_date'] = constant("self::SECURITY_COVERAGE_END_DATE_$version_suffix");
$info['security_coverage_ending_warn_date'] =
defined("self::SECURITY_COVERAGE_ENDING_WARN_DATE_$version_suffix")
? constant("self::SECURITY_COVERAGE_ENDING_WARN_DATE_$version_suffix")
: NULL;
}
elseif ($security_coverage_until_version = $this->getSecurityCoverageUntilVersion()) {
$info['security_coverage_end_version'] = $security_coverage_until_version;
$info['additional_minors_coverage'] = $this->getAdditionalSecurityCoveredMinors($security_coverage_until_version);
}
return $info;
}
/**
* Gets the release the current minor will receive security coverage until.
*
* @todo In https://www.drupal.org/node/2608062 determine how we will know
* what the final minor release of a particular major version will be. This
* method should not return a version beyond that minor.
*
* @return string|null
* The version the existing version will receive security coverage until or
* NULL if this cannot be determined.
*/
private function getSecurityCoverageUntilVersion() {
$existing_release_version = ModuleVersion::createFromVersionString($this->existingVersion);
if (!empty($existing_release_version->getVersionExtra())) {
// Only full releases receive security coverage.
return NULL;
}
return $existing_release_version->getMajorVersion() . '.'
. ($this->getSemanticMinorVersion($this->existingVersion) + static::CORE_MINORS_WITH_SECURITY_COVERAGE)
. '.0';
}
/**
* Gets the number of additional minor security covered releases.
*
* @param string $security_covered_version
* The version until which the existing version receives security coverage.
*
* @return int|null
* The number of additional minor releases that receive security coverage,
* or NULL if this cannot be determined.
*/
private function getAdditionalSecurityCoveredMinors($security_covered_version) {
$security_covered_version_major = ModuleVersion::createFromVersionString($security_covered_version)->getMajorVersion();
$security_covered_version_minor = $this->getSemanticMinorVersion($security_covered_version);
foreach ($this->releases as $release) {
$release_version = ModuleVersion::createFromVersionString($release['version']);
if ($release_version->getMajorVersion() === $security_covered_version_major && $release['status'] === 'published' && !$release_version->getVersionExtra()) {
// The releases are ordered with the most recent releases first.
// Therefore if we have found an official, published release with the
// same major version as $security_covered_version then this release
// can be used to determine the latest minor.
$latest_minor = $this->getSemanticMinorVersion($release['version']);
break;
}
}
// If $latest_minor is set, we know that $latest_minor and
// $security_covered_version_minor have the same major version. Therefore we
// can simply subtract to determine the number of additional minor security
// covered releases.
return isset($latest_minor) ? $security_covered_version_minor - $latest_minor : NULL;
}
/**
* Gets the minor version for a semantic version string.
*
* @param string $version
* The semantic version string.
*
* @return int
* The minor version as an integer.
*/
private function getSemanticMinorVersion($version) {
return (int) (explode('.', $version)[1]);
}
}

View File

@ -0,0 +1,291 @@
<?php
namespace Drupal\update;
use Drupal\Core\Render\Markup;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Url;
/**
* Class for generating a project's security requirement.
*
* @see update_requirements()
*
* @internal
* This class implements logic to determine security coverage for Drupal core
* according to Drupal core security policy. It should not be called directly.
*/
final class ProjectSecurityRequirement {
use StringTranslationTrait;
/**
* The project title.
*
* @var string|null
*/
protected $projectTitle;
/**
* Security coverage information for the project.
*
* @var array
*
* @see \Drupal\update\ProjectSecurityData::getCoverageInfo()
*/
private $securityCoverageInfo;
/**
* The next version after the installed version in the format [MAJOR].[MINOR].
*
* @var string|null
*/
private $nextMajorMinorVersion;
/**
* The existing (currently installed) version in the format [MAJOR].[MINOR].
*
* @var string|null
*/
private $existingMajorMinorVersion;
/**
* Constructs a ProjectSecurityRequirement object.
*
* @param string|null $project_title
* The project title.
* @param array $security_coverage_info
* Security coverage information as set by
* \Drupal\update\ProjectSecurityData::getCoverageInfo().
* @param string|null $existing_major_minor_version
* The existing (currently installed) version in the format [MAJOR].[MINOR].
* @param string|null $next_major_minor_version
* The next version after the installed version in the format
* [MAJOR].[MINOR].
*/
private function __construct($project_title = NULL, array $security_coverage_info = [], $existing_major_minor_version = NULL, $next_major_minor_version = NULL) {
$this->projectTitle = $project_title;
$this->securityCoverageInfo = $security_coverage_info;
$this->existingMajorMinorVersion = $existing_major_minor_version;
$this->nextMajorMinorVersion = $next_major_minor_version;
}
/**
* Creates a ProjectSecurityRequirement object from project data.
*
* @param array $project_data
* Project data from Drupal\update\UpdateManagerInterface::getProjects().
* The 'security_coverage_info' key should be set by
* calling \Drupal\update\ProjectSecurityData::getCoverageInfo() before
* calling this method. The following keys are used in this method:
* - existing_version (string): The version of the project that is installed
* on the site.
* - project_type (string): The type of project.
* - name (string): The project machine name.
* - title (string): The project title.
* @param array $security_coverage_info
* The security coverage information as returned by
* \Drupal\update\ProjectSecurityData::getCoverageInfo().
*
* @return static
*
* @see \Drupal\update\UpdateManagerInterface::getProjects()
* @see \Drupal\update\ProjectSecurityData::getCoverageInfo()
* @see update_process_project_info()
*/
public static function createFromProjectDataAndSecurityCoverageInfo(array $project_data, array $security_coverage_info) {
if ($project_data['project_type'] !== 'core' || $project_data['name'] !== 'drupal' || empty($security_coverage_info)) {
return new static();
}
if (isset($project_data['existing_version'])) {
list($major, $minor) = explode('.', $project_data['existing_version']);
$existing_version = "$major.$minor";
$next_version = "$major." . ((int) $minor + 1);
return new static($project_data['title'], $security_coverage_info, $existing_version, $next_version);
}
return new static($project_data['title'], $security_coverage_info);
}
/**
* Gets the security coverage requirement, if any.
*
* @return array
* Requirements array as specified by hook_requirements(), or an empty array
* if no requirements can be determined.
*/
public function getRequirement() {
if (isset($this->securityCoverageInfo['security_coverage_end_version'])) {
$requirement = $this->getVersionEndRequirement();
}
elseif (isset($this->securityCoverageInfo['security_coverage_end_date'])) {
$requirement = $this->getDateEndRequirement();
}
else {
return [];
}
$requirement['title'] = $this->t('Drupal core security coverage');
return $requirement;
}
/**
* Gets the requirements based on security coverage until a specific version.
*
* @return array
* Requirements array as specified by hook_requirements().
*/
private function getVersionEndRequirement() {
$requirement = [];
if ($security_coverage_message = $this->getVersionEndCoverageMessage()) {
$requirement['description'] = $security_coverage_message;
if ($this->securityCoverageInfo['additional_minors_coverage'] > 0) {
$requirement['value'] = $this->t('Supported minor version');
$requirement['severity'] = $this->securityCoverageInfo['additional_minors_coverage'] > 1 ? REQUIREMENT_INFO : REQUIREMENT_WARNING;
}
else {
$requirement['value'] = $this->t('Unsupported minor version');
$requirement['severity'] = REQUIREMENT_ERROR;
}
}
return $requirement;
}
/**
* Gets the message for additional minor version security coverage.
*
* @return string|\Drupal\Component\Render\MarkupInterface
* The security coverage message, or an empty string if there is none.
*
* @see \Drupal\update\ProjectSecurityData::getCoverageInfo()
*/
private function getVersionEndCoverageMessage() {
if ($this->securityCoverageInfo['additional_minors_coverage'] > 0) {
// If the installed minor version will receive security coverage until
// newer minor versions are released, inform the user.
$translation_arguments = [
'@project' => $this->projectTitle,
'@version' => $this->existingMajorMinorVersion,
'@coverage_version' => $this->securityCoverageInfo['security_coverage_end_version'],
];
$message = '<p>' . $this->t('The installed minor version of @project (@version), will stop receiving official security support after the release of @coverage_version.', $translation_arguments) . '</p>';
if ($this->securityCoverageInfo['additional_minors_coverage'] === 1) {
// If the installed minor version will only receive security coverage
// for 1 newer minor core version, encourage the site owner to update
// soon.
$message .= '<p>' . $this->t('Update to @next_minor or higher soon to continue receiving security updates.', ['@next_minor' => $this->nextMajorMinorVersion])
. ' ' . static::getAvailableUpdatesMessage() . '</p>';
}
}
else {
// Because the current minor version no longer has security coverage,
// advise the site owner to update.
$message = $this->getVersionNoSecurityCoverageMessage();
}
$message .= $this->getReleaseCycleLink();
return Markup::create($message);
}
/**
* Gets the security coverage requirement based on an end date.
*
* @return array
* Requirements array as specified by hook_requirements().
*/
private function getDateEndRequirement() {
$requirement = [];
/** @var \Drupal\Component\Datetime\Time $time */
$time = \Drupal::service('datetime.time');
/** @var \Drupal\Core\Datetime\DateFormatterInterface $date_formatter */
$date_formatter = \Drupal::service('date.formatter');
// 'security_coverage_end_date' will either be in format 'Y-m-d' or 'Y-m'.
if (substr_count($this->securityCoverageInfo['security_coverage_end_date'], '-') === 2) {
$date_format = 'Y-m-d';
$full_security_coverage_end_date = $this->securityCoverageInfo['security_coverage_end_date'];
}
else {
$date_format = 'Y-m';
// If the date does not include a day, use '15'. When calling
// \DateTime::createFromFormat() the current day will be used if one is
// not provided. This may cause the month to be wrong at the beginning or
// end of the month. '15' will never be displayed because we are using the
// 'Y-m' format.
$full_security_coverage_end_date = $this->securityCoverageInfo['security_coverage_end_date'] . '-15';
}
$security_coverage_end_timestamp = \DateTime::createFromFormat('Y-m-d', $full_security_coverage_end_date)->getTimestamp();
$formatted_end_date = $date_format === 'Y-m-d'
? $this->securityCoverageInfo['security_coverage_end_date']
: $date_formatter->format($security_coverage_end_timestamp, 'custom', 'F Y');
$comparable_request_date = $date_formatter->format($time->getRequestTime(), 'custom', $date_format);
if ($this->securityCoverageInfo['security_coverage_end_date'] <= $comparable_request_date) {
// Security coverage is over.
$requirement['value'] = $this->t('Unsupported minor version');
$requirement['severity'] = REQUIREMENT_ERROR;
$requirement['description'] = $this->getVersionNoSecurityCoverageMessage();
}
else {
$requirement['value'] = $this->t('Supported minor version');
$requirement['severity'] = REQUIREMENT_INFO;
$translation_arguments = [
'@project' => $this->projectTitle,
'@version' => $this->existingMajorMinorVersion,
'@date' => $formatted_end_date,
];
$requirement['description'] = '<p>' . $this->t('The installed minor version of @project (@version), will stop receiving official security support after @date.', $translation_arguments) . '</p>';
// 'security_coverage_ending_warn_date' will always be in the format
// 'Y-m-d'.
$request_date = $date_formatter->format($time->getRequestTime(), 'custom', 'Y-m-d');
if (!empty($this->securityCoverageInfo['security_coverage_ending_warn_date']) && $this->securityCoverageInfo['security_coverage_ending_warn_date'] <= $request_date) {
$requirement['description'] .= '<p>' . $this->t('Update to a supported minor version soon to continue receiving security updates.') . '</p>';
$requirement['severity'] = REQUIREMENT_WARNING;
}
}
$requirement['description'] = Markup::create($requirement['description'] . $this->getReleaseCycleLink());
return $requirement;
}
/**
* Gets the formatted message for a project with no security coverage.
*
* @return string
* The message for a version with no security coverage.
*/
private function getVersionNoSecurityCoverageMessage() {
return '<p>' . $this->t(
'The installed minor version of @project (@version), is no longer supported and will not receive security updates.',
[
'@project' => $this->projectTitle,
'@version' => $this->existingMajorMinorVersion,
])
. '</p><p>'
. $this->t('Update to a supported minor as soon as possible to continue receiving security updates.')
. ' ' . static::getAvailableUpdatesMessage() . '</p>';
}
/**
* Gets the message with a link to the available updates page.
*
* @return \Drupal\Core\StringTranslation\TranslatableMarkup
* The message.
*/
private function getAvailableUpdatesMessage() {
return $this->t(
'See the <a href=":update_status_report">available updates</a> page for more information.',
[':update_status_report' => Url::fromRoute('update.status')->toString()]
);
}
/**
* Gets a link the release cycle page on drupal.org.
*
* @return string
* A link to the release cycle page on drupal.org.
*/
private function getReleaseCycleLink() {
return '<p>' . $this->t(
'Visit the <a href=":url">release cycle overview</a> for more information on supported releases.',
[':url' => 'https://www.drupal.org/core/release-cycle-overview']
) . '</p>';
}
}

View File

@ -0,0 +1,88 @@
<?xml version="1.0" encoding="utf-8"?>
<project xmlns:dc="http://purl.org/dc/elements/1.1/">
<title>Drupal</title>
<short_name>drupal</short_name>
<dc:creator>Drupal</dc:creator>
<api_version>8.x</api_version>
<recommended_major>8</recommended_major>
<supported_majors>8</supported_majors>
<default_major>8</default_major>
<project_status>published</project_status>
<link>http://example.com/project/drupal</link>
<terms><term><name>Projects</name><value>Drupal project</value></term></terms>
<releases>
<release>
<name>Drupal 8.2.0</name>
<version>8.2.0</version>
<tag>DRUPAL-8-2-0</tag>
<status>published</status>
<release_link>http://example.com/drupal-8-2-0-release</release_link>
<download_link>http://example.com/drupal-8-2-0.tar.gz</download_link>
<date>1250424641</date>
<terms>
<term>
<name>Release type</name>
<value>New features</value>
</term>
<term>
<name>Release type</name>
<value>Bug fixes</value>
</term>
</terms>
</release>
<release>
<name>drupal 8.2.0-beta2</name>
<version>8.2.0-beta2</version>
<tag>8.2.0-beta2</tag>
<status>published</status>
<release_link>http://example.com/drupal-8-2-0-beta2-release</release_link>
<download_link>http://example.com/drupal-8-2-0-beta2.tar.gz</download_link>
<date>1533298080</date>
<terms>
<term><name>Release type</name><value>Bug fixes</value></term>
<term><name>Release type</name><value>New features</value></term>
</terms>
<security>
Beta releases are not covered by Drupal security advisories.
</security>
</release>
<release>
<name>Drupal 8.1.0</name>
<version>8.1.0</version>
<tag>DRUPAL-8-1-0</tag>
<status>published</status>
<release_link>http://example.com/drupal-8-1-0-release</release_link>
<download_link>http://example.com/drupal-8-1-0.tar.gz</download_link>
<date>1250424521</date>
<terms>
<term>
<name>Release type</name>
<value>New features</value>
</term>
<term>
<name>Release type</name>
<value>Bug fixes</value>
</term>
</terms>
</release>
<release>
<name>Drupal 8.0.0</name>
<version>8.0.0</version>
<tag>DRUPAL-8-0-0</tag>
<status>published</status>
<release_link>http://example.com/drupal-8-0-0-release</release_link>
<download_link>http://example.com/drupal-8-0-0.tar.gz</download_link>
<date>1250424521</date>
<terms>
<term>
<name>Release type</name>
<value>New features</value>
</term>
<term>
<name>Release type</name>
<value>Bug fixes</value>
</term>
</terms>
</release>
</releases>
</project>

View File

@ -0,0 +1,104 @@
<?xml version="1.0" encoding="utf-8"?>
<project xmlns:dc="http://purl.org/dc/elements/1.1/">
<title>Drupal</title>
<short_name>drupal</short_name>
<dc:creator>Drupal</dc:creator>
<api_version>8.x</api_version>
<recommended_major>8</recommended_major>
<supported_majors>8</supported_majors>
<default_major>8</default_major>
<project_status>published</project_status>
<link>http://example.com/project/drupal</link>
<terms><term><name>Projects</name><value>Drupal project</value></term></terms>
<releases>
<release>
<name>drupal 8.3.0-rc1</name>
<version>8.3.0-rc1</version>
<tag>8.3.0-rc1</tag>
<status>published</status>
<release_link>http://example.com/drupal-8-3-0-rc1-release</release_link>
<download_link>http://example.com/drupal-8-3-0-rc1.tar.gz</download_link>
<date>1533270485</date>
<terms>
<term><name>Release type</name><value>Bug fixes</value></term>
<term><name>Release type</name><value>New features</value></term>
</terms>
<security>
RC releases are not covered by Drupal security advisories.
</security>
</release>
<release>
<name>Drupal 8.2.0</name>
<version>8.2.0</version>
<tag>DRUPAL-8-2-0</tag>
<status>published</status>
<release_link>http://example.com/drupal-8-2-0-release</release_link>
<download_link>http://example.com/drupal-8-2-0.tar.gz</download_link>
<date>1250424641</date>
<terms>
<term>
<name>Release type</name>
<value>New features</value>
</term>
<term>
<name>Release type</name>
<value>Bug fixes</value>
</term>
</terms>
</release>
<release>
<name>drupal 8.2.0-beta2</name>
<version>8.2.0-beta2</version>
<tag>8.2.0-beta2</tag>
<status>published</status>
<release_link>http://example.com/drupal-8-2-0-beta2-release</release_link>
<download_link>http://example.com/drupal-8-2-0-beta2.tar.gz</download_link>
<date>1533298080</date>
<terms>
<term><name>Release type</name><value>Bug fixes</value></term>
<term><name>Release type</name><value>New features</value></term>
</terms>
<security>
Beta releases are not covered by Drupal security advisories.
</security>
</release>
<release>
<name>Drupal 8.1.0</name>
<version>8.1.0</version>
<tag>DRUPAL-8-1-0</tag>
<status>published</status>
<release_link>http://example.com/drupal-8-1-0-release</release_link>
<download_link>http://example.com/drupal-8-1-0.tar.gz</download_link>
<date>1250424521</date>
<terms>
<term>
<name>Release type</name>
<value>New features</value>
</term>
<term>
<name>Release type</name>
<value>Bug fixes</value>
</term>
</terms>
</release>
<release>
<name>Drupal 8.0.0</name>
<version>8.0.0</version>
<tag>DRUPAL-8-0-0</tag>
<status>published</status>
<release_link>http://example.com/drupal-8-0-0-release</release_link>
<download_link>http://example.com/drupal-8-0-0.tar.gz</download_link>
<date>1250424521</date>
<terms>
<term>
<name>Release type</name>
<value>New features</value>
</term>
<term>
<name>Release type</name>
<value>Bug fixes</value>
</term>
</terms>
</release>
</releases>
</project>

View File

@ -0,0 +1,126 @@
<?xml version="1.0" encoding="utf-8"?>
<project xmlns:dc="http://purl.org/dc/elements/1.1/">
<title>Drupal</title>
<short_name>drupal</short_name>
<dc:creator>Drupal</dc:creator>
<api_version>8.x</api_version>
<recommended_major>8</recommended_major>
<supported_majors>8</supported_majors>
<default_major>8</default_major>
<project_status>published</project_status>
<link>http://example.com/project/drupal</link>
<terms><term><name>Projects</name><value>Drupal project</value></term></terms>
<releases>
<release>
<name>Drupal 9.0.0</name>
<version>9.0.0</version>
<tag>DRUPAL-9-0-0</tag>
<status>published</status>
<release_link>http://example.com/drupal-9-0-0-release</release_link>
<download_link>http://example.com/drupal-9-0-0.tar.gz</download_link>
<date>1250424641</date>
<terms>
<term>
<name>Release type</name>
<value>New features</value>
</term>
<term>
<name>Release type</name>
<value>Bug fixes</value>
</term>
</terms>
</release>
<release>
<name>Drupal 8.3.0</name>
<version>8.3.0</version>
<tag>DRUPAL-8-3-0</tag>
<status>published</status>
<release_link>http://example.com/drupal-8-3-0-release</release_link>
<download_link>http://example.com/drupal-8-3-0.tar.gz</download_link>
<date>1250424641</date>
<terms>
<term>
<name>Release type</name>
<value>New features</value>
</term>
<term>
<name>Release type</name>
<value>Bug fixes</value>
</term>
</terms>
</release>
<release>
<name>Drupal 8.2.0</name>
<version>8.2.0</version>
<tag>DRUPAL-8-2-0</tag>
<status>published</status>
<release_link>http://example.com/drupal-8-2-0-release</release_link>
<download_link>http://example.com/drupal-8-2-0.tar.gz</download_link>
<date>1250424641</date>
<terms>
<term>
<name>Release type</name>
<value>New features</value>
</term>
<term>
<name>Release type</name>
<value>Bug fixes</value>
</term>
</terms>
</release>
<release>
<name>drupal 8.2.0-beta2</name>
<version>8.2.0-beta2</version>
<tag>8.2.0-beta2</tag>
<status>published</status>
<release_link>http://example.com/drupal-8-2-0-beta2-release</release_link>
<download_link>http://example.com/drupal-8-2-0-beta2.tar.gz</download_link>
<date>1533298080</date>
<terms>
<term><name>Release type</name><value>Bug fixes</value></term>
<term><name>Release type</name><value>New features</value></term>
</terms>
<security>
Beta releases are not covered by Drupal security advisories.
</security>
</release>
<release>
<name>Drupal 8.1.0</name>
<version>8.1.0</version>
<tag>DRUPAL-8-1-0</tag>
<status>published</status>
<release_link>http://example.com/drupal-8-1-0-release</release_link>
<download_link>http://example.com/drupal-8-1-0.tar.gz</download_link>
<date>1250424521</date>
<terms>
<term>
<name>Release type</name>
<value>New features</value>
</term>
<term>
<name>Release type</name>
<value>Bug fixes</value>
</term>
</terms>
</release>
<release>
<name>Drupal 8.0.0</name>
<version>8.0.0</version>
<tag>DRUPAL-8-0-0</tag>
<status>published</status>
<release_link>http://example.com/drupal-8-0-0-release</release_link>
<download_link>http://example.com/drupal-8-0-0.tar.gz</download_link>
<date>1250424521</date>
<terms>
<term>
<name>Release type</name>
<value>New features</value>
</term>
<term>
<name>Release type</name>
<value>Bug fixes</value>
</term>
</terms>
</release>
</releases>
</project>

View File

@ -0,0 +1,107 @@
<?xml version="1.0" encoding="utf-8"?>
<project xmlns:dc="http://purl.org/dc/elements/1.1/">
<title>Drupal</title>
<short_name>drupal</short_name>
<dc:creator>Drupal</dc:creator>
<api_version>8.x</api_version>
<recommended_major>8</recommended_major>
<supported_majors>8,9</supported_majors>
<default_major>8</default_major>
<project_status>published</project_status>
<link>http://example.com/project/drupal</link>
<terms><term><name>Projects</name><value>Drupal project</value></term></terms>
<releases>
<release>
<name>Drupal 8.9.0</name>
<version>8.9.0</version>
<tag>DRUPAL-8-9-0</tag>
<status>published</status>
<release_link>http://example.com/drupal-8-9-0-release</release_link>
<download_link>http://example.com/drupal-8-9-0.tar.gz</download_link>
<date>1250424641</date>
<terms>
<term>
<name>Release type</name>
<value>New features</value>
</term>
<term>
<name>Release type</name>
<value>Bug fixes</value>
</term>
</terms>
</release>
<release>
<name>drupal 8.9.0-beta2</name>
<version>8.9.0-beta2</version>
<tag>8.9.0-beta2</tag>
<status>published</status>
<release_link>http://example.com/drupal-8-9-0-beta2-release</release_link>
<download_link>http://example.com/drupal-8-9-0-beta2.tar.gz</download_link>
<date>1533298080</date>
<terms>
<term><name>Release type</name><value>Bug fixes</value></term>
<term><name>Release type</name><value>New features</value></term>
</terms>
<security>
Beta releases are not covered by Drupal security advisories.
</security>
</release>
<release>
<name>Drupal 8.8.0</name>
<version>8.8.0</version>
<tag>DRUPAL-8-8-0</tag>
<status>published</status>
<release_link>http://example.com/drupal-8-8-0-release</release_link>
<download_link>http://example.com/drupal-8-8-0.tar.gz</download_link>
<date>1250424641</date>
<terms>
<term>
<name>Release type</name>
<value>New features</value>
</term>
<term>
<name>Release type</name>
<value>Bug fixes</value>
</term>
</terms>
</release>
<release>
<name>Drupal 8.1.0</name>
<version>8.1.0</version>
<tag>DRUPAL-8-1-0</tag>
<status>published</status>
<release_link>http://example.com/drupal-8-1-0-release</release_link>
<download_link>http://example.com/drupal-8-1-0.tar.gz</download_link>
<date>1250424521</date>
<terms>
<term>
<name>Release type</name>
<value>New features</value>
</term>
<term>
<name>Release type</name>
<value>Bug fixes</value>
</term>
</terms>
</release>
<release>
<name>Drupal 8.0.0</name>
<version>8.0.0</version>
<tag>DRUPAL-8-0-0</tag>
<status>published</status>
<release_link>http://example.com/drupal-8-0-0-release</release_link>
<download_link>http://example.com/drupal-8-0-0.tar.gz</download_link>
<date>1250424521</date>
<terms>
<term>
<name>Release type</name>
<value>New features</value>
</term>
<term>
<name>Release type</name>
<value>Bug fixes</value>
</term>
</terms>
</release>
</releases>
</project>

View File

@ -0,0 +1,107 @@
<?xml version="1.0" encoding="utf-8"?>
<project xmlns:dc="http://purl.org/dc/elements/1.1/">
<title>Drupal</title>
<short_name>drupal</short_name>
<dc:creator>Drupal</dc:creator>
<api_version>9.x</api_version>
<recommended_major>8</recommended_major>
<supported_majors>8,9</supported_majors>
<default_major>9</default_major>
<project_status>published</project_status>
<link>http://example.com/project/drupal</link>
<terms><term><name>Projects</name><value>Drupal project</value></term></terms>
<releases>
<release>
<name>Drupal 9.9.0</name>
<version>9.9.0</version>
<tag>DRUPAL-9-9-0</tag>
<status>published</status>
<release_link>http://example.com/drupal-9-9-0-release</release_link>
<download_link>http://example.com/drupal-9-9-0.tar.gz</download_link>
<date>1250424641</date>
<terms>
<term>
<name>Release type</name>
<value>New features</value>
</term>
<term>
<name>Release type</name>
<value>Bug fixes</value>
</term>
</terms>
</release>
<release>
<name>drupal 9.9.0-beta2</name>
<version>9.9.0-beta2</version>
<tag>9.9.0-beta2</tag>
<status>published</status>
<release_link>http://example.com/drupal-9-9-0-beta2-release</release_link>
<download_link>http://example.com/drupal-9-9-0-beta2.tar.gz</download_link>
<date>1533298080</date>
<terms>
<term><name>Release type</name><value>Bug fixes</value></term>
<term><name>Release type</name><value>New features</value></term>
</terms>
<security>
Beta releases are not covered by Drupal security advisories.
</security>
</release>
<release>
<name>Drupal 9.8.0</name>
<version>9.8.0</version>
<tag>DRUPAL-9-8-0</tag>
<status>published</status>
<release_link>http://example.com/drupal-9-8-0-release</release_link>
<download_link>http://example.com/drupal-9-8-0.tar.gz</download_link>
<date>1250424641</date>
<terms>
<term>
<name>Release type</name>
<value>New features</value>
</term>
<term>
<name>Release type</name>
<value>Bug fixes</value>
</term>
</terms>
</release>
<release>
<name>Drupal 9.1.0</name>
<version>9.1.0</version>
<tag>DRUPAL-9-1-0</tag>
<status>published</status>
<release_link>http://example.com/drupal-9-1-0-release</release_link>
<download_link>http://example.com/drupal-9-1-0.tar.gz</download_link>
<date>1250424521</date>
<terms>
<term>
<name>Release type</name>
<value>New features</value>
</term>
<term>
<name>Release type</name>
<value>Bug fixes</value>
</term>
</terms>
</release>
<release>
<name>Drupal 9.0.0</name>
<version>9.0.0</version>
<tag>DRUPAL-9-0-0</tag>
<status>published</status>
<release_link>http://example.com/drupal-9-0-0-release</release_link>
<download_link>http://example.com/drupal-9-0-0.tar.gz</download_link>
<date>1250424521</date>
<terms>
<term>
<name>Release type</name>
<value>New features</value>
</term>
<term>
<name>Release type</name>
<value>Bug fixes</value>
</term>
</terms>
</release>
</releases>
</project>

View File

@ -0,0 +1,22 @@
<?php
namespace Drupal\update_test\Datetime;
use Drupal\Component\Datetime\Time;
/**
* Test service for altering the request time.
*/
class TestTime extends Time {
/**
* {@inheritdoc}
*/
public function getRequestTime() {
if ($mock_date = \Drupal::state()->get('update_test.mock_date', NULL)) {
return \DateTime::createFromFormat('Y-m-d', $mock_date)->getTimestamp();
}
return parent::getRequestTime();
}
}

View File

@ -0,0 +1,4 @@
services:
datetime.time:
class: Drupal\update_test\Datetime\TestTime
arguments: ['@request_stack']

View File

@ -357,6 +357,225 @@ class UpdateCoreTest extends UpdateTestBase {
return $test_cases;
}
/**
* Tests the security coverage messages for Drupal core versions.
*
* @param string $installed_version
* The installed Drupal version to test.
* @param string $fixture
* The test fixture that contains the test XML.
* @param string $requirements_section_heading
* The requirements section heading.
* @param string $message
* The expected coverage message.
* @param string $mock_date
* The mock date to use if needed in the format CCYY-MM-DD. If an empty
* string is provided, no mock date will be used.
*
* @dataProvider securityCoverageMessageProvider
*/
public function testSecurityCoverageMessage($installed_version, $fixture, $requirements_section_heading, $message, $mock_date) {
\Drupal::state()->set('update_test.mock_date', $mock_date);
$this->setSystemInfo($installed_version);
$this->refreshUpdateStatus(['drupal' => $fixture]);
$this->drupalGet('admin/reports/status');
if (empty($requirements_section_heading)) {
$this->assertSession()->pageTextNotContains('Drupal core security coverage');
return;
}
$all_requirements_details = $this->getSession()->getPage()->findAll(
'css',
'details.system-status-report__entry:contains("Drupal core security coverage")'
);
// Ensure we only have 1 security message section.
$this->assertCount(1, $all_requirements_details);
$requirements_details = $all_requirements_details[0];
// Ensure that messages are under the correct heading which could be
// 'Checked', 'Warnings found', or 'Errors found'.
$requirements_section_element = $requirements_details->getParent();
$this->assertCount(1, $requirements_section_element->findAll('css', "h3:contains('$requirements_section_heading')"));
$actual_message = $requirements_details->find('css', 'div.description')->getText();
$this->assertNotEmpty($actual_message);
$this->assertEquals($message, $actual_message);
}
/**
* Dataprovider for testSecurityCoverageMessage().
*
* These test cases rely on the following fixtures containing the following
* releases:
* - drupal.sec.2.0_3.0-rc1.xml
* - 8.2.0
* - 8.3.0-rc1
* - drupal.sec.2.0.xml
* - 8.2.0
* - drupal.sec.2.0_9.0.0.xml
* - 8.2.0
* - 9.0.0
* - drupal.sec.9.0.xml
* - 8.9.0
* - drupal.sec.9.9.0.xml
* - 9.9.0
*/
public function securityCoverageMessageProvider() {
$release_coverage_message = 'Visit the release cycle overview for more information on supported releases.';
$see_available_message = 'See the available updates page for more information.';
$update_asap_message = 'Update to a supported minor as soon as possible to continue receiving security updates.';
$update_soon_message = 'Update to a supported minor version soon to continue receiving security updates.';
$test_cases = [
'8.0.0, unsupported' => [
'installed_version' => '8.0.0',
'fixture' => 'sec.2.0_3.0-rc1',
'requirements_section_heading' => 'Errors found',
'message' => "The installed minor version of Drupal (8.0), is no longer supported and will not receive security updates.$update_asap_message $see_available_message$release_coverage_message",
'mock_date' => '',
],
'8.1.0, supported with 3rc' => [
'installed_version' => '8.1.0',
'fixture' => 'sec.2.0_3.0-rc1',
'requirements_section_heading' => 'Warnings found',
'message' => "The installed minor version of Drupal (8.1), will stop receiving official security support after the release of 8.3.0.Update to 8.2 or higher soon to continue receiving security updates. $see_available_message$release_coverage_message",
'mock_date' => '',
],
'8.1.0, supported' => [
'installed_version' => '8.1.0',
'fixture' => 'sec.2.0',
'requirements_section_heading' => 'Warnings found',
'message' => "The installed minor version of Drupal (8.1), will stop receiving official security support after the release of 8.3.0.Update to 8.2 or higher soon to continue receiving security updates. $see_available_message$release_coverage_message",
'mock_date' => '',
],
'8.2.0, supported with 3rc' => [
'installed_version' => '8.2.0',
'fixture' => 'sec.2.0_3.0-rc1',
'requirements_section_heading' => 'Checked',
'message' => "The installed minor version of Drupal (8.2), will stop receiving official security support after the release of 8.4.0.$release_coverage_message",
'mock_date' => '',
],
'8.2.0, supported' => [
'installed_version' => '8.2.0',
'fixture' => 'sec.2.0',
'requirements_section_heading' => 'Checked',
'message' => "The installed minor version of Drupal (8.2), will stop receiving official security support after the release of 8.4.0.$release_coverage_message",
'mock_date' => '',
],
// Ensure we don't show messages for pre-release or dev versions.
'8.2.0-beta2, no message' => [
'installed_version' => '8.2.0-beta2',
'fixture' => 'sec.2.0_3.0-rc1',
'requirements_section_heading' => '',
'message' => '',
'mock_date' => '',
],
'8.1.0-dev, no message' => [
'installed_version' => '8.1.0-dev',
'fixture' => 'sec.2.0_3.0-rc1',
'requirements_section_heading' => '',
'message' => '',
'mock_date' => '',
],
// Ensures the message is correct if the next major version has been
// released and the additional minors indicated by
// CORE_MINORS_WITH_SECURITY_COVERAGE minors have been released.
'8.0.0, 9 unsupported' => [
'installed_version' => '8.0.0',
'fixture' => 'sec.2.0_9.0.0',
'requirements_section_heading' => 'Errors found',
'message' => "The installed minor version of Drupal (8.0), is no longer supported and will not receive security updates.$update_asap_message $see_available_message$release_coverage_message",
'mock_date' => '',
],
// Ensures the message is correct if the next major version has been
// released and the additional minors indicated by
// CORE_MINORS_WITH_SECURITY_COVERAGE minors have not been released.
'8.2.0, 9 warning' => [
'installed_version' => '8.2.0',
'fixture' => 'sec.2.0_9.0.0',
'requirements_section_heading' => 'Warnings found',
'message' => "The installed minor version of Drupal (8.2), will stop receiving official security support after the release of 8.4.0.Update to 8.3 or higher soon to continue receiving security updates. $see_available_message$release_coverage_message",
'mock_date' => '',
],
];
// Drupal 8.8.x test cases.
$test_cases += [
// Ensure that a message is displayed during 8.8's active support.
'8.8.0, supported' => [
'installed_version' => '8.8.0',
'fixture' => 'sec.9.0',
'requirements_section_heading' => 'Checked',
'message' => "The installed minor version of Drupal (8.8), will stop receiving official security support after 2020-12-02.$release_coverage_message",
'mock_date' => '2020-06-01',
],
// Ensure a warning is displayed if less than six months remain until the
// end of 8.8's security coverage.
'8.8.0, supported, 6 months warn' => [
'installed_version' => '8.8.0',
'fixture' => 'sec.9.0',
'requirements_section_heading' => 'Warnings found',
'message' => "The installed minor version of Drupal (8.8), will stop receiving official security support after 2020-12-02.$update_soon_message$release_coverage_message",
'mock_date' => '2020-06-02',
],
];
// Ensure that the message does not change, including on the last day of
// security coverage.
$test_cases['8.8.0, supported, last day warn'] = $test_cases['8.8.0, supported, 6 months warn'];
$test_cases['8.8.0, supported, last day warn']['mock_date'] = '2020-12-01';
// Ensure that if the 8.8 support window is finished a message is
// displayed.
$test_cases['8.8.0, support over'] = [
'installed_version' => '8.8.0',
'fixture' => 'sec.9.0',
'requirements_section_heading' => 'Errors found',
'message' => "The installed minor version of Drupal (8.8), is no longer supported and will not receive security updates.$update_asap_message $see_available_message$release_coverage_message",
'mock_date' => '2020-12-02',
];
// Drupal 8.9 LTS test cases.
$test_cases['8.9.0, lts supported'] = [
'installed_version' => '8.9.0',
'fixture' => 'sec.9.0',
'requirements_section_heading' => 'Checked',
'message' => "The installed minor version of Drupal (8.9), will stop receiving official security support after November 2021.$release_coverage_message",
'mock_date' => '2021-01-01',
];
// Ensure that the message does not change, including on the last day of
// security coverage.
$test_cases['8.9.0, lts supported, last day'] = $test_cases['8.9.0, lts supported'];
$test_cases['8.9.0, lts supported, last day']['mock_date'] = '2021-10-31';
// Ensure that if LTS support window is finished a message is displayed.
$test_cases['8.9.0, lts support over'] = [
'installed_version' => '8.9.0',
'fixture' => 'sec.9.0',
'requirements_section_heading' => 'Errors found',
'message' => "The installed minor version of Drupal (8.9), is no longer supported and will not receive security updates.$update_asap_message $see_available_message$release_coverage_message",
'mock_date' => '2021-11-01',
];
// Drupal 9 test cases.
$test_cases += [
// Ensure the end dates for 8.8 and 8.9 only apply to major version 8.
'9.9.0' => [
'installed_version' => '9.9.0',
'fixture' => 'sec.9.9.0',
'requirements_section_heading' => 'Checked',
'message' => "The installed minor version of Drupal (9.9), will stop receiving official security support after the release of 9.11.0.$release_coverage_message",
'mock_date' => '',
],
'9.8.0' => [
'installed_version' => '9.8.0',
'fixture' => 'sec.9.9.0',
'requirements_section_heading' => 'Warnings found',
'message' => "The installed minor version of Drupal (9.8), will stop receiving official security support after the release of 9.10.0.Update to 9.9 or higher soon to continue receiving security updates. $see_available_message$release_coverage_message",
'mock_date' => '',
],
];
return $test_cases;
}
/**
* Ensures proper results where there are date mismatches among modules.
*/

View File

@ -7,6 +7,8 @@
use Drupal\Core\Link;
use Drupal\Core\Url;
use Drupal\update\ProjectSecurityData;
use Drupal\update\ProjectSecurityRequirement;
use Drupal\update\UpdateFetcherInterface;
use Drupal\update\UpdateManagerInterface;
@ -40,6 +42,13 @@ function update_requirements($phase) {
$data = update_calculate_project_data($available);
// First, populate the requirements for core:
$requirements['update_core'] = _update_requirement_check($data['drupal'], 'core');
if (!empty($available['drupal']['releases'])) {
$security_data = ProjectSecurityData::createFromProjectDataAndReleases($data['drupal'], $available['drupal']['releases'])->getCoverageInfo();
if ($core_coverage_requirement = ProjectSecurityRequirement::createFromProjectDataAndSecurityCoverageInfo($data['drupal'], $security_data)->getRequirement()) {
$requirements['coverage_core'] = $core_coverage_requirement;
}
}
// We don't want to check drupal a second time.
unset($data['drupal']);
if (!empty($data)) {