I don't know why it doesn't work. I think, i do all like in tutorials, so..
providers:
in_memory:
memory: ~
manager:
id: manager_user_provider
encoders:
AppBundle\Entity\Manager:
algorithm: bcrypt
cost: 12
easy_admin:
entities:
Manager:
class: AppBundle\Entity\Manager
app_bundle:
export_path: '%kernel.root_dir/../var/export/user'
password_encoding: { algorithm: 'bcrypt', cost: 12 }
form:
fields:
- 'email'
- {property: 'password', type: 'password'}
title: 'Add customer'
new:
fields:
- 'email'
- {property: 'password', type: 'password'}
- {property: 'roles', type: 'collection'}
and in fact, easyAdmin save new\edited user with plain text password.
symfony 3.1.3
easyadmin 1.15.0
If I understand the code correctly, the problem is that EasyAdmin sees your Manager entity as any other entity. Therefore, it doesn't understand that a password field must be hashed before persisting it. For EasyAdmin this is just a normal form field.
If you use FOSUserBundle, read this tutorial about how to integrate it. Otherwise, you need to create your custom AdminController and override some method to call to the Symfony's password encoding service and save the change in the entity. This is way easier than it sounds! It's explained here in detail: https://github.com/javiereguiluz/EasyAdminBundle/blob/master/Resources/doc/book/7-complex-dynamic-backends.md
@javiereguiluz ok, but maybe it will be easier to create entity listener?
@aLkRicha if you feel more comfortable with entity listeners, go for it! There's definitely more than one way to solve this.
However, "entity listeners" or "lifecycle callbacks" are considered a bad practice by Doctrine creators. See these slides for details.
The controller based solution feels a bit more "natural" for Symfony and it's a matter of adding a few lines of code.
In any case, let's close this issue as "fixed" because some valid solutions were proposed. Thanks!
@javiereguiluz ok, thanks!
the links here are all broken, what are the solutions?
thanks
I used the entity controller approach. If you create a UserController that extends the BaseAdminController and use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface, you can then override the editAction() and newAction() methods to hash the password before persisting to db like so:
namespace App\Controller;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AdminController as BaseAdminController;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use EasyCorp\Bundle\EasyAdminBundle\Event\EasyAdminEvents;
class UserController extends BaseAdminController
{
public function __construct(UserPasswordEncoderInterface $passwordEncoder)
{
$this->passwordEncoder = $passwordEncoder;
}
/**
* The method that is executed when the user performs an 'edit' action on the User entity.
*
* @return Response|RedirectResponse
*/
protected function editAction()
{
$this->dispatch(EasyAdminEvents::PRE_EDIT);
$id = $this->request->query->get('id');
$easyadmin = $this->request->attributes->get('easyadmin');
$entity = $easyadmin['item'];
if ($this->request->isXmlHttpRequest() && $property = $this->request->query->get('property')) {
$newValue = 'true' === mb_strtolower($this->request->query->get('newValue'));
$fieldsMetadata = $this->entity['list']['fields'];
if (!isset($fieldsMetadata[$property]) || 'toggle' !== $fieldsMetadata[$property]['dataType']) {
throw new \RuntimeException(sprintf('The type of the "%s" property is not "toggle".', $property));
}
$this->updateEntityProperty($entity, $property, $newValue);
// cast to integer instead of string to avoid sending empty responses for 'false'
return new Response((int) $newValue);
}
$fields = $this->entity['edit']['fields'];
$editForm = $this->executeDynamicMethod('create<EntityName>EditForm', array($entity, $fields));
$deleteForm = $this->createDeleteForm($this->entity['name'], $id);
$editForm->handleRequest($this->request);
if ($editForm->isSubmitted() && $editForm->isValid()) {
// hash the password before storing in db
$encodedPassword = $this->passwordEncoder->encodePassword(
$entity,
$entity->getPassword()
);
$entity->setPassword($encodedPassword);
$this->dispatch(EasyAdminEvents::PRE_UPDATE, array('entity' => $entity));
$this->executeDynamicMethod('preUpdate<EntityName>Entity', array($entity));
$this->executeDynamicMethod('update<EntityName>Entity', array($entity));
$this->dispatch(EasyAdminEvents::POST_UPDATE, array('entity' => $entity));
return $this->redirectToReferrer();
}
$this->dispatch(EasyAdminEvents::POST_EDIT);
$parameters = array(
'form' => $editForm->createView(),
'entity_fields' => $fields,
'entity' => $entity,
'delete_form' => $deleteForm->createView(),
);
return $this->executeDynamicMethod('render<EntityName>Template', array('edit', $this->entity['templates']['edit'], $parameters));
}
/**
* The method that is executed when the user performs a 'new' action on the User entity.
*
* @return Response|RedirectResponse
*/
protected function newAction()
{
$this->dispatch(EasyAdminEvents::PRE_NEW);
$entity = $this->executeDynamicMethod('createNew<EntityName>Entity');
$easyadmin = $this->request->attributes->get('easyadmin');
$easyadmin['item'] = $entity;
$this->request->attributes->set('easyadmin', $easyadmin);
$fields = $this->entity['new']['fields'];
$newForm = $this->executeDynamicMethod('create<EntityName>NewForm', array($entity, $fields));
$newForm->handleRequest($this->request);
if ($newForm->isSubmitted() && $newForm->isValid()) {
// hash the password before storing in db
$encodedPassword = $this->passwordEncoder->encodePassword(
$entity,
$entity->getPassword()
);
$entity->setPassword($encodedPassword);
$this->dispatch(EasyAdminEvents::PRE_PERSIST, array('entity' => $entity));
$this->executeDynamicMethod('prePersist<EntityName>Entity', array($entity));
$this->executeDynamicMethod('persist<EntityName>Entity', array($entity));
$this->dispatch(EasyAdminEvents::POST_PERSIST, array('entity' => $entity));
return $this->redirectToReferrer();
}
$this->dispatch(EasyAdminEvents::POST_NEW, array(
'entity_fields' => $fields,
'form' => $newForm,
'entity' => $entity,
));
$parameters = array(
'form' => $newForm->createView(),
'entity_fields' => $fields,
'entity' => $entity,
);
return $this->executeDynamicMethod('render<EntityName>Template', array('new', $this->entity['templates']['new'], $parameters));
}
}
Just make sure to mod easy_admin.yaml to implement your custom controller:
entities:
User:
class: App\Entity\User
controller: App\Controller\UserController
@fredmorgan - Thanks for this contribution. It worked perfectly for what I needed.
I'm sorry to up this issue, but, even if I not tested the @fredmorgan solution, 132 lines to hash a password, it would not be a little too much?
Okay, I found a very simplest solution, with my entity setter.
public function setPassword(string $password): self
{
global $kernel;
if (method_exists($kernel, 'getKernel'))
$kernel = $kernel->getKernel();
$this->password = $kernel->getContainer()->get('security.password_encoder')->encodePassword($this, $password);
return $this;
}
@luigifab38 Please, NEVER EVER use that solution in a real application! You are binding your entities to some external state which totally breaks encapsulation. This only works by chance (as long as the kernel instantiated in the front controller and assigned to the variable $kernel, this variable is never replaced with anything else, some service exists here and so on) and relies on too many assumptions made about the environment it is executed in.
Also, @luigifab38 it's not 132 lines of code to hash the password; it's really only 4 in each method. The edit and new methods are overrides to the methods in the extended controller thus require the other code to be executed in the override. The addition to the new method for instance is simply:
// hash the password before storing in db
$encodedPassword = $this->passwordEncoder->encodePassword(
$entity,
$entity->getPassword()
);
$entity->setPassword($encodedPassword);
@fredmorgan I've tried your method but for some reason it doesn't work. I've modified the easy_admin.yaml too but nothing. I'm not even getting an error message, I still get the plaintext password
My variant:
<?php
namespace App\Controller;
use App\Entity\User;
use EasyCorp\Bundle\EasyAdminBundle\Controller\EasyAdminController;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
class UserController extends EasyAdminController
{
/**
* @var UserPasswordEncoderInterface
*/
private $passwordEncoder;
/**
* UserController constructor.
*
* @param UserPasswordEncoderInterface $passwordEncoder
*/
public function __construct(UserPasswordEncoderInterface $passwordEncoder)
{
$this->passwordEncoder = $passwordEncoder;
}
public function persistEntity($entity)
{
$this->encodePassword($entity);
parent::persistEntity($entity);
}
public function updateEntity($entity)
{
$this->encodePassword($entity);
parent::updateEntity($entity);
}
public function encodePassword($user)
{
if (!$user instanceof User) {
return;
}
$user->setPassword(
$this->passwordEncoder->encodePassword($user, $user->getPassword())
);
}
}
and config/packages/easy_admin.yaml:
easy_admin:
entities:
# List the entity class name you want to manage
User:
class: App\Entity\User
controller: App\Controller\UserController
form:
fields:
# Other fields ...
- { property: 'password', type: 'password' }
by encoding password on any update of user you can obtain the following scenario:
1) user is created with password 'pass' that is correctly encoded to '$argon2i$v=19$m=1024,t=2,p=2$RHRpSk5iNkI2SldVTG5lTw$KtLM5p7JoTxn2skz845FERo62K5neVFu/BPqOHvo8cE'
2) user is updated and have changed roles. Nobody want change password for them. But your code gets encoded password and encode it again, then user can't login to his account.
@gustawdaniel good question.
Do not set to change the $password field directly. Use $plainPassword and if it is set, then encode $password
// ...
class User implements UserInterface
{
//...
/**
* @var string The hashed password
* @ORM\Column(type="string")
*/
private $password;
/**
* @var string
*/
private $plainPassword;
/**
* @see UserInterface
*/
public function eraseCredentials()
{
// If you store any temporary, sensitive data on the user, clear it here
$this->plainPassword = null;
}
}
Somethink like this for code @kprokopenko
//...
class AdminController extends BaseAdminController
{
//...
public function encodePassword($user)
{
if (!$user instanceof User || !$user->getPlainPassword()) {
return;
}
// now it's work if plainPassword string was set
$user->setPassword(
$this->passwordEncoder->encodePassword($user, $user->getPlainPassword())
);
}
}
This isn't working anymore in EasyAdmin3.
Most helpful comment
My variant:
and
config/packages/easy_admin.yaml: