Commit 531f95eb authored by alexpott's avatar alexpott

Issue #2286971 by znerol, Berdir, almaudoh, cilefen: Remove dependency of...

Issue #2286971 by znerol, Berdir, almaudoh, cilefen: Remove dependency of current_user on request and authentication manager
parent 98366a9e
...@@ -47,10 +47,17 @@ ...@@ -47,10 +47,17 @@
* The killswitch in settings.php overrides all else, otherwise, the user must * The killswitch in settings.php overrides all else, otherwise, the user must
* have access to the 'administer software updates' permission. * have access to the 'administer software updates' permission.
* *
* @param \Symfony\Component\HttpFoundation\Request $request
* The incoming request.
*
* @return bool * @return bool
* TRUE if the current user can run authorize.php, and FALSE if not. * TRUE if the current user can run authorize.php, and FALSE if not.
*/ */
function authorize_access_allowed() { function authorize_access_allowed(Request $request) {
$account = \Drupal::service('authentication')->authenticate($request);
if ($account) {
\Drupal::currentUser()->setAccount($account);
}
return Settings::get('allow_authorize_operations', TRUE) && \Drupal::currentUser()->hasPermission('administer software updates'); return Settings::get('allow_authorize_operations', TRUE) && \Drupal::currentUser()->hasPermission('administer software updates');
} }
...@@ -79,7 +86,7 @@ function authorize_access_allowed() { ...@@ -79,7 +86,7 @@ function authorize_access_allowed() {
$show_messages = TRUE; $show_messages = TRUE;
$response = new Response(); $response = new Response();
if (authorize_access_allowed()) { if (authorize_access_allowed($request)) {
// Load both the Form API and Batch API. // Load both the Form API and Batch API.
require_once __DIR__ . '/includes/form.inc'; require_once __DIR__ . '/includes/form.inc';
require_once __DIR__ . '/includes/batch.inc'; require_once __DIR__ . '/includes/batch.inc';
......
...@@ -733,11 +733,6 @@ services: ...@@ -733,11 +733,6 @@ services:
tags: tags:
- { name: route_enhancer } - { name: route_enhancer }
- { name: event_subscriber } - { name: event_subscriber }
route_enhancer.authentication:
class: Drupal\Core\Routing\Enhancer\AuthenticationEnhancer
tags:
- { name: route_enhancer, priority: 1000 }
arguments: ['@authentication', '@current_user']
route_enhancer.entity: route_enhancer.entity:
class: Drupal\Core\Entity\Enhancer\EntityRouteEnhancer class: Drupal\Core\Entity\Enhancer\EntityRouteEnhancer
tags: tags:
...@@ -1110,15 +1105,14 @@ services: ...@@ -1110,15 +1105,14 @@ services:
- { name: service_collector, tag: authentication_provider, call: addProvider } - { name: service_collector, tag: authentication_provider, call: addProvider }
authentication_subscriber: authentication_subscriber:
class: Drupal\Core\EventSubscriber\AuthenticationSubscriber class: Drupal\Core\EventSubscriber\AuthenticationSubscriber
arguments: ['@authentication', '@current_user']
tags: tags:
- { name: event_subscriber } - { name: event_subscriber }
arguments: ['@authentication']
account_switcher: account_switcher:
class: Drupal\Core\Session\AccountSwitcher class: Drupal\Core\Session\AccountSwitcher
arguments: ['@current_user', '@session_handler.write_safe'] arguments: ['@current_user', '@session_handler.write_safe']
current_user: current_user:
class: Drupal\Core\Session\AccountProxy class: Drupal\Core\Session\AccountProxy
arguments: ['@authentication', '@request_stack']
session_configuration: session_configuration:
class: Drupal\Core\Session\SessionConfiguration class: Drupal\Core\Session\SessionConfiguration
arguments: ['%session.storage.options%'] arguments: ['%session.storage.options%']
......
<?php
/**
* @file
* Contains Drupal\Core\Authentication\AuthenticationManagerInterface.
*/
namespace Drupal\Core\Authentication;
/**
* Defines an interface for authentication managers.
*/
interface AuthenticationManagerInterface extends AuthenticationProviderInterface {
/**
* Returns the service id of the default authentication provider.
*
* @return string
* The service id of the default authentication provider.
*/
public function defaultProviderId();
}
<?php
/**
* @file
* Contains \Drupal\Core\Authentication\AuthenticationProviderChallengeInterface
*/
namespace Drupal\Core\Authentication;
use Symfony\Component\HttpFoundation\Request;
/**
* Generate a challenge when access is denied for unauthenticated users.
*
* On a 403 (access denied), if there are no credentials on the request, some
* authentication methods (e.g. basic auth) require that a challenge is sent to
* the client.
*/
interface AuthenticationProviderChallengeInterface {
/**
* Constructs an exception which is used to generate the challenge.
*
* @var \Symfony\Component\HttpFoundation\Request
* The request.
* @var \Exception $exception
* The previous exception.
*
* @return \Symfony\Component\HttpKernel\Exception\HttpExceptionInterface|NULL
* An exception to be used in order to generate an authentication challenge.
*/
public function challengeException(Request $request, \Exception $previous);
}
<?php
/**
* @file
* Contains \Drupal\Core\Authentication\AuthenticationProviderFilterInterface
*/
namespace Drupal\Core\Authentication;
use Symfony\Component\HttpFoundation\Request;
/**
* Restrict authentication methods to a subset of the site.
*
* Some authentication methods should not be available throughout a whole site.
* E.g., there are good reasons to restrict insecure methods like HTTP basic
* auth or an URL token authentication method to API-only routes.
*/
interface AuthenticationProviderFilterInterface {
/**
* Checks whether the authentication method is allowed on a given route.
*
* While authentication itself is run before routing, this method is called
* after routing, hence RouteMatch is available and can be used to inspect
* route options.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The request.
* @param bool $authenticated
* Whether or not the request is authenticated.
*
* @return bool
* TRUE if an authentication method is allowed on the request, otherwise
* FALSE.
*/
public function appliesToRoutedRequest(Request $request, $authenticated);
}
...@@ -8,7 +8,6 @@ ...@@ -8,7 +8,6 @@
namespace Drupal\Core\Authentication; namespace Drupal\Core\Authentication;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
/** /**
* Interface for authentication providers. * Interface for authentication providers.
...@@ -16,52 +15,27 @@ ...@@ -16,52 +15,27 @@
interface AuthenticationProviderInterface { interface AuthenticationProviderInterface {
/** /**
* Declares whether the provider applies to a specific request or not. * Checks whether suitable authentication credentials are on the request.
* *
* @param \Symfony\Component\HttpFoundation\Request $request * @param \Symfony\Component\HttpFoundation\Request $request
* The request object. * The request object.
* *
* @return bool * @return bool
* TRUE if the provider applies to the passed request, FALSE otherwise. * TRUE if authentication credentials suitable for this provider are on the
* request, FALSE otherwise.
*/ */
public function applies(Request $request); public function applies(Request $request);
/** /**
* Authenticates the user. * Authenticates the user.
* *
* @param \Symfony\Component\HttpFoundation\Request|null $request * @param \Symfony\Component\HttpFoundation\Request|NULL $request
* The request object. * The request object.
* *
* @return \Drupal\Core\Session\AccountInterface|null * @return \Drupal\Core\Session\AccountInterface|NULL
* AccountInterface - in case of a successful authentication. * AccountInterface - in case of a successful authentication.
* NULL - in case where authentication failed. * NULL - in case where authentication failed.
*/ */
public function authenticate(Request $request); public function authenticate(Request $request);
/**
* Performs cleanup tasks at the end of a request.
*
* Allow the authentication provider to clean up before the response is sent.
* This is uses for instance in \Drupal\Core\Authentication\Provider\Cookie to
* ensure the session gets committed.
*
* @param Request $request
* The request object.
*/
public function cleanup(Request $request);
/**
* Handles an exception.
*
* In case exception has happened we allow authentication providers react.
* Used in \Drupal\Core\Authentication\Provider\BasicAuth to set up headers to
* prompt login.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
*
* @return bool
* TRUE - exception handled. No need to run through other providers.
* FALSE - no actions have been done. Run through other providers.
*/
public function handleException(GetResponseForExceptionEvent $event);
} }
...@@ -8,20 +8,36 @@ ...@@ -8,20 +8,36 @@
namespace Drupal\Core\Authentication\Provider; namespace Drupal\Core\Authentication\Provider;
use Drupal\Core\Authentication\AuthenticationProviderInterface; use Drupal\Core\Authentication\AuthenticationProviderInterface;
use Drupal\Core\Session\SessionManagerInterface; use Drupal\Core\Session\SessionConfigurationInterface;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
/** /**
* Cookie based authentication provider. * Cookie based authentication provider.
*/ */
class Cookie implements AuthenticationProviderInterface { class Cookie implements AuthenticationProviderInterface {
/**
* The session configuration.
*
* @var \Drupal\Core\Session\SessionConfigurationInterface
*/
protected $sessionConfiguration;
/**
* Constructs a new cookie authentication provider.
*
* @param \Drupal\Core\Session\SessionConfigurationInterface $session_configuration
* The session configuration.
*/
public function __construct(SessionConfigurationInterface $session_configuration) {
$this->sessionConfiguration = $session_configuration;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function applies(Request $request) { public function applies(Request $request) {
return $request->hasSession(); return $request->hasSession() && $this->sessionConfiguration->hasSession($request);
} }
/** /**
...@@ -29,7 +45,7 @@ public function applies(Request $request) { ...@@ -29,7 +45,7 @@ public function applies(Request $request) {
*/ */
public function authenticate(Request $request) { public function authenticate(Request $request) {
if ($request->getSession()->start()) { if ($request->getSession()->start()) {
// @todo Remove global in https://www.drupal.org/node/2286971 // @todo Remove global in https://www.drupal.org/node/2228393
global $_session_user; global $_session_user;
return $_session_user; return $_session_user;
} }
...@@ -37,16 +53,4 @@ public function authenticate(Request $request) { ...@@ -37,16 +53,4 @@ public function authenticate(Request $request) {
return NULL; return NULL;
} }
/**
* {@inheritdoc}
*/
public function cleanup(Request $request) {
}
/**
* {@inheritdoc}
*/
public function handleException(GetResponseForExceptionEvent $event) {
return FALSE;
}
} }
...@@ -685,6 +685,11 @@ protected function initializeContainer($rebuild = FALSE) { ...@@ -685,6 +685,11 @@ protected function initializeContainer($rebuild = FALSE) {
$this->containerNeedsDumping = FALSE; $this->containerNeedsDumping = FALSE;
$session_manager_started = FALSE; $session_manager_started = FALSE;
if (isset($this->container)) { if (isset($this->container)) {
// Save the id of the currently logged in user.
if ($this->container->initialized('current_user')) {
$current_user_id = $this->container->get('current_user')->id();
}
// If there is a session manager, close and save the session. // If there is a session manager, close and save the session.
if ($this->container->initialized('session_manager')) { if ($this->container->initialized('session_manager')) {
$session_manager = $this->container->get('session_manager'); $session_manager = $this->container->get('session_manager');
...@@ -731,6 +736,11 @@ protected function initializeContainer($rebuild = FALSE) { ...@@ -731,6 +736,11 @@ protected function initializeContainer($rebuild = FALSE) {
} }
} }
} }
if (!empty($current_user_id)) {
$this->container->get('current_user')->setInitialAccountId($current_user_id);
}
\Drupal::setContainer($this->container); \Drupal::setContainer($this->container);
// If needs dumping flag was set, dump the container. // If needs dumping flag was set, dump the container.
......
...@@ -7,71 +7,139 @@ ...@@ -7,71 +7,139 @@
namespace Drupal\Core\EventSubscriber; namespace Drupal\Core\EventSubscriber;
use Drupal\Core\Authentication\AuthenticationProviderFilterInterface;
use Drupal\Core\Authentication\AuthenticationProviderChallengeInterface;
use Drupal\Core\Authentication\AuthenticationProviderInterface; use Drupal\Core\Authentication\AuthenticationProviderInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/** /**
* Authentication subscriber. * Authentication subscriber.
* *
* Trigger authentication and cleanup during the request. * Trigger authentication during the request.
*/ */
class AuthenticationSubscriber implements EventSubscriberInterface { class AuthenticationSubscriber implements EventSubscriberInterface {
/** /**
* Authentication provider. * Authentication provider.
* *
* @var AuthenticationProviderInterface * @var \Drupal\Core\Authentication\AuthenticationProviderInterface
*/ */
protected $authenticationProvider; protected $authenticationProvider;
/** /**
* Keep authentication manager as private variable. * Authentication provider filter.
*
* @var \Drupal\Core\Authentication\AuthenticationProviderFilterInterface|NULL
*/
protected $filter;
/**
* Authentication challenge provider.
* *
* @param AuthenticationProviderInterface $authentication_manager * @var \Drupal\Core\Authentication\AuthenticationProviderChallengeInterface|NULL
* The authentication manager.
*/ */
public function __construct(AuthenticationProviderInterface $authentication_provider) { protected $challengeProvider;
/**
* Account proxy.
*
* @var \Drupal\Core\Session\AccountProxyInterface
*/
protected $accountProxy;
/**
* Constructs an authentication subscriber.
*
* @param \Drupal\Core\Authentication\AuthenticationProviderInterface $authentication_provider
* An authentication provider.
* @param \Drupal\Core\Session\AccountProxyInterface $account_proxy
* Account proxy.
*/
public function __construct(AuthenticationProviderInterface $authentication_provider, AccountProxyInterface $account_proxy) {
$this->authenticationProvider = $authentication_provider; $this->authenticationProvider = $authentication_provider;
$this->filter = ($authentication_provider instanceof AuthenticationProviderFilterInterface) ? $authentication_provider : NULL;
$this->challengeProvider = ($authentication_provider instanceof AuthenticationProviderChallengeInterface) ? $authentication_provider : NULL;
$this->accountProxy = $account_proxy;
} }
/** /**
* Triggers authentication clean up on response. * Authenticates user on request.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
* The request event.
* *
* @see \Drupal\Core\Authentication\AuthenticationProviderInterface::cleanup() * @see \Drupal\Core\Authentication\AuthenticationProviderInterface::authenticate()
*/ */
public function onRespond(FilterResponseEvent $event) { public function onKernelRequestAuthenticate(GetResponseEvent $event) {
if ($event->getRequestType() == HttpKernelInterface::MASTER_REQUEST) { if ($event->getRequestType() === HttpKernelInterface::MASTER_REQUEST) {
$request = $event->getRequest(); $request = $event->getRequest();
$this->authenticationProvider->cleanup($request); if ($this->authenticationProvider->applies($request)) {
$account = $this->authenticationProvider->authenticate($request);
if ($account) {
$this->accountProxy->setAccount($account);
}
}
} }
} }
/** /**
* Pass exception handling to authentication manager. * Denies access if authentication provider is not allowed on this route.
* *
* @param GetResponseForExceptionEvent $event * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
* The request event.
*/ */
public function onException(GetResponseForExceptionEvent $event) { public function onKernelRequestFilterProvider(GetResponseEvent $event) {
if ($event->getRequestType() == HttpKernelInterface::MASTER_REQUEST) { if (isset($this->filter) && $event->getRequestType() === HttpKernelInterface::MASTER_REQUEST) {
$this->authenticationProvider->handleException($event); $request = $event->getRequest();
if ($this->authenticationProvider->applies($request) && !$this->filter->appliesToRoutedRequest($request, TRUE)) {
throw new AccessDeniedHttpException();
}
} }
} }
/** /**
* {@inheritdoc} * Respond with a challenge on access denied exceptions if appropriate.
* *
* The priority for request must be higher than the highest event subscriber * On a 403 (access denied), if there are no credentials on the request, some
* accessing the current user. * authentication methods (e.g. basic auth) require that a challenge is sent
* The priority for the response must be as low as possible allowing e.g the * to the client.
* Cookie provider to send all relevant session data to the user. *
* @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
* The exception event.
*/
public function onExceptionSendChallenge(GetResponseForExceptionEvent $event) {
if (isset($this->challengeProvider) && $event->getRequestType() === HttpKernelInterface::MASTER_REQUEST) {
$request = $event->getRequest();
$exception = $event->getException();
if ($exception instanceof AccessDeniedHttpException && !$this->authenticationProvider->applies($request) && (!isset($this->filter) || $this->filter->appliesToRoutedRequest($request, FALSE))) {
$challenge_exception = $this->challengeProvider->challengeException($request, $exception);
if ($challenge_exception) {
$event->setException($challenge_exception);
}
}
}
}
/**
* {@inheritdoc}
*/ */
public static function getSubscribedEvents() { public static function getSubscribedEvents() {
$events[KernelEvents::RESPONSE][] = ['onRespond', 0]; // The priority for authentication must be higher than the highest event
$events[KernelEvents::EXCEPTION][] = ['onException', 75]; // subscriber accessing the current user. Especially it must be higher than
// LanguageRequestSubscriber as LanguageManager accesses the current user if
// the language module is enabled.
$events[KernelEvents::REQUEST][] = ['onKernelRequestAuthenticate', 300];
// Access check must be performed after routing.
$events[KernelEvents::REQUEST][] = ['onKernelRequestFilterProvider', 31];
$events[KernelEvents::EXCEPTION][] = ['onExceptionSendChallenge', 75];
return $events; return $events;
} }
} }
...@@ -7,7 +7,6 @@ ...@@ -7,7 +7,6 @@
namespace Drupal\Core\EventSubscriber; namespace Drupal\Core\EventSubscriber;
use Drupal\Component\Utility\String;
use Drupal\Core\Routing\RouteBuildEvent; use Drupal\Core\Routing\RouteBuildEvent;
use Drupal\Core\Routing\RouteSubscriberBase; use Drupal\Core\Routing\RouteSubscriberBase;
use Symfony\Cmf\Component\Routing\RouteObjectInterface; use Symfony\Cmf\Component\Routing\RouteObjectInterface;
...@@ -25,7 +24,6 @@ protected function alterRoutes(RouteCollection $collection) { ...@@ -25,7 +24,6 @@ protected function alterRoutes(RouteCollection $collection) {
$special_variables = array( $special_variables = array(
'system_path', 'system_path',
'_legacy', '_legacy',
'_authentication_provider',
'_raw_variables', '_raw_variables',
RouteObjectInterface::ROUTE_OBJECT, RouteObjectInterface::ROUTE_OBJECT,
RouteObjectInterface::ROUTE_NAME, RouteObjectInterface::ROUTE_NAME,
......
...@@ -88,10 +88,6 @@ public function getContext() { ...@@ -88,10 +88,6 @@ public function getContext() {
public function matchRequest(Request $request) { public function matchRequest(Request $request) {
$parameters = $this->chainRouter->matchRequest($request); $parameters = $this->chainRouter->matchRequest($request);
$request->attributes->add($parameters);