简体   繁体   中英

Symfony2: Edit user without having password

In my application, only the admin user can create and, theoretically, edit users. So far, using only the Symfony security system (no FOSUserBundle management - its complexity is not required), creating users with varying roles is just fine. The challenge that totally escapes me is how to edit a user without knowing the user's password. I keep running into the expected validation error

Password cannot be empty

. How can editing be accomplished? I'm surely missing something very fundamental here.

Edit action:

    public function editAction($id) {
        $em = $this->getDoctrine()->getManager();
        $user = $em->getRepository('ManaClientBundle:User')->find($id);
        $form = $this->createForm(new UserType(), $user);
        return array(
            'form' => $form->createView(),
            'user' => $user,
            'title' => 'Edit user',
            );
   }

Update action:

   public function updateAction(Request $request, $id) {
        $em = $this->getDoctrine()->getManager();
        $user = $em->getRepository('ManaClientBundle:User')->find($id);
        $originalPassword = $user->getPassword();
        $form = $this->createForm(new UserType(), $user);
        $form->bind($request);
        if ($form->isValid()) {
            $plainPassword = $form->get('password')->getData();
            if (!empty($plainPassword))  {  
                //encode the password   
                $encoder = $this->container->get('security.encoder_factory')->getEncoder($entity); //get encoder for hashing pwd later
                $tempPassword = $encoder->encodePassword($entity->getPassword(), $entity->getSalt()); 
                $user->setPassword($tempPassword);                
            }
            else {
                $user->setPassword($originalPassword);
            }
            $em->persist($user);
            $em->flush();
            return $this->redirect($this->generateUrl('user_main', array()));
        }        

User form:

public function buildForm(FormBuilderInterface $builder, array $options) {
    $builder
            ->add('enabled', 'choice', array(
                'choices' => array('Yes' => 'Yes', 'No' => 'No'),
                'expanded' => true,
                'multiple' => false,
                'label' => 'Enabled: ',
            ))
            ->add('fname')
            ->add('sname')
            ->add('email')
            ->add('username')
            ->add('password', 'repeated', array(
                'type' => 'password',
                'invalid_message' => 'Password fields do not match',
                'first_options' => array('label' => 'Password'),
                'second_options' => array('label' => 'Repeat Password'),
            ))
            ->add('role', 'choice', array(
                'choices' => array('ROLE_USER' => 'User', 'ROLE_ADMIN' => 'Admin'),
                'expanded' => true,
                'multiple' => false,
                'label' => 'Group: ',
            ))
    ;
}

Until I see a more elegant solution, here's what I came up with:

  1. Create a UserEditType form class with all fields but the password field(s)
  2. Assign UserEditType to a validation group other than Default
  3. Configure the password length constraint to the validation group in 2.
  4. Modify the edit and update actions to use UserEditType

And now users can be edited without having the password!

UserEditType:

class UserEditType extends AbstractType {
    public function buildForm(FormBuilderInterface $builder, array $options) {
        $builder
                ->add('enabled', 'choice', array(
                    'choices' => array('Yes' => 'Yes', 'No' => 'No'),
                    'expanded' => true,
                    'multiple' => false,
                    'label' => 'Enabled: ',
                ))
                ->add('fname')
                ->add('sname')
                ->add('email')
                ->add('username')
                ->add('role', 'choice', array(
                    'choices' => array('ROLE_USER' => 'User', 'ROLE_ADMIN' => 'Admin'),
                    'expanded' => true,
                    'multiple' => false,
                    'label' => 'Group: ',
                ))
        ;
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver) {
        $resolver->setDefaults(array(
            'data_class' => 'Mana\ClientBundle\Entity\User',
            'validation_groups' => array('edit'),
        ));
    }

Password in User entity:

 * @ORM\Column(name="userpass", type="string", length=100, nullable=false)
 * @Assert\NotBlank(message="Password may not be empty")
 * @Assert\Length(
 *      min = "5",
 *      max = "12",
 *      minMessage = "Password must be at least 5 characters long",
 *      maxMessage = "Password cannot be longer than than 12 characters",
 *      groups = {"Default"}
 * )

Update action:

public function updateAction(Request $request, $id) {
    $em = $this->getDoctrine()->getManager();
    $user = $em->getRepository('ManaClientBundle:User')->find($id);

    $form = $this->createForm(new UserEditType(), $user);
    $form->bind($request);
    if ($form->isValid()) {
        $em->persist($user);
        $em->flush();
        return $this->redirect($this->generateUrl('user_main', array()));
    }
    return array(
        'form' => $form->createView(),
        'user' => $user,
        'title' => 'Edit user',
    );
}

I've had the same problem here in my project.

I solved it by removing the password field from the form just for my edit action.

So, in my UserController , I changed the editAction :

//find the line where the form is created
$editForm = $this->createForm(new UserType($this->container), $entity)
        ->remove('password'); //add this to remove the password field

I do something like this (untested code)

My User entity has a password property mapped to DB
It also has a 'plainPassword' property, that is not mapped

class User {
  // mapped
  private string $username;

  // mapped
  private string $password;

  // not mapped - simple php property
  private string $plainPassword;
  
  // getters/setters
  ...
}

The form, uses the plainPassword property, not the mapped password.

class UserType extends AbstractType {
  ...
  public function buildForm(FormBuilderInterface $builder, array $options)
  {
    $builder
      ->add('username', TextType::class)
      ->add('plainPassword', PasswordType::class, ['required' => false])
  }
  ...
}

And then somewhere, controller in this example, we check if the plainPassword is not empty - thus the password is trying to be changed.

public function updateUserAction(User $user, Request $request)
{
  $form = $this->formFactory->createForm(UserType::class, $user);
  if($request->getMethod() === 'POST') {
    $form->handleRequest($request);

    if($form->isSubmitted() && $form->isValid()) {
      if(0 !== strlen($user->getPlainPassword()) {
        $encoder = $this->encoderFactory->getPasswordHasher($user);

        $salt = rtrim(str_replace('+', '.', base64_encode(random_bytes(32))), '=');
        $user->setSalt($salt);

        $hashedPassword = $encoder->hash($user->getPlainPassword(), $user->getSalt());
        $user->setPassword($hashedPassword);
        $user->setPlainPassword(null);
      }

      $this->em->persist($user);
      $this->em->flush();

      return $this->redirectToRoute('something');
    }
  }
}

If you want to use the remove() function then apply also at the form setting. At least in Symfony 3.3. In this way you will avoid the password confirmation stated by @pusle above:

        $form = $this->formFactory->createForm()->remove("current_password");
        $form->setData($user)->remove("current_password");

Here the whole method in the ProfileController of the FOSUserBundle. It works for me:


public function editDiffAction($id, Request $request)
    {
        $userManager = $this->get('fos_user.user_manager');
        $user = $userManager->findUserBy(['id' => $id]);

        $event = new GetResponseUserEvent($user, $request);

        if (null !== $event->getResponse()) {
            return $event->getResponse();
        }

        $form = $this->formFactory->createForm()->remove("current_password");
        $form->setData($user)->remove("current_password");

        $form->handleRequest($request);

        if ($form->isValid()) {
            $event = new FormEvent($form, $request);

            $userManager = $this->get('fos_user.user_manager');
            $userManager->updateUser($user);

            $url = $this->generateUrl('fos_user_profile_show_diff', array('id' => $user->getId() ));
            $response = new RedirectResponse($url);

            return $response;
        }

        return $this->render('@FOSUser/Profile/edit_diff.html.twig', array(
            'form' => $form->createView(),
            'user_id' => $user->getId(),
        ));
    }

只需添加 'disabled' => 'disabled' 并不会考虑此字段。

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM