diff options
-rw-r--r-- | Acl/README.md | 2 | ||||
-rw-r--r-- | Acl/composer.json | 2 | ||||
-rw-r--r-- | CHANGELOG.md | 5 | ||||
-rw-r--r-- | Core/Authentication/Provider/UserAuthenticationProvider.php | 4 | ||||
-rw-r--r-- | Core/Encoder/UserPasswordEncoder.php | 55 | ||||
-rw-r--r-- | Core/Encoder/UserPasswordEncoderInterface.php | 41 | ||||
-rw-r--r-- | Core/Exception/UsernameNotFoundException.php | 8 | ||||
-rw-r--r-- | Core/README.md | 2 | ||||
-rw-r--r-- | Core/Tests/Encoder/UserPasswordEncoderTest.php | 70 | ||||
-rw-r--r-- | Core/Tests/Exception/UsernameNotFoundExceptionTest.php | 25 | ||||
-rw-r--r-- | Core/Validator/Constraints/UserPassword.php | 2 | ||||
-rw-r--r-- | Core/composer.json | 2 | ||||
-rw-r--r-- | Csrf/README.md | 2 | ||||
-rw-r--r-- | Csrf/composer.json | 2 | ||||
-rw-r--r-- | Http/Authentication/AuthenticationUtils.php | 87 | ||||
-rw-r--r-- | Http/Firewall/ContextListener.php | 2 | ||||
-rw-r--r-- | Http/Firewall/RememberMeListener.php | 9 | ||||
-rw-r--r-- | Http/README.md | 2 | ||||
-rw-r--r-- | Http/Tests/Firewall/RememberMeListenerTest.php | 111 | ||||
-rw-r--r-- | Http/composer.json | 2 | ||||
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | composer.json | 2 |
22 files changed, 417 insertions, 22 deletions
diff --git a/Acl/README.md b/Acl/README.md index 6c009a3..bb2d4d7 100644 --- a/Acl/README.md +++ b/Acl/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/Acl/composer.json b/Acl/composer.json index 5f5787f..fb25b5d 100644 --- a/Acl/composer.json +++ b/Acl/composer.json @@ -36,7 +36,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.5-dev" + "dev-master": "2.6-dev" } } } diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ab61d0..cdc5c44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +2.6.0 +----- + + * added Symfony\Component\Security\Http\Authentication\AuthenticationUtils + 2.4.0 ----- diff --git a/Core/Authentication/Provider/UserAuthenticationProvider.php b/Core/Authentication/Provider/UserAuthenticationProvider.php index 3728c01..4371abf 100644 --- a/Core/Authentication/Provider/UserAuthenticationProvider.php +++ b/Core/Authentication/Provider/UserAuthenticationProvider.php @@ -70,7 +70,7 @@ abstract class UserAuthenticationProvider implements AuthenticationProviderInter $user = $this->retrieveUser($username, $token); } catch (UsernameNotFoundException $notFound) { if ($this->hideUserNotFoundExceptions) { - throw new BadCredentialsException('Bad credentials', 0, $notFound); + throw new BadCredentialsException('Bad credentials.', 0, $notFound); } $notFound->setUsername($username); @@ -87,7 +87,7 @@ abstract class UserAuthenticationProvider implements AuthenticationProviderInter $this->userChecker->checkPostAuth($user); } catch (BadCredentialsException $e) { if ($this->hideUserNotFoundExceptions) { - throw new BadCredentialsException('Bad credentials', 0, $e); + throw new BadCredentialsException('Bad credentials.', 0, $e); } throw $e; diff --git a/Core/Encoder/UserPasswordEncoder.php b/Core/Encoder/UserPasswordEncoder.php new file mode 100644 index 0000000..13ee835 --- /dev/null +++ b/Core/Encoder/UserPasswordEncoder.php @@ -0,0 +1,55 @@ +<?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\Core\Encoder; + +use Symfony\Component\Security\Core\User\UserInterface; + +/** + * A generic password encoder + * + * @author Ariel Ferrandini <arielferrandini@gmail.com> + */ +class UserPasswordEncoder implements UserPasswordEncoderInterface +{ + /** + * @var EncoderFactoryInterface + */ + private $encoderFactory; + + /** + * @param EncoderFactoryInterface $encoderFactory The encoder factory + */ + public function __construct(EncoderFactoryInterface $encoderFactory) + { + $this->encoderFactory = $encoderFactory; + } + + /** + * {@inheritdoc} + */ + public function encodePassword(UserInterface $user, $plainPassword) + { + $encoder = $this->encoderFactory->getEncoder($user); + + return $encoder->encodePassword($plainPassword, $user->getSalt()); + } + + /** + * {@inheritdoc} + */ + public function isPasswordValid(UserInterface $user, $raw) + { + $encoder = $this->encoderFactory->getEncoder($user); + + return $encoder->isPasswordValid($user->getPassword(), $raw, $user->getSalt()); + } +} diff --git a/Core/Encoder/UserPasswordEncoderInterface.php b/Core/Encoder/UserPasswordEncoderInterface.php new file mode 100644 index 0000000..39e906a --- /dev/null +++ b/Core/Encoder/UserPasswordEncoderInterface.php @@ -0,0 +1,41 @@ +<?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\Core\Encoder; + +use Symfony\Component\Security\Core\User\UserInterface; + +/** + * UserPasswordEncoderInterface is the interface for the password encoder service. + * + * @author Ariel Ferrandini <arielferrandini@gmail.com> + */ +interface UserPasswordEncoderInterface +{ + /** + * + * Encodes the plain password. + * + * @param UserInterface $user The user + * @param string $plainPassword The password to encode + * + * @return string The encoded password + */ + public function encodePassword(UserInterface $user, $plainPassword); + + /** + * @param UserInterface $user The user + * @param string $raw A raw password + * + * @return bool true if the password is valid, false otherwise + */ + public function isPasswordValid(UserInterface $user, $raw); +} diff --git a/Core/Exception/UsernameNotFoundException.php b/Core/Exception/UsernameNotFoundException.php index 11607d3..6979389 100644 --- a/Core/Exception/UsernameNotFoundException.php +++ b/Core/Exception/UsernameNotFoundException.php @@ -69,4 +69,12 @@ class UsernameNotFoundException extends AuthenticationException parent::unserialize($parentData); } + + /** + * {@inheritdoc} + */ + public function getMessageData() + { + return array('{{ username }}' => $this->username); + } } diff --git a/Core/README.md b/Core/README.md index 4585a5d..66c323e 100644 --- a/Core/README.md +++ b/Core/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/Core/Tests/Encoder/UserPasswordEncoderTest.php b/Core/Tests/Encoder/UserPasswordEncoderTest.php new file mode 100644 index 0000000..590652d --- /dev/null +++ b/Core/Tests/Encoder/UserPasswordEncoderTest.php @@ -0,0 +1,70 @@ +<?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\Core\Tests\Encoder; + +use Symfony\Component\Security\Core\Encoder\UserPasswordEncoder; + +class UserPasswordEncoderTest extends \PHPUnit_Framework_TestCase +{ + public function testEncodePassword() + { + $userMock = $this->getMock('Symfony\Component\Security\Core\User\UserInterface'); + $userMock->expects($this->any()) + ->method('getSalt') + ->will($this->returnValue('userSalt')); + + $mockEncoder = $this->getMock('Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface'); + $mockEncoder->expects($this->any()) + ->method('encodePassword') + ->with($this->equalTo('plainPassword'), $this->equalTo('userSalt')) + ->will($this->returnValue('encodedPassword')); + + $mockEncoderFactory = $this->getMock('Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface'); + $mockEncoderFactory->expects($this->any()) + ->method('getEncoder') + ->with($this->equalTo($userMock)) + ->will($this->returnValue($mockEncoder)); + + $passwordEncoder = new UserPasswordEncoder($mockEncoderFactory); + + $encoded = $passwordEncoder->encodePassword($userMock, 'plainPassword'); + $this->assertEquals('encodedPassword', $encoded); + } + + public function testIsPasswordValid() + { + $userMock = $this->getMock('Symfony\Component\Security\Core\User\UserInterface'); + $userMock->expects($this->any()) + ->method('getSalt') + ->will($this->returnValue('userSalt')); + $userMock->expects($this->any()) + ->method('getPassword') + ->will($this->returnValue('encodedPassword')); + + $mockEncoder = $this->getMock('Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface'); + $mockEncoder->expects($this->any()) + ->method('isPasswordValid') + ->with($this->equalTo('encodedPassword'), $this->equalTo('plainPassword'), $this->equalTo('userSalt')) + ->will($this->returnValue(true)); + + $mockEncoderFactory = $this->getMock('Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface'); + $mockEncoderFactory->expects($this->any()) + ->method('getEncoder') + ->with($this->equalTo($userMock)) + ->will($this->returnValue($mockEncoder)); + + $passwordEncoder = new UserPasswordEncoder($mockEncoderFactory); + + $isValid = $passwordEncoder->isPasswordValid($userMock, 'plainPassword'); + $this->assertTrue($isValid); + } +} diff --git a/Core/Tests/Exception/UsernameNotFoundExceptionTest.php b/Core/Tests/Exception/UsernameNotFoundExceptionTest.php new file mode 100644 index 0000000..98ea374 --- /dev/null +++ b/Core/Tests/Exception/UsernameNotFoundExceptionTest.php @@ -0,0 +1,25 @@ +<?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\Core\Tests\Exception; + +use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; + +class UsernameNotFoundExceptionTest extends \PHPUnit_Framework_TestCase +{ + public function testGetMessageData() + { + $exception = new UsernameNotFoundException('Username could not be found.'); + $this->assertEquals(array('{{ username }}' => null), $exception->getMessageData()); + $exception->setUsername('username'); + $this->assertEquals(array('{{ username }}' => 'username'), $exception->getMessageData()); + } +} diff --git a/Core/Validator/Constraints/UserPassword.php b/Core/Validator/Constraints/UserPassword.php index aee4cda..35537b3 100644 --- a/Core/Validator/Constraints/UserPassword.php +++ b/Core/Validator/Constraints/UserPassword.php @@ -19,7 +19,7 @@ use Symfony\Component\Validator\Constraint; */ class UserPassword extends Constraint { - public $message = 'This value should be the user current password.'; + public $message = 'This value should be the user\'s current password.'; public $service = 'security.validator.user_password'; /** diff --git a/Core/composer.json b/Core/composer.json index 249d4c1..54a76dc 100644 --- a/Core/composer.json +++ b/Core/composer.json @@ -40,7 +40,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.5-dev" + "dev-master": "2.6-dev" } } } diff --git a/Csrf/README.md b/Csrf/README.md index 95a1062..89ed66c 100644 --- a/Csrf/README.md +++ b/Csrf/README.md @@ -9,7 +9,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/Csrf/composer.json b/Csrf/composer.json index 398a2d3..4daba5c 100644 --- a/Csrf/composer.json +++ b/Csrf/composer.json @@ -32,7 +32,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.5-dev" + "dev-master": "2.6-dev" } } } diff --git a/Http/Authentication/AuthenticationUtils.php b/Http/Authentication/AuthenticationUtils.php new file mode 100644 index 0000000..ed4b0a1 --- /dev/null +++ b/Http/Authentication/AuthenticationUtils.php @@ -0,0 +1,87 @@ +<?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/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/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/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/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" } } } @@ -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/composer.json b/composer.json index a8a99f5..6d60781 100644 --- a/composer.json +++ b/composer.json @@ -52,7 +52,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.5-dev" + "dev-master": "2.6-dev" } } } |