diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php
index 4398a2a36d..6ac5c63338 100644
--- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php
+++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php
@@ -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);
}
diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml
index c34453cb38..244e75dea0 100644
--- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml
+++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml
@@ -111,6 +111,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -119,7 +134,6 @@
-
diff --git a/src/Symfony/Bundle/SecurityBundle/Security/FirewallConfig.php b/src/Symfony/Bundle/SecurityBundle/Security/FirewallConfig.php
new file mode 100644
index 0000000000..4cc5ce17ba
--- /dev/null
+++ b/src/Symfony/Bundle/SecurityBundle/Security/FirewallConfig.php
@@ -0,0 +1,126 @@
+
+ *
+ * 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
+ */
+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;
+ }
+}
diff --git a/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php b/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php
index 13d096d97e..9d00c121e5 100644
--- a/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php
+++ b/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php
@@ -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()
diff --git a/src/Symfony/Bundle/SecurityBundle/Security/FirewallMap.php b/src/Symfony/Bundle/SecurityBundle/Security/FirewallMap.php
index da79e3c8c2..f833a63e65 100644
--- a/src/Symfony/Bundle/SecurityBundle/Security/FirewallMap.php
+++ b/src/Symfony/Bundle/SecurityBundle/Security/FirewallMap.php
@@ -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);
}
}
diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php
index a0dc39a116..8f877de4cc 100644
--- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php
+++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php
@@ -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(
diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Security/FirewallConfigTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Security/FirewallConfigTest.php
new file mode 100644
index 0000000000..8d9b0519b1
--- /dev/null
+++ b/src/Symfony/Bundle/SecurityBundle/Tests/Security/FirewallConfigTest.php
@@ -0,0 +1,60 @@
+
+ *
+ * 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());
+ }
+}
diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Security/FirewallContextTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Security/FirewallContextTest.php
new file mode 100644
index 0000000000..86aecc1aa2
--- /dev/null
+++ b/src/Symfony/Bundle/SecurityBundle/Tests/Security/FirewallContextTest.php
@@ -0,0 +1,45 @@
+
+ *
+ * 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());
+ }
+}