[Security] Lazy load user providers

This commit is contained in:
Robin Chalas 2017-07-03 14:22:20 +02:00
parent d43355c6d8
commit d7914a6a7d
6 changed files with 57 additions and 18 deletions

View File

@ -245,7 +245,7 @@ class SecurityExtension extends Extension
foreach ($providerIds as $userProviderId) {
$userProviders[] = new Reference($userProviderId);
}
$arguments[1] = $userProviders;
$arguments[1] = new IteratorArgument($userProviders);
$definition->setArguments($arguments);
$customUserChecker = false;
@ -613,7 +613,7 @@ class SecurityExtension extends Extension
$container
->setDefinition($name, new ChildDefinition('security.user.provider.chain'))
->addArgument($providers);
->addArgument(new IteratorArgument($providers));
return $name;
}

View File

@ -12,6 +12,7 @@
namespace Symfony\Bundle\SecurityBundle\Tests\DependencyInjection;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Bundle\SecurityBundle\SecurityBundle;
use Symfony\Bundle\SecurityBundle\DependencyInjection\SecurityExtension;
@ -57,10 +58,10 @@ abstract class CompleteConfigurationTest extends TestCase
$this->assertEquals(array(), array_diff($providers, $expectedProviders));
// chain provider
$this->assertEquals(array(array(
$this->assertEquals(array(new IteratorArgument(array(
new Reference('security.user.provider.concrete.service'),
new Reference('security.user.provider.concrete.basic'),
)), $container->getDefinition('security.user.provider.concrete.chain')->getArguments());
))), $container->getDefinition('security.user.provider.concrete.chain')->getArguments());
}
public function testFirewalls()

View File

@ -172,6 +172,26 @@ class ChainUserProviderTest extends TestCase
$this->assertFalse($provider->supportsClass('foo'));
}
public function testAcceptsTraversable()
{
$provider1 = $this->getProvider();
$provider1
->expects($this->once())
->method('refreshUser')
->will($this->throwException(new UnsupportedUserException('unsupported')))
;
$provider2 = $this->getProvider();
$provider2
->expects($this->once())
->method('refreshUser')
->will($this->returnValue($account = $this->getAccount()))
;
$provider = new ChainUserProvider(new \ArrayObject(array($provider1, $provider2)));
$this->assertSame($account, $provider->refreshUser($this->getAccount()));
}
protected function getAccount()
{
return $this->getMockBuilder('Symfony\Component\Security\Core\User\UserInterface')->getMock();

View File

@ -26,7 +26,10 @@ class ChainUserProvider implements UserProviderInterface
{
private $providers;
public function __construct(array $providers)
/**
* @param iterable|UserProviderInterface[] $providers
*/
public function __construct($providers)
{
$this->providers = $providers;
}
@ -36,6 +39,10 @@ class ChainUserProvider implements UserProviderInterface
*/
public function getProviders()
{
if ($this->providers instanceof \Traversable) {
return iterator_to_array($this->providers);
}
return $this->providers;
}

View File

@ -44,18 +44,20 @@ class ContextListener implements ListenerInterface
private $registered;
private $trustResolver;
public function __construct(TokenStorageInterface $tokenStorage, array $userProviders, $contextKey, LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null, AuthenticationTrustResolverInterface $trustResolver = null)
/**
* @param TokenStorageInterface $tokenStorage
* @param iterable|UserProviderInterface[] $userProviders
* @param string $contextKey
* @param LoggerInterface|null $logger
* @param EventDispatcherInterface|null $dispatcher
* @param AuthenticationTrustResolverInterface|null $trustResolver
*/
public function __construct(TokenStorageInterface $tokenStorage, $userProviders, $contextKey, LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null, AuthenticationTrustResolverInterface $trustResolver = null)
{
if (empty($contextKey)) {
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->tokenStorage = $tokenStorage;
$this->userProviders = $userProviders;
$this->contextKey = $contextKey;
@ -158,6 +160,10 @@ class ContextListener implements ListenerInterface
$userNotFoundByProvider = false;
foreach ($this->userProviders as $provider) {
if (!$provider instanceof UserProviderInterface) {
throw new \InvalidArgumentException(sprintf('User provider "%s" must implement "%s".', get_class($provider), UserProviderInterface::class));
}
try {
$refreshedUser = $provider->refreshUser($user);
$token->setUser($refreshedUser);

View File

@ -53,11 +53,7 @@ class ContextListenerTest extends TestCase
*/
public function testUserProvidersNeedToImplementAnInterface()
{
new ContextListener(
$this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface')->getMock(),
array(new \stdClass()),
'key123'
);
$this->handleEventWithPreviousSession(new TokenStorage(), array(new \stdClass()));
}
public function testOnKernelResponseWillAddSession()
@ -287,6 +283,15 @@ class ContextListenerTest extends TestCase
$this->handleEventWithPreviousSession(new TokenStorage(), array(new NotSupportingUserProvider(), new NotSupportingUserProvider()));
}
public function testAcceptsProvidersAsTraversable()
{
$tokenStorage = new TokenStorage();
$refreshedUser = new User('foobar', 'baz');
$this->handleEventWithPreviousSession($tokenStorage, new \ArrayObject(array(new NotSupportingUserProvider(), new SupportingUserProvider($refreshedUser))));
$this->assertSame($refreshedUser, $tokenStorage->getToken()->getUser());
}
protected function runSessionOnKernelResponse($newToken, $original = null)
{
$session = new Session(new MockArraySessionStorage());
@ -315,7 +320,7 @@ class ContextListenerTest extends TestCase
return $session;
}
private function handleEventWithPreviousSession(TokenStorageInterface $tokenStorage, array $userProviders)
private function handleEventWithPreviousSession(TokenStorageInterface $tokenStorage, $userProviders)
{
$session = new Session(new MockArraySessionStorage());
$session->set('_security_context_key', serialize(new UsernamePasswordToken(new User('foo', 'bar'), '', 'context_key')));