GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

Token   B
last analyzed

Complexity

Total Complexity 47

Size/Duplication

Total Lines 354
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 110
c 1
b 0
f 1
dl 0
loc 354
rs 8.64
wmc 47

8 Methods

Rating   Name   Duplication   Size   Complexity  
A setKey() 0 5 1
A setTimestamp() 0 5 2
A addHeader() 0 5 1
D decode() 0 91 18
A encode() 0 16 2
A __construct() 0 4 2
A setAlgorithm() 0 9 2
D generate() 0 62 19

How to fix   Complexity   

Complex Class

Complex classes like Token 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 Token, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * This file is part of the O2System Framework package.
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 *
8
 * @author         Steeve Andrian Salim
9
 * @copyright      Copyright (c) Steeve Andrian Salim
10
 */
11
12
// ------------------------------------------------------------------------
13
14
namespace O2System\Security\Generators;
15
16
// ------------------------------------------------------------------------
17
18
use O2System\Security\Encoders\Base64;
19
use O2System\Security\Encoders\Json;
20
use O2System\Security\Encryptions\Algorithm;
21
use O2System\Spl\Traits\Collectors\ErrorCollectorTrait;
22
23
/**
24
 * Class Token
25
 *
26
 * Security token generator.
27
 *
28
 * @package O2System\Security\Generators
29
 */
