Symfony2 UserPassword constraint gets a NULL password - forms

I want just to allow users to change their password in my application. I have built the a form to edit the password but the validation never pass because the password of the current user in the UserPassword constraint is always NULL:
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien#symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Security\Core\Validator\Constraints;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
class UserPasswordValidator extends ConstraintValidator
{
private $securityContext;
private $encoderFactory;
public function __construct(SecurityContextInterface $securityContext, EncoderFactoryInterface $encoderFactory)
{
$this->securityContext = $securityContext;
$this->encoderFactory = $encoderFactory;
}
/**
* {#inheritdoc}
*/
public function validate($password, Constraint $constraint)
{
if (!$constraint instanceof UserPassword) {
throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\UserPassword');
}
$user = $this->securityContext->getToken()->getUser();
if (!$user instanceof UserInterface) {
throw new ConstraintDefinitionException('The User object must implement the UserInterface interface.');
}
$encoder = $this->encoderFactory->getEncoder($user);
//I tried to print $user->getPassword from here and it is always NULL
if (!$encoder->isPasswordValid($user->getPassword(), $password, $user->getSalt())) {
$this->context->addViolation($constraint->message);
}
}
}
This is fhe form I'm using to change the password:
class UserPasswordEditType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('oldPassword', 'password', array(
'constraints' => array(
new UserPassword(array(
'message' => 'password_current.invalid',
'groups' => 'user-password-edit'
)
),
new NotBlank(array(
'message' => 'not_blank',
'groups' => 'user-password-edit'
))
),
'mapped' => false,
'required' => true,
))
->add('password', 'repeated', array(
'type' => 'password',
'invalid_message' => 'password_repeat.invalid',
'required' => true,
'first_options' => array('label' => 'password.label'),
'second_options' => array('label' => 'password_repeat.label'),
))
->add('save', 'submit', array(
'label' => 'save.label'
));
}
public function getName()
{
return 'user_edit_password';
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'validation_groups' => array('user-password-edit'),
));
}
}
This is a slice of the security.yml
security:
encoders:
Symfony\Component\Security\Core\User\User: plaintext
XXX\PrivateApplication\Bundle\UserBundle\Entity\User: plaintext
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
providers:
chain_provider:
chain:
providers: [in_memory, user_db]
in_memory:
memory:
users:
API_DOC: { password: #aaa, roles: [ 'ROLE_API_DOC' ] }
user_db:
entity: { class: XXX\PrivateApplication\Bundle\UserBundle\Entity\User, property: username }
Why the password of the logged user is always NULL from the constraint? If I print it from the controller it works... I don't use the FOSUserBundle.
Thank you
PS:
I have found a similar question Using Symfony2 UserPassword validator in form type but without replies...

Why the password of the logged user is always NULL from the constraint? If I print it from the controller it works...
The thing is, that in $password is your plain password from the form, not in getPassword()! You retrieve the 'signed in' user from the token of the security context and the encoded password is NULL. That means, that probably also getUsername() is NULL and getRoles() is just anonymous or guest (don't know it right now).
Then the login at all doesn't work and the token is only anonymous.

Related

Symfony - File Type assert not null on form edit

On a Symfony app in an entity I have a field logo that is a required field.
So I build a form and made the constraint validation inside.
My problem is that happen on the constraint I defined on the edition of the form when this one have already been filled on time. When I try to apply the constraint the object I get always tell me that my field logo is null.
Here is the code of my formType to make you undertand.
<?php
namespace AppBundle\Form\Type\Step;
use AppBundle\Entity\Steps\Step14;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\Callback;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
class Step14Type extends AbstractType
{
private $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
/** #var Step14 $step14 */
$step14 = $options['step14'];
$builder
->add('logo', FileType::Class, array(
'label' => "* Logo",
'required' => true,
'data_class' => null
))
->add('name', TextType::Class, array(
'label' => '* Name',
'required' => true,
))
->add('surmane', TextType::Class, array(
'label' => '* Surname',
'required' => true,
))
->add('email', EmailType::class, array(
'label' => '* Email',
'required' => true,
))
;
}
public function validate(Step14 $step14, ExecutionContextInterface $context)
{
/** #var Step14 $searchStep14 */
$searchStep14 = $this->em->getRepository(Step14::class)->findOneBy(['id' => $step14->getId()]);
//If we are on creation mode (the user is filling the form for the firstTime)
if ($searchStep14 === null) {
if ($step14->getLogo() === null) {
$context->buildViolation("You must upload your logo")
->atPath('logo')
->addViolation();
} else {
if ($step14->getLogo()->getMimeType() !== 'image/png' &&
$step14->getLogo()->getMimeType() !== 'image/jpeg' &&
$step14->getLogo()->getMimeType() !== 'image/gif'
) {
$context->buildViolation("Your logo file must be at the format .png, .jpeg, .jpg or .gif")
->atPath('logo')
->addViolation();
}
}
} else {
//If we are on edition mode
//if no logo had already been uploaded
if ($searchStep14->getLogo() === null && $step14->getLogo() === null) {
$context->buildViolation("You must upload your logo")
->atPath('logo')
->addViolation();
}
if($step14->getLogo() !== null) {
if ($step14->getLogo()->getMimeType() !== 'image/png' &&
$step14->getLogo()->getMimeType() !== 'image/jpeg' &&
$step14->getLogo()->getMimeType() !== 'image/gif'
) {
$context->buildViolation("Your logo file must be at the format .png, .jpeg, .jpg or .gif")
->atPath('logo')
->addViolation();
}
}
}
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => Step14::class,
'userEdition' => null,
'step14' => null,
'edition' => null,
'constraints' => [
new Callback([$this, 'validate']),
],
));
}
}
My problem is that in my validate function I can't get the original value of the object $step14, before the form submit because I would like to know if I am on edit mode or not, to not have to force the user to upload again his logo he already did it before and if he just want to edit his name, surname or email.
In my validate function I thought that by calling : $this->em->getRepository(Step14::class)->findOneBy(['id' => $step14->getId()]);
I would get the original value of my object but in fact I still get a null value while at this point nothing has been presisted end flush on that entity in my controller.
Indeed if I take an example where my logo has already been filled and I only try to update my name, in the validate function :
$step14->getLogo() return null and $searchStep14->getLogo() return null too
Does anyone have an idea on how I could fix this problem ?
Thanks by advance.
Maybe you can use an hidden field type to know if a logo has already been uploaded :
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
...
->add('logoExists', HiddenType::class, [
'data' => 'yes', // or 'no' depending if logo exists
'mapped' => false,
])
and check in the form submission controller rather than in the form builder if a file has been uploaded if needed :
$file = $form->get('logo')->getData();
$logoExists = $form->get('logoExists')->getData();
if ( $logoExists == 'No' && !$file )
// form is not valide

