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

Create   A

Complexity

Total Complexity 14

Size/Duplication

Total Lines 234
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 14
eloc 129
c 1
b 0
f 0
dl 0
loc 234
rs 10

5 Methods

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