Commit 7d13c121 authored by Lucas Hedding's avatar Lucas Hedding Committed by Lucas Hedding
Browse files

Issue #3093782 by heddn, drumm, David Strauss, mbaynton, pwolanin: Use an...

Issue #3093782 by heddn, drumm, David Strauss, mbaynton, pwolanin: Use an external csig file, not packaged inside zip archive
parent 64c950f5
......@@ -15,11 +15,6 @@ class InPlaceUpdate {
*/
const DELETION_MANIFEST = 'DELETION_MANIFEST.txt';
/**
* The checksum file with hashes of archive file contents.
*/
const CHECKSUM_LIST = 'checksumlist.csig';
/**
* The directory inside the archive for file additions and modifications.
*/
......@@ -93,9 +88,17 @@ class InPlaceUpdate {
* The archive.
*/
protected static function getArchive($project_name, $from_version, $to_version) {
$url = self::buildUrl($project_name, self::getQuasiPatchFileName($project_name, $from_version, $to_version));
$destination = drupal_realpath(file_destination("temporary://$project_name.zip", FILE_EXISTS_RENAME));
self::doGetArchive($url, $destination);
$quasi_patch = self::getQuasiPatchFileName($project_name, $from_version, $to_version);
$url = self::buildUrl($project_name, $quasi_patch);
$temp_directory = self::getTempDirectory();
$destination = file_destination($temp_directory . $quasi_patch, FILE_EXISTS_REPLACE);
self::doGetResource($url, $destination);
$csig_file = $quasi_patch . '.csig';
$csig_url = self::buildUrl($project_name, $csig_file);
$csig_destination = drupal_realpath(file_destination('temporary://' . $csig_file, FILE_EXISTS_REPLACE));
self::doGetResource($csig_url, $csig_destination);
$csig = file_get_contents($csig_destination);
self::validateArchive($temp_directory, $csig);
if (file_exists($destination)) {
return new \ArchiverZip($destination);
}
......@@ -127,8 +130,7 @@ class InPlaceUpdate {
$archive_files = $archive->listContents();
foreach ($archive_files as $index => &$archive_file) {
$skipped_files = [
self::DELETION_MANIFEST,
self::CHECKSUM_LIST,
self::DELETION_MANIFEST
];
// Skip certain files and all directories.
if (in_array($archive_file, $skipped_files, TRUE) || substr($archive_file, -1) === '/') {
......@@ -157,12 +159,12 @@ class InPlaceUpdate {
* @param null|int $delay
* The delay, defaults to NULL.
*/
protected static function doGetArchive($url, $destination, $delay = 0) {
protected static function doGetResource($url, $destination, $delay = 0) {
sleep($delay);
$result = drupal_http_request($url);
if ($result->code === '429' || isset($result->headers['retry-after'])) {
$retry = $result->headers['retry-after'];
self::doGetArchive($url, $destination, $retry);
self::doGetResource($url, $destination, $retry);
}
elseif ($result->code !== '200') {
watchdog('automatic_updates', 'Retrieval of "@url" failed with: @message', [
......@@ -188,7 +190,6 @@ class InPlaceUpdate {
*/
protected static function processUpdate(\ArchiverZip $archive, $project_root) {
$archive->extract(self::getTempDirectory());
self::validateArchive(self::getTempDirectory());
foreach (self::getFilesList(self::getTempDirectory()) as $file) {
$file_real_path = self::getFileRealPath($file);
$file_path = substr($file_real_path, strlen(self::getTempDirectory() . self::ARCHIVE_DIRECTORY));
......@@ -211,17 +212,16 @@ class InPlaceUpdate {
*
* @param string $directory
* The location of the downloaded archive.
* @param string $csig
* The CSIG contents.
*
* @throws \SodiumException
*/
protected static function validateArchive($directory) {
$csig_file = $directory . DIRECTORY_SEPARATOR . self::CHECKSUM_LIST;
if (!file_exists($csig_file)) {
throw new \RuntimeException('The CSIG file does not exist in the archive.');
}
$contents = file_get_contents($csig_file);
protected static function validateArchive($directory, $csig) {
$module_path = drupal_get_path('module', 'automatic_updates');
$key = file_get_contents($module_path . '/artifacts/keys/root.pub');
$verifier = new Verifier($key);
$files = $verifier->verifyCsigMessage($contents);
$files = $verifier->verifyCsigMessage($csig);
$checksums = new ChecksumList($files, TRUE);
$failed_checksums = new FailedCheckumFilter($checksums, $directory);
if (iterator_count($failed_checksums)) {
......@@ -353,7 +353,6 @@ class InPlaceUpdate {
}
$skipped_files = [
self::DELETION_MANIFEST,
self::CHECKSUM_LIST,
];
return $file->isFile() && !in_array($file->getFilename(), $skipped_files, TRUE);
};
......@@ -436,7 +435,7 @@ class InPlaceUpdate {
protected static function getTempDirectory() {
if (!self::$tempDirectory) {
self::$tempDirectory = file_create_filename('automatic_updates-update', 'temporary://');
file_prepare_directory(self::$tempDirectory);
file_prepare_directory(self::$tempDirectory, FILE_CREATE_DIRECTORY);
self::$tempDirectory = drupal_realpath(self::$tempDirectory) . DIRECTORY_SEPARATOR;
}
return self::$tempDirectory;
......
......@@ -55,8 +55,9 @@ class ReadinessCheckerManager {
}
foreach (static::getSortedCheckers()[$category] as $checker) {
$messages = array_merge($messages, $checker::run());
$messages[] = $checker::run();
}
$messages = array_merge(...$messages);
// Guard against variable_set stampede by checking if the values have
// changed since previous run.
$previous_messages = variable_get("automatic_updates.readiness_check_results.$category");
......@@ -98,7 +99,7 @@ class ReadinessCheckerManager {
* {@inheritdoc}
*/
public static function getCategories() {
return ['warning', 'error'];
return ['error', 'warning'];
}
/**
......
......@@ -48,6 +48,7 @@ function automatic_updates_admin_form() {
];
update_refresh();
update_fetch_data();
$available = update_get_available(TRUE);
$projects = update_calculate_project_data($available);
$not_recommended_version = $projects['drupal']['status'] !== UPDATE_CURRENT;
......
......@@ -93,35 +93,31 @@ function automatic_updates_menu() {
* Implements hook_cron().
*/
function automatic_updates_cron() {
Notify::send();
foreach (ReadinessCheckerManager::getCategories() as $category) {
ReadinessCheckerManager::run($category);
}
// In-place updates won't function for dev releases of Drupal core.
if (strpos(VERSION, '-dev') !== FALSE) {
return;
}
if (variable_get('automatic_updates_enable_cron_updates', FALSE)) {
$dev_core = strpos(VERSION, '-dev') !== FALSE;
if (!$dev_core && variable_get('automatic_updates_enable_cron_updates', FALSE)) {
update_refresh();
update_fetch_data();
$available = update_get_available(TRUE);
$projects = update_calculate_project_data($available);
$not_recommended_version = $projects['drupal']['status'] !== UPDATE_CURRENT;
$dev_core = strpos(VERSION, '-dev') !== FALSE;
$security_update = in_array($projects['drupal']['status'], [UPDATE_NOT_SECURE, UPDATE_REVOKED], TRUE);
$recommended_release = $projects['drupal']['releases'][$projects['drupal']['recommended']];
$major_upgrade = $recommended_release['version_major'] !== $projects['drupal']['existing_major'];
// Don't automatically update major version bumps or a dev version of core.
if ($major_upgrade || $dev_core) {
return;
}
if (variable_get('automatic_updates_enable_cron_security_updates', FALSE)) {
$recommended_release = isset($projects['drupal']['releases'][$projects['drupal']['recommended']]) ? $projects['drupal']['releases'][$projects['drupal']['recommended']] : NULL;
$major_upgrade = isset($recommended_release['version_major']) ? $recommended_release['version_major'] !== $projects['drupal']['existing_major'] : TRUE;
if (!$major_upgrade && variable_get('automatic_updates_enable_cron_security_updates', FALSE)) {
if ($not_recommended_version && $security_update) {
InPlaceUpdate::update('drupal', 'core', VERSION, $recommended_release['version']);
}
}
elseif ($not_recommended_version) {
elseif (!$major_upgrade && $not_recommended_version) {
InPlaceUpdate::update('drupal', 'core', VERSION, $recommended_release['version']);
}
}
Notify::send();
foreach (ReadinessCheckerManager::getCategories() as $category) {
ReadinessCheckerManager::run($category);
}
}
/**
......
Supports Markdown
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