I know this answer is coming a few years late, but as I was stumbleing ybout the same Problem I want to present my solution:
The problem is that there was a connection in my case between the $user Entity I used for FormMapping and the User that comes form the security.context.
See following: (PasswordChange - Controller)
$username = $this->getUser()->getUsername();
$user = $this->getDoctrine()->getRepository("BlueChordCmsBaseBundle:User")->findOneBy(array("username"=>$username));
// Equal to $user = $this->getUser();
$form = $this->createForm(new ChangePasswordType(), $user);
//ChangePasswordType equals the one 'thesearentthedroids' posted
$form->handleRequest($request);
if($request->getMethod() === "POST" && $form->isValid()) {
$manager = $this->getDoctrine()->getManager();
$user->setPassword(password_hash($user->getPassword(), PASSWORD_BCRYPT));
[...]
}
return array(...);
The isValid() function is triggering the UserPassword Constraint Validator:
public function validate($password, Constraint $constraint)
{
if (!$constraint instanceof UserPassword) {
throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\UserPassword');
}
$user = $this->tokenStorage->getToken()->getUser();
if (!$user instanceof UserInterface) {
throw new ConstraintDefinitionException('The User object must implement the UserInterface interface.');
}
$encoder = $this->encoderFactory->getEncoder($user);
if (!$encoder->isPasswordValid($user->getPassword(), $password, $user->getSalt())) {
$this->context->addViolation($constraint->message);
}
}
The line of interest is: if (!$encoder->isPasswordValid($user->getPassword(), $password, $user->getSalt()))
In my case the $user->getPassword() was giving back the new Password I just entered in the form as my new Password.
Thats why the test allways failed!
I did not understand why there could be a connection between the User in the tokenStorage and the one that I loaded from my Database.
It feels like both Objects (MyDatabase one and the tokenStorage one) share the same processor address and are actually the same...
Weird!
My solution was to also decouple the (new)password field in ChangePasswordType from the EntityMapping: See
->add('currentpassword', 'password', array('label'=>'Current password', 'mapped' => false, 'constraints' => new UserPassword()))
->add('password', 'repeated', array(
'mapped' => false,
'type' => 'password',
'invalid_message' => 'The password fields must match.',
'required' => true,
'first_options' => array('label' => 'Password'),
'second_options' => array('label' => 'Repeat Password'),
))
->add('Send', 'submit')
->add('Reset','reset')
The line of interest is 'mapped' => false,
By this, the new password entered in the form will not be automatically mapped to the given $user Entity. Instead you now need to grab it from the form. See
$form->handleRequest($request);
if($request->getMethod() === "POST" && $form->isValid()) {
$data = $form->getData();
$manager = $this->getDoctrine()->getManager();
$user->setPassword(password_hash($data->getPassword(), PASSWORD_BCRYPT));
$manager->persist($user);
$manager->flush();
}
A bit of a workaround for problem what I could not fully understand. If anyone can explain the connection between the Database Object and the security.context Object I'd be glad to hear it!