Skip to content

Commit 1f7b15f

Browse files
committed
[Form] Add hash_mapping option to PasswordType
1 parent f4361fb commit 1f7b15f

File tree

9 files changed

+440
-1
lines changed

9 files changed

+440
-1
lines changed

src/Symfony/Bundle/SecurityBundle/Resources/config/password_hasher.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@
1111

1212
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
1313

14+
use Symfony\Component\Form\Extension\Core\Type\FormType;
15+
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
16+
use Symfony\Component\Form\Extension\PasswordHasher\PasswordHasherExtension;
17+
use Symfony\Component\Form\Extension\PasswordHasher\Type\FormTypePasswordHasherExtension;
18+
use Symfony\Component\Form\Extension\PasswordHasher\Type\PasswordTypePasswordHasherExtension;
1419
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactory;
1520
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface;
1621
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasher;
@@ -27,4 +32,22 @@
2732
->alias('security.password_hasher', 'security.user_password_hasher')
2833
->alias(UserPasswordHasherInterface::class, 'security.password_hasher')
2934
;
35+
36+
if (class_exists(PasswordHasherExtension::class)) {
37+
$container->services()
38+
->set('form.type_extension.form.password_hasher', FormTypePasswordHasherExtension::class)
39+
->args([
40+
service('security.password_hasher'),
41+
service('property_accessor')->nullOnInvalid(),
42+
])
43+
->tag('form.type_extension', ['extended-type' => FormType::class])
44+
45+
->set('form.type_extension.password.password_hasher', PasswordTypePasswordHasherExtension::class)
46+
->args([
47+
service('security.password_hasher'),
48+
service('property_accessor')->nullOnInvalid(),
49+
])
50+
->tag('form.type_extension', ['extended-type' => PasswordType::class])
51+
;
52+
}
3053
};

