Commit bc3dad6b authored by alexpott's avatar alexpott

Issue #2490228 by damiankloip, almaudoh, martin107, clemens.tolboom, -enzo-,...

Issue #2490228 by damiankloip, almaudoh, martin107, clemens.tolboom, -enzo-, znerol, dawehner: Add Authentication Collector
parent a3a86053
......@@ -1321,6 +1321,9 @@ services:
- [setThemeManager, ['@theme.manager']]
authentication:
class: Drupal\Core\Authentication\AuthenticationManager
arguments: ['@authentication_collector']
authentication_collector:
class: Drupal\Core\Authentication\AuthenticationCollector
tags:
- { name: service_collector, tag: authentication_provider, call: addProvider }
authentication_subscriber:
......
<?php
/**
* @file
* Contains \Drupal\Core\Authentication\AuthenticationCollector.
*/
namespace Drupal\Core\Authentication;
/**
* A collector class for authentication providers.
*/
class AuthenticationCollector implements AuthenticationCollectorInterface {
/**
* Array of all registered authentication providers, keyed by ID.
*
* @var \Drupal\Core\Authentication\AuthenticationProviderInterface[]
*/
protected $providers;
/**
* Array of all providers and their priority.
*
* @var array
*/
protected $providerOrders = [];
/**
* Sorted list of registered providers.
*
* @var \Drupal\Core\Authentication\AuthenticationProviderInterface[]
*/
protected $sortedProviders;
/**
* List of providers which are allowed on routes with no _auth option.
*
* @var string[]
*/
protected $globalProviders;
/**
* {@inheritdoc}
*/
public function addProvider(AuthenticationProviderInterface $provider, $provider_id, $priority = 0, $global = FALSE) {
$this->providers[$provider_id] = $provider;
$this->providerOrders[$priority][$provider_id] = $provider;
// Force the providers to be re-sorted.
$this->sortedProviders = NULL;
if ($global) {
$this->globalProviders[$provider_id] = TRUE;
}
}
/**
* {@inheritdoc}
*/
public function isGlobal($provider_id) {
return isset($this->globalProviders[$provider_id]);
}
/**
* {@inheritdoc}
*/
public function getProvider($provider_id) {
return isset($this->providers[$provider_id]) ? $this->providers[$provider_id] : NULL;
}
/**
* {@inheritdoc}
*/
public function getSortedProviders() {
if (!isset($this->sortedProviders)) {
// Sort the providers according to priority.
krsort($this->providerOrders);
// Merge nested providers from $this->providers into $this->sortedProviders.
$this->sortedProviders = [];
foreach ($this->providerOrders as $providers) {
$this->sortedProviders = array_merge($this->sortedProviders, $providers);
}
}
return $this->sortedProviders;
}
}
......@@ -2,15 +2,15 @@
/**
* @file
* Contains \Drupal\Core\Authentication\AuthenticationManagerInterface.
* Contains \Drupal\Core\Authentication\AuthenticationCollectorInterface.
*/
namespace Drupal\Core\Authentication;
/**
* Defines an interface for authentication managers.
* Interface for collectors of registered authentication providers.
*/
interface AuthenticationManagerInterface {
interface AuthenticationCollectorInterface {
/**
* Adds a provider to the array of registered providers.
......@@ -27,4 +27,36 @@ interface AuthenticationManagerInterface {
*/
public function addProvider(AuthenticationProviderInterface $provider, $provider_id, $priority = 0, $global = FALSE);
/**
* Returns whether a provider is considered global.
*
* @param string $provider_id
* The provider ID.
*
* @return bool
* TRUE if the provider is global, FALSE otherwise.
*
* @see \Drupal\Core\Authentication\AuthenticationCollectorInterface::addProvider
*/
public function isGlobal($provider_id);
/**
* Returns an authentication provider.
*
* @param string $provider_id
* The provider ID.
*
* @return \Drupal\Core\Authentication\AuthenticationProviderInterface|NULL
* The authentication provider which matches the ID.
*/
public function getProvider($provider_id);
/**
* Returns the sorted array of authentication providers.
*
* @return \Drupal\Core\Authentication\AuthenticationProviderInterface[]
* An array of authentication provider objects.
*/
public function getSortedProviders();
}
......@@ -20,69 +20,23 @@
*
* If no provider set an active user then the user is set to anonymous.
*/
class AuthenticationManager implements AuthenticationProviderInterface, AuthenticationProviderFilterInterface, AuthenticationProviderChallengeInterface, AuthenticationManagerInterface {
class AuthenticationManager implements AuthenticationProviderInterface, AuthenticationProviderFilterInterface, AuthenticationProviderChallengeInterface {
/**
* Array of all registered authentication providers, keyed by ID.
* The authentication provider collector.
*
* @var \Drupal\Core\Authentication\AuthenticationProviderInterface[]
* @var \Drupal\Core\Authentication\AuthenticationCollectorInterface
*/
protected $providers;
protected $authCollector;
/**
* Array of all providers and their priority.
* Creates a new authentication manager instance.
*
* @var array
* @param \Drupal\Core\Authentication\AuthenticationCollectorInterface $auth_collector
* The authentication provider collector.
*/
protected $providerOrders = array();
/**
* Sorted list of registered providers.
*
* @var \Drupal\Core\Authentication\AuthenticationProviderInterface[]
*/
protected $sortedProviders;
/**
* List of providers which implement the filter interface.
*
* @var \Drupal\Core\Authentication\AuthenticationProviderFilterInterface[]
*/
protected $filters;
/**
* List of providers which implement the challenge interface.
*
* @var \Drupal\Core\Authentication\AuthenticationProviderChallengeInterface[]
*/
protected $challengers;
/**
* List of providers which are allowed on routes with no _auth option.
*
* @var string[]
*/
protected $globalProviders;
/**
* {@inheritdoc}
*/
public function addProvider(AuthenticationProviderInterface $provider, $provider_id, $priority = 0, $global = FALSE) {
$this->providers[$provider_id] = $provider;
$this->providerOrders[$priority][$provider_id] = $provider;
// Force the builders to be re-sorted.
$this->sortedProviders = NULL;
if ($provider instanceof AuthenticationProviderFilterInterface) {
$this->filters[$provider_id] = $provider;
}
if ($provider instanceof AuthenticationProviderChallengeInterface) {
$this->challengers[$provider_id] = $provider;
}
if ($global) {
$this->globalProviders[$provider_id] = TRUE;
}
public function __construct(AuthenticationCollectorInterface $auth_collector) {
$this->authCollector = $auth_collector;
}
/**
......@@ -97,7 +51,13 @@ public function applies(Request $request) {
*/
public function authenticate(Request $request) {
$provider_id = $this->getProvider($request);
return $this->providers[$provider_id]->authenticate($request);
$provider = $this->authCollector->getProvider($provider_id);
if ($provider) {
return $provider->authenticate($request);
}
return NULL;
}
/**
......@@ -110,7 +70,7 @@ public function appliesToRoutedRequest(Request $request, $authenticated) {
$result = $this->applyFilter($request, $authenticated, $this->getProvider($request));
}
else {
foreach ($this->getSortedProviders() as $provider_id => $provider) {
foreach ($this->authCollector->getSortedProviders() as $provider_id => $provider) {
if ($this->applyFilter($request, $authenticated, $provider_id)) {
$result = TRUE;
break;
......@@ -126,8 +86,10 @@ public function appliesToRoutedRequest(Request $request, $authenticated) {
*/
public function challengeException(Request $request, \Exception $previous) {
$provider_id = $this->getChallenger($request);
if ($provider_id) {
return $this->challengers[$provider_id]->challengeException($request, $previous);
$provider = $this->authCollector->getProvider($provider_id);
return $provider->challengeException($request, $previous);
}
}
......@@ -142,7 +104,7 @@ public function challengeException(Request $request, \Exception $previous) {
* If no application detects appropriate credentials, then NULL is returned.
*/
protected function getProvider(Request $request) {
foreach ($this->getSortedProviders() as $provider_id => $provider) {
foreach ($this->authCollector->getSortedProviders() as $provider_id => $provider) {
if ($provider->applies($request)) {
return $provider_id;
}
......@@ -150,21 +112,19 @@ protected function getProvider(Request $request) {
}
/**
* Returns the id of the challenge provider for a request.
* Returns the ID of the challenge provider for a request.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The incoming request.
*
* @return string|NULL
* The id of the first authentication provider which applies to the request.
* The ID of the first authentication provider which applies to the request.
* If no application detects appropriate credentials, then NULL is returned.
*/
protected function getChallenger(Request $request) {
if (!empty($this->challengers)) {
foreach ($this->getSortedProviders($request, FALSE) as $provider_id => $provider) {
if (isset($this->challengers[$provider_id]) && !$provider->applies($request) && $this->applyFilter($request, FALSE, $provider_id)) {
return $provider_id;
}
foreach ($this->authCollector->getSortedProviders() as $provider_id => $provider) {
if (($provider instanceof AuthenticationProviderChallengeInterface) && !$provider->applies($request) && $this->applyFilter($request, FALSE, $provider_id)) {
return $provider_id;
}
}
}
......@@ -186,8 +146,10 @@ protected function getChallenger(Request $request) {
* TRUE if provider is allowed, FALSE otherwise.
*/
protected function applyFilter(Request $request, $authenticated, $provider_id) {
if (isset($this->filters[$provider_id])) {
$result = $this->filters[$provider_id]->appliesToRoutedRequest($request, $authenticated);
$provider = $this->authCollector->getProvider($provider_id);
if ($provider && ($provider instanceof AuthenticationProviderFilterInterface)) {
$result = $provider->appliesToRoutedRequest($request, $authenticated);
}
else {
$result = $this->defaultFilter($request, $provider_id);
......@@ -222,27 +184,8 @@ protected function defaultFilter(Request $request, $provider_id) {
return in_array($provider_id, $route->getOption('_auth'));
}
else {
return isset($this->globalProviders[$provider_id]);
}
}
/**
* Returns the sorted array of authentication providers.
*
* @return \Drupal\Core\Authentication\AuthenticationProviderInterface[]
* 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->authCollector->isGlobal($provider_id);
}
return $this->sortedProviders;
}
}
<?php
/**
* @file
* Contains \Drupal\Tests\Core\Authentication\AuthenticationCollectorTest.
*/
namespace Drupal\Tests\Core\Authentication;
use Drupal\Core\Authentication\AuthenticationCollector;
use Drupal\Core\Authentication\AuthenticationProviderInterface;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\HttpFoundation\Request;
/**
* @coversDefaultClass \Drupal\Core\Authentication\AuthenticationCollector
* @group Authentication
*/
class AuthenticationCollectorTest extends UnitTestCase {
/**
* Tests adding, getting, and order of priorities.
*
* @covers ::addProvider
* @covers ::getSortedProviders
* @covers ::getProvider
* @covers ::isGlobal
*/
public function testAuthenticationCollector() {
$providers = [];
$global = [];
$authentication_collector = new AuthenticationCollector();
$priorities = [2, 0, -8, 10, 1, 3, -5, 0, 6, -10, -4];
foreach ($priorities as $priority) {
$provider_id = $this->randomMachineName();
$provider = new TestAuthenticationProvider($provider_id);
$providers[$priority][$provider_id] = $provider;
$global[$provider_id] = rand(0, 1) > 0.5;
$authentication_collector->addProvider($provider, $provider_id, $priority, $global[$provider_id]);
}
// Sort the $providers array by priority (highest number is lowest priority)
// and compare with AuthenticationCollector::getSortedProviders().
krsort($providers);
// Merge nested providers from $providers into $sorted_providers.
$sorted_providers = [];
foreach ($providers as $providers_priority) {
$sorted_providers = array_merge($sorted_providers, $providers_priority);
}
$this->assertEquals($sorted_providers, $authentication_collector->getSortedProviders());
// Test AuthenticationCollector::getProvider() and
// AuthenticationCollector::isGlobal().
foreach ($sorted_providers as $provider) {
$this->assertEquals($provider, $authentication_collector->getProvider($provider->providerId));
$this->assertEquals($global[$provider->providerId], $authentication_collector->isGlobal($provider->providerId));
}
}
}
/**
* A simple provider for unit testing AuthenticationCollector.
*/
class TestAuthenticationProvider implements AuthenticationProviderInterface {
/**
* The provider id.
*
* @var string
*/
public $providerId;
/**
* Constructor.
*/
public function __construct($provider_id) {
$this->providerId = $provider_id;
}
/**
* {@inheritdoc}
*/
public function applies(Request $request) {
return TRUE;
}
/**
* {@inheritdoc}
*/
public function authenticate(Request $request) {
return NULL;
}
}
......@@ -7,6 +7,7 @@
namespace Drupal\Tests\Core\Authentication;
use Drupal\Core\Authentication\AuthenticationCollector;
use Drupal\Core\Authentication\AuthenticationManager;
use Drupal\Core\Authentication\AuthenticationProviderFilterInterface;
use Drupal\Core\Authentication\AuthenticationProviderInterface;
......@@ -28,9 +29,10 @@ class AuthenticationManagerTest extends UnitTestCase {
* @dataProvider providerTestDefaultFilter
*/
public function testDefaultFilter($applies, $has_route, $auth_option, $provider_id, $global) {
$authentication_manager = new AuthenticationManager();
$auth_provider = $this->getMock('Drupal\Core\Authentication\AuthenticationProviderInterface');
$authentication_manager->addProvider($auth_provider, $provider_id, 0, $global);
$auth_collector = new AuthenticationCollector();
$auth_collector->addProvider($auth_provider, $provider_id, 0, $global);
$authentication_manager = new AuthenticationManager($auth_collector);
$request = new Request();
if ($has_route) {
......@@ -48,14 +50,16 @@ public function testDefaultFilter($applies, $has_route, $auth_option, $provider_
* @covers ::applyFilter
*/
public function testApplyFilterWithFilterprovider() {
$authentication_manager = new AuthenticationManager();
$auth_provider = $this->getMock('Drupal\Tests\Core\Authentication\TestAuthenticationProviderInterface');
$authentication_manager->addProvider($auth_provider, 'filtered', 0);
$auth_provider->expects($this->once())
->method('appliesToRoutedRequest')
->willReturn(TRUE);
$authentication_collector = new AuthenticationCollector();
$authentication_collector->addProvider($auth_provider, 'filtered', 0);
$authentication_manager = new AuthenticationManager($authentication_collector);
$request = new Request();
$this->assertTrue($authentication_manager->appliesToRoutedRequest($request, FALSE));
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment