简体   繁体   中英

Symfony / Api-platform password update on PUT even if not specified

Symfony / Api-platform updates rehash password on put request gives error when login in again

I have the exact same issue as this person, looks like he found out the solution because he says "My bad I didn't follow the documentation quite thoroughly. It's not an issue anymore" in the comments.

<?php

namespace App\Entity;

use ApiPlatform\Core\Action\NotFoundAction;
use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiResource;
use App\Repository\UserRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Validator\Constraints as Assert;
use App\Controller\MeController;
use App\Controller\ForgottenPasswordController;
use App\Controller\UpdateUser;

#[ORM\Entity(repositoryClass: UserRepository::class)]
#[ORM\Table(name: '`user`')]
/**
 * Un utilisateur
 *
 *  @UniqueEntity(fields={"email"}, message="Il y a déjà un compte avec cet email")
 */
#[ApiResource(
    collectionOperations: [
        "get" => [
            "security" => "is_granted('ROLE_ADMIN')",
            "security_message" => "Vous n'avez pas accès à cette ressource."
        ],
        "post",
        "me" => [
            "path" => "/me",
            "method" => "get",
            "controller" => MeController::class,
          ],
        "forgotten_password" => [
            "method" => "post",
            "path" => "/forgotten-password",
            "controller" => ForgottenPasswordController::class,
            "denormalization_context" => ["groups" => ["forgotten-password"]]
          ],
    ],
    itemOperations: [
        "get" => [
            "security" => "is_granted('ROLE_ADMIN')",
            "security_message" => "Vous n'avez pas accès à cette ressource."
        ],
//        "put" => [
//            "security" => "object == user or is_granted('ROLE_ADMIN')",
//            "security_message" => "Vous n'avez pas accès à cette ressource."
//        ],
        "update_user" => [
              "method" => "PUT",
               "path" => "/users/{id}/update",
               "controller" => UpdateUser::class,
               "normalization_context"=>["groups"=>["user_update"]]
           ],
        "delete" => [
            "security" => "object == user or is_granted('ROLE_ADMIN')",
            "security_message" => "Vous n'avez pas accès à cette ressource."
        ],
//        "patch" => [
//            "security" => "is_granted('ROLE_ADMIN')",
//            "security_message" => "Vous n'avez pas accès à cette ressource."
//        ],
//        "update_password" => [
//            "method" => "PUT",
//            "path" => "users/{id}/update-password",
////            "controller"=> App\Controller\UserChangePassword::class,
////            "normalization_context"=>{"groups"={"user:passwordUpdate"}}
//        ]
    ],
    denormalizationContext: [
        'groups' => ['user:input']
    ],
    normalizationContext: [
        'groups' => ['user:output']
    ]
)]
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: 'integer')]
    #[Groups(['user:output'])]
    /**
     * L'identifiant d'un utilisateur
     */
    private $id;

    #[ORM\Column(type: 'string', length: 180, unique: true)]
    #[Groups(['user:output', 'user:input', 'forgotten-password', 'read:command', 'user_update'])]
    /**
     * L'email d'un utilisateur
     *
     * @Assert\NotNull
     * @Assert\Email(
     *     message = "L'email'{{ value }}' n'est pas valide."
     * )
     */
    private $email;

    #[ORM\Column(type: 'json')]
    #[Groups(['user:output', 'admin:input'])]
    /** Le role d'un utilisateur */
    private $roles = [];

    #[ORM\Column(type: 'string')]
    #[Groups(['user:input'])]
    /**
     * Le mot de passe d'un utilisateur
     *
     * @Assert\NotNull
     * @Assert\NotBlank(
     *     normalizer = "trim"
     * )
     *
     */
    private $password;

    #[ORM\Column(type: 'string', length: 255)]
    #[Groups(['user:output', 'user:input'])]
    /**
     * Le prénom d'un utilisateur
     *
     * @Assert\NotNull
     * @Assert\NotBlank(
     *     normalizer = "trim"
     * )
     * @Assert\Length(
     *      min = 1,
     *      max = 50,
     *      minMessage = "Doit contenir {{ limit }} caractères minimum",
     *      maxMessage = "Doit contenir {{ limit }} caractères maximum"
     * )
     */
    private $firstname;

    #[ORM\Column(type: 'string', length: 255)]
    #[Groups(['user:output', 'user:input'])]
    /**
     * Le nom d'un utilisateur
     *
     * @Assert\NotNull
     * @Assert\NotBlank(
     *     normalizer = "trim"
     * )
     * @Assert\Length(
     *      min = 1,
     *      max = 50,
     *      minMessage = "Doit contenir {{ limit }} caractères minimum",
     *      maxMessage = "Doit contenir {{ limit }} caractères maximum"
     * )
     *
     */
    private $lastname;

    #[ORM\OneToMany(mappedBy: 'owner', targetEntity: Command::class, orphanRemoval: true)]
    #[Groups(['user:output'])]
    /** Les commandes effectuées par un utilisateur */
    private $commands;

    #[ORM\Column(type: 'string', length: 255, nullable: true)]
    #[Groups(['user:output', 'user:input'])]
    /** L'adresse d'un utilisateur
     *
     * @Assert\NotBlank(
     *     normalizer = "trim"
     * )
     * @Assert\Length(
     *      min = 1,
     *      max = 255,
     *      minMessage = "Doit contenir {{ limit }} caractères minimum",
     *      maxMessage = "Doit contenir {{ limit }} caractères maximum"
     * )
     *
     */
    private $address;

    #[ORM\Column(type: 'string', length: 255, nullable: true)]
    private $stripeToken;

    #[ORM\Column(type: 'boolean')]
    private $isVerified = false;

    #[ORM\Column(type: 'datetime', nullable: true)]
    private $tokenValidAfter;

    public function __construct()
    {
        $this->commands = new ArrayCollection();
    }

    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;
    }

    /**
     * Un identifiant visuel qui représente cet utilisateur
     *
     * @see UserInterface
     */
    public function getUserIdentifier(): 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 PasswordAuthenticatedUserInterface
     */
    public function getPassword(): string
    {
        return $this->password;
    }

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

        return $this;
    }

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

    public function getFirstname(): ?string
    {
        return $this->firstname;
    }

    public function setFirstname(string $firstname): self
    {
        $this->firstname = $firstname;

        return $this;
    }

    public function getLastname(): ?string
    {
        return $this->lastname;
    }

    public function setLastname(string $lastname): self
    {
        $this->lastname = $lastname;

        return $this;
    }

    /**
     * @return Collection|Command[]
     */
    public function getCommands(): Collection
    {
        return $this->commands;
    }

    public function addCommand(Command $command): self
    {
        if (!$this->commands->contains($command)) {
            $this->commands[] = $command;
            $command->setOwner($this);
        }

        return $this;
    }

    public function removeCommand(Command $command): self
    {
        if ($this->commands->removeElement($command)) {
            // set the owning side to null (unless already changed)
            if ($command->getOwner() === $this) {
                $command->setOwner(null);
            }
        }

        return $this;
    }

    public function getAddress(): ?string
    {
        return $this->address;
    }

    public function setAddress(?string $address): self
    {
        $this->address = $address;

        return $this;
    }

    public function getStripeToken(): ?string
    {
        return $this->stripeToken;
    }

    public function setStripeToken(?string $stripeToken): self
    {
        $this->stripeToken = $stripeToken;

        return $this;
    }

    public function getIsVerified(): ?bool
    {
        return $this->isVerified;
    }

    public function setIsVerified(bool $isVerified): self
    {
        $this->isVerified = $isVerified;

        return $this;
    }

    public function getTokenValidAfter(): ?\DateTimeInterface
    {
        return $this->tokenValidAfter;
    }

    public function setTokenValidAfter(?\DateTimeInterface $tokenValidAfter): self
    {
        $this->tokenValidAfter = $tokenValidAfter;

        return $this;
    }

}
<?php
namespace App\DataPersister;
use ApiPlatform\Core\DataPersister\DataPersisterInterface;
use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
class UserDataPersister implements DataPersisterInterface
{
    private $entityManager;
    private $userPasswordEncoder;
    public function __construct(EntityManagerInterface $entityManager, UserPasswordHasherInterface $userPasswordEncoder)
    {
        $this->entityManager = $entityManager;
        $this->userPasswordEncoder = $userPasswordEncoder;
    }
    public function supports($data): bool
    {
        return $data instanceof User;
    }
    /**
     * @param User $data
     */
    public function persist($data)
    {
        if ($data->getPassword()) {
            $data->setPassword(
                $this->userPasswordEncoder->hashPassword($data, $data->getPassword())
            );
            $data->eraseCredentials();
        }
        $this->entityManager->persist($data);
        $this->entityManager->flush();
    }
    public function remove($data)
    {
        $this->entityManager->remove($data);
        $this->entityManager->flush();
    }
}

Your problem is due to the fact that your code hash an already hashed password and so generate a new password each time.

You have to add and use a plainPassword property.

use Symfony\Component\Serializer\Annotation\SerializedName;

... 
    #[ORM\Column(type: 'string')]
    /**
     * Le mot de passe d'un utilisateur
     *
     * @Assert\NotNull
     * @Assert\NotBlank(
     *     normalizer = "trim"
     * )
     *
     */
    private $password;

    #[Groups("user:input")]
    #[SerializedName("password")]
    private $plainPassword;

...

And in your UserDataPersister, use this plainPassword instead.

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