Skip to content
Snippets Groups Projects
Commit d84e5fb8 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 08792050
No related branches found
No related tags found
No related merge requests found
...@@ -68,46 +68,42 @@ function automatic_updates_page_top(array &$page_top) { ...@@ -68,46 +68,42 @@ function automatic_updates_page_top(array &$page_top) {
* Implements hook_cron(). * Implements hook_cron().
*/ */
function automatic_updates_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. // In-place updates won't function for dev releases of Drupal core.
if (strpos(\Drupal::VERSION, '-dev') !== FALSE) { $dev_core = strpos(\Drupal::VERSION, '-dev') !== FALSE;
return;
}
/** @var \Drupal\Core\Config\ImmutableConfig $config */ /** @var \Drupal\Core\Config\ImmutableConfig $config */
$config = \Drupal::config('automatic_updates.settings'); $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.manager')->refreshUpdateData();
\Drupal::service('update.processor')->fetchData();
$available = update_get_available(TRUE); $available = update_get_available(TRUE);
$projects = update_calculate_project_data($available); $projects = update_calculate_project_data($available);
$not_recommended_version = $projects['drupal']['status'] !== UpdateManagerInterface::CURRENT; $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); $security_update = in_array($projects['drupal']['status'], [UpdateManagerInterface::NOT_SECURE, UpdateManagerInterface::REVOKED], TRUE);
$recommended_release = $projects['drupal']['releases'][$projects['drupal']['recommended']]; $recommended_release = isset($projects['drupal']['releases'][$projects['drupal']['recommended']]) ? $projects['drupal']['releases'][$projects['drupal']['recommended']] : NULL;
$major_upgrade = $recommended_release['version_major'] !== $projects['drupal']['existing_major']; $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. // Don't automatically update major version bumps or a dev version of core.
if ($major_upgrade || $dev_core) { if (!$major_upgrade && $config->get('enable_cron_security_updates')) {
return;
}
if ($config->get('enable_cron_security_updates')) {
if ($not_recommended_version && $security_update) { if ($not_recommended_version && $security_update) {
/** @var \Drupal\automatic_updates\Services\UpdateInterface $updater */ /** @var \Drupal\automatic_updates\Services\UpdateInterface $updater */
$updater = \Drupal::service('automatic_updates.update'); $updater = \Drupal::service('automatic_updates.update');
$updater->update('drupal', 'core', \Drupal::VERSION, $recommended_release['version']); $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 */ /** @var \Drupal\automatic_updates\Services\UpdateInterface $updater */
$updater = \Drupal::service('automatic_updates.update'); $updater = \Drupal::service('automatic_updates.update');
$updater->update('drupal', 'core', \Drupal::VERSION, $recommended_release['version']); $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);
}
} }
/** /**
......
...@@ -71,8 +71,9 @@ class ReadinessCheckerManager implements ReadinessCheckerManagerInterface { ...@@ -71,8 +71,9 @@ class ReadinessCheckerManager implements ReadinessCheckerManagerInterface {
} }
foreach ($this->getSortedCheckers()[$category] as $checker) { 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_results.$category", $messages);
$this->keyValue->set('readiness_check_timestamp', \Drupal::time()->getRequestTime()); $this->keyValue->set('readiness_check_timestamp', \Drupal::time()->getRequestTime());
return $messages; return $messages;
......
...@@ -4,6 +4,7 @@ namespace Drupal\automatic_updates\Services; ...@@ -4,6 +4,7 @@ namespace Drupal\automatic_updates\Services;
use Drupal\automatic_updates\ProjectInfoTrait; use Drupal\automatic_updates\ProjectInfoTrait;
use Drupal\automatic_updates\ReadinessChecker\ReadinessCheckerManagerInterface; use Drupal\automatic_updates\ReadinessChecker\ReadinessCheckerManagerInterface;
use Drupal\Component\FileSystem\FileSystem;
use Drupal\Core\Archiver\ArchiverInterface; use Drupal\Core\Archiver\ArchiverInterface;
use Drupal\Core\Archiver\ArchiverManager; use Drupal\Core\Archiver\ArchiverManager;
use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Config\ConfigFactoryInterface;
...@@ -28,11 +29,6 @@ class InPlaceUpdate implements UpdateInterface { ...@@ -28,11 +29,6 @@ class InPlaceUpdate implements UpdateInterface {
*/ */
const DELETION_MANIFEST = 'DELETION_MANIFEST.txt'; 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. * The directory inside the archive for file additions and modifications.
*/ */
...@@ -175,10 +171,17 @@ class InPlaceUpdate implements UpdateInterface { ...@@ -175,10 +171,17 @@ class InPlaceUpdate implements UpdateInterface {
* The archive or NULL if download fails. * The archive or NULL if download fails.
*/ */
protected function getArchive($project_name, $from_version, $to_version) { protected function getArchive($project_name, $from_version, $to_version) {
$url = $this->buildUrl($project_name, $this->getQuasiPatchFileName($project_name, $from_version, $to_version)); $quasi_patch = $this->getQuasiPatchFileName($project_name, $from_version, $to_version);
$destination = $this->fileSystem->realpath($this->fileSystem->getDestinationFilename("temporary://$project_name.zip", FileSystemInterface::EXISTS_RENAME)); $url = $this->buildUrl($project_name, $quasi_patch);
$this->doGetArchive($url, $destination); $temp_directory = $this->getTempDirectory();
/** @var \Drupal\Core\Archiver\ArchiverInterface $archive */ $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]); return $this->archiveManager->getInstance(['filepath' => $destination]);
} }
...@@ -215,7 +218,6 @@ class InPlaceUpdate implements UpdateInterface { ...@@ -215,7 +218,6 @@ class InPlaceUpdate implements UpdateInterface {
foreach ($archive_files as $index => &$archive_file) { foreach ($archive_files as $index => &$archive_file) {
$skipped_files = [ $skipped_files = [
self::DELETION_MANIFEST, self::DELETION_MANIFEST,
self::CHECKSUM_LIST,
]; ];
// Skip certain files and all directories. // Skip certain files and all directories.
if (in_array($archive_file, $skipped_files, TRUE) || substr($archive_file, -1) === '/') { if (in_array($archive_file, $skipped_files, TRUE) || substr($archive_file, -1) === '/') {
...@@ -244,7 +246,7 @@ class InPlaceUpdate implements UpdateInterface { ...@@ -244,7 +246,7 @@ class InPlaceUpdate implements UpdateInterface {
* @param null|int $delay * @param null|int $delay
* The delay, defaults to NULL. * The delay, defaults to NULL.
*/ */
protected function doGetArchive($url, $destination, $delay = NULL) { protected function doGetResource($url, $destination, $delay = NULL) {
try { try {
$this->httpClient->get($url, [ $this->httpClient->get($url, [
'sink' => $destination, 'sink' => $destination,
...@@ -253,14 +255,15 @@ class InPlaceUpdate implements UpdateInterface { ...@@ -253,14 +255,15 @@ class InPlaceUpdate implements UpdateInterface {
} }
catch (RequestException $exception) { catch (RequestException $exception) {
$response = $exception->getResponse(); $response = $exception->getResponse();
if (!$response || ($response->getStatusCode() === 429 && ($retry = $response->getHeader('Retry-After')))) { if (!$response || ($retry = $response->getHeader('Retry-After'))) {
$this->doGetArchive($url, $destination, $retry[0] ?? 10 * 1000); $this->doGetResource($url, $destination, !empty($retry[0]) ? $retry[0] : 10 * 1000);
} }
else { else {
$this->logger->error('Retrieval of "@url" failed with: @message', [ $this->logger->error('Retrieval of "@url" failed with: @message', [
'@url' => $url, '@url' => $exception->getRequest()->getUri(),
'@message' => $exception->getMessage(), '@message' => $exception->getMessage(),
]); ]);
throw $exception;
} }
} }
} }
...@@ -278,7 +281,6 @@ class InPlaceUpdate implements UpdateInterface { ...@@ -278,7 +281,6 @@ class InPlaceUpdate implements UpdateInterface {
*/ */
protected function processUpdate(ArchiverInterface $archive, $project_root) { protected function processUpdate(ArchiverInterface $archive, $project_root) {
$archive->extract($this->getTempDirectory()); $archive->extract($this->getTempDirectory());
$this->validateArchive($this->getTempDirectory());
foreach ($this->getFilesList($this->getTempDirectory()) as $file) { foreach ($this->getFilesList($this->getTempDirectory()) as $file) {
$file_real_path = $this->getFileRealPath($file); $file_real_path = $this->getFileRealPath($file);
$file_path = substr($file_real_path, strlen($this->getTempDirectory() . self::ARCHIVE_DIRECTORY)); $file_path = substr($file_real_path, strlen($this->getTempDirectory() . self::ARCHIVE_DIRECTORY));
...@@ -311,17 +313,14 @@ class InPlaceUpdate implements UpdateInterface { ...@@ -311,17 +313,14 @@ class InPlaceUpdate implements UpdateInterface {
* *
* @param string $directory * @param string $directory
* The location of the downloaded archive. * The location of the downloaded archive.
* @param string $csig
* The CSIG contents.
*/ */
protected function validateArchive($directory) { protected function validateArchive($directory, $csig) {
$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);
$module_path = drupal_get_path('module', 'automatic_updates'); $module_path = drupal_get_path('module', 'automatic_updates');
$key = file_get_contents($module_path . '/artifacts/keys/root.pub'); $key = file_get_contents($module_path . '/artifacts/keys/root.pub');
$verifier = new Verifier($key); $verifier = new Verifier($key);
$files = $verifier->verifyCsigMessage($contents); $files = $verifier->verifyCsigMessage($csig);
$checksums = new ChecksumList($files, TRUE); $checksums = new ChecksumList($files, TRUE);
$failed_checksums = new FailedCheckumFilter($checksums, $directory); $failed_checksums = new FailedCheckumFilter($checksums, $directory);
if (iterator_count($failed_checksums)) { if (iterator_count($failed_checksums)) {
...@@ -455,7 +454,6 @@ class InPlaceUpdate implements UpdateInterface { ...@@ -455,7 +454,6 @@ class InPlaceUpdate implements UpdateInterface {
} }
$skipped_files = [ $skipped_files = [
self::DELETION_MANIFEST, self::DELETION_MANIFEST,
self::CHECKSUM_LIST,
]; ];
return $file->isFile() && !in_array($file->getFilename(), $skipped_files, TRUE); return $file->isFile() && !in_array($file->getFilename(), $skipped_files, TRUE);
}; };
...@@ -540,9 +538,9 @@ class InPlaceUpdate implements UpdateInterface { ...@@ -540,9 +538,9 @@ class InPlaceUpdate implements UpdateInterface {
*/ */
protected function getTempDirectory() { protected function getTempDirectory() {
if (!$this->tempDirectory) { if (!$this->tempDirectory) {
$this->tempDirectory = $this->fileSystem->createFilename('automatic_updates-update', 'temporary://'); $this->tempDirectory = $this->fileSystem->createFilename('automatic_updates-update', FileSystem::getOsTemporaryDirectory());
$this->fileSystem->prepareDirectory($this->tempDirectory); $this->fileSystem->prepareDirectory($this->tempDirectory, FileSystemInterface::CREATE_DIRECTORY);
$this->tempDirectory = $this->fileSystem->realpath($this->tempDirectory) . DIRECTORY_SEPARATOR; $this->tempDirectory .= DIRECTORY_SEPARATOR;
} }
return $this->tempDirectory; return $this->tempDirectory;
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment