FOSUserBundle
FOSUserBundle copied to clipboard
UserProvider::refreshUser() should support unserialized Doctrine proxy objects
When implementing custom authentication methods (for example, via social networks) it is sometimes desirable to manually log in the user.
This is done via manually creating token and storing it in the security context, similar to the way it is done in LoginManager::loginUser() after user completes the registration:
$token = $this->createToken($firewallName, $user);
$this->securityContext->setToken($token);
The problems is that that in the code the $user object can be either an instance of the actual user class or a Doctrine proxy object which extends the user class.
When doctrine proxy objects is serialized into session, it looses the value of its' primary key (since it is a private variable). After deserialization, $user->getId() will always return zero.
Unforutnately, UserProvider::refreshUser() relies on $user->getId() method to reload user from session:
if (null === $reloadedUser = $this->userManager->findUserBy(array('id' => $user->getId()))) {
throw new UsernameNotFoundException(sprintf('User with ID "%d" could not be reloaded.', $user->getId()));
}
So even if you correctly put $user object in the security context and store it in session, UserProvider::refreshUser will not be able to reload it and user will not be authenticated.
There is a similar EntityUserProvider bundled with Symfony. In it, the identifier is obtained using the class metadata, which works correctly even for deserialized proxy objects:
Symfony\Bridge\Doctrine\Security\User\EntityUserProvider:
$id = $this->metadata->getIdentifierValues($user)
Are you using a custom serialize() method on your User Class?
No, i simply extend from original FOS user class. In it id property is protected, so there is no problem with serialization.
The problem with serialization appears when it is wrapped by Doctrine proxy. Doctrine overrides getId() method so that it uses internal private variable _$identifier, whose value is lost during serialization:
<?php
namespace Proxies\__CG__\MyBundle\Entity;
/**
* THIS CLASS WAS GENERATED BY THE DOCTRINE ORM. DO NOT EDIT THIS FILE.
*/
class User extends \MyBundle\Entity\User implements \Doctrine\ORM\Proxy\Proxy
{
...
private $_identifier;
....
public function getId()
{
if ($this->__isInitialized__ === false) {
return (int) $this->_identifier["id"];
}
$this->__load();
return parent::getId();
}
...
}
This should be reported as a bug in Doctrine IMO. Serializing a proxy should trigger its initialization so that the unserialization is not broken.
Note that I don't see how this can happen for FOSUserBundle: we implement Serializable
so the serialization will trigger a method call on the proxy, which will already initialize it
I am not sure this can be classified as bug...
Serializing a proxy correctly triggers __load() and object properties are loaded from DB.
The problem arises from isInitialized property of the proxy object. It is not saved during serialization, so after unserialization of proxy it will be FALSE even if the object was originally initialized. And getId() method will try to extract id from $this->_identifier['id'] and fail.
This makes sense to some degree - formally model is not initialized because it is not in the entity manager yet, it needs to be merged.
Probably it can be solved by adding additional check that $this->_identifier is not null to the getId() method of Proxies
if ($this->__isInitialized__ === false && $this->_identifier !== NULL) {
return (int) $this->_identifier["id"];
}
hmm, I think I remember of an issue related to proxies not being marked as uninitialized when unserialized
is called. I will look at it in Doctrine.