Symfony2 - Adding Swiftmailer as a service

I'd like to move my email code from my controller into a service.
I've done the following thus far:
created the entry in services.yml
created a EmailManager.php file inside acme/demobundle/services/EmailManager.php
Could use some help on what needs to go into the EmailManager.php and how to call it in the controller?
services.yml
services:
email_manager:
class: Acme\DemoBundle\Services\EmailManager
arguments: [#request_stack, #mailer]
scope: request
EmailManager.php
<?php
// src/Acme/DemoBundle/Services/EmailManager.php
namespace Acme\DemoBundle\Services;
class EmailManager
{
private $mailer;
private $request;
public function __construct(RequestStack $requestStack, $mailer)
{
$this->request = $requestStack->getCurrentRequest();
$this->mailer = $mailer;
}
What needs to go here? Do I just copy/paste the code from the contactAction below into here?
}
Controller code with contactAction that I would like to move out of the controller into EmailManager service:
/**
* #Route("/", name="contact")
* #Template("AcmeDemoBundle:Default:index.html.twig")
*/
public function contactAction(Request $request)
{
$form = $this->createForm(new ContactType());
if ($request->isMethod('POST')) {
$form->submit($request);
if ($form->isValid()) {
$message = \Swift_Message::newInstance()
->setSubject($form->get('subject')->getData())
->setFrom($form->get('email')->getData())
->setTo('example#gmail.com')
->setBody(
$this->renderView(
'AcmeDemoBundle:Default:index.html.twig',
array(
'ip' => $request->getClientIp(),
'name' => $form->get('name')->getData(),
'message' => $form->get('message')->getData()
)
)
);
$this->get('mailer')->send($message);
$request->getSession()->getFlashBag()->add('success', 'Your email has been sent! Thanks!');
return $this->redirect($this->generateUrl('contact'));
}
}
return array(
'form' => $form->createView()
);
}
ContactType Form
class ContactType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', 'text', array(
'attr' => array(
'placeholder' => 'What\'s your name?',
'pattern' => '.{2,}' //minlength
)
))
->add('email', 'email', array(
'attr' => array(
'placeholder' => 'So I can get back to you.'
)
))
->add('subject', 'text', array(
'attr' => array(
'placeholder' => 'The subject of your message.',
'pattern' => '.{3,}' //minlength
)
))
->add('message', 'textarea', array(
'attr' => array(
'cols' => 90,
'rows' => 10,
'placeholder' => 'And your message to me...'
)
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$collectionConstraint = new Collection(array(
'name' => array(
new NotBlank(array('message' => 'Name should not be blank.')),
new Length(array('min' => 2))
),
'email' => array(
new NotBlank(array('message' => 'Email should not be blank.')),
new Email(array('message' => 'Invalid email address.'))
),
'subject' => array(
new NotBlank(array('message' => 'Subject should not be blank.')),
new Length(array('min' => 3))
),
'message' => array(
new NotBlank(array('message' => 'Message should not be blank.')),
new Length(array('min' => 5))
)
));
$resolver->setDefaults(array(
'constraints' => $collectionConstraint
));
}
public function getName()
{
return 'contact';
}
}
You can customize this as you see fit, but that's a general idea and a very quick draft to guide you:
public function send($subject, $recipientName, $recipientEmail, $bodyHtml, $bodyText)
{
/* #var $mailer \Swift_Mailer */
if(!$this->mailer->getTransport()->isStarted()){
$this->mailer->getTransport()->start();
}
/* #var $message \Swift_Message */
$message = $this->mailer->createMessage();
$message->setSubject($subject);
$message->setBody($bodyHtml, 'text/html');
$message->addPart($bodyText, 'text/plain', 'UTF8');
$message->addTo($recipientEmail, $recipientName);
$message->setFrom( array('example#gmail.com' => 'Chance') );
$this->mailer->send($message);
$this->mailer->getTransport()->stop();
}
Room for Improvement
You could have:
An email data model that would contain the fields necessary for an email (like $subject, $recipientEmail, ...)
A composer that would compose your email from your request
A sender that would send your email
EMAIL MODEL would look something like this:
/**
* Email Data Model
*/
class Email implements EmailInterface
{
/**
* The text part of the message.
*
* #var string
*/
protected $bodyText;
// etc...etc..
}
You'd have an EmailInterface too:
/**
* Email interface
*/
interface EmailInterface
{
/**
* #return string
*/
public function getBodyText();
// etc...etc..
}
THE SENDER would look like this (if kept inside EmailManager):
public function send(EmailInterface $email)
{
//...
}
THE COMPOSER would look like this (if kept inside EmailManager):
public function composeEmail(Request $request)
{
//...
return $email;
}
Note: Composer and Sender could also be a separate service for better reuse, that's up to you I guess. Here is what they would look like if there were just functions in your EmailManager

