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

Skip to content

Commit ed07ff3

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

File tree

126 files changed

+3871
-176
lines changed

Some content is hidden

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

126 files changed

+3871
-176
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/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: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ public function getConfigTreeBuilder()
9494
;
9595

9696
$this->addEncodersSection($rootNode);
97+
$this->addPasswordHashersSection($rootNode);
9798
$this->addProvidersSection($rootNode);
9899
$this->addFirewallsSection($rootNode, $this->factories);
99100
$this->addAccessControlSection($rootNode);
@@ -355,6 +356,7 @@ private function addEncodersSection(ArrayNodeDefinition $rootNode)
355356
->fixXmlConfig('encoder')
356357
->children()
357358
->arrayNode('encoders')
359+
->setDeprecated('symfony/security-bundle', '5.3', 'The child node "%node%" at path "%path%" is deprecated, use "password_hashers" instead.')
358360
->example([
359361
'App\Entity\User1' => 'auto',
360362
'App\Entity\User2' => [
@@ -401,6 +403,57 @@ private function addEncodersSection(ArrayNodeDefinition $rootNode)
401403
;
402404
}
403405

406+
private function addPasswordHashersSection(ArrayNodeDefinition $rootNode)
407+
{
408+
$rootNode
409+
->fixXmlConfig('password_hasher')
410+
->children()
411+
->arrayNode('password_hashers')
412+
->example([
413+
'App\Entity\User1' => 'auto',
414+
'App\Entity\User2' => [
415+
'algorithm' => 'auto',
416+
'time_cost' => 8,
417+
'cost' => 13,
418+
],
419+
])
420+
->requiresAtLeastOneElement()
421+
->useAttributeAsKey('class')
422+
->prototype('array')
423+
->canBeUnset()
424+
->performNoDeepMerging()
425+
->beforeNormalization()->ifString()->then(function ($v) { return ['algorithm' => $v]; })->end()
426+
->children()
427+
->scalarNode('algorithm')
428+
->cannotBeEmpty()
429+
->validate()
430+
->ifTrue(function ($v) { return !\is_string($v); })
431+
->thenInvalid('You must provide a string value.')
432+
->end()
433+
->end()
434+
->arrayNode('migrate_from')
435+
->prototype('scalar')->end()
436+
->beforeNormalization()->castToArray()->end()
437+
->end()
438+
->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()
439+
->scalarNode('key_length')->defaultValue(40)->end()
440+
->booleanNode('ignore_case')->defaultFalse()->end()
441+
->booleanNode('encode_as_base64')->defaultTrue()->end()
442+
->scalarNode('iterations')->defaultValue(5000)->end()
443+
->integerNode('cost')
444+
->min(4)
445+
->max(31)
446+
->defaultNull()
447+
->end()
448+
->scalarNode('memory_cost')->defaultNull()->end()
449+
->scalarNode('time_cost')->defaultNull()->end()
450+
->scalarNode('id')->end()
451+
->end()
452+
->end()
453+
->end()
454+
->end();
455+
}
456+
404457
private function getAccessDecisionStrategies()
405458
{
406459
$strategies = [

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

Lines changed: 137 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@
4040
use Symfony\Component\Security\Core\User\UserProviderInterface;
4141
use Symfony\Component\Security\Http\Controller\UserValueResolver;
4242
use Symfony\Component\Security\Http\Event\CheckPassportEvent;
43+
use Symfony\Component\Security\Password\Hasher\NativePasswordHasher;
44+
use Symfony\Component\Security\Password\Hasher\Pbkdf2PasswordHasher;
45+
use Symfony\Component\Security\Password\Hasher\PlaintextPasswordHasher;
46+
use Symfony\Component\Security\Password\Hasher\SodiumPasswordHasher;
4347
use Twig\Extension\AbstractExtension;
4448

4549
/**
@@ -168,13 +172,31 @@ public function load(array $configs, ContainerBuilder $container)
168172
$container->getDefinition('security.authentication.guard_handler')
169173
->replaceArgument(2, $this->statelessFirewallKeys);
170174

175+
// @deprecated since Symfony 5.3
171176
if ($config['encoders']) {
172177
$this->createEncoders($config['encoders'], $container);
178+
$config['password_hashers'] = $config['encoders'];
179+
} else {
180+
$container->removeAlias('security.encoder_factory');
181+
$container->removeAlias('security.password_encoder');
182+
$container->removeDefinition('security.user_password_encoder.generic');
183+
}
184+
185+
if ($config['password_hashers']) {
186+
$this->createHashers($config['password_hashers'], $container);
173187
}
174188

175189
if (class_exists(Application::class)) {
176190
$loader->load('console.php');
177-
$container->getDefinition('security.command.user_password_encoder')->replaceArgument(1, array_keys($config['encoders']));
191+
192+
// @deprecated since Symfony 5.3
193+
if ($config['encoders']) {
194+
$container->getDefinition('security.command.user_password_encoder')->replaceArgument(1, array_keys($config['encoders']));
195+
$container->removeDefinition('security.command.user_password_hash');
196+
} else {
197+
$container->getDefinition('security.command.user_password_hash')->replaceArgument(1, array_keys($config['password_hashers']));
198+
$container->removeDefinition('security.command.user_password_encoder');
199+
}
178200
}
179201

180202
if (!class_exists(UserValueResolver::class)) {
@@ -723,6 +745,117 @@ private function createEncoder(array $config)
723745
if ('native' === $config['algorithm']) {
724746
return [
725747
'class' => NativePasswordEncoder::class,
748+
'arguments' => [
749+
$config['time_cost'],
750+
(($config['memory_cost'] ?? 0) << 10) ?: null,
751+
$config['cost'],
752+
] + (isset($config['native_algorithm']) ? [3 => $config['native_algorithm']] : []),
753+
];
754+
}
755+
756+
if ('sodium' === $config['algorithm']) {
757+
if (!SodiumPasswordEncoder::isSupported()) {
758+
throw new InvalidConfigurationException('Libsodium is not available. Install the sodium extension or use "auto" instead.');
759+
}
760+
761+
return [
762+
'class' => SodiumPasswordEncoder::class,
763+
'arguments' => [
764+
$config['time_cost'],
765+
(($config['memory_cost'] ?? 0) << 10) ?: null,
766+
],
767+
];
768+
}
769+
770+
// run-time configured encoder
771+
return $config;
772+
}
773+
774+
private function createHashers(array $hashers, ContainerBuilder $container)
775+
{
776+
$hasherMap = [];
777+
foreach ($hashers as $class => $hasher) {
778+
$hasherMap[$class] = $this->createHasher($hasher);
779+
}
780+
781+
$container
782+
->getDefinition('security.hasher_factory.generic')
783+
->setArguments([$hasherMap])
784+
;
785+
}
786+
787+
private function createHasher(array $config)
788+
{
789+
// a custom hasher service
790+
if (isset($config['id'])) {
791+
return new Reference($config['id']);
792+
}
793+
794+
if ($config['migrate_from'] ?? false) {
795+
return $config;
796+
}
797+
798+
// plaintext hasher
799+
if ('plaintext' === $config['algorithm']) {
800+
$arguments = [$config['ignore_case']];
801+
802+
return [
803+
'class' => PlaintextPasswordHasher::class,
804+
'arguments' => $arguments,
805+
];
806+
}
807+
808+
// pbkdf2 hasher
809+
if ('pbkdf2' === $config['algorithm']) {
810+
return [
811+
'class' => Pbkdf2PasswordHasher::class,
812+
'arguments' => [
813+
$config['hash_algorithm'],
814+
$config['encode_as_base64'],
815+
$config['iterations'],
816+
$config['key_length'],
817+
],
818+
];
819+
}
820+
821+
// bcrypt hasher
822+
if ('bcrypt' === $config['algorithm']) {
823+
$config['algorithm'] = 'native';
824+
$config['native_algorithm'] = \PASSWORD_BCRYPT;
825+
826+
return $this->createHasher($config);
827+
}
828+
829+
// Argon2i hasher
830+
if ('argon2i' === $config['algorithm']) {
831+
if (SodiumPasswordEncoder::isSupported() && !\defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) {
832+
$config['algorithm'] = 'sodium';
833+
} elseif (\defined('PASSWORD_ARGON2I')) {
834+
$config['algorithm'] = 'native';
835+
$config['native_algorithm'] = \PASSWORD_ARGON2I;
836+
} else {
837+
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'));
838+
}
839+
840+
return $this->createHasher($config);
841+
}
842+
843+
if ('argon2id' === $config['algorithm']) {
844+
if (($hasSodium = SodiumPasswordHasher::isSupported()) && \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) {
845+
$config['algorithm'] = 'sodium';
846+
} elseif (\defined('PASSWORD_ARGON2ID')) {
847+
$config['algorithm'] = 'native';
848+
$config['native_algorithm'] = \PASSWORD_ARGON2ID;
849+
} else {
850+
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'));
851+
}
852+
853+
return $this->createHasher($config);
854+
}
855+
856+
if ('native' === $config['algorithm']) {
857+
return [
858+
'class' => NativePasswordHasher::class,
726859
'arguments' => [
727860
$config['time_cost'],
728861
(($config['memory_cost'] ?? 0) << 10) ?: null,
@@ -732,20 +865,20 @@ private function createEncoder(array $config)
732865
}
733866

734867
if ('sodium' === $config['algorithm']) {
735-
if (!SodiumPasswordEncoder::isSupported()) {
868+
if (!SodiumPasswordHasher::isSupported()) {
736869
throw new InvalidConfigurationException('Libsodium is not available. Install the sodium extension or use "auto" instead.');
737870
}
738871

739872
return [
740-
'class' => SodiumPasswordEncoder::class,
873+
'class' => SodiumPasswordHasher::class,
741874
'arguments' => [
742875
$config['time_cost'],
743876
(($config['memory_cost'] ?? 0) << 10) ?: null,
744877
],
745878
];
746879
}
747880

748-
// run-time configured encoder
881+
// run-time configured hasher
749882
return $config;
750883
}
751884

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)