REST::bearer_auth()   B
last analyzed

Complexity

Conditions 8
Paths 19

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 1 Features 0
Metric Value
cc 8
eloc 14
c 3
b 1
f 0
nc 19
nop 2
dl 0
loc 22
rs 8.4444
1
<?php
2
declare(strict_types=1);
3
defined('BASEPATH') OR exit('No direct script access allowed');
4
5
require_once('RESTAuth.php');
6
require_once('RESTResponse.php');
7
require_once('RESTExceptions.php');
8
9
class REST
10
{
11
  /**
12
   * [private description]
13
   * @var [type]
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...
14
   */
15
  private $ci;
16
17
  /**
18
   * [private description]
19
   * @var [type]
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...
20
   */
21
  private $api_key_limit_column;
22
23
  /**
24
   * [private description]
25
   * @var [type]
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...
26
   */
27
  private $api_key_column;
28
29
  /**
30
   * [private description]
31
   * @var [type]
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...
32
   */
33
  private $per_hour;
34
35
  /**
36
   * [private description]
37
   * @var [type]
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...
38
   */
39
  private $ip_per_hour;
40
41
  /**
42
   * [private description]
43
   * @var [type]
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...
44
   */
45
  private $show_header;
46
47
  /**
48
   * [private description]
49
   * @var [type]
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...
50
   */
51
  private $whitelist;
52
53
  /**
54
   * [private description]
55
   * @var [type]
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...
56
   */
57
  private $checked_rate_limit = false;
58
59
  /**
60
   * [private description]
61
   * @var [type]
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...
62
   */
63
  private $header_prefix;
64
65
  /**
66
   * [private description]
67
   * @var [type]
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...
68
   */
69
  private $limit_api;
70
71
  /**
72
   * [public description]
73
   * @var [type]
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...
74
   */
75
  public  $userId;
76
77
  /**
78
   * [public description]
79
   * @var [type]
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...
80
   */
81
  public $apiKey;
82
83
  /**
84
   * [public description]
85
   * @var [type]
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...
86
   */
87
  public  $apiKeyHeader;
88
89
  /**
90
   * [public description]
91
   * @var [type]
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...
92
   */
93
  public $token;
94
95
  /**
96
   * [public description]
97
   * @var [type]
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...
98
   */
99
  public $allowedIps;
100
101
  /**
102
   * [public description]
103
   * @var [type]
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...
104
   */
105
  public $config;
106
107
  /**
108
   * [public description]
109
   * @var [type]
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...
110
   */
111
  public $authPreempted = false;
112
113
  /**
114
   * [PACKAGE description]
115
   * @var string
116
   */
117
  const PACKAGE = "francis94c/ci-rest";
118
119
  /**
120
   * [RATE_LIMIT description]
121
   * @var string
122
   */
123
  const RATE_LIMIT = "RateLimit";
124
125
  /**
126
   * [AUTH_GRAVITY description]
127
   * @var integer
128
   */
129
  const AUTH_GRAVITY = 0b100;
130
  const AUTH_PASSIVE = 0b010;
131
  const AUTH_FINAL   = 0b001;
132
133
  /**
134
   * [__construct This is the part of the code that takes care of all
135
   * authentiations. allowing you to focus on building wonderful things at REST.
136
   * pun intended ;-)]
137
   * @param array|null $params Initialization parameters from the Slint system.
138
   *                           There's no use for this arg yet.
139
   */
140
  function __construct(?array $params=null)
141
  {
142
    $this->ci =& get_instance();
0 ignored issues
show
Bug introduced by
The function get_instance 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

142
    $this->ci =& /** @scrutinizer ignore-call */ get_instance();
Loading history...
143
144
    if ($this->ci->input->is_cli_request()) return;
145
146
    // Load Config If Exists.
147
    //$this->ci->config->load('rest', true, true);
148
    if (is_file(APPPATH . 'config/rest.php')) {
0 ignored issues
show
Bug introduced by
The constant APPPATH was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
149
      include APPPATH . 'config/rest.php';
150
    } else {
151
      $config = [];
152
    }
153
154
    $this->config = $config;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $config does not seem to be defined for all execution paths leading up to this point.
Loading history...
155
156
    // Load Database.
157
    $this->ci->load->database();
158
159
    // load URL Helper
160
    $this->ci->load->helper('url');
161
162
    // Load REST Helper.
163
    $this->ci->load->splint(self::PACKAGE, '%rest');
164
165
    // Load Model.
166
    $this->ci->load->splint(self::PACKAGE, '*RESTModel', 'rest_model');
167
    $this->rest_model =& $this->ci->rest_model;
0 ignored issues
show
Bug Best Practice introduced by
The property rest_model does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
168
169
    $this->rest_model->init([
170
      'users_table'           => $config['basic_auth']['users_table'] ?? null,
171
      'users_id_column'       => $config['basic_auth']['id_column'] ?? null,
172
      'users_username_column' => $config['basic_auth']['username_column'] ?? null,
173
      'users_email_column'    => $config['basic_auth']['email_column'] ?? null,
174
      'users_password_column' => $config['basic_auth']['password_column'] ?? null,
175
      'api_key_table'         => $config['api_key_auth']['api_key_table'] ?? null,
176
      'api_key_column'        => $config['api_key_auth']['api_key_column'] ?? null,
177
      'api_key_limit_column'  => $config['api_key_auth']['api_key_limit_column'] ?? null
178
    ]);
179
180
    // Load Variable(s) from Config.
181
    $this->allowedIps = $config['allowed_ips'] ?? ['127.0.0.1', '[::1]'];
182
    $this->apiKeyHeader = $config['api_key_header'] ?? 'X-API-KEY';
183
    $this->api_key_limit_column = $config['api_key_auth']['api_key_limit_column'] ?? null;
184
    $this->api_key_column = $config['api_key_auth']['api_key_column'] ?? null;
185
    $this->limit_api = $config['api_limiter']['api_limiter'] ?? false;
186
    $this->per_hour = $config['api_limiter']['per_hour'] ?? 100;
187
    $this->ip_per_hour = $config['api_limiter']['ip_per_hour'] ?? 50;
188
    $this->show_header = $config['api_limiter']['show_header'] ?? null;
189
    $this->whitelist = $config['api_limiter']['whitelist'] ?? [];
190
    $this->header_prefix = $config['api_limiter']['header_prefix'] ?? 'X-RateLimit-';
191
192
    // Limit Only?
193
    //if ($this->config['api_limiter']['api_limit_only'] ?? false) {
194
      //return;
195
    //}
196
197
    // Authenticate
198
    if ($this->ci->uri->total_segments() > 0) {
199
      $this->authenticate();
200
    }
201
202
    // Generic Rate Limiter.
203
    if ($this->limit_api && !$this->checked_rate_limit &&
204
    ($config['api_limiter']['limit_by_ip'] ?? false)) {
205
      $this->api_rest_limit_by_ip_address();
206
    }
207
208
    log_message('debug', 'REST Request Authenticated and REST Library Initialized.');
0 ignored issues
show
Bug introduced by
The function log_message 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

208
    /** @scrutinizer ignore-call */ 
209
    log_message('debug', 'REST Request Authenticated and REST Library Initialized.');
Loading history...
209
  }
210
211
  /**
212
   * [authenticate description]
213
   * @date 2020-01-30
214
   */
215
  private function authenticate():void
216
  {
217
    $auths = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $auths is dead and can be removed.
Loading history...
218
    $auths = $this->config['auth'] ?? null;
219
    if ($auths) $auths = is_array($auths) ? $auths : [$auths];
220
221
    if (!$auths) return; // No authentication(s) to carry out.
222
223
    /**
224
     * $this->process_auth() terminates the script if authentication fails
225
     * It will call the callable in the rest.php config file under
226
     * response_callbacks which matches the necesarry RESTResponse constant
227
     * before exiting. Which callable is called in any situation is documented
228
     * in README.md
229
     */
230
231
    foreach ($auths as $key => $auth) {
232
      if ($this->authPreempted) break;
233
      if (is_numeric($key)) {
234
        $this->process_auth($auth, self::AUTH_GRAVITY);
235
      } else {
236
        $this->process_auth($key, $auth);
237
      }
238
    }
239
  }
240
241
  /**
242
   * [process_auth description]
243
   * @date  2020-04-07
244
   * @param string     $auth  [description]
245
   * @param int        $flags [description]
246
   */
247
  private function process_auth(string &$auth, int $flags):void
248
  {
249
    switch ($auth) {
250
      case RESTAuth::IP: $this->ip_auth($flags); break;
251
      case RESTAuth::BASIC: $this->basic_auth($flags); break;
252
      case RESTAuth::API_KEY: $this->api_key_auth($flags); break;
253
      case RESTAuth::OAUTH2: $this->bearer_auth(RESTAuth::OAUTH2, $flags); break;
254
      case RESTAuth::BEARER: $this->bearer_auth(RESTAuth::BEARER, $flags); break;
255
      case RESTAuth::SECRET: $this->bearer_auth(RESTAuth::SECRET, $flags); break;
256
      default: $this->custom_auth($auth, $flags);
0 ignored issues
show
Unused Code introduced by
The call to REST::custom_auth() has too many arguments starting with $flags. ( Ignorable by Annotation )

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

256
      default: $this->/** @scrutinizer ignore-call */ custom_auth($auth, $flags);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
257
    }
258
  }
259
260
  /**
261
   * [auth_proceed description]
262
   * @date   2020-04-07
263
   * @param  bool       $success [description]
264
   * @param  int        $flags   [description]
265
   * @return bool                [description]
266
   */
267
  private function auth_proceed(bool $success, int $flags):bool
268
  {
269
    if ($flags & self::AUTH_GRAVITY) return $success;
270
    if ($success) {
271
      if ($flags & self::AUTH_FINAL) {
272
        $this->authPreempted = true;
273
        return true;
274
      }
0 ignored issues
show
Bug Best Practice introduced by
The function implicitly returns null when the if condition on line 271 is false. This is incompatible with the type-hinted return boolean. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
275
    } else {
276
      return $flags & self::AUTH_PASSIVE ? true : false;
277
    }
278
  }
279
280
  /**
281
   * [ip_auth description]
282
   * @date  2020-04-07
283
   * @param int        $flags [description]
284
   */
285
  private function ip_auth(int $flags):void
286
  {
287
    if (!$this->auth_proceed(in_array($this->ci->input->ip_address(), $this->allowedIps), $flags)) {
288
      $this->handle_response(RESTResponse::UN_AUTHORIZED, RESTAuth::IP); // Exits.
289
    }
290
  }
291
292
  /**
293
   * [bearer_auth description]
294
   * @date  2020-04-07
295
   * @param string     $auth  [description]
296
   * @param int        $flags [description]
297
   */
298
  private function bearer_auth(string $auth, int $flags):void
299
  {
300
    $authorization = $this->get_authorization_header();
301
    $shouldProceed = $this->auth_proceed(false, $flags);
302
    if ($authorization == null || substr_count($authorization, ' ') != 1) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $authorization of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
303
      if ($shouldProceed) return;
304
      $this->handle_response(RESTResponse::BAD_REQUEST, $auth, 'Bad Request'); // Exits.
305
    }
306
    $token = explode(" ", $authorization);
307
    if ($token[0] != $auth) {
308
      if ($shouldProceed) return;
309
      $this->handle_response(RESTResponse::BAD_REQUEST, $auth, 'Bad Request'); // Exits.
310
    }
311
    $this->token = $token[1];
312
    // Call Up Custom Implemented Bearer/Token Authorization.
313
    // Callback Check.
314
    if (!isset($this->config['auth_callbacks'][$auth])) {
315
      $this->handle_response(RESTResponse::NOT_IMPLEMENTED, $auth); // Exits.
316
    }
317
    // Authorization.
318
    if (!$this->auth_proceed($this->config['auth_callbacks'][$auth]($this, $this->token), $flags)) {
319
      $this->handle_response(RESTResponse::UN_AUTHORIZED, $auth); // Exits.
320
    }
321
  }
