diff --git a/AutomaticUpdatesPsa.php b/AutomaticUpdatesPsa.php index 9290631..de068a7 100644 --- a/AutomaticUpdatesPsa.php +++ b/AutomaticUpdatesPsa.php @@ -6,7 +6,6 @@ */ use Composer\Semver\VersionParser; -use Drupal\Component\Version\Constraint; /** * Class AutomaticUpdatesPsa. @@ -21,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,11 +43,20 @@ class AutomaticUpdatesPsa { $json_payload = json_decode($response->data); if (!is_null($json_payload)) { foreach ($json_payload as $json) { - if ($json->project === 'core') { - static::coreParser($messages, $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); + } } - else { - static::contribParser($messages, $json); + catch (\UnexpectedValueException $exception) { + watchdog_exception('automatic_updates', $exception); + $messages[] = t('Drupal PSA endpoint service is malformed.'); } } } @@ -57,14 +65,44 @@ 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 (count($version_array) === 2 && $version_array[0] === DRUPAL_CORE_COMPATIBILITY) { + return $version_array[1]; + } + }, $json->insecure)); + $version_array = explode('-', $info['version'], 2); + $extension_version = array_pop($version_array); + 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 +110,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: %message', 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: %message', array( - '%message' => $json->title, - '!url' => $json->link, - )); - } + protected static function message($title, $link) { + return format_string('@message', [ + '@message' => $title, + '@url' => $link, + ]); } } diff --git a/tests/automatic_updates_test.module b/tests/automatic_updates_test.module index 3127310..1980c8c 100644 --- a/tests/automatic_updates_test.module +++ b/tests/automatic_updates_test.module @@ -26,80 +26,92 @@ 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', '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); }