Skip to content

Commit

Permalink
[Security] Allow to set a check_path on json_login listener
Browse files Browse the repository at this point in the history
  • Loading branch information
chalasr authored and fabpot committed Apr 18, 2017
1 parent 4f0daa7 commit 9f7eb61
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 8 deletions.
Expand Up @@ -19,6 +19,8 @@
* JsonLoginFactory creates services for JSON login authentication.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*
* @experimental in version 3.3
*/
class JsonLoginFactory extends AbstractFactory
{
Expand Down Expand Up @@ -83,10 +85,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);
Expand Down
Expand Up @@ -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" />
Expand Down
Expand Up @@ -16,7 +16,7 @@ security:
pattern: ^/
anonymous: true
json_login:
check_path: /mychk
check_path: /chk
username_path: user.login
password_path: user.password

Expand Down
1 change: 1 addition & 0 deletions src/Symfony/Component/Security/CHANGELOG.md
Expand Up @@ -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
-----
Expand Down
Expand Up @@ -29,18 +29,22 @@
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;

/**
* UsernamePasswordJsonAuthenticationListener is a stateless implementation of
* 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;
Expand All @@ -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;
Expand All @@ -68,6 +73,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 {
Expand Down
Expand Up @@ -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 <dunglas@gmail.com>
Expand All @@ -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();
Expand All @@ -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()
Expand Down Expand Up @@ -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->assertSame('ok', $event->getResponse()->getContent());
}
}

0 comments on commit 9f7eb61

Please sign in to comment.