diff options
Diffstat (limited to 'Http')
-rw-r--r-- | Http/Authentication/DefaultAuthenticationSuccessHandler.php | 7 | ||||
-rw-r--r-- | Http/Firewall/ContextListener.php | 9 | ||||
-rw-r--r-- | Http/Firewall/ExceptionListener.php | 15 | ||||
-rw-r--r-- | Http/SecurityEvents.php | 10 | ||||
-rw-r--r-- | Http/Tests/Firewall/ContextListenerTest.php | 8 | ||||
-rw-r--r-- | Http/Tests/Firewall/ExceptionListenerTest.php | 14 | ||||
-rw-r--r-- | Http/Tests/Util/TargetPathTraitTest.php | 76 | ||||
-rw-r--r-- | Http/Util/TargetPathTrait.php | 58 | ||||
-rw-r--r-- | Http/composer.json | 2 |
9 files changed, 184 insertions, 15 deletions
diff --git a/Http/Authentication/DefaultAuthenticationSuccessHandler.php b/Http/Authentication/DefaultAuthenticationSuccessHandler.php index 078a366..38690c7 100644 --- a/Http/Authentication/DefaultAuthenticationSuccessHandler.php +++ b/Http/Authentication/DefaultAuthenticationSuccessHandler.php @@ -13,6 +13,7 @@ namespace Symfony\Component\Security\Http\Authentication; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Security\Http\Util\TargetPathTrait; use Symfony\Component\Security\Http\HttpUtils; use Symfony\Component\Security\Http\ParameterBagUtils; @@ -25,6 +26,8 @@ use Symfony\Component\Security\Http\ParameterBagUtils; */ class DefaultAuthenticationSuccessHandler implements AuthenticationSuccessHandlerInterface { + use TargetPathTrait; + protected $httpUtils; protected $options; protected $providerKey; @@ -113,8 +116,8 @@ class DefaultAuthenticationSuccessHandler implements AuthenticationSuccessHandle return $targetUrl; } - if (null !== $this->providerKey && $targetUrl = $request->getSession()->get('_security.'.$this->providerKey.'.target_path')) { - $request->getSession()->remove('_security.'.$this->providerKey.'.target_path'); + if (null !== $this->providerKey && $targetUrl = $this->getTargetPath($request->getSession(), $this->providerKey)) { + $this->removeTargetPath($request->getSession(), $this->providerKey); return $targetUrl; } diff --git a/Http/Firewall/ContextListener.php b/Http/Firewall/ContextListener.php index 9ac37cd..4d6f3f8 100644 --- a/Http/Firewall/ContextListener.php +++ b/Http/Firewall/ContextListener.php @@ -15,7 +15,10 @@ use Psr\Log\LoggerInterface; use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\HttpKernel\Event\FilterResponseEvent; use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver; +use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface; use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; +use Symfony\Component\Security\Core\Authentication\Token\RememberMeToken; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; @@ -39,8 +42,9 @@ class ContextListener implements ListenerInterface private $userProviders; private $dispatcher; private $registered; + private $trustResolver; - public function __construct(TokenStorageInterface $tokenStorage, array $userProviders, $contextKey, LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null) + public function __construct(TokenStorageInterface $tokenStorage, array $userProviders, $contextKey, LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null, AuthenticationTrustResolverInterface $trustResolver = null) { if (empty($contextKey)) { throw new \InvalidArgumentException('$contextKey must not be empty.'); @@ -58,6 +62,7 @@ class ContextListener implements ListenerInterface $this->sessionKey = '_security_'.$contextKey; $this->logger = $logger; $this->dispatcher = $dispatcher; + $this->trustResolver = $trustResolver ?: new AuthenticationTrustResolver(AnonymousToken::class, RememberMeToken::class); } /** @@ -121,7 +126,7 @@ class ContextListener implements ListenerInterface $request = $event->getRequest(); $session = $request->getSession(); - if ((null === $token = $this->tokenStorage->getToken()) || ($token instanceof AnonymousToken)) { + if ((null === $token = $this->tokenStorage->getToken()) || $this->trustResolver->isAnonymous($token)) { if ($request->hasPreviousSession()) { $session->remove($this->sessionKey); } diff --git a/Http/Firewall/ExceptionListener.php b/Http/Firewall/ExceptionListener.php index a1cae2a..98f5ac0 100644 --- a/Http/Firewall/ExceptionListener.php +++ b/Http/Firewall/ExceptionListener.php @@ -22,6 +22,7 @@ use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\Exception\AccessDeniedException; use Symfony\Component\Security\Core\Exception\InsufficientAuthenticationException; use Symfony\Component\Security\Core\Exception\LogoutException; +use Symfony\Component\Security\Http\Util\TargetPathTrait; use Symfony\Component\Security\Http\HttpUtils; use Symfony\Component\HttpFoundation\Request; use Psr\Log\LoggerInterface; @@ -39,6 +40,8 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface; */ class ExceptionListener { + use TargetPathTrait; + private $tokenStorage; private $providerKey; private $accessDeniedHandler; @@ -200,7 +203,15 @@ class ExceptionListener } } - return $this->authenticationEntryPoint->start($request, $authException); + $response = $this->authenticationEntryPoint->start($request, $authException); + + if (!$response instanceof Response) { + $given = is_object($response) ? get_class($response) : gettype($response); + + throw new \LogicException(sprintf('The %s::start() method must return a Response object (%s returned)', get_class($this->authenticationEntryPoint), $given)); + } + + return $response; } /** @@ -210,7 +221,7 @@ class ExceptionListener { // session isn't required when using HTTP basic authentication mechanism for example if ($request->hasSession() && $request->isMethodSafe() && !$request->isXmlHttpRequest()) { - $request->getSession()->set('_security.'.$this->providerKey.'.target_path', $request->getUri()); + $this->saveTargetPath($request->getSession(), $this->providerKey, $request->getUri()); } } } diff --git a/Http/SecurityEvents.php b/Http/SecurityEvents.php index 46c8257..550acb4 100644 --- a/Http/SecurityEvents.php +++ b/Http/SecurityEvents.php @@ -17,10 +17,7 @@ final class SecurityEvents * The INTERACTIVE_LOGIN event occurs after a user is logged in * interactively for authentication based on http, cookies or X509. * - * The event listener method receives a - * Symfony\Component\Security\Http\Event\InteractiveLoginEvent instance. - * - * @Event + * @Event("Symfony\Component\Security\Http\Event\InteractiveLoginEvent") * * @var string */ @@ -30,10 +27,7 @@ final class SecurityEvents * The SWITCH_USER event occurs before switch to another user and * before exit from an already switched user. * - * The event listener method receives a - * Symfony\Component\Security\Http\Event\SwitchUserEvent instance. - * - * @Event + * @Event("Symfony\Component\Security\Http\Event\SwitchUserEvent") * * @var string */ diff --git a/Http/Tests/Firewall/ContextListenerTest.php b/Http/Tests/Firewall/ContextListenerTest.php index ae1199a..0213330 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\AnonymousToken; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Http\Firewall\ContextListener; @@ -85,6 +86,13 @@ class ContextListenerTest extends \PHPUnit_Framework_TestCase $this->assertFalse($session->has('_security_session')); } + public function testOnKernelResponseWillRemoveSessionOnAnonymousToken() + { + $session = $this->runSessionOnKernelResponse(new AnonymousToken('secret', 'anon.'), 'C:10:"serialized"'); + + $this->assertFalse($session->has('_security_session')); + } + public function testOnKernelResponseWithoutSession() { $tokenStorage = new TokenStorage(); diff --git a/Http/Tests/Firewall/ExceptionListenerTest.php b/Http/Tests/Firewall/ExceptionListenerTest.php index 3d409e5..db0a242 100644 --- a/Http/Tests/Firewall/ExceptionListenerTest.php +++ b/Http/Tests/Firewall/ExceptionListenerTest.php @@ -65,6 +65,20 @@ class ExceptionListenerTest extends \PHPUnit_Framework_TestCase ); } + public function testExceptionWhenEntryPointReturnsBadValue() + { + $event = $this->createEvent(new AuthenticationException()); + + $entryPoint = $this->getMock('Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface'); + $entryPoint->expects($this->once())->method('start')->will($this->returnValue('NOT A RESPONSE')); + + $listener = $this->createExceptionListener(null, null, null, $entryPoint); + $listener->onKernelException($event); + // the exception has been replaced by our LogicException + $this->assertInstanceOf('LogicException', $event->getException()); + $this->assertStringEndsWith('start() method must return a Response object (string returned)', $event->getException()->getMessage()); + } + /** * @dataProvider getAccessDeniedExceptionProvider */ diff --git a/Http/Tests/Util/TargetPathTraitTest.php b/Http/Tests/Util/TargetPathTraitTest.php new file mode 100644 index 0000000..b2c4dc7 --- /dev/null +++ b/Http/Tests/Util/TargetPathTraitTest.php @@ -0,0 +1,76 @@ +<?php + +namespace Symfony\Component\Security\Http\Tests\Util; + +use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\Security\Http\Util\TargetPathTrait; + +class TargetPathTraitTest extends \PHPUnit_Framework_TestCase +{ + public function testSetTargetPath() + { + $obj = new TestClassWithTargetPathTrait(); + + $session = $this->getMockBuilder('Symfony\Component\HttpFoundation\Session\SessionInterface') + ->getMock(); + + $session->expects($this->once()) + ->method('set') + ->with('_security.firewall_name.target_path', '/foo'); + + $obj->doSetTargetPath($session, 'firewall_name', '/foo'); + } + + public function testGetTargetPath() + { + $obj = new TestClassWithTargetPathTrait(); + + $session = $this->getMockBuilder('Symfony\Component\HttpFoundation\Session\SessionInterface') + ->getMock(); + + $session->expects($this->once()) + ->method('get') + ->with('_security.cool_firewall.target_path') + ->willReturn('/bar'); + + $actualUri = $obj->doGetTargetPath($session, 'cool_firewall'); + $this->assertEquals( + '/bar', + $actualUri + ); + } + + public function testRemoveTargetPath() + { + $obj = new TestClassWithTargetPathTrait(); + + $session = $this->getMockBuilder('Symfony\Component\HttpFoundation\Session\SessionInterface') + ->getMock(); + + $session->expects($this->once()) + ->method('remove') + ->with('_security.best_firewall.target_path'); + + $obj->doRemoveTargetPath($session, 'best_firewall'); + } +} + +class TestClassWithTargetPathTrait +{ + use TargetPathTrait; + + public function doSetTargetPath(SessionInterface $session, $providerKey, $uri) + { + $this->saveTargetPath($session, $providerKey, $uri); + } + + public function doGetTargetPath(SessionInterface $session, $providerKey) + { + return $this->getTargetPath($session, $providerKey); + } + + public function doRemoveTargetPath(SessionInterface $session, $providerKey) + { + $this->removeTargetPath($session, $providerKey); + } +} diff --git a/Http/Util/TargetPathTrait.php b/Http/Util/TargetPathTrait.php new file mode 100644 index 0000000..986adb0 --- /dev/null +++ b/Http/Util/TargetPathTrait.php @@ -0,0 +1,58 @@ +<?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\Util; + +use Symfony\Component\HttpFoundation\Session\SessionInterface; + +/** + * Trait to get (and set) the URL the user last visited before being forced to authenticate. + */ +trait TargetPathTrait +{ + /** + * Sets the target path the user should be redirected to after authentication. + * + * Usually, you do not need to set this directly. + * + * @param SessionInterface $session + * @param string $providerKey The name of your firewall + * @param string $uri The URI to set as the target path + */ + private function saveTargetPath(SessionInterface $session, $providerKey, $uri) + { + $session->set('_security.'.$providerKey.'.target_path', $uri); + } + + /** + * Returns the URL (if any) the user visited that forced them to login. + * + * @param SessionInterface $session + * @param string $providerKey The name of your firewall + * + * @return string + */ + private function getTargetPath(SessionInterface $session, $providerKey) + { + return $session->get('_security.'.$providerKey.'.target_path'); + } + + /** + * Removes the target path from the session. + * + * @param SessionInterface $session + * @param string $providerKey The name of your firewall + */ + private function removeTargetPath(SessionInterface $session, $providerKey) + { + $session->remove('_security.'.$providerKey.'.target_path'); + } +} diff --git a/Http/composer.json b/Http/composer.json index a3e008a..f19d0e4 100644 --- a/Http/composer.json +++ b/Http/composer.json @@ -43,7 +43,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "3.1-dev" } } } |