1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
namespace AbterPhp\Admin\Console\Commands\ApiClient; |
6
|
|
|
|
7
|
|
|
use AbterPhp\Admin\Domain\Entities\AdminResource; |
8
|
|
|
use AbterPhp\Admin\Domain\Entities\ApiClient; |
9
|
|
|
use AbterPhp\Admin\Orm\AdminResourceRepo; |
10
|
|
|
use AbterPhp\Admin\Orm\ApiClientRepo; |
11
|
|
|
use AbterPhp\Admin\Orm\UserRepo; |
12
|
|
|
use AbterPhp\Framework\Authorization\CacheManager; |
13
|
|
|
use AbterPhp\Framework\Crypto\Crypto; |
14
|
|
|
use Hackzilla\PasswordGenerator\Generator\ComputerPasswordGenerator as PasswordGenerator; |
15
|
|
|
use Opulence\Console\Commands\Command; |
16
|
|
|
use Opulence\Console\Requests\Argument; |
17
|
|
|
use Opulence\Console\Requests\ArgumentTypes; |
18
|
|
|
use Opulence\Console\Requests\Option; |
19
|
|
|
use Opulence\Console\Requests\OptionTypes; |
20
|
|
|
use Opulence\Console\Responses\IResponse; |
21
|
|
|
use Opulence\Console\StatusCodes; |
22
|
|
|
use Opulence\Orm\IUnitOfWork; |
23
|
|
|
use Opulence\Orm\OrmException; |
24
|
|
|
use ZxcvbnPhp\Zxcvbn; |
25
|
|
|
|
26
|
|
|
class Create extends Command |
27
|
|
|
{ |
28
|
|
|
public const COMMAND_NAME = 'apiclient:create'; |
29
|
|
|
public const COMMAND_DESCRIPTION = 'Creates a new API client'; |
30
|
|
|
public const COMMAND_SUCCESS = '<success>New API client is created. ID: <b>%s</b></success>'; |
31
|
|
|
public const COMMAND_DRY_RUN_MESSAGE = '<info>Dry run prevented creating new API client.</info>'; |
32
|
|
|
|
33
|
|
|
public const ARGUMENT_USER = 'user'; |
34
|
|
|
public const ARGUMENT_DESCRIPTION = 'description'; |
35
|
|
|
public const ARGUMENT_RESOURCES = 'resources'; |
36
|
|
|
|
37
|
|
|
public const OPTION_DRY_RUN = 'dry-run'; |
38
|
|
|
public const SHORTENED_DRY_RUN = 'd'; |
39
|
|
|
|
40
|
|
|
public const RESPONSE_SECRET = '<info>Secret generated: <b>%s</b></info>'; |
41
|
|
|
|
42
|
|
|
protected UserRepo $userRepo; |
43
|
|
|
|
44
|
|
|
protected AdminResourceRepo $adminResourceRepo; |
45
|
|
|
|
46
|
|
|
protected ApiClientRepo $apiClientRepo; |
47
|
|
|
|
48
|
|
|
protected PasswordGenerator $passwordGenerator; |
49
|
|
|
|
50
|
|
|
protected Crypto $crypto; |
51
|
|
|
|
52
|
|
|
protected IUnitOfWork $unitOfWork; |
53
|
|
|
|
54
|
|
|
protected CacheManager $cacheManager; |
55
|
|
|
|
56
|
|
|
protected Zxcvbn $zxcvbn; |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* Create constructor. |
60
|
|
|
* |
61
|
|
|
* @param UserRepo $userRepo |
62
|
|
|
* @param AdminResourceRepo $adminResourceRepo |
63
|
|
|
* @param ApiClientRepo $apiClientRepo |
64
|
|
|
* @param PasswordGenerator $passwordGenerator |
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
|
|
|
AdminResourceRepo $adminResourceRepo, |
73
|
|
|
ApiClientRepo $apiClientRepo, |
74
|
|
|
PasswordGenerator $passwordGenerator, |
75
|
|
|
Crypto $crypto, |
76
|
|
|
IUnitOfWork $unitOfWork, |
77
|
|
|
CacheManager $cacheManager, |
78
|
|
|
Zxcvbn $zxcvbn |
79
|
|
|
) { |
80
|
|
|
$this->userRepo = $userRepo; |
81
|
|
|
$this->adminResourceRepo = $adminResourceRepo; |
82
|
|
|
$this->apiClientRepo = $apiClientRepo; |
83
|
|
|
$this->passwordGenerator = $passwordGenerator; |
84
|
|
|
$this->crypto = $crypto; |
85
|
|
|
$this->unitOfWork = $unitOfWork; |
86
|
|
|
$this->cacheManager = $cacheManager; |
87
|
|
|
$this->zxcvbn = $zxcvbn; |
88
|
|
|
|
89
|
|
|
parent::__construct(); |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
/** |
93
|
|
|
* @inheritdoc |
94
|
|
|
*/ |
95
|
|
|
protected function define() |
96
|
|
|
{ |
97
|
|
|
$this->setName(static::COMMAND_NAME) |
98
|
|
|
->setDescription(static::COMMAND_DESCRIPTION) |
99
|
|
|
->addArgument( |
100
|
|
|
new Argument( |
101
|
|
|
static::ARGUMENT_USER, |
102
|
|
|
ArgumentTypes::REQUIRED, |
103
|
|
|
'User Identifier (Email or Username)' |
104
|
|
|
) |
105
|
|
|
) |
106
|
|
|
->addArgument( |
107
|
|
|
new Argument( |
108
|
|
|
static::ARGUMENT_DESCRIPTION, |
109
|
|
|
ArgumentTypes::REQUIRED, |
110
|
|
|
'Description' |
111
|
|
|
) |
112
|
|
|
) |
113
|
|
|
->addArgument( |
114
|
|
|
new Argument( |
115
|
|
|
static::ARGUMENT_RESOURCES, |
116
|
|
|
ArgumentTypes::REQUIRED, |
117
|
|
|
'Resources (Comma separated list)' |
118
|
|
|
) |
119
|
|
|
) |
120
|
|
|
->addOption( |
121
|
|
|
new Option( |
122
|
|
|
static::OPTION_DRY_RUN, |
123
|
|
|
static::SHORTENED_DRY_RUN, |
124
|
|
|
OptionTypes::OPTIONAL_VALUE, |
125
|
|
|
'Dry run (default: 0)', |
126
|
|
|
'0' |
127
|
|
|
) |
128
|
|
|
); |
129
|
|
|
} |
130
|
|
|
|
131
|
|
|
/** |
132
|
|
|
* @inheritdoc |
133
|
|
|
*/ |
134
|
|
|
protected function doExecute(IResponse $response) |
135
|
|
|
{ |
136
|
|
|
$userIdentifier = $this->getArgumentValue(static::ARGUMENT_USER); |
137
|
|
|
|
138
|
|
|
try { |
139
|
|
|
$user = $this->userRepo->find($userIdentifier); |
140
|
|
|
if (!$user) { |
141
|
|
|
throw new \RuntimeException(); |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
$adminResources = $this->getAdminResources($user->getId()); |
145
|
|
|
|
146
|
|
|
$rawSecret = $this->passwordGenerator->generatePassword(); |
147
|
|
|
$preparedPassword = $this->crypto->prepareSecret($rawSecret); |
148
|
|
|
$packedPassword = $this->crypto->hashCrypt($preparedPassword); |
149
|
|
|
|
150
|
|
|
$apiClient = $this->getApiClient($user->getId(), $packedPassword, $adminResources); |
151
|
|
|
|
152
|
|
|
$this->apiClientRepo->add($apiClient); |
153
|
|
|
} catch (\Exception $e) { |
154
|
|
|
if ($e->getPrevious()) { |
155
|
|
|
$response->writeln(sprintf('<error>%s</error>', $e->getPrevious()->getMessage())); |
156
|
|
|
} |
157
|
|
|
$response->writeln(sprintf('<fatal>%s</fatal>', $e->getMessage())); |
158
|
|
|
|
159
|
|
|
return StatusCodes::FATAL; |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
|
163
|
|
|
$dryRun = (bool)$this->getOptionValue(static::OPTION_DRY_RUN); |
164
|
|
|
if ($dryRun) { |
165
|
|
|
$this->unitOfWork->dispose(); |
166
|
|
|
$response->writeln(static::COMMAND_DRY_RUN_MESSAGE); |
167
|
|
|
|
168
|
|
|
return StatusCodes::OK; |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
try { |
172
|
|
|
$this->unitOfWork->commit(); |
173
|
|
|
$this->cacheManager->clearAll(); |
174
|
|
|
} catch (\Exception $e) { |
175
|
|
|
if ($e->getPrevious()) { |
176
|
|
|
$response->writeln(sprintf('<error>%s</error>', $e->getPrevious()->getMessage())); |
177
|
|
|
} |
178
|
|
|
$response->writeln(sprintf('<fatal>%s</fatal>', $e->getMessage())); |
179
|
|
|
|
180
|
|
|
return StatusCodes::FATAL; |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
$response->writeln(sprintf(static::RESPONSE_SECRET, $rawSecret)); |
184
|
|
|
$response->writeln(sprintf(static::COMMAND_SUCCESS, $apiClient->getId())); |
185
|
|
|
|
186
|
|
|
return StatusCodes::OK; |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
/** |
190
|
|
|
* @param string $userId |
191
|
|
|
* @param string $packedPassword |
192
|
|
|
* @param AdminResource[] $adminResources |
193
|
|
|
* |
194
|
|
|
* @return ApiClient |
195
|
|
|
* @throws \RuntimeException |
196
|
|
|
*/ |
197
|
|
|
protected function getApiClient(string $userId, string $packedPassword, array $adminResources): ApiClient |
198
|
|
|
{ |
199
|
|
|
$description = $this->getArgumentValue(static::ARGUMENT_DESCRIPTION); |
200
|
|
|
|
201
|
|
|
return new ApiClient( |
202
|
|
|
'', |
203
|
|
|
$userId, |
204
|
|
|
$description, |
205
|
|
|
$packedPassword, |
206
|
|
|
$adminResources |
207
|
|
|
); |
208
|
|
|
} |
209
|
|
|
|
210
|
|
|
/** |
211
|
|
|
* @param string $userId |
212
|
|
|
* |
213
|
|
|
* @return AdminResource[] |
214
|
|
|
* @throws OrmException |
215
|
|
|
*/ |
216
|
|
|
protected function getAdminResources(string $userId): array |
217
|
|
|
{ |
218
|
|
|
$resources = explode(',', $this->getArgumentValue(static::ARGUMENT_RESOURCES)); |
219
|
|
|
if (!$resources) { |
|
|
|
|
220
|
|
|
return []; |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
$adminResources = []; |
224
|
|
|
foreach ($this->adminResourceRepo->getByUserId($userId) as $adminResource) { |
225
|
|
|
if (!in_array($adminResource->getIdentifier(), $resources, true)) { |
226
|
|
|
continue; |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
$adminResources[] = $adminResource; |
230
|
|
|
} |
231
|
|
|
|
232
|
|
|
if (count($resources) != count($adminResources)) { |
233
|
|
|
throw new \RuntimeException('User does not have all requested resources'); |
234
|
|
|
} |
235
|
|
|
|
236
|
|
|
return $adminResources; |
237
|
|
|
} |
238
|
|
|
} |
239
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.