fixed user refreshing after unserialization
This commit is contained in:
parent
c9f08c0a68
commit
3c692bd160
@ -9,11 +9,21 @@ class EntityUserProvider implements UserProviderInterface
|
||||
{
|
||||
protected $repository;
|
||||
protected $property;
|
||||
protected $name;
|
||||
|
||||
public function __construct($em, $class, $property = null)
|
||||
public function __construct($em, $name, $class, $property = null)
|
||||
{
|
||||
$this->repository = $em->getRepository($class);
|
||||
$this->property = $property;
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function isAggregate()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -35,6 +45,14 @@ class EntityUserProvider implements UserProviderInterface
|
||||
throw new UsernameNotFoundException(sprintf('User "%s" not found.', $username));
|
||||
}
|
||||
|
||||
return $user;
|
||||
return array($user, $this->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function supports($providerName)
|
||||
{
|
||||
return $this->name === $providerName;
|
||||
}
|
||||
}
|
||||
|
@ -9,11 +9,21 @@ class DocumentUserProvider implements UserProviderInterface
|
||||
{
|
||||
protected $repository;
|
||||
protected $property;
|
||||
protected $name;
|
||||
|
||||
public function __construct($em, $class, $property = null)
|
||||
public function __construct($em, $name, $class, $property = null)
|
||||
{
|
||||
$this->repository = $em->getRepository($class);
|
||||
$this->property = $property;
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function isAggregate()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -35,6 +45,14 @@ class DocumentUserProvider implements UserProviderInterface
|
||||
throw new UsernameNotFoundException(sprintf('User "%s" not found.', $username));
|
||||
}
|
||||
|
||||
return $user;
|
||||
return array($user, $this->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function supports($providerName)
|
||||
{
|
||||
return $this->name === $providerName;
|
||||
}
|
||||
}
|
||||
|
@ -126,12 +126,22 @@ class SecurityExtension extends Extension
|
||||
|
||||
protected function createFirewalls($config, ContainerBuilder $container)
|
||||
{
|
||||
$providerIds = $this->createAuthenticationProviders($config, $container);
|
||||
$providerIds = $this->createUserProviders($config, $container);
|
||||
|
||||
if (!$firewalls = $this->fixConfig($config, 'firewall')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// make the ContextListener aware of the configured user providers
|
||||
$definition = $container->getDefinition('security.context_listener');
|
||||
$arguments = $definition->getArguments();
|
||||
$userProviders = array();
|
||||
foreach (array_keys($providerIds) as $userProviderId) {
|
||||
$userProviders[] = new Reference($userProviderId);
|
||||
}
|
||||
$arguments[1] = $userProviders;
|
||||
$definition->setArguments($arguments);
|
||||
|
||||
// load service templates
|
||||
$c = new ContainerBuilder($container->getParameterBag());
|
||||
$loader = new XmlFileLoader($c, array(__DIR__.'/../Resources/config', __DIR__.'/Resources/config'));
|
||||
@ -204,8 +214,8 @@ class SecurityExtension extends Extension
|
||||
// Logout listener
|
||||
if (array_key_exists('logout', $firewall)) {
|
||||
$listenerId = 'security.logout_listener.'.$id;
|
||||
$listener = $container->setDefinition($listenerId, clone $container->getDefinition('security.logout_listener'));
|
||||
|
||||
$listener = $container->setDefinition($listenerId, clone $container->getDefinition('security.logout_listener'));
|
||||
|
||||
$listeners[] = new Reference($listenerId);
|
||||
|
||||
$arguments = $listener->getArguments();
|
||||
@ -217,11 +227,11 @@ class SecurityExtension extends Extension
|
||||
$arguments[2] = $firewall['logout']['target'];
|
||||
}
|
||||
$listener->setArguments($arguments);
|
||||
|
||||
|
||||
if (!isset($firewall['stateless']) || !$firewall['stateless']) {
|
||||
$listener->addMethodCall('addHandler', array(new Reference('security.logout.handler.session')));
|
||||
}
|
||||
|
||||
|
||||
if (count($cookies = $this->fixConfig($firewall['logout'], 'cookie')) > 0) {
|
||||
$cookieHandlerId = 'security.logout.handler.cookie_clearing.'.$id;
|
||||
$cookieHandler = $container->setDefinition($cookieHandlerId, clone $container->getDefinition('security.logout.handler.cookie_clearing'));
|
||||
@ -308,7 +318,7 @@ class SecurityExtension extends Extension
|
||||
}
|
||||
|
||||
// Parses user providers and returns an array of their ids
|
||||
protected function createAuthenticationProviders($config, ContainerBuilder $container)
|
||||
protected function createUserProviders($config, ContainerBuilder $container)
|
||||
{
|
||||
$providers = $this->fixConfig($config, 'provider');
|
||||
if (!$providers) {
|
||||
@ -318,6 +328,11 @@ class SecurityExtension extends Extension
|
||||
$providerIds = array();
|
||||
foreach ($providers as $name => $provider) {
|
||||
list($id, $encoder) = $this->createUserDaoProvider($name, $provider, $container);
|
||||
|
||||
if (isset($providerIds[$id])) {
|
||||
throw new \RuntimeException(sprintf('Provider names must be unique. Duplicate entry for %s.', $id));
|
||||
}
|
||||
|
||||
$providerIds[$id] = $encoder;
|
||||
}
|
||||
|
||||
@ -340,16 +355,14 @@ class SecurityExtension extends Extension
|
||||
}
|
||||
|
||||
if (!$name) {
|
||||
$name = md5(serialize($provider));
|
||||
throw new \RuntimeException('You must define a name for each user provider.');
|
||||
}
|
||||
|
||||
$name = $this->getUserProviderId($name);
|
||||
$name = $this->getUserProviderId(strtolower($name));
|
||||
|
||||
// Existing DAO service provider
|
||||
if (isset($provider['id'])) {
|
||||
$container->setAlias($name, $provider['id']);
|
||||
|
||||
return array($name, $encoder);
|
||||
return array($provider['id'], $encoder);
|
||||
}
|
||||
|
||||
// Chain provider
|
||||
@ -364,6 +377,7 @@ class SecurityExtension extends Extension
|
||||
->register($name, '%security.user.provider.entity.class%')
|
||||
->setArguments(array(
|
||||
new Reference('security.user.entity_manager'),
|
||||
$name,
|
||||
$provider['entity']['class'],
|
||||
isset($provider['entity']['property']) ? $provider['entity']['property'] : null,
|
||||
));
|
||||
@ -377,6 +391,7 @@ class SecurityExtension extends Extension
|
||||
->register($name, '%security.user.provider.document.class%')
|
||||
->setArguments(array(
|
||||
new Reference('security.user.document_manager'),
|
||||
$name,
|
||||
$provider['document']['class'],
|
||||
isset($provider['document']['property']) ? $provider['document']['property'] : null,
|
||||
));
|
||||
@ -385,7 +400,10 @@ class SecurityExtension extends Extension
|
||||
}
|
||||
|
||||
// In-memory DAO provider
|
||||
$definition = $container->register($name, '%security.user.provider.in_memory.class%');
|
||||
$definition = $container
|
||||
->register($name, '%security.user.provider.in_memory.class%')
|
||||
->setArguments(array($name))
|
||||
;
|
||||
foreach ($this->fixConfig($provider, 'user') as $username => $user) {
|
||||
if (isset($user['name'])) {
|
||||
$username = $user['name'];
|
||||
@ -443,8 +461,6 @@ class SecurityExtension extends Extension
|
||||
return $listenerId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected function createExceptionListener($container, $id, $defaultEntryPoint)
|
||||
{
|
||||
$exceptionListenerId = 'security.exception_listener.'.$id;
|
||||
|
@ -156,6 +156,7 @@
|
||||
|
||||
<service id="security.context_listener" class="%security.context_listener.class%">
|
||||
<argument type="service" id="security.context" />
|
||||
<argument type="collection"></argument>
|
||||
<argument type="service" id="logger" on-invalid="null" />
|
||||
</service>
|
||||
</services>
|
||||
|
@ -6,7 +6,7 @@
|
||||
xsi:schemaLocation="http://www.symfony-project.org/schema/dic/services http://www.symfony-project.org/schema/dic/services/services-1.0.xsd">
|
||||
|
||||
<config>
|
||||
<provider>
|
||||
<provider name="foo">
|
||||
<user name="foo" password="foo" roles="ROLE_USER" />
|
||||
</provider>
|
||||
|
||||
|
@ -88,7 +88,9 @@ class Firewall
|
||||
|
||||
// save current listener instances
|
||||
$this->currentListeners = $listeners;
|
||||
$this->currentListeners[] = $exception;
|
||||
if (null !== $exception) {
|
||||
$this->currentListeners[] = $exception;
|
||||
}
|
||||
|
||||
// initiate the listener chain
|
||||
$e = $this->dispatcher->notifyUntil(new Event($request, 'core.security', array('request' => $request)));
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
namespace Symfony\Component\HttpKernel\Security\Firewall;
|
||||
|
||||
use Symfony\Component\Security\User\AccountInterface;
|
||||
use Symfony\Component\Security\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\SecurityContext;
|
||||
use Symfony\Component\HttpKernel\Log\LoggerInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||
@ -23,15 +25,18 @@ use Symfony\Component\Security\Authentication\Token\AnonymousToken;
|
||||
* ContextListener manages the SecurityContext persistence through a session.
|
||||
*
|
||||
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
class ContextListener implements ListenerInterface
|
||||
{
|
||||
protected $context;
|
||||
protected $logger;
|
||||
protected $userProviders;
|
||||
|
||||
public function __construct(SecurityContext $context, LoggerInterface $logger = null)
|
||||
public function __construct(SecurityContext $context, array $userProviders, LoggerInterface $logger = null)
|
||||
{
|
||||
$this->context = $context;
|
||||
$this->userProviders = $userProviders;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
@ -47,7 +52,7 @@ class ContextListener implements ListenerInterface
|
||||
$dispatcher->connect('core.security', array($this, 'read'), 0);
|
||||
$dispatcher->connect('core.response', array($this, 'write'), 0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@ -76,11 +81,11 @@ class ContextListener implements ListenerInterface
|
||||
|
||||
$token = unserialize($token);
|
||||
|
||||
$this->context->setToken($token);
|
||||
if (null !== $token && false === $token->isImmutable()) {
|
||||
$token = $this->refreshUser($token);
|
||||
}
|
||||
|
||||
// FIXME: If the user is not an object, it probably means that it is persisted with a DAO
|
||||
// we need to load it now (that does not happen right now as the Token serialize the user
|
||||
// even if it is an object -- see Token)
|
||||
$this->context->setToken($token);
|
||||
}
|
||||
}
|
||||
|
||||
@ -111,4 +116,61 @@ class ContextListener implements ListenerInterface
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the user by reloading it from the user provider
|
||||
*
|
||||
* @param TokenInterface $token
|
||||
* @return TokenInterface|null
|
||||
*/
|
||||
protected function refreshUser(TokenInterface $token)
|
||||
{
|
||||
$user = $token->getUser();
|
||||
if (!$user instanceof AccountInterface) {
|
||||
return $token;
|
||||
} else if (0 === strlen($username = (string) $token)) {
|
||||
return $token;
|
||||
} else if (null === $providerName = $token->getUserProviderName()) {
|
||||
return $token;
|
||||
}
|
||||
|
||||
if (null !== $this->logger) {
|
||||
$this->logger->debug(sprintf('Reloading user from user provider "%s".', $providerName));
|
||||
}
|
||||
|
||||
foreach ($this->userProviders as $provider) {
|
||||
if (!$provider->isAggregate() && $provider->supports($providerName)) {
|
||||
try {
|
||||
$result = $provider->loadUserByUsername($username);
|
||||
|
||||
if (!is_array($result) || 2 !== count($result)) {
|
||||
throw new \RuntimeException('Provider returned an invalid result.');
|
||||
}
|
||||
|
||||
list($cUser, $cProviderName) = $result;
|
||||
} catch (\Exception $ex) {
|
||||
if (null !== $this->logger) {
|
||||
$this->logger->debug(sprintf('An exception occurred while reloading the user: '.$ex->getMessage()));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($providerName !== $cProviderName) {
|
||||
throw new \RuntimeException(sprintf('User was loaded from different provider. Requested "%s", Used: "%s"', $providerName, $cProviderName));
|
||||
}
|
||||
|
||||
$token->setRoles($user->getRoles());
|
||||
$token->setUser($cUser);
|
||||
|
||||
if (false === $cUser->equals($user)) {
|
||||
$token->setAuthenticated(false);
|
||||
}
|
||||
|
||||
return $token;
|
||||
}
|
||||
}
|
||||
|
||||
throw new \RuntimeException(sprintf('There is no user provider named "%s".', $providerName));
|
||||
}
|
||||
}
|
||||
|
@ -55,12 +55,19 @@ class DaoAuthenticationProvider extends UserAuthenticationProvider
|
||||
*/
|
||||
protected function checkAuthentication(AccountInterface $account, UsernamePasswordToken $token)
|
||||
{
|
||||
if (!$presentedPassword = (string) $token->getCredentials()) {
|
||||
throw new BadCredentialsException('Bad credentials');
|
||||
}
|
||||
$user = $token->getUser();
|
||||
if ($user instanceof AccountInterface) {
|
||||
if ($account->getPassword() !== $user->getPassword()) {
|
||||
throw new BadCredentialsException('The credentials were changed from another session.');
|
||||
}
|
||||
} else {
|
||||
if (!$presentedPassword = (string) $token->getCredentials()) {
|
||||
throw new BadCredentialsException('Bad credentials');
|
||||
}
|
||||
|
||||
if (!$this->passwordEncoder->isPasswordValid($account->getPassword(), $presentedPassword, $account->getSalt())) {
|
||||
throw new BadCredentialsException('Bad credentials');
|
||||
if (!$this->passwordEncoder->isPasswordValid($account->getPassword(), $presentedPassword, $account->getSalt())) {
|
||||
throw new BadCredentialsException('Bad credentials');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,19 +76,30 @@ class DaoAuthenticationProvider extends UserAuthenticationProvider
|
||||
*/
|
||||
protected function retrieveUser($username, UsernamePasswordToken $token)
|
||||
{
|
||||
$user = null;
|
||||
$user = $token->getUser();
|
||||
if ($user instanceof AccountInterface) {
|
||||
return array($user, $token->getUserProviderName());
|
||||
}
|
||||
|
||||
$result = null;
|
||||
try {
|
||||
$user = $this->userProvider->loadUserByUsername($username);
|
||||
$result = $this->userProvider->loadUserByUsername($username);
|
||||
} catch (UsernameNotFoundException $notFound) {
|
||||
throw $notFound;
|
||||
} catch (\Exception $repositoryProblem) {
|
||||
throw new AuthenticationServiceException($repositoryProblem->getMessage(), $token, 0, $repositoryProblem);
|
||||
}
|
||||
|
||||
if (!$user instanceof AccountInterface) {
|
||||
if (!is_array($result) || 2 !== count($result)) {
|
||||
throw new AuthenticationServiceException('User provider did not return an array, or array had invalid format.');
|
||||
}
|
||||
if (!$result[0] instanceof AccountInterface) {
|
||||
throw new AuthenticationServiceException('The user provider must return an AccountInterface object.');
|
||||
}
|
||||
if (empty($result[1])) {
|
||||
throw new AuthenticationServiceException('The user provider must return a non-empty user provider name.');
|
||||
}
|
||||
|
||||
return $user;
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ abstract class UserAuthenticationProvider implements AuthenticationProviderInter
|
||||
$username = null === $token->getUser() ? 'NONE_PROVIDED' : (string) $token;
|
||||
|
||||
try {
|
||||
$user = $this->retrieveUser($username, $token);
|
||||
$result = $this->retrieveUser($username, $token);
|
||||
} catch (UsernameNotFoundException $notFound) {
|
||||
if ($this->hideUserNotFoundExceptions) {
|
||||
throw new BadCredentialsException('Bad credentials', 0, $notFound);
|
||||
@ -63,15 +63,16 @@ abstract class UserAuthenticationProvider implements AuthenticationProviderInter
|
||||
throw $notFound;
|
||||
}
|
||||
|
||||
if (!$user instanceof AccountInterface) {
|
||||
throw new AuthenticationServiceException('The retrieveUser() methods must return an AccountInterface object.');
|
||||
if (!is_array($result) || 2 !== count($result)) {
|
||||
throw new AuthenticationServiceException('retrieveUser() did not return an array, or array had invalid format.');
|
||||
}
|
||||
list($user, $userProviderName) = $result;
|
||||
|
||||
$this->accountChecker->checkPreAuth($user);
|
||||
$this->checkAuthentication($user, $token);
|
||||
$this->accountChecker->checkPostAuth($user);
|
||||
|
||||
return new UsernamePasswordToken($user, $token->getCredentials(), $user->getRoles());
|
||||
return new UsernamePasswordToken($user, $token->getCredentials(), $userProviderName, $user->getRoles());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -88,7 +89,7 @@ abstract class UserAuthenticationProvider implements AuthenticationProviderInter
|
||||
* @param string $username The username to retrieve
|
||||
* @param UsernamePasswordToken $token The Token
|
||||
*
|
||||
* @return AccountInterface The user
|
||||
* @return array The user
|
||||
*
|
||||
* @throws AuthenticationException if the credentials could not be validated
|
||||
*/
|
||||
|
@ -19,12 +19,14 @@ use Symfony\Component\Security\User\AccountInterface;
|
||||
* Base class for Token instances.
|
||||
*
|
||||
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
abstract class Token implements TokenInterface
|
||||
{
|
||||
protected $roles;
|
||||
protected $authenticated;
|
||||
protected $user;
|
||||
protected $userProviderName;
|
||||
protected $credentials;
|
||||
protected $immutable;
|
||||
|
||||
@ -35,13 +37,7 @@ abstract class Token implements TokenInterface
|
||||
*/
|
||||
public function __construct(array $roles = array())
|
||||
{
|
||||
$this->roles = array();
|
||||
foreach ($roles as $role) {
|
||||
if (is_string($role)) {
|
||||
$role = new Role((string) $role);
|
||||
}
|
||||
$this->addRole($role);
|
||||
}
|
||||
$this->setRoles($roles);
|
||||
$this->authenticated = false;
|
||||
$this->immutable = false;
|
||||
}
|
||||
@ -53,6 +49,10 @@ abstract class Token implements TokenInterface
|
||||
*/
|
||||
public function addRole(RoleInterface $role)
|
||||
{
|
||||
if ($this->immutable) {
|
||||
throw new \LogicException('This token is considered immutable.');
|
||||
}
|
||||
|
||||
$this->roles[] = $role;
|
||||
}
|
||||
|
||||
@ -64,6 +64,22 @@ abstract class Token implements TokenInterface
|
||||
return $this->roles;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function setRoles(array $roles)
|
||||
{
|
||||
$this->roles = array();
|
||||
|
||||
foreach ($roles as $role) {
|
||||
if (is_string($role)) {
|
||||
$role = new Role($role);
|
||||
}
|
||||
|
||||
$this->addRole($role);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@ -91,6 +107,10 @@ abstract class Token implements TokenInterface
|
||||
*/
|
||||
public function setAuthenticated($authenticated)
|
||||
{
|
||||
if ($this->immutable) {
|
||||
throw new \LogicException('This token is considered immutable.');
|
||||
}
|
||||
|
||||
$this->authenticated = (Boolean) $authenticated;
|
||||
}
|
||||
|
||||
@ -110,11 +130,33 @@ abstract class Token implements TokenInterface
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function setUser($user)
|
||||
{
|
||||
if ($this->immutable) {
|
||||
throw new \LogicException('This token is considered immutable.');
|
||||
}
|
||||
|
||||
if (!is_string($user) && !is_object($user)) {
|
||||
throw new \InvalidArgumentException('$user must be an object, or a primitive string.');
|
||||
} else if (is_object($user) && !method_exists($user, '__toString')) {
|
||||
throw new \InvalidArgumentException('If $user is an object, it must implement __toString().');
|
||||
}
|
||||
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function eraseCredentials()
|
||||
{
|
||||
if ($this->immutable) {
|
||||
throw new \LogicException('This token is considered immutable.');
|
||||
}
|
||||
|
||||
if ($this->getCredentials() instanceof AccountInterface) {
|
||||
$this->getCredentials()->eraseCredentials();
|
||||
}
|
||||
@ -124,6 +166,14 @@ abstract class Token implements TokenInterface
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getUserProviderName()
|
||||
{
|
||||
return $this->userProviderName;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@ -135,9 +185,9 @@ abstract class Token implements TokenInterface
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setImmutable($value)
|
||||
public function setImmutable()
|
||||
{
|
||||
$this->immutable = (Boolean) $value;
|
||||
$this->immutable = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -145,9 +195,7 @@ abstract class Token implements TokenInterface
|
||||
*/
|
||||
public function serialize()
|
||||
{
|
||||
// FIXME: don't serialize the user object, just the username (see ContextListener)
|
||||
//return serialize(array((string) $this, $this->credentials, $this->authenticated, $this->roles, $this->immutable));
|
||||
return serialize(array($this->user, $this->credentials, $this->authenticated, $this->roles, $this->immutable));
|
||||
return serialize(array($this->user, $this->userProviderName, $this->credentials, $this->authenticated, $this->roles, $this->immutable));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -155,6 +203,6 @@ abstract class Token implements TokenInterface
|
||||
*/
|
||||
public function unserialize($serialized)
|
||||
{
|
||||
list($this->user, $this->credentials, $this->authenticated, $this->roles, $this->immutable) = unserialize($serialized);
|
||||
list($this->user, $this->userProviderName, $this->credentials, $this->authenticated, $this->roles, $this->immutable) = unserialize($serialized);
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
namespace Symfony\Component\Security\Authentication\Token;
|
||||
|
||||
use Symfony\Component\Security\User\AccountInterface;
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
@ -32,6 +34,14 @@ interface TokenInterface extends \Serializable
|
||||
*/
|
||||
function getRoles();
|
||||
|
||||
/**
|
||||
* Sets the user's roles
|
||||
*
|
||||
* @param array $roles
|
||||
* @return void
|
||||
*/
|
||||
function setRoles(array $roles);
|
||||
|
||||
/**
|
||||
* Returns the user credentials.
|
||||
*
|
||||
@ -40,19 +50,28 @@ interface TokenInterface extends \Serializable
|
||||
function getCredentials();
|
||||
|
||||
/**
|
||||
* Checks whether the token is immutable or not.
|
||||
* Returns a user representation.
|
||||
*
|
||||
* @return Boolean true if the token is immutable, false otherwise
|
||||
*/
|
||||
function isImmutable();
|
||||
|
||||
/**
|
||||
* Returns a user instance.
|
||||
*
|
||||
* @return object The User instance
|
||||
* @return mixed either returns an object which implements __toString(), or
|
||||
* a primitive string is returned.
|
||||
*/
|
||||
function getUser();
|
||||
|
||||
/**
|
||||
* Sets the user.
|
||||
*
|
||||
* @param mixed $user can either be an object which implements __toString(), or
|
||||
* only a primitive string
|
||||
*/
|
||||
function setUser($user);
|
||||
|
||||
/**
|
||||
* Returns a unique id for the user provider that was used to retrieve the user
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function getUserProviderName();
|
||||
|
||||
/**
|
||||
* Checks if the user is authenticated or not.
|
||||
*
|
||||
@ -67,6 +86,22 @@ interface TokenInterface extends \Serializable
|
||||
*/
|
||||
function setAuthenticated($isAuthenticated);
|
||||
|
||||
/**
|
||||
* Whether this token is considered immutable
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
function isImmutable();
|
||||
|
||||
/**
|
||||
* Marks this token as immutable. This change cannot be reversed.
|
||||
*
|
||||
* You'll need to create a new token if you want a mutable token again.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function setImmutable();
|
||||
|
||||
/**
|
||||
* Removes sensitive information from the token.
|
||||
*/
|
||||
|
@ -21,12 +21,13 @@ class UsernamePasswordToken extends Token
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct($user, $credentials, array $roles = array())
|
||||
public function __construct($user, $credentials, $userProviderName = null, array $roles = array())
|
||||
{
|
||||
parent::__construct($roles);
|
||||
|
||||
$this->user = $user;
|
||||
$this->setUser($user);
|
||||
$this->credentials = $credentials;
|
||||
$this->userProviderName = $userProviderName;
|
||||
|
||||
parent::setAuthenticated((Boolean) count($roles));
|
||||
}
|
||||
|
@ -57,4 +57,16 @@ interface AccountInterface
|
||||
* Removes sensitive data from the user.
|
||||
*/
|
||||
function eraseCredentials();
|
||||
|
||||
/**
|
||||
* The equality comparison should neither be done by referential equality
|
||||
* nor by comparing identities (i.e. getId() === getId()).
|
||||
*
|
||||
* However, you do not need to compare every attribute, but only those that
|
||||
* are relevant for assessing whether re-authentication is required.
|
||||
*
|
||||
* @param AccountInterface $account
|
||||
* @return Boolean
|
||||
*/
|
||||
function equals(AccountInterface $account);
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ use Symfony\Component\Security\Exception\UsernameNotFoundException;
|
||||
class InMemoryUserProvider implements UserProviderInterface
|
||||
{
|
||||
protected $users;
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
@ -32,8 +33,9 @@ class InMemoryUserProvider implements UserProviderInterface
|
||||
* an array of attributes: 'password', 'enabled', and 'roles'.
|
||||
*
|
||||
* @param array $users An array of users
|
||||
* @param string $name
|
||||
*/
|
||||
public function __construct(array $users = array())
|
||||
public function __construct($name, array $users = array())
|
||||
{
|
||||
foreach ($users as $username => $attributes) {
|
||||
$password = isset($attributes['password']) ? $attributes['password'] : null;
|
||||
@ -43,6 +45,8 @@ class InMemoryUserProvider implements UserProviderInterface
|
||||
|
||||
$this->createUser($user);
|
||||
}
|
||||
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -59,6 +63,14 @@ class InMemoryUserProvider implements UserProviderInterface
|
||||
$this->users[strtolower($user->getUsername())] = $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function isAggregate()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@ -70,7 +82,15 @@ class InMemoryUserProvider implements UserProviderInterface
|
||||
|
||||
$user = $this->users[strtolower($username)];
|
||||
|
||||
return new User($user->getUsername(), $user->getPassword(), $user->getRoles(), $user->isEnabled(), $user->isAccountNonExpired(),
|
||||
$user->isCredentialsNonExpired(), $user->isAccountNonLocked());
|
||||
return array(new User($user->getUsername(), $user->getPassword(), $user->getRoles(), $user->isEnabled(), $user->isAccountNonExpired(),
|
||||
$user->isCredentialsNonExpired(), $user->isAccountNonLocked()), $this->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function supports($providerName)
|
||||
{
|
||||
return $this->name === $providerName;
|
||||
}
|
||||
}
|
||||
|
@ -121,4 +121,44 @@ class User implements AdvancedAccountInterface
|
||||
{
|
||||
$this->password = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function equals(AccountInterface $account)
|
||||
{
|
||||
if (!$account instanceof User) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->password !== $account->getPassword()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->getSalt() !== $account->getSalt()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->username !== $account->getUsername()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->accountNonExpired !== $account->isAccountNonExpired()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->accountNonLocked !== $account->isAccountNonLocked()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->credentialsNonExpired !== $account->isCredentialsNonExpired()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->enabled !== $account->isEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,6 @@
|
||||
|
||||
namespace Symfony\Component\Security\User;
|
||||
|
||||
use Symfony\Component\Security\Exception\UsernameNotFoundException;
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
@ -21,6 +19,13 @@ use Symfony\Component\Security\Exception\UsernameNotFoundException;
|
||||
*/
|
||||
interface UserProviderInterface
|
||||
{
|
||||
/**
|
||||
* Whether this provider is an aggregate of user providers
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
function isAggregate();
|
||||
|
||||
/**
|
||||
* Loads the user for the given username.
|
||||
*
|
||||
@ -29,9 +34,19 @@ interface UserProviderInterface
|
||||
*
|
||||
* @param string $username The username
|
||||
*
|
||||
* @return AccountInterface A user instance
|
||||
* @return array of the form: array(AccountInterface, string) with the
|
||||
* implementation of AccountInterface, and the name of the provider
|
||||
* that was used to retrieve it
|
||||
*
|
||||
* @throws UsernameNotFoundException if the user is not found
|
||||
*/
|
||||
function loadUserByUsername($username);
|
||||
|
||||
/**
|
||||
* Determines whether this provider supports the given provider name
|
||||
*
|
||||
* @param string $providerName
|
||||
* @return Boolean
|
||||
*/
|
||||
function supports($providerName);
|
||||
}
|
||||
|
@ -62,6 +62,32 @@ class DaoAuthenticationProviderTest extends \PHPUnit_Framework_TestCase
|
||||
$method->invoke($provider, 'fabien', $this->getSupportedToken());
|
||||
}
|
||||
|
||||
public function testRetrieveUserReturnsUserFromTokenOnReauthentication()
|
||||
{
|
||||
$userProvider = $this->getMock('Symfony\Component\Security\User\UserProviderInterface');
|
||||
$userProvider->expects($this->never())
|
||||
->method('loadUserByUsername')
|
||||
;
|
||||
|
||||
$user = $this->getMock('Symfony\Component\Security\User\AccountInterface');
|
||||
$token = $this->getSupportedToken();
|
||||
$token->expects($this->once())
|
||||
->method('getUser')
|
||||
->will($this->returnValue($user))
|
||||
;
|
||||
$token->expects($this->once())
|
||||
->method('getUserProviderName')
|
||||
->will($this->returnValue('foo'))
|
||||
;
|
||||
|
||||
$provider = new DaoAuthenticationProvider($userProvider, $this->getMock('Symfony\Component\Security\User\AccountCheckerInterface'));
|
||||
$reflection = new \ReflectionMethod($provider, 'retrieveUser');
|
||||
$reflection->setAccessible(true);
|
||||
$result = $reflection->invoke($provider, null, $token);
|
||||
|
||||
$this->assertSame(array($user, 'foo'), $result);
|
||||
}
|
||||
|
||||
public function testRetrieveUser()
|
||||
{
|
||||
$user = $this->getMock('Symfony\Component\Security\User\AccountInterface');
|
||||
@ -69,14 +95,14 @@ class DaoAuthenticationProviderTest extends \PHPUnit_Framework_TestCase
|
||||
$userProvider = $this->getMock('Symfony\Component\Security\User\UserProviderInterface');
|
||||
$userProvider->expects($this->once())
|
||||
->method('loadUserByUsername')
|
||||
->will($this->returnValue($user))
|
||||
->will($this->returnValue($result = array($user, 'foo')))
|
||||
;
|
||||
|
||||
$provider = new DaoAuthenticationProvider($userProvider, $this->getMock('Symfony\Component\Security\User\AccountCheckerInterface'));
|
||||
$method = new \ReflectionMethod($provider, 'retrieveUser');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$this->assertSame($user, $method->invoke($provider, 'fabien', $this->getSupportedToken()));
|
||||
$this->assertSame($result, $method->invoke($provider, 'fabien', $this->getSupportedToken()));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -121,6 +147,59 @@ class DaoAuthenticationProviderTest extends \PHPUnit_Framework_TestCase
|
||||
$method->invoke($provider, $this->getMock('Symfony\Component\Security\User\AccountInterface'), $token);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Symfony\Component\Security\Exception\BadCredentialsException
|
||||
*/
|
||||
public function testCheckAuthenticationDoesNotReauthenticateWhenPasswordHasChanged()
|
||||
{
|
||||
$user = $this->getMock('Symfony\Component\Security\User\AccountInterface');
|
||||
$user->expects($this->once())
|
||||
->method('getPassword')
|
||||
->will($this->returnValue('foo'))
|
||||
;
|
||||
|
||||
$token = $this->getSupportedToken();
|
||||
$token->expects($this->once())
|
||||
->method('getUser')
|
||||
->will($this->returnValue($user));
|
||||
|
||||
$dbUser = $this->getMock('Symfony\Component\Security\User\AccountInterface');
|
||||
$dbUser->expects($this->once())
|
||||
->method('getPassword')
|
||||
->will($this->returnValue('newFoo'))
|
||||
;
|
||||
|
||||
$provider = $this->getProvider(false, false, null);
|
||||
$reflection = new \ReflectionMethod($provider, 'checkAuthentication');
|
||||
$reflection->setAccessible(true);
|
||||
$reflection->invoke($provider, $dbUser, $token);
|
||||
}
|
||||
|
||||
public function testCheckAuthenticationWhenTokenNeedsReauthenticationWorksWithoutOriginalCredentials()
|
||||
{
|
||||
$user = $this->getMock('Symfony\Component\Security\User\AccountInterface');
|
||||
$user->expects($this->once())
|
||||
->method('getPassword')
|
||||
->will($this->returnValue('foo'))
|
||||
;
|
||||
|
||||
$token = $this->getSupportedToken();
|
||||
$token->expects($this->once())
|
||||
->method('getUser')
|
||||
->will($this->returnValue($user));
|
||||
|
||||
$dbUser = $this->getMock('Symfony\Component\Security\User\AccountInterface');
|
||||
$dbUser->expects($this->once())
|
||||
->method('getPassword')
|
||||
->will($this->returnValue('foo'))
|
||||
;
|
||||
|
||||
$provider = $this->getProvider(false, false, null);
|
||||
$reflection = new \ReflectionMethod($provider, 'checkAuthentication');
|
||||
$reflection->setAccessible(true);
|
||||
$reflection->invoke($provider, $dbUser, $token);
|
||||
}
|
||||
|
||||
public function testCheckAuthentication()
|
||||
{
|
||||
$encoder = $this->getMock('Symfony\Component\Security\Encoder\PasswordEncoderInterface');
|
||||
@ -144,7 +223,7 @@ class DaoAuthenticationProviderTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
protected function getSupportedToken()
|
||||
{
|
||||
return $this->getMock('Symfony\Component\Security\Authentication\Token\UsernamePasswordToken', array('getCredentials'), array(), '', false);
|
||||
return $this->getMock('Symfony\Component\Security\Authentication\Token\UsernamePasswordToken', array('getCredentials', 'getUser', 'getUserProviderName'), array(), '', false);
|
||||
}
|
||||
|
||||
protected function getProvider($user = false, $userChecker = false, $passwordEncoder = null)
|
||||
|
@ -86,7 +86,7 @@ class UserAuthenticationProviderTest extends \PHPUnit_Framework_TestCase
|
||||
$provider = $this->getProvider($userChecker);
|
||||
$provider->expects($this->once())
|
||||
->method('retrieveUser')
|
||||
->will($this->returnValue($this->getMock('Symfony\Component\Security\User\AccountInterface')))
|
||||
->will($this->returnValue(array($this->getMock('Symfony\Component\Security\User\AccountInterface'), 'foo')))
|
||||
;
|
||||
|
||||
$provider->authenticate($this->getSupportedToken());
|
||||
@ -106,7 +106,7 @@ class UserAuthenticationProviderTest extends \PHPUnit_Framework_TestCase
|
||||
$provider = $this->getProvider($userChecker);
|
||||
$provider->expects($this->once())
|
||||
->method('retrieveUser')
|
||||
->will($this->returnValue($this->getMock('Symfony\Component\Security\User\AccountInterface')))
|
||||
->will($this->returnValue(array($this->getMock('Symfony\Component\Security\User\AccountInterface'), 'foo')))
|
||||
;
|
||||
|
||||
$provider->authenticate($this->getSupportedToken());
|
||||
@ -120,7 +120,7 @@ class UserAuthenticationProviderTest extends \PHPUnit_Framework_TestCase
|
||||
$provider = $this->getProvider();
|
||||
$provider->expects($this->once())
|
||||
->method('retrieveUser')
|
||||
->will($this->returnValue($this->getMock('Symfony\Component\Security\User\AccountInterface')))
|
||||
->will($this->returnValue(array($this->getMock('Symfony\Component\Security\User\AccountInterface'), 'foo')))
|
||||
;
|
||||
$provider->expects($this->once())
|
||||
->method('checkAuthentication')
|
||||
@ -141,7 +141,7 @@ class UserAuthenticationProviderTest extends \PHPUnit_Framework_TestCase
|
||||
$provider = $this->getProvider();
|
||||
$provider->expects($this->once())
|
||||
->method('retrieveUser')
|
||||
->will($this->returnValue($user))
|
||||
->will($this->returnValue(array($user, 'foo')))
|
||||
;
|
||||
|
||||
$token = $this->getSupportedToken();
|
||||
@ -154,6 +154,7 @@ class UserAuthenticationProviderTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
$this->assertInstanceOf('Symfony\Component\Security\Authentication\Token\UsernamePasswordToken', $authToken);
|
||||
$this->assertSame($user, $authToken->getUser());
|
||||
$this->assertSame('foo', $authToken->getUserProviderName());
|
||||
$this->assertEquals(array(new Role('ROLE_FOO')), $authToken->getRoles());
|
||||
$this->assertEquals('foo', $authToken->getCredentials());
|
||||
}
|
||||
|
@ -15,17 +15,27 @@ use Symfony\Component\Security\Role\Role;
|
||||
|
||||
class Token extends BaseToken
|
||||
{
|
||||
public function setUser($user)
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
public function setCredentials($credentials)
|
||||
{
|
||||
$this->credentials = $credentials;
|
||||
}
|
||||
}
|
||||
|
||||
class TestUser
|
||||
{
|
||||
protected $name;
|
||||
|
||||
public function __construct($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
}
|
||||
|
||||
class TokenTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testMagicToString()
|
||||
@ -34,7 +44,7 @@ class TokenTest extends \PHPUnit_Framework_TestCase
|
||||
$token->setUser('fabien');
|
||||
$this->assertEquals('fabien', (string) $token);
|
||||
|
||||
$token->setUser(new \stdClass('fabien'));
|
||||
$token->setUser(new TestUser('fabien'));
|
||||
$this->assertEquals('n/a', (string) $token);
|
||||
|
||||
$user = $this->getMock('Symfony\Component\Security\User\AccountInterface');
|
||||
@ -120,10 +130,30 @@ class TokenTest extends \PHPUnit_Framework_TestCase
|
||||
$token = new Token();
|
||||
$this->assertFalse($token->isImmutable());
|
||||
|
||||
$token->setImmutable(true);
|
||||
$token->setImmutable();
|
||||
$this->assertTrue($token->isImmutable());
|
||||
}
|
||||
|
||||
$token->setImmutable(false);
|
||||
$this->assertFalse($token->isImmutable());
|
||||
/**
|
||||
* @expectedException \LogicException
|
||||
* @dataProvider getImmutabilityTests
|
||||
*/
|
||||
public function testImmutabilityIsEnforced($setter, $value)
|
||||
{
|
||||
$token = new Token();
|
||||
$token->setImmutable(true);
|
||||
$token->$setter($value);
|
||||
}
|
||||
|
||||
public function getImmutabilityTests()
|
||||
{
|
||||
return array(
|
||||
array('setUser', 'foo'),
|
||||
array('eraseCredentials', null),
|
||||
array('setAuthenticated', true),
|
||||
array('setAuthenticated', false),
|
||||
array('addRole', new Role('foo')),
|
||||
array('setRoles', array('foo', 'asdf')),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ class UsernamePasswordTokenTest extends \PHPUnit_Framework_TestCase
|
||||
$token = new UsernamePasswordToken('foo', 'bar');
|
||||
$this->assertFalse($token->isAuthenticated());
|
||||
|
||||
$token = new UsernamePasswordToken('foo', 'bar', array('ROLE_FOO'));
|
||||
$token = new UsernamePasswordToken('foo', 'bar', null, array('ROLE_FOO'));
|
||||
$this->assertEquals(array(new Role('ROLE_FOO')), $token->getRoles());
|
||||
$this->assertTrue($token->isAuthenticated());
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ class InMemoryUserProviderTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testConstructor()
|
||||
{
|
||||
$provider = new InMemoryUserProvider(array(
|
||||
$provider = new InMemoryUserProvider('foo', array(
|
||||
'fabien' => array(
|
||||
'password' => 'foo',
|
||||
'enabled' => false,
|
||||
@ -25,7 +25,8 @@ class InMemoryUserProviderTest extends \PHPUnit_Framework_TestCase
|
||||
),
|
||||
));
|
||||
|
||||
$user = $provider->loadUserByUsername('fabien');
|
||||
list($user, $providerName) = $provider->loadUserByUsername('fabien');
|
||||
$this->assertSame('foo', $providerName);
|
||||
$this->assertEquals('foo', $user->getPassword());
|
||||
$this->assertEquals(array('ROLE_USER'), $user->getRoles());
|
||||
$this->assertFalse($user->isEnabled());
|
||||
@ -33,10 +34,11 @@ class InMemoryUserProviderTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
public function testCreateUser()
|
||||
{
|
||||
$provider = new InMemoryUserProvider();
|
||||
$provider = new InMemoryUserProvider('foo');
|
||||
$provider->createUser(new User('fabien', 'foo'));
|
||||
|
||||
$this->assertEquals('foo', $provider->loadUserByUsername('fabien')->getPassword());
|
||||
list($user, $providerName) = $provider->loadUserByUsername('fabien');
|
||||
$this->assertEquals('foo', $user->getPassword());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -44,7 +46,7 @@ class InMemoryUserProviderTest extends \PHPUnit_Framework_TestCase
|
||||
*/
|
||||
public function testCreateUserAlreadyExist()
|
||||
{
|
||||
$provider = new InMemoryUserProvider();
|
||||
$provider = new InMemoryUserProvider('foo');
|
||||
$provider->createUser(new User('fabien', 'foo'));
|
||||
$provider->createUser(new User('fabien', 'foo'));
|
||||
}
|
||||
@ -54,7 +56,7 @@ class InMemoryUserProviderTest extends \PHPUnit_Framework_TestCase
|
||||
*/
|
||||
public function testLoadUserByUsernameDoesNotExist()
|
||||
{
|
||||
$provider = new InMemoryUserProvider();
|
||||
$provider = new InMemoryUserProvider('foo');
|
||||
$provider->loadUserByUsername('fabien');
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user