Symfony 5 Форма изменяет пользователя в сеансе - PullRequest
0 голосов
/ 29 марта 2020

Проблема: У меня возникла проблема, когда, если пользователь отправляет электронное письмо на мою уже использованную форму, он получает сообщение об ошибке (как и предполагалось). После получения ошибки, если страница обновляется или они переходят на другую страницу на сайте, на котором пользователь вышел из системы, я думаю, что пользователь уже вышел из системы после того, как он получил форму с ошибкой, но страница по-прежнему отображается как пользователь вошел в систему.

Пользовательский объект не имеет поля имени пользователя и использует адрес электронной почты в качестве визуального идентификатора.

Сводка : после сбоя формы пользователя uniqueEntity validation user выходит из системы при следующем запросе,

Вопрос : Можно ли как-то предотвратить это поведение?

ОБНОВЛЕНИЕ: похоже вызывает сеттеры объекта пользователя, когда вы пытаетесь обновить электронную почту независимо от того, является ли она действительной или нет. пользователь, который хранится в сеансе, видоизменяется, а затем после следующего запроса он не совпадает с пользователем в базе данных и нуждается в повторной аутентификации. Это ошибка, которая возникает только тогда, когда объект текущего пользователя помещается в форму. вопрос остается прежним.

Здесь я включу весь код, необходимый для воспроизведения этой проблемы.

Класс пользователя:

/**
 * @ORM\Entity(repositoryClass="App\Repository\UserRepository")
 *
 * @UniqueEntity(
 *     fields={"email"},
 *     message="general.unique_email"
 * )
 */
class User implements UserInterface
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=180, unique=true)
     * @Assert\NotBlank(message="general.not_blank")
     * @Assert\Email
     */
    private $email;

    /**
     * @ORM\Column(type="json")
     */
    private $roles = [];

    /**
     * @var string The hashed password
     * @ORM\Column(type="string")
     */
    private $password;

    /**
     * @ORM\Column(type="date")
     * @Assert\NotBlank(message="general.not_blank")
     * @Assert\LessThan(
     *     value="-16 years",
     *     message="general.min_age"
     * )
     */
    private $birthDate;

    /**
     * @ORM\Column(type="string", length=255)
     * @Assert\NotBlank(message="general.not_blank")
     * @Assert\Length(
     *     min="10",
     *     minMessage="general.characters_min",
     *     max="15",
     *     maxMessage="general.characters_max",
     * )
     */
    private $phone;

    /**
     * @ORM\Column(type="string", length=255)
     * @Assert\NotBlank(message="general.not_blank")
     * @Assert\Length(
     *     min="2",
     *     minMessage="general.characters_min",
     *     max="30",
     *     maxMessage="general.characters_max",
     * )
     */
    private $firstName;

    /**
     * @ORM\Column(type="string", length=255, nullable=true)
     * @Assert\Length(
     *     min="2",
     *     minMessage="general.characters_min",
     *     max="20",
     *     maxMessage="general.characters_max",
     * )
     */
    private $prepositions;

    /**
     * @ORM\Column(type="string", length=255)
     * @Assert\NotBlank(message="general.not_blank")
     * @Assert\Length(
     *     min="2",
     *     minMessage="general.characters_min",
     *     max="30",
     *     maxMessage="general.characters_max",
     * )
     */
    private $lastName;

    /**
     * @ORM\Column(type="boolean", nullable=true)
     */
    private $gender;

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getEmail(): ?string
    {
        return $this->email;
    }

    public function setEmail(?string $email): self
    {
        $this->email = $email;

        return $this;
    }

    /**
     * A visual identifier that represents this user.
     *
     * @see UserInterface
     */
    public function getUsername(): string
    {
        return (string) $this->email;
    }

    /**
     * @see UserInterface
     */
    public function getRoles(): array
    {
        $roles = $this->roles;
        // guarantee every user at least has ROLE_USER
        $roles[] = 'ROLE_USER';

        return array_unique($roles);
    }

    public function setRoles(?array $roles): self
    {
        $this->roles = $roles;

        return $this;
    }

    /**
     * @see UserInterface
     */
    public function getPassword(): string
    {
        return $this->password;
    }

    public function setPassword(?string $password): self
    {
        $this->password = $password;

        return $this;
    }

    /**
     * @see UserInterface
     */
    public function getSalt()
    {
        // not needed when using the "bcrypt" algorithm in security.yaml
    }

    /**
     * @see UserInterface
     */
    public function eraseCredentials(): void
    {
        // If you store any temporary, sensitive data on the user, clear it here
        // $this->plainPassword = null;
    }

    public function getBirthDate(): ?DateTimeInterface
    {
        return $this->birthDate;
    }

    public function setBirthDate(?DateTimeInterface $birthDate): self
    {
        $this->birthDate = $birthDate;

        return $this;
    }

    public function getPhone(): ?string
    {
        return $this->phone;
    }

    public function setPhone(?string $phone): self
    {
        $this->phone = $phone;

        return $this;
    }

    public function getFirstName(): ?string
    {
        return $this->firstName;
    }

    public function setFirstName(?string $firstName): self
    {
        $this->firstName = $firstName;

        return $this;
    }

    public function getPrepositions(): ?string
    {
        return $this->prepositions;
    }

    public function setPrepositions(?string $prepositions): self
    {
        $this->prepositions = $prepositions;

        return $this;
    }

    public function getLastName(): ?string
    {
        return $this->lastName;
    }

    public function setLastName(?string $lastName): self
    {
        $this->lastName = $lastName;

        return $this;
    }

    public function getGender(): ?bool
    {
        return $this->gender;
    }

    public function setGender(?bool $gender): self
    {
        $this->gender = $gender;

        return $this;
    }
}

