summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlex Pott2013-06-29 16:26:55 (GMT)
committer Alex Pott2013-06-29 16:26:55 (GMT)
commitaa9c2ccc856a248711c09caad84fb7638f9f217a (patch)
tree36450b13c8b286b00fc74602c705b4a0cba3d746
parent30a45aded3cc64b322d2b8934a351da0ef0350f0 (diff)
Issue #1890878 by corvus_ch, ygerasimov, berenddeboer, Crell, fubhy the cat, effulgentsia: Add modular authentication system, including Http Basic; deprecate global .
-rw-r--r--core/authorize.php6
-rw-r--r--core/core.services.yml21
-rw-r--r--core/includes/bootstrap.inc18
-rw-r--r--core/includes/install.core.inc2
-rw-r--r--core/lib/Drupal/Core/Authentication/AuthenticationManager.php202
-rw-r--r--core/lib/Drupal/Core/Authentication/AuthenticationManagerInterface.php22
-rw-r--r--core/lib/Drupal/Core/Authentication/AuthenticationProviderInterface.php67
-rw-r--r--core/lib/Drupal/Core/Authentication/Provider/Cookie.php56
-rw-r--r--core/lib/Drupal/Core/Authentication/Provider/HttpBasic.php86
-rw-r--r--core/lib/Drupal/Core/Controller/ExceptionController.php8
-rw-r--r--core/lib/Drupal/Core/CoreServiceProvider.php3
-rw-r--r--core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterAuthenticationPass.php44
-rw-r--r--core/lib/Drupal/Core/EventSubscriber/AuthenticationSubscriber.php93
-rw-r--r--core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php2
-rw-r--r--core/lib/Drupal/Core/EventSubscriber/LegacyAccessSubscriber.php17
-rw-r--r--core/lib/Drupal/Core/Routing/Enhancer/AuthenticationEnhancer.php59
-rw-r--r--core/modules/simpletest/lib/Drupal/simpletest/TestBase.php1
-rw-r--r--core/modules/system/lib/Drupal/system/Tests/Authentication/HttpBasicTest.php96
-rw-r--r--core/modules/system/lib/Drupal/system/Tests/Upgrade/UpgradePathTestBase.php4
-rw-r--r--core/modules/system/tests/modules/router_test/lib/Drupal/router_test/TestContent.php11
-rw-r--r--core/modules/system/tests/modules/router_test/router_test.routing.yml9
-rw-r--r--core/modules/system/tests/modules/session_test/lib/Drupal/session_test/EventSubscriber/SessionTestSubscriber.php2
-rw-r--r--core/modules/user/lib/Drupal/user/Access/RoleAccessCheck.php4
-rw-r--r--core/modules/user/user.module4
-rw-r--r--core/tests/Drupal/Tests/Core/Route/RoleAccessCheckTest.php7
-rw-r--r--core/update.php4
26 files changed, 812 insertions, 36 deletions
diff --git a/core/authorize.php b/core/authorize.php
index f347ba5..fe39394 100644
--- a/core/authorize.php
+++ b/core/authorize.php
@@ -48,10 +48,12 @@ function authorize_access_denied_page() {
* The killswitch in settings.php overrides all else, otherwise, the user must
* have access to the 'administer software updates' permission.
*
- * @return
+ * @return bool
* TRUE if the current user can run authorize.php, and FALSE if not.
*/
function authorize_access_allowed() {
+ require_once DRUPAL_ROOT . '/' . settings()->get('session_inc', 'core/includes/session.inc');
+ drupal_session_initialize();
return settings()->get('allow_authorize_operations', TRUE) && user_access('administer software updates');
}
@@ -65,7 +67,7 @@ require_once __DIR__ . '/includes/ajax.inc';
// We prepare only a minimal bootstrap. This includes the database and
// variables, however, so we have access to the class autoloader.
-drupal_bootstrap(DRUPAL_BOOTSTRAP_SESSION);
+drupal_bootstrap(DRUPAL_BOOTSTRAP_VARIABLES);
// This must go after drupal_bootstrap(), which unsets globals!
global $conf;
diff --git a/core/core.services.yml b/core/core.services.yml
index 0659827..d915e10 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -296,6 +296,11 @@ services:
tags:
- { name: event_subscriber }
arguments: ['@settings']
+ route_enhancer.authentication:
+ class: Drupal\Core\Routing\Enhancer\AuthenticationEnhancer
+ tags:
+ - { name: route_enhancer, priority: 1000 }
+ arguments: ['@authentication']
route_enhancer.content_controller:
class: Drupal\Core\Routing\Enhancer\ContentControllerEnhancer
arguments: ['@content_negotiation']
@@ -538,3 +543,19 @@ services:
class: Zend\Feed\Writer\Extension\Threading\Renderer\Entry
feed.writer.wellformedwebrendererentry:
class: Zend\Feed\Writer\Extension\WellFormedWeb\Renderer\Entry
+ authentication:
+ class: Drupal\Core\Authentication\AuthenticationManager
+ authentication.cookie:
+ class: Drupal\Core\Authentication\Provider\Cookie
+ tags:
+ - { name: authentication_provider, priority: 0 }
+ authentication.http_basic:
+ class: Drupal\Core\Authentication\Provider\HttpBasic
+ arguments: ['@config.factory']
+ tags:
+ - { name: authentication_provider, priority: 100 }
+ authentication_subscriber:
+ class: Drupal\Core\EventSubscriber\AuthenticationSubscriber
+ tags:
+ - { name: event_subscriber }
+ arguments: ['@authentication']
diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index 14ba5f1..07ea4bd 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -158,19 +158,14 @@ const DRUPAL_BOOTSTRAP_DATABASE = 3;
const DRUPAL_BOOTSTRAP_VARIABLES = 4;
/**
- * Sixth bootstrap phase: initialize session handling.
- */
-const DRUPAL_BOOTSTRAP_SESSION = 5;
-
-/**
* Eighth bootstrap phase: load code for subsystems and modules.
*/
-const DRUPAL_BOOTSTRAP_CODE = 6;
+const DRUPAL_BOOTSTRAP_CODE = 5;
/**
* Final bootstrap phase: initialize language, path, theme, and modules.
*/
-const DRUPAL_BOOTSTRAP_FULL = 7;
+const DRUPAL_BOOTSTRAP_FULL = 6;
/**
* Role ID for anonymous users; should match what's in the "role" table.
@@ -1770,7 +1765,6 @@ function drupal_anonymous_user() {
* - DRUPAL_BOOTSTRAP_PAGE_CACHE: Tries to serve a cached page.
* - DRUPAL_BOOTSTRAP_DATABASE: Initializes the database layer.
* - DRUPAL_BOOTSTRAP_VARIABLES: Initializes the variable system.
- * - DRUPAL_BOOTSTRAP_SESSION: Initializes session handling.
* - DRUPAL_BOOTSTRAP_CODE: Loads code for subsystems and modules.
* - DRUPAL_BOOTSTRAP_FULL: Fully loads Drupal. Validates and fixes input
* data.
@@ -1789,7 +1783,6 @@ function drupal_bootstrap($phase = NULL, $new_phase = TRUE) {
DRUPAL_BOOTSTRAP_PAGE_CACHE,
DRUPAL_BOOTSTRAP_DATABASE,
DRUPAL_BOOTSTRAP_VARIABLES,
- DRUPAL_BOOTSTRAP_SESSION,
DRUPAL_BOOTSTRAP_CODE,
DRUPAL_BOOTSTRAP_FULL,
);
@@ -1839,11 +1832,6 @@ function drupal_bootstrap($phase = NULL, $new_phase = TRUE) {
_drupal_bootstrap_variables();
break;
- case DRUPAL_BOOTSTRAP_SESSION:
- require_once DRUPAL_ROOT . '/' . settings()->get('session_inc', 'core/includes/session.inc');
- drupal_session_initialize();
- break;
-
case DRUPAL_BOOTSTRAP_CODE:
require_once __DIR__ . '/common.inc';
_drupal_bootstrap_code();
@@ -1910,7 +1898,7 @@ function drupal_get_user_timezone() {
global $user;
$config = config('system.timezone');
- if ($config->get('user.configurable') && $user->uid && $user->timezone) {
+ if ($user && $config->get('user.configurable') && $user->uid && $user->timezone) {
return $user->timezone;
}
else {
diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index 03904cd..c16a3cc 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -1752,6 +1752,8 @@ function install_load_profile(&$install_state) {
*/
function install_bootstrap_full() {
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
+ require_once DRUPAL_ROOT . '/' . settings()->get('session_inc', 'core/includes/session.inc');
+ drupal_session_initialize();
}
/**
diff --git a/core/lib/Drupal/Core/Authentication/AuthenticationManager.php b/core/lib/Drupal/Core/Authentication/AuthenticationManager.php
new file mode 100644
index 0000000..8ea0ff4
--- /dev/null
+++ b/core/lib/Drupal/Core/Authentication/AuthenticationManager.php
@@ -0,0 +1,202 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Authentication\AuthenticationManager.
+ */
+
+namespace Drupal\Core\Authentication;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Cmf\Component\Routing\RouteObjectInterface;
+use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
+
+/**
+ * Manager for authentication.
+ *
+ * On each request, let all authentication providers try to authenticate the
+ * user. The providers are iterated according to their priority and the first
+ * provider detecting credentials for its method will become the triggered
+ * provider. No further provider will get triggered.
+ *
+ * If no provider was triggered the lowest-priority provider is assumed to
+ * be responsible. If no provider set an active user then the user is set to
+ * anonymous.
+ */
+class AuthenticationManager implements AuthenticationProviderInterface, AuthenticationManagerInterface {
+
+ /**
+ * Array of all registered authentication providers, keyed by ID.
+ *
+ * @var array
+ */
+ protected $providers;
+
+ /**
+ * Array of all providers and their priority.
+ *
+ * @var array
+ */
+ protected $providerOrders;
+
+ /**
+ * Sorted list of registered providers.
+ *
+ * @var array
+ */
+ protected $sortedProviders;
+
+ /**
+ * Id of the provider that authenticated the user.
+ *
+ * @var string
+ */
+ protected $triggeredProviderId = '';
+
+ /**
+ * Adds a provider to the array of registered providers.
+ *
+ * @param string $provider_id
+ * Identifier of the provider.
+ * @param \Drupal\Core\Authentication\AuthenticationProviderInterface $provider
+ * The provider object.
+ * @param int $priority
+ * The providers priority.
+ */
+ public function addProvider($provider_id, AuthenticationProviderInterface $provider, $priority = 0) {
+ $provider_id = substr($provider_id, strlen('authentication.'));
+
+ $this->providers[$provider_id] = $provider;
+ $this->providerOrders[$priority][$provider_id] = $provider;
+ // Force the builders to be re-sorted.
+ $this->sortedProviders = NULL;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function applies(Request $request) {
+ return TRUE;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function authenticate(Request $request) {
+ global $user;
+
+ $account = NULL;
+
+ // Iterate the availlable providers.
+ foreach ($this->getSortedProviders() as $provider_id => $provider) {
+ if ($provider->applies($request)) {
+ // Try to authenticate with this provider, skipping all others.
+ $account = $provider->authenticate($request);
+ $this->triggeredProviderId = $provider_id;
+ break;
+ }
+ }
+
+ // No provider returned a valid account, so set the user to anonymous.
+ if (!$account) {
+ $account = drupal_anonymous_user();
+ }
+
+ // No provider was fired, so assume the one with the least priority
+ // should have.
+ if (!$this->triggeredProviderId) {
+ $this->triggeredProviderId = $this->defaultProviderId();
+ }
+
+ // Save the authenticated account and the provider that supplied it
+ // for later access.
+ $request->attributes->set('account', $account);
+ $request->attributes->set('_authentication_provider', $this->triggeredProviderId);
+
+ // The global $user object is included for backward compatibility only and
+ // should be considered deprecated.
+ // @todo Remove this line once global $user is no longer used.
+ $user = $account;
+
+ return $account;
+ }
+
+ /**
+ * Returns the default provider ID.
+ *
+ * The default provider is the one with the lowest registered priority.
+ *
+ * @return string
+ * The ID of the default provider.
+ */
+ public function defaultProviderId() {
+ $providers = $this->getSortedProviders();
+ $provider_ids = array_keys($providers);
+ return end($provider_ids);
+ }
+
+ /**
+ * Returns the sorted array of authentication providers.
+ *
+ * @return array
+ * An array of authentication provider objects.
+ */
+ protected function getSortedProviders() {
+ if (!isset($this->sortedProviders)) {
+ // Sort the builders according to priority.
+ krsort($this->providerOrders);
+ // Merge nested providers from $this->providers into $this->sortedProviders.
+ $this->sortedProviders = array();
+ foreach ($this->providerOrders as $providers) {
+ $this->sortedProviders = array_merge($this->sortedProviders, $providers);
+ }
+ }
+ return $this->sortedProviders;
+ }
+
+ /**
+ * Cleans up the authentication.
+ *
+ * Allow the triggered provider to clean up before the response is sent, e.g.
+ * trigger a session commit.
+ *
+ * @param \Symfony\Component\HttpFoundation\Request $request
+ * The request object.
+ *
+ * @see \Drupal\Core\Authentication\Provider\Cookie::cleanup()
+ */
+ public function cleanup(Request $request) {
+ if (empty($this->providers[$this->triggeredProviderId])) {
+ return;
+ }
+ $this->providers[$this->triggeredProviderId]->cleanup($request);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function handleException(GetResponseForExceptionEvent $event) {
+ $request = $event->getRequest();
+
+ // Legacy routes won't have a Route object; they have drupal_menu_item
+ // instead. Assume those were authenticated by cookie, because the legacy
+ // router didn't support anything else.
+ // @todo Remove this check once the old router is fully removed.
+ if ($request->attributes->has('drupal_menu_item')) {
+ $active_providers = array('cookie');
+ }
+ else {
+ $route = $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT);
+ $active_providers = ($route && $route->getOption('_auth')) ? $route->getOption('_auth') : array($this->defaultProviderId());
+ }
+
+ // Get the sorted list of active providers for the given route.
+ $providers = array_intersect($active_providers, array_keys($this->providers));
+
+ foreach ($providers as $provider_id) {
+ if ($this->providers[$provider_id]->handleException($event) == TRUE) {
+ break;
+ }
+ }
+ }
+}
diff --git a/core/lib/Drupal/Core/Authentication/AuthenticationManagerInterface.php b/core/lib/Drupal/Core/Authentication/AuthenticationManagerInterface.php
new file mode 100644
index 0000000..b547edd
--- /dev/null
+++ b/core/lib/Drupal/Core/Authentication/AuthenticationManagerInterface.php
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\Authentication\AuthenticationManagerInterface.
+ */
+
+namespace Drupal\Core\Authentication;
+
+/**
+ * Defines an interface for authentication managers.
+ */
+interface AuthenticationManagerInterface {
+
+ /**
+ * Returns the service id of the default authentication provider.
+ *
+ * @return string
+ * The service id of the default authentication provider.
+ */
+ public function defaultProviderId();
+}
diff --git a/core/lib/Drupal/Core/Authentication/AuthenticationProviderInterface.php b/core/lib/Drupal/Core/Authentication/AuthenticationProviderInterface.php
new file mode 100644
index 0000000..b683ba1
--- /dev/null
+++ b/core/lib/Drupal/Core/Authentication/AuthenticationProviderInterface.php
@@ -0,0 +1,67 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Authentication\AuthenticationProviderInterface.
+ */
+
+namespace Drupal\Core\Authentication;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
+
+/**
+ * Interface for authentication providers.
+ */
+interface AuthenticationProviderInterface {
+
+ /**
+ * Declares whether the provider applies to a specific request or not.
+ *
+ * @param \Symfony\Component\HttpFoundation\Request $request
+ * The request object.
+ *
+ * @return bool
+ * TRUE if the provider applies to the passed request, FALSE otherwise.
+ */
+ public function applies(Request $request);
+
+ /**
+ * Authenticates the user.
+ *
+ * @param \Symfony\Component\HttpFoundation\Request $request
+ * The request object.
+ *
+ * @return \Drupal\Core\Session\AccountInterface|null
+ * AccountInterface - in case of a successful authentication.
+ * NULL - in case where authentication failed.
+ */
+ 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\HttpBasic 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);
+}
diff --git a/core/lib/Drupal/Core/Authentication/Provider/Cookie.php b/core/lib/Drupal/Core/Authentication/Provider/Cookie.php
new file mode 100644
index 0000000..ae108dc
--- /dev/null
+++ b/core/lib/Drupal/Core/Authentication/Provider/Cookie.php
@@ -0,0 +1,56 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Authentication\Provider\Cookie.
+ */
+
+namespace Drupal\Core\Authentication\Provider;
+
+use Drupal\Core\Authentication\AuthenticationProviderInterface;
+use Symfony\Component\HttpFoundation\RedirectResponse;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
+use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
+
+/**
+ * Cookie based authentication provider.
+ */
+class Cookie implements AuthenticationProviderInterface {
+
+ /**
+ * {@inheritdoc}
+ */
+ public function applies(Request $request) {
+ return TRUE;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function authenticate(Request $request) {
+ // Global $user is deprecated, but the session system is still based on it.
+ global $user;
+ require_once DRUPAL_ROOT . '/' . settings()->get('session_inc', 'core/includes/session.inc');
+ drupal_session_initialize();
+ if (drupal_session_started()) {
+ return $user;
+ }
+ return NULL;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function cleanup(Request $request) {
+ drupal_session_commit();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function handleException(GetResponseForExceptionEvent $event) {
+ return FALSE;
+ }
+}
diff --git a/core/lib/Drupal/Core/Authentication/Provider/HttpBasic.php b/core/lib/Drupal/Core/Authentication/Provider/HttpBasic.php
new file mode 100644
index 0000000..5a0fefe
--- /dev/null
+++ b/core/lib/Drupal/Core/Authentication/Provider/HttpBasic.php
@@ -0,0 +1,86 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Authentication\Provider\HttpBasic.
+ */
+
+namespace Drupal\Core\Authentication\Provider;
+
+use \Drupal\Component\Utility\String;
+use Drupal\Core\Authentication\AuthenticationProviderInterface;
+use Drupal\Core\Config\Config;
+use Drupal\Core\Config\ConfigFactory;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
+use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
+use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
+
+/**
+ * HTTP Basic authentication provider.
+ */
+class HttpBasic implements AuthenticationProviderInterface {
+
+ /**
+ * The config factory.
+ *
+ * @var \Drupal\Core\Config\ConfigFactory
+ */
+ protected $configFactory;
+
+ /**
+ * Constructs a HTTP basic authentication provider object.
+ *
+ * @param \Drupal\Core\Config\ConfigFactory $config_factory
+ * The config factory.
+ */
+ public function __construct(ConfigFactory $config_factory) {
+ $this->configFactory = $config_factory;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function applies(Request $request) {
+ $username = $request->headers->get('PHP_AUTH_USER');
+ $password = $request->headers->get('PHP_AUTH_PW');
+ return isset($username) && isset($password);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function authenticate(Request $request) {
+ $username = $request->headers->get('PHP_AUTH_USER');
+ $password = $request->headers->get('PHP_AUTH_PW');
+ $uid = user_authenticate($username, $password);
+ if ($uid) {
+ return user_load($uid);
+ }
+ return NULL;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function cleanup(Request $request) {}
+
+ /**
+ * {@inheritdoc}
+ */
+ public function handleException(GetResponseForExceptionEvent $event) {
+ $exception = $event->getException();
+ if (user_is_anonymous() && $exception instanceof AccessDeniedHttpException) {
+ if (!$this->applies($event->getRequest())) {
+ $site_name = $this->configFactory->get('system.site')->get('name');
+ global $base_url;
+ $challenge = String::format('Basic realm="@realm"', array(
+ '@realm' => !empty($site_name) ? $site_name : $base_url,
+ ));
+ $event->setException(new UnauthorizedHttpException($challenge, 'No authentication credentials provided.', $exception));
+ }
+ return TRUE;
+ }
+ return FALSE;
+ }
+}
diff --git a/core/lib/Drupal/Core/Controller/ExceptionController.php b/core/lib/Drupal/Core/Controller/ExceptionController.php
index c9a661b..9e7d77e 100644
--- a/core/lib/Drupal/Core/Controller/ExceptionController.php
+++ b/core/lib/Drupal/Core/Controller/ExceptionController.php
@@ -42,12 +42,12 @@ class ExceptionController extends ContainerAware {
/**
* Handles an exception on a request.
*
- * @param Symfony\Component\HttpKernel\Exception\FlattenException $exception
+ * @param \Symfony\Component\HttpKernel\Exception\FlattenException $exception
* The flattened exception.
- * @param Symfony\Component\HttpFoundation\Request $request
+ * @param \Symfony\Component\HttpFoundation\Request $request
* The request that generated the exception.
*
- * @return Symfony\Component\HttpFoundation\Response
+ * @return \Symfony\Component\HttpFoundation\Response
* A response object to be sent to the server.
*/
public function execute(FlattenException $exception, Request $request) {
@@ -57,7 +57,7 @@ class ExceptionController extends ContainerAware {
return $this->$method($exception, $request);
}
- return new Response('A fatal error occurred: ' . $exception->getMessage(), $exception->getStatusCode());
+ return new Response('A fatal error occurred: ' . $exception->getMessage(), $exception->getStatusCode(), $exception->getHeaders());
}
/**
diff --git a/core/lib/Drupal/Core/CoreServiceProvider.php b/core/lib/Drupal/Core/CoreServiceProvider.php
index 310343e..15924c3 100644
--- a/core/lib/Drupal/Core/CoreServiceProvider.php
+++ b/core/lib/Drupal/Core/CoreServiceProvider.php
@@ -21,6 +21,7 @@ use Drupal\Core\DependencyInjection\Compiler\RegisterParamConvertersPass;
use Drupal\Core\DependencyInjection\Compiler\RegisterServicesForDestructionPass;
use Drupal\Core\DependencyInjection\Compiler\RegisterStringTranslatorsPass;
use Drupal\Core\DependencyInjection\Compiler\RegisterBreadcrumbBuilderPass;
+use Drupal\Core\DependencyInjection\Compiler\RegisterAuthenticationPass;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Definition;
@@ -71,6 +72,8 @@ class CoreServiceProvider implements ServiceProviderInterface {
// Add the compiler pass that lets service providers modify existing
// service definitions.
$container->addCompilerPass(new ModifyServiceDefinitionsPass());
+ // Add the compiler pass that will process tagged authentication services.
+ $container->addCompilerPass(new RegisterAuthenticationPass());
}
/**
diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterAuthenticationPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterAuthenticationPass.php
new file mode 100644
index 0000000..dd975a9
--- /dev/null
+++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterAuthenticationPass.php
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\DependencyInjection\Compiler\RegisterAuthenticationPass.
+ */
+
+namespace Drupal\Core\DependencyInjection\Compiler;
+
+use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+
+/**
+ * Adds services tagged 'authentication_provider'.
+ */
+class RegisterAuthenticationPass implements CompilerPassInterface {
+
+ /**
+ * Adds authentication providers to the authentication manager.
+ *
+ * Check for services tagged with 'authentication_provider' and add them to
+ * the authentication manager.
+ *
+ * @see \Drupal\Core\Authentication\AuthenticationManager
+ * @see \Drupal\Core\Authentication\AuthenticationProviderInterface
+ */
+ public function process(ContainerBuilder $container) {
+ if (!$container->hasDefinition('authentication')) {
+ return;
+ }
+ // Get the authentication manager.
+ $matcher = $container->getDefinition('authentication');
+ // Iterate all autentication providers and add them to the manager.
+ foreach ($container->findTaggedServiceIds('authentication_provider') as $id => $attributes) {
+ $priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
+ $matcher->addMethodCall('addProvider', array(
+ $id,
+ new Reference($id),
+ $priority,
+ ));
+ }
+ }
+}
diff --git a/core/lib/Drupal/Core/EventSubscriber/AuthenticationSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/AuthenticationSubscriber.php
new file mode 100644
index 0000000..5bdf61c
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/AuthenticationSubscriber.php
@@ -0,0 +1,93 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\EventSubscriber\AuthenticationSubscriber.
+ */
+
+namespace Drupal\Core\EventSubscriber;
+
+use Drupal\Core\Authentication\AuthenticationProviderInterface;
+use Symfony\Component\HttpKernel\HttpKernelInterface;
+use Symfony\Component\HttpKernel\KernelEvents;
+use Symfony\Component\HttpKernel\Event\GetResponseEvent;
+use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
+use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Authentication subscriber.
+ *
+ * Trigger authentication and cleanup during the request.
+ */
+class AuthenticationSubscriber implements EventSubscriberInterface {
+
+ /**
+ * Authentication provider.
+ *
+ * @var AuthenticationProviderInterface
+ */
+ protected $authenticationProvider;
+
+ /**
+ * Keep authentication manager as private variable.
+ *
+ * @param AuthenticationProviderInterface $authentication_manager
+ * The authentication manager.
+ */
+ public function __construct(AuthenticationProviderInterface $authentication_provider) {
+ $this->authenticationProvider = $authentication_provider;
+ }
+
+ /**
+ * Authenticates user on request.
+ *
+ * @see \Drupal\Core\Authentication\AuthenticationProviderInterface::authenticate()
+ */
+ public function onKernelRequestAuthenticate(GetResponseEvent $event) {
+ if ($event->getRequestType() == HttpKernelInterface::MASTER_REQUEST) {
+ $request = $event->getRequest();
+ $this->authenticationProvider->authenticate($request);
+ }
+ }
+
+ /**
+ * Triggers authentication clean up on response.
+ *
+ * @see \Drupal\Core\Authentication\AuthenticationProviderInterface::cleanup()
+ */
+ public function onRespond(FilterResponseEvent $event) {
+ if ($event->getRequestType() == HttpKernelInterface::MASTER_REQUEST) {
+ $request = $event->getRequest();
+ $this->authenticationProvider->cleanup($request);
+ }
+ }
+
+ /**
+ * Pass exception handling to authentication manager.
+ *
+ * @param GetResponseForExceptionEvent $event
+ */
+ public function onException(GetResponseForExceptionEvent $event) {
+ if ($event->getRequestType() == HttpKernelInterface::MASTER_REQUEST) {
+ $this->authenticationProvider->handleException($event);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * The priority for request must be higher than the highest event subscriber
+ * accessing the global $user.
+ * The priority for the response must be as low as possible allowing e.g the
+ * Cookie provider to send all relevant session data to the user.
+ */
+ public static function getSubscribedEvents() {
+ // Priority must be higher than LanguageRequestSubscriber as LanguageManager
+ // access global $user in case language module enabled.
+ $events[KernelEvents::REQUEST][] = array('onKernelRequestAuthenticate', 300);
+ $events[KernelEvents::RESPONSE][] = array('onRespond', 0);
+ $events[KernelEvents::EXCEPTION][] = array('onException', 0);
+ return $events;
+ }
+}
diff --git a/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
index 8b385ca..0c7307e 100644
--- a/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
@@ -91,8 +91,6 @@ class FinishResponseSubscriber implements EventSubscriberInterface {
// @todo Revisit whether or not this is still appropriate now that the
// Response object does its own cache control processing and we intend to
// use partial page caching more extensively.
- // Commit the user session, if needed.
- drupal_session_commit();
// Attach globally-declared headers to the response object so that Symfony
// can send them for us correctly.
diff --git a/core/lib/Drupal/Core/EventSubscriber/LegacyAccessSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/LegacyAccessSubscriber.php
index 566e82c..1716526 100644
--- a/core/lib/Drupal/Core/EventSubscriber/LegacyAccessSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/LegacyAccessSubscriber.php
@@ -23,12 +23,25 @@ class LegacyAccessSubscriber implements EventSubscriberInterface {
* @todo This is a total hack to keep our current access system working. It
* should be replaced with something robust and injected at some point.
*
- * @param Symfony\Component\HttpKernel\Event\GetResponseEvent $event
+ * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
* The Event to process.
+ *
+ * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
*/
public function onKernelRequestAccessCheck(GetResponseEvent $event) {
- $router_item = $event->getRequest()->attributes->get('drupal_menu_item');
+ $request_attributes = $event->getRequest()->attributes;
+
+ $router_item = $request_attributes->get('drupal_menu_item');
+
+ // For legacy routes we do not allow any user not authenticated by cookie
+ // provider.
+ $provider = $request_attributes->get('_authentication_provider');
+ if ($request_attributes->get('_legacy') && $provider && $provider != 'cookie') {
+ $GLOBALS['user'] = drupal_anonymous_user();
+ $request_attributes->set('account', $GLOBALS['user']);
+ throw new AccessDeniedHttpException();
+ }
if (isset($router_item['access']) && !$router_item['access']) {
throw new AccessDeniedHttpException();
diff --git a/core/lib/Drupal/Core/Routing/Enhancer/AuthenticationEnhancer.php b/core/lib/Drupal/Core/Routing/Enhancer/AuthenticationEnhancer.php
new file mode 100644
index 0000000..16b1482
--- /dev/null
+++ b/core/lib/Drupal/Core/Routing/Enhancer/AuthenticationEnhancer.php
@@ -0,0 +1,59 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Routing\Enhancer\AuthenticationEnhancer.
+ */
+
+namespace Drupal\Core\Routing\Enhancer;
+
+use Drupal\Core\Authentication\AuthenticationManagerInterface;
+use Symfony\Cmf\Component\Routing\Enhancer\RouteEnhancerInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Cmf\Component\Routing\RouteObjectInterface;
+
+/**
+ * Authentication cleanup for incoming routes.
+ *
+ * The authentication system happens before routing, so all authentication
+ * providers will attempt to authorize a user. However, not all routes allow
+ * all authentication mechanisms. Instead, we check if the used provider is
+ * valid for the matched route and if not, force the user to anonymous.
+ */
+class AuthenticationEnhancer implements RouteEnhancerInterface {
+
+ /**
+ * The authentication manager.
+ *
+ * @var \Drupal\Core\Authentication\AuthenticationManager
+ */
+ protected $manager;
+
+ /**
+ * Constructs a AuthenticationEnhancer object.
+ *
+ * @param AuthenticationManagerInterface $manager
+ * The authentication manager.
+ */
+ function __construct(AuthenticationManagerInterface $manager) {
+ $this->manager = $manager;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function enhance(array $defaults, Request $request) {
+ $auth_provider_triggered = $request->attributes->get('_authentication_provider');
+ if (!empty($auth_provider_triggered)) {
+ $route = isset($defaults[RouteObjectInterface::ROUTE_OBJECT]) ? $defaults[RouteObjectInterface::ROUTE_OBJECT] : NULL;
+
+ $auth_providers = ($route && $route->getOption('_auth')) ? $route->getOption('_auth') : array($this->manager->defaultProviderId());
+ // If the request was authenticated with a non-permitted provider,
+ // force the user back to anonymous.
+ if (!in_array($auth_provider_triggered, $auth_providers)) {
+ $request->attributes->set('account', drupal_anonymous_user());
+ }
+ }
+ return $defaults;
+ }
+}
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
index 818b7b0..58a39d9 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
@@ -890,6 +890,7 @@ abstract class TestBase {
$this->originalUser = isset($user) ? clone $user : NULL;
// Ensure that the current session is not changed by the new environment.
+ require_once DRUPAL_ROOT . '/' . settings()->get('session_inc', 'core/includes/session.inc');
drupal_save_session(FALSE);
// Run all tests as a anonymous user by default, web tests will replace that
// during the test set up.
diff --git a/core/modules/system/lib/Drupal/system/Tests/Authentication/HttpBasicTest.php b/core/modules/system/lib/Drupal/system/Tests/Authentication/HttpBasicTest.php
new file mode 100644
index 0000000..2d1c907
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/Authentication/HttpBasicTest.php
@@ -0,0 +1,96 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\Tests\Authentication\HttpBasicTest.
+ */
+
+namespace Drupal\system\Tests\Authentication;
+
+use Drupal\Core\Authentication\Provider\HttpBasic;
+use Drupal\simpletest\WebTestBase;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Test for http basic authentication.
+ */
+class HttpBasicTest extends WebTestBase {
+
+ /**
+ * Modules enabled for all tests.
+ *
+ * @var array
+ */
+ public static $modules = array('router_test');
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'HttpBasic authentication',
+ 'description' => 'Tests for HttpBasic authentication provider.',
+ 'group' => 'Authentication',
+ );
+ }
+
+ /**
+ * Test http basic authentication.
+ */
+ public function testHttpBasic() {
+ $account = $this->drupalCreateUser();
+
+ $this->basicAuthGet('router_test/test11', $account->name, $account->pass_raw);
+ $this->assertText($account->name, 'Account name is displayed.');
+ $this->assertResponse('200', 'HTTP response is OK');
+ $this->curlClose();
+
+ $this->basicAuthGet('router_test/test11', $account->name, $this->randomName());
+ $this->assertNoText($account->name, 'Bad basic auth credentials do not authenticate the user.');
+ $this->assertResponse('403', 'Access is not granted.');
+ $this->curlClose();
+
+ $this->drupalGet('router_test/test11');
+ $this->assertResponse('401', 'Not authenticated on the route that allows only http_basic. Prompt to authenticate received.');
+
+ $this->drupalGet('admin');
+ $this->assertResponse('403', 'No authentication prompt for routes not explicitly defining authentication providers.');
+
+ $account = $this->drupalCreateUser(array('access administration pages'));
+
+ $this->basicAuthGet('admin', $account->name, $account->pass_raw);
+ $this->assertNoLink('Log out', 0, 'User is not logged in');
+ $this->assertResponse('403', 'No basic authentication for routes not explicitly defining authentication providers.');
+ $this->curlClose();
+ }
+
+ /**
+ * Does HTTP basic auth request.
+ *
+ * We do not use \Drupal\simpletest\WebTestBase::drupalGet because we need to
+ * set curl settings for basic authentication.
+ *
+ * @param string $path
+ * The request path.
+ * @param string $username
+ * The user name to authenticate with.
+ * @param string $password
+ * The password.
+ *
+ * @return string
+ * Curl output.
+ */
+ protected function basicAuthGet($path, $username, $password) {
+ $out = $this->curlExec(
+ array(
+ CURLOPT_HTTPGET => TRUE,
+ CURLOPT_URL => url($path, array('absolute' => TRUE)),
+ CURLOPT_NOBODY => FALSE,
+ CURLOPT_HTTPAUTH => CURLAUTH_BASIC,
+ CURLOPT_USERPWD => $username . ':' . $password,
+ )
+ );
+
+ $this->verbose('GET request to: ' . $path .
+ '<hr />' . $out);
+
+ return $out;
+ }
+}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Upgrade/UpgradePathTestBase.php b/core/modules/system/lib/Drupal/system/Tests/Upgrade/UpgradePathTestBase.php
index 8a1cb68..2ebeb66 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Upgrade/UpgradePathTestBase.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Upgrade/UpgradePathTestBase.php
@@ -93,6 +93,10 @@ abstract class UpgradePathTestBase extends WebTestBase {
// Load the Update API.
require_once DRUPAL_ROOT . '/core/includes/update.inc';
+ // Load Session API.
+ require_once DRUPAL_ROOT . '/core/includes/session.inc';
+ drupal_session_initialize();
+
// Reset flags.
$this->upgradedSite = FALSE;
$this->upgradeErrors = array();
diff --git a/core/modules/system/tests/modules/router_test/lib/Drupal/router_test/TestContent.php b/core/modules/system/tests/modules/router_test/lib/Drupal/router_test/TestContent.php
index 56d567b..37c68b0 100644
--- a/core/modules/system/tests/modules/router_test/lib/Drupal/router_test/TestContent.php
+++ b/core/modules/system/tests/modules/router_test/lib/Drupal/router_test/TestContent.php
@@ -19,4 +19,15 @@ class TestContent {
return 'abcde';
}
+ /**
+ * Provides example content for route specific authentication.
+ *
+ * @returns string
+ * The user name of the current logged in user.
+ */
+ public function test11() {
+ $account = \Drupal::request()->attributes->get('account');
+ return isset($account->name) ? $account->name : '';
+ }
+
}
diff --git a/core/modules/system/tests/modules/router_test/router_test.routing.yml b/core/modules/system/tests/modules/router_test/router_test.routing.yml
index 2a989df..2eda2c8 100644
--- a/core/modules/system/tests/modules/router_test/router_test.routing.yml
+++ b/core/modules/system/tests/modules/router_test/router_test.routing.yml
@@ -60,3 +60,12 @@ router_test_10:
_content: '\Drupal\router_test\TestContent::test1'
requirements:
_access: 'TRUE'
+
+router_test_11:
+ pattern: '/router_test/test11'
+ options:
+ _auth: [ 'http_basic' ]
+ requirements:
+ _user_is_logged_in: 'TRUE'
+ defaults:
+ _content: '\Drupal\router_test\TestContent::test11'
diff --git a/core/modules/system/tests/modules/session_test/lib/Drupal/session_test/EventSubscriber/SessionTestSubscriber.php b/core/modules/system/tests/modules/session_test/lib/Drupal/session_test/EventSubscriber/SessionTestSubscriber.php
index 45234a8..44e2882 100644
--- a/core/modules/system/tests/modules/session_test/lib/Drupal/session_test/EventSubscriber/SessionTestSubscriber.php
+++ b/core/modules/system/tests/modules/session_test/lib/Drupal/session_test/EventSubscriber/SessionTestSubscriber.php
@@ -65,7 +65,7 @@ class SessionTestSubscriber implements EventSubscriberInterface {
*/
static function getSubscribedEvents() {
$events[KernelEvents::RESPONSE][] = array('onKernelResponseSessionTest', 300);
- $events[KernelEvents::REQUEST][] = array('onKernelRequestSessionTest', 300);
+ $events[KernelEvents::REQUEST][] = array('onKernelRequestSessionTest', 100);
return $events;
}
diff --git a/core/modules/user/lib/Drupal/user/Access/RoleAccessCheck.php b/core/modules/user/lib/Drupal/user/Access/RoleAccessCheck.php
index 7940bf3..fd9756f 100644
--- a/core/modules/user/lib/Drupal/user/Access/RoleAccessCheck.php
+++ b/core/modules/user/lib/Drupal/user/Access/RoleAccessCheck.php
@@ -34,9 +34,7 @@ class RoleAccessCheck implements AccessCheckInterface {
// Requirements just allow strings, so this might be a comma separated list.
$rid_string = $route->getRequirement('_role');
- // @todo Replace the role check with a correctly injected and session-using
- // alternative.
- $account = $GLOBALS['user'];
+ $account = $request->attributes->get('account');
$explode_and = array_filter(array_map('trim', explode('+', $rid_string)));
if (count($explode_and) > 1) {
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index 901240d..911f00b 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -457,7 +457,9 @@ function user_access($string, AccountInterface $account = NULL) {
global $user;
if (!isset($account)) {
- $account = $user;
+ // In the installer request session is not set, so we have to fall back
+ // to the global $user. In all other cases the session key is preferred.
+ $account = Drupal::request()->attributes->get('account') ?: $user;
}
// Make sure we are working with the BC decorator.
diff --git a/core/tests/Drupal/Tests/Core/Route/RoleAccessCheckTest.php b/core/tests/Drupal/Tests/Core/Route/RoleAccessCheckTest.php
index b6e5ab5..a0ba259 100644
--- a/core/tests/Drupal/Tests/Core/Route/RoleAccessCheckTest.php
+++ b/core/tests/Drupal/Tests/Core/Route/RoleAccessCheckTest.php
@@ -159,19 +159,16 @@ class RoleAccessCheckTest extends UnitTestCase {
$collection = $this->getTestRouteCollection();
foreach ($grant_accounts as $account) {
- // @todo Replace the global user with a properly injection session.
- $GLOBALS['user'] = $account;
-
$subrequest = Request::create($path, 'GET');
+ $subrequest->attributes->set('account', $account);
$message = sprintf('Access granted for user with the roles %s on path: %s', implode(', ', $account->roles), $path);
$this->assertSame(AccessCheckInterface::ALLOW, $role_access_check->access($collection->get($path), $subrequest), $message);
}
// Check all users which don't have access.
foreach ($deny_accounts as $account) {
- $GLOBALS['user'] = $account;
-
$subrequest = Request::create($path, 'GET');
+ $subrequest->attributes->set('account', $account);
$message = sprintf('Access denied for user %s with the roles %s on path: %s', $account->uid, implode(', ', $account->roles), $path);
$has_access = $role_access_check->access($collection->get($path), $subrequest);
$this->assertSame(AccessCheckInterface::DENY, $has_access , $message);
diff --git a/core/update.php b/core/update.php
index 3b6afe3..b74e226 100644
--- a/core/update.php
+++ b/core/update.php
@@ -427,7 +427,9 @@ require_once __DIR__ . '/includes/schema.inc';
update_prepare_d8_bootstrap();
// Determine if the current user has access to run update.php.
-drupal_bootstrap(DRUPAL_BOOTSTRAP_SESSION);
+drupal_bootstrap(DRUPAL_BOOTSTRAP_VARIABLES);
+require_once DRUPAL_ROOT . '/' . settings()->get('session_inc', 'core/includes/session.inc');
+drupal_session_initialize();
// A request object from the HTTPFoundation to tell us about the request.
// @todo These two lines were copied from index.php which has its own todo about