Bladeren bron

[Security] adds a chain user provider

Johannes M. Schmitt 14 jaren geleden
bovenliggende
commit
53f3ff8258

+ 4 - 0
src/Symfony/Bundle/SecurityBundle/DependencyInjection/Configuration.php

@@ -192,6 +192,10 @@ class Configuration
                     ->scalarNode('id')->end()
                     ->fixXmlConfig('provider')
                     ->arrayNode('providers')
+                        ->beforeNormalization()
+                            ->ifString()
+                            ->then(function($v) { return preg_split('/\s*,\s*/', $v); })
+                        ->end()
                         ->prototype('scalar')->end()
                     ->end()
                     ->fixXmlConfig('user')

+ 12 - 3
src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php

@@ -433,9 +433,18 @@ class SecurityExtension extends Extension
         }
 
         // Chain provider
-        if (count($provider['providers']) > 0) {
-            // FIXME
-            throw new \RuntimeException('Not implemented yet.');
+        if ($provider['providers']) {
+            $providers = array();
+            foreach ($provider['providers'] as $providerName) {
+                $providers[] = new Reference($this->getUserProviderId(strtolower($providerName)));
+            }
+
+            $container
+                ->setDefinition($name, new DefinitionDecorator('security.user.provider.chain'))
+                ->addArgument($providers)
+            ;
+
+            return $name;
         }
 
         // Doctrine Entity DAO provider

+ 3 - 0
src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml

@@ -17,6 +17,7 @@
         <parameter key="security.user.provider.entity.class">Symfony\Component\Security\Core\User\EntityUserProvider</parameter>
         <parameter key="security.user.provider.in_memory.class">Symfony\Component\Security\Core\User\InMemoryUserProvider</parameter>
         <parameter key="security.user.provider.in_memory.user.class">Symfony\Component\Security\Core\User\User</parameter>
+        <parameter key="security.user.provider.chain.class">Symfony\Component\Security\Core\User\ChainUserProvider</parameter>
 
         <parameter key="security.authentication.trust_resolver.class">Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver</parameter>
         <parameter key="security.authentication.trust_resolver.anonymous_class">Symfony\Component\Security\Core\Authentication\Token\AnonymousToken</parameter>
@@ -129,5 +130,7 @@
 
         <service id="security.user.provider.in_memory" class="%security.user.provider.in_memory.class%" abstract="true" public="false" />
         <service id="security.user.provider.in_memory.user" class="%security.user.provider.in_memory.user.class%" abstract="true" public="false" />
+
+        <service id="security.user.provider.chain" class="%security.user.provider.chain.class%" abstract="true" public="false" />
     </services>
 </container>

+ 3 - 0
src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/container1.php

@@ -38,6 +38,9 @@ $container->loadFromExtension('security', array(
         'service' => array(
             'id' => 'user.manager',
         ),
+        'chain' => array(
+            'providers' => array('service', 'doctrine', 'basic'),
+        ),
     ),
 
     'firewalls' => array(

+ 2 - 0
src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/container1.xml

@@ -32,6 +32,8 @@
         </provider>
 
         <provider name="service" id="user.manager" />
+        
+        <provider name="chain" providers="service, doctrine, basic" />
 
         <firewall name="simple" pattern="/login" security="false" />
 

+ 3 - 0
src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/container1.yml

@@ -26,6 +26,9 @@ security:
             entity: { class: SecurityBundle:User, property: username }
         service:
             id: user.manager
+        chain:
+            providers: [service, doctrine, basic]
+            
 
     firewalls:
         simple: { pattern: /login, security: false }

+ 8 - 0
src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php

@@ -48,10 +48,18 @@ abstract class SecurityExtensionTest extends \PHPUnit_Framework_TestCase
             'security.user.provider.concrete.basic_bar',
             'security.user.provider.concrete.doctrine',
             'security.user.provider.concrete.service',
+            'security.user.provider.concrete.chain',
         );
 
         $this->assertEquals(array(), array_diff($expectedProviders, $providers));
         $this->assertEquals(array(), array_diff($providers, $expectedProviders));
+
+        // chain provider
+        $this->assertEquals(array(array(
+            new Reference('security.user.provider.concrete.service'),
+            new Reference('security.user.provider.concrete.doctrine'),
+            new Reference('security.user.provider.concrete.basic'),
+        )), $container->getDefinition('security.user.provider.concrete.chain')->getArguments());
     }
 
     public function testFirewalls()

+ 70 - 0
src/Symfony/Component/Security/Core/User/ChainUserProvider.php

@@ -0,0 +1,70 @@
+<?php
+
+namespace Symfony\Component\Security\Core\User;
+
+use Symfony\Component\Security\Core\Exception\UnsupportedAccountException;
+use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
+
+/**
+ * Chain User Provider.
+ *
+ * This provider calls several leaf providers in a chain until one is able to
+ * handle the request.
+ *
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>
+ */
+class ChainUserProvider implements UserProviderInterface
+{
+    protected $providers;
+
+    public function __construct(array $providers)
+    {
+        $this->providers = $providers;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function loadUserByUsername($username)
+    {
+        foreach ($this->providers as $provider) {
+            try {
+                return $provider->loadUserByUsername($username);
+            } catch (UsernameNotFoundException $notFound) {
+                // try next one
+            }
+        }
+
+        throw new UsernameNotFoundException(sprintf('There is no user with name "%s".', $username));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function loadUserByAccount(AccountInterface $account)
+    {
+        foreach ($this->providers as $provider) {
+            try {
+                return $provider->loadUserByAccount($account);
+            } catch (UnsupportedAccountException $unsupported) {
+                // try next one
+            }
+        }
+
+        throw new UnsupportedAccountException(sprintf('The account "%s" is not supported.', get_class($account)));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function supportsClass($class)
+    {
+        foreach ($this->providers as $provider) {
+            if ($provider->supportsClass($class)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+}

+ 156 - 0
tests/Symfony/Tests/Component/Security/Core/User/ChainUserProviderTest.php

@@ -0,0 +1,156 @@
+<?php
+
+namespace Symfony\Tests\Component\Security\Core\User;
+
+use Symfony\Component\Security\Core\Exception\UnsupportedAccountException;
+
+use Symfony\Component\Security\Core\User\ChainUserProvider;
+
+use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
+
+class ChainUserProviderTest extends \PHPUnit_Framework_TestCase
+{
+    public function testLoadUserByUsername()
+    {
+        $provider1 = $this->getProvider();
+        $provider1
+            ->expects($this->once())
+            ->method('loadUserByUsername')
+            ->with($this->equalTo('foo'))
+            ->will($this->throwException(new UsernameNotFoundException('not found')))
+        ;
+
+        $provider2 = $this->getProvider();
+        $provider2
+            ->expects($this->once())
+            ->method('loadUserByUsername')
+            ->with($this->equalTo('foo'))
+            ->will($this->returnValue($account = $this->getAccount()))
+        ;
+
+        $provider = new ChainUserProvider(array($provider1, $provider2));
+        $this->assertSame($account, $provider->loadUserByUsername('foo'));
+    }
+
+    /**
+     * @expectedException Symfony\Component\Security\Core\Exception\UsernameNotFoundException
+     */
+    public function testLoadUserByUsernameThrowsUsernameNotFoundException()
+    {
+        $provider1 = $this->getProvider();
+        $provider1
+            ->expects($this->once())
+            ->method('loadUserByUsername')
+            ->with($this->equalTo('foo'))
+            ->will($this->throwException(new UsernameNotFoundException('not found')))
+        ;
+
+        $provider2 = $this->getProvider();
+        $provider2
+            ->expects($this->once())
+            ->method('loadUserByUsername')
+            ->with($this->equalTo('foo'))
+            ->will($this->throwException(new UsernameNotFoundException('not found')))
+        ;
+
+        $provider = new ChainUserProvider(array($provider1, $provider2));
+        $provider->loadUserByUsername('foo');
+    }
+
+    public function testLoadUserByAccount()
+    {
+        $provider1 = $this->getProvider();
+        $provider1
+            ->expects($this->once())
+            ->method('loadUserByAccount')
+            ->will($this->throwException(new UnsupportedAccountException('unsupported')))
+        ;
+
+        $provider2 = $this->getProvider();
+        $provider2
+            ->expects($this->once())
+            ->method('loadUserByAccount')
+            ->will($this->returnValue($account = $this->getAccount()))
+        ;
+
+        $provider = new ChainUserProvider(array($provider1, $provider2));
+        $this->assertSame($account, $provider->loadUserByAccount($this->getAccount()));
+    }
+
+    /**
+     * @expectedException Symfony\Component\Security\Core\Exception\UnsupportedAccountException
+     */
+    public function testLoadUserByAccountThrowsUnsupportedAccountException()
+    {
+        $provider1 = $this->getProvider();
+        $provider1
+            ->expects($this->once())
+            ->method('loadUserByAccount')
+            ->will($this->throwException(new UnsupportedAccountException('unsupported')))
+        ;
+
+        $provider2 = $this->getProvider();
+        $provider2
+            ->expects($this->once())
+            ->method('loadUserByAccount')
+            ->will($this->throwException(new UnsupportedAccountException('unsupported')))
+        ;
+
+        $provider = new ChainUserProvider(array($provider1, $provider2));
+        $provider->loadUserByAccount($this->getAccount());
+    }
+
+    public function testSupportsClass()
+    {
+        $provider1 = $this->getProvider();
+        $provider1
+            ->expects($this->once())
+            ->method('supportsClass')
+            ->with($this->equalTo('foo'))
+            ->will($this->returnValue(false))
+        ;
+
+        $provider2 = $this->getProvider();
+        $provider2
+            ->expects($this->once())
+            ->method('supportsClass')
+            ->with($this->equalTo('foo'))
+            ->will($this->returnValue(true))
+        ;
+
+        $provider = new ChainUserProvider(array($provider1, $provider2));
+        $this->assertTrue($provider->supportsClass('foo'));
+    }
+
+    public function testSupportsClassWhenNotSupported()
+    {
+        $provider1 = $this->getProvider();
+        $provider1
+            ->expects($this->once())
+            ->method('supportsClass')
+            ->with($this->equalTo('foo'))
+            ->will($this->returnValue(false))
+        ;
+
+        $provider2 = $this->getProvider();
+        $provider2
+            ->expects($this->once())
+            ->method('supportsClass')
+            ->with($this->equalTo('foo'))
+            ->will($this->returnValue(false))
+        ;
+
+        $provider = new ChainUserProvider(array($provider1, $provider2));
+        $this->assertFalse($provider->supportsClass('foo'));
+    }
+
+    protected function getAccount()
+    {
+        return $this->getMock('Symfony\Component\Security\Core\User\AccountInterface');
+    }
+
+    protected function getProvider()
+    {
+        return $this->getMock('Symfony\Component\Security\Core\User\UserProviderInterface');
+    }
+}