diff --git a/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php b/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php index 544e1de1921af..7904fa889e852 100644 --- a/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php +++ b/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\SecurityBundle\DataCollector; +use Symfony\Bundle\SecurityBundle\Security\FirewallConfigRegistry; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Role\RoleHierarchyInterface; use Symfony\Component\HttpFoundation\Request; @@ -21,8 +22,6 @@ use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface; use Symfony\Component\Security\Core\Authorization\DebugAccessDecisionManager; use Symfony\Component\VarDumper\Cloner\Data; -use Symfony\Component\Security\Http\FirewallMapInterface; -use Symfony\Bundle\SecurityBundle\Security\FirewallMap; /** * SecurityDataCollector. @@ -35,7 +34,7 @@ class SecurityDataCollector extends DataCollector private $roleHierarchy; private $logoutUrlGenerator; private $accessDecisionManager; - private $firewallMap; + private $firewallConfigRegistry; /** * Constructor. @@ -44,15 +43,15 @@ class SecurityDataCollector extends DataCollector * @param RoleHierarchyInterface|null $roleHierarchy * @param LogoutUrlGenerator|null $logoutUrlGenerator * @param AccessDecisionManagerInterface|null $accessDecisionManager - * @param FirewallMapInterface|null $firewallMap + * @param FirewallConfigRegistry|null $firewallConfigRegistry */ - public function __construct(TokenStorageInterface $tokenStorage = null, RoleHierarchyInterface $roleHierarchy = null, LogoutUrlGenerator $logoutUrlGenerator = null, AccessDecisionManagerInterface $accessDecisionManager = null, FirewallMapInterface $firewallMap = null) + public function __construct(TokenStorageInterface $tokenStorage = null, RoleHierarchyInterface $roleHierarchy = null, LogoutUrlGenerator $logoutUrlGenerator = null, AccessDecisionManagerInterface $accessDecisionManager = null, FirewallConfigRegistry $firewallConfigRegistry = null) { $this->tokenStorage = $tokenStorage; $this->roleHierarchy = $roleHierarchy; $this->logoutUrlGenerator = $logoutUrlGenerator; $this->accessDecisionManager = $accessDecisionManager; - $this->firewallMap = $firewallMap; + $this->firewallConfigRegistry = $firewallConfigRegistry; } /** @@ -140,9 +139,8 @@ public function collect(Request $request, Response $response, \Exception $except // collect firewall context information $this->data['firewall'] = null; - if ($this->firewallMap instanceof FirewallMap) { - $firewallConfig = $this->firewallMap->getFirewallConfig($request); - if (null !== $firewallConfig) { + if (null !== $this->firewallConfigRegistry) { + if (null !== $firewallConfig = $this->firewallConfigRegistry->fromRequest($request)) { $this->data['firewall'] = array( 'name' => $firewallConfig->getName(), 'allows_anonymous' => $firewallConfig->allowsAnonymous(), diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index 5c88548bcff1d..ba48030725940 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -236,8 +236,10 @@ private function createFirewalls($config, ContainerBuilder $container) // load firewall map $mapDef = $container->getDefinition('security.firewall.map'); $map = $authenticationProviders = array(); + $firewallConfigs = array(); + $matchers = array(); foreach ($firewalls as $name => $firewall) { - $configId = 'security.firewall.map.config.'.$name; + $configId = 'security.firewall.config.'.$name; list($matcher, $listeners, $exceptionListener) = $this->createFirewall($container, $name, $firewall, $authenticationProviders, $providerIds, $configId); @@ -246,10 +248,11 @@ private function createFirewalls($config, ContainerBuilder $container) $context ->replaceArgument(0, $listeners) ->replaceArgument(1, $exceptionListener) - ->replaceArgument(2, new Reference($configId)) ; $map[$contextId] = $matcher; + $matchers[$name] = $matcher; + $firewallConfigs[] = new Reference($configId); } $mapDef->replaceArgument(1, $map); @@ -261,6 +264,11 @@ private function createFirewalls($config, ContainerBuilder $container) ->getDefinition('security.authentication.manager') ->replaceArgument(0, $authenticationProviders) ; + + $container->getDefinition('security.firewall.config_registry') + ->replaceArgument(0, $firewallConfigs) + ->replaceArgument(1, $matchers) + ; } private function createFirewall(ContainerBuilder $container, $id, $firewall, &$authenticationProviders, $providerIds, $configId) diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/collectors.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/collectors.xml index f812a9d790bb8..f726d7e46ea37 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/collectors.xml +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/collectors.xml @@ -11,7 +11,7 @@ - + diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml index 8def320482486..8cd6b1b8be574 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml @@ -103,6 +103,12 @@ + + + + + + @@ -111,7 +117,6 @@ - diff --git a/src/Symfony/Bundle/SecurityBundle/Security/FirewallConfigRegistry.php b/src/Symfony/Bundle/SecurityBundle/Security/FirewallConfigRegistry.php new file mode 100644 index 0000000000000..a7dc62d8923b1 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Security/FirewallConfigRegistry.php @@ -0,0 +1,119 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Security; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestMatcher; +use Symfony\Component\HttpFoundation\RequestStack; + +/** + * Stores firewall config objects. + * + * @author Maxime Steinhausser + */ +class FirewallConfigRegistry +{ + private $firewallConfigs; + private $requestStack; + private $requestMatchers; + + /** + * @param FirewallConfig[] $firewallConfigs + * @param RequestMatcher[] $requestMatchers Indexed by firewall name + * @param RequestStack|null $requestStack To get the current firewall config + */ + public function __construct(array $firewallConfigs = array(), array $requestMatchers = array(), RequestStack $requestStack = null) + { + $this->firewallConfigs = $firewallConfigs; + $this->requestStack = $requestStack; + $this->requestMatchers = $requestMatchers; + } + + /** + * @param string $name The firewall name + * + * @return FirewallConfig|null + */ + public function get($name) + { + foreach ($this->firewallConfigs as $config) { + if ($config->getName() === $name) { + return $config; + } + } + } + + /** + * @return FirewallConfig|null + */ + public function current() + { + if (!$this->requestStack) { + throw new \LogicException('Unable to get current firewall config without a RequestStack.'); + } + + return $this->fromRequest($this->requestStack->getCurrentRequest()); + } + + /** + * @param Request $request + * + * @return FirewallConfig|null + */ + public function fromRequest(Request $request) + { + foreach ($this->firewallConfigs as $config) { + $requestMatcher = $this->getRequestMatcher($config); + if (null === $requestMatcher || $requestMatcher->matches($request)) { + return $config; + } + } + } + + /** + * @return FirewallConfig[] + */ + public function all() + { + return $this->firewallConfigs; + } + + /** + * @return FirewallConfig[] + */ + public function inContext($context) + { + return array_filter($this->firewallConfigs, function (FirewallConfig $config) use ($context) { + return $context === $config->getContext(); + }); + } + + /** + * @param FirewallConfig $config + * + * @return RequestMatcher|null + */ + private function getRequestMatcher(FirewallConfig $config) + { + if (empty($config->getRequestMatcher())) { + return; + } + + $firewallName = $config->getName(); + + if (!isset($this->requestMatchers[$firewallName])) { + throw new \LogicException(sprintf('Request matcher not found for "%s" firewall.', $firewallName)); + } + + return $this->requestMatchers[$firewallName]; + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php b/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php index 9d00c121e5160..13d096d97e951 100644 --- a/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php +++ b/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php @@ -23,22 +23,11 @@ class FirewallContext { private $listeners; private $exceptionListener; - private $config; - public function __construct(array $listeners, ExceptionListener $exceptionListener = null, FirewallConfig $config = null) + public function __construct(array $listeners, ExceptionListener $exceptionListener = 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 f833a63e65966..2944165532705 100644 --- a/src/Symfony/Bundle/SecurityBundle/Security/FirewallMap.php +++ b/src/Symfony/Bundle/SecurityBundle/Security/FirewallMap.php @@ -49,20 +49,6 @@ public function getListeners(Request $request) 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)) { diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php index 2d912721ed4f4..ca953b63ab150 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php @@ -13,12 +13,11 @@ use Symfony\Bundle\SecurityBundle\DataCollector\SecurityDataCollector; use Symfony\Bundle\SecurityBundle\Security\FirewallConfig; -use Symfony\Bundle\SecurityBundle\Security\FirewallMap; +use Symfony\Bundle\SecurityBundle\Security\FirewallConfigRegistry; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\Role\Role; use Symfony\Component\Security\Core\Role\RoleHierarchy; -use Symfony\Component\Security\Http\FirewallMapInterface; class SecurityDataCollectorTest extends \PHPUnit_Framework_TestCase { @@ -81,17 +80,17 @@ public function testGetFirewall() $firewallConfig = new FirewallConfig('dummy', 'security.request_matcher.dummy', 'security.user_checker.dummy'); $request = $this->getRequest(); - $firewallMap = $this - ->getMockBuilder(FirewallMap::class) + $registry = $this + ->getMockBuilder(FirewallConfigRegistry::class) ->disableOriginalConstructor() ->getMock(); - $firewallMap + $registry ->expects($this->once()) - ->method('getFirewallConfig') + ->method('fromRequest') ->with($request) ->willReturn($firewallConfig); - $collector = new SecurityDataCollector(null, null, null, null, $firewallMap); + $collector = new SecurityDataCollector(null, null, null, null, $registry); $collector->collect($request, $this->getResponse()); $collected = $collector->getFirewall(); @@ -119,23 +118,13 @@ public function testGetFirewallReturnsNull() $collector->collect($request, $response); $this->assertNull($collector->getFirewall()); - // Inject an instance that is not context aware - $firewallMap = $this - ->getMockBuilder(FirewallMapInterface::class) - ->disableOriginalConstructor() - ->getMock(); - - $collector = new SecurityDataCollector(null, null, null, null, $firewallMap); - $collector->collect($request, $response); - $this->assertNull($collector->getFirewall()); - // Null config - $firewallMap = $this - ->getMockBuilder(FirewallMap::class) + $registry = $this + ->getMockBuilder(FirewallConfigRegistry::class) ->disableOriginalConstructor() ->getMock(); - $collector = new SecurityDataCollector(null, null, null, null, $firewallMap); + $collector = new SecurityDataCollector(null, null, null, null, $registry); $collector->collect($request, $response); $this->assertNull($collector->getFirewall()); } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php index 34e3ad56114ac..35cbbceff1065 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php @@ -65,14 +65,47 @@ 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']); + } + + $this->assertEquals(array( + array(), + array( + 'security.channel_listener', + 'security.logout_listener.secure', + 'security.authentication.listener.x509.secure', + 'security.authentication.listener.remote_user.secure', + 'security.authentication.listener.form.secure', + 'security.authentication.listener.basic.secure', + 'security.authentication.listener.digest.secure', + 'security.authentication.listener.rememberme.secure', + 'security.authentication.listener.anonymous.secure', + 'security.authentication.switchuser_listener.secure', + 'security.access_listener', + ), + array( + 'security.channel_listener', + 'security.context_listener.0', + 'security.authentication.listener.basic.host', + 'security.authentication.listener.anonymous.host', + 'security.access_listener', + ), + array( + 'security.channel_listener', + 'security.context_listener.1', + 'security.authentication.listener.basic.with_user_checker', + 'security.authentication.listener.anonymous.with_user_checker', + 'security.access_listener', + ), + ), $listeners); - $configDef = $container->getDefinition($arguments['index_2']); - $configs[] = array_values($configDef->getArguments()); + $configs = array(); + $configDefs = $container->getDefinition('security.firewall.config_registry')->getArgument(0); + foreach ($configDefs as $configRef) { + $configs[] = array_values($container->getDefinition($configRef)->getArguments()); } $this->assertEquals(array( @@ -131,37 +164,6 @@ public function testFirewalls() ), ), ), $configs); - - $this->assertEquals(array( - array(), - array( - 'security.channel_listener', - 'security.logout_listener.secure', - 'security.authentication.listener.x509.secure', - 'security.authentication.listener.remote_user.secure', - 'security.authentication.listener.form.secure', - 'security.authentication.listener.basic.secure', - 'security.authentication.listener.digest.secure', - 'security.authentication.listener.rememberme.secure', - 'security.authentication.listener.anonymous.secure', - 'security.authentication.switchuser_listener.secure', - 'security.access_listener', - ), - array( - 'security.channel_listener', - 'security.context_listener.0', - 'security.authentication.listener.basic.host', - 'security.authentication.listener.anonymous.host', - 'security.access_listener', - ), - array( - 'security.channel_listener', - 'security.context_listener.1', - 'security.authentication.listener.basic.with_user_checker', - 'security.authentication.listener.anonymous.with_user_checker', - 'security.access_listener', - ), - ), $listeners); } public function testFirewallRequestMatchers() diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Security/FirewallConfigRegistryTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Security/FirewallConfigRegistryTest.php new file mode 100644 index 0000000000000..d6cce41c45aa5 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Security/FirewallConfigRegistryTest.php @@ -0,0 +1,92 @@ + + * + * 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\FirewallConfigRegistry; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestMatcher; +use Symfony\Component\HttpFoundation\RequestStack; + +class FirewallConfigRegistryTest extends \PHPUnit_Framework_TestCase +{ + public function testGet() + { + $registry = new FirewallConfigRegistry(array( + $adminConfig = new FirewallConfig('admin', 'admin_request_matcher', 'user_checker'), + $appConfig = new FirewallConfig('app', '', 'user_checker'), + )); + + $this->assertSame($adminConfig, $registry->get('admin')); + $this->assertSame($appConfig, $registry->get('app')); + } + + public function testAll() + { + $registry = new FirewallConfigRegistry(array( + $adminConfig = new FirewallConfig('admin', 'admin_request_matcher', 'user_checker'), + $appConfig = new FirewallConfig('app', '', 'user_checker'), + )); + + $this->assertSame(array($adminConfig, $appConfig), $registry->all()); + } + + public function testInContext() + { + $registry = new FirewallConfigRegistry(array( + $adminConfig = new FirewallConfig('admin', 'admin_request_matcher', 'user_checker', true, false, null, 'secured'), + $appConfig = new FirewallConfig('app', '', 'user_checker', true, false, null, 'secured'), + new FirewallConfig('stateless', '', 'user_checker', true, true, null, null), + )); + + $this->assertSame(array($adminConfig, $appConfig), $registry->inContext('secured')); + } + + public function testFromRequest() + { + $registry = new FirewallConfigRegistry(array( + $adminConfig = new FirewallConfig('admin', 'admin_request_matcher', 'user_checker', true, false, null, 'secured'), + $appConfig = new FirewallConfig('app', '', 'user_checker', true, false, null, 'secured'), + ), array( + 'admin' => new RequestMatcher('/admin'), + )); + + $request = $this->getMockBuilder(Request::class) + ->disableOriginalConstructor() + ->getMock() + ; + $request->method('getPathInfo')->willReturn('/admin'); + + $this->assertSame($adminConfig, $registry->fromRequest($request)); + } + + public function testCurrent() + { + $requestStack = new RequestStack(); + + $request = $this->getMockBuilder(Request::class) + ->disableOriginalConstructor() + ->getMock() + ; + $request->method('getPathInfo')->willReturn('/admin'); + $requestStack->push($request); + + $registry = new FirewallConfigRegistry(array( + $adminConfig = new FirewallConfig('admin', 'admin_request_matcher', 'user_checker', true, false, null, 'secured'), + $appConfig = new FirewallConfig('app', '', 'user_checker', true, false, null, 'secured'), + ), array( + 'admin' => new RequestMatcher('/admin'), + ), $requestStack); + + $this->assertSame($adminConfig, $registry->current()); + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Security/FirewallContextTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Security/FirewallContextTest.php index 098e2c51f80dd..99589a25be4c1 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Security/FirewallContextTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Security/FirewallContextTest.php @@ -11,7 +11,6 @@ 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; @@ -20,8 +19,6 @@ class FirewallContextTest extends \PHPUnit_Framework_TestCase { public function testGetters() { - $config = new FirewallConfig('main', 'request_matcher', 'user_checker'); - $exceptionListener = $this ->getMockBuilder(ExceptionListener::class) ->disableOriginalConstructor() @@ -34,9 +31,8 @@ public function testGetters() ->getMock(), ); - $context = new FirewallContext($listeners, $exceptionListener, $config); + $context = new FirewallContext($listeners, $exceptionListener); $this->assertEquals(array($listeners, $exceptionListener), $context->getContext()); - $this->assertEquals($config, $context->getConfig()); } }