Issues (13)

libraries/JWT.php (6 issues)

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 = [];
36
37
  /**
38
   * [private JWT payload array]
39
   * @var array
40
   */
41
  private $payload = [];
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 Package constructor.]
69
   * @date  2020-03-28
70
   * @param [type] $params Config array.
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):JWT
83
  {
84
    $this->secret = $config["secret"] ?? $this->secret;
85
    $this->allow_unsigned = $config["allow_unsigned"] ?? $this->allow_unsigned;
86
    $this->auto_expire = $config["auto_expire"] ?? $this->auto_expire;
87
    $this->algorithm = $config["algorithm"] ?? $this->algorithm;
88
    $this->set_iat = $config["set_iat"] ?? $this->set_iat;
89
    return $this;
90
  }
91
92
  /**
93
   * [algorithm description]
94
   * @date   2020-04-06
95
   * @param  string $algorithm [description]
96
   * @return JWT               [description]
97
   */
98
  public function algorithm(string $algorithm):JWT
99
  {
100
    $this->algorithm = $algorithm;
101
    return $this;
102
  }
103
104
  /**
105
   * [header Add an item to the header array.]
106
   * @param  string|null     $key   Key of the item. e.g "alg", "typ".
107
   * @param  string|int|null $value Value of the item.
108
   *
109
   * @return mixed           The value of the given key in the header array
110
   *                         if specified, or the header array if no key is
111
   *                         specified. Method chaining is supported.
112
   */
113
  public function header(?string $key=null, $value=null)
114
  {
115
    if (!$key) return $this->header;
116
    if ($value === null) return $this->header[$key];
117
    $this->header[$key] = $value;
118
    return $this;
119
  }
120
121
  /**
122
   * [headerArray Get the header array.]
123
   * @deprecated
124
   * @return array JWT header array.
125
   */
126
  public function headerArray():array
127
  {
128
    return $this->header;
129
  }
130
131
  /**
132
   * [payload Adds an item/claim with a key to the payload array.]
133
   * @param  string      $key   JWT Claim
134
   * @param  mixed       $value JWT Claim Value.
135
   *
136
   * @return mixed|null Value of $key if $value == null, payload array if $key
137
   *                    == null. Method chaining supported.
138
   */
139
  public function payload(?string $key=null, $value=null)
140
  {
141
    if (!$key) return $this->payload;
142
    if ($value === null) return $this->payload[$key];
143
    $this->payload[$key] = $value;
144
    return $this;
145
  }
146
147
  /**
148
   * [__call Magic method, get or set items in the payload array.]
149
   * @date   2020-04-06
150
   * @param  string     $method Payload field key.
151
   * @param  array      $args   Value.
152
   * @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...
153
   */
154
  public function __call(string $method, array $args)
155
  {
156
    if (count($args) == 0) return $this->payload[$method];
157
    $this->payload[$method] = $args[0];
158
    return $this;
159
  }
160
161
  /**
162
   * [iss Convinient function for setting the iss claim]
163
   * @param  mixed      $iss Value to set the 'iss' claim to.
164
   * @return mixed|none      Value of the 'iss' claim, if the $iss argument wasn't
0 ignored issues
show
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...
165
   *                         supplied. Otherwise, null.
166
   */
167
  public function iss(string $iss=null)
168
  {
169
    if ($iss === null) return $this->payload['iss'];
170
    $this->payload['iss'] = $iss;
171
    return $this;
172
  }
173
174
  /**
175
   * [payloadArray Get the payload array.]
176
   * @deprecated
177
   * @return array The payload array.
178
   */
179
  public function payloadArray(): array
180
  {
181
    return $this->payload;
182
  }
183
184
  /**
185
   * [Creat/Start afresh, empty/reset header and payload array.]
186
   * @return JWT Method Chaining.
187
   */
188
  public function create():JWT
189
  {
190
    $this->header = [];
191
    $this->payload = [];
192
    return $this;
193
  }
194
195
  /**
196
   * [sign description]
197
   * @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...
198
   * @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...
199
   */
200
  public function sign(string $secret=null):?string {
201
    // Checks.
202
    if  (count($this->payload) == 0) return null;
203
    // $key is $secret.
204
    $key = $secret ?? $this->secret;
205
    $this->header["alg"] = $this->header["alg"] ?? ($this->algorithm ?? self::HS512);
206
    $this->header["typ"] = $this->header["typ"] ?? self::JWT;
207
    // Generate Issued At Time.
208
    if ($this->set_iat) $this->payload["iat"] = $this->payload['iat'] ?? time();
209
    // Auto Expire.
210
    if ($this->auto_expire != null && !isset($this->payload['exp'])) $this->payload['exp'] = strtotime($this->auto_expire);
211
    $jwt = base64url_encode(json_encode($this->header));
212
    if ($jwt === false) return null;
213
    if ($jwt != "") $jwt .= ".";
214
    $payload = base64url_encode(json_encode($this->payload));
215
    $jwt .= $payload;
216
    if ($key != "") return $this->sign_token($jwt, $key, $this->header["alg"]);
217
    return $jwt . ".";
218
  }
