Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to edit user's informations and password #834

Closed
wants to merge 2 commits into from

Conversation

ker0x
Copy link
Contributor

@ker0x ker0x commented Jul 17, 2018

As requested in #820

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

return $this->redirectToRoute('security_logout');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should remplace redirect to $this->redirectToRoute('user_index') and I add a flashbag message

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find it safer to logout the user after he has changed his password, but I can be wrong. I would like to get feedback from the community about that.


public function testChangePassword()
{
$newUserPassword = $this->generateRandomString(10);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's better to use a simple constant here. The test should be simple as possible.

$newUserPassword = 'new-password';

/**
* @Route("/", methods={"GET", "POST"}, name="user_index")
*
* @param \Symfony\Component\HttpFoundation\Request $request
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This docblock comment is not needed.

*
* @param \Symfony\Component\HttpFoundation\Request $request
*
* @return \Symfony\Component\HttpFoundation\Response
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This docblock comment is not needed too.

<trans-unit id="action.change_password">
<source>action.change_password</source>
<target>Change password</target>
</trans-unit>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The action.update_user_info is missing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, it was the translation key in the template file that was wrong.

public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => null,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this config isn't necessary. If you aren't going to bind any data to this form, it's already null by default (source).

->add('currentPassword', PasswordType::class, [
'constraints' => [
new NotBlank(),
new UserPassword(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think NotBlank constraint isn't necessary since a "Not blank" value is already verified in UserPassword constraint.

new NotBlank(),
new Length([
'min' => 5,
]),
Copy link
Member

@yceruto yceruto Jul 18, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also suggest adding a callback() constraint and check by BasePasswordEncoder::isPasswordTooLong($password) otherwise we could get an uncatched error BadCredentialsException in controller during password encoding.

Copy link
Member

@ogizanagi ogizanagi Jul 18, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's not something that would happen in a real-life scenario unless really trying to make it fail. No sensible user would try setting a 4096+ chars long password. So I would not showcase it here or instead arbitrary set a reasonable max value in Length constraint above.

(relates to #834 (comment))

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's 72 chars for BCryptPasswordEncoder which is the current configured encoder :P but you're right, setting it (BCryptPasswordEncoder::MAX_PASSWORD_LENGTH) in max Length constraint is simple.

* @Route("/password", methods={"GET", "POST"}, name="user_password")
*
* @param \Symfony\Component\HttpFoundation\Request $request
* @param \Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface $encoder
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All these PHPDoc can be removed too.

$user->setPassword($encoder->encodePassword($user, $form->get('newPassword')->getData()));

$em = $this->getDoctrine()->getManager();
$em->persist($user);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

persist is unnecessary here as the entity is not a new one and already managed by doctrine.

new NotBlank(),
new Length([
'min' => 5,
]),
Copy link
Member

@ogizanagi ogizanagi Jul 18, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's not something that would happen in a real-life scenario unless really trying to make it fail. No sensible user would try setting a 4096+ chars long password. So I would not showcase it here or instead arbitrary set a reasonable max value in Length constraint above.

(relates to #834 (comment))


$builder
->add('username', TextType::class, [
'attr' => ['readonly' => true],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setting the readonly attribute will not enforce this field cannot be submit and username not changed.
You must use disabled instead.

Copy link
Contributor Author

@ker0x ker0x Jul 19, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I use disabled, I got an error Argument 1 passed to App\Entity\User::setUsername() must be of the type string, null given.

So, what should I do ?

  • leave readonly
  • add the nullable type to setUsername() in App\Entity\User
  • allow user to change his username
  • remove the username in UserType

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ker0x, He means a form option, not an HTML attribute:

$builder
    ->add('username', TextType::class,
        'label' => 'label.username',
        'disabled' => true,
    ])

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ho right, i'm so stupid! Thanks 👍

@ker0x ker0x force-pushed the master branch 3 times, most recently from 1237860 to 19b0bb5 Compare July 19, 2018 00:25
/**
* @Route("/password", methods={"GET", "POST"}, name="user_password")
*/
public function password(Request $request, UserPasswordEncoderInterface $encoder): Response
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changePassword ? (same for the template name & route name)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed it for /profile/change-password

/**
* @Route("/", methods={"GET", "POST"}, name="user_index")
*/
public function index(Request $request): Response
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

profile / editProfile ? (same for the template name & route name)

$builder
->add('username', TextType::class, [
'label' => 'label.username',
'disabled' => true,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the intention is not to modify the username, how about removing this field and print {{ user.username }} directly in the template?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As this is a demo application, maybe we can keep the disabled option to show people how to disable an input in a form ?

$user->setPassword($encoder->encodePassword($user, $form->get('newPassword')->getData()));

$em = $this->getDoctrine()->getManager();
$em->flush();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$this->getDoctrine()->getManager()->flush()?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, already change 👍

/**
* Controller used to manage current user.
*
* @Route("/user")
Copy link
Member

@yceruto yceruto Jul 19, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/user-profile, /profile/user, /user-account, /account/user... ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed it for /profile/edit

{{ form_widget(form) }}

<button type="submit" class="{{ button_css|default("btn btn-primary") }}">
<i class="fa fa-save" aria-hidden="true"></i> {{ button_label|default('action.save'|trans) }}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here {{ 'action.save'|trans }}

{{ form_start(form, {attr: attr|default({})}) }}
{{ form_widget(form) }}

<button type="submit" class="{{ button_css|default("btn btn-primary") }}">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not btn btn-primary directly? same for password.html.twig template.

{% block main %}
<h1>{{ 'title.edit_user'|trans }}</h1>

{{ form_start(form, {attr: attr|default({})}) }}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{attr: attr|default({})} can be removed.

Copy link

@maxhelias maxhelias left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems good to me.
Nevertheless, I did not see if there was a check that the new password was different from the old one

@bobdenotter
Copy link
Contributor

Is there a reason that it's App\Form\Type\ChangePasswordType and App\Form\UserType? Id think they should both be in App\Form\.

@sebastianblum
Copy link

@bobdenotter I think I can answer your question.
Root Types should be in App\Form
Child Types should be in App\Form\Type
so in my opinion this behavior is the current symfony best practise

@javiereguiluz
Copy link
Member

@ker0x you did it really great. This is a ver well done feature. Thanks for working on this and thanks for your patience during the review. And thanks to all reviewers too!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants