summaryrefslogtreecommitdiffstats
path: root/Http
diff options
context:
space:
mode:
Diffstat (limited to 'Http')
-rw-r--r--Http/Authentication/AuthenticationUtils.php86
-rw-r--r--Http/Firewall/AnonymousAuthenticationListener.php25
-rw-r--r--Http/Firewall/ContextListener.php2
-rw-r--r--Http/Firewall/RememberMeListener.php9
-rw-r--r--Http/Firewall/RemoteUserAuthenticationListener.php49
-rw-r--r--Http/README.md2
-rw-r--r--Http/Tests/Firewall/AnonymousAuthenticationListenerTest.php34
-rw-r--r--Http/Tests/Firewall/ContextListenerTest.php5
-rw-r--r--Http/Tests/Firewall/RememberMeListenerTest.php111
-rw-r--r--Http/Tests/Firewall/RemoteUserAuthenticationListenerTest.php91
-rw-r--r--Http/composer.json2
11 files changed, 389 insertions, 27 deletions
diff --git a/Http/Authentication/AuthenticationUtils.php b/Http/Authentication/AuthenticationUtils.php
new file mode 100644
index 0000000..03f5e44
--- /dev/null
+++ b/Http/Authentication/AuthenticationUtils.php
@@ -0,0 +1,86 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Security\Http\Authentication;
+
+use Symfony\Component\HttpFoundation\RequestStack;
+use Symfony\Component\Security\Core\Exception\AuthenticationException;
+use Symfony\Component\Security\Core\SecurityContextInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Extracts Security Errors from Request
+ *
+ * @author Boris Vujicic <boris.vujicic@gmail.com>
+ */
+class AuthenticationUtils
+{
+ /**
+ * @var RequestStack
+ */
+ private $requestStack;
+
+ /**
+ * @param RequestStack $requestStack
+ */
+ public function __construct(RequestStack $requestStack)
+ {
+ $this->requestStack = $requestStack;
+ }
+
+ /**
+ * @param bool $clearSession
+ * @return null|AuthenticationException
+ */
+ public function getLastAuthenticationError($clearSession = true)
+ {
+ $request = $this->getRequest();
+ $session = $request->getSession();
+ $authenticationException = null;
+
+ if ($request->attributes->has(SecurityContextInterface::AUTHENTICATION_ERROR)) {
+ $authenticationException = $request->attributes->get(SecurityContextInterface::AUTHENTICATION_ERROR);
+ } elseif ($session !== null && $session->has(SecurityContextInterface::AUTHENTICATION_ERROR)) {
+ $authenticationException = $session->get(SecurityContextInterface::AUTHENTICATION_ERROR);
+
+ if ($clearSession) {
+ $session->remove(SecurityContextInterface::AUTHENTICATION_ERROR);
+ }
+ }
+
+ return $authenticationException;
+ }
+
+ /**
+ * @return string
+ */
+ public function getLastUsername()
+ {
+ $session = $this->getRequest()->getSession();
+
+ return null === $session ? '' : $session->get(SecurityContextInterface::LAST_USERNAME);
+ }
+
+ /**
+ * @return Request
+ * @throws \LogicException
+ */
+ private function getRequest()
+ {
+ $request = $this->requestStack->getCurrentRequest();
+
+ if (null === $request) {
+ throw new \LogicException('Request should exist so it can be processed for error.');
+ }
+
+ return $request;
+ }
+}
diff --git a/Http/Firewall/AnonymousAuthenticationListener.php b/Http/Firewall/AnonymousAuthenticationListener.php
index 59f05ff..986c9a8 100644
--- a/Http/Firewall/AnonymousAuthenticationListener.php
+++ b/Http/Firewall/AnonymousAuthenticationListener.php
@@ -11,6 +11,8 @@
namespace Symfony\Component\Security\Http\Firewall;
+use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
+use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
@@ -26,13 +28,15 @@ class AnonymousAuthenticationListener implements ListenerInterface
{
private $context;
private $key;
+ private $authenticationManager;
private $logger;
- public function __construct(SecurityContextInterface $context, $key, LoggerInterface $logger = null)
+ public function __construct(SecurityContextInterface $context, $key, AuthenticationManagerInterface $authenticationManager, LoggerInterface $logger = null)
{
- $this->context = $context;
- $this->key = $key;
- $this->logger = $logger;
+ $this->context = $context;
+ $this->key = $key;
+ $this->authenticationManager = $authenticationManager;
+ $this->logger = $logger;
}
/**
@@ -46,10 +50,17 @@ class AnonymousAuthenticationListener implements ListenerInterface
return;
}
- $this->context->setToken(new AnonymousToken($this->key, 'anon.', array()));
+ try {
+ $token = $this->authenticationManager->authenticate(new AnonymousToken($this->key, 'anon.', array()));
+ $this->context->setToken($token);
- if (null !== $this->logger) {
- $this->logger->info('Populated SecurityContext with an anonymous Token');
+ if (null !== $this->logger) {
+ $this->logger->info('Populated SecurityContext with an anonymous Token');
+ }
+ } catch (AuthenticationException $failed) {
+ if (null !== $this->logger) {
+ $this->logger->info(sprintf('Anonymous authentication failed: %s', $failed->getMessage()));
+ }
}
}
}
diff --git a/Http/Firewall/ContextListener.php b/Http/Firewall/ContextListener.php
index 435c440..e61907e 100644
--- a/Http/Firewall/ContextListener.php
+++ b/Http/Firewall/ContextListener.php
@@ -142,7 +142,7 @@ class ContextListener implements ListenerInterface
*
* @throws \RuntimeException
*/
- private function refreshUser(TokenInterface $token)
+ protected function refreshUser(TokenInterface $token)
{
$user = $token->getUser();
if (!$user instanceof UserInterface) {
diff --git a/Http/Firewall/RememberMeListener.php b/Http/Firewall/RememberMeListener.php
index 6ca3842..44000d3 100644
--- a/Http/Firewall/RememberMeListener.php
+++ b/Http/Firewall/RememberMeListener.php
@@ -33,6 +33,7 @@ class RememberMeListener implements ListenerInterface
private $authenticationManager;
private $logger;
private $dispatcher;
+ private $catchExceptions = true;
/**
* Constructor.
@@ -42,14 +43,16 @@ class RememberMeListener implements ListenerInterface
* @param AuthenticationManagerInterface $authenticationManager
* @param LoggerInterface $logger
* @param EventDispatcherInterface $dispatcher
+ * @param bool $catchExceptions
*/
- public function __construct(SecurityContextInterface $securityContext, RememberMeServicesInterface $rememberMeServices, AuthenticationManagerInterface $authenticationManager, LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null)
+ public function __construct(SecurityContextInterface $securityContext, RememberMeServicesInterface $rememberMeServices, AuthenticationManagerInterface $authenticationManager, LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null, $catchExceptions = true)
{
$this->securityContext = $securityContext;
$this->rememberMeServices = $rememberMeServices;
$this->authenticationManager = $authenticationManager;
$this->logger = $logger;
$this->dispatcher = $dispatcher;
+ $this->catchExceptions = $catchExceptions;
}
/**
@@ -90,6 +93,10 @@ class RememberMeListener implements ListenerInterface
}
$this->rememberMeServices->loginFail($request);
+
+ if (!$this->catchExceptions) {
+ throw $failed;
+ }
}
}
}
diff --git a/Http/Firewall/RemoteUserAuthenticationListener.php b/Http/Firewall/RemoteUserAuthenticationListener.php
new file mode 100644
index 0000000..f190a17
--- /dev/null
+++ b/Http/Firewall/RemoteUserAuthenticationListener.php
@@ -0,0 +1,49 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Security\Http\Firewall;
+
+use Symfony\Component\Security\Core\SecurityContextInterface;
+use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Security\Core\Exception\BadCredentialsException;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+
+/**
+ * REMOTE_USER authentication listener.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Maxime Douailin <maxime.douailin@gmail.com>
+ */
+class RemoteUserAuthenticationListener extends AbstractPreAuthenticatedListener
+{
+ private $userKey;
+
+ public function __construct(SecurityContextInterface $securityContext, AuthenticationManagerInterface $authenticationManager, $providerKey, $userKey = 'REMOTE_USER', LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null)
+ {
+ parent::__construct($securityContext, $authenticationManager, $providerKey, $logger, $dispatcher);
+
+ $this->userKey = $userKey;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getPreAuthenticatedData(Request $request)
+ {
+ if (!$request->server->has($this->userKey)) {
+ throw new BadCredentialsException(sprintf('User key was not found: %s', $this->userKey));
+ }
+
+ return array($request->server->get($this->userKey), null);
+ }
+}
diff --git a/Http/README.md b/Http/README.md
index c0760d4..e19af42 100644
--- a/Http/README.md
+++ b/Http/README.md
@@ -11,7 +11,7 @@ Resources
Documentation:
-http://symfony.com/doc/2.5/book/security.html
+http://symfony.com/doc/2.6/book/security.html
Tests
-----
diff --git a/Http/Tests/Firewall/AnonymousAuthenticationListenerTest.php b/Http/Tests/Firewall/AnonymousAuthenticationListenerTest.php
index 1fb7350..e6bab4e 100644
--- a/Http/Tests/Firewall/AnonymousAuthenticationListenerTest.php
+++ b/Http/Tests/Firewall/AnonymousAuthenticationListenerTest.php
@@ -11,6 +11,7 @@
namespace Symfony\Component\Security\Http\Tests\Firewall;
+use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken;
use Symfony\Component\Security\Http\Firewall\AnonymousAuthenticationListener;
class AnonymousAuthenticationListenerTest extends \PHPUnit_Framework_TestCase
@@ -28,7 +29,13 @@ class AnonymousAuthenticationListenerTest extends \PHPUnit_Framework_TestCase
->method('setToken')
;
- $listener = new AnonymousAuthenticationListener($context, 'TheKey');
+ $authenticationManager = $this->getMock('Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface');
+ $authenticationManager
+ ->expects($this->never())
+ ->method('authenticate')
+ ;
+
+ $listener = new AnonymousAuthenticationListener($context, 'TheKey', $authenticationManager);
$listener->handle($this->getMock('Symfony\Component\HttpKernel\Event\GetResponseEvent', array(), array(), '', false));
}
@@ -40,16 +47,27 @@ class AnonymousAuthenticationListenerTest extends \PHPUnit_Framework_TestCase
->method('getToken')
->will($this->returnValue(null))
;
- $context
+
+ $anonymousToken = new AnonymousToken('TheKey', 'anon.', array());
+
+ $authenticationManager = $this->getMock('Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface');
+ $authenticationManager
->expects($this->once())
- ->method('setToken')
+ ->method('authenticate')
->with(self::logicalAnd(
- $this->isInstanceOf('Symfony\Component\Security\Core\Authentication\Token\AnonymousToken'),
- $this->attributeEqualTo('key', 'TheKey')
+ $this->isInstanceOf('Symfony\Component\Security\Core\Authentication\Token\AnonymousToken'),
+ $this->attributeEqualTo('key', 'TheKey')
))
+ ->will($this->returnValue($anonymousToken))
;
- $listener = new AnonymousAuthenticationListener($context, 'TheKey');
+ $context
+ ->expects($this->once())
+ ->method('setToken')
+ ->with($anonymousToken)
+ ;
+
+ $listener = new AnonymousAuthenticationListener($context, 'TheKey', $authenticationManager);
$listener->handle($this->getMock('Symfony\Component\HttpKernel\Event\GetResponseEvent', array(), array(), '', false));
}
@@ -66,7 +84,9 @@ class AnonymousAuthenticationListenerTest extends \PHPUnit_Framework_TestCase
->with('Populated SecurityContext with an anonymous Token')
;
- $listener = new AnonymousAuthenticationListener($context, 'TheKey', $logger);
+ $authenticationManager = $this->getMock('Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface');
+
+ $listener = new AnonymousAuthenticationListener($context, 'TheKey', $authenticationManager, $logger);
$listener->handle($this->getMock('Symfony\Component\HttpKernel\Event\GetResponseEvent', array(), array(), '', false));
}
}
diff --git a/Http/Tests/Firewall/ContextListenerTest.php b/Http/Tests/Firewall/ContextListenerTest.php
index d6bc5b4..90af07e 100644
--- a/Http/Tests/Firewall/ContextListenerTest.php
+++ b/Http/Tests/Firewall/ContextListenerTest.php
@@ -18,6 +18,7 @@ use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\KernelEvents;
+use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\SecurityContext;
use Symfony\Component\Security\Http\Firewall\ContextListener;
@@ -27,8 +28,8 @@ class ContextListenerTest extends \PHPUnit_Framework_TestCase
protected function setUp()
{
$this->securityContext = new SecurityContext(
- $this->getMock('Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface'),
- $this->getMock('Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface')
+ new TokenStorage(),
+ $this->getMock('Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface')
);
}
diff --git a/Http/Tests/Firewall/RememberMeListenerTest.php b/Http/Tests/Firewall/RememberMeListenerTest.php
index 9506692..68dfc14 100644
--- a/Http/Tests/Firewall/RememberMeListenerTest.php
+++ b/Http/Tests/Firewall/RememberMeListenerTest.php
@@ -14,12 +14,13 @@ namespace Symfony\Component\Security\Http\Tests\Firewall;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Firewall\RememberMeListener;
use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Security\Http\SecurityEvents;
class RememberMeListenerTest extends \PHPUnit_Framework_TestCase
{
public function testOnCoreSecurityDoesNotTryToPopulateNonEmptySecurityContext()
{
- list($listener, $context, $service,,) = $this->getListener();
+ list($listener, $context,,,,) = $this->getListener();
$context
->expects($this->once())
@@ -99,6 +100,48 @@ class RememberMeListenerTest extends \PHPUnit_Framework_TestCase
$listener->handle($event);
}
+ /**
+ * @expectedException Symfony\Component\Security\Core\Exception\AuthenticationException
+ * @expectedExceptionMessage Authentication failed.
+ */
+ public function testOnCoreSecurityIgnoresAuthenticationOptionallyRethrowsExceptionThrownAuthenticationManagerImplementation()
+ {
+ list($listener, $context, $service, $manager,) = $this->getListener(false, false);
+
+ $context
+ ->expects($this->once())
+ ->method('getToken')
+ ->will($this->returnValue(null))
+ ;
+
+ $service
+ ->expects($this->once())
+ ->method('autoLogin')
+ ->will($this->returnValue($this->getMock('Symfony\Component\Security\Core\Authentication\Token\TokenInterface')))
+ ;
+
+ $service
+ ->expects($this->once())
+ ->method('loginFail')
+ ;
+
+ $exception = new AuthenticationException('Authentication failed.');
+ $manager
+ ->expects($this->once())
+ ->method('authenticate')
+ ->will($this->throwException($exception))
+ ;
+
+ $event = $this->getGetResponseEvent();
+ $event
+ ->expects($this->once())
+ ->method('getRequest')
+ ->will($this->returnValue(new Request()))
+ ;
+
+ $listener->handle($event);
+ }
+
public function testOnCoreSecurity()
{
list($listener, $context, $service, $manager,) = $this->getListener();
@@ -138,6 +181,55 @@ class RememberMeListenerTest extends \PHPUnit_Framework_TestCase
$listener->handle($event);
}
+ public function testOnCoreSecurityInteractiveLoginEventIsDispatchedIfDispatcherIsPresent()
+ {
+ list($listener, $context, $service, $manager,, $dispatcher) = $this->getListener(true);
+
+ $context
+ ->expects($this->once())
+ ->method('getToken')
+ ->will($this->returnValue(null))
+ ;
+
+ $token = $this->getMock('Symfony\Component\Security\Core\Authentication\Token\TokenInterface');
+ $service
+ ->expects($this->once())
+ ->method('autoLogin')
+ ->will($this->returnValue($token))
+ ;
+
+ $context
+ ->expects($this->once())
+ ->method('setToken')
+ ->with($this->equalTo($token))
+ ;
+
+ $manager
+ ->expects($this->once())
+ ->method('authenticate')
+ ->will($this->returnValue($token))
+ ;
+
+ $event = $this->getGetResponseEvent();
+ $request = new Request();
+ $event
+ ->expects($this->once())
+ ->method('getRequest')
+ ->will($this->returnValue($request))
+ ;
+
+ $dispatcher
+ ->expects($this->once())
+ ->method('dispatch')
+ ->with(
+ SecurityEvents::INTERACTIVE_LOGIN,
+ $this->isInstanceOf('Symfony\Component\Security\Http\Event\InteractiveLoginEvent')
+ )
+ ;
+
+ $listener->handle($event);
+ }
+
protected function getGetResponseEvent()
{
return $this->getMock('Symfony\Component\HttpKernel\Event\GetResponseEvent', array(), array(), '', false);
@@ -148,16 +240,18 @@ class RememberMeListenerTest extends \PHPUnit_Framework_TestCase
return $this->getMock('Symfony\Component\HttpKernel\Event\FilterResponseEvent', array(), array(), '', false);
}
- protected function getListener()
+ protected function getListener($withDispatcher = false, $catchExceptions = true)
{
$listener = new RememberMeListener(
$context = $this->getContext(),
$service = $this->getService(),
$manager = $this->getManager(),
- $logger = $this->getLogger()
+ $logger = $this->getLogger(),
+ $dispatcher = ($withDispatcher ? $this->getDispatcher() : null),
+ $catchExceptions
);
- return array($listener, $context, $service, $manager, $logger);
+ return array($listener, $context, $service, $manager, $logger, $dispatcher);
}
protected function getLogger()
@@ -177,8 +271,11 @@ class RememberMeListenerTest extends \PHPUnit_Framework_TestCase
protected function getContext()
{
- return $this->getMockBuilder('Symfony\Component\Security\Core\SecurityContext')
- ->disableOriginalConstructor()
- ->getMock();
+ return $this->getMock('Symfony\Component\Security\Core\SecurityContextInterface');
+ }
+
+ protected function getDispatcher()
+ {
+ return $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
}
}
diff --git a/Http/Tests/Firewall/RemoteUserAuthenticationListenerTest.php b/Http/Tests/Firewall/RemoteUserAuthenticationListenerTest.php
new file mode 100644
index 0000000..2bc1ad6
--- /dev/null
+++ b/Http/Tests/Firewall/RemoteUserAuthenticationListenerTest.php
@@ -0,0 +1,91 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Security\Http\Tests\Firewall;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Security\Http\Firewall\RemoteUserAuthenticationListener;
+
+class RemoteUserAuthenticationListenerTest extends \PHPUnit_Framework_TestCase
+{
+ public function testGetPreAuthenticatedData()
+ {
+ $serverVars = array(
+ 'REMOTE_USER' => 'TheUser'
+ );
+
+ $request = new Request(array(), array(), array(), array(), array(), $serverVars);
+
+ $context = $this->getMock('Symfony\Component\Security\Core\SecurityContextInterface');
+
+ $authenticationManager = $this->getMock('Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface');
+
+ $listener = new RemoteUserAuthenticationListener(
+ $context,
+ $authenticationManager,
+ 'TheProviderKey'
+ );
+
+ $method = new \ReflectionMethod($listener, 'getPreAuthenticatedData');
+ $method->setAccessible(true);
+
+ $result = $method->invokeArgs($listener, array($request));
+ $this->assertSame($result, array('TheUser', null));
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Security\Core\Exception\BadCredentialsException
+ */
+ public function testGetPreAuthenticatedDataNoUser()
+ {
+ $request = new Request(array(), array(), array(), array(), array(), array());
+
+ $context = $this->getMock('Symfony\Component\Security\Core\SecurityContextInterface');
+
+ $authenticationManager = $this->getMock('Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface');
+
+ $listener = new RemoteUserAuthenticationListener(
+ $context,
+ $authenticationManager,
+ 'TheProviderKey'
+ );
+
+ $method = new \ReflectionMethod($listener, 'getPreAuthenticatedData');
+ $method->setAccessible(true);
+
+ $result = $method->invokeArgs($listener, array($request));
+ }
+
+ public function testGetPreAuthenticatedDataWithDifferentKeys()
+ {
+ $userCredentials = array('TheUser', null);
+
+ $request = new Request(array(), array(), array(), array(), array(), array(
+ 'TheUserKey' => 'TheUser'
+ ));
+ $context = $this->getMock('Symfony\Component\Security\Core\SecurityContextInterface');
+
+ $authenticationManager = $this->getMock('Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface');
+
+ $listener = new RemoteUserAuthenticationListener(
+ $context,
+ $authenticationManager,
+ 'TheProviderKey',
+ 'TheUserKey'
+ );
+
+ $method = new \ReflectionMethod($listener, 'getPreAuthenticatedData');
+ $method->setAccessible(true);
+
+ $result = $method->invokeArgs($listener, array($request));
+ $this->assertSame($result, $userCredentials);
+ }
+}
diff --git a/Http/composer.json b/Http/composer.json
index c544ad1..8129523 100644
--- a/Http/composer.json
+++ b/Http/composer.json
@@ -38,7 +38,7 @@
"minimum-stability": "dev",
"extra": {
"branch-alias": {
- "dev-master": "2.5-dev"
+ "dev-master": "2.6-dev"
}
}
}