Commit 18feefc3 authored by alexpott's avatar alexpott

Issue #2403307 by dawehner, marthinal, tedbow, clemens.tolboom, Wim Leers,...

Issue #2403307 by dawehner, marthinal, tedbow, clemens.tolboom, Wim Leers, neclimdul, Crell, klausi, andypost, e0ipso: RPC endpoints for user authentication: log in, check login status, log out
parent a1af9fdc
...@@ -1126,7 +1126,7 @@ services: ...@@ -1126,7 +1126,7 @@ services:
arguments: ['@state', '@current_user'] arguments: ['@state', '@current_user']
maintenance_mode_subscriber: maintenance_mode_subscriber:
class: Drupal\Core\EventSubscriber\MaintenanceModeSubscriber class: Drupal\Core\EventSubscriber\MaintenanceModeSubscriber
arguments: ['@maintenance_mode', '@config.factory', '@string_translation', '@url_generator', '@current_user', '@bare_html_page_renderer'] arguments: ['@maintenance_mode', '@config.factory', '@string_translation', '@url_generator', '@current_user', '@bare_html_page_renderer', '@messenger']
tags: tags:
- { name: event_subscriber } - { name: event_subscriber }
path_subscriber: path_subscriber:
...@@ -1631,3 +1631,6 @@ services: ...@@ -1631,3 +1631,6 @@ services:
arguments: ['@current_user', '@path.current', '@path.matcher', '@language_manager'] arguments: ['@current_user', '@path.current', '@path.matcher', '@language_manager']
tags: tags:
- { name: event_subscriber } - { name: event_subscriber }
messenger:
class: Drupal\Core\Messenger\SessionMessenger
arguments: ['@page_cache_kill_switch']
...@@ -9,8 +9,6 @@ ...@@ -9,8 +9,6 @@
use Drupal\Component\Utility\SafeMarkup; use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\Unicode; use Drupal\Component\Utility\Unicode;
use Drupal\Core\Logger\RfcLogLevel; use Drupal\Core\Logger\RfcLogLevel;
use Drupal\Core\Render\Markup;
use Drupal\Component\Render\MarkupInterface;
use Drupal\Core\Session\AccountInterface; use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Site\Settings; use Drupal\Core\Site\Settings;
use Drupal\Core\Utility\Error; use Drupal\Core\Utility\Error;
...@@ -437,30 +435,15 @@ function watchdog_exception($type, Exception $exception, $message = NULL, $varia ...@@ -437,30 +435,15 @@ function watchdog_exception($type, Exception $exception, $message = NULL, $varia
* *
* @see drupal_get_messages() * @see drupal_get_messages()
* @see status-messages.html.twig * @see status-messages.html.twig
*
* @deprecated Deprecated as of Drupal 8.2.
* Use \Drupal::service('messenger')->addMessage() instead.
*/ */
function drupal_set_message($message = NULL, $type = 'status', $repeat = FALSE) { function drupal_set_message($message = NULL, $type = 'status', $repeat = FALSE) {
if (isset($message)) { /* @var \Drupal\Core\Messenger\MessengerInterface $messenger */
if (!isset($_SESSION['messages'][$type])) { $messenger = \Drupal::service('messenger');
$_SESSION['messages'][$type] = array(); $messenger->addMessage($message, $type, $repeat);
} return $messenger->getMessages();
// Convert strings which are safe to the simplest Markup objects.
if (!($message instanceof Markup) && $message instanceof MarkupInterface) {
$message = Markup::create((string) $message);
}
// Do not use strict type checking so that equivalent string and
// MarkupInterface objects are detected.
if ($repeat || !in_array($message, $_SESSION['messages'][$type])) {
$_SESSION['messages'][$type][] = $message;
}
// Mark this page as being uncacheable.
\Drupal::service('page_cache_kill_switch')->trigger();
}
// Messages not set when DB connection fails.
return isset($_SESSION['messages']) ? $_SESSION['messages'] : NULL;
} }
/** /**
...@@ -487,12 +470,19 @@ function drupal_set_message($message = NULL, $type = 'status', $repeat = FALSE) ...@@ -487,12 +470,19 @@ function drupal_set_message($message = NULL, $type = 'status', $repeat = FALSE)
* *
* @see drupal_set_message() * @see drupal_set_message()
* @see status-messages.html.twig * @see status-messages.html.twig
*
* @deprecated Deprecated as of Drupal 8.2.
* Use \Drupal::service('messenger')->getMessages() or
* \Drupal::service('messenger')->getMessagesByType() instead.
*/ */
function drupal_get_messages($type = NULL, $clear_queue = TRUE) { function drupal_get_messages($type = NULL, $clear_queue = TRUE) {
if ($messages = drupal_set_message()) { /** @var \Drupal\Core\Messenger\MessengerInterface $messenger */
$messenger = \Drupal::service('messenger');
if ($messages = $messenger->getMessages()) {
if ($type) { if ($type) {
if ($clear_queue) { if ($clear_queue) {
unset($_SESSION['messages'][$type]); $messenger->deleteMessagesByType($type);
} }
if (isset($messages[$type])) { if (isset($messages[$type])) {
return array($type => $messages[$type]); return array($type => $messages[$type]);
...@@ -500,7 +490,7 @@ function drupal_get_messages($type = NULL, $clear_queue = TRUE) { ...@@ -500,7 +490,7 @@ function drupal_get_messages($type = NULL, $clear_queue = TRUE) {
} }
else { else {
if ($clear_queue) { if ($clear_queue) {
unset($_SESSION['messages']); $messenger->deleteMessages();
} }
return $messages; return $messages;
} }
......
...@@ -27,6 +27,17 @@ protected static function getPriority() { ...@@ -27,6 +27,17 @@ protected static function getPriority() {
return -75; return -75;
} }
/**
* Handles a 400 error for JSON.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
* The event to process.
*/
public function on400(GetResponseForExceptionEvent $event) {
$response = new JsonResponse(array('message' => $event->getException()->getMessage()), Response::HTTP_BAD_REQUEST);
$event->setResponse($response);
}
/** /**
* Handles a 403 error for JSON. * Handles a 403 error for JSON.
* *
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
use Drupal\Component\Utility\SafeMarkup; use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Render\BareHtmlPageRendererInterface; use Drupal\Core\Render\BareHtmlPageRendererInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Routing\RouteMatch; use Drupal\Core\Routing\RouteMatch;
use Drupal\Core\Routing\UrlGeneratorInterface; use Drupal\Core\Routing\UrlGeneratorInterface;
use Drupal\Core\Session\AccountInterface; use Drupal\Core\Session\AccountInterface;
...@@ -58,6 +59,13 @@ class MaintenanceModeSubscriber implements EventSubscriberInterface { ...@@ -58,6 +59,13 @@ class MaintenanceModeSubscriber implements EventSubscriberInterface {
*/ */
protected $bareHtmlPageRenderer; protected $bareHtmlPageRenderer;
/**
* The messenger.
*
* @var \Drupal\Core\Messenger\MessengerInterface
*/
protected $messenger;
/** /**
* Constructs a new MaintenanceModeSubscriber. * Constructs a new MaintenanceModeSubscriber.
* *
...@@ -73,14 +81,17 @@ class MaintenanceModeSubscriber implements EventSubscriberInterface { ...@@ -73,14 +81,17 @@ class MaintenanceModeSubscriber implements EventSubscriberInterface {
* The current user. * The current user.
* @param \Drupal\Core\Render\BareHtmlPageRendererInterface $bare_html_page_renderer * @param \Drupal\Core\Render\BareHtmlPageRendererInterface $bare_html_page_renderer
* The bare HTML page renderer. * The bare HTML page renderer.
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* The messenger.
*/ */
public function __construct(MaintenanceModeInterface $maintenance_mode, ConfigFactoryInterface $config_factory, TranslationInterface $translation, UrlGeneratorInterface $url_generator, AccountInterface $account, BareHtmlPageRendererInterface $bare_html_page_renderer) { public function __construct(MaintenanceModeInterface $maintenance_mode, ConfigFactoryInterface $config_factory, TranslationInterface $translation, UrlGeneratorInterface $url_generator, AccountInterface $account, BareHtmlPageRendererInterface $bare_html_page_renderer, MessengerInterface $messenger) {
$this->maintenanceMode = $maintenance_mode; $this->maintenanceMode = $maintenance_mode;
$this->config = $config_factory; $this->config = $config_factory;
$this->stringTranslation = $translation; $this->stringTranslation = $translation;
$this->urlGenerator = $url_generator; $this->urlGenerator = $url_generator;
$this->account = $account; $this->account = $account;
$this->bareHtmlPageRenderer = $bare_html_page_renderer; $this->bareHtmlPageRenderer = $bare_html_page_renderer;
$this->messenger = $messenger;
} }
/** /**
...@@ -118,10 +129,10 @@ public function onKernelRequestMaintenance(GetResponseEvent $event) { ...@@ -118,10 +129,10 @@ public function onKernelRequestMaintenance(GetResponseEvent $event) {
// settings page. // settings page.
if ($route_match->getRouteName() != 'system.site_maintenance_mode') { if ($route_match->getRouteName() != 'system.site_maintenance_mode') {
if ($this->account->hasPermission('administer site configuration')) { if ($this->account->hasPermission('administer site configuration')) {
$this->drupalSetMessage($this->t('Operating in maintenance mode. <a href=":url">Go online.</a>', array(':url' => $this->urlGenerator->generate('system.site_maintenance_mode'))), 'status', FALSE); $this->messenger->addMessage($this->t('Operating in maintenance mode. <a href=":url">Go online.</a>', [':url' => $this->urlGenerator->generate('system.site_maintenance_mode')]), 'status', FALSE);
} }
else { else {
$this->drupalSetMessage($this->t('Operating in maintenance mode.'), 'status', FALSE); $this->messenger->addMessage($this->t('Operating in maintenance mode.'), 'status', FALSE);
} }
} }
} }
...@@ -140,13 +151,6 @@ protected function getSiteMaintenanceMessage() { ...@@ -140,13 +151,6 @@ protected function getSiteMaintenanceMessage() {
)); ));
} }
/**
* Wraps the drupal_set_message function.
*/
protected function drupalSetMessage($message = NULL, $type = 'status', $repeat = FALSE) {
return drupal_set_message($message, $type, $repeat);
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\ServiceProviderInterface; use Drupal\Core\DependencyInjection\ServiceProviderInterface;
use Drupal\Core\DependencyInjection\ServiceModifierInterface; use Drupal\Core\DependencyInjection\ServiceModifierInterface;
use Drupal\Core\Messenger\StaticMessenger;
use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Reference;
/** /**
...@@ -34,6 +35,9 @@ public function register(ContainerBuilder $container) { ...@@ -34,6 +35,9 @@ public function register(ContainerBuilder $container) {
->register('keyvalue', 'Drupal\Core\KeyValueStore\KeyValueMemoryFactory'); ->register('keyvalue', 'Drupal\Core\KeyValueStore\KeyValueMemoryFactory');
$container $container
->register('keyvalue.expirable', 'Drupal\Core\KeyValueStore\KeyValueNullExpirableFactory'); ->register('keyvalue.expirable', 'Drupal\Core\KeyValueStore\KeyValueNullExpirableFactory');
$definition = $container->getDefinition('messenger');
$definition->setClass(StaticMessenger::class);
$definition->setArguments([new Reference('page_cache_kill_switch')]);
// Replace services with no-op implementations. // Replace services with no-op implementations.
$container $container
......
<?php
namespace Drupal\Core\Messenger;
/**
* Stores runtime messages sent out to individual users on the page.
*
* An example for these messages is for example: "Content X got saved".
*/
interface MessengerInterface {
/**
* A status message.
*/
const TYPE_STATUS = 'status';
/**
* A warning.
*/
const TYPE_WARNING = 'warning';
/**
* An error.
*/
const TYPE_ERROR = 'error';
/**
* Adds a new message to the queue.
*
* @param string|\Drupal\Component\Render\MarkupInterface $message
* (optional) The translated message to be displayed to the user. For
* consistency with other messages, it should begin with a capital letter
* and end with a period.
* @param string $type
* (optional) The message's type. Either self::TYPE_STATUS,
* self::TYPE_WARNING, or self::TYPE_ERROR.
* @param bool $repeat
* (optional) If this is FALSE and the message is already set, then the
* message won't be repeated. Defaults to FALSE.
*
* @return $this
*/
public function addMessage($message, $type = self::TYPE_STATUS, $repeat = FALSE);
/**
* Gets all messages.
*
* @return string[][]|\Drupal\Component\Render\MarkupInterface[][]
* Keys are message types and values are indexed arrays of messages. Message
* types are either self::TYPE_STATUS, self::TYPE_WARNING, or
* self::TYPE_ERROR.
*/
public function getMessages();
/**
* Gets all messages of a certain type.
*
* @param string $type
* The messages' type. Either self::TYPE_STATUS, self::TYPE_WARNING,
* or self::TYPE_ERROR.
*
* @return string[]|\Drupal\Component\Render\MarkupInterface[]
*/
public function getMessagesByType($type);
/**
* Deletes all messages.
*
* @return $this
*/
public function deleteMessages();
/**
* Deletes all messages of a certain type.
*
* @param string $type
* The messages' type. Either self::TYPE_STATUS, self::TYPE_WARNING, or
* self::TYPE_ERROR.
*
* @return $this
*/
public function deleteMessagesByType($type);
}
<?php
namespace Drupal\Core\Messenger;
use Drupal\Component\Render\MarkupInterface;
use Drupal\Core\PageCache\ResponsePolicy\KillSwitch;
use Drupal\Core\Render\Markup;
/**
* Provides a session-based messenger.
*/
class SessionMessenger implements MessengerInterface {
/**
* The page caching kill switch.
*
* @var \Drupal\Core\PageCache\ResponsePolicy\KillSwitch
*/
protected $pageCacheKillSwitch;
/**
* Constructs a new instance.
*
* @param \Drupal\Core\Session\SessionManagerInterface
* @param \Drupal\Core\PageCache\ResponsePolicy\KillSwitch $page_cache_kill_switch
* The page caching kill switch.
*/
public function __construct(KillSwitch $page_cache_kill_switch) {
$this->pageCacheKillSwitch = $page_cache_kill_switch;
}
/**
* {@inheritdoc}
*/
public function addMessage($message, $type = self::TYPE_STATUS, $repeat = FALSE) {
if (!isset($_SESSION['messages'][$type])) {
$_SESSION['messages'][$type] = [];
}
// Convert strings which are safe to the simplest Markup objects.
if (!($message instanceof Markup) && $message instanceof MarkupInterface) {
$message = Markup::create((string) $message);
}
// Do not use strict type checking so that equivalent string and
// \Drupal\Core\Render\Markup objects are detected.
if ($repeat || !in_array($message, $_SESSION['messages'][$type])) {
$_SESSION['messages'][$type][] = $message;
$this->pageCacheKillSwitch->trigger();
}
return $this;
}
/**
* {@inheritdoc}
*/
public function getMessages() {
$messages = isset($_SESSION['messages']) ? $_SESSION['messages'] : [];
foreach ($messages as $type => $messages_by_type) {
$messages[$type] = $messages_by_type;
}
return $messages;
}
/**
* {@inheritdoc}
*/
public function getMessagesByType($type) {
$messages = isset($_SESSION['messages']) && isset($_SESSION['messages'][$type]) ? $_SESSION['messages'][$type] : [];
return $messages;
}
/**
* {@inheritdoc}
*/
public function deleteMessages() {
unset($_SESSION['messages']);
return $this;
}
/**
* {@inheritdoc}
*/
public function deleteMessagesByType($type) {
unset($_SESSION['messages'][$type]);
return $this;
}
}
<?php
namespace Drupal\Core\Messenger;
use Drupal\Component\Render\MarkupInterface;
use Drupal\Core\PageCache\ResponsePolicy\KillSwitch;
use Drupal\Core\Render\Markup;
/**
* Provides a messenger that stores messages for this request only.
*/
class StaticMessenger implements MessengerInterface {
/**
* The messages that have been set.
*
* @var array[]
* Keys are either self::TYPE_STATUS, self::TYPE_WARNING, or
* self::TYPE_ERROR. Values are arrays of arrays with the following keys:
* - message (string): the message.
* - safe (bool): whether the message is marked as safe markup.
*/
protected $messages = [];
/**
* The page caching kill switch.
*
* @var \Drupal\Core\PageCache\ResponsePolicy\KillSwitch
*/
protected $pageCacheKillSwitch;
/**
* Constructs a new instance.
*
* @param \Drupal\Core\PageCache\ResponsePolicy\KillSwitch $page_cache_kill_switch
* The page caching kill switch.
*/
public function __construct(KillSwitch $page_cache_kill_switch) {
$this->pageCacheKillSwitch = $page_cache_kill_switch;
}
/**
* {@inheritdoc}
*/
public function addMessage($message, $type = self::TYPE_STATUS, $repeat = FALSE) {
if (!isset($this->messages[$type])) {
$this->messages[$type] = [];
}
// Convert strings which are safe to the simplest Markup objects.
if (!($message instanceof Markup) && $message instanceof MarkupInterface) {
$message = Markup::create((string) $message);
}
// Do not use strict type checking so that equivalent string and
// MarkupInterface objects are detected.
if ($repeat || !in_array($message, $this->messages[$type])) {
$this->messages[$type][] = $message;
$this->pageCacheKillSwitch->trigger();
}
return $this;
}
/**
* {@inheritdoc}
*/
public function getMessages() {
$messages = isset($this->messages) ? $this->messages : [];
foreach ($messages as $type => $messages_by_type) {
$messages[$type] = $messages_by_type;
}
return $messages;
}
/**
* {@inheritdoc}
*/
public function getMessagesByType($type) {
$messages = isset($this->messages) && isset($this->messages[$type]) ? $this->messages[$type] : [];
return $messages;
}
/**
* {@inheritdoc}
*/
public function deleteMessages() {
unset($this->messages);
return $this;
}
/**
* {@inheritdoc}
*/
public function deleteMessagesByType($type) {
unset($this->messages[$type]);
return $this;
}
}
...@@ -64,3 +64,13 @@ services: ...@@ -64,3 +64,13 @@ services:
class: Drupal\serialization\EntityResolver\TargetIdResolver class: Drupal\serialization\EntityResolver\TargetIdResolver
tags: tags:
- { name: entity_resolver} - { name: entity_resolver}
serialization.exception.default:
class: Drupal\serialization\EventSubscriber\DefaultExceptionSubscriber
tags:
- { name: event_subscriber }
arguments: ['@serializer', '%serializer.formats%']
serialization.user_route_alter_subscriber:
class: Drupal\serialization\EventSubscriber\UserRouteAlterSubscriber
tags:
- { name: event_subscriber }
arguments: ['@serializer', '%serializer.formats%']
...@@ -115,6 +115,16 @@ public function on422(GetResponseForExceptionEvent $event) { ...@@ -115,6 +115,16 @@ public function on422(GetResponseForExceptionEvent $event) {
$this->setEventResponse($event, Response::HTTP_UNPROCESSABLE_ENTITY); $this->setEventResponse($event, Response::HTTP_UNPROCESSABLE_ENTITY);
} }
/**
* Handles a 429 error for HTTP.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
* The event to process.
*/
public function on429(GetResponseForExceptionEvent $event) {
$this->setEventResponse($event, Response::HTTP_TOO_MANY_REQUESTS);
}
/** /**
* Sets the Response for the exception event. * Sets the Response for the exception event.
* *
......
<?php
namespace Drupal\serialization\EventSubscriber;
use Drupal\Core\Routing\RouteBuildEvent;
use Drupal\Core\Routing\RoutingEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Serializer\SerializerInterface;
/**
* Alters user authentication routes to support additional serialization formats.
*/
class UserRouteAlterSubscriber implements EventSubscriberInterface {
/**
* The serializer.
*
* @var \Symfony\Component\Serializer\Serializer
*/
protected $serializer;
/**
* The available serialization formats.
*
* @var array
*/
protected $serializerFormats = [];
/**
* UserRouteAlterSubscriber constructor.
*
* @param \Symfony\Component\Serializer\SerializerInterface $serializer
* The serializer service.
* @param array $serializer_formats
* The available serializer formats.
*/
public function __construct(SerializerInterface $serializer, array $serializer_formats) {
$this->serializer = $serializer;
$this->serializerFormats = $serializer_formats;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
$events[RoutingEvents::ALTER][] = 'onRoutingAlterAddFormats';
return $events;
}
/**
* Adds supported formats to the user authentication HTTP routes.
*
* @param \Drupal\Core\Routing\RouteBuildEvent $event
* The event to process.
*/
public function onRoutingAlterAddFormats(RouteBuildEvent $event) {
$route_names = [
'user.login_status.http',
'user.login.http',
'user.logout.http',
];
$routes = $event->getRouteCollection();
foreach ($route_names as $route_name) {
if ($route = $routes->get($route_name)) {
$formats = explode('|', $route->getRequirement('_format'));
$formats = array_unique($formats + $this->serializerFormats);
$route->setRequirement('_format', implode('|', $formats));
}
}
}
}
This diff is collapsed.
...@@ -129,6 +129,34 @@ user.login: ...@@ -129,6 +129,34 @@ user.login:
options: options:
_maintenance_access: TRUE _maintenance_access: TRUE
user.login.http:
path: '/user/login'
defaults:
_controller: \Drupal\user\Controller\UserAuthenticationController::login
methods: [POST]
requirements:
_user_is_logged_in: 'FALSE'
_format: 'json'
user.login_status.http:
path: '/user/login_status'
defaults:
_controller: \Drupal\user\Controller\UserAuthenticationController::loginStatus
methods: [GET]
requirements:
_access: 'TRUE'
_format: 'json'
user.logout.http:
path: '/user/logout'
defaults:
_controller: \Drupal\user\Controller\UserAuthenticationController::logout
methods: [POST]
requirements:
_user_is_logged_in: 'TRUE'
_format: 'json'
_csrf_token: 'TRUE'
user.cancel_confirm: user.cancel_confirm:
path: '/user/{user}/cancel/confirm/{timestamp}/{hashed_pass}' path: '/user/{user}/cancel/confirm/{timestamp}/{hashed_pass}'
defaults: defaults:
......
<?php
namespace Drupal\Tests\Core\Messenger;
use Drupal\Core\Messenger\SessionMessenger;
use Drupal\Core\PageCache\ResponsePolicy\KillSwitch;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\Core\Messenger\SessionMessenger