Reputation: 7759
I've implemented a simple password change algorithm. Here's the code :
public function postChangePasswordAction(Request $request, $username)
{
$em = $this->getDoctrine()->getManager();
$user = $em->getRepository("TskUserBundle:User")->findOneByUsername($username);
if(!$user)
{
throw $this->createNotFoundException("User not found!");
}
$form = $this->createPasswordChangeForm();
$form->handleRequest($request);
$old_password = $form->get('old_password')->getData();
$new_password = $form->get("new_password")->getData();
if($form->isValid())
{
$old_password_encoded = $this->get("tsk_encoder")->encode($user, $old_password, $user->getSalt());
if($old_password_encoded == $user->getPassword())
{
$user->setPlainPassword($new_password);
$user->setFirstname("rofa");
$em->flush();
return $this->redirect($this->generateUrl("tsk_user_profile", array("username"=>$user->getUsername())));
} else { return new Response($old_password_encoded."\n".$user->getPassword());}
}
return $this->render("TskUserBundle:User:change_password.html.twig", array("username" => $user->getUsername(), "form"=>$form->createView()));
}
My User
entity is:
<?php
namespace Tsk\UserBundle\Entity;
use Symfony\Component\Security\Core\User\AdvancedUserInterface;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Validator\Constraints as Assert;
/**
* @ORM\Entity(repositoryClass="Tsk\UserBundle\Repository\UserRepository")
* @ORM\Table(name="users")
* @ORM\HasLifecycleCallbacks
* @Assert\GroupSequence({"FormCreate", "FormEdit", "User"})
*/
class User implements AdvancedUserInterface, \Serializable
{
/**
* @ORM\Column(type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @ORM\Column(type="string", length=25, unique=true)
* @Assert\NotBlank(groups={"FormEdit", "FormCreate"})
*/
protected $username;
/**
* @Assert\NotBlank(groups={"FormCreate"})
* @Assert\Length(min="4", minMessage="Password must be 4 characters at least", groups={"FormCreate"})
*/
protected $plain_password;
/**
* @ORM\Column(type="string", length=128)
* @Assert\NotBlank
*/
protected $password;
/**
* @ORM\Column(type="string", length=50, unique=true)
* @Assert\Email(groups={"FormEdit", "FormCreate"});
*/
protected $email;
/**
* @ORM\Column(type="boolean")
*/
protected $is_active;
/**
* @ORM\Column(type="string", length=59)
* @Assert\NotBlank(groups={"FormEdit", "FormCreate"})
*/
protected $firstname;
/**
* @ORM\Column(type="string", length=59)
* @Assert\NotBlank(groups={"FormEdit", "FormCreate"})
*/
protected $lastname;
/**
* @ORM\ManyToMany(targetEntity="Role", inversedBy="users", cascade={"persist"})
* @Assert\NotBlank
*/
protected $roles;
/**
* @ORM\Column(type="string", length=50)
*/
protected $salt;
public function __construct()
{
$this->roles = new ArrayCollection();
$this->salt = md5(uniqid(null, true));
}
/**
* @ORM\PrePersist
*/
public function setIsActiveValue()
{
$this->setIsActive(true);
}
public function getPlainPassword()
{
return $this->plain_password;
}
public function setPlainPassword($plain_password)
{
$this->plain_password = $plain_password;
return $this;
}
public function setPassword($password)
{
$this->password = $password;
return $this;
}
public function isAccountNonExpired()
{
return true;
}
public function isAccountNonLocked()
{
return true;
}
public function isCredentialsNonExpired()
{
return true;
}
public function isEnabled()
{
return $this->getIsActive();
}
public function getRoles()
{
return $this->roles->toArray();
}
public function getPassword()
{
return $this->password;
}
public function getSalt()
{
return $this->salt;
}
public function getUsername()
{
return $this->username;
}
/**
* Removes sensitive data from the user.
*
* This is important if, at any given point, sensitive information like
* the plain-text password is stored on this object.
*/
public function eraseCredentials()
{
}
public function serialize()
{
return serialize(array($this->getUsername()));
}
public function unserialize($serialized)
{
list($this->username) = unserialize($serialized);
}
public function getId()
{
return $this->id;
}
public function setUsername($username)
{
$this->username = $username;
return $this;
}
public function setEmail($email)
{
$this->email = $email;
return $this;
}
public function getEmail()
{
return $this->email;
}
public function setIsActive($isActive)
{
$this->is_active = $isActive;
return $this;
}
public function getIsActive()
{
return $this->is_active;
}
public function setFirstname($firstname)
{
$this->firstname = $firstname;
return $this;
}
public function getFirstname()
{
return $this->firstname;
}
public function setLastname($lastname)
{
$this->lastname = $lastname;
return $this;
}
public function getLastname()
{
return $this->lastname;
}
public function addRole(Role $roles)
{
$this->roles[] = $roles;
return $this;
}
public function removeRole(Role $roles)
{
$this->roles->removeElement($roles);
}
}
And I have an Event subscriber for both prePersist
and preUpdate
. When i create a new user it's fired normally. But when i only change the password it doesn't work at all.
(note that plain_password
property is a virtual one. Does not get saved on the DB)
Here's the code:
<?php
namespace Tsk\UserBundle\EventSubscriber;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Tsk\UserBundle\Entity\User;
use Tsk\UserBundle\Utils\Encoder;
class EncoderSubscriber implements EventSubscriber
{
protected $encoder;
public function __construct(Encoder $ef)
{
$this->encoder = $ef;
}
/**
* Returns an array of events this subscriber wants to listen to.
*/
public function getSubscribedEvents()
{
return array(
'preUpdate',
'prePersist',
);
}
public function preUpdate(LifecycleEventArgs $args)
{
$this->encode($args);
}
public function prePersist(LifecycleEventArgs $args)
{
$this->encode($args);
}
protected function encode(LifecycleEventArgs $args)
{
$user = $args->getEntity();
if($user instanceof User)
{
$encodedPassword = ($this->encoder->encode($user, $user->getPlainPassword(), $user->getSalt()));
$user->setPassword($encodedPassword);
}
}
}
?>
Upvotes: 2
Views: 1869
Reputation: 812
I met the same problem too.
The reason is that Doctrine's Unit of Work is not able to compute the change set between the original entity and the password-changed entity because plain_password
is not going to be persisted into the database, so a change in the plain_password
field is ignored by Unit of Work (or I should say, Unit of Work finds no difference between the data before you call flush and after you call flush).
The way to make it work is to update your password
before you call EntityManager#flush
. I implemented this by dispatching my UserEvents::CHANGE_PASSWORD
when password changing is detected (manually, of course). By using this method, you do not have to subscribe to the preUpdate
event anymore if you're just going to update your password field in it.
You can also refer to FOSUserBundle's implementation.
Upvotes: 4