Create::getApiClient()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 7
c 1
b 0
f 0
dl 0
loc 10
rs 10
cc 1
nc 1
nop 3
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) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $resources of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
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