322
323
  /**
324
   * [basic_auth description]
325
   * @date  2020-04-07
326
   * @param int        $flags [description]
327
   */
328
  private function basic_auth(int $flags):void
329
  {
330
    $username = $_SERVER['PHP_AUTH_USER'] ?? null;
331
    $password = $_SERVER['PHP_AUTH_PW'] ?? null;
332
    if (!$this->auth_proceed(!$username || !$password, $flags)) $this->handle_response(RESTResponse::BAD_REQUEST, RESTAuth::BASIC); // Exits.
333
    if (!$this->auth_proceed($this->rest_model->basicAuth($this, $username, $password), $flags)) $this->handle_response(RESTResponse::UN_AUTHORIZED, RESTAuth::BASIC); // Exits.
334
  }
335
  /**
336
   * [api_key_auth description]
337
   */
338
  private function api_key_auth(int $flags=self::AUTH_GRAVITY):void
339
  {
340
    if (uri_string() == '')  return;
0 ignored issues
show
Bug introduced by
The function uri_string 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

340
    if (/** @scrutinizer ignore-call */ uri_string() == '')  return;
Loading history...
341
    $shouldProceed = $this->auth_proceed(false, $flags);
342
343
    if (!$this->ci->input->get_request_header($this->apiKeyHeader, true) && !$shouldProceed) {
344
    // if (!isset($_SERVER['HTTP_' . str_replace("-", "_", $this->apiKeyHeader)])) {
345
      $this->handle_response(RESTResponse::BAD_REQUEST, RESTAuth::API_KEY); // Exits.
346
    }
347
348
    $apiKey = $this->rest_model->getAPIKeyData(
349
      $this->ci->input->get_request_header($this->apiKeyHeader, true)
350
    );
351
352
    if ($apiKey == null && !$shouldProceed) {
353
      $this->handle_response(RESTResponse::UN_AUTHORIZED, RESTAuth::API_KEY); // Exits.
354
    }
355
356
    $this->apiKey = $apiKey;
357
358
    if (!$this->auth_proceed(true, $flags)) return;
359
360
    // ==== API KEY Auth Passed ==== //
361
362
    if ($this->limit_api && $this->api_key_limit_column != null && $apiKey->{$this->api_key_limit_column} == 1) {
363
      $this->limitAPIKey($apiKey->{$this->api_key_column});
364
    }
365
366
    $this->checked_rate_limit = true; // Ignore Limit By IP.
367
  }
