From aa3434c4aaf00383dcfe26fbb499d63e1b260aa7 Mon Sep 17 00:00:00 2001 From: Alex Pott <alex.a.pott@googlemail.com> Date: Thu, 30 Dec 2021 11:16:28 +0000 Subject: [PATCH] Issue #3049048 by danflanagan8, ndobromirov, mglaman, bbrala, alexpott, Wim Leers, gabesullice: Invalid JSON:API responses when maintenance mode is on --- core/core.services.yml | 4 +- .../MaintenanceModeSubscriber.php | 64 ++++++++----- core/lib/Drupal/Core/Site/MaintenanceMode.php | 27 +++++- .../Core/Site/MaintenanceModeEvents.php | 15 ++++ .../Core/Site/MaintenanceModeInterface.php | 8 ++ .../config/install/jsonapi.settings.yml | 3 + .../jsonapi/config/schema/jsonapi.schema.yml | 10 +++ core/modules/jsonapi/jsonapi.install | 14 +++ core/modules/jsonapi/jsonapi.services.yml | 5 ++ .../JsonapiMaintenanceModeSubscriber.php | 90 +++++++++++++++++++ .../jsonapi/tests/fixtures/update/jsonapi.php | 54 +++++++++++ .../fixtures/update/jsonapi.settings.yml | 2 + .../src/Functional/JsonApiFunctionalTest.php | 33 +++++++ .../Update/JsonApiUpdatePathTest.php | 50 +++++++++++ .../MaintenanceModeSubscriber.php | 30 ++++++- 15 files changed, 380 insertions(+), 29 deletions(-) create mode 100644 core/lib/Drupal/Core/Site/MaintenanceModeEvents.php create mode 100644 core/modules/jsonapi/src/EventSubscriber/JsonapiMaintenanceModeSubscriber.php create mode 100644 core/modules/jsonapi/tests/fixtures/update/jsonapi.php create mode 100644 core/modules/jsonapi/tests/fixtures/update/jsonapi.settings.yml create mode 100644 core/modules/jsonapi/tests/src/Functional/Update/JsonApiUpdatePathTest.php diff --git a/core/core.services.yml b/core/core.services.yml index 0ddf970ed43f..f0c703651547 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -1229,10 +1229,10 @@ services: - { name: access_check, needs_incoming_request: TRUE } maintenance_mode: class: Drupal\Core\Site\MaintenanceMode - arguments: ['@state'] + arguments: ['@state', '@config.factory'] maintenance_mode_subscriber: class: Drupal\Core\EventSubscriber\MaintenanceModeSubscriber - arguments: ['@maintenance_mode', '@config.factory', '@string_translation', '@url_generator', '@current_user', '@bare_html_page_renderer', '@messenger'] + arguments: ['@maintenance_mode', '@config.factory', '@string_translation', '@url_generator', '@current_user', '@bare_html_page_renderer', '@messenger', '@event_dispatcher'] tags: - { name: event_subscriber } route_access_response_subscriber: diff --git a/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php index 579d07d4380f..ee2129d7fced 100644 --- a/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php @@ -2,13 +2,13 @@ namespace Drupal\Core\EventSubscriber; -use Drupal\Component\Render\FormattableMarkup; use Drupal\Core\Config\ConfigFactoryInterface; -use Drupal\Core\Render\BareHtmlPageRendererInterface; use Drupal\Core\Messenger\MessengerInterface; +use Drupal\Core\Render\BareHtmlPageRendererInterface; use Drupal\Core\Routing\RouteMatch; use Drupal\Core\Routing\UrlGeneratorInterface; use Drupal\Core\Session\AccountInterface; +use Drupal\Core\Site\MaintenanceModeEvents; use Drupal\Core\Site\MaintenanceModeInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Core\StringTranslation\TranslationInterface; @@ -16,6 +16,7 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; /** * Maintenance mode subscriber for controller requests. @@ -66,6 +67,13 @@ class MaintenanceModeSubscriber implements EventSubscriberInterface { */ protected $messenger; + /** + * An event dispatcher instance to use for configuration events. + * + * @var \Symfony\Contracts\EventDispatcher\EventDispatcherInterface + */ + protected $eventDispatcher; + /** * Constructs a new MaintenanceModeSubscriber. * @@ -83,8 +91,10 @@ class MaintenanceModeSubscriber implements EventSubscriberInterface { * The bare HTML page renderer. * @param \Drupal\Core\Messenger\MessengerInterface $messenger * The messenger. + * @param \Symfony\Contracts\EventDispatcher\EventDispatcherInterface $event_dispatcher + * The event dispatcher. */ - public function __construct(MaintenanceModeInterface $maintenance_mode, ConfigFactoryInterface $config_factory, TranslationInterface $translation, UrlGeneratorInterface $url_generator, AccountInterface $account, BareHtmlPageRendererInterface $bare_html_page_renderer, MessengerInterface $messenger) { + public function __construct(MaintenanceModeInterface $maintenance_mode, ConfigFactoryInterface $config_factory, TranslationInterface $translation, UrlGeneratorInterface $url_generator, AccountInterface $account, BareHtmlPageRendererInterface $bare_html_page_renderer, MessengerInterface $messenger, EventDispatcherInterface $event_dispatcher = NULL) { $this->maintenanceMode = $maintenance_mode; $this->config = $config_factory; $this->stringTranslation = $translation; @@ -92,6 +102,11 @@ public function __construct(MaintenanceModeInterface $maintenance_mode, ConfigFa $this->account = $account; $this->bareHtmlPageRenderer = $bare_html_page_renderer; $this->messenger = $messenger; + if (!$event_dispatcher) { + @trigger_error('Calling MaintenanceModeSubscriber::__construct() without the $event_dispatcher argument is deprecated in drupal:9.4.0 and the $event_dispatcher argument will be required in drupal:10.0.0. See https://www.drupal.org/node/3255799', E_USER_DEPRECATED); + $event_dispatcher = \Drupal::service('event_dispatcher'); + } + $this->eventDispatcher = $event_dispatcher; } /** @@ -108,20 +123,8 @@ public function onKernelRequestMaintenance(RequestEvent $event) { \Drupal::service('page_cache_kill_switch')->trigger(); if (!$this->maintenanceMode->exempt($this->account)) { - // Deliver the 503 page if the site is in maintenance mode and the - // logged in user is not allowed to bypass it. - - // If the request format is not 'html' then show default maintenance - // mode page else show a text/plain page with maintenance message. - if ($request->getRequestFormat() !== 'html') { - $response = new Response($this->getSiteMaintenanceMessage(), 503, ['Content-Type' => 'text/plain']); - $event->setResponse($response); - return; - } - drupal_maintenance_theme(); - $response = $this->bareHtmlPageRenderer->renderBarePage(['#markup' => $this->getSiteMaintenanceMessage()], $this->t('Site under maintenance'), 'maintenance_page'); - $response->setStatusCode(503); - $event->setResponse($response); + // When the account is not exempt, other subscribers handle request. + $this->eventDispatcher->dispatch($event, MaintenanceModeEvents::MAINTENANCE_MODE_REQUEST); } else { // Display a message if the logged in user has access to the site in @@ -140,15 +143,24 @@ public function onKernelRequestMaintenance(RequestEvent $event) { } /** - * Gets the site maintenance message. + * Returns response when site is in maintenance mode and user is not exempt. * - * @return \Drupal\Component\Render\MarkupInterface - * The formatted site maintenance message. + * @param \Symfony\Component\HttpKernel\Event\RequestEvent $event + * The event to process. */ - protected function getSiteMaintenanceMessage() { - return new FormattableMarkup($this->config->get('system.maintenance')->get('message'), [ - '@site' => $this->config->get('system.site')->get('name'), - ]); + public function onMaintenanceModeRequest(RequestEvent $event) { + $request = $event->getRequest(); + if ($request->getRequestFormat() !== 'html') { + $response = new Response($this->maintenanceMode->getSiteMaintenanceMessage(), 503, ['Content-Type' => 'text/plain']); + // Calling RequestEvent::setResponse() also stops propagation of event. + $event->setResponse($response); + return; + } + drupal_maintenance_theme(); + $response = $this->bareHtmlPageRenderer->renderBarePage(['#markup' => $this->maintenanceMode->getSiteMaintenanceMessage()], $this->t('Site under maintenance'), 'maintenance_page'); + $response->setStatusCode(503); + // Calling RequestEvent::setResponse() also stops propagation of the event. + $event->setResponse($response); } /** @@ -157,6 +169,10 @@ protected function getSiteMaintenanceMessage() { public static function getSubscribedEvents(): array { $events[KernelEvents::REQUEST][] = ['onKernelRequestMaintenance', 30]; $events[KernelEvents::EXCEPTION][] = ['onKernelRequestMaintenance']; + $events[MaintenanceModeEvents::MAINTENANCE_MODE_REQUEST][] = [ + 'onMaintenanceModeRequest', + -1000, + ]; return $events; } diff --git a/core/lib/Drupal/Core/Site/MaintenanceMode.php b/core/lib/Drupal/Core/Site/MaintenanceMode.php index 38b8bd7a8979..1713d6346c57 100644 --- a/core/lib/Drupal/Core/Site/MaintenanceMode.php +++ b/core/lib/Drupal/Core/Site/MaintenanceMode.php @@ -2,6 +2,8 @@ namespace Drupal\Core\Site; +use Drupal\Component\Render\FormattableMarkup; +use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Session\AccountInterface; use Drupal\Core\State\StateInterface; @@ -18,14 +20,28 @@ class MaintenanceMode implements MaintenanceModeInterface { */ protected $state; + /** + * The configuration factory. + * + * @var \Drupal\Core\Config\ConfigFactoryInterface + */ + protected $config; + /** * Constructs a new maintenance mode service. * * @param \Drupal\Core\State\StateInterface $state * The state. + * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * The config factory. */ - public function __construct(StateInterface $state) { + public function __construct(StateInterface $state, ConfigFactoryInterface $config_factory = NULL) { $this->state = $state; + if (!$config_factory) { + @trigger_error('Calling MaintenanceMode::__construct() without the $config_factory argument is deprecated in drupal:9.4.0 and the $config_factory argument will be required in drupal:10.0.0. See https://www.drupal.org/node/3255815', E_USER_DEPRECATED); + $config_factory = \Drupal::service('config.factory'); + } + $this->config = $config_factory; } /** @@ -52,4 +68,13 @@ public function exempt(AccountInterface $account) { return $account->hasPermission('access site in maintenance mode'); } + /** + * {@inheritdoc} + */ + public function getSiteMaintenanceMessage() { + return new FormattableMarkup($this->config->get('system.maintenance')->get('message'), [ + '@site' => $this->config->get('system.site')->get('name'), + ]); + } + } diff --git a/core/lib/Drupal/Core/Site/MaintenanceModeEvents.php b/core/lib/Drupal/Core/Site/MaintenanceModeEvents.php new file mode 100644 index 000000000000..2348fd54d7ad --- /dev/null +++ b/core/lib/Drupal/Core/Site/MaintenanceModeEvents.php @@ -0,0 +1,15 @@ +<?php + +namespace Drupal\Core\Site; + +/** + * Defines events for maintenance mode. + */ +final class MaintenanceModeEvents { + + /** + * The name of the event fired when request is made in maintenance more. + */ + const MAINTENANCE_MODE_REQUEST = 'site.maintenance_mode_request'; + +} diff --git a/core/lib/Drupal/Core/Site/MaintenanceModeInterface.php b/core/lib/Drupal/Core/Site/MaintenanceModeInterface.php index bc606b50f255..b3f42f43f52a 100644 --- a/core/lib/Drupal/Core/Site/MaintenanceModeInterface.php +++ b/core/lib/Drupal/Core/Site/MaintenanceModeInterface.php @@ -32,4 +32,12 @@ public function applies(RouteMatchInterface $route_match); */ public function exempt(AccountInterface $account); + /** + * Gets the site maintenance message. + * + * @return \Drupal\Component\Render\MarkupInterface + * The formatted site maintenance message. + */ + public function getSiteMaintenanceMessage(); + } diff --git a/core/modules/jsonapi/config/install/jsonapi.settings.yml b/core/modules/jsonapi/config/install/jsonapi.settings.yml index c94a4047576d..6ec8f68a4793 100644 --- a/core/modules/jsonapi/config/install/jsonapi.settings.yml +++ b/core/modules/jsonapi/config/install/jsonapi.settings.yml @@ -1,2 +1,5 @@ langcode: en read_only: true +maintenance_header_retry_seconds: + min: 5 + max: 10 diff --git a/core/modules/jsonapi/config/schema/jsonapi.schema.yml b/core/modules/jsonapi/config/schema/jsonapi.schema.yml index 0fdec75d1c37..23cde29c466b 100644 --- a/core/modules/jsonapi/config/schema/jsonapi.schema.yml +++ b/core/modules/jsonapi/config/schema/jsonapi.schema.yml @@ -5,3 +5,13 @@ jsonapi.settings: read_only: type: boolean label: 'Restrict JSON:API to only read operations' + maintenance_header_retry_seconds: + type: mapping + label: 'Maintenance mode Retry-After header settings' + mapping: + min: + type: integer + label: 'Minimum value for Retry-After header in seconds' + max: + type: integer + label: 'Maximum value for Retry-After header in seconds' diff --git a/core/modules/jsonapi/jsonapi.install b/core/modules/jsonapi/jsonapi.install index 36bc4f021105..80009b525a46 100644 --- a/core/modules/jsonapi/jsonapi.install +++ b/core/modules/jsonapi/jsonapi.install @@ -82,3 +82,17 @@ function jsonapi_requirements($phase) { function jsonapi_update_last_removed() { return 8701; } + +/** + * Set values for maintenance_header_retry_seconds min and max. + * + * @see https://www.drupal.org/node/3247453 + */ +function jsonapi_update_9401() { + $config = \Drupal::configFactory()->getEditable('jsonapi.settings'); + $config->set('maintenance_header_retry_seconds', [ + 'min' => 5, + 'max' => 10, + ]); + $config->save(TRUE); +} diff --git a/core/modules/jsonapi/jsonapi.services.yml b/core/modules/jsonapi/jsonapi.services.yml index 56e585f90072..33852841aa80 100644 --- a/core/modules/jsonapi/jsonapi.services.yml +++ b/core/modules/jsonapi/jsonapi.services.yml @@ -211,6 +211,11 @@ services: - [setValidator, []] tags: - { name: event_subscriber, priority: 1000 } + jsonapi.maintenance_mode_subscriber: + class: Drupal\jsonapi\EventSubscriber\JsonapiMaintenanceModeSubscriber + arguments: ['@maintenance_mode', '@config.factory'] + tags: + - { name: event_subscriber } # Revision management. jsonapi.version_negotiator: diff --git a/core/modules/jsonapi/src/EventSubscriber/JsonapiMaintenanceModeSubscriber.php b/core/modules/jsonapi/src/EventSubscriber/JsonapiMaintenanceModeSubscriber.php new file mode 100644 index 000000000000..a00db170c5f4 --- /dev/null +++ b/core/modules/jsonapi/src/EventSubscriber/JsonapiMaintenanceModeSubscriber.php @@ -0,0 +1,90 @@ +<?php + +namespace Drupal\jsonapi\EventSubscriber; + +use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Site\MaintenanceModeEvents; +use Drupal\Core\Site\MaintenanceModeInterface; +use Drupal\jsonapi\JsonApiResource\ErrorCollection; +use Drupal\jsonapi\JsonApiResource\JsonApiDocumentTopLevel; +use Drupal\jsonapi\JsonApiResource\LinkCollection; +use Drupal\jsonapi\JsonApiResource\NullIncludedData; +use Drupal\jsonapi\ResourceResponse; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\HttpKernel\Exception\HttpException; + +/** + * Maintenance mode subscriber for JSON:API requests. + * + * @internal JSON:API maintains no PHP API. The API is the HTTP API. This class + * may change at any time and could break any dependencies on it. + */ +class JsonapiMaintenanceModeSubscriber implements EventSubscriberInterface { + + /** + * The maintenance mode. + * + * @var \Drupal\Core\Site\MaintenanceMode + */ + protected $maintenanceMode; + + /** + * The configuration factory. + * + * @var \Drupal\Core\Config\ConfigFactoryInterface + */ + protected $config; + + /** + * Constructs a new JsonapiMaintenanceModeSubscriber. + * + * @param \Drupal\Core\Site\MaintenanceModeInterface $maintenance_mode + * The maintenance mode. + * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * The config factory. + */ + public function __construct(MaintenanceModeInterface $maintenance_mode, ConfigFactoryInterface $config_factory) { + $this->maintenanceMode = $maintenance_mode; + $this->config = $config_factory; + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + $events = []; + $events[MaintenanceModeEvents::MAINTENANCE_MODE_REQUEST][] = [ + 'onMaintenanceModeRequest', + -800, + ]; + return $events; + } + + /** + * Returns response when site is in maintenance mode and user is not exempt. + * + * @param \Symfony\Component\HttpKernel\Event\RequestEvent $event + * The event to process. + */ + public function onMaintenanceModeRequest(RequestEvent $event) { + $request = $event->getRequest(); + + if ($request->getRequestFormat() !== 'api_json') { + return; + } + // Retry-After will be random within a range defined in jsonapi settings. + // The goals are to keep it short and to reduce the thundering herd problem. + $header_settings = $this->config->get('jsonapi.settings')->get('maintenance_header_retry_seconds'); + $retry_after_time = rand($header_settings['min'], $header_settings['max']); + $http_exception = new HttpException(503, $this->maintenanceMode->getSiteMaintenanceMessage()); + $document = new JsonApiDocumentTopLevel(new ErrorCollection([$http_exception]), new NullIncludedData(), new LinkCollection([])); + $response = new ResourceResponse($document, $http_exception->getStatusCode(), [ + 'Content-Type' => 'application/vnd.api+json', + 'Retry-After' => $retry_after_time, + ]); + // Calling RequestEvent::setResponse() also stops propagation of event. + $event->setResponse($response); + } + +} diff --git a/core/modules/jsonapi/tests/fixtures/update/jsonapi.php b/core/modules/jsonapi/tests/fixtures/update/jsonapi.php new file mode 100644 index 000000000000..462590152a1b --- /dev/null +++ b/core/modules/jsonapi/tests/fixtures/update/jsonapi.php @@ -0,0 +1,54 @@ +<?php + +/** + * @file + * Test fixture. + */ + +use Drupal\Core\Database\Database; +use Drupal\Core\Serialization\Yaml; + +$connection = Database::getConnection(); + +$connection->insert('key_value') + ->fields([ + 'collection', + 'name', + 'value', + ]) + ->values([ + 'collection' => 'system.schema', + 'name' => 'jsonapi', + 'value' => serialize(9000), + ]) + ->execute(); + +// Update core.extension. +$extensions = $connection->select('config') + ->fields('config', ['data']) + ->condition('collection', '') + ->condition('name', 'core.extension') + ->execute() + ->fetchField(); +$extensions = unserialize($extensions); +$extensions['module']['jsonapi'] = 0; +$extensions['module']['serialization'] = 0; +$connection->update('config') + ->fields(['data' => serialize($extensions)]) + ->condition('collection', '') + ->condition('name', 'core.extension') + ->execute(); + +$jsonapi_settings = Yaml::decode(file_get_contents(__DIR__ . '/jsonapi.settings.yml')); +$connection->insert('config') + ->fields([ + 'collection', + 'name', + 'data', + ]) + ->values([ + 'collection' => '', + 'name' => 'jsonapi.settings', + 'data' => serialize($jsonapi_settings), + ]) + ->execute(); diff --git a/core/modules/jsonapi/tests/fixtures/update/jsonapi.settings.yml b/core/modules/jsonapi/tests/fixtures/update/jsonapi.settings.yml new file mode 100644 index 000000000000..c94a4047576d --- /dev/null +++ b/core/modules/jsonapi/tests/fixtures/update/jsonapi.settings.yml @@ -0,0 +1,2 @@ +langcode: en +read_only: true diff --git a/core/modules/jsonapi/tests/src/Functional/JsonApiFunctionalTest.php b/core/modules/jsonapi/tests/src/Functional/JsonApiFunctionalTest.php index 272fbdf395c4..167e279b444c 100644 --- a/core/modules/jsonapi/tests/src/Functional/JsonApiFunctionalTest.php +++ b/core/modules/jsonapi/tests/src/Functional/JsonApiFunctionalTest.php @@ -513,6 +513,39 @@ public function testRead() { ])); $this->assertSession()->statusCodeEquals(200); $this->assertCount(0, $collection_output['data']); + + // Request in maintenance mode returns valid JSON. + $this->container->get('state')->set('system.maintenance_mode', TRUE); + $response = $this->drupalGet('/jsonapi/taxonomy_term/tags'); + $this->assertSession()->statusCodeEquals(503); + $this->assertSession()->responseHeaderContains('Content-Type', 'application/vnd.api+json'); + $retry_after_time = $this->getSession()->getResponseHeader('Retry-After'); + $this->assertTrue($retry_after_time >= 5 && $retry_after_time <= 10); + $expected_message = 'Drupal is currently under maintenance. We should be back shortly. Thank you for your patience.'; + $this->assertSame($expected_message, Json::decode($response)['errors'][0]['detail']); + + // Test that logged in user does not get logged out in maintenance mode + // when hitting jsonapi route. + $this->container->get('state')->set('system.maintenance_mode', FALSE); + $this->drupalLogin($this->userCanViewProfiles); + $this->container->get('state')->set('system.maintenance_mode', TRUE); + $this->drupalGet('/jsonapi/taxonomy_term/tags'); + $this->assertSession()->statusCodeEquals(503); + $this->assertTrue($this->drupalUserIsLoggedIn($this->userCanViewProfiles)); + // Test that user gets logged out when hitting non-jsonapi route. + $this->drupalGet('/some/normal/route'); + $this->assertFalse($this->drupalUserIsLoggedIn($this->userCanViewProfiles)); + $this->container->get('state')->set('system.maintenance_mode', FALSE); + + // Test that admin user can bypass maintenance mode. + $admin_user = $this->drupalCreateUser([], NULL, TRUE); + $this->drupalLogin($admin_user); + $this->container->get('state')->set('system.maintenance_mode', TRUE); + $this->drupalGet('/jsonapi/taxonomy_term/tags'); + $this->assertSession()->statusCodeEquals(200); + $this->assertTrue($this->drupalUserIsLoggedIn($admin_user)); + $this->container->get('state')->set('system.maintenance_mode', FALSE); + $this->drupalLogout(); } /** diff --git a/core/modules/jsonapi/tests/src/Functional/Update/JsonApiUpdatePathTest.php b/core/modules/jsonapi/tests/src/Functional/Update/JsonApiUpdatePathTest.php new file mode 100644 index 000000000000..ec9b1cbe0d85 --- /dev/null +++ b/core/modules/jsonapi/tests/src/Functional/Update/JsonApiUpdatePathTest.php @@ -0,0 +1,50 @@ +<?php + +namespace Drupal\Tests\jsonapi\Functional\Update; + +use Drupal\FunctionalTests\Update\UpdatePathTestBase; + +/** + * Tests adding retry-after header settings. + * + * @group legacy + * @group jsonapi + */ +class JsonApiUpdatePathTest extends UpdatePathTestBase { + + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + + /** + * {@inheritdoc} + */ + protected function setDatabaseDumpFiles() { + $this->databaseDumpFiles = [ + __DIR__ . '/../../../../../system/tests/fixtures/update/drupal-9.0.0.bare.standard.php.gz', + __DIR__ . '/../../../../tests/fixtures/update/jsonapi.php', + ]; + } + + /** + * Tests adding retry-after header settings. + * + * @see jsonapi_update_9401() + */ + public function testUpdate9401() { + $config = $this->config('jsonapi.settings'); + $this->assertTrue($config->get('read_only')); + $this->assertNull($config->get('maintenance_header_retry_seconds')); + + // Run updates. + $this->runUpdates(); + + $config = $this->config('jsonapi.settings'); + $this->assertTrue($config->get('read_only')); + $header_settings = $config->get('maintenance_header_retry_seconds'); + $this->assertSame(5, $header_settings['min']); + $this->assertSame(10, $header_settings['max']); + } + +} diff --git a/core/modules/user/src/EventSubscriber/MaintenanceModeSubscriber.php b/core/modules/user/src/EventSubscriber/MaintenanceModeSubscriber.php index e805162c0825..390dc182cd62 100644 --- a/core/modules/user/src/EventSubscriber/MaintenanceModeSubscriber.php +++ b/core/modules/user/src/EventSubscriber/MaintenanceModeSubscriber.php @@ -4,12 +4,12 @@ use Drupal\Core\Routing\RouteMatch; use Drupal\Core\Session\AccountInterface; +use Drupal\Core\Site\MaintenanceModeEvents; use Drupal\Core\Site\MaintenanceModeInterface; use Drupal\Core\Url; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpKernel\Event\RequestEvent; -use Symfony\Component\HttpKernel\KernelEvents; /** * Maintenance mode subscriber to log out users. @@ -48,8 +48,14 @@ public function __construct(MaintenanceModeInterface $maintenance_mode, AccountI * * @param \Symfony\Component\HttpKernel\Event\RequestEvent $event * The event to process. + * + * @deprecated in drupal:9.4.0 and is removed from drupal:10.0.0. Use + * \Drupal\user\EventSubscriber::onMaintenanceModeRequest() instead. + * + * @see https://www.drupal.org/node/3255799 */ public function onKernelRequestMaintenance(RequestEvent $event) { + @trigger_error('\Drupal\user\EventSubscriber::onKernelRequestMaintenance() is deprecated in drupal:9.4.0 and is removed from drupal:10.0.0. Use \Drupal\user\EventSubscriber::onMaintenanceModeRequest() instead. See https://www.drupal.org/node/3255799', E_USER_DEPRECATED); $request = $event->getRequest(); $route_match = RouteMatch::createFromRequest($request); if ($this->maintenanceMode->applies($route_match)) { @@ -64,11 +70,31 @@ public function onKernelRequestMaintenance(RequestEvent $event) { } } + /** + * Logout users if site is in maintenance mode and user is not exempt. + * + * @param \Symfony\Component\HttpKernel\Event\RequestEvent $event + * The event to process. + */ + public function onMaintenanceModeRequest(RequestEvent $event) { + // If the site is offline, log out unprivileged users. + if ($this->account->isAuthenticated()) { + user_logout(); + // Redirect to homepage. + $event->setResponse( + new RedirectResponse(Url::fromRoute('<front>')->toString()) + ); + } + } + /** * {@inheritdoc} */ public static function getSubscribedEvents(): array { - $events[KernelEvents::REQUEST][] = ['onKernelRequestMaintenance', 31]; + $events[MaintenanceModeEvents::MAINTENANCE_MODE_REQUEST][] = [ + 'onMaintenanceModeRequest', + -900, + ]; return $events; } -- GitLab