diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index ddb1d3cc92b06..064014421747f 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -721,7 +721,10 @@ private function createHasher(array $config): Reference|array { // a custom hasher service if (isset($config['id'])) { - return new Reference($config['id']); + return $config['migrate_from'] ?? false ? [ + 'instance' => new Reference($config['id']), + 'migrate_from' => $config['migrate_from'], + ] : new Reference($config['id']); } if ($config['migrate_from'] ?? false) { diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php index 4c8c16e6a3245..00a0b089d2b41 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php @@ -887,6 +887,32 @@ public function testNothingDoneWithEmptyConfiguration() $this->assertFalse($container->has('security.authorization_checker')); } + public function testCustomHasherWithMigrateFrom() + { + $container = $this->getRawContainer(); + + $container->loadFromExtension('security', [ + 'password_hashers' => [ + 'legacy' => 'md5', + 'App\User' => [ + 'id' => 'App\Security\CustomHasher', + 'migrate_from' => 'legacy', + ], + ], + 'firewalls' => ['main' => ['http_basic' => true]], + ]); + + $container->compile(); + + $hashersMap = $container->getDefinition('security.password_hasher_factory')->getArgument(0); + + $this->assertArrayHasKey('App\User', $hashersMap); + $this->assertEquals($hashersMap['App\User'], [ + 'instance' => new Reference('App\Security\CustomHasher'), + 'migrate_from' => ['legacy'], + ]); + } + protected function getRawContainer() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Component/PasswordHasher/Hasher/PasswordHasherFactory.php b/src/Symfony/Component/PasswordHasher/Hasher/PasswordHasherFactory.php index 88ef53a926a85..7c0391714ac4d 100644 --- a/src/Symfony/Component/PasswordHasher/Hasher/PasswordHasherFactory.php +++ b/src/Symfony/Component/PasswordHasher/Hasher/PasswordHasherFactory.php @@ -70,6 +70,14 @@ public function getPasswordHasher(string|PasswordAuthenticatedUserInterface|Pass */ private function createHasher(array $config, bool $isExtra = false): PasswordHasherInterface { + if (isset($config['instance'])) { + if (!isset($config['migrate_from'])) { + return $config['instance']; + } + + $config = $this->getMigratingPasswordConfig($config); + } + if (isset($config['algorithm'])) { $rawConfig = $config; $config = $this->getHasherConfigFromAlgorithm($config); @@ -126,24 +134,8 @@ private function getHasherConfigFromAlgorithm(array $config): array ]; } - if ($frompasswordHashers = ($config['migrate_from'] ?? false)) { - unset($config['migrate_from']); - $hasherChain = [$this->createHasher($config, true)]; - - foreach ($frompasswordHashers as $name) { - if ($hasher = $this->passwordHashers[$name] ?? false) { - $hasher = $hasher instanceof PasswordHasherInterface ? $hasher : $this->createHasher($hasher, true); - } else { - $hasher = $this->createHasher(['algorithm' => $name], true); - } - - $hasherChain[] = $hasher; - } - - return [ - 'class' => MigratingPasswordHasher::class, - 'arguments' => $hasherChain, - ]; + if ($config['migrate_from'] ?? false) { + return $this->getMigratingPasswordConfig($config); } switch ($config['algorithm']) { @@ -223,4 +215,26 @@ private function getHasherConfigFromAlgorithm(array $config): array ], ]; } + + private function getMigratingPasswordConfig(array $config): array + { + $frompasswordHashers = $config['migrate_from']; + unset($config['migrate_from']); + $hasherChain = [$this->createHasher($config, true)]; + + foreach ($frompasswordHashers as $name) { + if ($hasher = $this->passwordHashers[$name] ?? false) { + $hasher = $hasher instanceof PasswordHasherInterface ? $hasher : $this->createHasher($hasher, true); + } else { + $hasher = $this->createHasher(['algorithm' => $name], true); + } + + $hasherChain[] = $hasher; + } + + return [ + 'class' => MigratingPasswordHasher::class, + 'arguments' => $hasherChain, + ]; + } } diff --git a/src/Symfony/Component/PasswordHasher/Tests/Hasher/PasswordHasherFactoryTest.php b/src/Symfony/Component/PasswordHasher/Tests/Hasher/PasswordHasherFactoryTest.php index f1851cc588058..81268f72d5f9c 100644 --- a/src/Symfony/Component/PasswordHasher/Tests/Hasher/PasswordHasherFactoryTest.php +++ b/src/Symfony/Component/PasswordHasher/Tests/Hasher/PasswordHasherFactoryTest.php @@ -48,6 +48,18 @@ public function testGetHasherWithService() $this->assertEquals($expectedHasher->hash('foo', ''), $hasher->hash('foo', '')); } + public function testGetHasherWithInstance() + { + $factory = new PasswordHasherFactory([ + PasswordAuthenticatedUserInterface::class => ['instance' => new MessageDigestPasswordHasher('sha1')], + ]); + + $hasher = $factory->getPasswordHasher($this->createMock(PasswordAuthenticatedUserInterface::class)); + $expectedHasher = new MessageDigestPasswordHasher('sha1'); + $this->assertEquals($expectedHasher->hash('foo', ''), $hasher->hash('foo', '')); + } + + public function testGetHasherWithClassName() { $factory = new PasswordHasherFactory([ @@ -162,6 +174,28 @@ public function testMigrateFrom() $this->assertStringStartsWith(\SODIUM_CRYPTO_PWHASH_STRPREFIX, $hasher->hash('foo', null)); } + public function testMigrateFromWithCustomInstance() + { + if (!SodiumPasswordHasher::isSupported()) { + $this->markTestSkipped('Sodium is not available'); + } + + $sodium = new SodiumPasswordHasher(); + + $factory = new PasswordHasherFactory([ + 'digest_hasher' => $digest = new MessageDigestPasswordHasher('sha256'), + SomeUser::class => ['instance' => $sodium, 'migrate_from' => ['bcrypt', 'digest_hasher']], + ]); + + $hasher = $factory->getPasswordHasher(SomeUser::class); + $this->assertInstanceOf(MigratingPasswordHasher::class, $hasher); + + $this->assertTrue($hasher->verify((new SodiumPasswordHasher())->hash('foo', null), 'foo', null)); + $this->assertTrue($hasher->verify((new NativePasswordHasher(null, null, null, \PASSWORD_BCRYPT))->hash('foo', null), 'foo', null)); + $this->assertTrue($hasher->verify($digest->hash('foo', null), 'foo', null)); + $this->assertStringStartsWith(\SODIUM_CRYPTO_PWHASH_STRPREFIX, $hasher->hash('foo', null)); + } + public function testDefaultMigratingHashers() { $this->assertInstanceOf(
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: