From 0fb09293fd365a4a7912f9c37bb4e84734980b1a Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Sat, 4 Mar 2017 12:27:34 +0100 Subject: [PATCH] context listener: hardening user provider handling --- .../Http/Firewall/ContextListener.php | 8 +- .../Tests/Firewall/ContextListenerTest.php | 104 ++++++++++++++++++ 2 files changed, 111 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Security/Http/Firewall/ContextListener.php b/src/Symfony/Component/Security/Http/Firewall/ContextListener.php index 9ac37cdf6d41..b443fa14881a 100644 --- a/src/Symfony/Component/Security/Http/Firewall/ContextListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/ContextListener.php @@ -150,6 +150,8 @@ protected function refreshUser(TokenInterface $token) return $token; } + $userNotFoundByProvider = false; + foreach ($this->userProviders as $provider) { try { $refreshedUser = $provider->refreshUser($user); @@ -167,10 +169,14 @@ protected function refreshUser(TokenInterface $token) $this->logger->warning('Username could not be found in the selected user provider.', array('username' => $e->getUsername(), 'provider' => get_class($provider))); } - return; + $userNotFoundByProvider = true; } } + if ($userNotFoundByProvider) { + return; + } + throw new \RuntimeException(sprintf('There is no user provider for user "%s".', get_class($user))); } } diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php index 15bc8bda692b..ba8edc3b1740 100644 --- a/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php @@ -17,10 +17,17 @@ use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; 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\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; +use Symfony\Component\Security\Core\Exception\UnsupportedUserException; +use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; +use Symfony\Component\Security\Core\User\User; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Http\Firewall\ContextListener; use Symfony\Component\EventDispatcher\EventDispatcher; @@ -238,6 +245,40 @@ public function testHandleRemovesTokenIfNoPreviousSessionWasFound() $listener->handle($event); } + public function testTryAllUserProvidersUntilASupportingUserProviderIsFound() + { + $tokenStorage = new TokenStorage(); + $refreshedUser = new User('foobar', 'baz'); + $this->handleEventWithPreviousSession($tokenStorage, array(new NotSupportingUserProvider(), new SupportingUserProvider($refreshedUser))); + + $this->assertSame($refreshedUser, $tokenStorage->getToken()->getUser()); + } + + public function testNextSupportingUserProviderIsTriedIfPreviousSupportingUserProviderDidNotLoadTheUser() + { + $tokenStorage = new TokenStorage(); + $refreshedUser = new User('foobar', 'baz'); + $this->handleEventWithPreviousSession($tokenStorage, array(new SupportingUserProvider(), new SupportingUserProvider($refreshedUser))); + + $this->assertSame($refreshedUser, $tokenStorage->getToken()->getUser()); + } + + public function testTokenIsSetToNullIfNoUserWasLoadedByTheRegisteredUserProviders() + { + $tokenStorage = new TokenStorage(); + $this->handleEventWithPreviousSession($tokenStorage, array(new NotSupportingUserProvider(), new SupportingUserProvider())); + + $this->assertNull($tokenStorage->getToken()); + } + + /** + * @expectedException \RuntimeException + */ + public function testRuntimeExceptionIsThrownIfNoSupportingUserProviderWasRegistered() + { + $this->handleEventWithPreviousSession(new TokenStorage(), array(new NotSupportingUserProvider(), new NotSupportingUserProvider())); + } + protected function runSessionOnKernelResponse($newToken, $original = null) { $session = new Session(new MockArraySessionStorage()); @@ -265,4 +306,67 @@ protected function runSessionOnKernelResponse($newToken, $original = null) return $session; } + + private function handleEventWithPreviousSession(TokenStorageInterface $tokenStorage, array $userProviders) + { + $session = new Session(new MockArraySessionStorage()); + $session->set('_security_context_key', serialize(new UsernamePasswordToken(new User('foo', 'bar'), '', 'context_key'))); + + $request = new Request(); + $request->setSession($session); + $request->cookies->set('MOCKSESSID', true); + + $listener = new ContextListener($tokenStorage, $userProviders, 'context_key'); + $listener->handle(new GetResponseEvent($this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(), $request, HttpKernelInterface::MASTER_REQUEST)); + } +} + +class NotSupportingUserProvider implements UserProviderInterface +{ + public function loadUserByUsername($username) + { + throw new UsernameNotFoundException(); + } + + public function refreshUser(UserInterface $user) + { + throw new UnsupportedUserException(); + } + + public function supportsClass($class) + { + return false; + } +} + +class SupportingUserProvider implements UserProviderInterface +{ + private $refreshedUser; + + public function __construct(User $refreshedUser = null) + { + $this->refreshedUser = $refreshedUser; + } + + public function loadUserByUsername($username) + { + } + + public function refreshUser(UserInterface $user) + { + if (!$user instanceof User) { + throw new UnsupportedUserException(); + } + + if (null === $this->refreshedUser) { + throw new UsernameNotFoundException(); + } + + return $this->refreshedUser; + } + + public function supportsClass($class) + { + return 'Symfony\Component\Security\Core\User\User' === $class; + } }