<?php 
 
/* 
 * This file is part of the Symfony package. 
 * 
 * (c) Fabien Potencier <fabien@symfony.com> 
 * 
 * For the full copyright and license information, please view the LICENSE 
 * file that was distributed with this source code. 
 */ 
 
namespace Symfony\Component\Security\Core\Encoder; 
 
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherAwareInterface; 
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactory; 
use Symfony\Component\PasswordHasher\LegacyPasswordHasherInterface; 
use Symfony\Component\PasswordHasher\PasswordHasherInterface; 
use Symfony\Component\Security\Core\Exception\LogicException; 
 
trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use "%s" instead.', EncoderFactory::class, PasswordHasherFactory::class); 
 
/** 
 * A generic encoder factory implementation. 
 * 
 * @author Johannes M. Schmitt <schmittjoh@gmail.com> 
 * 
 * @deprecated since Symfony 5.3, use {@link PasswordHasherFactory} instead 
 */ 
class EncoderFactory implements EncoderFactoryInterface 
{ 
    private $encoders; 
 
    public function __construct(array $encoders) 
    { 
        $this->encoders = $encoders; 
    } 
 
    /** 
     * {@inheritdoc} 
     */ 
    public function getEncoder($user) 
    { 
        $encoderKey = null; 
 
        if (($user instanceof PasswordHasherAwareInterface && null !== $encoderName = $user->getPasswordHasherName()) || ($user instanceof EncoderAwareInterface && null !== $encoderName = $user->getEncoderName())) { 
            if (!\array_key_exists($encoderName, $this->encoders)) { 
                throw new \RuntimeException(sprintf('The encoder "%s" was not configured.', $encoderName)); 
            } 
 
            $encoderKey = $encoderName; 
        } else { 
            foreach ($this->encoders as $class => $encoder) { 
                if ((\is_object($user) && $user instanceof $class) || (!\is_object($user) && (is_subclass_of($user, $class) || $user == $class))) { 
                    $encoderKey = $class; 
                    break; 
                } 
            } 
        } 
 
        if (null === $encoderKey) { 
            throw new \RuntimeException(sprintf('No encoder has been configured for account "%s".', \is_object($user) ? get_debug_type($user) : $user)); 
        } 
 
        if (!$this->encoders[$encoderKey] instanceof PasswordEncoderInterface) { 
            if ($this->encoders[$encoderKey] instanceof LegacyPasswordHasherInterface) { 
                $this->encoders[$encoderKey] = new LegacyPasswordHasherEncoder($this->encoders[$encoderKey]); 
            } elseif ($this->encoders[$encoderKey] instanceof PasswordHasherInterface) { 
                $this->encoders[$encoderKey] = new PasswordHasherEncoder($this->encoders[$encoderKey]); 
            } else { 
                $this->encoders[$encoderKey] = $this->createEncoder($this->encoders[$encoderKey]); 
            } 
        } 
 
        return $this->encoders[$encoderKey]; 
    } 
 
    /** 
     * Creates the actual encoder instance. 
     * 
     * @throws \InvalidArgumentException 
     */ 
    private function createEncoder(array $config, bool $isExtra = false): PasswordEncoderInterface 
    { 
        if (isset($config['algorithm'])) { 
            $rawConfig = $config; 
            $config = $this->getEncoderConfigFromAlgorithm($config); 
        } 
        if (!isset($config['class'])) { 
            throw new \InvalidArgumentException('"class" must be set in '.json_encode($config)); 
        } 
        if (!isset($config['arguments'])) { 
            throw new \InvalidArgumentException('"arguments" must be set in '.json_encode($config)); 
        } 
 
        $encoder = new $config['class'](...$config['arguments']); 
 
        if ($isExtra || !\in_array($config['class'], [NativePasswordEncoder::class, SodiumPasswordEncoder::class], true)) { 
            return $encoder; 
        } 
 
        if ($rawConfig ?? null) { 
            $extraEncoders = array_map(function (string $algo) use ($rawConfig): PasswordEncoderInterface { 
                $rawConfig['algorithm'] = $algo; 
 
                return $this->createEncoder($rawConfig); 
            }, ['pbkdf2', $rawConfig['hash_algorithm'] ?? 'sha512']); 
        } else { 
            $extraEncoders = [new Pbkdf2PasswordEncoder(), new MessageDigestPasswordEncoder()]; 
        } 
 
        return new MigratingPasswordEncoder($encoder, ...$extraEncoders); 
    } 
 
    private function getEncoderConfigFromAlgorithm(array $config): array 
    { 
        if ('auto' === $config['algorithm']) { 
            $encoderChain = []; 
            // "plaintext" is not listed as any leaked hashes could then be used to authenticate directly 
            foreach ([SodiumPasswordEncoder::isSupported() ? 'sodium' : 'native', 'pbkdf2', $config['hash_algorithm']] as $algo) { 
                $config['algorithm'] = $algo; 
                $encoderChain[] = $this->createEncoder($config, true); 
            } 
 
            return [ 
                'class' => MigratingPasswordEncoder::class, 
                'arguments' => $encoderChain, 
            ]; 
        } 
 
        if ($fromEncoders = ($config['migrate_from'] ?? false)) { 
            unset($config['migrate_from']); 
            $encoderChain = [$this->createEncoder($config, true)]; 
 
            foreach ($fromEncoders as $name) { 
                if ($encoder = $this->encoders[$name] ?? false) { 
                    $encoder = $encoder instanceof PasswordEncoderInterface ? $encoder : $this->createEncoder($encoder, true); 
                } else { 
                    $encoder = $this->createEncoder(['algorithm' => $name], true); 
                } 
 
                $encoderChain[] = $encoder; 
            } 
 
            return [ 
                'class' => MigratingPasswordEncoder::class, 
                'arguments' => $encoderChain, 
            ]; 
        } 
 
        switch ($config['algorithm']) { 
            case 'plaintext': 
                return [ 
                    'class' => PlaintextPasswordEncoder::class, 
                    'arguments' => [$config['ignore_case']], 
                ]; 
 
            case 'pbkdf2': 
                return [ 
                    'class' => Pbkdf2PasswordEncoder::class, 
                    'arguments' => [ 
                        $config['hash_algorithm'] ?? 'sha512', 
                        $config['encode_as_base64'] ?? true, 
                        $config['iterations'] ?? 1000, 
                        $config['key_length'] ?? 40, 
                    ], 
                ]; 
 
            case 'bcrypt': 
                $config['algorithm'] = 'native'; 
                $config['native_algorithm'] = \PASSWORD_BCRYPT; 
 
                return $this->getEncoderConfigFromAlgorithm($config); 
 
            case 'native': 
                return [ 
                    'class' => NativePasswordEncoder::class, 
                    'arguments' => [ 
                        $config['time_cost'] ?? null, 
                        (($config['memory_cost'] ?? 0) << 10) ?: null, 
                        $config['cost'] ?? null, 
                    ] + (isset($config['native_algorithm']) ? [3 => $config['native_algorithm']] : []), 
                ]; 
 
            case 'sodium': 
                return [ 
                    'class' => SodiumPasswordEncoder::class, 
                    'arguments' => [ 
                        $config['time_cost'] ?? null, 
                        (($config['memory_cost'] ?? 0) << 10) ?: null, 
                    ], 
                ]; 
 
            case 'argon2i': 
                if (SodiumPasswordEncoder::isSupported() && !\defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) { 
                    $config['algorithm'] = 'sodium'; 
                } elseif (\defined('PASSWORD_ARGON2I')) { 
                    $config['algorithm'] = 'native'; 
                    $config['native_algorithm'] = \PASSWORD_ARGON2I; 
                } else { 
                    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", ' : '')); 
                } 
 
                return $this->getEncoderConfigFromAlgorithm($config); 
 
            case 'argon2id': 
                if (($hasSodium = SodiumPasswordEncoder::isSupported()) && \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) { 
                    $config['algorithm'] = 'sodium'; 
                } elseif (\defined('PASSWORD_ARGON2ID')) { 
                    $config['algorithm'] = 'native'; 
                    $config['native_algorithm'] = \PASSWORD_ARGON2ID; 
                } else { 
                    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", ' : '')); 
                } 
 
                return $this->getEncoderConfigFromAlgorithm($config); 
        } 
 
        return [ 
            'class' => MessageDigestPasswordEncoder::class, 
            'arguments' => [ 
                $config['algorithm'], 
                $config['encode_as_base64'] ?? true, 
                $config['iterations'] ?? 5000, 
            ], 
        ]; 
    } 
}