219
220
  /**
221
   * [token description]
222
   * @return string [description]
223
   */
224
  public function token():?string
225
  {
226
    // Checks.
227
    if  (count($this->payload) == 0) return null;
228
    // Begin.
229
    $this->header["alg"] = self::NONE;
230
    if ($this->set_iat) $this->payload["iat"] = $this->payload["iat"] ?? time();
231
    if ($this->auto_expire != null) $this->payload["exp"] = strtotime($this->auto_expire);
232
    return base64url_encode(json_encode($this->header)) . "." . base64url_encode(json_encode($this->payload)) . ".";
233
  }
234
235
  /**
236
   * [verify description]
237
   * @param  string $jwt    [description]
238
   * @param  string $secret [description]
239
   * @return bool           [description]
240
   */
241
  public function verify(string $jwt, string $secret=null):bool {
242
    if (substr_count($jwt, ".") != 2) return false; // Invalid JWT.
243
    $key = $secret ?? $this->secret;
244
    $parts = explode(".", $jwt);
245
    $header = json_decode(base64url_decode($parts[0]) ,true);
246
    if ($header == null) return false;
247
    $alg = $this->algorithm ?? $header["alg"] ?? self::HS256;
248
    $payload = json_decode(base64url_decode($parts[1]) ,true);
249
    if ($payload == null) return false;
250
    if ($parts[2] == "") {
251
      return $this->allow_unsigned;
252
    }
253
    return $this->hashmac($alg, $parts[0] . "." . $parts[1], $parts[2], $key);
254
  }
255
256
  /**
257
   * [expire Sets expiry date of JWT. This basically assigns the return value of
258
   *         PHP's 'strtotime()' function to the 'exp' field of the payload,
259
   *         passing it the $when argument.
260
   *         see https://www.php.net/manual/en/function.strtotime.php]
261
   * @param string $when Future time e.g +1 Week, +1 week 2 days 4 hours 2 seconds.
262
   */
263
  public function expire(string $when):void
264
  {
265
    $this->payload["exp"] = strtotime($when);
266
  }
267
268
  /**
269
   * [decode description]
270
   * @param  string  $jwt [description]
271
   * @return boolean      [description]
272
   */
273
  public function decode(string $jwt):bool {
274
    $parts = explode(".", $jwt);
275
    $header = json_decode(base64url_decode($parts[0]), true);
276
    if ($header === false) return false;
277
    $payload = json_decode(base64url_decode($parts[1]), true);
278
    if ($payload === false) return false;
279
    $this->header = $header;
280
    $this->payload = $payload;
281
    return true;
282
  }
283
  /**
284
   * [expired description]
285
   * @param  string $jwt [description]
286
   * @return bool        [description]
287
   */
288
  public function expired(string $jwt=null):bool {
289
    $exp = $jwt == null ? ($this->payload["exp"] ?? time() + 4) : $this->get_expired($jwt);
0 ignored issues
show
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...
290
    return time() >= $exp;
291
  }
292
293
  /**
294
   * [hashmac description]
295
   * @param  string $alg       [description]
296
   * @param  string $data      [description]
297
   * @param  string $signature [description]
298
   * @param  string $secret    [description]
299
   * @return bool              [description]
300
   */
301
  private function hashmac(string $alg, string $data, string $signature, string $secret):bool {
302
    return $signature === hash_hmac(self::ALGOS[$alg], $data, $secret);
303
  }
304
305
  /**
306
   * [get_expired description]
307
   * @param  string $jwt [description]
308
   * @return int         [description]
309
   */
310
  private function get_expired(string $jwt):int
311
  {
312
    $parts = explode(".", $jwt);
313
    return json_decode(base64url_decode($parts[1]) ,true)["exp"] ?? time() + 4;
314
  }
315
316
  /**
317
   * [sign_token Sign JWT]
318
   * @param  string $token base64 url encoded header and payload token pair.
319
   * @param  string $key   The scecret used to sign the token.
320
   * @param  string $alg   The algorithm used to sign the token.
321
   * @return string        Complete JWT.
322
   */
323
  private function sign_token(string $token, string $key, string $alg):string
324
  {
325
    if ($alg == self::NONE) return $token . ".";
326
    $token = rtrim($token, ".");
327
    $signature = hash_hmac(self::ALGOS[$alg], $token, $key);
328
    return $token . "." . $signature;
329
  }
330
}
331