368
369
  /**
370
   * [limitAPIKey description]
371
   * @date  2020-04-08
372
   * @param string     $apiKey [description]
373
   */
374
  public function limitAPIKey(string $apiKey):void
375
  {
376
    // Trunctate Rate Limit Data.
377
    $this->rest_model->truncateRatelimitData();
378
    // Check Whitelist.
379
    if (in_array($this->ci->input->ip_address(), $this->whitelist)) {
380
      $this->checked_rate_limit = true; // Ignore Limit By IP.
381
      return;
382
    }
383
    // Should we acyually Limit?
384
    if ($this->per_hour > 0) {
385
      $client = hash('md5', $this->ci->input->ip_address() . "%" . $apiKey);
386
      $limitData = $this->rest_model->getLimitData($client, '_api_keyed_user');
387
      if ($limitData == null) {
388
        $limitData = [];
389
        $limitData['count'] = 0;
390
        $limitData['reset_epoch'] = gmdate('d M Y H:i:s', time() + (60 * 60));
391
        $limitData['start'] = date('d M Y H:i:s');
392
      }
393
      if ($this->per_hour - $limitData['count'] > 0) {
394
        if (!$this->rest_model->insertLimitData($client, '_api_keyed_user')) {
395
          $this->handle_response(RESTResponse::INTERNAL_SERVER_ERROR, self::RATE_LIMIT); // Exits.
396
        }
397
        ++$limitData['count'];
398
        if ($this->show_header) {
399
          header($this->header_prefix.'Limit: '.$this->per_hour);
400
          header($this->header_prefix.'Remaining: '.($this->per_hour - $limitData['count']));
401
          header($this->header_prefix.'Reset: '.strtotime($limitData['reset_epoch']));
402
        }
403
      } else {
404
        header('Retry-After: '.(strtotime($limitData['reset_epoch']) - strtotime(gmdate('d M Y H:i:s'))));
405
        $this->handle_response(RESTResponse::TOO_MANY_REQUESTS, self::RATE_LIMIT); // Exits.
406
      }
407
    }
408
  }