Symfony2 - validate a group of fields (and change values) only if one of them is filled

I'm working on profile edit form type. I have problem with how to validate fields oldPassword and password only if one of them are filled. Firstly I set required => false, but I cannot find how turn off validation and turn on only if field oldPassword is filled, or repeated field password. Also, if validation wasn't done, values of these fields will not be processed into DB.
In form type class I have this:
->add('oldPassword', 'password', array('label' => 'Old password', 'required' => false, 'mapped' => false, 'error_bubbling' => true))
->add('password', 'repeated', array('type' => 'password', 'required' => false, 'invalid_message' => 'New passwords must equal', 'first_options' => array('label' => 'New password'), 'second_options' => array('label' => 'Repeat new password'), 'error_bubbling' => true,))
In my entity:
/**
* #ORM\Column(type="string", length=255)
* #Assert\Length(
* min = "4",
* minMessage = "Password must have at least 4 characters"
* )
*/
private $password;
/**
* #SecurityAssert\UserPassword(
* message = "Old password must equal with current"
* )
*/
protected $oldPassword;
And in controller nothing special:
public function profileAction(Request $request)
{
if (false === $this->get('security.context')->isGranted('ROLE_ADMIN')) {
throw new AccessDeniedException();
}
$em = $this->getDoctrine()->getManager();
$user = $this->getUser();
if(!$user)
{
$this->get('session')->getFlashBag()->add(
'error',
'User with ID ' . $user->getId() . ' does not exist'
);
return $this->redirect($this->generateUrl('admin_dashboard'));
}
$form = $this->createForm('user', $user);
$form->handleRequest($request);
if($form->isValid())
{
$em->persist($user);
try {
$em->flush();
} catch (\PDOException $e) {
// sth
}
$this->get('session')->getFlashBag()->add(
'success',
'Profile was successfuly edited'
);
$em->refresh($user);
}
return $this->render('AcmeUserBundle:Admin:profile.html.twig', array('form' => $form->createView()));
}
And now I need that these fields (oldPassword, password) will not be validated if there are not filled and also will not change current values, because there are more fields such as username, email, name, etc.
Is this work for FormType or Controller or both?

