feature #19398 [DX][SecurityBundle] Introduce a FirewallConfig class accessible from FirewallContext (chalasr)
This PR was merged into the 3.2-dev branch.
Discussion
----------
[DX][SecurityBundle] Introduce a FirewallConfig class accessible from FirewallContext
| Q | A |
| --- | --- |
| Branch? | master |
| Bug fix? | no |
| New feature? | yes |
| BC breaks? | no |
| Deprecations? | yes but it should not have any impact in userland |
| Tests pass? | yes |
| Fixed tickets | #15294 |
| License | MIT |
| Doc PR | todo |
With this, the `FirewallContext` class now has a `getConfig()` method returning a `FirewallConfig` object representing the firewall configuration.
Also this adds a `getContext()` method to the `FirewallMap` class of the `SecurityBundle`, to be able to retrieve the current context.
In a next time, this could be useful to display some firewall related informations to the Profiler, as pointed out in #15294.
Also, it can be useful to be able to access the current firewall configuration from an AuthenticationListener, especially for third party bundles (I can develop on demand).
Commits
-------
52d25ed
Introduce a FirewallConfig class
This commit is contained in:
commit
904e90ba63
@ -110,6 +110,7 @@ class SecurityExtension extends Extension
|
||||
'Symfony\Component\Security\Core\Authorization\AccessDecisionManager',
|
||||
'Symfony\Component\Security\Core\Authorization\AuthorizationChecker',
|
||||
'Symfony\Component\Security\Core\Authorization\Voter\VoterInterface',
|
||||
'Symfony\Bundle\SecurityBundle\Security\FirewallConfig',
|
||||
'Symfony\Bundle\SecurityBundle\Security\FirewallMap',
|
||||
'Symfony\Bundle\SecurityBundle\Security\FirewallContext',
|
||||
'Symfony\Component\HttpFoundation\RequestMatcher',
|
||||
@ -236,14 +237,18 @@ class SecurityExtension extends Extension
|
||||
$mapDef = $container->getDefinition('security.firewall.map');
|
||||
$map = $authenticationProviders = array();
|
||||
foreach ($firewalls as $name => $firewall) {
|
||||
list($matcher, $listeners, $exceptionListener) = $this->createFirewall($container, $name, $firewall, $authenticationProviders, $providerIds);
|
||||
$configId = 'security.firewall.map.config.'.$name;
|
||||
|
||||
list($matcher, $listeners, $exceptionListener) = $this->createFirewall($container, $name, $firewall, $authenticationProviders, $providerIds, $configId);
|
||||
|
||||
$contextId = 'security.firewall.map.context.'.$name;
|
||||
$context = $container->setDefinition($contextId, new DefinitionDecorator('security.firewall.context'));
|
||||
$context
|
||||
->replaceArgument(0, $listeners)
|
||||
->replaceArgument(1, $exceptionListener)
|
||||
->replaceArgument(2, new Reference($configId))
|
||||
;
|
||||
|
||||
$map[$contextId] = $matcher;
|
||||
}
|
||||
$mapDef->replaceArgument(1, $map);
|
||||
@ -258,8 +263,11 @@ class SecurityExtension extends Extension
|
||||
;
|
||||
}
|
||||
|
||||
private function createFirewall(ContainerBuilder $container, $id, $firewall, &$authenticationProviders, $providerIds)
|
||||
private function createFirewall(ContainerBuilder $container, $id, $firewall, &$authenticationProviders, $providerIds, $configId)
|
||||
{
|
||||
$config = $container->setDefinition($configId, new DefinitionDecorator('security.firewall.config'));
|
||||
$config->replaceArgument(0, $id);
|
||||
|
||||
// Matcher
|
||||
$matcher = null;
|
||||
if (isset($firewall['request_matcher'])) {
|
||||
@ -271,11 +279,16 @@ class SecurityExtension extends Extension
|
||||
$matcher = $this->createRequestMatcher($container, $pattern, $host, $methods);
|
||||
}
|
||||
|
||||
$config->replaceArgument(1, (string) $matcher);
|
||||
$config->replaceArgument(2, $firewall['security']);
|
||||
|
||||
// Security disabled?
|
||||
if (false === $firewall['security']) {
|
||||
return array($matcher, array(), null);
|
||||
}
|
||||
|
||||
$config->replaceArgument(3, $firewall['stateless']);
|
||||
|
||||
// Provider id (take the first registered provider if none defined)
|
||||
if (isset($firewall['provider'])) {
|
||||
$defaultProvider = $this->getUserProviderId($firewall['provider']);
|
||||
@ -283,8 +296,11 @@ class SecurityExtension extends Extension
|
||||
$defaultProvider = reset($providerIds);
|
||||
}
|
||||
|
||||
$config->replaceArgument(4, $defaultProvider);
|
||||
|
||||
// Register listeners
|
||||
$listeners = array();
|
||||
$listenerKeys = array();
|
||||
|
||||
// Channel listener
|
||||
$listeners[] = new Reference('security.channel_listener');
|
||||
@ -296,11 +312,14 @@ class SecurityExtension extends Extension
|
||||
$contextKey = $firewall['context'];
|
||||
}
|
||||
|
||||
$config->replaceArgument(5, $contextKey);
|
||||
|
||||
$listeners[] = new Reference($this->createContextListener($container, $contextKey));
|
||||
}
|
||||
|
||||
// Logout listener
|
||||
if (isset($firewall['logout'])) {
|
||||
$listenerKeys[] = 'logout';
|
||||
$listenerId = 'security.logout_listener.'.$id;
|
||||
$listener = $container->setDefinition($listenerId, new DefinitionDecorator('security.logout_listener'));
|
||||
$listener->replaceArgument(3, array(
|
||||
@ -363,10 +382,13 @@ class SecurityExtension extends Extension
|
||||
// Authentication listeners
|
||||
list($authListeners, $defaultEntryPoint) = $this->createAuthenticationListeners($container, $id, $firewall, $authenticationProviders, $defaultProvider, $configuredEntryPoint);
|
||||
|
||||
$config->replaceArgument(6, $configuredEntryPoint ?: $defaultEntryPoint);
|
||||
|
||||
$listeners = array_merge($listeners, $authListeners);
|
||||
|
||||
// Switch user listener
|
||||
if (isset($firewall['switch_user'])) {
|
||||
$listenerKeys[] = 'switch_user';
|
||||
$listeners[] = new Reference($this->createSwitchUserListener($container, $id, $firewall['switch_user'], $defaultProvider));
|
||||
}
|
||||
|
||||
@ -376,7 +398,30 @@ class SecurityExtension extends Extension
|
||||
// Exception listener
|
||||
$exceptionListener = new Reference($this->createExceptionListener($container, $firewall, $id, $configuredEntryPoint ?: $defaultEntryPoint, $firewall['stateless']));
|
||||
|
||||
if (isset($firewall['access_denied_handler'])) {
|
||||
$config->replaceArgument(7, $firewall['access_denied_handler']);
|
||||
}
|
||||
if (isset($firewall['access_denied_url'])) {
|
||||
$config->replaceArgument(8, $firewall['access_denied_url']);
|
||||
}
|
||||
|
||||
$container->setAlias(new Alias('security.user_checker.'.$id, false), $firewall['user_checker']);
|
||||
$config->replaceArgument(9, $firewall['user_checker']);
|
||||
|
||||
foreach ($this->factories as $position) {
|
||||
foreach ($position as $factory) {
|
||||
$key = str_replace('-', '_', $factory->getKey());
|
||||
if (array_key_exists($key, $firewall)) {
|
||||
$listenerKeys[] = $key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($firewall['anonymous'])) {
|
||||
$listenerKeys[] = 'anonymous';
|
||||
}
|
||||
|
||||
$config->replaceArgument(10, $listenerKeys);
|
||||
|
||||
return array($matcher, $listeners, $exceptionListener);
|
||||
}
|
||||
|
@ -111,6 +111,21 @@
|
||||
<service id="security.firewall.context" class="Symfony\Bundle\SecurityBundle\Security\FirewallContext" abstract="true">
|
||||
<argument type="collection" />
|
||||
<argument type="service" id="security.exception_listener" />
|
||||
<argument /> <!-- FirewallConfig -->
|
||||
</service>
|
||||
|
||||
<service id="security.firewall.config" class="Symfony\Bundle\SecurityBundle\Security\FirewallConfig" abstract="true" public="false">
|
||||
<argument /> <!-- name -->
|
||||
<argument /> <!-- request_matcher -->
|
||||
<argument /> <!-- security enabled -->
|
||||
<argument /> <!-- stateless -->
|
||||
<argument /> <!-- provider -->
|
||||
<argument /> <!-- context -->
|
||||
<argument /> <!-- entry_point -->
|
||||
<argument /> <!-- user_checker -->
|
||||
<argument /> <!-- access_denied_handler -->
|
||||
<argument /> <!-- access_denied_url -->
|
||||
<argument type="collection" /> <!-- listeners -->
|
||||
</service>
|
||||
|
||||
<service id="security.logout_url_generator" class="Symfony\Component\Security\Http\Logout\LogoutUrlGenerator" public="false">
|
||||
@ -119,7 +134,6 @@
|
||||
<argument type="service" id="security.token_storage" on-invalid="null" />
|
||||
</service>
|
||||
|
||||
|
||||
<!-- Provisioning -->
|
||||
<service id="security.user.provider.in_memory" class="Symfony\Component\Security\Core\User\InMemoryUserProvider" abstract="true" public="false" />
|
||||
<service id="security.user.provider.in_memory.user" class="Symfony\Component\Security\Core\User\User" abstract="true" public="false" />
|
||||
|
126
src/Symfony/Bundle/SecurityBundle/Security/FirewallConfig.php
Normal file
126
src/Symfony/Bundle/SecurityBundle/Security/FirewallConfig.php
Normal file
@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\Security;
|
||||
|
||||
/**
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
class FirewallConfig
|
||||
{
|
||||
private $name;
|
||||
private $requestMatcher;
|
||||
private $securityEnabled;
|
||||
private $stateless;
|
||||
private $provider;
|
||||
private $context;
|
||||
private $entryPoint;
|
||||
private $accessDeniedHandler;
|
||||
private $accessDeniedUrl;
|
||||
private $userChecker;
|
||||
private $listeners;
|
||||
|
||||
public function __construct($name, $requestMatcher, $securityEnabled = true, $stateless = false, $provider = null, $context = null, $entryPoint = null, $accessDeniedHandler = null, $accessDeniedUrl = null, $userChecker = null, $listeners = array())
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->requestMatcher = $requestMatcher;
|
||||
$this->securityEnabled = $securityEnabled;
|
||||
$this->stateless = $stateless;
|
||||
$this->provider = $provider;
|
||||
$this->context = $context;
|
||||
$this->entryPoint = $entryPoint;
|
||||
$this->accessDeniedHandler = $accessDeniedHandler;
|
||||
$this->accessDeniedUrl = $accessDeniedUrl;
|
||||
$this->userChecker = $userChecker;
|
||||
$this->listeners = $listeners;
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string The request matcher service id
|
||||
*/
|
||||
public function getRequestMatcher()
|
||||
{
|
||||
return $this->requestMatcher;
|
||||
}
|
||||
|
||||
public function isSecurityEnabled()
|
||||
{
|
||||
return $this->securityEnabled;
|
||||
}
|
||||
|
||||
public function allowsAnonymous()
|
||||
{
|
||||
return in_array('anonymous', $this->listeners, true);
|
||||
}
|
||||
|
||||
public function isStateless()
|
||||
{
|
||||
return $this->stateless;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string The provider service id
|
||||
*/
|
||||
public function getProvider()
|
||||
{
|
||||
return $this->provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string The context key
|
||||
*/
|
||||
public function getContext()
|
||||
{
|
||||
return $this->context;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string The entry_point service id
|
||||
*/
|
||||
public function getEntryPoint()
|
||||
{
|
||||
return $this->entryPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string The user_checker service id
|
||||
*/
|
||||
public function getUserChecker()
|
||||
{
|
||||
return $this->userChecker;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string The access_denied_handler service id
|
||||
*/
|
||||
public function getAccessDeniedHandler()
|
||||
{
|
||||
return $this->accessDeniedHandler;
|
||||
}
|
||||
|
||||
public function getAccessDeniedUrl()
|
||||
{
|
||||
return $this->accessDeniedUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array An array of listener keys
|
||||
*/
|
||||
public function getListeners()
|
||||
{
|
||||
return $this->listeners;
|
||||
}
|
||||
}
|
@ -23,11 +23,22 @@ class FirewallContext
|
||||
{
|
||||
private $listeners;
|
||||
private $exceptionListener;
|
||||
private $config;
|
||||
|
||||
public function __construct(array $listeners, ExceptionListener $exceptionListener = null)
|
||||
public function __construct(array $listeners, ExceptionListener $exceptionListener = null, FirewallConfig $config = null)
|
||||
{
|
||||
if (null === $config) {
|
||||
@trigger_error(sprintf('"%s()" expects an instance of "%s" as third argument since version 3.2 and will trigger an error in 4.0 if not provided.', __METHOD__, FirewallConfig::class), E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
$this->listeners = $listeners;
|
||||
$this->exceptionListener = $exceptionListener;
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
public function getConfig()
|
||||
{
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
public function getContext()
|
||||
|
@ -35,7 +35,35 @@ class FirewallMap implements FirewallMapInterface
|
||||
$this->contexts = new \SplObjectStorage();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getListeners(Request $request)
|
||||
{
|
||||
$context = $this->getFirewallContext($request);
|
||||
|
||||
if (null === $context) {
|
||||
return array(array(), null);
|
||||
}
|
||||
|
||||
return $context->getContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FirewallConfig|null
|
||||
*/
|
||||
public function getFirewallConfig(Request $request)
|
||||
{
|
||||
$context = $this->getFirewallContext($request);
|
||||
|
||||
if (null === $context) {
|
||||
return;
|
||||
}
|
||||
|
||||
return $context->getConfig();
|
||||
}
|
||||
|
||||
private function getFirewallContext(Request $request)
|
||||
{
|
||||
if ($this->contexts->contains($request)) {
|
||||
return $this->contexts[$request];
|
||||
@ -43,10 +71,8 @@ class FirewallMap implements FirewallMapInterface
|
||||
|
||||
foreach ($this->map as $contextId => $requestMatcher) {
|
||||
if (null === $requestMatcher || $requestMatcher->matches($request)) {
|
||||
return $this->contexts[$request] = $this->container->get($contextId)->getContext();
|
||||
return $this->contexts[$request] = $this->container->get($contextId);
|
||||
}
|
||||
}
|
||||
|
||||
return array(array(), null);
|
||||
}
|
||||
}
|
||||
|
@ -63,15 +63,74 @@ abstract class CompleteConfigurationTest extends \PHPUnit_Framework_TestCase
|
||||
public function testFirewalls()
|
||||
{
|
||||
$container = $this->getContainer('container1');
|
||||
|
||||
$arguments = $container->getDefinition('security.firewall.map')->getArguments();
|
||||
$listeners = array();
|
||||
$configs = array();
|
||||
foreach (array_keys($arguments[1]) as $contextId) {
|
||||
$contextDef = $container->getDefinition($contextId);
|
||||
$arguments = $contextDef->getArguments();
|
||||
$listeners[] = array_map(function ($ref) { return (string) $ref; }, $arguments['index_0']);
|
||||
|
||||
$configDef = $container->getDefinition($arguments['index_2']);
|
||||
$configs[] = array_values($configDef->getArguments());
|
||||
}
|
||||
|
||||
$this->assertEquals(array(
|
||||
array(
|
||||
'simple',
|
||||
'security.request_matcher.707b20193d4cb9f2718114abcbebb32af48f948484fc166a03482f49bf14f25e271f72c7',
|
||||
false,
|
||||
),
|
||||
array(
|
||||
'secure',
|
||||
'',
|
||||
true,
|
||||
true,
|
||||
'security.user.provider.concrete.default',
|
||||
'security.authentication.form_entry_point.secure',
|
||||
'security.user_checker',
|
||||
array(
|
||||
'logout',
|
||||
'switch_user',
|
||||
'x509',
|
||||
'remote_user',
|
||||
'form_login',
|
||||
'http_basic',
|
||||
'http_digest',
|
||||
'remember_me',
|
||||
'anonymous',
|
||||
),
|
||||
),
|
||||
array(
|
||||
'host',
|
||||
'security.request_matcher.dda8b565689ad8509623ee68fb2c639cd81cd4cb339d60edbaf7d67d30e6aa09bd8c63c3',
|
||||
true,
|
||||
false,
|
||||
'security.user.provider.concrete.default',
|
||||
'host',
|
||||
'security.authentication.basic_entry_point.host',
|
||||
'security.user_checker',
|
||||
array(
|
||||
'http_basic',
|
||||
'anonymous',
|
||||
),
|
||||
),
|
||||
array(
|
||||
'with_user_checker',
|
||||
'',
|
||||
true,
|
||||
false,
|
||||
'security.user.provider.concrete.default',
|
||||
'with_user_checker',
|
||||
'security.authentication.basic_entry_point.with_user_checker',
|
||||
'app.user_checker',
|
||||
array(
|
||||
'http_basic',
|
||||
'anonymous',
|
||||
),
|
||||
),
|
||||
), $configs);
|
||||
|
||||
$this->assertEquals(array(
|
||||
array(),
|
||||
array(
|
||||
|
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\Tests\Security;
|
||||
|
||||
use Symfony\Bundle\SecurityBundle\Security\FirewallConfig;
|
||||
|
||||
class FirewallConfigTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testGetters()
|
||||
{
|
||||
$listeners = array('logout', 'remember_me', 'anonymous');
|
||||
$options = array(
|
||||
'request_matcher' => 'foo_request_matcher',
|
||||
'security' => false,
|
||||
'stateless' => false,
|
||||
'provider' => 'foo_provider',
|
||||
'context' => 'foo_context',
|
||||
'entry_point' => 'foo_entry_point',
|
||||
'access_denied_url' => 'foo_access_denied_url',
|
||||
'access_denied_handler' => 'foo_access_denied_handler',
|
||||
'user_checker' => 'foo_user_checker',
|
||||
);
|
||||
|
||||
$config = new FirewallConfig(
|
||||
'foo_firewall',
|
||||
$options['request_matcher'],
|
||||
$options['security'],
|
||||
$options['stateless'],
|
||||
$options['provider'],
|
||||
$options['context'],
|
||||
$options['entry_point'],
|
||||
$options['access_denied_handler'],
|
||||
$options['access_denied_url'],
|
||||
$options['user_checker'],
|
||||
$listeners
|
||||
);
|
||||
|
||||
$this->assertSame('foo_firewall', $config->getName());
|
||||
$this->assertSame($options['request_matcher'], $config->getRequestMatcher());
|
||||
$this->assertSame($options['security'], $config->isSecurityEnabled());
|
||||
$this->assertSame($options['stateless'], $config->isStateless());
|
||||
$this->assertSame($options['provider'], $config->getProvider());
|
||||
$this->assertSame($options['context'], $config->getContext());
|
||||
$this->assertSame($options['entry_point'], $config->getEntryPoint());
|
||||
$this->assertSame($options['access_denied_handler'], $config->getAccessDeniedHandler());
|
||||
$this->assertSame($options['access_denied_url'], $config->getAccessDeniedUrl());
|
||||
$this->assertSame($options['user_checker'], $config->getUserChecker());
|
||||
$this->assertTrue($config->allowsAnonymous());
|
||||
$this->assertSame($listeners, $config->getListeners());
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\Tests\Security;
|
||||
|
||||
use Symfony\Bundle\SecurityBundle\Security\FirewallConfig;
|
||||
use Symfony\Bundle\SecurityBundle\Security\FirewallContext;
|
||||
use Symfony\Component\Security\Http\Firewall\ExceptionListener;
|
||||
use Symfony\Component\Security\Http\Firewall\ListenerInterface;
|
||||
|
||||
class FirewallContextTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testGetters()
|
||||
{
|
||||
$config = $this
|
||||
->getMockBuilder(FirewallConfig::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$exceptionListener = $this
|
||||
->getMockBuilder(ExceptionListener::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$listeners = array(
|
||||
$this
|
||||
->getMockBuilder(ListenerInterface::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock(),
|
||||
);
|
||||
|
||||
$context = new FirewallContext($listeners, $exceptionListener, $config);
|
||||
|
||||
$this->assertEquals(array($listeners, $exceptionListener), $context->getContext());
|
||||
$this->assertEquals($config, $context->getConfig());
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user