Create::doExecute()   B
last analyzed

Complexity

Conditions 7
Paths 19

Size

Total Lines 49
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 30
c 1
b 0
f 0
dl 0
loc 49
rs 8.5066
cc 7
nc 19
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace AbterPhp\Admin\Console\Commands\User;
6
7
use AbterPhp\Admin\Domain\Entities\User;
8
use AbterPhp\Admin\Orm\UserGroupRepo;
9
use AbterPhp\Admin\Orm\UserLanguageRepo;
10
use AbterPhp\Admin\Orm\UserRepo;
11
use AbterPhp\Framework\Authorization\CacheManager;
12
use AbterPhp\Framework\Crypto\Crypto;
13
use Opulence\Console\Commands\Command;
14
use Opulence\Console\Requests\Argument;
15
use Opulence\Console\Requests\ArgumentTypes;
16
use Opulence\Console\Requests\Option;
17
use Opulence\Console\Requests\OptionTypes;
18
use Opulence\Console\Responses\IResponse;
19
use Opulence\Console\StatusCodes;
20
use Opulence\Orm\IUnitOfWork;
21
use ZxcvbnPhp\Zxcvbn;
22
23
class Create extends Command
24
{
25
    public const COMMAND_NAME            = 'user:create';
26
    public const COMMAND_DESCRIPTION     = 'Creates a new user';
27
    public const COMMAND_SUCCESS         = '<success>New user is created.</success>';
28
    public const COMMAND_DRY_RUN_MESSAGE = '<info>Dry run prevented creating new user.</info>';
29
    public const COMMAND_UNSAFE_PASSWORD = '<fatal>Password provided is not safe.</fatal>';
30
31
    public const ARGUMENT_USERNAME    = 'username';
32
    public const ARGUMENT_EMAIL       = 'email';
33
    public const ARGUMENT_PASSWORD    = 'password';
34
    public const ARGUMENT_USER_GROUPS = 'usergroups';
35
    public const ARGUMENT_USER_LANG   = 'lang';
36
37
    public const OPTION_CAN_LOGIN       = 'can-login';
38
    public const SHORTENED_CAN_LOGIN    = 'l';
39
    public const OPTION_HAS_GRAVATAR    = 'has-gravatar';
40
    public const SHORTENED_HAS_GRAVATAR = 'g';
41
    public const OPTION_DRY_RUN         = 'dry-run';
42
    public const SHORTENED_DRY_RUN      = 'd';
43
    public const OPTION_UNSAFE          = 'unsafe';
44
45
    protected UserRepo $userRepo;
46
47
    protected UserGroupRepo $userGroupRepo;
48
49
    protected UserLanguageRepo $userLanguageRepo;
50
51
    protected Crypto $crypto;
52
53
    protected IUnitOfWork $unitOfWork;
54
55
    protected CacheManager $cacheManager;
56
57
    protected Zxcvbn $zxcvbn;
58
59
    /**
60
     * CreateCommand constructor.
61
     *
62
     * @param UserRepo         $userRepo
63
     * @param UserGroupRepo    $userGroupRepo
64
     * @param UserLanguageRepo $userLanguageRepo
65
     * @param Crypto           $crypto
66
     * @param IUnitOfWork      $unitOfWork
67
     * @param CacheManager     $cacheManager
68
     * @param Zxcvbn           $zxcvbn
69
     */
70
    public function __construct(
71
        UserRepo $userRepo,
72
        UserGroupRepo $userGroupRepo,
73
        UserLanguageRepo $userLanguageRepo,
74
        Crypto $crypto,
75
        IUnitOfWork $unitOfWork,
76
        CacheManager $cacheManager,
77
        Zxcvbn $zxcvbn
78
    ) {
79
        $this->userRepo         = $userRepo;
80
        $this->userGroupRepo    = $userGroupRepo;
81
        $this->userLanguageRepo = $userLanguageRepo;
82
        $this->crypto           = $crypto;
83
        $this->unitOfWork       = $unitOfWork;
84
        $this->cacheManager     = $cacheManager;
85
        $this->zxcvbn           = $zxcvbn;
86
87
        parent::__construct();
88
    }
89
90
    /**
91
     * @inheritdoc
92
     */
93
    protected function define()
94
    {
95
        $this->setName(static::COMMAND_NAME)
96
            ->setDescription(static::COMMAND_DESCRIPTION)
97
            ->addArgument(new Argument(static::ARGUMENT_USERNAME, ArgumentTypes::REQUIRED, 'Username'))
98
            ->addArgument(new Argument(static::ARGUMENT_EMAIL, ArgumentTypes::REQUIRED, 'Email'))
99
            ->addArgument(new Argument(static::ARGUMENT_PASSWORD, ArgumentTypes::REQUIRED, 'Password'))
100
            ->addArgument(
101
                new Argument(
102
                    static::ARGUMENT_USER_GROUPS,
103
                    ArgumentTypes::REQUIRED,
104
                    'User Groups (comma separated list)'
105
                )
106
            )
107
            ->addArgument(new Argument(static::ARGUMENT_USER_LANG, ArgumentTypes::OPTIONAL, 'Language', 'en'))
108
            ->addOption(
109
                new Option(
110
                    static::OPTION_CAN_LOGIN,
111
                    static::SHORTENED_CAN_LOGIN,
112
                    OptionTypes::OPTIONAL_VALUE,
113
                    'Can user log in',
114
                    '1'
115
                )
116
            )
117
            ->addOption(
118
                new Option(
119
                    static::OPTION_HAS_GRAVATAR,
120
                    static::SHORTENED_HAS_GRAVATAR,
121
                    OptionTypes::OPTIONAL_VALUE,
122
                    'Does user have gravatar (https://en.gravatar.com/)',
123
                    '1'
124
                )
125
            )
126
            ->addOption(
127
                new Option(
128
                    static::OPTION_DRY_RUN,
129
                    static::SHORTENED_DRY_RUN,
130
                    OptionTypes::OPTIONAL_VALUE,
131
                    'Dry run (default: 0)',
132
                    '0'
133
                )
134
            )
135
            ->addOption(
136
                new Option(
137
                    static::OPTION_UNSAFE,
138
                    null,
139
                    OptionTypes::OPTIONAL_VALUE,
140
                    'Unsafe (default: 0)',
141
                    '0'
142
                )
143
            );
144
    }
145
146
    /**
147
     * @inheritdoc
148
     */
149
    protected function doExecute(IResponse $response)
150
    {
151
        if (!$this->isSafe()) {
152
            $response->writeln(static::COMMAND_UNSAFE_PASSWORD);
153
154
            return StatusCodes::ERROR;
155
        }
156
157
        try {
158
            $entity = $this->getEntity();
159
160
            $password         = (string)$this->getArgumentValue(static::ARGUMENT_PASSWORD);
161
            $preparedPassword = $this->crypto->prepareSecret($password);
162
            $packedPassword   = $this->crypto->hashCrypt($preparedPassword);
163
164
            $entity->setPassword($packedPassword);
165
            $this->userRepo->add($entity);
166
        } catch (\Exception $e) {
167
            if ($e->getPrevious()) {
168
                $response->writeln(sprintf('<error>%s</error>', $e->getPrevious()->getMessage()));
169
            }
170
            $response->writeln(sprintf('<fatal>%s</fatal>', $e->getMessage()));
171
172
            return StatusCodes::FATAL;
173
        }
174
175
        $dryRun = (bool)$this->getOptionValue(static::OPTION_DRY_RUN);
176
        if ($dryRun) {
177
            $this->unitOfWork->dispose();
178
            $response->writeln(static::COMMAND_DRY_RUN_MESSAGE);
179
180
            return StatusCodes::OK;
181
        }
182
183
        try {
184
            $this->unitOfWork->commit();
185
            $this->cacheManager->clearAll();
186
        } catch (\Exception $e) {
187
            if ($e->getPrevious()) {
188
                $response->writeln(sprintf('<error>%s</error>', $e->getPrevious()->getMessage()));
189
            }
190
            $response->writeln(sprintf('<fatal>%s</fatal>', $e->getMessage()));
191
192
            return StatusCodes::FATAL;
193
        }
194
195
        $response->writeln(static::COMMAND_SUCCESS);
196
197
        return StatusCodes::OK;
198
    }
199
200
    /**
201
     * @return User
202
     * @throws \Opulence\Orm\OrmException
203
     * @throws \RuntimeException
204
     */
205
    protected function getEntity(): User
206
    {
207
        $username      = $this->getArgumentValue(static::ARGUMENT_USERNAME);
208
        $email         = $this->getArgumentValue(static::ARGUMENT_EMAIL);
209
        $ugIdentifiers = $this->getArgumentValue(static::ARGUMENT_USER_GROUPS);
210
        $ulIdentifier  = $this->getArgumentValue(static::ARGUMENT_USER_LANG);
211
        $canLogin      = (bool)$this->getArgumentValue(static::ARGUMENT_USER_LANG);
212
        $hasGravatar   = (bool)$this->getArgumentValue(static::ARGUMENT_USER_LANG);
213
214
        $userGroups = [];
215
        foreach (explode(',', $ugIdentifiers) as $ugIdentifier) {
216
            $ug = $this->userGroupRepo->getByIdentifier($ugIdentifier);
217
            if ($ug !== null) {
218
                $userGroups[] = $ug;
219
            }
220
        }
221
        $userLanguage = $this->userLanguageRepo->getByIdentifier($ulIdentifier);
222
223
        if (!$userLanguage) {
224
            throw new \RuntimeException('Language not found');
225
        }
226
227
        return new User(
228
            '',
229
            $username,
230
            $email,
231
            '',
232
            $canLogin,
233
            $hasGravatar,
234
            $userLanguage,
235
            $userGroups
236
        );
237
    }
238
239
    /**
240
     * @return bool
241
     */
242
    protected function isSafe(): bool
243
    {
244
        $unsafe = $this->getOptionValue(static::OPTION_UNSAFE);
245
        if ($unsafe) {
246
            return true;
247
        }
248
249
        $password = (string)$this->getArgumentValue(static::ARGUMENT_PASSWORD);
250
        $strength = $this->zxcvbn->passwordStrength($password);
251
252
        return (int)$strength['score'] >= 4;
253
    }
254
}
255