From d84e5fb8da58d776989fb7a381031c578256122c Mon Sep 17 00:00:00 2001 From: lucashedding <lucashedding@1463982.no-reply.drupal.org> Date: Thu, 14 Nov 2019 11:31:55 -0600 Subject: [PATCH] Issue #3093782 by heddn, drumm, David Strauss, mbaynton, pwolanin: Use an external csig file, not packaged inside zip archive --- automatic_updates.module | 36 ++++++------- .../ReadinessCheckerManager.php | 3 +- src/Services/InPlaceUpdate.php | 50 +++++++++---------- 3 files changed, 42 insertions(+), 47 deletions(-) diff --git a/automatic_updates.module b/automatic_updates.module index 78de7ab724..98c5b52d2a 100644 --- a/automatic_updates.module +++ b/automatic_updates.module @@ -68,46 +68,42 @@ function automatic_updates_page_top(array &$page_top) { * Implements hook_cron(). */ function automatic_updates_cron() { - /** @var \Drupal\automatic_updates\Services\NotifyInterface $notify */ - $notify = \Drupal::service('automatic_updates.psa_notify'); - $notify->send(); - /** @var \Drupal\automatic_updates\ReadinessChecker\ReadinessCheckerManagerInterface $checker */ - $checker = \Drupal::service('automatic_updates.readiness_checker'); - foreach ($checker->getCategories() as $category) { - $checker->run($category); - } // In-place updates won't function for dev releases of Drupal core. - if (strpos(\Drupal::VERSION, '-dev') !== FALSE) { - return; - } + $dev_core = strpos(\Drupal::VERSION, '-dev') !== FALSE; /** @var \Drupal\Core\Config\ImmutableConfig $config */ $config = \Drupal::config('automatic_updates.settings'); - if ($config->get('enable_cron_updates')) { + if (!$dev_core && $config->get('enable_cron_updates')) { \Drupal::service('update.manager')->refreshUpdateData(); + \Drupal::service('update.processor')->fetchData(); $available = update_get_available(TRUE); $projects = update_calculate_project_data($available); $not_recommended_version = $projects['drupal']['status'] !== UpdateManagerInterface::CURRENT; - $dev_core = strpos(\Drupal::VERSION, '-dev') !== FALSE; $security_update = in_array($projects['drupal']['status'], [UpdateManagerInterface::NOT_SECURE, UpdateManagerInterface::REVOKED], TRUE); - $recommended_release = $projects['drupal']['releases'][$projects['drupal']['recommended']]; - $major_upgrade = $recommended_release['version_major'] !== $projects['drupal']['existing_major']; + $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; // Don't automatically update major version bumps or a dev version of core. - if ($major_upgrade || $dev_core) { - return; - } - if ($config->get('enable_cron_security_updates')) { + if (!$major_upgrade && $config->get('enable_cron_security_updates')) { if ($not_recommended_version && $security_update) { /** @var \Drupal\automatic_updates\Services\UpdateInterface $updater */ $updater = \Drupal::service('automatic_updates.update'); $updater->update('drupal', 'core', \Drupal::VERSION, $recommended_release['version']); } } - elseif ($not_recommended_version) { + elseif (!$major_upgrade && $not_recommended_version) { /** @var \Drupal\automatic_updates\Services\UpdateInterface $updater */ $updater = \Drupal::service('automatic_updates.update'); $updater->update('drupal', 'core', \Drupal::VERSION, $recommended_release['version']); } } + + /** @var \Drupal\automatic_updates\Services\NotifyInterface $notify */ + $notify = \Drupal::service('automatic_updates.psa_notify'); + $notify->send(); + /** @var \Drupal\automatic_updates\ReadinessChecker\ReadinessCheckerManagerInterface $checker */ + $checker = \Drupal::service('automatic_updates.readiness_checker'); + foreach ($checker->getCategories() as $category) { + $checker->run($category); + } } /** diff --git a/src/ReadinessChecker/ReadinessCheckerManager.php b/src/ReadinessChecker/ReadinessCheckerManager.php index 443d552b83..e4732dc713 100644 --- a/src/ReadinessChecker/ReadinessCheckerManager.php +++ b/src/ReadinessChecker/ReadinessCheckerManager.php @@ -71,8 +71,9 @@ class ReadinessCheckerManager implements ReadinessCheckerManagerInterface { } foreach ($this->getSortedCheckers()[$category] as $checker) { - $messages = array_merge($messages, $checker->run()); + $messages[] = $checker->run(); } + $messages = array_merge(...$messages); $this->keyValue->set("readiness_check_results.$category", $messages); $this->keyValue->set('readiness_check_timestamp', \Drupal::time()->getRequestTime()); return $messages; diff --git a/src/Services/InPlaceUpdate.php b/src/Services/InPlaceUpdate.php index a9f1b4c076..f1d6daf8ff 100644 --- a/src/Services/InPlaceUpdate.php +++ b/src/Services/InPlaceUpdate.php @@ -4,6 +4,7 @@ namespace Drupal\automatic_updates\Services; use Drupal\automatic_updates\ProjectInfoTrait; use Drupal\automatic_updates\ReadinessChecker\ReadinessCheckerManagerInterface; +use Drupal\Component\FileSystem\FileSystem; use Drupal\Core\Archiver\ArchiverInterface; use Drupal\Core\Archiver\ArchiverManager; use Drupal\Core\Config\ConfigFactoryInterface; @@ -28,11 +29,6 @@ class InPlaceUpdate implements UpdateInterface { */ 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. */ @@ -175,10 +171,17 @@ class InPlaceUpdate implements UpdateInterface { * The archive or NULL if download fails. */ protected function getArchive($project_name, $from_version, $to_version) { - $url = $this->buildUrl($project_name, $this->getQuasiPatchFileName($project_name, $from_version, $to_version)); - $destination = $this->fileSystem->realpath($this->fileSystem->getDestinationFilename("temporary://$project_name.zip", FileSystemInterface::EXISTS_RENAME)); - $this->doGetArchive($url, $destination); - /** @var \Drupal\Core\Archiver\ArchiverInterface $archive */ + $quasi_patch = $this->getQuasiPatchFileName($project_name, $from_version, $to_version); + $url = $this->buildUrl($project_name, $quasi_patch); + $temp_directory = $this->getTempDirectory(); + $destination = $this->fileSystem->getDestinationFilename($temp_directory . $quasi_patch, FileSystemInterface::EXISTS_REPLACE); + $this->doGetResource($url, $destination); + $csig_file = $quasi_patch . '.csig'; + $csig_url = $this->buildUrl($project_name, $csig_file); + $csig_destination = $this->fileSystem->getDestinationFilename(FileSystem::getOsTemporaryDirectory() . DIRECTORY_SEPARATOR . $csig_file, FileSystemInterface::EXISTS_REPLACE); + $this->doGetResource($csig_url, $csig_destination); + $csig = file_get_contents($csig_destination); + $this->validateArchive($temp_directory, $csig); return $this->archiveManager->getInstance(['filepath' => $destination]); } @@ -215,7 +218,6 @@ class InPlaceUpdate implements UpdateInterface { foreach ($archive_files as $index => &$archive_file) { $skipped_files = [ self::DELETION_MANIFEST, - self::CHECKSUM_LIST, ]; // Skip certain files and all directories. if (in_array($archive_file, $skipped_files, TRUE) || substr($archive_file, -1) === '/') { @@ -244,7 +246,7 @@ class InPlaceUpdate implements UpdateInterface { * @param null|int $delay * The delay, defaults to NULL. */ - protected function doGetArchive($url, $destination, $delay = NULL) { + protected function doGetResource($url, $destination, $delay = NULL) { try { $this->httpClient->get($url, [ 'sink' => $destination, @@ -253,14 +255,15 @@ class InPlaceUpdate implements UpdateInterface { } catch (RequestException $exception) { $response = $exception->getResponse(); - if (!$response || ($response->getStatusCode() === 429 && ($retry = $response->getHeader('Retry-After')))) { - $this->doGetArchive($url, $destination, $retry[0] ?? 10 * 1000); + if (!$response || ($retry = $response->getHeader('Retry-After'))) { + $this->doGetResource($url, $destination, !empty($retry[0]) ? $retry[0] : 10 * 1000); } else { $this->logger->error('Retrieval of "@url" failed with: @message', [ - '@url' => $url, + '@url' => $exception->getRequest()->getUri(), '@message' => $exception->getMessage(), ]); + throw $exception; } } } @@ -278,7 +281,6 @@ class InPlaceUpdate implements UpdateInterface { */ protected function processUpdate(ArchiverInterface $archive, $project_root) { $archive->extract($this->getTempDirectory()); - $this->validateArchive($this->getTempDirectory()); foreach ($this->getFilesList($this->getTempDirectory()) as $file) { $file_real_path = $this->getFileRealPath($file); $file_path = substr($file_real_path, strlen($this->getTempDirectory() . self::ARCHIVE_DIRECTORY)); @@ -311,17 +313,14 @@ class InPlaceUpdate implements UpdateInterface { * * @param string $directory * The location of the downloaded archive. + * @param string $csig + * The CSIG contents. */ - protected 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 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)) { @@ -455,7 +454,6 @@ class InPlaceUpdate implements UpdateInterface { } $skipped_files = [ self::DELETION_MANIFEST, - self::CHECKSUM_LIST, ]; return $file->isFile() && !in_array($file->getFilename(), $skipped_files, TRUE); }; @@ -540,9 +538,9 @@ class InPlaceUpdate implements UpdateInterface { */ protected function getTempDirectory() { if (!$this->tempDirectory) { - $this->tempDirectory = $this->fileSystem->createFilename('automatic_updates-update', 'temporary://'); - $this->fileSystem->prepareDirectory($this->tempDirectory); - $this->tempDirectory = $this->fileSystem->realpath($this->tempDirectory) . DIRECTORY_SEPARATOR; + $this->tempDirectory = $this->fileSystem->createFilename('automatic_updates-update', FileSystem::getOsTemporaryDirectory()); + $this->fileSystem->prepareDirectory($this->tempDirectory, FileSystemInterface::CREATE_DIRECTORY); + $this->tempDirectory .= DIRECTORY_SEPARATOR; } return $this->tempDirectory; } -- GitLab