diff --git a/core/modules/auto_updates/auto_updates.install b/core/modules/auto_updates/auto_updates.install index 6799a1c510..173d1ceaef 100644 --- a/core/modules/auto_updates/auto_updates.install +++ b/core/modules/auto_updates/auto_updates.install @@ -18,7 +18,7 @@ function auto_updates_requirements($phase) { } /** @var \Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManagerInterface $checker_manager */ - $checker_manager = \Drupal::service('auto_updates.readiness_checker'); + $checker_manager = \Drupal::service('auto_updates.readiness_checker_manager'); if (!$checker_manager->isEnabled()) { return []; } @@ -75,6 +75,6 @@ function auto_updates_requirements($phase) { */ function auto_updates_install($is_syncing) { /** @var \Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManagerInterface $checker_manager */ - $checker_manager = \Drupal::service('auto_updates.readiness_checker'); + $checker_manager = \Drupal::service('auto_updates.readiness_checker_manager'); $checker_manager->run(); } diff --git a/core/modules/auto_updates/auto_updates.module b/core/modules/auto_updates/auto_updates.module index 7fec2f650f..70a68230aa 100644 --- a/core/modules/auto_updates/auto_updates.module +++ b/core/modules/auto_updates/auto_updates.module @@ -32,7 +32,7 @@ function auto_updates_page_top(array &$page_top) { if (in_array(\Drupal::routeMatch()->getRouteName(), $disabled_routes, TRUE)) { return; } - $last_check_timestamp = \Drupal::service('auto_updates.readiness_checker')->timestamp(); + $last_check_timestamp = \Drupal::service('auto_updates.readiness_checker_manager')->timestamp(); if (\Drupal::time()->getRequestTime() > $last_check_timestamp + ReadinessCheckerManagerInterface::LAST_CHECKED_WARNING) { $readiness_settings = Url::fromRoute('auto_updates.settings'); \Drupal::messenger()->addError(t('Your site has not recently run an update readiness check. Administer automatic updates and run readiness checks manually.', [ @@ -40,7 +40,7 @@ function auto_updates_page_top(array &$page_top) { ])); } /** @var \Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManagerInterface $checker_manager */ - $checker_manager = \Drupal::service('auto_updates.readiness_checker'); + $checker_manager = \Drupal::service('auto_updates.readiness_checker_manager'); $results = $checker_manager->getResults(ReadinessCheckerManagerInterface::ERROR); if ($results) { \Drupal::messenger()->addError(t('Your site is currently failing readiness checks for automatic updates. It cannot be automatically updated until further action is performed:', [':readiness_checks' => 'https://www.drupal.org/docs/8/update/auto-updates#readiness-checks'])); @@ -64,7 +64,7 @@ function auto_updates_page_top(array &$page_top) { function auto_updates_cron() { $request_time = \Drupal::time()->getRequestTime(); /** @var \Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManagerInterface $checker_manager */ - $checker_manager = \Drupal::service('auto_updates.readiness_checker'); + $checker_manager = \Drupal::service('auto_updates.readiness_checker_manager'); $last_check = $checker_manager->timestamp(); // Only allow cron to run once every hour. if ($last_check && ($request_time - $last_check) < 3600) { @@ -72,7 +72,7 @@ function auto_updates_cron() { } /** @var \Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManagerInterface $checker_manager */ - $checker_manager = \Drupal::service('auto_updates.readiness_checker'); + $checker_manager = \Drupal::service('auto_updates.readiness_checker_manager'); $checker_manager->run(); } @@ -81,7 +81,7 @@ function auto_updates_cron() { */ function auto_updates_modules_installed($modules) { /** @var ReadinessCheckerManagerInterface $checker_manager */ - $checker_manager = \Drupal::service('auto_updates.readiness_checker'); + $checker_manager = \Drupal::service('auto_updates.readiness_checker_manager'); if ($checker_manager->clearResultsStaleResults()) { $checker_manager->run(); } diff --git a/core/modules/auto_updates/auto_updates.routing.yml b/core/modules/auto_updates/auto_updates.routing.yml index 1616bc5390..ce01cdf9f4 100644 --- a/core/modules/auto_updates/auto_updates.routing.yml +++ b/core/modules/auto_updates/auto_updates.routing.yml @@ -14,5 +14,6 @@ auto_updates.update_readiness: _title: 'Update readiness checking...' requirements: _permission: 'administer software updates' + _custom_access: '\Drupal\auto_updates\Controller\ReadinessCheckerController::access' options: _admin_route: TRUE diff --git a/core/modules/auto_updates/auto_updates.services.yml b/core/modules/auto_updates/auto_updates.services.yml index 0be04c9d2e..461fbcd4fb 100644 --- a/core/modules/auto_updates/auto_updates.services.yml +++ b/core/modules/auto_updates/auto_updates.services.yml @@ -4,7 +4,7 @@ services: arguments: ['%app.root%'] tags: - { name: readiness_checker, category: error} - auto_updates.readiness_checker: + auto_updates.readiness_checker_manager: class: Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManager arguments: ['@keyvalue', '@config.factory'] tags: diff --git a/core/modules/auto_updates/src/Controller/ReadinessCheckerController.php b/core/modules/auto_updates/src/Controller/ReadinessCheckerController.php index cd5ac996c5..27160de369 100644 --- a/core/modules/auto_updates/src/Controller/ReadinessCheckerController.php +++ b/core/modules/auto_updates/src/Controller/ReadinessCheckerController.php @@ -3,6 +3,7 @@ namespace Drupal\auto_updates\Controller; use Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManagerInterface; +use Drupal\Core\Access\AccessResult; use Drupal\Core\Controller\ControllerBase; use Drupal\Core\StringTranslation\TranslationInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -40,7 +41,7 @@ public function __construct(ReadinessCheckerManagerInterface $checker_manager, T */ public static function create(ContainerInterface $container) { return new static( - $container->get('auto_updates.readiness_checker'), + $container->get('auto_updates.readiness_checker_manager'), $container->get('string_translation') ); } @@ -58,4 +59,14 @@ public function run() { return $this->redirect('auto_updates.settings'); } + /** + * Checks access based on whether the readiness checkers are enabled. + * + * @return \Drupal\Core\Access\AccessResultInterface + * The access result. + */ + public function access() { + return AccessResult::allowedIf($this->checkerManager->isEnabled()); + } + } diff --git a/core/modules/auto_updates/src/Form/SettingsForm.php b/core/modules/auto_updates/src/Form/SettingsForm.php index a88eaaf14f..27ba01d1ae 100644 --- a/core/modules/auto_updates/src/Form/SettingsForm.php +++ b/core/modules/auto_updates/src/Form/SettingsForm.php @@ -31,7 +31,7 @@ class SettingsForm extends ConfigFormBase { */ public static function create(ContainerInterface $container) { $instance = parent::create($container); - $instance->checkerManager = $container->get('auto_updates.readiness_checker'); + $instance->checkerManager = $container->get('auto_updates.readiness_checker_manager'); $instance->dateFormatter = $container->get('date.formatter'); return $instance; } diff --git a/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerInterface.php b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerInterface.php index b559d8fdd7..ca3031e6d4 100644 --- a/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerInterface.php +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerInterface.php @@ -3,7 +3,7 @@ namespace Drupal\auto_updates\ReadinessChecker; /** - * Interface for objects capable of readiness checking. + * Defines an interface for readiness checker services. */ interface ReadinessCheckerInterface { diff --git a/core/modules/auto_updates/tests/src/Functional/ReadinessCheckerTest.php b/core/modules/auto_updates/tests/src/Functional/ReadinessCheckerTest.php index 99d81d968e..e42f21c6b3 100644 --- a/core/modules/auto_updates/tests/src/Functional/ReadinessCheckerTest.php +++ b/core/modules/auto_updates/tests/src/Functional/ReadinessCheckerTest.php @@ -51,12 +51,13 @@ protected function setUp(): void { */ public function testReadinessChecksStatusReport() { $assert = $this->assertSession(); + $page = $this->getSession()->getPage(); $this->container->get('module_installer')->uninstall(['automated_cron']); $this->container->get('module_installer')->install(['auto_updates', 'auto_updates_test']); // If the the site is ready for updates the users will see the same output - // regardless of the user has permission to run updates. + // regardless of whether the user has permission to run updates. foreach ([$this->reportViewerUser, $this->checkerRunnerUser] as $user) { $this->drupalLogin($user); $this->drupalGet('admin/reports/status'); @@ -64,14 +65,14 @@ public function testReadinessChecksStatusReport() { } // Confirm a user without the permission to run readiness checks does not - // have a link to run the checks when the checks need to run again. + // have a link to run the checks when the checks need to be run again. $this->setFakeTime('+2 days'); $this->drupalLogin($this->reportViewerUser); $this->drupalGet('admin/reports/status'); $this->assertReadinessReportMatches('Your site has not recently checked if it is ready to apply automatic updates. Readiness checks were last run %s ago.'); // Confirm a user with the permission to run readiness checks does have a - // link to run the checks when the checks need to run again. + // link to run the checks when the checks need to be run again. $this->drupalLogin($this->checkerRunnerUser); $this->drupalGet('admin/reports/status'); $this->assertReadinessReportMatches('Your site has not recently checked if it is ready to apply automatic updates.' @@ -83,6 +84,7 @@ public function testReadinessChecksStatusReport() { // @todo If coming from the status report page should you be redirected there? // This is how 'Run cron" works. $assert->addressEquals('/admin/config/auto_updates'); + $assert->checkboxChecked('enable_readiness_checks'); $assert->pageTextNotContains("Access denied"); $assert->pageTextContains('Your site is currently failing readiness checks for automatic updates. It cannot be automatically updated until further action is performed:'); $assert->pageTextContains('OMG 🚒. Your server is on 🔥!'); @@ -92,9 +94,39 @@ public function testReadinessChecksStatusReport() { // Confirm the error is displayed on the status report page. $this->drupalGet('admin/reports/status'); $this->assertReadinessReportMatches('1 check failed: OMG 🚒. Your server is on 🔥!'); - // @todo Should always show when the checks were last run and a link to + // @todo Should we always show when the checks were last run and a link to // run when there is an error? } + + // Disable readiness checks. + $this->drupalLogin($this->checkerRunnerUser); + $this->drupalGet('admin/config/auto_updates'); + $page->uncheckField('enable_readiness_checks'); + $page->pressButton('Save configuration'); + + // Confirm that when readiness checkers are disabled no information on the + // last run is displayed. + $assert->pageTextNotContains('Readiness checks have never been run.'); + $assert->pageTextNotContains('Readiness checks were last run'); + $assert->pageTextNotContains('>run the readiness checks'); + + // Confirm that access is denied when manually going to the readiness + // checker controller. + $this->drupalGet('admin/config/auto_updates/readiness'); + $assert->pageTextContains('Access denied'); + + $this->drupalGet('admin/reports/status'); + $assert->pageTextNotContains('Update readiness checks'); + + // Re-enable readiness checks. + $this->drupalGet('admin/config/auto_updates'); + $page->checkField('enable_readiness_checks'); + $page->pressButton('Save configuration'); + + // Confirm that the last message displayed is displayed again. + $this->drupalGet('admin/reports/status'); + $this->assertReadinessReportMatches('1 check failed: OMG 🚒. Your server is on 🔥!'); + } /** @@ -121,6 +153,8 @@ public function testReadinessCheckAfterInstall() { $this->container->get('state')->set(TestChecker::STATE_KEY, 'Security has been compromised. "pass123" was a bad password!'); $this->container->get('module_installer')->install(['color']); $this->drupalGet('admin/reports/status'); + // Confirm that new checker message is not displayed because the checker was + // not run again. $this->assertReadinessReportMatches('1 check failed: 😿Oh no! A hacker now owns your files!'); // Confirm the new message is displayed after running the checkers manually. @@ -138,6 +172,8 @@ public function testReadinessCheckAfterInstall() { */ public function testCronRun() { $assert = $this->assertSession(); + $page = $this->getSession()->getPage(); + $this->drupalLogin($this->reportViewerUser); $this->container->get('module_installer')->install(['auto_updates', 'auto_updates_test']); $this->drupalGet('admin/reports/status'); @@ -163,10 +199,40 @@ public function testCronRun() { $this->setFakeTime('+125 minutes'); $this->clickLink('Run cron'); $this->assertReadinessReportMatches('1 check failed: OMG 💦. Now your server is filled with water!'); + + // Disable readiness checks. + $this->drupalLogin($this->checkerRunnerUser); + $this->drupalGet('admin/config/auto_updates'); + $page->uncheckField('enable_readiness_checks'); + $page->pressButton('Save configuration'); + + // Run cron while readiness checks are disabled. + $this->container->get('state')->set(TestChecker::STATE_KEY, '😨 Now your hard drive is missing! How is that even possible?'); + $this->setFakeTime('+190 minutes'); + $this->drupalGet('admin/reports/status'); + $assert->pageTextNotContains('Update readiness checks'); + $this->clickLink('Run cron'); + $assert->pageTextNotContains('Update readiness checks'); + + // Re-enable readiness checks. + $this->drupalGet('admin/config/auto_updates'); + $page->checkField('enable_readiness_checks'); + $page->pressButton('Save configuration'); + + // Confirm that the new test message for the test readiness checker is not + // displayed because the checkers were not run during cron when they were + // disabled. + $this->drupalGet('admin/reports/status'); + $this->assertReadinessReportMatches('1 check failed: OMG 💦. Now your server is filled with water!'); + + // Confirm running cron displays the new message. + $this->setFakeTime('+255 minutes'); + $this->clickLink('Run cron'); + $this->assertReadinessReportMatches('1 check failed: 😨 Now your hard drive is missing! How is that even possible?'); } /** - * Sets a fake time that will be used in that test. + * Sets a fake time that will be used in the test. * * @param string $offset * A date/time offset string. diff --git a/core/modules/auto_updates/tests/src/Kernel/ReadinessChecker/DiskSpaceTest.php b/core/modules/auto_updates/tests/src/Kernel/ReadinessChecker/DiskSpaceTest.php index 633b774a30..51a09e5aad 100644 --- a/core/modules/auto_updates/tests/src/Kernel/ReadinessChecker/DiskSpaceTest.php +++ b/core/modules/auto_updates/tests/src/Kernel/ReadinessChecker/DiskSpaceTest.php @@ -32,11 +32,16 @@ public function testDiskSpace() { $disk_space = new TestDiskSpace($this->container->getParameter('app.root')); $messages = $disk_space->run(); $this->assertCount(2, $messages); + $this->assertStringMatchesFormat('Logical disk "%s" has insufficient space. There must be at least %s megabytes free.', (string) $messages[0]); + $this->assertStringMatchesFormat('Directory "%s" has insufficient space. There must be at least %s megabytes free.', (string) $messages[1]); // Out of space not the same logical disk. $disk_space = new TestDiskSpaceNonSameDisk($this->container->getParameter('app.root')); $messages = $disk_space->run(); $this->assertCount(3, $messages); + $this->assertStringMatchesFormat('Drupal root filesystem "%s" has insufficient space. There must be at least %s megabytes free.', (string) $messages[0]); + $this->assertStringMatchesFormat('Vendor filesystem "%s" has insufficient space. There must be at least %s megabytes free.', (string) $messages[1]); + $this->assertStringMatchesFormat('Directory "%s" has insufficient space. There must be at least %s megabytes free.', (string) $messages[2]); } }