diff options
Diffstat (limited to 'Http/Firewall')
-rw-r--r-- | Http/Firewall/AbstractAuthenticationListener.php | 12 | ||||
-rw-r--r-- | Http/Firewall/AccessListener.php | 4 | ||||
-rw-r--r-- | Http/Firewall/ChannelListener.php | 4 | ||||
-rw-r--r-- | Http/Firewall/ContextListener.php | 21 | ||||
-rw-r--r-- | Http/Firewall/ExceptionListener.php | 17 | ||||
-rw-r--r-- | Http/Firewall/LogoutListener.php | 68 | ||||
-rw-r--r-- | Http/Firewall/RememberMeListener.php | 18 | ||||
-rw-r--r-- | Http/Firewall/SwitchUserListener.php | 16 |
8 files changed, 108 insertions, 52 deletions
diff --git a/Http/Firewall/AbstractAuthenticationListener.php b/Http/Firewall/AbstractAuthenticationListener.php index 9452e29..6d95314 100644 --- a/Http/Firewall/AbstractAuthenticationListener.php +++ b/Http/Firewall/AbstractAuthenticationListener.php @@ -133,7 +133,7 @@ abstract class AbstractAuthenticationListener implements ListenerInterface try { if (!$request->hasPreviousSession()) { - throw new SessionUnavailableException('Your session has timed-out, or you have disabled cookies.'); + throw new SessionUnavailableException('Your session has timed out, or you have disabled cookies.'); } if (null === $returnValue = $this->attemptAuthentication($request)) { @@ -192,7 +192,9 @@ abstract class AbstractAuthenticationListener implements ListenerInterface $this->securityContext->setToken(null); if (null !== $this->failureHandler) { - return $this->failureHandler->onAuthenticationFailure($request, $failed); + if (null !== $response = $this->failureHandler->onAuthenticationFailure($request, $failed)) { + return $response; + } } if (null === $this->options['failure_path']) { @@ -236,9 +238,11 @@ abstract class AbstractAuthenticationListener implements ListenerInterface $this->dispatcher->dispatch(SecurityEvents::INTERACTIVE_LOGIN, $loginEvent); } + $response = null; if (null !== $this->successHandler) { $response = $this->successHandler->onAuthenticationSuccess($request, $token); - } else { + } + if (null === $response) { $response = $this->httpUtils->createRedirectResponse($request, $this->determineTargetUrl($request)); } @@ -273,7 +277,7 @@ abstract class AbstractAuthenticationListener implements ListenerInterface return $targetUrl; } - if ($this->options['use_referer'] && $targetUrl = $request->headers->get('Referer')) { + if ($this->options['use_referer'] && ($targetUrl = $request->headers->get('Referer')) && $targetUrl !== $request->getUriForPath($this->options['login_path'])) { return $targetUrl; } diff --git a/Http/Firewall/AccessListener.php b/Http/Firewall/AccessListener.php index 877b6c3..3e2d3a5 100644 --- a/Http/Firewall/AccessListener.php +++ b/Http/Firewall/AccessListener.php @@ -13,7 +13,7 @@ namespace Symfony\Component\Security\Http\Firewall; use Symfony\Component\Security\Core\SecurityContextInterface; use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface; -use Symfony\Component\Security\Http\AccessMap; +use Symfony\Component\Security\Http\AccessMapInterface; use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use Symfony\Component\HttpKernel\Log\LoggerInterface; use Symfony\Component\HttpKernel\Event\GetResponseEvent; @@ -33,7 +33,7 @@ class AccessListener implements ListenerInterface private $authManager; private $logger; - public function __construct(SecurityContextInterface $context, AccessDecisionManagerInterface $accessDecisionManager, AccessMap $map, AuthenticationManagerInterface $authManager, LoggerInterface $logger = null) + public function __construct(SecurityContextInterface $context, AccessDecisionManagerInterface $accessDecisionManager, AccessMapInterface $map, AuthenticationManagerInterface $authManager, LoggerInterface $logger = null) { $this->context = $context; $this->accessDecisionManager = $accessDecisionManager; diff --git a/Http/Firewall/ChannelListener.php b/Http/Firewall/ChannelListener.php index 847753f..9b0f8c6 100644 --- a/Http/Firewall/ChannelListener.php +++ b/Http/Firewall/ChannelListener.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Security\Http\Firewall; -use Symfony\Component\Security\Http\AccessMap; +use Symfony\Component\Security\Http\AccessMapInterface; use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface; use Symfony\Component\HttpKernel\Log\LoggerInterface; use Symfony\Component\HttpKernel\Event\GetResponseEvent; @@ -28,7 +28,7 @@ class ChannelListener implements ListenerInterface private $authenticationEntryPoint; private $logger; - public function __construct(AccessMap $map, AuthenticationEntryPointInterface $authenticationEntryPoint, LoggerInterface $logger = null) + public function __construct(AccessMapInterface $map, AuthenticationEntryPointInterface $authenticationEntryPoint, LoggerInterface $logger = null) { $this->map = $map; $this->authenticationEntryPoint = $authenticationEntryPoint; diff --git a/Http/Firewall/ContextListener.php b/Http/Firewall/ContextListener.php index 52dea56..423ccb2 100644 --- a/Http/Firewall/ContextListener.php +++ b/Http/Firewall/ContextListener.php @@ -22,6 +22,7 @@ use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; use Symfony\Component\Security\Core\Exception\UnsupportedUserException; use Symfony\Component\Security\Core\SecurityContextInterface; use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; /** @@ -43,6 +44,12 @@ class ContextListener implements ListenerInterface throw new \InvalidArgumentException('$contextKey must not be empty.'); } + foreach ($userProviders as $userProvider) { + if (!$userProvider instanceof UserProviderInterface) { + throw new \InvalidArgumentException(sprintf('User provider "%s" must implement "Symfony\Component\Security\Core\User\UserProviderInterface".', get_class($userProvider))); + } + } + $this->context = $context; $this->userProviders = $userProviders; $this->contextKey = $contextKey; @@ -96,19 +103,19 @@ class ContextListener implements ListenerInterface return; } - if (null === $token = $this->context->getToken()) { - return; + if (null !== $this->logger) { + $this->logger->debug('Write SecurityContext in the session'); } - if (null === $token || $token instanceof AnonymousToken) { + if (null === $session = $event->getRequest()->getSession()) { return; } - if (null !== $this->logger) { - $this->logger->debug('Write SecurityContext in the session'); + if ((null === $token = $this->context->getToken()) || ($token instanceof AnonymousToken)) { + $session->remove('_security_'.$this->contextKey); + } else { + $session->set('_security_'.$this->contextKey, serialize($token)); } - - $event->getRequest()->getSession()->set('_security_'.$this->contextKey, serialize($token)); } /** diff --git a/Http/Firewall/ExceptionListener.php b/Http/Firewall/ExceptionListener.php index f61df81..05e7d14 100644 --- a/Http/Firewall/ExceptionListener.php +++ b/Http/Firewall/ExceptionListener.php @@ -15,12 +15,12 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Security\Http\Authorization\AccessDeniedHandlerInterface; use Symfony\Component\Security\Core\SecurityContextInterface; use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface; -use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface; use Symfony\Component\Security\Core\Exception\AccountStatusException; 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\HttpUtils; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Log\LoggerInterface; @@ -98,7 +98,7 @@ class ExceptionListener $token = $this->context->getToken(); if (!$this->authenticationTrustResolver->isFullFledged($token)) { if (null !== $this->logger) { - $this->logger->debug('Access denied (user is not fully authenticated); redirecting to authentication entry point'); + $this->logger->debug(sprintf('Access is denied (user is not fully authenticated) by "%s" at line %s; redirecting to authentication entry point', $exception->getFile(), $exception->getLine())); } try { @@ -110,7 +110,7 @@ class ExceptionListener } } else { if (null !== $this->logger) { - $this->logger->debug('Access is denied (and user is neither anonymous, nor remember-me)'); + $this->logger->debug(sprintf('Access is denied (and user is neither anonymous, nor remember-me) by "%s" at line %s', $exception->getFile(), $exception->getLine())); } try { @@ -141,6 +141,14 @@ class ExceptionListener return; } } + } elseif ($exception instanceof LogoutException) { + if (null !== $this->logger) { + $this->logger->info(sprintf('Logout exception occurred; wrapping with AccessDeniedHttpException (%s)', $exception->getMessage())); + } + + $event->setException(new AccessDeniedHttpException($exception->getMessage(), $exception)); + + return; } else { return; } @@ -160,10 +168,9 @@ class ExceptionListener $this->setTargetPath($request); - if ($authException instanceof AccountStatusException && ($token = $this->context->getToken()) instanceof UsernamePasswordToken) { + if ($authException instanceof AccountStatusException) { // remove the security token to prevent infinite redirect loops $this->context->setToken(null); - $request->getSession()->remove('_security_'.$token->getProviderKey()); } return $this->authenticationEntryPoint->start($request, $authException); diff --git a/Http/Firewall/LogoutListener.php b/Http/Firewall/LogoutListener.php index 07244f5..d145fa1 100644 --- a/Http/Firewall/LogoutListener.php +++ b/Http/Firewall/LogoutListener.php @@ -11,13 +11,15 @@ namespace Symfony\Component\Security\Http\Firewall; -use Symfony\Component\Security\Http\Logout\LogoutSuccessHandlerInterface; - -use Symfony\Component\Security\Http\Logout\LogoutHandlerInterface; -use Symfony\Component\Security\Core\SecurityContextInterface; -use Symfony\Component\Security\Http\HttpUtils; +use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\Security\Core\SecurityContextInterface; +use Symfony\Component\Security\Core\Exception\LogoutException; +use Symfony\Component\Security\Http\HttpUtils; +use Symfony\Component\Security\Http\Logout\LogoutHandlerInterface; +use Symfony\Component\Security\Http\Logout\LogoutSuccessHandlerInterface; /** * LogoutListener logout users. @@ -27,28 +29,33 @@ use Symfony\Component\HttpKernel\Event\GetResponseEvent; class LogoutListener implements ListenerInterface { private $securityContext; - private $logoutPath; - private $targetUrl; + private $options; private $handlers; private $successHandler; private $httpUtils; + private $csrfProvider; /** * Constructor * * @param SecurityContextInterface $securityContext * @param HttpUtils $httpUtils An HttpUtilsInterface instance - * @param string $logoutPath The path that starts the logout process - * @param string $targetUrl The URL to redirect to after logout - * @param LogoutSuccessHandlerInterface $successHandler + * @param array $options An array of options to process a logout attempt + * @param LogoutSuccessHandlerInterface $successHandler A LogoutSuccessHandlerInterface instance + * @param CsrfProviderInterface $csrfProvider A CsrfProviderInterface instance */ - public function __construct(SecurityContextInterface $securityContext, HttpUtils $httpUtils, $logoutPath, $targetUrl = '/', LogoutSuccessHandlerInterface $successHandler = null) + public function __construct(SecurityContextInterface $securityContext, HttpUtils $httpUtils, array $options = array(), LogoutSuccessHandlerInterface $successHandler = null, CsrfProviderInterface $csrfProvider = null) { $this->securityContext = $securityContext; $this->httpUtils = $httpUtils; - $this->logoutPath = $logoutPath; - $this->targetUrl = $targetUrl; + $this->options = array_merge(array( + 'csrf_parameter' => '_csrf_token', + 'intention' => 'logout', + 'logout_path' => '/logout', + 'target_url' => '/', + ), $options); $this->successHandler = $successHandler; + $this->csrfProvider = $csrfProvider; $this->handlers = array(); } @@ -56,8 +63,6 @@ class LogoutListener implements ListenerInterface * Adds a logout handler * * @param LogoutHandlerInterface $handler - * - * @return void */ public function addHandler(LogoutHandlerInterface $handler) { @@ -67,16 +72,29 @@ class LogoutListener implements ListenerInterface /** * Performs the logout if requested * + * If a CsrfProviderInterface instance is available, it will be used to + * validate the request. + * * @param GetResponseEvent $event A GetResponseEvent instance + * @throws InvalidCsrfTokenException if the CSRF token is invalid + * @throws RuntimeException if the LogoutSuccessHandlerInterface instance does not return a response */ public function handle(GetResponseEvent $event) { $request = $event->getRequest(); - if (!$this->httpUtils->checkRequestPath($request, $this->logoutPath)) { + if (!$this->requiresLogout($request)) { return; } + if (null !== $this->csrfProvider) { + $csrfToken = $request->get($this->options['csrf_parameter'], null, true); + + if (false === $this->csrfProvider->isCsrfTokenValid($this->options['intention'], $csrfToken)) { + throw new LogoutException('Invalid CSRF token.'); + } + } + if (null !== $this->successHandler) { $response = $this->successHandler->onLogoutSuccess($request); @@ -84,7 +102,7 @@ class LogoutListener implements ListenerInterface throw new \RuntimeException('Logout Success Handler did not return a Response.'); } } else { - $response = $this->httpUtils->createRedirectResponse($request, $this->targetUrl); + $response = $this->httpUtils->createRedirectResponse($request, $this->options['target_url']); } // handle multiple logout attempts gracefully @@ -98,4 +116,20 @@ class LogoutListener implements ListenerInterface $event->setResponse($response); } + + /** + * Whether this request is asking for logout. + * + * The default implementation only processed requests to a specific path, + * but a subclass could change this to logout requests where + * certain parameters is present. + * + * @param Request $request + * + * @return Boolean + */ + protected function requiresLogout(Request $request) + { + return $this->httpUtils->checkRequestPath($request, $this->options['logout_path']); + } } diff --git a/Http/Firewall/RememberMeListener.php b/Http/Firewall/RememberMeListener.php index ccda113..3614f79 100644 --- a/Http/Firewall/RememberMeListener.php +++ b/Http/Firewall/RememberMeListener.php @@ -1,5 +1,14 @@ <?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\HttpKernel\Log\LoggerInterface; @@ -12,15 +21,6 @@ use Symfony\Component\Security\Http\Event\InteractiveLoginEvent; use Symfony\Component\Security\Http\SecurityEvents; use Symfony\Component\EventDispatcher\EventDispatcherInterface; -/* - * This file is part of the Symfony framework. - * - * (c) Fabien Potencier <fabien@symfony.com> - * - * This source file is subject to the MIT license that is bundled - * with this source code in the file LICENSE. - */ - /** * RememberMeListener implements authentication capabilities via a cookie * diff --git a/Http/Firewall/SwitchUserListener.php b/Http/Firewall/SwitchUserListener.php index 9780860..7f0aa78 100644 --- a/Http/Firewall/SwitchUserListener.php +++ b/Http/Firewall/SwitchUserListener.php @@ -86,9 +86,7 @@ class SwitchUserListener implements ListenerInterface try { $this->securityContext->setToken($this->attemptSwitchUser($request)); } catch (AuthenticationException $e) { - if (null !== $this->logger) { - $this->logger->warn(sprintf('Switch User failed: "%s"', $e->getMessage())); - } + throw new \LogicException(sprintf('Switch User failed: "%s"', $e->getMessage())); } } @@ -108,8 +106,14 @@ class SwitchUserListener implements ListenerInterface private function attemptSwitchUser(Request $request) { $token = $this->securityContext->getToken(); - if (false !== $this->getOriginalToken($token)) { - throw new \LogicException(sprintf('You are already switched to "%s" user.', $token->getUsername())); + $originalToken = $this->getOriginalToken($token); + + if (false !== $originalToken) { + if ($token->getUsername() === $request->get($this->usernameParameter)) { + return $token; + } else { + throw new \LogicException(sprintf('You are already switched to "%s" user.', $token->getUsername())); + } } if (false === $this->accessDecisionManager->decide($token, array($this->role))) { @@ -148,7 +152,7 @@ class SwitchUserListener implements ListenerInterface private function attemptExitUser(Request $request) { if (false === $original = $this->getOriginalToken($this->securityContext->getToken())) { - throw new AuthenticationCredentialsNotFoundException(sprintf('Could not find original Token object.')); + throw new AuthenticationCredentialsNotFoundException('Could not find original Token object.'); } if (null !== $this->dispatcher) { |