Passed
Push — master ( 2d0f29...158660 )
by Francis
06:26
created

JWT::algorithm()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 4
rs 10
1
<?php
2
defined('BASEPATH') OR exit('No direct script access allowed');
3
4
class JWT
5
{
6
  /**
7
   * [JWT description]
8
   * @var string
9
   */
10
  const JWT = "jwt";
11
12
  // Signing Algorithms.
13
  const NONE  = 'none';
14
  const HS256 = 'HS256';
15
  const HS512 = 'HS512';
16
  const HS384 = 'HS384';
17
18
  // JWT Standard Algs to PHP Algs.
19
  const ALGOS = [
20
    self::HS256 => 'sha256',
21
    self::HS512 => 'sha512',
22
    self::HS384 => 'sha384'
23
  ];
24
25
  /**
26
   * [private Default Signing Secret]
27
   * @var string
28
   */
29
  private $secret = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
30
31
  /**
32
   * [private JWT header array]
33
   * @var array
34
   */
35
  private $header = array();
36
37
  /**
38
   * [private JWT payload array]
39
   * @var string
40
   */
41
  private $payload = array();
42
43
  /**
44
   * [private Allow Unsigned JWT]
45
   * @var bool
46
   */
47
  private $allow_unsigned = false;
48
49
  /**
50
   * [private Set Issued at Time]
51
   * @var bool
52
   */
53
  private $set_iat = true;
54
55
  /**
56
   * [private Auto expire at. Argument for PHP 'strtotime' function.]
57
   * @var string
58
   */
59
  private $auto_expire;
60
61
  /**
62
   * [private Default Signing Algorithm]
63
   * @var string
64
   */
65
  private $algorithm;
66
67
  /**
68
   * [__construct description]
69
   * @date  2020-03-28
70
   * @param [type]     $params [description]
0 ignored issues
show
Documentation Bug introduced by
The doc comment [type] at position 0 could not be parsed: Unknown type name '[' at position 0 in [type].
Loading history...
71
   */
72
  public function __construct(?array $params=null)
73
  {
74
    if ($params != null) $this->init($params);
75
    get_instance()->load->splint("francis94c/ci-jwt", "%base64");
76
  }
77
78
  /**
79
   * [init For setting config variables.]
80
   * @param  array  $config Config Options.
81
   */
82
  public function init(array $config) {
83
    $this->secret = $config["secret"] ?? $this->secret;
84
    $this->allow_unsigned = $config["allow_unsigned"] ?? $this->allow_unsigned;
85
    $this->auto_expire = $config["auto_expire"] ?? $this->auto_expire;
86
    $this->algorithm = $config["algorithm"] ?? $this->algorithm;
87
    $this->set_iat = $config["set_iat"] ?? $this->set_iat;
88
  }
89
90
  /**
91
   * [algorithm description]
92
   * @date   2020-04-06
93
   * @param  string $algorithm [description]
94
   * @return JWT               [description]
95
   */
96
  public function algorithm(string $algorithm):JWT
97
  {
98
    $this->algorithm = $algorithm;
99
    return $this;
100
  }
101
102
  /**
103
   * [header Add an item to the header array.]
104
   * @param  string     $key   Key of the item. e.g "alg", "typ".
105
   * @param  string|int $value Value of the item.
106
   */
107
  public function header(string $key, $value=null)
108
  {
109
    if ($value === null) return $this->header[$key];
110
    $this->header[$key] = $value;
111
    return $this;
112
  }
113
114
  /**
115
   * [headerArray Returns the header array.]
116
   * @return array Header Array.
117
   */
118
  public function headerArray(): array
119
  {
120
    return $this->header;
121
  }
122
123
  /**
124
   * [payload Adds an item/claim with a key to the payload array.]
125
   * @param  string      $key   JWT Claim
126
   * @param  mixed       $value JWT Claim Value.
127
   * @return mixed|none         Value of $key if $value == null, else
0 ignored issues
show
Bug introduced by
The type none was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
128
   *                            returns none[NOTHING].
129
   */
130
  public function payload(string $key, $value=null)
131
  {
132
    if ($value === null) return $this->payload[$key];
133
    $this->payload[$key] = $value;
134
    return $this;
135
  }
136
137
  /**
138
   * [__call Magic method, get or set items in the payload array.]
139
   * @date   2020-04-06
140
   * @param  string     $method Payload field key.
141
   * @param  array      $args   Value.
142
   * @return [type]             [description]
0 ignored issues
show
Documentation Bug introduced by
The doc comment [type] at position 0 could not be parsed: Unknown type name '[' at position 0 in [type].
Loading history...
143
   */
144
  public function __call(string $method, array $args)
145
  {
146
    if (count($args) == 0) return $this->payload[$method];
147
    $this->payload[$method] = $args[0];
148
    return $this;
149
  }
150
151
  /**
152
   * [iss Convinient function for setting the iss claim]
153
   * @param  mixed      $iss Value to set the 'iss' claim to.
154
   * @return mixed|none      Value of the 'iss' claim, if the $iss argument wasn't
155
   *                         supplied. Otherwise, null.
156
   */
157
  public function iss(string $iss=null)
158
  {
159
    if ($iss === null) return $this->payload['iss'];
160
    $this->payload['iss'] = $iss;
161
    return $this;
162
  }
163
164
  /**
165
   * [payloadArray Get the payload array.]
166
   * @return array The payload array.
167
   */
168
  public function payloadArray(): array
169
  {
170
    return $this->payload;
171
  }
172
173
  /**
174
   * [create Start afresh, empty/reset header and ]
175
   * @return [type] [description]
0 ignored issues
show
Documentation Bug introduced by
The doc comment [type] at position 0 could not be parsed: Unknown type name '[' at position 0 in [type].
Loading history...
176
   */
177
  public function create():JWT
178
  {
179
    $this->header = [];
180
    $this->payload = [];
0 ignored issues
show
Documentation Bug introduced by
It seems like array() of type array is incompatible with the declared type string of property $payload.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
181
    return $this;
182
  }
183
184
  /**
185
   * [sign description]
186
   * @param  [type] $secret [description]
0 ignored issues
show
Documentation Bug introduced by
The doc comment [type] at position 0 could not be parsed: Unknown type name '[' at position 0 in [type].
Loading history...
187
   * @return [type]         [description]
0 ignored issues
show
Documentation Bug introduced by
The doc comment [type] at position 0 could not be parsed: Unknown type name '[' at position 0 in [type].
Loading history...
188
   */
189
  public function sign(string $secret=null):?string {
190
    // Checks.
191
    if  (count($this->payload) == 0) return null;
192
    // $key is $secret.
193
    $key = $secret ?? $this->secret;
194
    $this->header["alg"] = $this->header["alg"] ?? ($this->algorithm ?? self::HS512);
195
    $this->header["typ"] = $this->header["typ"] ?? self::JWT;
196
    // Generate Issued At Time.
197
    if ($this->set_iat) $this->payload["iat"] = $this->payload['iat'] ?? time();
198
    // Auto Expire.
199
    if ($this->auto_expire != null && !isset($this->payload['exp'])) $this->payload['exp'] = strtotime($this->auto_expire);
200
    $jwt = base64url_encode(json_encode($this->header));
201
    if ($jwt === false) return null;
202
    if ($jwt != "") $jwt .= ".";
203
    $payload = base64url_encode(json_encode($this->payload));
204
    $jwt .= $payload;
205
    if ($key != "") return $this->sign_token($jwt, $key, $this->header["alg"]);
206
    return $jwt . ".";
207
  }
208
209
  /**
210
   * [token description]
211
   * @return string [description]
212
   */
213
  public function token():?string
214
  {
215
    // Checks.
216
    if  (count($this->payload) == 0) return null;
217
    // Begin.
218
    $this->header["alg"] = self::NONE;
219
    if ($this->set_iat) $this->payload["iat"] = $this->payload["iat"] ?? time();
220
    if ($this->auto_expire != null) $this->payload["exp"] = strtotime($this->auto_expire);
221
    return base64url_encode(json_encode($this->header)) . "." . base64url_encode(json_encode($this->payload)) . ".";
222
  }
223
224
  /**
225
   * [verify description]
226
   * @param  string $jwt    [description]
227
   * @param  string $secret [description]
228
   * @return bool           [description]
229
   */
230
  public function verify(string $jwt, string $secret=null):bool {
231
    if (substr_count($jwt, ".") != 2) return false; // Invalid JWT.
232
    $key = $secret ?? $this->secret;
233
    $parts = explode(".", $jwt);
234
    $header = json_decode(base64url_decode($parts[0]) ,true);
235
    if ($header == null) return false;
236
    $alg = $this->algorithm ?? $header["alg"] ?? self::HS256;
237
    $payload = json_decode(base64url_decode($parts[1]) ,true);
238
    if ($payload == null) return false;
239
    if ($parts[2] == "") {
240
      return $this->allow_unsigned;
241
    }
242
    return $this->hashmac($alg, $parts[0] . "." . $parts[1], $parts[2], $key);
243
  }
244
245
  /**
246
   * [expire Sets expiry date of JWT. This basically assigns the return value of
247
   *         PHP's 'strtotime()' function to the 'exp' field of the payload,
248
   *         passing it the $when argument.
249
   *         see https://www.php.net/manual/en/function.strtotime.php]
250
   * @param string $when Future time e.g +1 Week, +1 week 2 days 4 hours 2 seconds.
251
   */
252
  public function expire(string $when):void
253
  {
254
    $this->payload["exp"] = strtotime($when);
255
  }
256
257
  /**
258
   * [decode description]
259
   * @param  string  $jwt [description]
260
   * @return boolean      [description]
261
   */
262
  public function decode(string $jwt):bool {
263
    $parts = explode(".", $jwt);
264
    $header = json_decode(base64url_decode($parts[0]), true);
265
    if ($header === false) return false;
266
    $payload = json_decode(base64url_decode($parts[1]), true);
267
    if ($payload === false) return false;
268
    $this->header = $header;
269
    $this->payload = $payload;
270
    return true;
271
  }
272
  /**
273
   * [expired description]
274
   * @param  string $jwt [description]
275
   * @return bool        [description]
276
   */
277
  public function expired(string $jwt=null):bool {
278
    $exp = $jwt == null ? ($this->payload["exp"] ?? time() + 4) : $this->get_expired($jwt);
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $jwt of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
279
    return time() >= $exp;
280
  }
281
282
  /**
283
   * [hashmac description]
284
   * @param  string $alg       [description]
285
   * @param  string $data      [description]
286
   * @param  string $signature [description]
287
   * @param  string $secret    [description]
288
   * @return bool              [description]
289
   */
290
  private function hashmac(string $alg, string $data, string $signature, string $secret):bool {
291
    return $signature === hash_hmac(self::ALGOS[$alg], $data, $secret);
292
  }
293
294
  /**
295
   * [get_expired description]
296
   * @param  string $jwt [description]
297
   * @return int         [description]
298
   */
299
  private function get_expired(string $jwt):int
300
  {
301
    $parts = explode(".", $jwt);
302
    return json_decode(base64url_decode($parts[1]) ,true)["exp"] ?? time() + 4;
303
  }
304
305
  /**
306
   * [sign_token Sign JWT]
307
   * @param  string $token base64 url encoded header and payload token pair.
308
   * @param  string $key   The scecret used to sign the token.
309
   * @param  string $alg   The algorithm used to sign the token.
310
   * @return string        Complete JWT.
311
   */
312
  private function sign_token(string $token, string $key, string $alg):string
313
  {
314
    if ($alg == self::NONE) return $token . ".";
315
    $token = rtrim($token, ".");
316
    $signature = hash_hmac(self::ALGOS[$alg], $token, $key);
317
    return $token . "." . $signature;
318
  }
319
}
320