phpvalidationsymfonypasswordsformbuilder

Using Symfony2 UserPassword validator in form type


Im trying to use a specific validator in a form.

That form is for an user to redefine his password, he must also enter his current password. For that I use a built in validator from symfony

in my form:

use Symfony\Component\Security\Core\Validator\Constraints\UserPassword;

and the form type looks like that:

 /**
 * @param FormBuilderInterface $builder
 * @param array $options
 */
public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        ->add('currentpassword', 'password', array('label'=>'Current password',
            'mapped' => false,
            'constraints' => new UserPassword(array('message' => 'you wot m8?')),
            'required' => true
        ))
        ->add('password', 'repeated', array(
            'first_name' => 'new',
            'second_name' => 'confirm',
            'type' => 'password',
            'required' => true
        ))
    ;
}

I know in my controller I could just get the data form, get the currentpassword value, call the security.encoder_factory, etc but that validator looked handy.

my problem is that the form always return an error (here: 'you wot m8?') just like I had entered the wrong current password.

Any idea what I am doing wrong?


Solution

  • 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!