feature #10005 [Security] Added named encoders to EncoderFactory (tamirvs)

This PR was squashed before being merged into the 2.5-dev branch (closes #10005).

Discussion
----------

[Security] Added named encoders to EncoderFactory

| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #9743
| License       | MIT
| Doc PR        | -

This PR is basically merging FOSAdvancedEncoder. I think it's better than having a separate bundle that most of it's code is a copy of the core.
A use case is: having a different encoders or bcrypt cost based on the user's roles.

Commits
-------

c69e2ca [Security] Added named encoders to EncoderFactory
This commit is contained in:
Fabien Potencier 2014-01-18 10:09:36 +01:00
commit 4ad343bd0f
3 changed files with 113 additions and 8 deletions

View File

@ -0,0 +1,28 @@
<?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\Core\Encoder;
/**
* @author Christophe Coevoet <stof@notk.org>
*/
interface EncoderAwareInterface
{
/**
* Gets the name of the encoder used to encode the password.
*
* If the method returns null, the standard way to retrieve the encoder
* will be used instead.
*
* @return string
*/
public function getEncoderName();
}

View File

@ -30,19 +30,32 @@ class EncoderFactory implements EncoderFactoryInterface
*/
public function getEncoder($user)
{
foreach ($this->encoders as $class => $encoder) {
if ((is_object($user) && !$user instanceof $class) || (!is_object($user) && !is_subclass_of($user, $class) && $user != $class)) {
continue;
$encoderKey = null;
if ($user instanceof EncoderAwareInterface && (null !== $encoderName = $user->getEncoderName())) {
if (!array_key_exists($encoderName, $this->encoders)) {
throw new \RuntimeException(sprintf('The encoder "%s" was not configured.', $encoderName));
}
if (!$encoder instanceof PasswordEncoderInterface) {
return $this->encoders[$class] = $this->createEncoder($encoder);
$encoderKey = $encoderName;
} else {
foreach ($this->encoders as $class => $encoder) {
if ((is_object($user) && $user instanceof $class) || (!is_object($user) && (is_subclass_of($user, $class) || $user == $class))) {
$encoderKey = $class;
break;
}
}
return $this->encoders[$class];
}
throw new \RuntimeException(sprintf('No encoder has been configured for account "%s".', is_object($user) ? get_class($user) : $user));
if (null === $encoderKey) {
throw new \RuntimeException(sprintf('No encoder has been configured for account "%s".', is_object($user) ? get_class($user) : $user));
}
if (!$this->encoders[$encoderKey] instanceof PasswordEncoderInterface) {
$this->encoders[$encoderKey] = $this->createEncoder($this->encoders[$encoderKey]);
}
return $this->encoders[$encoderKey];
}
/**

View File

@ -13,6 +13,7 @@ namespace Symfony\Component\Security\Core\Tests\Encoder;
use Symfony\Component\Security\Core\Encoder\MessageDigestPasswordEncoder;
use Symfony\Component\Security\Core\Encoder\EncoderFactory;
use Symfony\Component\Security\Core\Encoder\EncoderAwareInterface;
use Symfony\Component\Security\Core\User\User;
use Symfony\Component\Security\Core\User\UserInterface;
@ -78,6 +79,59 @@ class EncoderFactoryTest extends \PHPUnit_Framework_TestCase
$expectedEncoder = new MessageDigestPasswordEncoder('sha1');
$this->assertEquals($expectedEncoder->encodePassword('foo', ''), $encoder->encodePassword('foo', ''));
}
public function testGetNamedEncoderForEncoderAware()
{
$factory = new EncoderFactory(array(
'Symfony\Component\Security\Core\Tests\Encoder\EncAwareUser' => new MessageDigestPasswordEncoder('sha256'),
'encoder_name' => new MessageDigestPasswordEncoder('sha1')
));
$encoder = $factory->getEncoder(new EncAwareUser('user', 'pass'));
$expectedEncoder = new MessageDigestPasswordEncoder('sha1');
$this->assertEquals($expectedEncoder->encodePassword('foo', ''), $encoder->encodePassword('foo', ''));
}
public function testGetNullNamedEncoderForEncoderAware()
{
$factory = new EncoderFactory(array(
'Symfony\Component\Security\Core\Tests\Encoder\EncAwareUser' => new MessageDigestPasswordEncoder('sha1'),
'encoder_name' => new MessageDigestPasswordEncoder('sha256')
));
$user = new EncAwareUser('user', 'pass');
$user->encoderName = null;
$encoder = $factory->getEncoder($user);
$expectedEncoder = new MessageDigestPasswordEncoder('sha1');
$this->assertEquals($expectedEncoder->encodePassword('foo', ''), $encoder->encodePassword('foo', ''));
}
/**
* @expectedException RuntimeException
*/
public function testGetInvalidNamedEncoderForEncoderAware()
{
$factory = new EncoderFactory(array(
'Symfony\Component\Security\Core\Tests\Encoder\EncAwareUser' => new MessageDigestPasswordEncoder('sha1'),
'encoder_name' => new MessageDigestPasswordEncoder('sha256')
));
$user = new EncAwareUser('user', 'pass');
$user->encoderName = 'invalid_encoder_name';
$encoder = $factory->getEncoder($user);
}
public function testGetEncoderForEncoderAwareWithClassName()
{
$factory = new EncoderFactory(array(
'Symfony\Component\Security\Core\Tests\Encoder\EncAwareUser' => new MessageDigestPasswordEncoder('sha1'),
'encoder_name' => new MessageDigestPasswordEncoder('sha256')
));
$encoder = $factory->getEncoder('Symfony\Component\Security\Core\Tests\Encoder\EncAwareUser');
$expectedEncoder = new MessageDigestPasswordEncoder('sha1');
$this->assertEquals($expectedEncoder->encodePassword('foo', ''), $encoder->encodePassword('foo', ''));
}
}
class SomeUser implements UserInterface
@ -92,3 +146,13 @@ class SomeUser implements UserInterface
class SomeChildUser extends SomeUser
{
}
class EncAwareUser extends SomeUser implements EncoderAwareInterface
{
public $encoderName = 'encoder_name';
public function getEncoderName()
{
return $this->encoderName;
}
}