30
class Token
31
{
32
    use ErrorCollectorTrait;
33
34
    /**
35
     * Token::ALPHANUMERIC_STRING
36
     *
37
     * @var int
38
     */
39
    const ALPHANUMERIC_STRING = 0;
40
41
    /**
42
     * Token::ALPHAUPPERCASE_STRING
43
     *
44
     * @var int
45
     */
46
    const ALPHAUPPERCASE_STRING = 1;
47
48
    /**
49
     * Token::ALPHALOWERCASE_STRING
50
     *
51
     * @var int
52
     */
53
    const ALPHALOWERCASE_STRING = 2;
54
55
    /**
56
     * Token::ALPHAHASH_STRING
57
     *
58
     * @var int
59
     */
60
    const ALPHAHASH_STRING = 3;
61
62
    /**
63
     * Token::NUMERIC_STRING
64
     *
65
     * @var int
66
     */
67
    const NUMERIC_STRING = 4;
68
69
    /**
70
     * Token::$key
71
     *
72
     * @var string|null
73
     */
74
    protected $key = null;
75
76
    /**
77
     * Allow the current timestamp to be specified.
78
     * Useful for fixing a value within unit testing.
79
     *
80
     * Will default to PHP time() value if null.
81
     */
82
    protected $timestamp;
83
84
    /**
85
     * Token::$algorithm
86
     *
87
     * @var string
88
     */
89
    protected $algorithm = 'HMAC-SHA256';
90
91
    /**
92
     * Token::$headers
93
     *
94
     * @var array
95
     */
96
    protected $headers = [];
97
98
    // ------------------------------------------------------------------------
99
100
    /**
101
     * Token::__construct
102
     */
103
    public function __construct()
104
    {
105
        if (class_exists('O2System\Framework', false)) {
106
            $this->key = config()->getItem('security')->encryptionKey;
0 ignored issues
show
Bug introduced by
The function config was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

106
            $this->key = /** @scrutinizer ignore-call */ config()->getItem('security')->encryptionKey;
Loading history...
107
        }
108
    }
109
110
    // ------------------------------------------------------------------------
111
112
    /**
113
     * Token::generate
114
     *
115
     * @param int $length Token string length.
116
     * @param int $type   Token string type.
117
     *
118
     * @return string
119
     * @throws \Exception
120
     */
121
    public static function generate($length = 8, $type = self::ALPHANUMERIC_STRING)
122
    {
123
        if ($type !== self::ALPHAHASH_STRING) {
124
            switch ($type) {
125
                default:
126
                case self::ALPHANUMERIC_STRING:
127
                    $codeAlphabet = implode(range('A', 'Z')); // Uppercase Alphabet
128
                    $codeAlphabet .= implode(range('a', 'z')); // Lowercase Alphabet
129
                    $codeAlphabet .= implode(range(0, 9)); // Numeric Alphabet
130
                    break;
131
                case self::ALPHAUPPERCASE_STRING:
132
                    $codeAlphabet = implode(range('A', 'Z')); // Uppercase Alphabet
133
                    break;
134
                case self::ALPHALOWERCASE_STRING:
135
                    $codeAlphabet = implode(range('a', 'z')); // Lowercase Alphabet
136
                    break;
137
                case self::NUMERIC_STRING:
138
                    $codeAlphabet = implode(range(0, 9)); // Numeric Alphabet
139
                    break;
140
            }
141
142
            $token = '';
143
            $max = strlen($codeAlphabet);
144
145
            for ($i = 0; $i < $length; $i++) {
146
                $token .= $codeAlphabet[ random_int(0, $max - 1) ];
147
            }
148
149
            return $token;
150
        }
151
152
        /**
153
         * ALPHABIN2HEX_STRING
154
         */
155
        if (function_exists('random_bytes')) {
156
            $randomData = random_bytes(20);
157
            if ($randomData !== false && strlen($randomData) === 20) {
158
                return bin2hex($randomData);
159
            }
160
        }
161
        if (function_exists('openssl_random_pseudo_bytes')) {
162
            $randomData = openssl_random_pseudo_bytes(20);
163
            if ($randomData !== false && strlen($randomData) === 20) {
164
                return bin2hex($randomData);
165
            }
166
        }
167
        if (function_exists('mcrypt_create_iv')) {
168
            $randomData = mcrypt_create_iv(20, MCRYPT_DEV_URANDOM);
0 ignored issues
show
Deprecated Code introduced by
The function mcrypt_create_iv() has been deprecated: 7.1 ( Ignorable by Annotation )

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

168
            $randomData = /** @scrutinizer ignore-deprecated */ mcrypt_create_iv(20, MCRYPT_DEV_URANDOM);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
169
            if ($randomData !== false && strlen($randomData) === 20) {
170
                return bin2hex($randomData);
171
            }
172
        }
173
        if (@file_exists('/dev/urandom')) { // Get 100 bytes of random data
174
            $randomData = file_get_contents('/dev/urandom', false, null, 0, 20);
175
            if ($randomData !== false && strlen($randomData) === 20) {
176
                return bin2hex($randomData);
177
            }
178
        }
179
        // Last resort which you probably should just get rid of:
180
        $randomData = mt_rand() . mt_rand() . mt_rand() . mt_rand() . microtime(true) . uniqid(mt_rand(), true);
181
182
        return substr(hash('sha512', $randomData), 0, $length);
183
    }
184
185
    // ------------------------------------------------------------------------
186
187
    /**
188
     * Token::setKey
189
     *
190
     * @param string $key
191
     *
192
     * @return static
193
     */
194
    public function setKey($key)
195
    {
196
        $this->key = $key;
197
198
        return $this;
199
    }
200
201
    // ------------------------------------------------------------------------
202
203
    /**
204
     * Token::setAlgorithm
205
     *
206
     * @param string $algorithm
207
     *
208
     * @return static
209
     */
210
    public function setAlgorithm($algorithm)
211
    {
212
        $algorithm = strtoupper($algorithm);
213
214
        if (Algorithm::validate($algorithm)) {
215
            $this->algorithm = $algorithm;
216
        }
217
218
        return $this;
219
    }
220
221
    // ------------------------------------------------------------------------
222
223
    /**
224
     * Token::setTimestamp
225
     *
226
     * @param int|string $timestamp
227
     *
228
     * @return static
229
     */
230
    public function setTimestamp($timestamp)
231
    {
232
        $this->timestamp = is_numeric($timestamp) ? $timestamp : strtotime($timestamp);
233
234
        return $this;
235
    }
236
237
    // ------------------------------------------------------------------------
238
239
    /**
240
     * Token::encode
241
     *
242
     * @param array $payload
243
     * @param null  $key
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $key is correct as it would always require null to be passed?
Loading history...
244
     *
245
     * @return string
246
     * @throws \O2System\Spl\Exceptions\Logic\DomainException
247
     */
248
    public function encode(array $payload, $key = null)
249
    {
250
        $key = empty($key) ? $this->key : $key;
251
252
        $this->addHeader('algorithm', $this->algorithm);
253
254
        // Create Header Segment
255
        $segments[] = Base64::encode(Json::encode($this->headers));
0 ignored issues
show
Comprehensibility Best Practice introduced by
$segments was never initialized. Although not strictly required by PHP, it is generally a good practice to add $segments = array(); before regardless.
Loading history...
256
257
        // Create Payload Segment
258
        $segments[] = Base64::encode(Json::encode($payload));
259
260
        // Create Signature Segment
261
        $segments[] = Base64::encode(Signature::generate($segments, $key, $this->algorithm));
262
263
        return implode('.', $segments);
264
    }
265
266
    // ------------------------------------------------------------------------
267
268
    /**
269
     * Token::addHeader
270
     *
271
     * @param string $key
272
     * @param mixed  $value
273
     *
274
     * @return static
275
     */
276
    public function addHeader($key, $value)
277
    {
278
        $this->headers[ $key ] = $value;
279
280
        return $this;
281
    }
282
283
    // ------------------------------------------------------------------------
284
285
    /**
286
     * Token::decode
287
     *
288
     * @param string $token
289
     * @param null   $key
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $key is correct as it would always require null to be passed?
Loading history...
290
     *
291
     * @return bool|\O2System\Spl\DataStructures\SplArrayObject|string|null
292
     */
293
    public function decode($token, $key = null)
294
    {
295
        $key = empty($key) ? $this->key : $key;
296
297
        $timestamp = empty($this->timestamp) ? time() : $this->timestamp;
298
299
        $segments = explode('.', $token);
300
        $segments = array_map('trim', $segments);
301
302
        if (count($segments) == 3) {
303
            list($headers, $payload, $signature) = $segments;
304
305
            // Base64 decode headers
306
            if (false === ($headers = Base64::decode($headers))) {
307
                $this->errors[] = 'Invalid header base64 decoding';
308
309
                return false;
310
            }
311
312
            // Json decode headers
313
            if (null === ($headers = Json::decode($headers))) {
314
                $this->errors[] = 'Invalid header json decoding';
315
316
                return false;
317
            }
318
319
            // Validate algorithm header
320
            if (empty($headers->alg)) {
321
                $this->errors[] = 'Invalid algorithm';
322
323
                return false;
324
            } elseif ( ! Algorithm::validate($headers->alg)) {
325
                $this->errors[] = 'Unsupported algorithm';
326
327
                return false;
328
            }
329
330
            // Base64 decode payload
331
            if (false === ($payload = Base64::decode($payload))) {
332
                $this->errors[] = 'Invalid payload base64 decoding';
333
334
                return false;
335
            }
336
337
            // Json decode payload
338
            if (null === ($payload = Json::decode($payload))) {
339
                $this->errors[] = 'Invalid payload json decoding';
340
341
                return false;
342
            }
343
344
            // Base64 decode payload
345
            if (false === ($signature = Base64::decode($signature))) {
346
                $this->errors[] = 'Invalid signature base64 decoding';
347
348
                return false;
349
            }
350
351
            if (Signature::verify($token, $signature, $key, $headers->alg) === false) {
352
                $this->errors[] = 'Invalid signature';
353
354
                return false;
355
            }
356
357
            // Check if the nbf if it is defined. This is the time that the
358
            // token can actually be used. If it's not yet that time, abort.
359
            if (isset($payload->nbf) && $payload->nbf > $timestamp) {
360
                $this->errors[] = 'Cannot handle token prior to ' . date(\DateTime::ISO8601, $payload->nbf);
361
362
                return false;
363
            }
364
365
            // Check that this token has been created before 'now'. This prevents
366
            // using tokens that have been created for later use (and haven't
367
            // correctly used the nbf claim).
368
            if (isset($payload->iat) && $payload->iat > $timestamp) {
369
                $this->errors[] = 'Cannot handle token prior to ' . date(\DateTime::ISO8601, $payload->iat);
370
371
                return false;
372
            }
373
            // Check if this token has expired.
374
            if (isset($payload->exp) && $timestamp >= $payload->exp) {
375
                $this->errors[] = 'Expired token';
376
377
                return false;
378
            }
379
380
            return $payload;
381
        }
382
383
        return false;
384
    }
385
}