409
410
  /**
411
   * [api_rest_limit_by_ip_address description]
412
   * TODO: Implement.
413
   */
414
  private function api_rest_limit_by_ip_address():void
415
  {
416
    // Trunctate Rate Limit Data.
417
    $this->rest_model->truncateRatelimitData();
418
    // Check Whitelist.
419
    if (in_array($this->ci->input->ip_address(), $this->whitelist)) return;
420
    // Should we acyually Limit?
421
    if ($this->ip_per_hour > 0) {
422
      $client = hash('md5', $this->ci->input->ip_address());
423
      $limitData = $this->rest_model->getLimitData($client, '_ip_address');
424
      if ($limitData == null) {
425
        $limitData = [];
426
        $limitData['count'] = 0;
427
        $limitData['reset_epoch'] = gmdate('d M Y H:i:s', time() + (60 * 60));
428
        $limitData['start'] = date('d M Y H:i:s');
429
      }
430
      if ($this->ip_per_hour - $limitData['count'] > 0) {
431
        if (!$this->rest_model->insertLimitData($client, '_ip_address')) {
432
          $this->handle_response(RESTResponse::INTERNAL_SERVER_ERROR, self::RATE_LIMIT); // Exits.
433
        }
434
        ++$limitData['count'];
435
        if ($this->show_header) {
436
          header($this->header_prefix.'Limit: '.$this->ip_per_hour);
437
          header($this->header_prefix.'Remaining: '.($this->ip_per_hour - $limitData['count']));
438
          header($this->header_prefix.'Reset: '.strtotime($limitData['reset_epoch']));
439
        }
440
      } else {
441
        header('Retry-After: '.(strtotime($limitData['reset_epoch']) - strtotime(gmdate('d M Y H:i:s'))));
442
        $this->handle_response(RESTResponse::TOO_MANY_REQUESTS, self::RATE_LIMIT); // Exits.
443
      }
444
    }
445
  }
