From a001dacffe9fa76c9ba816bb2f1bf2613a12663f Mon Sep 17 00:00:00 2001 From: phenaproxima <phenaproxima@205645.no-reply.drupal.org> Date: Thu, 21 Apr 2022 19:19:03 +0000 Subject: [PATCH] Issue #3276255 by phenaproxima: Disable unattended updates until TUF integration is complete --- automatic_updates.module | 50 ---------------- automatic_updates.services.yml | 4 +- src/CronUpdater.php | 33 ++++++++-- src/Validation/AdminReadinessMessages.php | 19 +++--- src/Validation/ReadinessValidationManager.php | 15 +---- src/Validator/CronFrequencyValidator.php | 17 ++++-- src/Validator/CronUpdateVersionValidator.php | 6 +- .../automatic_updates_test_cron.info.yml | 4 ++ .../automatic_updates_test_cron.module | 60 +++++++++++++++++++ .../automatic_updates_test_cron.services.yml | 5 ++ .../src/Enabler.php | 37 ++++++++++++ tests/src/Build/UpdateTestBase.php | 1 + .../AutomaticUpdatesFunctionalTestBase.php | 1 + .../Kernel/AutomaticUpdatesKernelTestBase.php | 10 +++- .../CronFrequencyValidatorTest.php | 3 +- 15 files changed, 176 insertions(+), 89 deletions(-) create mode 100644 tests/modules/automatic_updates_test_cron/automatic_updates_test_cron.info.yml create mode 100644 tests/modules/automatic_updates_test_cron/automatic_updates_test_cron.module create mode 100644 tests/modules/automatic_updates_test_cron/automatic_updates_test_cron.services.yml create mode 100644 tests/modules/automatic_updates_test_cron/src/Enabler.php diff --git a/automatic_updates.module b/automatic_updates.module index 22bbd964a2..1a375a68fb 100644 --- a/automatic_updates.module +++ b/automatic_updates.module @@ -6,15 +6,11 @@ */ use Drupal\automatic_updates\BatchProcessor; -use Drupal\automatic_updates\ProjectInfo; use Drupal\Core\Routing\RouteMatchInterface; -use Drupal\automatic_updates\CronUpdater; use Drupal\automatic_updates\Validation\AdminReadinessMessages; -use Drupal\Core\Extension\ExtensionVersion; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Url; use Drupal\system\Controller\DbUpdateController; -use Drupal\update\ProjectSecurityData; /** * Implements hook_help(). @@ -144,52 +140,6 @@ function automatic_updates_form_update_manager_update_form_alter(&$form, FormSta } } -/** - * Implements hook_form_FORM_ID_alter() for 'update_settings' form. - */ -function automatic_updates_form_update_settings_alter(array &$form, FormStateInterface $form_state, string $form_id) { - $project_info = new ProjectInfo('drupal'); - $version = ExtensionVersion::createFromVersionString($project_info->getInstalledVersion()); - $current_minor = $version->getMajorVersion() . '.' . $version->getMinorVersion(); - // @todo In https://www.drupal.org/node/2998285 use the update XML to - // determine when the installed of core will become unsupported. - $supported_until_version = $version->getMajorVersion() . '.' - . ((int) $version->getMinorVersion() + ProjectSecurityData::CORE_MINORS_WITH_SECURITY_COVERAGE) - . '.0'; - - $form['automatic_updates_cron'] = [ - '#type' => 'radios', - '#title' => t('Automatically update Drupal core'), - '#options' => [ - CronUpdater::DISABLED => t('Disabled'), - CronUpdater::ALL => t('All supported updates'), - CronUpdater::SECURITY => t('Security updates only'), - ], - '#default_value' => \Drupal::config('automatic_updates.settings')->get('cron'), - '#description' => t( - 'If enabled, Drupal core will be automatically updated when an update is available. Automatic updates are only supported for @current_minor.x versions of Drupal core. Drupal @current_minor will receive security updates until @supported_until_version is released.', - [ - '@current_minor' => $current_minor, - '@supported_until_version' => $supported_until_version, - ] - ), - ]; - $form += [ - '#submit' => ['::submitForm'], - ]; - $form['#submit'][] = '_automatic_updates_update_settings_form_submit'; -} - -/** - * Submit function for the 'update_settings' form. - */ -function _automatic_updates_update_settings_form_submit(array &$form, FormStateInterface $form_state) { - \Drupal::configFactory() - ->getEditable('automatic_updates.settings') - ->set('cron', $form_state->getValue('automatic_updates_cron')) - ->save(); -} - /** * Implements hook_local_tasks_alter(). */ diff --git a/automatic_updates.services.yml b/automatic_updates.services.yml index 97c4994647..467b719259 100644 --- a/automatic_updates.services.yml +++ b/automatic_updates.services.yml @@ -11,7 +11,6 @@ services: - '@event_dispatcher' - '@automatic_updates.updater' - '@automatic_updates.cron_updater' - - '@config.factory' - 24 tags: - { name: event_subscriber } @@ -119,6 +118,7 @@ services: - '@state' - '@datetime.time' - '@string_translation' + - '@automatic_updates.cron_updater' tags: - { name: event_subscriber } automatic_updates.validator.staged_database_updates: @@ -134,6 +134,6 @@ services: tags: - { name: event_subscriber } automatic_updates.validator.target_release: - class: \Drupal\automatic_updates\Validator\UpdateReleaseValidator + class: Drupal\automatic_updates\Validator\UpdateReleaseValidator tags: - { name: event_subscriber } diff --git a/src/CronUpdater.php b/src/CronUpdater.php index 7f6400c6c5..34bd9fa608 100644 --- a/src/CronUpdater.php +++ b/src/CronUpdater.php @@ -14,6 +14,15 @@ use Drupal\package_manager\Exception\StageValidationException; */ class CronUpdater extends Updater { + /** + * Whether or not cron updates are hard-disabled. + * + * @var bool + * + * @todo Remove this when TUF integration is stable. + */ + private static $disabled = TRUE; + /** * All automatic updates are disabled. * @@ -69,7 +78,7 @@ class CronUpdater extends Updater { * Handles updates during cron. */ public function handleCron(): void { - if ($this->isDisabled()) { + if ($this->getMode() === static::DISABLED) { return; } @@ -134,13 +143,25 @@ class CronUpdater extends Updater { } /** - * Determines if cron updates are disabled. + * Gets the cron update mode. + * + * @return string + * The cron update mode. Will be one of the following constants: + * - \Drupal\automatic_updates\CronUpdater::DISABLED if updates during cron + * are entirely disabled. + * - \Drupal\automatic_updates\CronUpdater::SECURITY only security updates + * can be done during cron. + * - \Drupal\automatic_updates\CronUpdater::ALL if all updates are allowed + * during cron. * - * @return bool - * TRUE if cron updates are disabled, otherwise FALSE. + * @todo Make this always return a string, with a sensible default, in + * https://www.drupal.org/i/3276534. */ - private function isDisabled(): bool { - return $this->configFactory->get('automatic_updates.settings')->get('cron') === static::DISABLED; + final public function getMode(): ?string { + if (self::$disabled) { + return static::DISABLED; + } + return $this->configFactory->get('automatic_updates.settings')->get('cron'); } } diff --git a/src/Validation/AdminReadinessMessages.php b/src/Validation/AdminReadinessMessages.php index 4febff5004..82084f29ce 100644 --- a/src/Validation/AdminReadinessMessages.php +++ b/src/Validation/AdminReadinessMessages.php @@ -3,7 +3,6 @@ namespace Drupal\automatic_updates\Validation; use Drupal\automatic_updates\CronUpdater; -use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; use Drupal\Core\Messenger\MessengerInterface; use Drupal\Core\Messenger\MessengerTrait; @@ -60,11 +59,11 @@ final class AdminReadinessMessages implements ContainerInjectionInterface { protected $currentRouteMatch; /** - * The config factory service. + * The cron updater service. * - * @var \Drupal\Core\Config\ConfigFactoryInterface + * @var \Drupal\automatic_updates\CronUpdater */ - protected $config; + protected $cronUpdater; /** * Constructs a ReadinessRequirement object. @@ -81,17 +80,17 @@ final class AdminReadinessMessages implements ContainerInjectionInterface { * The translation service. * @param \Drupal\Core\Routing\CurrentRouteMatch $current_route_match * The current route match. - * @param \Drupal\Core\Config\ConfigFactoryInterface $config - * The config factory service. + * @param \Drupal\automatic_updates\CronUpdater $cron_updater + * The cron updater service. */ - public function __construct(ReadinessValidationManager $readiness_checker_manager, MessengerInterface $messenger, AdminContext $admin_context, AccountProxyInterface $current_user, TranslationInterface $translation, CurrentRouteMatch $current_route_match, ConfigFactoryInterface $config) { + public function __construct(ReadinessValidationManager $readiness_checker_manager, MessengerInterface $messenger, AdminContext $admin_context, AccountProxyInterface $current_user, TranslationInterface $translation, CurrentRouteMatch $current_route_match, CronUpdater $cron_updater) { $this->readinessCheckerManager = $readiness_checker_manager; $this->setMessenger($messenger); $this->adminContext = $admin_context; $this->currentUser = $current_user; $this->setStringTranslation($translation); $this->currentRouteMatch = $current_route_match; - $this->config = $config; + $this->cronUpdater = $cron_updater; } /** @@ -105,7 +104,7 @@ final class AdminReadinessMessages implements ContainerInjectionInterface { $container->get('current_user'), $container->get('string_translation'), $container->get('current_route_match'), - $container->get('config.factory') + $container->get('automatic_updates.cron_updater') ); } @@ -142,7 +141,7 @@ final class AdminReadinessMessages implements ContainerInjectionInterface { protected function displayResultsOnCurrentPage(): bool { // If updates will not run during cron then we don't need to show the // readiness checks on admin pages. - if ($this->config->get('automatic_updates.settings')->get('cron') === CronUpdater::DISABLED) { + if ($this->cronUpdater->getMode() === CronUpdater::DISABLED) { return FALSE; } diff --git a/src/Validation/ReadinessValidationManager.php b/src/Validation/ReadinessValidationManager.php index 08e50d6f8e..f6292bd5cd 100644 --- a/src/Validation/ReadinessValidationManager.php +++ b/src/Validation/ReadinessValidationManager.php @@ -6,7 +6,6 @@ use Drupal\automatic_updates\CronUpdater; use Drupal\automatic_updates\Event\ReadinessCheckEvent; use Drupal\automatic_updates\Updater; use Drupal\Component\Datetime\TimeInterface; -use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface; use Drupal\package_manager\Event\PostApplyEvent; use Symfony\Component\EventDispatcher\EventDispatcherInterface; @@ -60,13 +59,6 @@ class ReadinessValidationManager implements EventSubscriberInterface { */ protected $cronUpdater; - /** - * The config factory service. - * - * @var \Drupal\Core\Config\ConfigFactoryInterface - */ - protected $config; - /** * Constructs a ReadinessValidationManager. * @@ -80,18 +72,15 @@ class ReadinessValidationManager implements EventSubscriberInterface { * The updater service. * @param \Drupal\automatic_updates\CronUpdater $cron_updater * The cron updater service. - * @param \Drupal\Core\Config\ConfigFactoryInterface $config - * The config factory service. * @param int $results_time_to_live * The number of hours to store results. */ - public function __construct(KeyValueExpirableFactoryInterface $key_value_expirable_factory, TimeInterface $time, EventDispatcherInterface $dispatcher, Updater $updater, CronUpdater $cron_updater, ConfigFactoryInterface $config, int $results_time_to_live) { + public function __construct(KeyValueExpirableFactoryInterface $key_value_expirable_factory, TimeInterface $time, EventDispatcherInterface $dispatcher, Updater $updater, CronUpdater $cron_updater, int $results_time_to_live) { $this->keyValueExpirable = $key_value_expirable_factory->get('automatic_updates'); $this->time = $time; $this->eventDispatcher = $dispatcher; $this->updater = $updater; $this->cronUpdater = $cron_updater; - $this->config = $config; $this->resultsTimeToLive = $results_time_to_live; } @@ -104,7 +93,7 @@ class ReadinessValidationManager implements EventSubscriberInterface { // If updates will run during cron, use the cron updater service provided by // this module. This will allow subscribers to ReadinessCheckEvent to run // specific validation for conditions that only affect cron updates. - if ($this->config->get('automatic_updates.settings')->get('cron') === CronUpdater::DISABLED) { + if ($this->cronUpdater->getMode() === CronUpdater::DISABLED) { $stage = $this->updater; } else { diff --git a/src/Validator/CronFrequencyValidator.php b/src/Validator/CronFrequencyValidator.php index 353722b7ce..4003a7273e 100644 --- a/src/Validator/CronFrequencyValidator.php +++ b/src/Validator/CronFrequencyValidator.php @@ -75,6 +75,13 @@ class CronFrequencyValidator implements EventSubscriberInterface { */ protected $time; + /** + * The cron updater service. + * + * @var \Drupal\automatic_updates\CronUpdater + */ + protected $cronUpdater; + /** * CronFrequencyValidator constructor. * @@ -88,13 +95,16 @@ class CronFrequencyValidator implements EventSubscriberInterface { * The time service. * @param \Drupal\Core\StringTranslation\TranslationInterface $translation * The translation service. + * @param \Drupal\automatic_updates\CronUpdater $cron_updater + * The cron updater service. */ - public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, StateInterface $state, TimeInterface $time, TranslationInterface $translation) { + public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, StateInterface $state, TimeInterface $time, TranslationInterface $translation, CronUpdater $cron_updater) { $this->configFactory = $config_factory; $this->moduleHandler = $module_handler; $this->state = $state; $this->time = $time; $this->setStringTranslation($translation); + $this->cronUpdater = $cron_updater; } /** @@ -104,12 +114,9 @@ class CronFrequencyValidator implements EventSubscriberInterface { * The event object. */ public function checkCronFrequency(ReadinessCheckEvent $event): void { - $cron_enabled = $this->configFactory->get('automatic_updates.settings') - ->get('cron'); - // If automatic updates are disabled during cron, there's nothing we need // to validate. - if ($cron_enabled === CronUpdater::DISABLED) { + if ($this->cronUpdater->getMode() === CronUpdater::DISABLED) { return; } elseif ($this->moduleHandler->moduleExists('automated_cron')) { diff --git a/src/Validator/CronUpdateVersionValidator.php b/src/Validator/CronUpdateVersionValidator.php index 8e167336b6..e5af7e9d83 100644 --- a/src/Validator/CronUpdateVersionValidator.php +++ b/src/Validator/CronUpdateVersionValidator.php @@ -91,10 +91,14 @@ final class CronUpdateVersionValidator extends UpdateVersionValidator { ]); } + // We cannot use dependency injection to get the cron updater because that + // would create a circular service dependency. + $level = \Drupal::service('automatic_updates.cron_updater') + ->getMode(); + // If both the from and to version numbers are valid check if the current // settings only allow security updates during cron and if so ensure the // update release is a security release. - $level = $this->configFactory->get('automatic_updates.settings')->get('cron'); if ($level === CronUpdater::SECURITY) { $releases = (new ProjectInfo('drupal'))->getInstallableReleases(); // @todo Remove this check and add validation to diff --git a/tests/modules/automatic_updates_test_cron/automatic_updates_test_cron.info.yml b/tests/modules/automatic_updates_test_cron/automatic_updates_test_cron.info.yml new file mode 100644 index 0000000000..9d5d25b793 --- /dev/null +++ b/tests/modules/automatic_updates_test_cron/automatic_updates_test_cron.info.yml @@ -0,0 +1,4 @@ +name: 'Automatic Updates Test: Cron' +type: module +description: 'Enables cron updates for testing purposes.' +package: Testing diff --git a/tests/modules/automatic_updates_test_cron/automatic_updates_test_cron.module b/tests/modules/automatic_updates_test_cron/automatic_updates_test_cron.module new file mode 100644 index 0000000000..34842fcf68 --- /dev/null +++ b/tests/modules/automatic_updates_test_cron/automatic_updates_test_cron.module @@ -0,0 +1,60 @@ +<?php + +/** + * @file + * Contains hook implementations to enable automatic updates during cron. + * + * @todo Move into automatic_updates when TUF integration is stable. + */ + +use Drupal\automatic_updates\ProjectInfo; +use Drupal\automatic_updates\CronUpdater; +use Drupal\Core\Extension\ExtensionVersion; +use Drupal\Core\Form\FormStateInterface; +use Drupal\update\ProjectSecurityData; + +/** + * Implements hook_form_FORM_ID_alter() for 'update_settings' form. + */ +function automatic_updates_test_cron_form_update_settings_alter(array &$form, FormStateInterface $form_state, string $form_id) { + $project_info = new ProjectInfo('drupal'); + $version = ExtensionVersion::createFromVersionString($project_info->getInstalledVersion()); + $current_minor = $version->getMajorVersion() . '.' . $version->getMinorVersion(); + // @todo In https://www.drupal.org/node/2998285 use the update XML to + // determine when the installed of core will become unsupported. + $supported_until_version = $version->getMajorVersion() . '.' + . ((int) $version->getMinorVersion() + ProjectSecurityData::CORE_MINORS_WITH_SECURITY_COVERAGE) + . '.0'; + + $form['automatic_updates_cron'] = [ + '#type' => 'radios', + '#title' => t('Automatically update Drupal core'), + '#options' => [ + CronUpdater::DISABLED => t('Disabled'), + CronUpdater::ALL => t('All supported updates'), + CronUpdater::SECURITY => t('Security updates only'), + ], + '#default_value' => \Drupal::config('automatic_updates.settings')->get('cron'), + '#description' => t( + 'If enabled, Drupal core will be automatically updated when an update is available. Automatic updates are only supported for @current_minor.x versions of Drupal core. Drupal @current_minor will receive security updates until @supported_until_version is released.', + [ + '@current_minor' => $current_minor, + '@supported_until_version' => $supported_until_version, + ] + ), + ]; + $form += [ + '#submit' => ['::submitForm'], + ]; + $form['#submit'][] = '_automatic_updates_test_cron_update_settings_form_submit'; +} + +/** + * Submit function for the 'update_settings' form. + */ +function _automatic_updates_test_cron_update_settings_form_submit(array &$form, FormStateInterface $form_state) { + \Drupal::configFactory() + ->getEditable('automatic_updates.settings') + ->set('cron', $form_state->getValue('automatic_updates_cron')) + ->save(); +} diff --git a/tests/modules/automatic_updates_test_cron/automatic_updates_test_cron.services.yml b/tests/modules/automatic_updates_test_cron/automatic_updates_test_cron.services.yml new file mode 100644 index 0000000000..b8e5c8bddd --- /dev/null +++ b/tests/modules/automatic_updates_test_cron/automatic_updates_test_cron.services.yml @@ -0,0 +1,5 @@ +services: + automatic_updates_test_cron.enabler: + class: Drupal\automatic_updates_test_cron\Enabler + tags: + - { name: event_subscriber } diff --git a/tests/modules/automatic_updates_test_cron/src/Enabler.php b/tests/modules/automatic_updates_test_cron/src/Enabler.php new file mode 100644 index 0000000000..e7cb069df4 --- /dev/null +++ b/tests/modules/automatic_updates_test_cron/src/Enabler.php @@ -0,0 +1,37 @@ +<?php + +namespace Drupal\automatic_updates_test_cron; + +use Drupal\automatic_updates\CronUpdater; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * Enables automatic updates during cron. + * + * @todo Remove this when TUF integration is stable. + */ +class Enabler implements EventSubscriberInterface { + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + return [ + KernelEvents::REQUEST => 'enableCron', + ]; + } + + /** + * Enables automatic updates during cron. + */ + public function enableCron(): void { + if (class_exists(CronUpdater::class)) { + $reflector = new \ReflectionClass(CronUpdater::class); + $reflector = $reflector->getProperty('disabled'); + $reflector->setAccessible(TRUE); + $reflector->setValue(NULL, FALSE); + } + } + +} diff --git a/tests/src/Build/UpdateTestBase.php b/tests/src/Build/UpdateTestBase.php index cb93d1dcf6..f80214db3f 100644 --- a/tests/src/Build/UpdateTestBase.php +++ b/tests/src/Build/UpdateTestBase.php @@ -39,6 +39,7 @@ abstract class UpdateTestBase extends TemplateProjectTestBase { $this->installModules([ 'automatic_updates', 'automatic_updates_test', + 'automatic_updates_test_cron', 'automatic_updates_test_release_history', ]); } diff --git a/tests/src/Functional/AutomaticUpdatesFunctionalTestBase.php b/tests/src/Functional/AutomaticUpdatesFunctionalTestBase.php index 1a17142fdc..f23bbee42c 100644 --- a/tests/src/Functional/AutomaticUpdatesFunctionalTestBase.php +++ b/tests/src/Functional/AutomaticUpdatesFunctionalTestBase.php @@ -15,6 +15,7 @@ abstract class AutomaticUpdatesFunctionalTestBase extends BrowserTestBase { * {@inheritdoc} */ protected static $modules = [ + 'automatic_updates_test_cron', 'automatic_updates_test_disable_validators', 'package_manager_bypass', ]; diff --git a/tests/src/Kernel/AutomaticUpdatesKernelTestBase.php b/tests/src/Kernel/AutomaticUpdatesKernelTestBase.php index de10b117ad..da4da98abc 100644 --- a/tests/src/Kernel/AutomaticUpdatesKernelTestBase.php +++ b/tests/src/Kernel/AutomaticUpdatesKernelTestBase.php @@ -24,7 +24,12 @@ abstract class AutomaticUpdatesKernelTestBase extends PackageManagerKernelTestBa /** * {@inheritdoc} */ - protected static $modules = ['system', 'update', 'update_test']; + protected static $modules = [ + 'automatic_updates_test_cron', + 'system', + 'update', + 'update_test', + ]; /** * The mocked HTTP client that returns metadata about available updates. @@ -68,6 +73,9 @@ abstract class AutomaticUpdatesKernelTestBase extends PackageManagerKernelTestBa // from a sane state. // @see \Drupal\automatic_updates\Validator\CronFrequencyValidator $this->container->get('state')->set('system.cron_last', time()); + + // @todo Remove this when TUF integration is stable. + $this->container->get('automatic_updates_test_cron.enabler')->enableCron(); } /** diff --git a/tests/src/Kernel/ReadinessValidation/CronFrequencyValidatorTest.php b/tests/src/Kernel/ReadinessValidation/CronFrequencyValidatorTest.php index e44cc77e34..47d15ae228 100644 --- a/tests/src/Kernel/ReadinessValidation/CronFrequencyValidatorTest.php +++ b/tests/src/Kernel/ReadinessValidation/CronFrequencyValidatorTest.php @@ -34,7 +34,8 @@ class CronFrequencyValidatorTest extends AutomaticUpdatesKernelTestBase { $this->container->get('module_handler'), $this->container->get('state'), $this->container->get('datetime.time'), - $this->container->get('string_translation') + $this->container->get('string_translation'), + $this->container->get('automatic_updates.cron_updater') ) extends CronFrequencyValidator { /** -- GitLab