Symfony 2 - How to validate Entity Field Type when it is populated using Ajax?

I am confronted to a problem that is driving me crazy for more than 3 days and I do not find any solutions. Nevertheless I found a post on stackoverflow that is EXCACTLY the problem I am facing. Unfortunately the person did manage to find a solution on his down but he or she did not shared it fully. As he explained it perfectly let just copy paste it here below:
By the way it seems that person who created that post only created his account for this problem and never came back since for other things. That it is why I allow myself to ask here again...
I have 2 entities (A and B) with a Many to One relationship between
them.
I create my form with the A entity and i use an entity field (dropdown
list) to display the rows in the B entity. I use a query builder to
filter them. If don't change the values in the list (ie. with ajax),
everything is working fine.
But if I change dynamicly the values in the dropdown, when I submit
the form I have this error "This value is invalid"
It's because the submitted value isn't included in the "array"
returned by the query builder.
It seems that this validation is automatic in symfony for entity field
(I don't use any asserts on this field). I'd like to get rid of this.
But how ?
It seems that I need to implement Form Events. Unfortunatally I do not get it. I read the documentation which is very poor on that subject, read a lot of posts, searched on the Internet but did not found anything.
Here below my personal form type. What I do is the following. I create the first entity field type with the mapped property set to false and filter the entity just to get the departements. Then I create another entity type called localisation. By default I filter the entity to get nothing (''). What I do then to populate it is to use Jquery. But unfortunatelly I am confro,ted to the same problem as the other buddy (see above).
use Symfony\Component\Form\FormBuilderInterface;
use FOS\UserBundle\Form\Type\RegistrationFormType as BaseType;
use Doctrine\ORM\EntityRepository;
use Auth\GeoBundle\Form\LocalisationType;
class RegistrationFormType extends BaseType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
parent::buildForm($builder, $options);
$builder->add('departement', 'entity', array(
'mapped' => false,
'empty_value' => '',
'class' => 'AuthGeoBundle:Localisation',
'property' => 'departement',
'query_builder' => function ($repository) {
return $repository
->createQueryBuilder('e')
->add('groupBy', 'e.departement')
;
},
));
$builder->add('localisation', 'entity', array(
'empty_value' => '',
'class' => 'AuthGeoBundle:Localisation',
'property' => 'formLabel',
'query_builder' => function ($repository) use ($dpt) {
return $repository
->createQueryBuilder('e')
->where('e.departement = :dpt')
->setParameter('dpt', '')
->add('orderBy', 'e.ville ASC')
;
},
));
//some other fields here...
}
public function getName()
{
return 'auth_user_registration';
}
}
I finally manage to find a solution using the form events. I played with the "tutorial" at http://symfony.com/doc/current/cookbook/form/dynamic_form_generation.html and got it working :) Here below the code I used in case somebody interested.
My formType:
<?php
//src/Auth/UserBundle/Form/Type/RegistrationFormType.php
namespace Auth\UserBundle\Form\Type;
use Symfony\Component\Form\FormBuilderInterface;
use FOS\UserBundle\Form\Type\RegistrationFormType as BaseType;
use Doctrine\ORM\EntityRepository;
use Auth\GeoBundle\Form\LocalisationType;
use Auth\UserBundle\Form\EventListener\IsAdminFieldSubscriber;
class RegistrationFormType extends BaseType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
parent::buildForm($builder, $options);
//NE PAS EFFACER -> exempled e comment ajouter un champ qui n'a rien à voir avec nos entitys
//$builder->add("firstName", "text", array("mapped" => false));
$builder->add('departement', 'genemu_jqueryselect2_entity', array(
'mapped' => false,
'empty_value' => '',
'class' => 'AuthGeoBundle:Localisation',
'property' => 'departement',
'query_builder' => function ($repository) {
return $repository
->createQueryBuilder('e')
->add('groupBy', 'e.departement')
;
},
));
$dpt = "";
$builder->add('localisation', 'genemu_jqueryselect2_entity', array(
'empty_value' => '',
'class' => 'AuthGeoBundle:Localisation',
'property' => 'formLabel',
'query_builder' => function ($repository) use ($dpt) {
return $repository
->createQueryBuilder('e')
->where('e.departement = :dpt')
->setParameter('dpt', $dpt)
->add('orderBy', 'e.ville ASC')
;
},
));
$builder->add('sexe', 'genemu_jqueryselect2_choice', array(
'empty_value' => '',
'choices' => array(
'homme' => 'Homme',
'femme' => 'Femme',
),
'configs' => array(
'minimumResultsForSearch' => 5,
)
));
$builder->add('date_naissance', 'date', array(
'empty_value' => '',
'widget' => 'choice',
'attr' => array('class' => 'input-small'),
'years' => range(1900,2100),
'months' => range(1,12),
'days' => range(1,31),
));
$builder->add('petit_mot');
$subscriber = new IsAdminFieldSubscriber($builder->getFormFactory());
$builder->addEventSubscriber($subscriber);
}
public function getName()
{
return 'auth_user_registration';
}
}
my EventListener:
<?php
//src/Auth/UserBundle/Form/EventListener/isAdminFieldSubscriber.php
namespace Auth\UserBundle\Form\EventListener;
use Symfony\Component\Form\Event\DataEvent;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\FormEvents;
class IsAdminFieldSubscriber implements EventSubscriberInterface
{
/**
* #var FormFactoryInterface
*/
private $factory;
/**
* #param FormFactoryInterface $factory
*/
public function __construct(FormFactoryInterface $factory)
{
$this->factory = $factory;
}
/**
* #return array
*/
public static function getSubscribedEvents()
{
return array(
FormEvents::PRE_BIND => 'preBind',
);
}
/**
* Called before form data is set
*
* #param DataEvent $event
*/
public function preBind(DataEvent $event)
{
$data = $event->getData();
$form = $event->getForm();
if (null === $data) {
return;
}
$dpt = $data['localisation'];
$form->add($this->factory->createNamed('localisation', 'entity', null, array(
'empty_value' => '',
'class' => 'AuthGeoBundle:Localisation',
'property' => 'formLabel',
'query_builder' => function ($repository) use ($dpt) {
return $repository
->createQueryBuilder('e')
->where('e.id = :dpt_id')
->setParameter('dpt_id', $dpt)
->add('orderBy', 'e.ville ASC')
;
},
)));
}
}
You explain the problem yourself:
"It's because the submitted value isn't included in the "array" returned by the query builder."
You can use data transformers to solve this issue.

