userLoad   C
last analyzed

Complexity

Total Complexity 54

Size/Duplication

Total Lines 433
Duplicated Lines 0 %

Importance

Changes 15
Bugs 0 Features 0
Metric Value
eloc 199
c 15
b 0
f 0
dl 0
loc 433
rs 6.4799
wmc 54

10 Methods

Rating   Name   Duplication   Size   Complexity  
A futureToken() 0 29 6
C getUserJWT() 0 67 12
A refreshTokenGenerate() 0 31 5
A setProperty() 0 3 1
A __construct() 0 26 5
A setOptions() 0 4 1
A refreshJWT() 0 22 2
A tokenExpiresIn() 0 12 2
B setColumn() 0 22 8
C account() 0 104 12

How to fix   Complexity   

Complex Class

Complex classes like userLoad often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use userLoad, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * ==================================
5
 * Responsible PHP API
6
 * ==================================
7
 *
8
 * @link Git https://github.com/vince-scarpa/responsibleAPI.git
9
 *
10
 * @api Responible API
11
 * @package responsible\core\user
12
 *
13
 * @author Vince scarpa <[email protected]>
14
 *
15
 */
16
17
namespace responsible\core\user;
18
19
use responsible\core\auth;
20
use responsible\core\exception;
21
use responsible\core\headers;
22
use responsible\core\keys;
23
use responsible\core\route;
24
use responsible\core\encoder;
25
26
class userLoad extends user
27
{
28
    /**
29
     * [$column Load user by what column]
30
     * @var string
31
     */
32
    private $column;
33
34
    /**
35
     * [$requestRefreshToken Request a new refresh JWT]
36
     * @var boolean
37
     */
38
    private $requestRefreshToken = false;
39
40
    /**
41
     * [$requestRefreshToken Request a new refresh JWT, request from authorization headers]
42
     * @var boolean
43
     */
44
    private $authorizationRefresh = false;
45
46
    /**
47
     * [$requestRefreshToken Get an encoded user token]
48
     * @var boolean
49
     */
50
    private $getToken = false;
51
52
    /**
53
     * [$secret]
54
     * @var string
55
     */
56
    private $secret = '';
57
58
    /**
59
     * [$keys]
60
     */
61
    private $keys;
62
63
    /**
64
     * [$jwt]
65
     * @var object
66
     */
67
    private $jwt;
68
69
    /**
70
     * [$property]
71
     * @var string
72
     */
73
    protected $property;
74
75
    /**
76
     * [$secret request by system to append the users secret from DB]
77
     * @var boolean
78
     */
79
    private $secretAppend = false;
80
81
    public function __construct($property, $options)
82
    {
83
        if (is_null($property) || empty($property)) {
84
            (new exception\errorException())
85
                ->setOptions($this->options)
86
                ->message('No load property was provided!')
87
                ->error('ACCOUNT_ID');
88
        }
89
90
        $loadBy = $this->checkVal($options, 'loadBy', 'account_id');
91
92
        $this->getToken = $this->checkVal($options, 'getJWT');
93
        $this->requestRefreshToken = $this->checkVal($options, 'refreshToken');
94
        $this->authorizationRefresh = $this->checkVal($options, 'authorizationRefresh');
95
96
        $this->keys = new keys\key();
97
        $this->jwt = new auth\jwt();
98
99
        $this->setColumn($loadBy);
100
        $this->setProperty($property);
101
        $this->timeNow();
102
103
        $this->secret = $this->getDefaults()['config']['MASTER_KEY'];
104
105
        if (isset($options['secret']) && $options['secret'] == 'append') {
106
            $this->secretAppend = true;
107
        }
108
    }
109
110
    /**
111
     * [account Get the account]
112
     * @return array
113
     */
114
    public function account()
115
    {
116
        /**
117
         * [Validate the requested account exists]
118
         */
119
        $account = $this->DB()
120
            ->row(
121
                "SELECT
122
                USR.uid,
123
                USR.account_id,
124
                USR.name,
125
                USR.mail,
126
                USR.status,
127
                USR.access,
128
                USR.secret,
129
                USR.refresh_token,
130
                TKN.bucket
131
                FROM responsible_api_users USR
132
                INNER JOIN responsible_token_bucket TKN
133
                    ON USR.account_id = TKN.account_id
134
135
                    WHERE {$this->column} = ?
136
                    AND status = 1
137
            ;",
138
                array(
139
                    $this->property,
140
                ),
141
                \PDO::FETCH_OBJ
142
            );
143
144
        if ($this->secretAppend) {
145
            $this->secret = $account->secret;
146
        }
147
148
        if (!empty($account)) {
149
            $this->setAccountID($account->account_id);
150
151
            if (
152
                isset($this->getOptions()['jwt']['signWith']) &&
153
                !empty($this->getOptions()['jwt']['signWith']) &&
154
                !$this->secretAppend
155
            ) {
156
                $this->secret = $this->getOptions()['jwt']['signWith'];
157
            }
158
159
            if ($this->requestRefreshToken) {
160
                $account->refresh_token = $this->refreshTokenGenerate($account);
161
                $headers = new headers\header();
162
                $headers->setOptions($this->getOptions());
163
                $sentToken = $headers->hasBearerToken();
164
165
                if ($sentToken) {
166
                    /**
167
                     * [$jwt Decode the JWT]
168
                     * @var auth\jwt
169
                     */
170
                    $jwt = new auth\jwt();
171
                    $decoded = $jwt
172
                        ->setOptions($this->getOptions())
173
                        ->token($sentToken)
174
                        ->key('payloadOnly')
175
                        ->decode()
176
                    ;
177
178
                    $leeway = ($this->checkVal($this->options['jwt'], 'leeway'))
179
                        ?: $this->jwt->getLeeway()
180
                    ;
181
                    $absSeconds = ($decoded['exp'] - ($this->timeNow() - $leeway));
182
183
                    if ($absSeconds > 0) {
184
                        $account->JWT = $sentToken;
185
                    }
186
187
                    $account->tokenExpire = [
188
                        'tokenExpire' => [
189
                            'leeway' => $leeway,
190
                            'expiresIn' => $absSeconds,
191
                            'expiresString' => $this->tokenExpiresIn($absSeconds),
192
                        ]
193
                    ];
194
                }
195
196
                $account->refreshToken = [
197
                    'token' => $sentToken,
198
                    'refresh' => $account->refresh_token
199
                ];
200
            }
201
202
            if (isset($account->tokenExpire['tokenExpire']['expiresIn'])) {
203
                $account->refreshToken['expiresIn'] = $account->tokenExpire['tokenExpire']['expiresIn'];
204
            }
205
206
            if ($this->getToken) {
207
                $account->JWT = $this->getUserJWT();
208
                $account->refresh_token = $this->refreshTokenGenerate($account);
209
                $account->refreshToken = ['token' => $account->refresh_token];
210
            }
211
212
            return (array) $account;
213
        }
214
215
        (new exception\errorException())
216
            ->setOptions($this->options)
217
            ->error('UNAUTHORIZED');
218
    }
219
220
    /**
221
     * [refreshToken New way to request refresh token]
222
     * @return string
223
     */
224
    public function refreshTokenGenerate($account)
225
    {
226
        $offset = 86400;
227
        $time = ($this->timeNow() + $offset);
228
229
        if (isset($account->refresh_token) && !empty($account->refresh_token)) {
230
            $raToken = explode('.', $account->refresh_token);
231
            if (!empty($raToken)) {
232
                $raToken = array_values(array_filter($raToken));
233
                $time = ($raToken[0] <= ($this->timeNow() - $offset) ) ? ($this->timeNow() + $offset) : $raToken[0];
234
            }
235
        }
236
237
        $cipher = new encoder\cipher();
238
        $refreshHash = $account->account_id . ':' . $this->secret;
239
        $refreshHash = $cipher->encode($cipher->hash('sha256', $refreshHash, $this->secret));
240
241
        $refreshHash = $time . '.' . $refreshHash;
242
        $account->refreshToken = $refreshHash;
243
244
        $updateProp = [
245
            'where' => [
246
                'account_id' => $account->account_id
247
            ],
248
            'update' => [
249
                'refresh_token' => $refreshHash,
250
            ]
251
        ];
252
        parent::update($updateProp);
253
254
        return $refreshHash;
255
    }
256
257
    /**
258
     * [refreshJWT Get a refresh JWT]
259
     * @return array
260
     */
261
    public function refreshJWT($userPayload)
262
    {
263
        $leeway = ($this->checkVal($this->options['jwt'], 'leeway')) ?: $this->jwt->getLeeway();
264
        $expires = $userPayload['payload']['exp'] + $leeway;
265
266
        $this->options['jwt'] = [
267
            'leeway' => $leeway,
268
            'issuedAt' => $expires,
269
            'expires' => $expires,
270
            'notBeFor' => $expires - 10,
271
        ];
272
273
        $absSeconds = ($userPayload['payload']['exp'] - ($this->timeNow() - $leeway));
274
275
        return [
276
            'tokenExpire' => [
277
                'leeway' => $leeway,
278
                'expiresIn' => $absSeconds,
279
                'expiresDate' => date(\DateTime::ISO8601, ($userPayload['payload']['exp'] + $leeway)),
280
                'expiresString' => $this->tokenExpiresIn($absSeconds),
281
            ],
282
            'refresh' => $this->getUserJWT(),
283
        ];
284
    }
285
286
    /**
287
     * [futureToken Get a future refresh JWT]
288
     * @return array|null
289
     */
290
    public function futureToken()
291
    {
292
        if (!isset($this->secret)) {
293
            (new exception\errorException())
294
                ->setOptions($this->options)
295
                ->message('There was an error trying to retrieve the server master key. Please read the documentation on setting up a configuration file')
296
                ->error('NO_CONTENT');
297
        }
298
299
        $key = $this->secret;
300
        $userPayload = $this->getJWT($key);
301
302
        if (empty($userPayload) || !isset($userPayload['payload'])) {
303
            return;
304
        }
305
306
        /**
307
         * Check unlimited access set
308
         */
309
        $skipExpiry = $this->checkVal($this->options, 'unlimited', true);
310
311
        /**
312
         * Check token expiry
313
         */
314
        if ($this->checkVal($userPayload['payload'], 'exp') && !$skipExpiry) {
315
            return $this->refreshJWT($userPayload);
316
        }
317
318
        return;
319
    }
320
321
    /**
322
     * [tokenExpiresIn Get the token expiry as a string]
323
     * @param  integer $seconds
324
     * @return string
325
     */
326
    private function tokenExpiresIn($seconds)
327
    {
328
        if ($seconds <= 0) {
329
            return 0;
330
        }
331
332
        $minutes = (float) $seconds / 60;
333
        $zero = new \DateTime('@0');
334
        $offset = new \DateTime('@' . $minutes * 60);
335
        $diff = $zero->diff($offset);
336
337
        return $diff->format('%a Days, %h Hours, %i Minutes, %s Seconds');
338
    }
339
340
    /**
341
     * [getUserJWT Get an ecoded user token]
342
     * @return string
343
     */
344
    public function getUserJWT($refresh = false)
345
    {
346
        if (!isset($this->secret)) {
347
            (new exception\errorException())
348
                ->setOptions($this->options)
349
                ->message('There was an error trying to retrieve the server master key. Please read the documentation on setting up a configuration file')
350
                ->error('NO_CONTENT');
351
        }
352
353
        $key = $this->secret;
354
355
        /**
356
         * [$payload Set the default payload]
357
         * @var array
358
         */
359
        $payload = array(
360
            "iss" => (new route\router())->getIssuer(),
361
            "sub" => $this->getAccountID(),
362
            "iat" => $this->timeNow(),
363
            "nbf" => $this->timeNow() + 10,
364
        );
365
366
        /**
367
         * [$jwtOptions JWT options may be set as Responsible option overrides]
368
         * @var array
369
         */
370
        $exp = false;
371
        if (false !== ($jwtOptions = $this->checkVal($this->getOptions(), 'jwt'))) {
372
            if (false !== ($exp = $this->checkVal($jwtOptions, 'expires'))) {
373
                $payload['exp'] = $exp;
374
            }
375
            if (false !== ($iat = $this->checkVal($jwtOptions, 'issuedAt'))) {
376
                $payload['iat'] = $iat;
377
            }
378
            if (false !== ($nbf = $this->checkVal($jwtOptions, 'notBeFor'))) {
379
                if (strtolower($nbf) == 'issuedat' && isset($payload['iat'])) {
380
                    $nbf = $payload['iat'] + 10;
381
                }
382
                $payload['nbf'] = $nbf;
383
            }
384
        }
385
386
        if ($refresh && $exp) {
387
            $refreshPayload = $payload;
388
389
            $offset = $exp - $this->timeNow();
390
            $leeway = ($this->checkVal($this->options['jwt'], 'leeway')) ?: $this->jwt->getLeeway();
391
392
            $refreshPayload['exp'] = $exp + $offset + $leeway;
393
394
            $refreshJWT = $this->refreshJWT([
395
                'payload' => $refreshPayload
396
            ]);
397
398
            if (isset($refreshJWT['refresh'])) {
399
                return $refreshJWT['refresh'];
400
            }
401
        }
402
403
        /**
404
         * Return the encoded JWT
405
         */
406
        return $this->jwt
407
            ->setOptions($this->getOptions())
408
            ->key($key)
409
            ->setPayload($payload)
410
            ->encode($payload)
411
        ;
412
    }
413
414
    /**
415
     * [setOptions Set the Responsible API options]
416
     * @param array $options
417
     */
418
    public function setOptions($options)
419
    {
420
        $this->options = $options;
421
        return $this;
422
    }
423
424
    /**
425
     * [setProperty Set the property we want the Responsible API load an account by]
426
     * @param string $property
427
     */
428
    private function setProperty($property)
429
    {
430
        $this->property = $property;
431
    }
432
433
    /**
434
     * [setColumn Set the column type we want the Responsible API load an account by]
435
     * @param string $column
436
     */
437
    private function setColumn($column)
438
    {
439
        switch ($column) {
440
            case ($column == 'account_id' || strtolower($column == 'accountid')):
0 ignored issues
show
Bug introduced by
$column == 'accountid' of type boolean is incompatible with the type string expected by parameter $string of strtolower(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

440
            case ($column == 'account_id' || strtolower(/** @scrutinizer ignore-type */ $column == 'accountid')):
Loading history...
441
                $this->column = 'BINARY USR.account_id';
442
                break;
443
444
            case ($column == 'username' || $column == 'name'):
445
                $this->column = 'USR.name';
446
                break;
447
448
            case ($column == 'email' || $column == 'mail'):
449
                $this->column = 'USR.mail';
450
                break;
451
452
            case ($column == 'refresh_token'):
453
                $this->column = 'USR.refresh_token';
454
                break;
455
456
            default:
457
                $this->column = '';
458
                break;
459
        }
460
    }
461
}
462