diff --git a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md
index b53fa3bf55dfe..5a18e1f7b05ee 100644
--- a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md
+++ b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md
@@ -4,6 +4,7 @@ CHANGELOG
4.4.0
-----
+ * Added `migrate_from` option to encoders configuration.
* Added new `argon2id` encoder, undeprecated the `bcrypt` and `argon2i` ones (using `auto` is still recommended by default.)
* Deprecated the usage of "query_string" without a "search_dn" and a "search_password" config key in Ldap factories.
* Marked the `SecurityDataCollector` class as `@final`.
diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php
index 20c6a25984a37..58ea0f3fe4778 100644
--- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php
+++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php
@@ -394,6 +394,10 @@ private function addEncodersSection(ArrayNodeDefinition $rootNode)
->beforeNormalization()->ifString()->then(function ($v) { return ['algorithm' => $v]; })->end()
->children()
->scalarNode('algorithm')->cannotBeEmpty()->end()
+ ->arrayNode('migrate_from')
+ ->prototype('scalar')->end()
+ ->beforeNormalization()->castToArray()->end()
+ ->end()
->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()
->scalarNode('key_length')->defaultValue(40)->end()
->booleanNode('ignore_case')->defaultFalse()->end()
diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php
index 39cf8986b2218..480768a6d07fc 100644
--- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php
+++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php
@@ -512,6 +512,10 @@ private function createEncoder(array $config)
return new Reference($config['id']);
}
+ if ($config['migrate_from'] ?? false) {
+ return $config;
+ }
+
// plaintext encoder
if ('plaintext' === $config['algorithm']) {
$arguments = [$config['ignore_case']];
diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php
index 66a6c6842ab96..8b1ce20bd57a7 100644
--- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php
+++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php
@@ -287,6 +287,7 @@ public function testEncoders()
'memory_cost' => null,
'time_cost' => null,
'threads' => null,
+ 'migrate_from' => [],
],
'JMS\FooBundle\Entity\User3' => [
'algorithm' => 'md5',
@@ -299,6 +300,7 @@ public function testEncoders()
'memory_cost' => null,
'time_cost' => null,
'threads' => null,
+ 'migrate_from' => [],
],
'JMS\FooBundle\Entity\User4' => new Reference('security.encoder.foo'),
'JMS\FooBundle\Entity\User5' => [
@@ -320,6 +322,7 @@ public function testEncoders()
'memory_cost' => null,
'time_cost' => null,
'threads' => null,
+ 'migrate_from' => [],
],
]], $container->getDefinition('security.encoder_factory.generic')->getArguments());
}
@@ -348,6 +351,7 @@ public function testEncodersWithLibsodium()
'memory_cost' => null,
'time_cost' => null,
'threads' => null,
+ 'migrate_from' => [],
],
'JMS\FooBundle\Entity\User3' => [
'algorithm' => 'md5',
@@ -360,6 +364,7 @@ public function testEncodersWithLibsodium()
'memory_cost' => null,
'time_cost' => null,
'threads' => null,
+ 'migrate_from' => [],
],
'JMS\FooBundle\Entity\User4' => new Reference('security.encoder.foo'),
'JMS\FooBundle\Entity\User5' => [
@@ -401,6 +406,7 @@ public function testEncodersWithArgon2i()
'memory_cost' => null,
'time_cost' => null,
'threads' => null,
+ 'migrate_from' => [],
],
'JMS\FooBundle\Entity\User3' => [
'algorithm' => 'md5',
@@ -413,6 +419,7 @@ public function testEncodersWithArgon2i()
'memory_cost' => null,
'time_cost' => null,
'threads' => null,
+ 'migrate_from' => [],
],
'JMS\FooBundle\Entity\User4' => new Reference('security.encoder.foo'),
'JMS\FooBundle\Entity\User5' => [
@@ -430,9 +437,74 @@ public function testEncodersWithArgon2i()
]], $container->getDefinition('security.encoder_factory.generic')->getArguments());
}
+ public function testMigratingEncoder()
+ {
+ if (!($sodium = SodiumPasswordEncoder::isSupported() && !\defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) && !\defined('PASSWORD_ARGON2I')) {
+ $this->markTestSkipped('Argon2i algorithm is not supported.');
+ }
+
+ $container = $this->getContainer('migrating_encoder');
+
+ $this->assertEquals([[
+ 'JMS\FooBundle\Entity\User1' => [
+ 'class' => 'Symfony\Component\Security\Core\Encoder\PlaintextPasswordEncoder',
+ 'arguments' => [false],
+ ],
+ 'JMS\FooBundle\Entity\User2' => [
+ 'algorithm' => 'sha1',
+ 'encode_as_base64' => false,
+ 'iterations' => 5,
+ 'hash_algorithm' => 'sha512',
+ 'key_length' => 40,
+ 'ignore_case' => false,
+ 'cost' => null,
+ 'memory_cost' => null,
+ 'time_cost' => null,
+ 'threads' => null,
+ 'migrate_from' => [],
+ ],
+ 'JMS\FooBundle\Entity\User3' => [
+ 'algorithm' => 'md5',
+ 'hash_algorithm' => 'sha512',
+ 'key_length' => 40,
+ 'ignore_case' => false,
+ 'encode_as_base64' => true,
+ 'iterations' => 5000,
+ 'cost' => null,
+ 'memory_cost' => null,
+ 'time_cost' => null,
+ 'threads' => null,
+ 'migrate_from' => [],
+ ],
+ 'JMS\FooBundle\Entity\User4' => new Reference('security.encoder.foo'),
+ 'JMS\FooBundle\Entity\User5' => [
+ 'class' => 'Symfony\Component\Security\Core\Encoder\Pbkdf2PasswordEncoder',
+ 'arguments' => ['sha1', false, 5, 30],
+ ],
+ 'JMS\FooBundle\Entity\User6' => [
+ 'class' => 'Symfony\Component\Security\Core\Encoder\NativePasswordEncoder',
+ 'arguments' => [8, 102400, 15],
+ ],
+ 'JMS\FooBundle\Entity\User7' => [
+ 'algorithm' => 'argon2i',
+ 'hash_algorithm' => 'sha512',
+ 'key_length' => 40,
+ 'ignore_case' => false,
+ 'encode_as_base64' => true,
+ 'iterations' => 5000,
+ 'cost' => null,
+ 'memory_cost' => 256,
+ 'time_cost' => 1,
+ 'threads' => null,
+ 'migrate_from' => ['bcrypt'],
+ ],
+ ]], $container->getDefinition('security.encoder_factory.generic')->getArguments());
+ }
+
public function testEncodersWithBCrypt()
{
$container = $this->getContainer('bcrypt_encoder');
+
$this->assertEquals([[
'JMS\FooBundle\Entity\User1' => [
'class' => 'Symfony\Component\Security\Core\Encoder\PlaintextPasswordEncoder',
@@ -449,6 +521,7 @@ public function testEncodersWithBCrypt()
'memory_cost' => null,
'time_cost' => null,
'threads' => null,
+ 'migrate_from' => [],
],
'JMS\FooBundle\Entity\User3' => [
'algorithm' => 'md5',
@@ -461,6 +534,7 @@ public function testEncodersWithBCrypt()
'memory_cost' => null,
'time_cost' => null,
'threads' => null,
+ 'migrate_from' => [],
],
'JMS\FooBundle\Entity\User4' => new Reference('security.encoder.foo'),
'JMS\FooBundle\Entity\User5' => [
diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/migrating_encoder.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/migrating_encoder.php
new file mode 100644
index 0000000000000..14c008be9d8d0
--- /dev/null
+++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/migrating_encoder.php
@@ -0,0 +1,14 @@
+load('container1.php', $container);
+
+$container->loadFromExtension('security', [
+ 'encoders' => [
+ 'JMS\FooBundle\Entity\User7' => [
+ 'algorithm' => 'argon2i',
+ 'memory_cost' => 256,
+ 'time_cost' => 1,
+ 'migrate_from' => 'bcrypt',
+ ],
+ ],
+]);
diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/migrating_encoder.xml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/migrating_encoder.xml
new file mode 100644
index 0000000000000..d820118075108
--- /dev/null
+++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/migrating_encoder.xml
@@ -0,0 +1,18 @@
+
+
+
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: