From 9f7eb618a4535b569c9b6a9590dda544300657f6 Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Thu, 13 Apr 2017 19:48:35 +0200 Subject: [PATCH] [Security] Allow to set a check_path on json_login listener --- .../Security/Factory/JsonLoginFactory.php | 10 +++--- .../Resources/config/security_listeners.xml | 1 + .../Tests/Functional/app/JsonLogin/config.yml | 2 +- src/Symfony/Component/Security/CHANGELOG.md | 1 + ...namePasswordJsonAuthenticationListener.php | 12 ++++++- ...PasswordJsonAuthenticationListenerTest.php | 32 +++++++++++++++++-- 6 files changed, 50 insertions(+), 8 deletions(-) diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/JsonLoginFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/JsonLoginFactory.php index 2bda0ea9dc..f7da4aa4a6 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/JsonLoginFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/JsonLoginFactory.php @@ -19,6 +19,8 @@ use Symfony\Component\DependencyInjection\Reference; * JsonLoginFactory creates services for JSON login authentication. * * @author Kévin Dunglas + * + * @experimental in version 3.3 */ class JsonLoginFactory extends AbstractFactory { @@ -83,10 +85,10 @@ class JsonLoginFactory extends AbstractFactory { $listenerId = $this->getListenerId(); $listener = new ChildDefinition($listenerId); - $listener->replaceArgument(2, $id); - $listener->replaceArgument(3, new Reference($this->createAuthenticationSuccessHandler($container, $id, $config))); - $listener->replaceArgument(4, new Reference($this->createAuthenticationFailureHandler($container, $id, $config))); - $listener->replaceArgument(5, array_intersect_key($config, $this->options)); + $listener->replaceArgument(3, $id); + $listener->replaceArgument(4, new Reference($this->createAuthenticationSuccessHandler($container, $id, $config))); + $listener->replaceArgument(5, new Reference($this->createAuthenticationFailureHandler($container, $id, $config))); + $listener->replaceArgument(6, array_intersect_key($config, $this->options)); $listenerId .= '.'.$id; $container->setDefinition($listenerId, $listener); diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.xml index f6b9cbf811..1eb7687a5d 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.xml +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.xml @@ -147,6 +147,7 @@ + diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/config.yml index 8234b21727..d6ed10e896 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/config.yml @@ -16,7 +16,7 @@ security: pattern: ^/ anonymous: true json_login: - check_path: /mychk + check_path: /chk username_path: user.login password_path: user.password diff --git a/src/Symfony/Component/Security/CHANGELOG.md b/src/Symfony/Component/Security/CHANGELOG.md index afc48f927d..69cdd28528 100644 --- a/src/Symfony/Component/Security/CHANGELOG.md +++ b/src/Symfony/Component/Security/CHANGELOG.md @@ -6,6 +6,7 @@ CHANGELOG * deprecated `AccessDecisionManager::setVoters()` in favor of passing the voters to the constructor. + * [EXPERIMENTAL] added a `json_login` listener for stateless authentication 3.2.0 ----- diff --git a/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordJsonAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordJsonAuthenticationListener.php index 8b40119632..2cfd4d9d23 100644 --- a/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordJsonAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordJsonAuthenticationListener.php @@ -29,6 +29,7 @@ use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface; use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface; use Symfony\Component\Security\Http\Event\InteractiveLoginEvent; +use Symfony\Component\Security\Http\HttpUtils; use Symfony\Component\Security\Http\SecurityEvents; /** @@ -36,11 +37,14 @@ use Symfony\Component\Security\Http\SecurityEvents; * an authentication via a JSON document composed of a username and a password. * * @author Kévin Dunglas + * + * @experimental in version 3.3 */ class UsernamePasswordJsonAuthenticationListener implements ListenerInterface { private $tokenStorage; private $authenticationManager; + private $httpUtils; private $providerKey; private $successHandler; private $failureHandler; @@ -49,10 +53,11 @@ class UsernamePasswordJsonAuthenticationListener implements ListenerInterface private $eventDispatcher; private $propertyAccessor; - public function __construct(TokenStorageInterface $tokenStorage, AuthenticationManagerInterface $authenticationManager, $providerKey, AuthenticationSuccessHandlerInterface $successHandler, AuthenticationFailureHandlerInterface $failureHandler, array $options = array(), LoggerInterface $logger = null, EventDispatcherInterface $eventDispatcher = null, PropertyAccessorInterface $propertyAccessor = null) + public function __construct(TokenStorageInterface $tokenStorage, AuthenticationManagerInterface $authenticationManager, HttpUtils $httpUtils, $providerKey, AuthenticationSuccessHandlerInterface $successHandler, AuthenticationFailureHandlerInterface $failureHandler, array $options = array(), LoggerInterface $logger = null, EventDispatcherInterface $eventDispatcher = null, PropertyAccessorInterface $propertyAccessor = null) { $this->tokenStorage = $tokenStorage; $this->authenticationManager = $authenticationManager; + $this->httpUtils = $httpUtils; $this->providerKey = $providerKey; $this->successHandler = $successHandler; $this->failureHandler = $failureHandler; @@ -68,6 +73,11 @@ class UsernamePasswordJsonAuthenticationListener implements ListenerInterface public function handle(GetResponseEvent $event) { $request = $event->getRequest(); + + if (isset($this->options['check_path']) && !$this->httpUtils->checkRequestPath($request, $this->options['check_path'])) { + return; + } + $data = json_decode($request->getContent()); try { diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/UsernamePasswordJsonAuthenticationListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/UsernamePasswordJsonAuthenticationListenerTest.php index e5435cb1b2..634d281a7a 100644 --- a/src/Symfony/Component/Security/Http/Tests/Firewall/UsernamePasswordJsonAuthenticationListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Firewall/UsernamePasswordJsonAuthenticationListenerTest.php @@ -24,6 +24,7 @@ use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface; use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface; use Symfony\Component\Security\Http\Firewall\UsernamePasswordJsonAuthenticationListener; +use Symfony\Component\Security\Http\HttpUtils; /** * @author Kévin Dunglas @@ -35,9 +36,15 @@ class UsernamePasswordJsonAuthenticationListenerTest extends TestCase */ private $listener; - private function createListener(array $options = array(), $success = true) + private function createListener(array $options = array(), $success = true, $matchCheckPath = true) { $tokenStorage = $this->getMockBuilder(TokenStorageInterface::class)->getMock(); + $httpUtils = $this->getMockBuilder(HttpUtils::class)->getMock(); + $httpUtils + ->expects($this->any()) + ->method('checkRequestPath') + ->will($this->returnValue($matchCheckPath)) + ; $authenticationManager = $this->getMockBuilder(AuthenticationManagerInterface::class)->getMock(); $authenticatedToken = $this->getMockBuilder(TokenInterface::class)->getMock(); @@ -53,7 +60,7 @@ class UsernamePasswordJsonAuthenticationListenerTest extends TestCase $authenticationFailureHandler = $this->getMockBuilder(AuthenticationFailureHandlerInterface::class)->getMock(); $authenticationFailureHandler->method('onAuthenticationFailure')->willReturn(new Response('ko')); - $this->listener = new UsernamePasswordJsonAuthenticationListener($tokenStorage, $authenticationManager, 'providerKey', $authenticationSuccessHandler, $authenticationFailureHandler, $options); + $this->listener = new UsernamePasswordJsonAuthenticationListener($tokenStorage, $authenticationManager, $httpUtils, 'providerKey', $authenticationSuccessHandler, $authenticationFailureHandler, $options); } public function testHandleSuccess() @@ -136,4 +143,25 @@ class UsernamePasswordJsonAuthenticationListenerTest extends TestCase $this->listener->handle($event); $this->assertSame('ko', $event->getResponse()->getContent()); } + + public function testDoesNotAttemptAuthenticationIfRequestPathDoesNotMatchCheckPath() + { + $this->createListener(array('check_path' => '/'), true, false); + $request = new Request(); + $event = new GetResponseEvent($this->getMockBuilder(KernelInterface::class)->getMock(), $request, KernelInterface::MASTER_REQUEST); + $event->setResponse(new Response('original')); + + $this->listener->handle($event); + $this->assertSame('original', $event->getResponse()->getContent()); + } + + public function testAttemptAuthenticationIfRequestPathMatchesCheckPath() + { + $this->createListener(array('check_path' => '/')); + $request = new Request(array(), array(), array(), array(), array(), array(), '{"username": "dunglas", "password": "foo"}'); + $event = new GetResponseEvent($this->getMockBuilder(KernelInterface::class)->getMock(), $request, KernelInterface::MASTER_REQUEST); + + $this->listener->handle($event); + $this->assertSame('ok', $event->getResponse()->getContent()); + } }