HashPassword::configure()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 9
c 1
b 0
f 0
dl 0
loc 12
rs 9.9666
cc 1
nc 1
nop 0
1
<?php
2
3
/**
4
 * This file is part of web-stack
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
10
declare(strict_types=1);
11
12
namespace Slick\WebStack\UserInterface\Console\Security;
13
14
use Psr\Container\ContainerExceptionInterface;
15
use Psr\Container\NotFoundExceptionInterface;
16
use Slick\Di\ContainerInterface;
17
use Slick\WebStack\Domain\Security\PasswordHasher\Hasher\Pbkdf2PasswordHasher;
18
use Slick\WebStack\Domain\Security\PasswordHasher\Hasher\PhpPasswordHasher;
19
use Slick\WebStack\Domain\Security\PasswordHasher\Hasher\PlaintextPasswordHasher;
20
use Slick\WebStack\Domain\Security\PasswordHasher\PasswordHasherInterface;
21
use Symfony\Component\Console\Attribute\AsCommand;
22
use Symfony\Component\Console\Command\Command;
23
use Symfony\Component\Console\Input\InputArgument;
24
use Symfony\Component\Console\Input\InputInterface;
25
use Symfony\Component\Console\Input\InputOption;
26
use Symfony\Component\Console\Output\OutputInterface;
27
use Symfony\Component\Console\Style\SymfonyStyle;
28
use Throwable;
29
use UnhandledMatchError;
30
31
/**
32
 * HashPassword
33
 *
34
 * @package Slick\WebStack\Infrastructure\Console\Security
35
 */
36
#[AsCommand(
37
    name: 'security:hash-password',
38
    description: 'Hashes a given password using a one-way cryptographic algorithm',
39
    aliases: ["hash_pwd"]
40
)]
41
final class HashPassword extends Command
42
{
43
44
    public function __construct(private readonly ContainerInterface $container)
45
    {
46
        parent::__construct();
47
    }
48
49
    /**
50
     * @inheritDoc
51
     */
52
    protected function configure(): void
53
    {
54
        $this
55
            ->addArgument('plainPassword', InputArgument::REQUIRED, 'The password you need to hash')
56
            ->addOption(
57
                'type',
58
                't',
59
                InputOption::VALUE_OPTIONAL,
60
                "The algorithm type to use. Possible values 'bcrypt', 'pbkdf2', 'plain' or 'default'",
61
                'default'
62
            )
63
            ->addUsage('security:hash-password -t pbkdf2 "!AnExtremelyStrongPlainP4ssw0rd"')
64
        ;
65
    }
66
67
68
    /**
69
     * @inheritDoc
70
     */
71
    protected function execute(InputInterface $input, OutputInterface $output): int
72
    {
73
        $style = new SymfonyStyle($input, $output);
74
        $inputPassword = $input->getArgument('plainPassword');
75
        try {
76
            $passwordHasher = $this->retrieveHasher($input);
77
            $hash = $passwordHasher->hash($inputPassword);
78
            $this->renderInfo($style, $inputPassword, $hash, $passwordHasher);
79
        } catch (UnhandledMatchError) {
80
            $style->error('Unknown algorithm type');
81
            return Command::INVALID;
82
        } catch (Throwable $e) {
83
            $style->error($e->getMessage());
84
            return Command::FAILURE;
85
        }
86
        return Command::SUCCESS;
87
    }
88
89
    /**
90
     * @throws NotFoundExceptionInterface
91
     * @throws ContainerExceptionInterface
92
     */
93
    private function retrieveHasher(InputInterface $input): PasswordHasherInterface
94
    {
95
        $option = (string) $input->getOption('type');
96
        return match ($option) {
97
            "pbkdf2" => $this->container->get(Pbkdf2PasswordHasher::class),
98
            "plain" => $this->container->get(PlaintextPasswordHasher::class),
99
            "bcrypt" => $this->container->get(PhpPasswordHasher::class),
100
            "default" => $this->container->get(PasswordHasherInterface::class),
101
            default => throw new UnhandledMatchError()
102
        };
103
    }
104
105
    private function renderInfo(
106
        SymfonyStyle $style,
107
        string $inputPassword,
108
        string $hash,
109
        PasswordHasherInterface $passwordHasher
110
    ): void {
111
        $options = '';
112
        foreach ($passwordHasher->info() as $name => $value) {
113
            $options .= sprintf("<info>%s</info>: %s\n", $name, $value);
114
        }
115
        $table = $style->createTable();
116
        $table->setHeaders(['Password', 'Hash', 'Hasher']);
117
        $table->setHeaderTitle("Password hash utility");
118
        $table->addRow([$inputPassword, $hash, trim($options)]);
119
        $table->render();
120
    }
121
}
122