Search code examples
symfonydoctrine-ormdoctrinepassword-encryptionsonata

Sonata, Symfony, Password Encryption, Doctrine Listerner/Subscriber


  • Infos : I'm new to Symfony and Sonata

  • My objective : Encrypte the password only for the database side. I would be able to display this password in clear. All this for the field password in the entity Service.

    • What did i'm trying ? : I tryed to create a Doctrine Listener which use bcrypt encryption

      security:

      encoders: App\Entity\Service: bcrypt


here is the HashPasswordLisetener.php in my App\Doctrine (in the $formMapper of my configureFormField function in App\Admin\ServiceAdmin.php i have a row like this ->add('password'))

<?php

namespace App\Doctrine;

use App\Entity\Service;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;

class HashPasswordListener implements EventSubscriber
{
    private $passwordEncoder;

    public function __construct(UserPasswordEncoderInterface $passwordEncoder)
    {
        $this->passwordEncoder = $passwordEncoder;
    }

    public function prePersist(LifecycleEventArgs $args)
    {
        $entity = $args->getEntity();
        if (!$entity instanceof Service) {
            return;
        }
        $this->encodePassword($entity);
    }

    public function preUpdate(LifecycleEventArgs $args)
    {
        $entity = $args->getEntity();
        if (!$entity instanceof Service) {
            return;
        }
        $this->encodePassword($entity);

        $em = $args->getEntityManager();
        $meta = $em->getClassMetadata(get_class($entity));
        $em->getUnitOfWork()->recomputeSingleEntityChangeSet($meta, $entity);
    }

    public function getSubscribedEvents()
    {
        return ['prePersist', 'preUpdate'];
    }

    /**
     * @param Service $entity
     */
    private function encodePassword(Service $entity)
    {
        if (!$entity->getPlainPassword()) {
            return;
        }
        $encoded = $this->passwordEncoder->encodePassword(
            $entity,
            $entity->getPlainPassword()
        );
        $entity->setPassword($encoded);
    }
}

here is the Service entity in the App\Entity

<?php

namespace App\Entity;

use App\Admin\AbstractAdmin;
use App\Repository\ServiceRepository;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Timestampable\Traits\TimestampableEntity;
use Symfony\Component\Security\Core\User\UserInterface;

/**
 * @ORM\Entity(repositoryClass=ServiceRepository::class)
 */
class Service implements UserInterface
{

    use TimestampableEntity;

    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $name;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $password;

    /**
     * @ORM\Column(type="text", nullable=true, length=255)
     */
    private $comment;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $identifier;

    private $plainPassword;

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

    public function getName(): ?string
    {
        return $this->name;
    }

    public function setName(string $name): self
    {
        $this->name = $name;

        return $this;
    }

    public function getPassword(): ?string
    {
        return $this->password;
    }

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

        return $this;
    }

    public function getComment(): ?string
    {
        return $this->comment;
    }

    public function setComment(?string $comment): self
    {
        $this->comment = $comment;

        return $this;
    }

    public function getIdentifier(): ?string
    {
        return $this->identifier;
    }

    public function setIdentifier(string $identifier): self
    {
        $this->identifier = $identifier;

        return $this;
    }

    /**
     * @return mixed
     */
    public function getPlainPassword()
    {
        return $this->plainPassword;
    }

    /**
     * @param mixed $plainPassword
     */
    public function setPlainPassword($plainPassword): void
    {
        $this->plainPassword = $plainPassword;
        $this->password = null;
    }

    public function getRoles()
    {
        return array('ROLE_USER');
    }

    public function getSalt()
    {
    }

    public function eraseCredentials()
    {
        $this->plainPassword = null;
    }

    public function getUsername()
    {
        return $this->identifier;
    }
}

Also my sonata_admin.yaml :

app.doctrine.hash_password_listener:
    class: App\Doctrine\HashPasswordListener
    autowire : true
    tags:
        - { name: doctrine.event_subscriber, connection: 'default' }

  • The result it give me : enter image description here

  • My Probleme : I understand the function encodePassword is waiting for a UserInterface in 1st arg (instead of my entity) and the password to encrypt in 2nd arg but i don't understand who use this UserInterface ? Where i'm suppose to call it ? to get it ? to send it ?

I think i give lot of details but if i forgot something feel free to notice me ^^ Thanks the time you spend at least reading.


Solution

  • I had a probleme but i was coding an Hashing type of code but i was looking for an encrypting methode. but here is how i deal for having a working hashing :

    • Step 1 : implementing UserInterface in my Service entity

    • Step 2 : Replace UserPasswordEncoder for UserPasswordEncoderInterface in the Listener

    • Step 2.5 : Added all function needed for the UserInterface like eraseCredidential getSalt().. see : this for more details

    • Step 3 : Adding a getUsername which return this->identifier

    • Step 4 : using plainPasswords in form field instead of password

    • Step 5 : added a provider :

      app_user:

        entity:
        class: 'App\Entity\Service'
        property: 'identifier'
      
    • Step 6 : Using TextType::class for plainPassword form field type + fixing the use with : Symfony\Component\Form\Extension\Core\Type\TextType

    • Step 7 : Working

    (Special thanks to @msg who helped me a lot)