Класс формы:

class UserType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('email', EmailType::class, [
                'label' => 'user.email',
            ])
            ->add('firstName', TextType::class, [
                'label' => 'user.first_name',
            ])
            ->add('prepositions', TextType::class, [
                'label' => 'user.prepositions',
                'required' => false,
            ])
            ->add('lastName', TextType::class, [
                'label' => 'user.last_name'
            ])
            ->add('phone', TextType::class, [
                'label' => 'user.phone',
            ])
            ->add('birthDate', BirthdayType::class, [
                'label' => 'user.birth_date',
                'widget' => 'single_text',
                'placeholder' => [
                    'day' => 'general.day',
                    'month' => 'general.mon th',
                    'year' => 'general.year',
                ]
            ])
            ->add('gender', ChoiceType::class, [
                'label' => 'user.gender.label',
                'choices' => [
                    'user.gender.male' => true,
                    'user.gender.female' => false,
                    'user.gender.other' => null,
                ]
            ]);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => User::class,
            'translation_domain' => 'form',
        ]);
    }
}

Контроллер:

    /**
     * @IsGranted("ROLE_USER")
     * @Route("/account", name="account")
     */
    public function account(Request $request, UserPasswordEncoderInterface $passwordEncoder, EntityManagerInterface $em): Response
    {
        /** @var User $user */
        $user = $this->getUser();
        $userForm = $this->createForm(UserType::class, $user);
        $userForm->handleRequest($request);

        if ($userForm->isSubmitted() && $userForm->isValid()) {
            $this->addFlash('success', 'applied user data');
            $em->flush();

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

        $passwordForm = $this->createForm(ChangePasswordType::class);
        $passwordForm->handleRequest($request);

        if ($passwordForm->isSubmitted() && $passwordForm->isValid()) {
            /** @var ChangePassword $changePassword */
            $changePassword = $passwordForm->getData();
            $this->getUser()->setPassword($passwordEncoder->encodePassword($this->getUser(), $changePassword->getNewPassword()));
            $em->flush();
            $this->addFlash('success', 'changed password');

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

        return $this->render('page/security/account.html.twig', [
            'title' => 'My Site | Account',
                'userForm' => $userForm->createView(),
                'passwordForm' => $passwordForm->createView(),
        ]);
    }

Шаблон веточки:

{% extends 'content_base.html.twig' %}

{% block content_container %}
    <h1>{{ 'user.header'|trans([], 'form') }}</h1>
    {{ form_start(userForm, {"novalidate": "novalidate"}) }}
        {{ form_widget(userForm) }}
        <button class="btn btn-warning btn-block" type="submit">Submit</button>
    {{ form_end(userForm) }}
    <h1>{{ 'password.header'|trans([], 'form') }}</h1>
    {{ form_start(passwordForm, {"novalidate": "novalidate"}) }}
        {{ form_widget(passwordForm) }}
        <button class="btn btn-warning btn-block" type="submit">Submit</button>
    {{ form_end(passwordForm) }}
{% endblock %}

Security.yaml:

security:
    encoders:
        App\Entity\User:
            algorithm: auto
    # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
    providers:
        # used to reload user from session & other features (e.g. switch_user)
        app_user_provider:
            entity:
                class: App\Entity\User
                property: email
        # used to reload user from session & other features (e.g. switch_user)
    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
        main:
            anonymous: lazy
            provider: app_user_provider
            guard:
                authenticators:
                    - App\Security\LoginFormAuthenticator
            logout:
                path: app_security_logout
                # where to redirect after logout
                target: app_security_login
            remember_me:
                secret: '%kernel.secret%'
                lifetime: 604800 # 1 week in seconds
                path: /

1 Ответ

0 голосов
/ 30 марта 2020

Из-за унаследованного конструктивного недостатка безопасности и привязки сущностей к формам нет реального исправления, которое позволило бы мне сохранить тот же дизайн, поэтому мне придется выяснить, как с этим справиться. Я думаю, что решение будет заключаться в создании пользовательской формы для обработки электронной почты, чтобы она не влияла на текущую сущность.

...