added generic encoder factory

This commit is contained in:
Johannes Schmitt 2010-12-21 16:10:53 +01:00
parent d87c3c581c
commit 27f540463a
12 changed files with 271 additions and 52 deletions

View File

@ -21,12 +21,12 @@ use Symfony\Component\DependencyInjection\Reference;
*/
class FormLoginFactory implements SecurityFactoryInterface
{
public function create(ContainerBuilder $container, $id, $config, $userProvider, $providerIds, $defaultEntryPoint)
public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint)
{
$provider = 'security.authentication.provider.dao.'.$id;
$container
->register($provider, '%security.authentication.provider.dao.class%')
->setArguments(array(new Reference($userProvider), new Reference('security.account_checker'), new Reference('security.encoder.'.$providerIds[$userProvider])));
->setArguments(array(new Reference($userProvider), new Reference('security.account_checker'), new Reference('security.encoder_factory')));
;
// listener

View File

@ -21,12 +21,12 @@ use Symfony\Component\DependencyInjection\Reference;
*/
class HttpBasicFactory implements SecurityFactoryInterface
{
public function create(ContainerBuilder $container, $id, $config, $userProvider, $providerIds, $defaultEntryPoint)
public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint)
{
$provider = 'security.authentication.provider.dao.'.$id;
$container
->register($provider, '%security.authentication.provider.dao.class%')
->setArguments(array(new Reference($userProvider), new Reference('security.account_checker'), new Reference('security.encoder.'.$providerIds[$userProvider])));
->setArguments(array(new Reference($userProvider), new Reference('security.account_checker'), new Reference('security.encoder_factory')));
;
// listener

View File

@ -21,12 +21,12 @@ use Symfony\Component\DependencyInjection\Reference;
*/
class HttpDigestFactory implements SecurityFactoryInterface
{
public function create(ContainerBuilder $container, $id, $config, $userProvider, $providerIds, $defaultEntryPoint)
public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint)
{
$provider = 'security.authentication.provider.dao.'.$id;
$container
->register($provider, '%security.authentication.provider.dao.class%')
->setArguments(array(new Reference($userProvider), new Reference('security.account_checker'), new Reference('security.encoder.'.$providerIds[$userProvider])));
->setArguments(array(new Reference($userProvider), new Reference('security.account_checker'), new Reference('security.encoder_factory')));
;
// listener

View File

@ -20,7 +20,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder;
*/
interface SecurityFactoryInterface
{
function create(ContainerBuilder $container, $id, $config, $userProvider, $providerIds, $defaultEntryPoint);
function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint);
function getPosition();

View File

@ -21,7 +21,7 @@ use Symfony\Component\DependencyInjection\Reference;
*/
class X509Factory implements SecurityFactoryInterface
{
public function create(ContainerBuilder $container, $id, $config, $userProvider, $providerIds, $defaultEntryPoint)
public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint)
{
$provider = 'security.authentication.provider.pre_authenticated.'.$id;
$container

View File

@ -2,6 +2,8 @@
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\Parameter;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\Resource\FileResource;
@ -128,6 +130,8 @@ class SecurityExtension extends Extension
{
$providerIds = $this->createUserProviders($config, $container);
$encoders = $this->createEncoders($config, $container);
if (!$firewalls = $this->fixConfig($config, 'firewall')) {
return;
}
@ -136,7 +140,7 @@ class SecurityExtension extends Extension
$definition = $container->getDefinition('security.context_listener');
$arguments = $definition->getArguments();
$userProviders = array();
foreach (array_keys($providerIds) as $userProviderId) {
foreach ($providerIds as $userProviderId) {
$userProviders[] = new Reference($userProviderId);
}
$arguments[1] = $userProviders;
@ -195,8 +199,7 @@ class SecurityExtension extends Extension
if (!$providerIds) {
throw new \InvalidArgumentException('You must provide at least one authentication provider.');
}
$keys = array_keys($providerIds);
$defaultProvider = current($keys);
$defaultProvider = reset($providerIds);
}
// Register listeners
@ -242,7 +245,7 @@ class SecurityExtension extends Extension
}
// Authentication listeners
list($authListeners, $providers, $defaultEntryPoint) = $this->createAuthenticationListeners($container, $id, $firewall, $defaultProvider, $providerIds);
list($authListeners, $providers, $defaultEntryPoint) = $this->createAuthenticationListeners($container, $id, $firewall, $defaultProvider);
$listeners = array_merge($listeners, $authListeners);
@ -263,7 +266,7 @@ class SecurityExtension extends Extension
return array($matcher, $listeners, $exceptionListener);
}
protected function createAuthenticationListeners($container, $id, $firewall, $defaultProvider, $providerIds)
protected function createAuthenticationListeners($container, $id, $firewall, $defaultProvider)
{
$listeners = array();
$providers = array();
@ -295,7 +298,7 @@ class SecurityExtension extends Extension
if (array_key_exists($key, $firewall)) {
$userProvider = isset($firewall[$key]['provider']) ? $this->getUserProviderId($firewall[$key]['provider']) : $defaultProvider;
list($provider, $listener, $defaultEntryPoint) = $factory->create($container, $id, $firewall[$key], $userProvider, $providerIds, $defaultEntryPoint);
list($provider, $listener, $defaultEntryPoint) = $factory->create($container, $id, $firewall[$key], $userProvider, $defaultEntryPoint);
$listeners[] = new Reference($listener);
$providers[] = new Reference($provider);
@ -327,29 +330,97 @@ class SecurityExtension extends Extension
$providerIds = array();
foreach ($providers as $name => $provider) {
list($id, $encoder) = $this->createUserDaoProvider($name, $provider, $container);
$id = $this->createUserDaoProvider($name, $provider, $container);
if (isset($providerIds[$id])) {
if (in_array($id, $providerIds, true)) {
throw new \RuntimeException(sprintf('Provider names must be unique. Duplicate entry for %s.', $id));
}
$providerIds[$id] = $encoder;
$providerIds[] = $id;
}
return $providerIds;
}
protected function createEncoders($config, ContainerBuilder $container)
{
$encoders = $this->fixConfig($config, 'encoder');
if (!$encoders) {
return array();
}
$encoderMap = array();
foreach ($encoders as $class => $encoder) {
$encoderMap = $this->createEncoder($encoderMap, $class, $encoder, $container);
}
$container
->getDefinition('security.encoder_factory.generic')
->setArguments(array($encoderMap))
;
}
protected function createEncoder(array $encoderMap, $accountClass, $config, ContainerBuilder $container)
{
if (is_array($config) && isset($config['class'])) {
$accountClass = $config['class'];
}
if (empty($accountClass)) {
throw new \RuntimeException('Each encoder needs an account class.');
}
// a minimal message digest encoder
if (is_string($config)) {
$encoderMap[$accountClass] = array(
'class' => new Parameter('security.encoder.digest.class'),
'arguments' => array($config),
);
return $encoderMap;
}
// a custom encoder service
if (isset($config['id'])) {
$container
->getDefinition('security.encoder_factory.generic')
->addMethodCall('addEncoder', array($accountClass, new Reference($config['id'])))
;
return $encoderMap;
}
// a message digest encoder
if (!isset($config['algorithm'])) {
throw new \RuntimeException('"algoritm" must be defined.');
}
$arguments = array($config['algorithm']);
// add optional arguments
if (isset($config['encode-as-base64'])) {
$arguments[1] = $config['encode-as-base64'];
} else {
$arguments[1] = false;
}
if (isset($config['iterations'])) {
$arguments[2] = $config['iterations'];
} else {
$arguments[2] = 1;
}
$encoderMap[$accountClass] = array(
'class' => new Parameter('security.encoder.digest.class'),
'arguments' => $arguments,
);
return $encoderMap;
}
// Parses a <provider> tag and returns the id for the related user provider service
protected function createUserDaoProvider($name, $provider, ContainerBuilder $container, $master = true)
{
// encoder
$encoder = 'plain';
if (isset($provider['password-encoder'])) {
$encoder = $provider['password-encoder'];
} elseif (isset($provider['password_encoder'])) {
$encoder = $provider['password_encoder'];
}
if (isset($provider['name'])) {
$name = $provider['name'];
}
@ -362,7 +433,7 @@ class SecurityExtension extends Extension
// Existing DAO service provider
if (isset($provider['id'])) {
return array($provider['id'], $encoder);
return $provider['id'];
}
// Chain provider
@ -381,7 +452,7 @@ class SecurityExtension extends Extension
isset($provider['entity']['property']) ? $provider['entity']['property'] : null,
));
return array($name, $encoder);
return $name;
}
// Doctrine Document DAO provider
@ -394,7 +465,7 @@ class SecurityExtension extends Extension
isset($provider['document']['property']) ? $provider['document']['property'] : null,
));
return array($name, $encoder);
return $name;
}
// In-memory DAO provider
@ -429,7 +500,7 @@ class SecurityExtension extends Extension
$definition->addMethodCall('createUser', array(new Reference($userId)));
}
return array($name, $encoder);
return $name;
}
protected function getUserProviderId($name)

