Skip to content

Commit cfb34c4

Browse files
committed
[make:user] Use password_hashers instead of encoders
And also improve the position of this config when "enable_authenticator_manager" is the first option.
1 parent 5da4340 commit cfb34c4

24 files changed

+219
-17
lines changed

src/Maker/MakeUser.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
use Symfony\Component\Console\Input\InputArgument;
3232
use Symfony\Component\Console\Input\InputInterface;
3333
use Symfony\Component\Console\Input\InputOption;
34+
use Symfony\Component\PasswordHasher\Hasher\NativePasswordHasher;
3435
use Symfony\Component\Security\Core\Encoder\Argon2iPasswordEncoder;
3536
use Symfony\Component\Security\Core\Encoder\NativePasswordEncoder;
3637
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
@@ -116,6 +117,11 @@ class_exists(DoctrineBundle::class)
116117
$userWillHavePassword = $io->confirm('Does this app need to hash/check user passwords?');
117118
$input->setOption('with-password', $userWillHavePassword);
118119

120+
$symfonyGte53 = class_exists(NativePasswordHasher::class);
121+
if ($symfonyGte53) {
122+
return;
123+
}
124+
119125
if ($userWillHavePassword && !class_exists(NativePasswordEncoder::class) && Argon2iPasswordEncoder::isSupported()) {
120126
$io->writeln('The newer <comment>Argon2i</comment> password hasher requires PHP 7.2, libsodium or paragonie/sodium_compat. Your system DOES support this algorithm.');
121127
$io->writeln('You should use <comment>Argon2i</comment> unless your production system will not support it.');

src/Resources/skeleton/security/UserProvider.tpl.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,13 @@ public function supportsClass($class)
6262
<?php if ($password_upgrader): ?>
6363

6464
/**
65-
* Upgrades the encoded password of a user, typically for using a better hash algorithm.
65+
* Upgrades the hashed password of a user, typically for using a better hash algorithm.
6666
*/
67-
public function upgradePassword(UserInterface $user, string $newEncodedPassword): void
67+
public function upgradePassword(UserInterface $user, string $newHashedPassword): void
6868
{
69-
// TODO: when encoded passwords are in use, this method should:
69+
// TODO: when hashed passwords are in use, this method should:
7070
// 1. persist the new password in the user storage
71-
// 2. update the $user object with $user->setPassword($newEncodedPassword);
71+
// 2. update the $user object with $user->setPassword($newHashedPassword);
7272
}
7373
<?php endif ?>
7474
}

src/Security/SecurityConfigUpdater.php

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313

1414
use Symfony\Bundle\MakerBundle\Util\YamlSourceManipulator;
1515
use Symfony\Component\HttpKernel\Log\Logger;
16+
use Symfony\Component\PasswordHasher\Hasher\NativePasswordHasher;
1617
use Symfony\Component\Security\Core\Encoder\NativePasswordEncoder;
18+
use function array_key_first;
1719

1820
/**
1921
* @author Ryan Weaver <ryan@symfonycasts.com>
@@ -50,7 +52,8 @@ public function updateForUserClass(string $yamlSource, UserClassConfiguration $u
5052
$this->updateProviders($userConfig, $userClass);
5153

5254
if ($userConfig->hasPassword()) {
53-
$this->updateEncoders($userConfig, $userClass);
55+
$symfonyGte53 = class_exists(NativePasswordHasher::class);
56+
$this->updatePasswordHashers($userConfig, $userClass, $symfonyGte53 ? 'password_hashers' : 'encoders');
5457
}
5558

5659
$contents = $this->manipulator->getContents();
@@ -175,18 +178,34 @@ private function updateProviders(UserClassConfiguration $userConfig, string $use
175178
$this->manipulator->setData($newData);
176179
}
177180

178-
private function updateEncoders(UserClassConfiguration $userConfig, string $userClass)
181+
private function updatePasswordHashers(UserClassConfiguration $userConfig, string $userClass, string $keyName = 'password_hashers')
179182
{
180183
$newData = $this->manipulator->getData();
181-
if (!isset($newData['security']['encoders'])) {
182-
// encoders is usually the first key, by convention
183-
$newData['security'] = ['encoders' => []] + $newData['security'];
184+
if ('password_hashers' === $keyName && isset($newData['security']['encoders'])) {
185+
// fallback to "encoders" if the user already defined encoder config
186+
$this->updatePasswordHashers($userClass, $userClass, 'encoders');
187+
188+
return;
189+
}
190+
191+
if (!isset($newData['security'][$keyName])) {
192+
// by convention, password_hashers are put before the user provider option
193+
$providersIndex = array_search('providers', array_keys($newData['security']));
194+
if (false === $providersIndex) {
195+
$newData['security'] = [$keyName => []] + $newData['security'];
196+
} else {
197+
$newData['security'] = array_merge(
198+
array_slice($newData['security'], 0, $providersIndex),
199+
[$keyName => []],
200+
array_slice($newData['security'], $providersIndex)
201+
);
202+
}
184203
}
185204

186-
$newData['security']['encoders'][$userClass] = [
187-
'algorithm' => $userConfig->shouldUseArgon2() ? 'argon2i' : (class_exists(NativePasswordEncoder::class) ? 'auto' : 'bcrypt'),
205+
$newData['security'][$keyName][$userClass] = [
206+
'algorithm' => $userConfig->shouldUseArgon2() ? 'argon2i' : ((class_exists(NativePasswordHasher::class) || class_exists(NativePasswordEncoder::class)) ? 'auto' : 'bcrypt'),
188207
];
189-
$newData['security']['encoders']['_'] = $this->manipulator->createEmptyLine();
208+
$newData['security'][$keyName]['_'] = $this->manipulator->createEmptyLine();
190209

191210
$this->manipulator->setData($newData);
192211
}

tests/Security/SecurityConfigUpdaterTest.php

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Symfony\Bundle\MakerBundle\Security\SecurityConfigUpdater;
1717
use Symfony\Bundle\MakerBundle\Security\UserClassConfiguration;
1818
use Symfony\Component\HttpKernel\Log\Logger;
19+
use Symfony\Component\PasswordHasher\Hasher\NativePasswordHasher;
1920
use Symfony\Component\Security\Core\Encoder\NativePasswordEncoder;
2021

2122
class SecurityConfigUpdaterTest extends TestCase
@@ -48,9 +49,10 @@ public function testUpdateForUserClass(UserClassConfiguration $userConfig, strin
4849
$updater = new SecurityConfigUpdater($this->ysmLogger);
4950
$source = file_get_contents(__DIR__.'/yaml_fixtures/source/'.$startingSourceFilename);
5051
$actualSource = $updater->updateForUserClass($source, $userConfig, $userClass);
51-
$expectedSource = file_get_contents(__DIR__.'/yaml_fixtures/expected_user_class/'.$expectedSourceFilename);
52+
$symfonyVersion = class_exists(NativePasswordHasher::class) ? '5.3' : 'legacy';
53+
$expectedSource = file_get_contents(__DIR__.'/yaml_fixtures/expected_user_class/'.$symfonyVersion.'/'.$expectedSourceFilename);
5254

53-
$bcryptOrAuto = class_exists(NativePasswordEncoder::class) ? 'auto' : 'bcrypt';
55+
$bcryptOrAuto = ('5.3' === $symfonyVersion || class_exists(NativePasswordEncoder::class)) ? 'auto' : 'bcrypt';
5456
$expectedSource = str_replace('{BCRYPT_OR_AUTO}', $bcryptOrAuto, $expectedSource);
5557

5658
$this->assertSame($expectedSource, $actualSource);
@@ -95,6 +97,18 @@ public function getUserClassTests()
9597
'simple_security_with_single_memory_provider_configured.yaml',
9698
'simple_security_with_single_memory_provider_configured.yaml',
9799
];
100+
101+
yield 'security_52_empty_security' => [
102+
new UserClassConfiguration(true, 'email', true),
103+
'security_52_entity_email_with_password.yaml',
104+
'security_52_empty_security.yaml',
105+
];
106+
107+
yield 'security_52_with_firewalls_and_logout' => [
108+
new UserClassConfiguration(true, 'email', true),
109+
'security_52_complex_entity_email_with_password.yaml',
110+
'security_52_with_firewalls_and_logout.yaml',
111+
];
98112
}
99113

100114
/**

tests/Security/yaml_fixtures/expected_authenticator/empty_source.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ security:
33
main:
44
anonymous: lazy
55
guard:
6-
authenticators: [App\Security\AppCustomAuthenticator]
6+
authenticators: [App\Security\AppCustomAuthenticator]
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
security:
2+
password_hashers:
3+
App\Security\User:
4+
algorithm: {BCRYPT_OR_AUTO}
5+
6+
providers:
7+
# used to reload user from session & other features (e.g. switch_user)
8+
app_user_provider:
9+
id: App\Security\UserProvider
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
security:
2+
password_hashers:
3+
App\Entity\User:
4+
algorithm: {BCRYPT_OR_AUTO}
5+
6+
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
7+
providers:
8+
# used to reload user from session & other features (e.g. switch_user)
9+
app_user_provider:
10+
entity:
11+
class: App\Entity\User
12+
property: email
13+
14+
firewalls:
15+
dev: ~
16+
main: ~
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
security:
2+
password_hashers:
3+
App\Security\User:
4+
algorithm: {BCRYPT_OR_AUTO}
5+
6+
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
7+
providers:
8+
in_memory: { memory: ~ }
9+
other_provider: { foo: true }
10+
# used to reload user from session & other features (e.g. switch_user)
11+
app_user_provider:
12+
id: App\Security\UserProvider
13+
14+
firewalls:
15+
dev: ~
16+
main: ~
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
security:
2+
password_hashers:
3+
App\Security\User:
4+
algorithm: {BCRYPT_OR_AUTO}
5+
6+
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
7+
providers:
8+
# used to reload user from session & other features (e.g. switch_user)
9+
app_user_provider:
10+
id: App\Security\UserProvider
11+
12+
firewalls:
13+
dev: ~
14+
main: ~
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
security:
2+
enable_authenticator_manager: true
3+
4+
password_hashers:
5+
App\Entity\User:
6+
algorithm: {BCRYPT_OR_AUTO}
7+
8+
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
9+
providers:
10+
# used to reload user from session & other features (e.g. switch_user)
11+
app_user_provider:
12+
entity:
13+
class: App\Entity\User
14+
property: email
15+
16+
firewalls:
17+
dev:
18+
pattern: ^/(_(profiler|wdt)|css|images|js)/
19+
security: false
20+
main:
21+
lazy: true
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
security:
2+
enable_authenticator_manager: true
3+
4+
password_hashers:
5+
App\Entity\User:
6+
algorithm: {BCRYPT_OR_AUTO}
7+
8+
providers:
9+
# used to reload user from session & other features (e.g. switch_user)
10+
app_user_provider:
11+
entity:
12+
class: App\Entity\User
13+
property: email
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
security:
2+
password_hashers:
3+
App\Entity\User:
4+
algorithm: {BCRYPT_OR_AUTO}
5+
6+
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
7+
providers:
8+
# used to reload user from session & other features (e.g. switch_user)
9+
app_user_provider:
10+
entity:
11+
class: App\Entity\User
12+
property: email
13+
14+
firewalls:
15+
dev: ~
16+
main:
17+
anonymous: lazy
18+
provider: app_user_provider

tests/Security/yaml_fixtures/expected_user_class/empty_source_model_email_with_password.yaml renamed to tests/Security/yaml_fixtures/expected_user_class/legacy/empty_source_model_email_with_password.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ security:
66
providers:
77
# used to reload user from session & other features (e.g. switch_user)
88
app_user_provider:
9-
id: App\Security\UserProvider
9+
id: App\Security\UserProvider
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
security:
2+
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
3+
providers:
4+
# used to reload user from session & other features (e.g. switch_user)
5+
app_user_provider:
6+
entity:
7+
class: App\Entity\User
8+
property: username
9+
10+
firewalls:
11+
dev: ~
12+
main: ~
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
security:
2+
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
3+
providers:
4+
# used to reload user from session & other features (e.g. switch_user)
5+
app_user_provider:
6+
id: App\Security\UserProvider
7+
8+
firewalls:
9+
dev: ~
10+
main: ~
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
security:
2+
enable_authenticator_manager: true
3+
4+
encoders:
5+
App\Entity\User:
6+
algorithm: {BCRYPT_OR_AUTO}
7+
8+
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
9+
providers:
10+
# used to reload user from session & other features (e.g. switch_user)
11+
app_user_provider:
12+
entity:
13+
class: App\Entity\User
14+
property: email
15+
16+
firewalls:
17+
dev:
18+
pattern: ^/(_(profiler|wdt)|css|images|js)/
19+
security: false
20+
main:
21+
lazy: true
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
security:
2+
enable_authenticator_manager: true
3+
4+
encoders:
5+
App\Entity\User:
6+
algorithm: {BCRYPT_OR_AUTO}
7+
8+
providers:
9+
# used to reload user from session & other features (e.g. switch_user)
10+
app_user_provider:
11+
entity:
12+
class: App\Entity\User
13+
property: email
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
security: {}
1+
security: {}

0 commit comments

Comments
 (0)