UpdatePassword::doExecute()   B
last analyzed

Complexity

Conditions 6
Paths 8

Size

Total Lines 46
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 27
c 1
b 0
f 0
dl 0
loc 46
rs 8.8657
cc 6
nc 8
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace AbterPhp\Admin\Console\Commands\User;
6
7
use AbterPhp\Admin\Orm\UserRepo;
8
use AbterPhp\Framework\Authorization\CacheManager;
9
use AbterPhp\Framework\Crypto\Crypto;
10
use Opulence\Console\Commands\Command;
11
use Opulence\Console\Requests\Argument;
12
use Opulence\Console\Requests\ArgumentTypes;
13
use Opulence\Console\Requests\Option;
14
use Opulence\Console\Requests\OptionTypes;
15
use Opulence\Console\Responses\IResponse;
16
use Opulence\Console\StatusCodes;
17
use Opulence\Orm\IUnitOfWork;
18
use ZxcvbnPhp\Zxcvbn;
19
20
class UpdatePassword extends Command
21
{
22
    public const COMMAND_NAME            = 'user:update-password';
23
    public const COMMAND_DESCRIPTION     = 'Update the password of an existing user';
24
    public const COMMAND_SUCCESS         = '<success>User password is updated.</success>';
25
    public const COMMAND_DRY_RUN_MESSAGE = '<info>Dry run prevented updating user password.</info>';
26
    public const COMMAND_UNSAFE_PASSWORD = '<fatal>Password provided is not safe.</fatal>';
27
28
    public const ARGUMENT_IDENTIFIER = 'identifier';
29
    public const ARGUMENT_PASSWORD   = 'password';
30
31
    public const OPTION_DRY_RUN    = 'dry-run';
32
    public const SHORTENED_DRY_RUN = 'd';
33
    public const OPTION_UNSAFE     = 'unsafe';
34
35
    protected UserRepo $userRepo;
36
37
    protected Crypto $crypto;
38
39
    protected IUnitOfWork $unitOfWork;
40
41
    protected CacheManager $cacheManager;
42
43
    protected Zxcvbn $zxcvbn;
44
45
    /**
46
     * CreateUserCommand constructor.
47
     *
48
     * @param UserRepo     $userRepo
49
     * @param Crypto       $crypto
50
     * @param IUnitOfWork  $unitOfWork
51
     * @param CacheManager $cacheManager
52
     * @param Zxcvbn       $zxcvbn
53
     */
54
    public function __construct(
55
        UserRepo $userRepo,
56
        Crypto $crypto,
57
        IUnitOfWork $unitOfWork,
58
        CacheManager $cacheManager,
59
        Zxcvbn $zxcvbn
60
    ) {
61
        $this->userRepo     = $userRepo;
62
        $this->crypto       = $crypto;
63
        $this->unitOfWork   = $unitOfWork;
64
        $this->cacheManager = $cacheManager;
65
        $this->zxcvbn       = $zxcvbn;
66
67
        parent::__construct();
68
    }
69
70
    /**
71
     * @inheritdoc
72
     */
73
    protected function define()
74
    {
75
        $this->setName(static::COMMAND_NAME)
76
            ->setDescription(static::COMMAND_DESCRIPTION)
77
            ->addArgument(
78
                new Argument(
79
                    static::ARGUMENT_IDENTIFIER,
80
                    ArgumentTypes::REQUIRED,
81
                    'Identifier (Email or Username)'
82
                )
83
            )
84
            ->addArgument(new Argument(static::ARGUMENT_PASSWORD, ArgumentTypes::REQUIRED, 'Password'))
85
            ->addOption(
86
                new Option(
87
                    static::OPTION_DRY_RUN,
88
                    static::SHORTENED_DRY_RUN,
89
                    OptionTypes::OPTIONAL_VALUE,
90
                    'Dry run (default: 0)',
91
                    '0'
92
                )
93
            )
94
            ->addOption(
95
                new Option(
96
                    static::OPTION_UNSAFE,
97
                    null,
98
                    OptionTypes::OPTIONAL_VALUE,
99
                    'Unsafe (default: 0)',
100
                    '0'
101
                )
102
            );
103
    }
104
105
    /**
106
     * @inheritdoc
107
     */
108
    protected function doExecute(IResponse $response)
109
    {
110
        $identifier = $this->getArgumentValue(static::ARGUMENT_IDENTIFIER);
111
        $password   = $this->getArgumentValue(static::ARGUMENT_PASSWORD);
112
        $dryRun     = $this->getOptionValue(static::OPTION_DRY_RUN);
113
114
        if (!$this->isSafe()) {
115
            $response->writeln(static::COMMAND_UNSAFE_PASSWORD);
116
117
            return StatusCodes::ERROR;
118
        }
119
120
        $preparedPassword = $this->crypto->prepareSecret($password);
121
        $packedPassword   = $this->crypto->hashCrypt($preparedPassword);
122
123
        $entity = $this->userRepo->find($identifier);
124
        if (!$entity) {
125
            $response->writeln(sprintf('<fatal>User not found</fatal>'));
126
127
            return StatusCodes::ERROR;
128
        }
129
130
        $entity->setPassword($packedPassword);
131
132
        if ($dryRun) {
133
            $this->unitOfWork->dispose();
134
            $response->writeln(static::COMMAND_DRY_RUN_MESSAGE);
135
136
            return StatusCodes::OK;
137
        }
138
139
        try {
140
            $this->unitOfWork->commit();
141
            $this->cacheManager->clearAll();
142
        } catch (\Exception $e) {
143
            if ($e->getPrevious()) {
144
                $response->writeln(sprintf('<error>%s</error>', $e->getPrevious()->getMessage()));
145
            }
146
            $response->writeln(sprintf('<fatal>%s</fatal>', $e->getMessage()));
147
148
            return StatusCodes::FATAL;
149
        }
150
151
        $response->writeln(static::COMMAND_SUCCESS);
152
153
        return StatusCodes::OK;
154
    }
155
156
    /**
157
     * @return bool
158
     */
159
    protected function isSafe(): bool
160
    {
161
        $unsafe = $this->getOptionValue(static::OPTION_UNSAFE);
162
        if ($unsafe) {
163
            return true;
164
        }
165
166
        $password = (string)$this->getArgumentValue(static::ARGUMENT_PASSWORD);
167
        $strength = $this->zxcvbn->passwordStrength($password);
168
169
        return (int)$strength['score'] >= 4;
170
    }
171
}
172