View File

@ -11,6 +11,7 @@
<parameter key="security.access_denied.url">null</parameter>
<parameter key="security.encoder_factory.generic.class">Symfony\Component\Security\Encoder\EncoderFactory</parameter>
<parameter key="security.encoder.digest.class">Symfony\Component\Security\Encoder\MessageDigestPasswordEncoder</parameter>
<parameter key="security.encoder.plain.class">Symfony\Component\Security\Encoder\PlaintextPasswordEncoder</parameter>
@ -87,15 +88,11 @@
<service id="security.account_checker" class="%security.account_checker.class%" />
<service id="security.encoder.sha1" class="%security.encoder.digest.class%">
<argument>sha1</argument>
<service id="security.encoder_factory.generic" class="%security.encoder_factory.generic.class%">
<argument type="collection"></argument>
</service>
<service id="security.encoder.md5" class="%security.encoder.digest.class%">
<argument>md5</argument>
</service>
<service id="security.encoder.plain" class="%security.encoder.plain.class%" />
<service id="security.encoder_factory" alias="security.encoder_factory.generic"></service>
<service id="security.logout.handler.session" class="%security.logout.handler.session.class%"></service>

View File

@ -2,11 +2,10 @@
namespace Symfony\Component\Security\Authentication\Provider;
use Symfony\Component\Security\Encoder\EncoderFactoryInterface;
use Symfony\Component\Security\User\UserProviderInterface;
use Symfony\Component\Security\User\AccountCheckerInterface;
use Symfony\Component\Security\User\AccountInterface;
use Symfony\Component\Security\Encoder\PasswordEncoderInterface;
use Symfony\Component\Security\Encoder\PlaintextPasswordEncoder;
use Symfony\Component\Security\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Exception\AuthenticationServiceException;
use Symfony\Component\Security\Exception\BadCredentialsException;
@ -29,7 +28,7 @@ use Symfony\Component\Security\Authentication\Token\UsernamePasswordToken;
*/
class DaoAuthenticationProvider extends UserAuthenticationProvider
{
protected $passwordEncoder;
protected $encoderFactory;
protected $userProvider;
/**
@ -37,16 +36,13 @@ class DaoAuthenticationProvider extends UserAuthenticationProvider
*
* @param UserProviderInterface $userProvider A UserProviderInterface instance
* @param AccountCheckerInterface $accountChecker An AccountCheckerInterface instance
* @param PasswordEncoderInterface $passwordEncoder A PasswordEncoderInterface instance
* @param EncoderFactoryInterface $encoderFactory A EncoderFactoryInterface instance
*/
public function __construct(UserProviderInterface $userProvider, AccountCheckerInterface $accountChecker, PasswordEncoderInterface $passwordEncoder = null, $hideUserNotFoundExceptions = true)
public function __construct(UserProviderInterface $userProvider, AccountCheckerInterface $accountChecker, EncoderFactoryInterface $encoderFactory, $hideUserNotFoundExceptions = true)
{
parent::__construct($accountChecker, $hideUserNotFoundExceptions);
if (null === $passwordEncoder) {
$passwordEncoder = new PlaintextPasswordEncoder();
}
$this->passwordEncoder = $passwordEncoder;
$this->encoderFactory = $encoderFactory;
$this->userProvider = $userProvider;
}
@ -65,7 +61,7 @@ class DaoAuthenticationProvider extends UserAuthenticationProvider
throw new BadCredentialsException('Bad credentials');
}
if (!$this->passwordEncoder->isPasswordValid($account->getPassword(), $presentedPassword, $account->getSalt())) {
if (!$this->encoderFactory->getEncoder($account)->isPasswordValid($account->getPassword(), $presentedPassword, $account->getSalt())) {
throw new BadCredentialsException('Bad credentials');
}
}