src/Symfony/Component/Form/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
6.2
5+
---
6+
7+
* Add a `hash_mapping` option to `PasswordType`
8+
49
6.1
510
---
611

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form\Extension\PasswordHasher\EventListener;
13+
14+
use Symfony\Component\Form\Exception\InvalidConfigurationException;
15+
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
16+
use Symfony\Component\Form\FormEvent;
17+
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
18+
use Symfony\Component\PropertyAccess\PropertyAccess;
19+
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
20+
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
21+
22+
/**
23+
* @author Sébastien Alfaiate <s.alfaiate@webarea.fr>
24+
*/
25+
class PasswordHasherListener
26+
{
27+
private $passwordHasher;
28+
private $propertyAccessor;
29+
30+
private static $passwords = [];
31+
32+
public function __construct(UserPasswordHasherInterface $passwordHasher, PropertyAccessorInterface $propertyAccessor = null)
33+
{
34+
$this->passwordHasher = $passwordHasher;
35+
$this->propertyAccessor = $propertyAccessor ?? PropertyAccess::createPropertyAccessor();
36+
}
37+
38+
public function registerPassword(FormEvent $event)
39+
{
40+
$form = $event->getForm();
41+
$parentForm = $form->getParent();
42+
$mapped = $form->getConfig()->getMapped();
43+
44+
if ($parentForm && $parentForm->getConfig()->getType()->getInnerType() instanceof RepeatedType) {
45+
$mapped = $parentForm->getConfig()->getMapped();
46+
$parentForm = $parentForm->getParent();
47+
}
48+
49+
if ($mapped) {
50+
throw new InvalidConfigurationException('The hash_mapping option cannot be used on mapped field.');
51+
}
52+
53+
if (!$parentForm || !($user = $parentForm->getData()) || !($user instanceof PasswordAuthenticatedUserInterface)) {
54+
throw new InvalidConfigurationException('The hash_mapping option only supports PasswordAuthenticatedUserInterface objects.');
55+
}
56+
57+
self::$passwords[] = [
58+
'user' => $user,
59+
'property_path' => $form->getConfig()->getOption('hash_mapping'),
60+
'hashed_password' => $this->passwordHasher->hashPassword($user, $event->getData()),
61+
];
62+
}
63+
64+
public function hashPasswords(FormEvent $event)
65+
{
66+
$form = $event->getForm();
67+
68+
if (!$form->isRoot()) {
69+
return;
70+
}
71+
72+
if ($form->isValid()) {
73+
foreach (self::$passwords as $password) {
74+
$this->propertyAccessor->setValue($password['user'], $password['property_path'], $password['hashed_password']);
75+
}
76+
}
77+
78+
self::$passwords = [];
79+
}
80+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form\Extension\PasswordHasher;
13+
14+
use Symfony\Component\Form\AbstractExtension;
15+
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
16+
use Symfony\Component\PropertyAccess\PropertyAccess;
17+
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
18+
19+
/**
20+
* Integrates the PasswordHasher component with the Form library.
21+
*
22+
* @author Sébastien Alfaiate <s.alfaiate@webarea.fr>
23+
*/
24+
class PasswordHasherExtension extends AbstractExtension
25+
{
26+
private $passwordHasher;
27+
private $propertyAccessor;
28+
29+
public function __construct(UserPasswordHasherInterface $passwordHasher, PropertyAccessorInterface $propertyAccessor = null)
30+
{
31+
$this->passwordHasher = $passwordHasher;
32+
$this->propertyAccessor = $propertyAccessor ?? PropertyAccess::createPropertyAccessor();
33+
}
34+
35+
/**
36+
* {@inheritdoc}
37+
*/
38+
protected function loadTypeExtensions(): array
39+
{
40+
return [
41+
new Type\FormTypePasswordHasherExtension($this->passwordHasher, $this->propertyAccessor),
42+
new Type\PasswordTypePasswordHasherExtension($this->passwordHasher, $this->propertyAccessor),
43+
];
44+
}
45+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form\Extension\PasswordHasher\Type;
13+
14+
use Symfony\Component\Form\AbstractTypeExtension;
15+
use Symfony\Component\Form\Extension\Core\Type\FormType;
16+
use Symfony\Component\Form\Extension\PasswordHasher\EventListener\PasswordHasherListener;
17+
use Symfony\Component\Form\FormBuilderInterface;
18+
use Symfony\Component\Form\FormEvents;
19+
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
20+
use Symfony\Component\PropertyAccess\PropertyAccess;
21+
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
22+
23+
/**
24+
* @author Sébastien Alfaiate <s.alfaiate@webarea.fr>
25+
*/
26+
class FormTypePasswordHasherExtension extends AbstractTypeExtension
27+
{
28+
private $passwordHasher;
29+
private $propertyAccessor;
30+
31+
public function __construct(UserPasswordHasherInterface $passwordHasher, PropertyAccessorInterface $propertyAccessor = null)
32+
{
33+
$this->passwordHasher = $passwordHasher;
34+
$this->propertyAccessor = $propertyAccessor ?? PropertyAccess::createPropertyAccessor();
35+
}
36+
37+
/**
38+
* {@inheritdoc}
39+
*/
40+
public function buildForm(FormBuilderInterface $builder, array $options)
41+
{
42+
$builder->addEventListener(FormEvents::POST_SUBMIT, [new PasswordHasherListener($this->passwordHasher, $this->propertyAccessor), 'hashPasswords']);
43+
}
44+
45+
/**
46+
* {@inheritdoc}
47+
*/
48+
public static function getExtendedTypes(): iterable
49+
{
50+
return [FormType::class];
51+
}
52+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form\Extension\PasswordHasher\Type;
13+
14+
use Symfony\Component\Form\AbstractTypeExtension;
15+
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
16+
use Symfony\Component\Form\Extension\PasswordHasher\EventListener\PasswordHasherListener;
17+
use Symfony\Component\Form\FormBuilderInterface;
18+
use Symfony\Component\Form\FormEvents;
19+
use Symfony\Component\OptionsResolver\OptionsResolver;
20+
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
21+
use Symfony\Component\PropertyAccess\PropertyAccess;
22+
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
23+
24+
/**
25+
* @author Sébastien Alfaiate <s.alfaiate@webarea.fr>
26+
*/
27+
class PasswordTypePasswordHasherExtension extends AbstractTypeExtension
28+
{
29+
private $passwordHasher;
30+
private $propertyAccessor;
31+
32+
public function __construct(UserPasswordHasherInterface $passwordHasher, PropertyAccessorInterface $propertyAccessor = null)
33+
{
34+
$this->passwordHasher = $passwordHasher;
35+
$this->propertyAccessor = $propertyAccessor ?? PropertyAccess::createPropertyAccessor();
36+
}
37+
38+
/**
39+
* {@inheritdoc}
40+
*/
41+
public function buildForm(FormBuilderInterface $builder, array $options)
42+
{
43+
if ($options['hash_mapping']) {
44+
$builder->addEventListener(FormEvents::POST_SUBMIT, [new PasswordHasherListener($this->passwordHasher, $this->propertyAccessor), 'registerPassword']);
45+
}
46+
}
47+
48+
/**
49+
* {@inheritdoc}
50+
*/
51+
public function configureOptions(OptionsResolver $resolver)
52+
{
53+
$resolver->setDefaults([
54+
'hash_mapping' => null,
55+
]);
56+
}
57+
58+
/**
59+
* {@inheritdoc}
60+
*/
61+
public static function getExtendedTypes(): iterable
62+
{
63+
return [PasswordType::class];
64+
}
65+
}

0 commit comments

Comments
 (0)