Passed
Push — master ( 80fed1...32ef61 )
by Peter
09:46
created

UpdatePassword::doExecute()   B

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