Passed
Push — master ( 151dda...f1e0bb )
by Vince
01:47
created

header::authorizationHeaders()   B

Complexity

Conditions 9
Paths 9

Size

Total Lines 33
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 9
eloc 16
c 1
b 0
f 0
nc 9
nop 1
dl 0
loc 33
rs 8.0555
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\headers
11
 *
12
 * @author Vince scarpa <[email protected]>
13
 *
14
 */
15
namespace responsible\core\headers;
16
17
use responsible\core\encoder;
18
use responsible\core\exception;
19
use responsible\core\server;
20
use responsible\core\user;
21
use responsible\core\auth;
22
use responsible\core\helpers\help as helper;
23
use responsible\core\interfaces;
24
25
class header extends server implements interfaces\optionsInterface
26
{
27
    use \responsible\core\traits\optionsTrait;
28
29
    /**
30
     * Max age constant
31
     */
32
    const MAX_WINDOW = 3600;
33
34
    /**
35
     * [$REQUEST_APPLICATION]
36
     * @var array
37
     */
38
    private $REQUEST_APPLICATION = array(
39
        'xml' => 'text/xml',
40
        'json' => 'application/json',
41
        'html' => 'text/html',
42
        'array' => 'text/plain',
43
        'object' => 'text/plain',
44
    );
45
46
    /**
47
     * [$REQUEST_TYPE / Default is json]
48
     * @var string
49
     */
50
    private $REQUEST_TYPE;
51
52
    /**
53
     * [$REQUEST_METHOD]
54
     * @var array
55
     */
56
    private $REQUEST_METHOD = [];
57
58
    /**
59
     * [__construct Silence...]
60
     */
61
    public function __construct() {}
62
63
    /**
64
     * [requestType]
65
     * @return void
66
     */
67
    public function requestType($type = 'json')
68
    {
69
        $this->REQUEST_TYPE = $type;
70
    }
71
72
    /**
73
     * [getRequestType]
74
     * @return string
75
     */
76
    public function getRequestType()
77
    {
78
        return $this->REQUEST_TYPE;
79
    }
80
81
    /**
82
     * [requestMethod Set and return the request method]
83
     * @return object
84
     */
85
    public function requestMethod()
86
    {
87
        switch (strtolower($_SERVER['REQUEST_METHOD'])) {
88
89
            case 'get':
90
                $this->REQUEST_METHOD = ['method' => 'get', 'data' => $_GET];
91
                break;
92
93
            case 'post':
94
                $_POST_DATA = $_POST;
95
                $jsonData = json_decode(file_get_contents("php://input"));
96
97
                if (is_object($jsonData) || is_array($jsonData)) {
98
                    $_POST_DATA = json_decode(file_get_contents("php://input"), true);
99
                }
100
                $_POST = array_merge($_REQUEST, $_POST);
101
                $_REQUEST = array_merge($_POST, $_POST_DATA);
102
103
                $this->REQUEST_METHOD = ['method' => 'post', 'data' => $_REQUEST];
104
                break;
105
106
            case 'options':
107
                $this->REQUEST_METHOD = ['method' => 'options', 'data' => $_POST];
108
                echo json_encode(['success'=>true]);
109
                $this->setHeaders();
110
                exit;
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...
111
                break;
112
113
            case 'put':
114
                parse_str(file_get_contents("php://input"), $_PUT);
115
116
                foreach ($_PUT as $key => $value) {
117
                    unset($_PUT[$key]);
118
                    $_PUT[str_replace('amp;', '', $key)] = $value;
119
                }
120
121
                $_REQUEST = array_merge($_REQUEST, $_PUT);
122
123
                $this->REQUEST_METHOD = ['method' => 'put', 'data' => $_REQUEST];
124
                break;
125
126
            case 'patch':
127
                # [TODO]
128
                $this->REQUEST_METHOD = ['method' => 'patch', 'data' => []];
129
                break;
130
131
            case 'delete':
132
                # [TODO]
133
                $this->REQUEST_METHOD = ['method' => 'delete', 'data' => []];
134
                break;
135
136
            default:
137
                $this->REQUEST_METHOD = [];
138
                break;
139
        }
140
    }
141
142
    /**
143
     * [getMethod Get the request method]
144
     * @return object
145
     */
146
    public function getMethod()
147
    {
148
        return (object) $this->REQUEST_METHOD;
149
    }
150
151
    /**
152
     * [setAllowedMethods Set the allowed methods for endpoints]
153
     * @param array $methods [GET, POST, PUT, PATCH, DELETE, ect..]
154
     */
155
    public function setAllowedMethods(array $methods)
156
    {
157
        $this->setHeader('Access-Control-Allow-Methods', array(
158
            implode(',', $methods),
159
        ));
160
161
        $requestMethod = $this->getServerMethod();
162
        if (!in_array($requestMethod, $methods)) {
163
            (new exception\errorException)->error('METHOD_NOT_ALLOWED');
164
        }
165
    }
166
167
    /**
168
     * [getMethod Get the request method]
169
     * @return string
170
     */
171
    public function getServerMethod()
172
    {
173
        if (!isset($_SERVER['REQUEST_METHOD'])) {
174
            return '';
175
        }
176
        return $_SERVER['REQUEST_METHOD'];
177
    }
178
179
    /**
180
     * [getHeaders List all headers Server headers and Apache headers]
181
     * @return array
182
     */
183
    public function getHeaders()
184
    {
185
        $headers_list = headers_list();
186
        foreach ($headers_list as $index => $headValue) {
187
            @list($key, $value) = explode(": ", $headValue);
188
            
189
            if (!is_null($key) && !is_null($value) ) {
190
                $headers_list[$key] = $value;
191
                unset($headers_list[$index]);
192
            }
193
        }
194
195
        if (!function_exists('apache_request_headers')) {
196
            $apacheRequestHeaders = $this->apacheRequestHeaders();
197
        } else {
198
            $apacheRequestHeaders = apache_request_headers();
199
        }
200
201
        if( is_null($apacheRequestHeaders) || empty($apacheRequestHeaders) ) {
202
            return [];
203
        }
204
205
        $apache_headers = array_merge($headers_list, $apacheRequestHeaders);
206
207
        $headers = array();
208
209
        foreach ($_SERVER as $key => $value) {
210
            if (substr($key, 0, 5) != 'HTTP_') {
211
                continue;
212
            }
213
            $header = str_replace(' ', '-', ucwords(str_replace('_', ' ', strtolower(substr($key, 5)))));
214
            $headers[$header] = $value;
215
        }
216
217
        return array_merge($headers, $apache_headers);
218
    }
219
220
    /**
221
     * [setHeader Append aditional headers]
222
     * @return void
223
     */
224
    public function setHeader($header, $headerValue = array(), $status = '', $delimiter = ';')
225
    {
226
        $header = trim(str_replace(':', '', $header)) . ': ';
227
        $headerValue = implode($delimiter . ' ', $headerValue);
228
229
        header($header . $status . $headerValue);
230
    }
231
232
    /**
233
     * [setHeaders Default headers]
234
     * @return void
235
     */
236
    public function setHeaders()
237
    {
238
        $application = 'json';
239
        if (isset($this->REQUEST_APPLICATION[$this->getRequestType()])) {
240
            $application = $this->REQUEST_APPLICATION[$this->getRequestType()];
241
        }
242
243
        $this->setHeader('Content-Type', array(
244
            $application, 'charset=UTF-8',
245
        ));
246
247
        $this->setHeader('Accept-Ranges', array(
248
            'bytes',
249
        ));
250
251
        $this->setHeader('Access-Control-Allow-Credentials', array(
252
            true,
253
        ));
254
255
        $this->setHeader('Access-Control-Allow-Origin', array(
256
            '*',
257
        ));
258
259
        if( !array_key_exists('Access-Control-Allow-Methods', $this->getHeaders()) ) {
260
            $this->setHeader('Access-Control-Allow-Methods', array(
261
                'GET,POST,OPTIONS',
262
            ));
263
        }
264
265
        $this->setHeader('Access-Control-Expose-Headers', array(
266
            'Content-Range',
267
        ));
268
269
        $this->setHeader('Access-Control-Allow-Headers', array(
270
            'origin,x-requested-with,Authorization,cache-control',
271
        ));
272
273
        $this->setHeader('Access-Control-Max-Age', array(
274
            $this->getMaxWindow(),
275
        ));
276
277
        $this->setHeader('Expires', array(
278
            'Wed, 20 September 1978 00:00:00 GMT',
279
        ));
280
281
        $this->setHeader('Cache-Control', array(
282
            'no-store, no-cache, must-revalidate',
283
        ));
284
285
        $this->setHeader('Cache-Control', array(
286
            'post-check=0, pre-check=0',
287
        ));
288
289
        $this->setHeader('Pragma', array(
290
            'no-cache',
291
        ));
292
293
        $this->setHeader('X-Content-Type-Options', array(
294
            'nosniff',
295
        ));
296
297
        $this->setHeader('X-XSS-Protection', array(
298
            '1', 'mode=block',
299
        ));
300
301
        if (isset($this->getOptions()['addHeaders']) &&
302
            (is_array($this->getOptions()['addHeaders']) && !empty($this->getOptions()['addHeaders']))
303
        ) {
304
            foreach ($this->getOptions()['addHeaders'] as $i => $customHeader) {
305
                if (is_array($customHeader) && sizeof($customHeader) == 2) {
306
                    $this->setHeader($customHeader[0], array(
307
                        $customHeader[1],
308
                    ));
309
                }
310
            }
311
        }
312
    }
313
314
    /**
315
     * [apacheRequestHeaders Native replacment fuction]
316
     * https://www.php.net/manual/en/function.apache-request-headers.php#70810
317
     * @return array
318
     */
319
    public function apacheRequestHeaders()
320
    {
321
        $arh = array();
322
        $rx_http = '/\AHTTP_/';
323
324
        foreach ($_SERVER as $key => $val) {
325
            if (preg_match($rx_http, $key)) {
326
                $arh_key = preg_replace($rx_http, '', $key);
327
                $rx_matches = explode('_', $arh_key);
328
                if (count($rx_matches) > 0 and strlen($arh_key) > 2) {
329
                    foreach ($rx_matches as $ak_key => $ak_val) {
330
                        $rx_matches[$ak_key] = ucfirst($ak_val);
331
                    }
332
333
                    $arh_key = implode('-', $rx_matches);
334
                }
335
                $arh[$arh_key] = $val;
336
            }
337
        }
338
        return ($arh);
339
    }
340
341
    /**
342
     * [setHeaderStatus]
343
     * @param void
344
     */
345
    public function setHeaderStatus($status)
346
    {
347
        http_response_code($status);
348
    }
349
350
    /**
351
     * [getHeaderStatus]
352
     * @return integer
353
     */
354
    public function getHeaderStatus()
355
    {
356
        return http_response_code();
357
    }
358
359
    /**
360
     * [hasBearerToken Check if bearer token is present]
361
     * @return string|null
362
     */
363
    public function hasBearerToken()
364
    {
365
        $auth_headers = $this->getHeaders();
366
367
        if (isset($auth_headers["Authorization"]) && !empty($auth_headers["Authorization"])) {
368
369
            list($type, $clientToken) = explode(" ", $auth_headers["Authorization"], 2);
370
371
            if (strcasecmp($type, "Bearer") == 0 && !empty($clientToken)) {
372
                return $clientToken;
373
            }
374
        }
375
        return;
376
    }
377
378
    /**
379
     * Check if the request is a token grant
380
     * @return array|boolean
381
     */
382
    public function isGrantRequest($auth_headers)
383
    {
384
        $helper = new helper;
385
386
        if( $grantType = $helper->checkVal($_REQUEST, 'grant_type') ) {
387
388
            $refreshToken = false;
389
390
            if ($grantType == 'client_credentials') {
391
                $refreshToken = $this->accessCredentialHeaders($auth_headers);
392
            }
393
394
            if ($grantType == 'refresh_token') {
395
                $refreshToken = $this->accessRefreshHeaders($auth_headers);
396
            }
397
398
            if ($refreshToken) {
399
                return [
400
                    'client_access_request' => $refreshToken,
401
                ];
402
            }
403
        }
404
        return false;
405
    }
406
407
    /**
408
     * [authorizationHeaders Scan for "Authorization" header]
409
     * @return string|array [mixed: string / error]
410
     */
411
    public function authorizationHeaders($skipError = false)
412
    {
413
        $auth_headers = $this->getHeaders();
414
415
        if (isset($auth_headers["Authorization"]) && !empty($auth_headers["Authorization"])) {
416
417
            if ($grant = $this->isGrantRequest($auth_headers)) {
418
                return $grant;
419
            }
420
421
            /**
422
             * Test if it's a Authorization Bearer token
423
             */
424
            if (strcasecmp(trim($auth_headers["Authorization"]), "Bearer") == 0) {
425
                $this->unauthorised();
426
            }
427
428
            list($type, $clientToken) = explode(" ", $auth_headers["Authorization"], 2);
429
430
            if (strcasecmp($type, "Bearer") == 0 && !empty($clientToken)) {
431
                return $clientToken;
432
            } else {
433
                if (!$skipError) {
434
                    $this->unauthorised();
435
                }
436
            }
437
        } else {
438
            if (!$skipError) {
439
                $this->unauthorised();
440
            }
441
        }
442
443
        return '';
444
    }
445
446
    /**
447
     * [accessRefreshHeaders description]
448
     * @return string|array [mixed: string / error]
449
     */
450
    private function accessRefreshHeaders($auth_headers)
451
    {
452
        list($type, $clientToken) = explode(" ", $auth_headers["Authorization"], 2);
453
454
        if (strcasecmp($type, "Bearer") == 0 && !empty($clientToken)) {
455
456
            $user = new user\user;
457
            $account = $user
458
                ->setOptions($this->options)
459
                ->load(
460
                    $clientToken,
461
                    array(
462
                        'loadBy' => 'refresh_token',
463
                        'getJWT' => true,
464
                        'authorizationRefresh' => true,
465
                    )
466
                );
467
468
            if( empty($account) ) {
469
                $this->unauthorised();
470
            }
471
472
            $tokens = [
473
                'token' => $account['JWT'],
474
                'refresh_token' => $account['refreshToken']['token']
475
            ];
476
477
            $account['refreshToken'] = $tokens;
478
479
            return $account;
480
481
        } else {
482
            $this->unauthorised();
483
        }
484
    }
485
486
    /**
487
     * [accessCredentialHeaders Check if the credentials are correct]
488
     * @param  array $auth_headers
489
     * @return string|array [mixed: string / error]
490
     */
491
    private function accessCredentialHeaders($auth_headers)
492
    {
493
        $cipher = new encoder\cipher;
494
495
        list($type, $clientCredentials) = explode(" ", $auth_headers["Authorization"], 2);
496
497
        if (strcasecmp($type, "Basic") == 0 && !empty($clientCredentials)) {
498
            $credentails = explode('/', $clientCredentials);
499
            if (!empty($credentails) && is_array($credentails)) {
500
                $credentails = explode(':', $cipher->decode($clientCredentials));
501
502
                if (!empty($credentails) && is_array($credentails) && sizeof($credentails) == 2) {
503
                    $user = new user\user;
504
                    $user->setAccountID($credentails[0]);
505
506
                    $account = $user
507
                        ->setOptions($this->options)
508
                        ->load(
509
                            $credentails[0],
510
                            array(
511
                                'loadBy' => 'account_id',
512
                                'getJWT' => true,
513
                                'authorizationRefresh' => true,
514
                            )
515
                        );
516
517
                    $tokens = [
518
                        'token' => $account['JWT'],
519
                        'refresh_token' => $account['refreshToken']['token']
520
                    ];
521
522
                    $account['refreshToken'] = $tokens;
523
524
                    if (!empty($account)) {
525
                        if (strcasecmp($account['secret'], $credentails[1]) == 0) {
526
                            return $account;
527
                        }
528
                    }
529
                }
530
            }
531
        } else {
532
            $this->unauthorised();
533
        }
534
    }
535
536
    /**
537
     * [unauthorised Set an unauthorised header]
538
     * @return array [exit exception message]
539
     */
540
    public function unauthorised()
541
    {
542
        $this->setHeaders();
543
544
        $this->setHeader('HTTP/1.1', array(
545
            'Unauthorized',
546
        ), 401);
547
548
        (new exception\errorException)->error('UNAUTHORIZED');
549
    }
550
551
    /**
552
     * [getMaxWindow Get the max control age window]
553
     * @return integer
554
     */
555
    private function getMaxWindow()
556
    {
557
        if ($this->getOptions()) {
558
            if (isset($this->getOptions()['maxWindow']) && !empty($this->getOptions()['maxWindow'])) {
559
                if (!is_numeric($this->getOptions()['maxWindow'])) {
560
                    (new exception\errorException)
561
                        ->message('maxWindow option must be an integer type')
562
                        ->error('APPLICATION_ERROR');
563
                }
564
565
                return $this->getOptions()['maxWindow'];
566
            }
567
        }
568
        return self::MAX_WINDOW;
569
    }
570
571
    /**
572
     * [setData Set request method data]
573
     * @param array $data
574
     * @return void
575
     */
576
    public function setData($data = []) 
577
    {
578
        $this->REQUEST_METHOD['data'] = $data;
579
    }
580
}
581