Commit 9d66fbfb authored by heddn's avatar heddn Committed by heddn
Browse files

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

Issue #3045273 by heddn, tatarbj, mlhess, catch: Add real endpoint after drupal.org provides a live feed
parent 2ffaa9a6
......@@ -6,16 +6,13 @@
*/
use Composer\Semver\VersionParser;
use Drupal\Component\Version\Constraint;
/**
* Class AutomaticUpdatesPsa.
*/
class AutomaticUpdatesPsa {
// @TODO: update once real endpoint is available. See
// https://www.drupal.org/project/automatic_updates/issues/3045273
const PSA_ENDPOINT = 'http://localhost/automatic_updates/test-json';
const PSA_ENDPOINT = 'https://updates.drupal.org/psa.json';
/**
* {@inheritdoc}
......@@ -23,7 +20,7 @@ class AutomaticUpdatesPsa {
public static function getPublicServiceMessages() {
$messages = array();
if (!variable_get('automatic_updates_enable_psa')) {
if (!variable_get('automatic_updates_enable_psa', TRUE)) {
return $messages;
}
......@@ -44,12 +41,23 @@ class AutomaticUpdatesPsa {
}
$json_payload = json_decode($response->data);
foreach ($json_payload as $json) {
if ($json->project === 'core') {
static::coreParser($messages, $json);
}
else {
static::contribParser($messages, $json);
if (!is_null($json_payload)) {
foreach ($json_payload as $json) {
try {
if ($json->is_psa && ($json->type === 'core' || static::isValidExtension($json->type, $json->project))) {
$messages[] = static::message($json->title, $json->link);
}
elseif ($json->type === 'core') {
static::parseConstraints($messages, $json, VERSION);
}
elseif (static::isValidExtension($json->type, $json->project)) {
static::contribParser($messages, $json);
}
}
catch (\UnexpectedValueException $exception) {
watchdog_exception('automatic_updates', $exception);
$messages[] = t('Drupal PSA endpoint service is malformed.');
}
}
}
......@@ -57,14 +65,50 @@ class AutomaticUpdatesPsa {
}
/**
* Parse core project JSON version strings.
* Parse contrib project JSON version strings.
*
* @param array $messages
* The messages array.
* @param object $json
* The JSON object.
*/
protected static function contribParser(array &$messages, $json) {
$extension_path = drupal_get_path($json->type, $json->project);
$info = drupal_parse_info_file($extension_path . DIRECTORY_SEPARATOR . $json->project . '.info');
$json->insecure = array_filter(array_map(function ($version) {
$version_array = explode('-', $version, 2);
if ($version_array && $version_array[0] === DRUPAL_CORE_COMPATIBILITY) {
return isset($version_array[1]) ? $version_array[1] : NULL;
}
elseif (count($version_array) === 1) {
return $version_array[0];
}
elseif (count($version_array) === 2 && $version_array[1] === 'dev') {
return $version;
}
}, $json->insecure));
$version_array = explode('-', $info['version'], 2);
$extension_version = isset($version_array[1]) && $version_array[1] !== 'dev' ? $version_array[1] : $info['version'];
static::parseConstraints($messages, $json, $extension_version);
}
/**
* Compare versions and add a message, if appropriate.
*
* @param array $messages
* The messages array.
* @param object $json
* The JSON object.
* @param string $current_version
* The current extension version.
*
* @throws \UnexpectedValueException
*/
protected static function coreParser(array &$messages, $json) {
protected static function parseConstraints(array &$messages, $json, $current_version) {
$version_string = implode('||', $json->insecure);
if (empty($version_string)) {
return;
}
$module_directory = drupal_get_path('module', 'automatic_updates');
include_once $module_directory . '/semver/src/Constraint/ConstraintInterface.php';
include_once $module_directory . '/semver/src/Constraint/Constraint.php';
......@@ -72,76 +116,48 @@ class AutomaticUpdatesPsa {
include_once $module_directory . '/semver/src/Constraint/MultiConstraint.php';
include_once $module_directory . '/semver/src/VersionParser.php';
$parser = new VersionParser();
// Remove Drupal 8 version.
$json->secure_versions = array_filter($json->secure_versions, function ($version) {
return substr($version, 0, 1) == 7;
});
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(VERSION);
if ($psa_constraint->matches($core_constraint)) {
$messages[] = t('Drupal Core PSA: <a href="!url">%message</a>', array(
'%message' => $json->title,
'!url' => $json->link,
));
$contrib_constraint = $parser->parseConstraints($current_version);
if ($psa_constraint->matches($contrib_constraint)) {
$messages[] = static::message($json->title, $json->link);
}
}
/**
* Parse contrib 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.
* @param string $project_name
* The project.
*
* @return bool
* TRUE if extension exists, else FALSE.
*/
protected static function contribParser(array &$messages, $json) {
array_walk($json->secure_versions, function (&$version) {
if (substr($version, 0, 4) === DRUPAL_CORE_COMPATIBILITY . '-') {
$version = substr($version, 4);
}
});
$modules = array_keys(module_list());
$themes = array_keys(list_themes());
$extensions = array_merge($modules, $themes);
foreach ($json->extensions as $extension_name) {
if (in_array($extension_name, $extensions)) {
foreach (array('module', 'theme') as $type) {
// If an extension doesn't exist, errors are triggered. Hide them.
$extension_path = @drupal_get_path($type, $extension_name);
if ($extension_path && ($info = drupal_parse_info_file($extension_path . DIRECTORY_SEPARATOR . $extension_name . '.info'))) {
static::contribMessage($messages, $json, $info['version']);
}
}
}
protected static function isValidExtension($extension_type, $project_name) {
$extension_path = @drupal_get_path($extension_type, $project_name);
if ($extension_path && ($info = drupal_parse_info_file($extension_path . DIRECTORY_SEPARATOR . $project_name . '.info'))) {
return isset($info['version']);
}
return FALSE;
}
/**
* Add a contrib message PSA, if appropriate.
* Return a message.
*
* @param array $messages
* The messages array.
* @param object $json
* The JSON object.
* @param string $extension_version
* The extension version.
* @param string $title
* The title.
* @param string $link
* The link.
*
* @return string
* The PSA or SA message.
*/
protected static function contribMessage(array &$messages, $json, $extension_version) {
$module_directory = drupal_get_path('module', 'automatic_updates');
include_once $module_directory . '/Drupal/Component/Version/Constraint.php';
$version_string = implode('||', $json->secure_versions);
$constraint = new Constraint("<$extension_version", DRUPAL_CORE_COMPATIBILITY);
if (!$constraint->isCompatible($version_string)) {
$messages[] = t('Drupal Contrib Project PSA: <a href="!url">%message</a>', array(
'%message' => $json->title,
'!url' => $json->link,
));
}
protected static function message($title, $link) {
return format_string('<a href="@url">@message</a>', [
'@message' => $title,
'@url' => $link,
]);
}
}
......@@ -14,7 +14,7 @@ function automatic_updates_admin_form() {
];
$form['automatic_updates_enable_psa'] = [
'#type' => 'checkbox',
'#title' => t('Show public service announcements on administrative pages.'),
'#title' => t('Show Public service announcements on administrative pages.'),
'#default_value' => variable_get('automatic_updates_enable_psa', TRUE),
];
$last_check_timestamp = ReadinessCheckerManager::timestamp();
......
name= Automatic Updates
type = module
description = Drupal Automatic Updates
description = Display Public service announcements and verify readiness for applying automatic updates to the site.
core = 7.x
php = 5.6
package = Security
......
......@@ -78,7 +78,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.
......@@ -88,7 +88,7 @@ function _automatic_updates_psa_requirements(array &$requirements) {
return;
}
$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.'),
];
......
......@@ -29,7 +29,7 @@ function automatic_updates_init() {
$messages = AutomaticUpdatesPsa::getPublicServiceMessages();
if ($messages) {
drupal_set_message(t('Drupal public service announcements:'), 'error', FALSE);
drupal_set_message(t('Public service announcements:'), 'error', FALSE);
foreach ($messages as $message) {
drupal_set_message($message, 'error', FALSE);
}
......
......@@ -44,16 +44,15 @@ class AutomaticUpdatesTestCase extends DrupalWebTestCase {
public function testAutomaticUpdates() {
// Test PSAs.
$this->drupalGet('admin');
$this->assertText('Drupal Core PSA: Critical Release - PSA-2019-02-19');
$this->assertNoText('Drupal Core PSA: Critical Release - PSA-Really Old');
$this->assertText('Critical Release - SA-2019-02-19');
$this->assertText('Seven - Moderately critical - Access bypass - SA-CONTRIB-2019');
$this->assertText('Critical Release - PSA-Really Old');
$this->assertNoText('Node - Moderately critical - Access bypass - SA-CONTRIB-2019');
$this->assertText('Drupal Contrib Project PSA: Standard - Moderately critical - Access bypass - SA-CONTRIB-2019');
$this->assertText('Drupal Contrib Project PSA: Seven - Moderately critical - Access bypass - SA-CONTRIB-2019');
// Test disabling PSAs.
variable_set('automatic_updates_enable_psa', FALSE);
$this->drupalGet('admin');
$this->assertNoText('Drupal Core PSA: Critical Release - PSA-2019-02-19');
$this->assertNoText('Critical Release - SA-2019-02-19');
variable_set('automatic_updates_enable_psa', TRUE);
// Test site status report.
......@@ -64,7 +63,7 @@ class AutomaticUpdatesTestCase extends DrupalWebTestCase {
$psa_endpoint = $this->getAbsoluteUrl('automatic_updates/test-json-denied');
variable_set('automatic_updates_psa_endpoint', $psa_endpoint);
$this->drupalGet('admin');
$this->assertText('Drupal Core PSA: Critical Release - PSA-2019-02-19');
$this->assertText('Critical Release - SA-2019-02-19');
// Test transmit errors with JSON endpoint.
drupal_flush_all_caches();
......@@ -80,7 +79,7 @@ class AutomaticUpdatesTestCase extends DrupalWebTestCase {
$this->drupalGet($this->getAbsoluteUrl('admin/config/system/automatic_updates'));
$this->clickLink('run the readiness checks');
$this->assertText('Your site does not pass some readiness checks for automatic updates. Depending on the nature of the failures, it might effect the eligibility for automatic updates.');
$this->assertText('The project "drupal_autoload_test" can not be updated because its version is either undefined or a dev release');
$this->assertText('The project "automatic_updates" can not be updated because its version is either undefined or a dev release');
// Ignore certain folders for readiness checks.
variable_set('automatic_updates_ignored_paths', "modules/*\nthemes/*\nprofiles/*\nsites/all/modules/*");
......
......@@ -26,80 +26,99 @@ function automatic_updates_test_menu() {
* Page callback for test JSON.
*/
function automatic_updates_test_json() {
$feed = array();
$feed[] = array(
'title' => 'Critical Release - PSA-2019-02-19',
'link' => 'https://www.drupal.org/psa-2019-02-19',
'project' => 'core',
'extensions' => array(),
'type' => 'module',
'secure_versions' => array(
'7.999',
'8.10.99',
'8.9.99',
'8.8.99',
'8.7.99',
'8.6.99',
'8.5.99',
),
$feed = [];
$feed[] = [
'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',
VERSION,
],
'is_psa' => '0',
'pubDate' => 'Tue, 19 Feb 2019 14:11:01 +0000',
);
$feed[] = array(
];
$feed[] = [
'title' => 'Critical Release - PSA-Really Old',
'link' => 'https://www.drupal.org/psa',
'project' => 'core',
'extensions' => array(),
'type' => 'module',
'secure_versions' => array(
'7.22',
'8.4.0',
),
'project' => 'drupal',
'type' => 'core',
'is_psa' => '1',
'insecure' => [],
'pubDate' => 'Tue, 19 Feb 2019 14:11:01 +0000',
);
$feed[] = array(
];
$feed[] = [
'title' => 'Node - Moderately critical - Access bypass - SA-CONTRIB-2019',
'link' => 'https://www.drupal.org/sa-contrib-2019',
'project' => 'node',
'extensions' => array('node'),
'type' => 'module',
'secure_versions' => array('7.x-7.22', '8.x-8.8.0'),
'pubDate' => 'Tue, 19 Mar 2019 12:50:00 +0000',
);
$feed[] = array(
'title' => 'Standard - Moderately critical - Access bypass - SA-CONTRIB-2019',
'link' => 'https://www.drupal.org/sa-contrib-2019',
'project' => 'Standard Install Profile',
'extensions' => array('standard'),
'type' => 'profile',
'secure_versions' => array('7.x-7.999'),
'is_psa' => '0',
'insecure' => [
'7.x-7.22',
'7.x-7.22-alpha',
'7.x-7.22-beta1',
'8.7.0-beta1',
'8.7.0-beta2',
'8.x-8.2.0',
],
'pubDate' => 'Tue, 19 Mar 2019 12:50:00 +0000',
);
$feed[] = array(
];
$feed[] = [
'title' => 'Seven - Moderately critical - Access bypass - SA-CONTRIB-2019',
'link' => 'https://www.drupal.org/sa-contrib-2019',
'project' => 'seven',
'extensions' => array('seven'),
'type' => 'theme',
'secure_versions' => array('7.x-7.999'),
'is_psa' => '0',
'insecure' => ['8.x-8.7.0', VERSION],
'pubDate' => 'Tue, 19 Mar 2019 12:50:00 +0000',
);
$feed[] = array(
];
$feed[] = [
'title' => 'Foobar - Moderately critical - Access bypass - SA-CONTRIB-2019',
'link' => 'https://www.drupal.org/sa-contrib-2019',
'project' => 'foobar',
'extensions' => array('foobar'),
'type' => 'foobar',
'secure_versions' => array('7.x-1.2'),
'is_psa' => '1',
'insecure' => [],
'pubDate' => 'Tue, 19 Mar 2019 12:50:00 +0000',
);
$feed[] = array(
];
$feed[] = [
'title' => 'Token - Moderately critical - Access bypass - SA-CONTRIB-2019',
'link' => 'https://www.drupal.org/sa-contrib-2019',
'project' => 'token',
'extensions' => array('token'),
'type' => 'module',
'secure_versions' => array('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 drupal_json_output($feed);
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment