From 7e6c2613446e833a9be81ebeaf51da337014561f Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Thu, 13 Apr 2017 19:48:35 +0200 Subject: [PATCH 1/2] Add check_path option to json_login listener --- .../Security/Factory/JsonLoginFactory.php | 8 ++--- .../Resources/config/security_listeners.xml | 1 + .../Tests/Functional/app/JsonLogin/config.yml | 2 +- ...namePasswordJsonAuthenticationListener.php | 10 +++++- ...PasswordJsonAuthenticationListenerTest.php | 32 +++++++++++++++++-- 5 files changed, 45 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 2bda0ea9dc271..c1d3bcad34cfc 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/JsonLoginFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/JsonLoginFactory.php @@ -83,10 +83,10 @@ protected function createListener($container, $id, $config, $userProvider) { $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 f6b9cbf811ee3..1eb7687a5db5a 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 8234b21727135..d6ed10e896ff9 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/Http/Firewall/UsernamePasswordJsonAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordJsonAuthenticationListener.php index 8b40119632b36..f66bf9656342b 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\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; /** @@ -41,6 +42,7 @@ class UsernamePasswordJsonAuthenticationListener implements ListenerInterface { private $tokenStorage; private $authenticationManager; + private $httpUtils; private $providerKey; private $successHandler; private $failureHandler; @@ -49,10 +51,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 +71,11 @@ public function __construct(TokenStorageInterface $tokenStorage, AuthenticationM 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 e5435cb1b215e..c687ea5a31768 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\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 @@ private function createListener(array $options = array(), $success = true) $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 @@ public function testAttemptAuthenticationUsernameTooLong() $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->assertEquals('ok', $event->getResponse()->getContent()); + } } From e4533f83e7faa4e54e960837f51b30596af193af Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Thu, 13 Apr 2017 22:25:32 +0200 Subject: [PATCH 2/2] Flag the json_login listener as experimental --- .../DependencyInjection/Security/Factory/JsonLoginFactory.php | 2 ++ src/Symfony/Component/Security/CHANGELOG.md | 1 + .../Firewall/UsernamePasswordJsonAuthenticationListener.php | 2 ++ .../Firewall/UsernamePasswordJsonAuthenticationListenerTest.php | 2 +- 4 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/JsonLoginFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/JsonLoginFactory.php index c1d3bcad34cfc..f7da4aa4a65cc 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 @@ * JsonLoginFactory creates services for JSON login authentication. * * @author Kévin Dunglas + * + * @experimental in version 3.3 */ class JsonLoginFactory extends AbstractFactory { diff --git a/src/Symfony/Component/Security/CHANGELOG.md b/src/Symfony/Component/Security/CHANGELOG.md index afc48f927d3a8..69cdd285282fa 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 f66bf9656342b..2cfd4d9d236e2 100644 --- a/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordJsonAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordJsonAuthenticationListener.php @@ -37,6 +37,8 @@ * 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 { diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/UsernamePasswordJsonAuthenticationListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/UsernamePasswordJsonAuthenticationListenerTest.php index c687ea5a31768..634d281a7ad9c 100644 --- a/src/Symfony/Component/Security/Http/Tests/Firewall/UsernamePasswordJsonAuthenticationListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Firewall/UsernamePasswordJsonAuthenticationListenerTest.php @@ -162,6 +162,6 @@ public function testAttemptAuthenticationIfRequestPathMatchesCheckPath() $event = new GetResponseEvent($this->getMockBuilder(KernelInterface::class)->getMock(), $request, KernelInterface::MASTER_REQUEST); $this->listener->handle($event); - $this->assertEquals('ok', $event->getResponse()->getContent()); + $this->assertSame('ok', $event->getResponse()->getContent()); } }