Implement change password in Symfony2

What is the best way to implement change password functionality in Symfony2?
Right now I'm using this:
$builder->add('password', 'repeated', array(
'first_name' => 'New password',
'second_name' => 'Confirm new password',
'type' => 'password'
));
It should also contain the current password check for security reasons.
Note: I'm not using FOSUserBundle.
Since Symfony 2.3 you can easily use UserPassword validation constraint.
Acme\UserBundle\Form\Model\ChangePassword.php
namespace Acme\UserBundle\Form\Model;
use Symfony\Component\Security\Core\Validator\Constraints as SecurityAssert;
use Symfony\Component\Validator\Constraints as Assert;
class ChangePassword
{
/**
* #SecurityAssert\UserPassword(
* message = "Wrong value for your current password"
* )
*/
protected $oldPassword;
/**
* #Assert\Length(
* min = 6,
* minMessage = "Password should be at least 6 chars long"
* )
*/
protected $newPassword;
}
Acme\UserBundle\Form\ChangePasswordType.php
namespace Acme\UserBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class ChangePasswordType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('oldPassword', 'password');
$builder->add('newPassword', 'repeated', array(
'type' => 'password',
'invalid_message' => 'The password fields must match.',
'required' => true,
'first_options' => array('label' => 'Password'),
'second_options' => array('label' => 'Repeat Password'),
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\UserBundle\Form\Model\ChangePassword',
));
}
public function getName()
{
return 'change_passwd';
}
}
Acme\UserBundle\Controller\DemoController.php
namespace Acme\UserBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Acme\UserBundle\Form\ChangePasswordType;
use Acme\UserBundle\Form\Model\ChangePassword;
class DemoController extends Controller
{
public function changePasswdAction(Request $request)
{
$changePasswordModel = new ChangePassword();
$form = $this->createForm(new ChangePasswordType(), $changePasswordModel);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// perform some action,
// such as encoding with MessageDigestPasswordEncoder and persist
return $this->redirect($this->generateUrl('change_passwd_success'));
}
return $this->render('AcmeUserBundle:Demo:changePasswd.html.twig', array(
'form' => $form->createView(),
));
}
}
You have to either create another model with two fields:
one for the current password;
and the other for the new one.
Or add a non-persisted property to your user model like the FOSUserBundle does (see the plainPassword property).
So once you checked both current and new password are valid, you encode the new password and replace the old one with it.
Just add this to your form type:
$builder->add('oldPlainPassword', \Symfony\Component\Form\Extension\Core\Type\PasswordType::class, array(
'constraints' => array(
new \Symfony\Component\Security\Core\Validator\Constraints\UserPassword(),
),
'mapped' => false,
'required' => true,
'label' => 'Current Password',
));
I use a action from my controller:
public function changepasswordAction(Request $request) {
$session = $request->getSession();
if($request->getMethod() == 'POST') {
$old_pwd = $request->get('old_password');
$new_pwd = $request->get('new_password');
$user = $this->getUser();
$encoder = $this->container->get('security.encoder_factory')->getEncoder($user);
$old_pwd_encoded = $encoder->encodePassword($old_pwd, $user->getSalt());
if($user->getPassword() != $old_pwd_encoded) {
$session->getFlashBag()->set('error_msg', "Wrong old password!");
} else {
$new_pwd_encoded = $encoder->encodePassword($new_pwd, $user->getSalt());
$user->setPassword($new_pwd_encoded);
$manager = $this->getDoctrine()->getManager();
$manager->persist($user);
$manager->flush();
$session->getFlashBag()->set('success_msg', "Password change successfully!");
}
return $this->render('#adminlte/profile/change_password.html.twig');
}
return $this->render('#adminlte/profile/change_password.html.twig', array(
));
}
Can't You get old password from User before binding form?
// in action:
$oldpassword = $user->getPassword();
if ($request->getMethod() == 'POST')
{
$form->bindRequest($request);
if ($form->isValid())
{
// check password here (by hashing new one)