Passed
Push — master ( 3867af...2fbb63 )
by Vince
12:37 queued 11:19
created

userLoad::refreshTokenGenerate()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 31
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

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

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
441
    {
442
        return $this->column;
443
    }
444
}
445