vendor/symfony/security-core/Encoder/EncoderFactory.php line 20

Open in your IDE?
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Security\Core\Encoder;
  11. use Symfony\Component\PasswordHasher\Hasher\PasswordHasherAwareInterface;
  12. use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactory;
  13. use Symfony\Component\PasswordHasher\LegacyPasswordHasherInterface;
  14. use Symfony\Component\PasswordHasher\PasswordHasherInterface;
  15. use Symfony\Component\Security\Core\Exception\LogicException;
  16. trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use "%s" instead.', EncoderFactory::class, PasswordHasherFactory::class);
  17. /**
  18. * A generic encoder factory implementation.
  19. *
  20. * @author Johannes M. Schmitt <schmittjoh@gmail.com>
  21. *
  22. * @deprecated since Symfony 5.3, use {@link PasswordHasherFactory} instead
  23. */
  24. class EncoderFactory implements EncoderFactoryInterface
  25. {
  26. private $encoders;
  27. public function __construct(array $encoders)
  28. {
  29. $this->encoders = $encoders;
  30. }
  31. /**
  32. * {@inheritdoc}
  33. */
  34. public function getEncoder($user)
  35. {
  36. $encoderKey = null;
  37. if (($user instanceof PasswordHasherAwareInterface && null !== $encoderName = $user->getPasswordHasherName()) || ($user instanceof EncoderAwareInterface && null !== $encoderName = $user->getEncoderName())) {
  38. if (!\array_key_exists($encoderName, $this->encoders)) {
  39. throw new \RuntimeException(sprintf('The encoder "%s" was not configured.', $encoderName));
  40. }
  41. $encoderKey = $encoderName;
  42. } else {
  43. foreach ($this->encoders as $class => $encoder) {
  44. if ((\is_object($user) && $user instanceof $class) || (!\is_object($user) && (is_subclass_of($user, $class) || $user == $class))) {
  45. $encoderKey = $class;
  46. break;
  47. }
  48. }
  49. }
  50. if (null === $encoderKey) {
  51. throw new \RuntimeException(sprintf('No encoder has been configured for account "%s".', \is_object($user) ? get_debug_type($user) : $user));
  52. }
  53. if (!$this->encoders[$encoderKey] instanceof PasswordEncoderInterface) {
  54. if ($this->encoders[$encoderKey] instanceof LegacyPasswordHasherInterface) {
  55. $this->encoders[$encoderKey] = new LegacyPasswordHasherEncoder($this->encoders[$encoderKey]);
  56. } elseif ($this->encoders[$encoderKey] instanceof PasswordHasherInterface) {
  57. $this->encoders[$encoderKey] = new PasswordHasherEncoder($this->encoders[$encoderKey]);
  58. } else {
  59. $this->encoders[$encoderKey] = $this->createEncoder($this->encoders[$encoderKey]);
  60. }
  61. }
  62. return $this->encoders[$encoderKey];
  63. }
  64. /**
  65. * Creates the actual encoder instance.
  66. *
  67. * @throws \InvalidArgumentException
  68. */
  69. private function createEncoder(array $config, bool $isExtra = false): PasswordEncoderInterface
  70. {
  71. if (isset($config['algorithm'])) {
  72. $rawConfig = $config;
  73. $config = $this->getEncoderConfigFromAlgorithm($config);
  74. }
  75. if (!isset($config['class'])) {
  76. throw new \InvalidArgumentException('"class" must be set in '.json_encode($config));
  77. }
  78. if (!isset($config['arguments'])) {
  79. throw new \InvalidArgumentException('"arguments" must be set in '.json_encode($config));
  80. }
  81. $encoder = new $config['class'](...$config['arguments']);
  82. if ($isExtra || !\in_array($config['class'], [NativePasswordEncoder::class, SodiumPasswordEncoder::class], true)) {
  83. return $encoder;
  84. }
  85. if ($rawConfig ?? null) {
  86. $extraEncoders = array_map(function (string $algo) use ($rawConfig): PasswordEncoderInterface {
  87. $rawConfig['algorithm'] = $algo;
  88. return $this->createEncoder($rawConfig);
  89. }, ['pbkdf2', $rawConfig['hash_algorithm'] ?? 'sha512']);
  90. } else {
  91. $extraEncoders = [new Pbkdf2PasswordEncoder(), new MessageDigestPasswordEncoder()];
  92. }
  93. return new MigratingPasswordEncoder($encoder, ...$extraEncoders);
  94. }
  95. private function getEncoderConfigFromAlgorithm(array $config): array
  96. {
  97. if ('auto' === $config['algorithm']) {
  98. $encoderChain = [];
  99. // "plaintext" is not listed as any leaked hashes could then be used to authenticate directly
  100. foreach ([SodiumPasswordEncoder::isSupported() ? 'sodium' : 'native', 'pbkdf2', $config['hash_algorithm']] as $algo) {
  101. $config['algorithm'] = $algo;
  102. $encoderChain[] = $this->createEncoder($config, true);
  103. }
  104. return [
  105. 'class' => MigratingPasswordEncoder::class,
  106. 'arguments' => $encoderChain,
  107. ];
  108. }
  109. if ($fromEncoders = ($config['migrate_from'] ?? false)) {
  110. unset($config['migrate_from']);
  111. $encoderChain = [$this->createEncoder($config, true)];
  112. foreach ($fromEncoders as $name) {
  113. if ($encoder = $this->encoders[$name] ?? false) {
  114. $encoder = $encoder instanceof PasswordEncoderInterface ? $encoder : $this->createEncoder($encoder, true);
  115. } else {
  116. $encoder = $this->createEncoder(['algorithm' => $name], true);
  117. }
  118. $encoderChain[] = $encoder;
  119. }
  120. return [
  121. 'class' => MigratingPasswordEncoder::class,
  122. 'arguments' => $encoderChain,
  123. ];
  124. }
  125. switch ($config['algorithm']) {
  126. case 'plaintext':
  127. return [
  128. 'class' => PlaintextPasswordEncoder::class,
  129. 'arguments' => [$config['ignore_case']],
  130. ];
  131. case 'pbkdf2':
  132. return [
  133. 'class' => Pbkdf2PasswordEncoder::class,
  134. 'arguments' => [
  135. $config['hash_algorithm'] ?? 'sha512',
  136. $config['encode_as_base64'] ?? true,
  137. $config['iterations'] ?? 1000,
  138. $config['key_length'] ?? 40,
  139. ],
  140. ];
  141. case 'bcrypt':
  142. $config['algorithm'] = 'native';
  143. $config['native_algorithm'] = \PASSWORD_BCRYPT;
  144. return $this->getEncoderConfigFromAlgorithm($config);
  145. case 'native':
  146. return [
  147. 'class' => NativePasswordEncoder::class,
  148. 'arguments' => [
  149. $config['time_cost'] ?? null,
  150. (($config['memory_cost'] ?? 0) << 10) ?: null,
  151. $config['cost'] ?? null,
  152. ] + (isset($config['native_algorithm']) ? [3 => $config['native_algorithm']] : []),
  153. ];
  154. case 'sodium':
  155. return [
  156. 'class' => SodiumPasswordEncoder::class,
  157. 'arguments' => [
  158. $config['time_cost'] ?? null,
  159. (($config['memory_cost'] ?? 0) << 10) ?: null,
  160. ],
  161. ];
  162. case 'argon2i':
  163. if (SodiumPasswordEncoder::isSupported() && !\defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) {
  164. $config['algorithm'] = 'sodium';
  165. } elseif (\defined('PASSWORD_ARGON2I')) {
  166. $config['algorithm'] = 'native';
  167. $config['native_algorithm'] = \PASSWORD_ARGON2I;
  168. } else {
  169. throw new LogicException(sprintf('Algorithm "argon2i" is not available. Either use %s"auto" or upgrade to PHP 7.2+ instead.', \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13') ? '"argon2id", ' : ''));
  170. }
  171. return $this->getEncoderConfigFromAlgorithm($config);
  172. case 'argon2id':
  173. if (($hasSodium = SodiumPasswordEncoder::isSupported()) && \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) {
  174. $config['algorithm'] = 'sodium';
  175. } elseif (\defined('PASSWORD_ARGON2ID')) {
  176. $config['algorithm'] = 'native';
  177. $config['native_algorithm'] = \PASSWORD_ARGON2ID;
  178. } else {
  179. throw new LogicException(sprintf('Algorithm "argon2id" is not available. Either use %s"auto", upgrade to PHP 7.3+ or use libsodium 1.0.15+ instead.', \defined('PASSWORD_ARGON2I') || $hasSodium ? '"argon2i", ' : ''));
  180. }
  181. return $this->getEncoderConfigFromAlgorithm($config);
  182. }
  183. return [
  184. 'class' => MessageDigestPasswordEncoder::class,
  185. 'arguments' => [
  186. $config['algorithm'],
  187. $config['encode_as_base64'] ?? true,
  188. $config['iterations'] ?? 5000,
  189. ],
  190. ];
  191. }
  192. }