Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b6ed024

Browse files
committed
[Security] Extract password hashing from security-core - using the right naming
1 parent f7a0926 commit b6ed024

File tree

130 files changed

+3883
-179
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

130 files changed

+3883
-179
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
"symfony/security-csrf": "self.version",
8787
"symfony/security-guard": "self.version",
8888
"symfony/security-http": "self.version",
89+
"symfony/security-password": "self.version",
8990
"symfony/semaphore": "self.version",
9091
"symfony/sendgrid-mailer": "self.version",
9192
"symfony/serializer": "self.version",

src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/TestBundle.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,6 @@ public function build(ContainerBuilder $container)
3737
$extension->setCustomConfig(new CustomConfig());
3838

3939
$container->addCompilerPass(new AnnotationReaderPass(), PassConfig::TYPE_AFTER_REMOVING);
40-
$container->addCompilerPass(new CheckTypeDeclarationsPass(true), PassConfig::TYPE_AFTER_REMOVING, -100);
40+
$container->addCompilerPass(new CheckTypeDeclarationsPass(false), PassConfig::TYPE_AFTER_REMOVING, -100);
4141
}
4242
}

src/Symfony/Bundle/FrameworkBundle/composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
"symfony/messenger": "^5.2",
5151
"symfony/mime": "^4.4|^5.0",
5252
"symfony/process": "^4.4|^5.0",
53-
"symfony/security-bundle": "^5.1",
53+
"symfony/security-bundle": "^5.3",
5454
"symfony/security-csrf": "^4.4|^5.0",
5555
"symfony/security-http": "^4.4|^5.0",
5656
"symfony/serializer": "^5.2",

src/Symfony/Bundle/SecurityBundle/Command/UserPasswordEncoderCommand.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,16 @@
2323
use Symfony\Component\Console\Style\SymfonyStyle;
2424
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
2525
use Symfony\Component\Security\Core\Encoder\SelfSaltingEncoderInterface;
26+
use Symfony\Component\Security\Password\Command\UserPasswordHashCommand;
2627

2728
/**
2829
* Encode a user's password.
2930
*
3031
* @author Sarah Khalil <[email protected]>
3132
*
3233
* @final
34+
*
35+
* @deprecated since Symfony 5.3, use {@link UserPasswordHashCommand} instead
3336
*/
3437
class UserPasswordEncoderCommand extends Command
3538
{

src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,25 @@ public function getConfigTreeBuilder()
6565
return $v;
6666
})
6767
->end()
68+
->beforeNormalization()
69+
->ifTrue(function ($v) {
70+
if (($v['encoders'] ?? false)) {
71+
trigger_deprecation('symfony/security-bundle', '5.3', 'The child node "encoders" at path "security" is deprecated, use "password_hashers" instead.');
72+
73+
return true;
74+
}
75+
76+
if ($v['password_hashers'] ?? false) {
77+
return true;
78+
}
79+
})
80+
->then(function ($v) {
81+
$v['password_hashers'] = array_merge($v['password_hashers'] ?? [], $v['encoders'] ?? []);
82+
$v['encoders'] = array_merge($v['password_hashers'] ?? [], $v['encoders'] ?? []);
83+
84+
return $v;
85+
})
86+
->end()
6887
->children()
6988
->scalarNode('access_denied_url')->defaultNull()->example('/foo/error403')->end()
7089
->enumNode('session_fixation_strategy')
@@ -94,6 +113,7 @@ public function getConfigTreeBuilder()
94113
;
95114

96115
$this->addEncodersSection($rootNode);
116+
$this->addPasswordHashersSection($rootNode);
97117
$this->addProvidersSection($rootNode);
98118
$this->addFirewallsSection($rootNode, $this->factories);
99119
$this->addAccessControlSection($rootNode);
@@ -401,6 +421,57 @@ private function addEncodersSection(ArrayNodeDefinition $rootNode)
401421
;
402422
}
403423

424+
private function addPasswordHashersSection(ArrayNodeDefinition $rootNode)
425+
{
426+
$rootNode
427+
->fixXmlConfig('password_hasher')
428+
->children()
429+
->arrayNode('password_hashers')
430+
->example([
431+
'App\Entity\User1' => 'auto',
432+
'App\Entity\User2' => [
433+
'algorithm' => 'auto',
434+
'time_cost' => 8,
435+
'cost' => 13,
436+
],
437+
])
438+
->requiresAtLeastOneElement()
439+
->useAttributeAsKey('class')
440+
->prototype('array')
441+
->canBeUnset()
442+
->performNoDeepMerging()
443+
->beforeNormalization()->ifString()->then(function ($v) { return ['algorithm' => $v]; })->end()
444+
->children()
445+
->scalarNode('algorithm')
446+
->cannotBeEmpty()
447+
->validate()
448+
->ifTrue(function ($v) { return !\is_string($v); })
449+
->thenInvalid('You must provide a string value.')
450+
->end()
451+
->end()
452+
->arrayNode('migrate_from')
453+
->prototype('scalar')->end()
454+
->beforeNormalization()->castToArray()->end()
455+
->end()
456+
->scalarNode('hash_algorithm')->info('Name of hashing algorithm for PBKDF2 (i.e. sha256, sha512, etc..) See hash_algos() for a list of supported algorithms.')->defaultValue('sha512')->end()
457+
->scalarNode('key_length')->defaultValue(40)->end()
458+
->booleanNode('ignore_case')->defaultFalse()->end()
459+
->booleanNode('encode_as_base64')->defaultTrue()->end()
460+
->scalarNode('iterations')->defaultValue(5000)->end()
461+
->integerNode('cost')
462+
->min(4)
463+
->max(31)
464+
->defaultNull()
465+
->end()
466+
->scalarNode('memory_cost')->defaultNull()->end()
467+
->scalarNode('time_cost')->defaultNull()->end()
468+
->scalarNode('id')->end()
469+
->end()
470+
->end()
471+
->end()
472+
->end();
473+
}
474+
404475
private function getAccessDecisionStrategies()
405476
{
406477
$strategies = [

src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php

Lines changed: 127 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@
3838
use Symfony\Component\Security\Core\User\ChainUserProvider;
3939
use Symfony\Component\Security\Core\User\UserProviderInterface;
4040
use Symfony\Component\Security\Http\Event\CheckPassportEvent;
41+
use Symfony\Component\Security\Password\Hasher\NativePasswordHasher;
42+
use Symfony\Component\Security\Password\Hasher\Pbkdf2PasswordHasher;
43+
use Symfony\Component\Security\Password\Hasher\PlaintextPasswordHasher;
44+
use Symfony\Component\Security\Password\Hasher\SodiumPasswordHasher;
4145
use Twig\Extension\AbstractExtension;
4246

4347
/**
@@ -166,13 +170,22 @@ public function load(array $configs, ContainerBuilder $container)
166170
$container->getDefinition('security.authentication.guard_handler')
167171
->replaceArgument(2, $this->statelessFirewallKeys);
168172

173+
// @deprecated since Symfony 5.3
169174
if ($config['encoders']) {
170175
$this->createEncoders($config['encoders'], $container);
171176
}
172177

178+
if ($config['password_hashers']) {
179+
$this->createHashers($config['password_hashers'], $container);
180+
}
181+
173182
if (class_exists(Application::class)) {
174183
$loader->load('console.php');
184+
185+
// @deprecated since Symfony 5.3
175186
$container->getDefinition('security.command.user_password_encoder')->replaceArgument(1, array_keys($config['encoders']));
187+
188+
$container->getDefinition('security.command.user_password_hash')->replaceArgument(1, array_keys($config['password_hashers']));
176189
}
177190

178191
$container->registerForAutoconfiguration(VoterInterface::class)
@@ -717,6 +730,117 @@ private function createEncoder(array $config)
717730
if ('native' === $config['algorithm']) {
718731
return [
719732
'class' => NativePasswordEncoder::class,
733+
'arguments' => [
734+
$config['time_cost'],
735+
(($config['memory_cost'] ?? 0) << 10) ?: null,
736+
$config['cost'],
737+
] + (isset($config['native_algorithm']) ? [3 => $config['native_algorithm']] : []),
738+
];
739+
}
740+
741+
if ('sodium' === $config['algorithm']) {
742+
if (!SodiumPasswordEncoder::isSupported()) {
743+
throw new InvalidConfigurationException('Libsodium is not available. Install the sodium extension or use "auto" instead.');
744+
}
745+
746+
return [
747+
'class' => SodiumPasswordEncoder::class,
748+
'arguments' => [
749+
$config['time_cost'],
750+
(($config['memory_cost'] ?? 0) << 10) ?: null,
751+
],
752+
];
753+
}
754+
755+
// run-time configured encoder
756+
return $config;
757+
}
758+
759+
private function createHashers(array $hashers, ContainerBuilder $container)
760+
{
761+
$hasherMap = [];
762+
foreach ($hashers as $class => $hasher) {
763+
$hasherMap[$class] = $this->createHasher($hasher);
764+
}
765+
766+
$container
767+
->getDefinition('security.hasher_factory.generic')
768+
->setArguments([$hasherMap])
769+
;
770+
}
771+
772+
private function createHasher(array $config)
773+
{
774+
// a custom hasher service
775+
if (isset($config['id'])) {
776+
return new Reference($config['id']);
777+
}
778+
779+
if ($config['migrate_from'] ?? false) {
780+
return $config;
781+
}
782+
783+
// plaintext hasher
784+
if ('plaintext' === $config['algorithm']) {
785+
$arguments = [$config['ignore_case']];
786+
787+
return [
788+
'class' => PlaintextPasswordHasher::class,
789+
'arguments' => $arguments,
790+
];
791+
}
792+
793+
// pbkdf2 hasher
794+
if ('pbkdf2' === $config['algorithm']) {
795+
return [
796+
'class' => Pbkdf2PasswordHasher::class,
797+
'arguments' => [
798+
$config['hash_algorithm'],
799+
$config['encode_as_base64'],
800+
$config['iterations'],
801+
$config['key_length'],
802+
],
803+
];
804+
}
805+
806+
// bcrypt hasher
807+
if ('bcrypt' === $config['algorithm']) {
808+
$config['algorithm'] = 'native';
809+
$config['native_algorithm'] = \PASSWORD_BCRYPT;
810+
811+
return $this->createHasher($config);
812+
}
813+
814+
// Argon2i hasher
815+
if ('argon2i' === $config['algorithm']) {
816+
if (SodiumPasswordHasher::isSupported() && !\defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) {
817+
$config['algorithm'] = 'sodium';
818+
} elseif (\defined('PASSWORD_ARGON2I')) {
819+
$config['algorithm'] = 'native';
820+
$config['native_algorithm'] = \PASSWORD_ARGON2I;
821+
} else {
822+
throw new InvalidConfigurationException(sprintf('Algorithm "argon2i" is not available. Either use "%s" or upgrade to PHP 7.2+ instead.', \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13') ? 'argon2id", "auto' : 'auto'));
823+
}
824+
825+
return $this->createHasher($config);
826+
}
827+
828+
if ('argon2id' === $config['algorithm']) {
829+
if (($hasSodium = SodiumPasswordHasher::isSupported()) && \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) {
830+
$config['algorithm'] = 'sodium';
831+
} elseif (\defined('PASSWORD_ARGON2ID')) {
832+
$config['algorithm'] = 'native';
833+
$config['native_algorithm'] = \PASSWORD_ARGON2ID;
834+
} else {
835+
throw new InvalidConfigurationException(sprintf('Algorithm "argon2id" is not available. Either use "%s", upgrade to PHP 7.3+ or use libsodium 1.0.15+ instead.', \defined('PASSWORD_ARGON2I') || $hasSodium ? 'argon2i", "auto' : 'auto'));
836+
}
837+
838+
return $this->createHasher($config);
839+
}
840+
841+
if ('native' === $config['algorithm']) {
842+
return [
843+
'class' => NativePasswordHasher::class,
720844
'arguments' => [
721845
$config['time_cost'],
722846
(($config['memory_cost'] ?? 0) << 10) ?: null,
@@ -726,20 +850,20 @@ private function createEncoder(array $config)
726850
}
727851

728852
if ('sodium' === $config['algorithm']) {
729-
if (!SodiumPasswordEncoder::isSupported()) {
853+
if (!SodiumPasswordHasher::isSupported()) {
730854
throw new InvalidConfigurationException('Libsodium is not available. Install the sodium extension or use "auto" instead.');
731855
}
732856

733857
return [
734-
'class' => SodiumPasswordEncoder::class,
858+
'class' => SodiumPasswordHasher::class,
735859
'arguments' => [
736860
$config['time_cost'],
737861
(($config['memory_cost'] ?? 0) << 10) ?: null,
738862
],
739863
];
740864
}
741865

742-
// run-time configured encoder
866+
// run-time configured hasher
743867
return $config;
744868
}
745869

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
1313

1414
use Symfony\Bundle\SecurityBundle\Command\UserPasswordEncoderCommand;
15+
use Symfony\Component\Security\Password\Command\UserPasswordHashCommand;
1516

1617
return static function (ContainerConfigurator $container) {
1718
$container->services()
@@ -21,5 +22,15 @@
2122
abstract_arg('encoders user classes'),
2223
])
2324
->tag('console.command', ['command' => 'security:encode-password'])
25+
->deprecate('symfony/security-bundle', '5.3', 'The "%service_id%" service is deprecated, use "security.command.user_password_hash" instead.')
26+
;
27+
28+
$container->services()
29+
->set('security.command.user_password_hash', UserPasswordHashCommand::class)
30+
->args([
31+
service('security.hasher_factory'),
32+
abstract_arg('hashers user classes'),
33+
])
34+
->tag('console.command', ['command' => 'security:hash-password'])
2435
;
2536
};

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
abstract_arg('User Provider'),
3535
abstract_arg('Provider-shared Key'),
3636
abstract_arg('User Checker'),
37-
service('security.password_encoder'),
37+
service('security.password_hasher'),
3838
])
3939

4040
->set('security.authentication.listener.guard', GuardAuthenticationListener::class)

src/Symfony/Bundle/SecurityBundle/Resources/config/schema/security-1.0.xsd

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
<xsd:element name="access-decision-manager" type="access_decision_manager" minOccurs="0" maxOccurs="1" />
1212
<xsd:element name="encoders" type="encoders" minOccurs="0" maxOccurs="1" />
1313
<xsd:element name="encoder" type="encoder" minOccurs="0" maxOccurs="unbounded" />
14+
<xsd:element name="password_hashers" type="password_hashers" minOccurs="0" maxOccurs="1" />
15+
<xsd:element name="password_hasher" type="password_hasher" minOccurs="0" maxOccurs="unbounded" />
1416
<xsd:element name="providers" type="providers" minOccurs="0" maxOccurs="1" />
1517
<xsd:element name="provider" type="provider" minOccurs="0" maxOccurs="unbounded" />
1618
<xsd:element name="firewalls" type="firewalls" minOccurs="0" maxOccurs="1" />
@@ -31,6 +33,12 @@
3133
</xsd:sequence>
3234
</xsd:complexType>
3335

36+
<xsd:complexType name="password_hashers">
37+
<xsd:sequence>
38+
<xsd:element name="password_hasher" type="password_hasher" minOccurs="1" maxOccurs="unbounded" />
39+
</xsd:sequence>
40+
</xsd:complexType>
41+
3442
<xsd:complexType name="providers">
3543
<xsd:sequence>
3644
<xsd:element name="provider" type="provider" minOccurs="1" maxOccurs="unbounded" />
@@ -84,6 +92,23 @@
8492
<xsd:attribute name="id" type="xsd:string" />
8593
</xsd:complexType>
8694

95+
<xsd:complexType name="password_hasher">
96+
<xsd:sequence>
97+
<xsd:element name="migrate-from" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
98+
</xsd:sequence>
99+
<xsd:attribute name="class" type="xsd:string" use="required" />
100+
<xsd:attribute name="algorithm" type="xsd:string" />
101+
<xsd:attribute name="hash-algorithm" type="xsd:string" />
102+
<xsd:attribute name="key-length" type="xsd:string" />
103+
<xsd:attribute name="ignore-case" type="xsd:boolean" />
104+
<xsd:attribute name="encode-as-base64" type="xsd:boolean" />
105+
<xsd:attribute name="iterations" type="xsd:string" />
106+
<xsd:attribute name="cost" type="xsd:integer" />
107+
<xsd:attribute name="memory-cost" type="xsd:string" />
108+
<xsd:attribute name="time-cost" type="xsd:string" />
109+
<xsd:attribute name="id" type="xsd:string" />
110+
</xsd:complexType>
111+
87112
<xsd:complexType name="provider">
88113
<xsd:choice minOccurs="0" maxOccurs="1">
89114
<xsd:element name="chain" type="chain" />

0 commit comments

Comments
 (0)