Passed
Push — master ( f1e0bb...f40f88 )
by Vince
01:35
created

header::authorizationHeaders()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 4
eloc 6
nc 4
nop 1
dl 0
loc 12
rs 10
c 2
b 0
f 0
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
     * [authorizationHeaders Scan for "Authorization" header]
361
     * @return string|array [mixed: string / error]
362
     */
363
    public function authorizationHeaders($skipError = false)
364
    {
365
        if ($grant = $this->isGrantRequest()) {
366
            return $grant;
367
        }
368
369
        if ($clientToken = $this->hasBearerToken()) {
370
            return $clientToken;
371
        }
372
373
        if (!$skipError) {
374
            $this->unauthorised();
375
        }
376
    }
377
378
    /**
379
     * [hasBearerValue Check if Authorization headers has Bearer value]
380
     * @throws Exception
381
     *         Unauthorised
382
     * @return boolean
383
     */
384
    private function hasBearerValue()
385
    {
386
        $auth_headers = $this->getHeaders();
387
388
        if (isset($auth_headers["Authorization"]) && !empty($auth_headers["Authorization"])) {
389
            
390
            list($type, $clientToken) = explode(" ", $auth_headers["Authorization"], 2);
391
392
            if (strcasecmp(trim($type), "Bearer") == 0) {
393
                return true;
394
            }
395
        }
396
397
        return false;
398
    }
399
400
    /**
401
     * [hasBearerToken Check if bearer token is present]
402
     * @return string|null
403
     */
404
    public function hasBearerToken()
405
    {
406
        $auth_headers = $this->getHeaders();
407
408
        if( $this->hasBearerValue() ) {
409
410
            list($type, $clientToken) = explode(" ", $auth_headers["Authorization"], 2);
411
412
            if (strcasecmp(trim($type), "Bearer") == 0 && !empty($clientToken)) {
413
                return $clientToken;
414
            }
415
        }
416
417
        return;
418
    }
419
420
    /**
421
     * Check if the request is a token grant
422
     * @return array|boolean
423
     */
424
    public function isGrantRequest()
425
    {
426
        $auth_headers = $this->getHeaders();
427
        $helper = new helper;
428
429
        if (isset($auth_headers["Authorization"]) && !empty($auth_headers["Authorization"])) {
430
            if( $grantType = $helper->checkVal($_REQUEST, 'grant_type') ) {
431
432
                $refreshToken = false;
433
434
                if ($grantType == 'client_credentials') {
435
                    $refreshToken = $this->accessCredentialHeaders($auth_headers);
436
                }
437
438
                if ($grantType == 'refresh_token') {
439
                    $refreshToken = $this->accessRefreshHeaders($auth_headers);
440
                }
441
442
                if ($refreshToken) {
443
                    return [
444
                        'client_access_request' => $refreshToken,
445
                    ];
446
                }
447
            }
448
        }
449
450
        return false;
451
    }
452
453
    /**
454
     * [accessRefreshHeaders description]
455
     * @return string|array [mixed: string / error]
456
     */
457
    private function accessRefreshHeaders($auth_headers)
458
    {
459
        list($type, $clientToken) = explode(" ", $auth_headers["Authorization"], 2);
460
461
        if (strcasecmp($type, "Bearer") == 0 && !empty($clientToken)) {
462
463
            $user = new user\user;
464
            $account = $user
465
                ->setOptions($this->options)
466
                ->load(
467
                    $clientToken,
468
                    array(
469
                        'loadBy' => 'refresh_token',
470
                        'getJWT' => true,
471
                        'authorizationRefresh' => true,
472
                    )
473
                );
474
475
            if( empty($account) ) {
476
                $this->unauthorised();
477
            }
478
479
            $tokens = [
480
                'token' => $account['JWT'],
481
                'refresh_token' => $account['refreshToken']['token']
482
            ];
483
484
            $account['refreshToken'] = $tokens;
485
486
            return $account;
487
488
        } else {
489
            $this->unauthorised();
490
        }
491
    }
492
493
    /**
494
     * [accessCredentialHeaders Check if the credentials are correct]
495
     * @param  array $auth_headers
496
     * @return string|array [mixed: string / error]
497
     */
498
    private function accessCredentialHeaders($auth_headers)
499
    {
500
        $cipher = new encoder\cipher;
501
502
        list($type, $clientCredentials) = explode(" ", $auth_headers["Authorization"], 2);
503
504
        if (strcasecmp($type, "Basic") == 0 && !empty($clientCredentials)) {
505
            $credentails = explode('/', $clientCredentials);
506
            if (!empty($credentails) && is_array($credentails)) {
507
                $credentails = explode(':', $cipher->decode($clientCredentials));
508
509
                if (!empty($credentails) && is_array($credentails) && sizeof($credentails) == 2) {
510
                    $user = new user\user;
511
                    $user->setAccountID($credentails[0]);
512
513
                    $account = $user
514
                        ->setOptions($this->options)
515
                        ->load(
516
                            $credentails[0],
517
                            array(
518
                                'loadBy' => 'account_id',
519
                                'getJWT' => true,
520
                                'authorizationRefresh' => true,
521
                            )
522
                        );
523
524
                    $tokens = [
525
                        'token' => $account['JWT'],
526
                        'refresh_token' => $account['refreshToken']['token']
527
                    ];
528
529
                    $account['refreshToken'] = $tokens;
530
531
                    if (!empty($account)) {
532
                        if (strcasecmp($account['secret'], $credentails[1]) == 0) {
533
                            return $account;
534
                        }
535
                    }
536
                }
537
            }
538
        } else {
539
            $this->unauthorised();
540
        }
541
    }
542
543
    /**
544
     * [unauthorised Set an unauthorised header]
545
     * @return array [exit exception message]
546
     */
547
    public function unauthorised()
548
    {
549
        $this->setHeaders();
550
551
        $this->setHeader('HTTP/1.1', array(
552
            'Unauthorized',
553
        ), 401);
554
555
        (new exception\errorException)->error('UNAUTHORIZED');
556
    }
557
558
    /**
559
     * [getMaxWindow Get the max control age window]
560
     * @return integer
561
     */
562
    private function getMaxWindow()
563
    {
564
        if ($this->getOptions()) {
565
            if (isset($this->getOptions()['maxWindow']) && !empty($this->getOptions()['maxWindow'])) {
566
                if (!is_numeric($this->getOptions()['maxWindow'])) {
567
                    (new exception\errorException)
568
                        ->message('maxWindow option must be an integer type')
569
                        ->error('APPLICATION_ERROR');
570
                }
571
572
                return $this->getOptions()['maxWindow'];
573
            }
574
        }
575
        return self::MAX_WINDOW;
576
    }
577
578
    /**
579
     * [setData Set request method data]
580
     * @param array $data
581
     * @return void
582
     */
583
    public function setData($data = []) 
584
    {
585
        $this->REQUEST_METHOD['data'] = $data;
586
    }
587
}
588