bug #22425 [Security] Allow to set a check_path on json_login listener (chalasr)

This PR was squashed before being merged into the 3.3-dev branch (closes #22425).

Discussion
----------

[Security] Allow to set a check_path on json_login listener

| Q             | A
| ------------- | ---
| Branch?       | 3.3
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no, master only
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #21948, ~~#22423~~
| License       | MIT
| Doc PR        | n/a

The listener should allow to restrict authentication to a given check_path, as stated in the docs http://symfony.com/doc/master/security/json_login_setup.html

Commits
-------

9f7eb618a4 [Security] Allow to set a check_path on json_login listener
This commit is contained in:
Fabien Potencier 2017-04-18 16:20:19 -06:00
commit 6c7bceda81
6 changed files with 50 additions and 8 deletions

View File

@ -19,6 +19,8 @@ use Symfony\Component\DependencyInjection\Reference;
* JsonLoginFactory creates services for JSON login authentication.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*
* @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);

View File

@ -147,6 +147,7 @@
<tag name="monolog.logger" channel="security" />
<argument type="service" id="security.token_storage" />
<argument type="service" id="security.authentication.manager" />
<argument type="service" id="security.http_utils" />
<argument /> <!-- Provider-shared Key -->
<argument type="service" id="security.authentication.success_handler" />
<argument type="service" id="security.authentication.failure_handler" />

View File

@ -16,7 +16,7 @@ security:
pattern: ^/
anonymous: true
json_login:
check_path: /mychk
check_path: /chk
username_path: user.login
password_path: user.password

View File

@ -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
-----

View File

@ -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 <dunglas@gmail.com>
*
* @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 {

View File

@ -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 <dunglas@gmail.com>
@ -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());
}
}