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()); + } +}