446
  /**
447
   * [custom_auth description]
448
   * @param string $auth [description]
449
   */
450
  private function custom_auth(string &$auth):void
451
  {
452
    // Header Check.
453
    if (!isset($_SERVER[$auth])) {
454
      $this->handle_response(RESTResponse::BAD_REQUEST, $auth);
455
    }
456
    // Callback Check.
457
    if (!isset($this->config['auth_callbacks'][$auth])) {
458
      $this->handle_response(RESTResponse::NOT_IMPLEMENTED, $auth); // Exits.
459
    }
460
    // Authentication.
461
    if (!$this->config['auth_callbacks'][$auth]($this, $this->ci->security->xss_clean($_SERVER[$auth]))) {
462
      $this->handle_response(RESTResponse::UN_AUTHORIZED, $auth); // Exits.
463
    }
464
  }
465
  /**
466
   * [get_authorization_header description]
467
   * @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...
468
   */
469
  private function get_authorization_header():?string
470
  {
471
    if (isset($_SERVER['Authorization'])) {
472
      return trim($_SERVER["Authorization"]);
473
    } else if (isset($_SERVER['HTTP_AUTHORIZATION'])) { //Nginx or fast CGI
474
      return trim($_SERVER["HTTP_AUTHORIZATION"]);
475
    } elseif (function_exists('apache_request_headers')) {
476
      $requestHeaders = apache_request_headers();
477
478
      // Avoid Surprises.
479
      $requestHeaders = array_combine(array_map('ucwords', array_keys($requestHeaders)), array_values($requestHeaders));
0 ignored issues
show
Bug introduced by
It seems like $requestHeaders can also be of type false; however, parameter $input of array_keys() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

479
      $requestHeaders = array_combine(array_map('ucwords', array_keys(/** @scrutinizer ignore-type */ $requestHeaders)), array_values($requestHeaders));
Loading history...
Bug introduced by
It seems like $requestHeaders can also be of type false; however, parameter $input of array_values() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

479
      $requestHeaders = array_combine(array_map('ucwords', array_keys($requestHeaders)), array_values(/** @scrutinizer ignore-type */ $requestHeaders));
Loading history...
480
481
      if (isset($requestHeaders['Authorization'])) {
482
        return trim($requestHeaders['Authorization']);
483
      }
484
    }
485
    return null;
486
  }
487
488
  /**
489
   * [handle_response description]
490
   * @param int $code [description]
491
   */
492
  private function handle_response(int $code, $auth=null, ?string $errorReason=null):void
493
  {
494
    http_response_code($code);
495
    header("Content-Type: application/json");
496
    if (isset($this->config['response_callbacks'][$code])) {
497
      $this->config['response_callbacks'][$code]($auth, $errorReason);
498
    }
499
    if (ENVIRONMENT != 'testing') exit($code);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
Bug introduced by
The constant ENVIRONMENT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
500
    throw new Exception("Error $code in $auth", $code);
501
  }
502
}
503
?>
0 ignored issues
show
Best Practice introduced by
It is not recommended to use PHP's closing tag ?> in files other than templates.

Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore.

A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever.

Loading history...
504