View File

@ -0,0 +1,77 @@
<?php
namespace Symfony\Component\Security\Encoder;
use Symfony\Component\Security\User\AccountInterface;
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* A generic encoder factory implementation
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class EncoderFactory implements EncoderFactoryInterface
{
protected $encoders;
protected $encoderMap;
public function __construct(array $encoderMap)
{
$this->encoders = array();
$this->encoderMap = $encoderMap;
}
/**
* {@inheritDoc}
*/
public function getEncoder(AccountInterface $account)
{
foreach ($this->encoders as $class => $encoder) {
if ($account instanceof $class) {
return $encoder;
}
}
return $this->createEncoder($account);
}
/**
* Adds an encoder instance to the factory
*
* @param string $class
* @param PasswordEncoderInterface $encoder
* @return void
*/
public function addEncoder($class, PasswordEncoderInterface $encoder)
{
$this->encoders[$class] = $encoder;
}
/**
* Creates the actual encoder instance
*
* @param AccountInterface $account
* @return PasswordEncoderInterface
*/
protected function createEncoder($account)
{
foreach ($this->encoderMap as $class => $config) {
if ($account instanceof $class) {
$reflection = new \ReflectionClass($config['class']);
$this->encoders[$class] = $reflection->newInstanceArgs($config['arguments']);
return $this->encoders[$class];
}
}
throw new \InvalidArgumentException(sprintf('No encoder has been configured for account "%s".', get_class($account)));
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace Symfony\Component\Security\Encoder;
use Symfony\Component\Security\User\AccountInterface;
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* EncoderFactoryInterface to support different encoders for different accounts.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
interface EncoderFactoryInterface
{
/**
* Returns the password encoder to use for the given account
*
* @param AccountInterface $account
* @return PasswordEncoderInterface never null
*/
function getEncoder(AccountInterface $account);
}

View File

@ -10,6 +10,10 @@
namespace Symfony\Tests\Component\Security\Authentication\Provider;
use Symfony\Component\Security\Encoder\EncoderFactory;
use Symfony\Component\Security\Encoder\PlaintextPasswordEncoder;
use Symfony\Component\Security\Authentication\Provider\DaoAuthenticationProvider;
class DaoAuthenticationProviderTest extends \PHPUnit_Framework_TestCase
@ -37,7 +41,7 @@ class DaoAuthenticationProviderTest extends \PHPUnit_Framework_TestCase
->will($this->throwException($this->getMock('Symfony\Component\Security\Exception\UsernameNotFoundException', null, array(), '', false)))
;
$provider = new DaoAuthenticationProvider($userProvider, $this->getMock('Symfony\Component\Security\User\AccountCheckerInterface'));
$provider = new DaoAuthenticationProvider($userProvider, $this->getMock('Symfony\Component\Security\User\AccountCheckerInterface'), $this->getMock('Symfony\Component\Security\Encoder\EncoderFactoryInterface'));
$method = new \ReflectionMethod($provider, 'retrieveUser');
$method->setAccessible(true);
@ -55,7 +59,7 @@ class DaoAuthenticationProviderTest extends \PHPUnit_Framework_TestCase
->will($this->throwException($this->getMock('RuntimeException', null, array(), '', false)))
;
$provider = new DaoAuthenticationProvider($userProvider, $this->getMock('Symfony\Component\Security\User\AccountCheckerInterface'));
$provider = new DaoAuthenticationProvider($userProvider, $this->getMock('Symfony\Component\Security\User\AccountCheckerInterface'), $this->getMock('Symfony\Component\Security\Encoder\EncoderFactoryInterface'));
$method = new \ReflectionMethod($provider, 'retrieveUser');
$method->setAccessible(true);
@ -76,7 +80,7 @@ class DaoAuthenticationProviderTest extends \PHPUnit_Framework_TestCase
->will($this->returnValue($user))
;
$provider = new DaoAuthenticationProvider($userProvider, $this->getMock('Symfony\Component\Security\User\AccountCheckerInterface'));
$provider = new DaoAuthenticationProvider($userProvider, $this->getMock('Symfony\Component\Security\User\AccountCheckerInterface'), $this->getMock('Symfony\Component\Security\Encoder\EncoderFactoryInterface'));
$reflection = new \ReflectionMethod($provider, 'retrieveUser');
$reflection->setAccessible(true);
$result = $reflection->invoke($provider, null, $token);
@ -94,7 +98,7 @@ class DaoAuthenticationProviderTest extends \PHPUnit_Framework_TestCase
->will($this->returnValue($user))
;
$provider = new DaoAuthenticationProvider($userProvider, $this->getMock('Symfony\Component\Security\User\AccountCheckerInterface'));
$provider = new DaoAuthenticationProvider($userProvider, $this->getMock('Symfony\Component\Security\User\AccountCheckerInterface'), $this->getMock('Symfony\Component\Security\Encoder\EncoderFactoryInterface'));
$method = new \ReflectionMethod($provider, 'retrieveUser');
$method->setAccessible(true);
@ -236,6 +240,17 @@ class DaoAuthenticationProviderTest extends \PHPUnit_Framework_TestCase
$userChecker = $this->getMock('Symfony\Component\Security\User\AccountCheckerInterface');
}
return new DaoAuthenticationProvider($userProvider, $userChecker, $passwordEncoder);
if (null === $passwordEncoder) {
$passwordEncoder = new PlaintextPasswordEncoder();
}
$encoderFactory = $this->getMock('Symfony\Component\Security\Encoder\EncoderFactoryInterface');
$encoderFactory
->expects($this->any())
->method('getEncoder')
->will($this->returnValue($passwordEncoder))
;
return new DaoAuthenticationProvider($userProvider, $userChecker, $encoderFactory);
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace Symfony\Tests\Component\Security\Encoder;
use Symfony\Component\Security\Encoder\MessageDigestPasswordEncoder;
use Symfony\Component\Security\Encoder\EncoderFactory;
class EncoderFactoryTest extends \PHPUnit_Framework_TestCase
{
public function testGetEncoderWithMessageDigestEncoder()
{
$factory = new EncoderFactory(array('Symfony\Component\Security\User\AccountInterface' => array(
'class' => 'Symfony\Component\Security\Encoder\MessageDigestPasswordEncoder',
'arguments' => array('sha512', true, 5),
)));
$encoder = $factory->getEncoder($this->getMock('Symfony\Component\Security\User\AccountInterface'));
$expectedEncoder = new MessageDigestPasswordEncoder('sha512', true, 5);
$this->assertEquals($expectedEncoder->encodePassword('foo', 'moo'), $encoder->encodePassword('foo', 'moo'));
}
public function testGetEncoderWithService()
{
$factory = new EncoderFactory(array());
$factory->addEncoder('Symfony\Component\Security\User\AccountInterface', new MessageDigestPasswordEncoder('sha1'));
$encoder = $factory->getEncoder($this->getMock('Symfony\Component\Security\User\AccountInterface'));
$expectedEncoder = new MessageDigestPasswordEncoder('sha1');
$this->assertEquals($expectedEncoder->encodePassword('foo', ''), $encoder->encodePassword('foo', ''));
}
}