· 6 years ago · Aug 24, 2019, 05:48 PM
1<?php
2
3defined('BASEPATH') OR exit('No direct script access allowed');
4
5/**
6 * CodeIgniter Rest Controller
7 * A fully RESTful server implementation for CodeIgniter using one library, one config file and one controller.
8 *
9 * @package CodeIgniter
10 * @subpackage Libraries
11 * @category Libraries
12 * @author Phil Sturgeon, Chris Kacerguis
13 * @license MIT
14 * @link https://github.com/chriskacerguis/codeigniter-restserver
15 * @version 3.0.0
16 */
17abstract class REST_Controller extends CI_Controller {
18
19 // Note: Only the widely used HTTP status codes are documented
20
21 // Informational
22
23 const HTTP_CONTINUE = 100;
24 const HTTP_SWITCHING_PROTOCOLS = 101;
25 const HTTP_PROCESSING = 102; // RFC2518
26
27 // Success
28
29 /**
30 * The request has succeeded
31 */
32 const HTTP_OK = 200;
33
34 /**
35 * The server successfully created a new resource
36 */
37 const HTTP_CREATED = 201;
38 const HTTP_ACCEPTED = 202;
39 const HTTP_NON_AUTHORITATIVE_INFORMATION = 203;
40
41 /**
42 * The server successfully processed the request, though no content is returned
43 */
44 const HTTP_NO_CONTENT = 204;
45 const HTTP_RESET_CONTENT = 205;
46 const HTTP_PARTIAL_CONTENT = 206;
47 const HTTP_MULTI_STATUS = 207; // RFC4918
48 const HTTP_ALREADY_REPORTED = 208; // RFC5842
49 const HTTP_IM_USED = 226; // RFC3229
50
51 // Redirection
52
53 const HTTP_MULTIPLE_CHOICES = 300;
54 const HTTP_MOVED_PERMANENTLY = 301;
55 const HTTP_FOUND = 302;
56 const HTTP_SEE_OTHER = 303;
57
58 /**
59 * The resource has not been modified since the last request
60 */
61 const HTTP_NOT_MODIFIED = 304;
62 const HTTP_USE_PROXY = 305;
63 const HTTP_RESERVED = 306;
64 const HTTP_TEMPORARY_REDIRECT = 307;
65 const HTTP_PERMANENTLY_REDIRECT = 308; // RFC7238
66
67 // Client Error
68
69 /**
70 * The request cannot be fulfilled due to multiple errors
71 */
72 const HTTP_BAD_REQUEST = 400;
73
74 /**
75 * The user is unauthorized to access the requested resource
76 */
77 const HTTP_UNAUTHORIZED = 401;
78 const HTTP_PAYMENT_REQUIRED = 402;
79
80 /**
81 * The requested resource is unavailable at this present time
82 */
83 const HTTP_FORBIDDEN = 403;
84
85 /**
86 * The requested resource could not be found
87 *
88 * Note: This is sometimes used to mask if there was an UNAUTHORIZED (401) or
89 * FORBIDDEN (403) error, for security reasons
90 */
91 const HTTP_NOT_FOUND = 404;
92
93 /**
94 * The request method is not supported by the following resource
95 */
96 const HTTP_METHOD_NOT_ALLOWED = 405;
97
98 /**
99 * The request was not acceptable
100 */
101 const HTTP_NOT_ACCEPTABLE = 406;
102 const HTTP_PROXY_AUTHENTICATION_REQUIRED = 407;
103 const HTTP_REQUEST_TIMEOUT = 408;
104
105 /**
106 * The request could not be completed due to a conflict with the current state
107 * of the resource
108 */
109 const HTTP_CONFLICT = 409;
110 const HTTP_GONE = 410;
111 const HTTP_LENGTH_REQUIRED = 411;
112 const HTTP_PRECONDITION_FAILED = 412;
113 const HTTP_REQUEST_ENTITY_TOO_LARGE = 413;
114 const HTTP_REQUEST_URI_TOO_LONG = 414;
115 const HTTP_UNSUPPORTED_MEDIA_TYPE = 415;
116 const HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = 416;
117 const HTTP_EXPECTATION_FAILED = 417;
118 const HTTP_I_AM_A_TEAPOT = 418; // RFC2324
119 const HTTP_UNPROCESSABLE_ENTITY = 422; // RFC4918
120 const HTTP_LOCKED = 423; // RFC4918
121 const HTTP_FAILED_DEPENDENCY = 424; // RFC4918
122 const HTTP_RESERVED_FOR_WEBDAV_ADVANCED_COLLECTIONS_EXPIRED_PROPOSAL = 425; // RFC2817
123 const HTTP_UPGRADE_REQUIRED = 426; // RFC2817
124 const HTTP_PRECONDITION_REQUIRED = 428; // RFC6585
125 const HTTP_TOO_MANY_REQUESTS = 429; // RFC6585
126 const HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431; // RFC6585
127
128 // Server Error
129
130 /**
131 * The server encountered an unexpected error
132 *
133 * Note: This is a generic error message when no specific message
134 * is suitable
135 */
136 const HTTP_INTERNAL_SERVER_ERROR = 500;
137
138 /**
139 * The server does not recognise the request method
140 */
141 const HTTP_NOT_IMPLEMENTED = 501;
142 const HTTP_BAD_GATEWAY = 502;
143 const HTTP_SERVICE_UNAVAILABLE = 503;
144 const HTTP_GATEWAY_TIMEOUT = 504;
145 const HTTP_VERSION_NOT_SUPPORTED = 505;
146 const HTTP_VARIANT_ALSO_NEGOTIATES_EXPERIMENTAL = 506; // RFC2295
147 const HTTP_INSUFFICIENT_STORAGE = 507; // RFC4918
148 const HTTP_LOOP_DETECTED = 508; // RFC5842
149 const HTTP_NOT_EXTENDED = 510; // RFC2774
150 const HTTP_NETWORK_AUTHENTICATION_REQUIRED = 511;
151
152 /**
153 * This defines the rest format
154 * Must be overridden it in a controller so that it is set
155 *
156 * @var string|NULL
157 */
158 protected $rest_format = NULL;
159
160 /**
161 * Defines the list of method properties such as limit, log and level
162 *
163 * @var array
164 */
165 protected $methods = [];
166
167 /**
168 * List of allowed HTTP methods
169 *
170 * @var array
171 */
172 protected $allowed_http_methods = ['get', 'delete', 'post', 'put', 'options', 'patch', 'head'];
173
174 /**
175 * Contains details about the request
176 * Fields: body, format, method, ssl
177 * Note: This is a dynamic object (stdClass)
178 *
179 * @var object
180 */
181 protected $request = NULL;
182
183 /**
184 * Contains details about the response
185 * Fields: format, lang
186 * Note: This is a dynamic object (stdClass)
187 *
188 * @var object
189 */
190 protected $response = NULL;
191
192 /**
193 * Contains details about the REST API
194 * Fields: db, ignore_limits, key, level, user_id
195 * Note: This is a dynamic object (stdClass)
196 *
197 * @var object
198 */
199 protected $rest = NULL;
200
201 /**
202 * The arguments for the GET request method
203 *
204 * @var array
205 */
206 protected $_get_args = [];
207
208 /**
209 * The arguments for the POST request method
210 *
211 * @var array
212 */
213 protected $_post_args = [];
214
215 /**
216 * The arguments for the PUT request method
217 *
218 * @var array
219 */
220 protected $_put_args = [];
221
222 /**
223 * The arguments for the DELETE request method
224 *
225 * @var array
226 */
227 protected $_delete_args = [];
228
229 /**
230 * The arguments for the PATCH request method
231 *
232 * @var array
233 */
234 protected $_patch_args = [];
235
236 /**
237 * The arguments for the HEAD request method
238 *
239 * @var array
240 */
241 protected $_head_args = [];
242
243 /**
244 * The arguments for the OPTIONS request method
245 *
246 * @var array
247 */
248 protected $_options_args = [];
249
250 /**
251 * The arguments for the query parameters
252 *
253 * @var array
254 */
255 protected $_query_args = [];
256
257 /**
258 * The arguments from GET, POST, PUT, DELETE, PATCH, HEAD and OPTIONS request methods combined
259 *
260 * @var array
261 */
262 protected $_args = [];
263
264 /**
265 * The insert_id of the log entry (if we have one)
266 *
267 * @var string
268 */
269 protected $_insert_id = '';
270
271 /**
272 * If the request is allowed based on the API key provided
273 *
274 * @var bool
275 */
276 protected $_allow = TRUE;
277
278 /**
279 * The LDAP Distinguished Name of the User post authentication
280 *
281 * @var string
282 */
283 protected $_user_ldap_dn = '';
284
285 /**
286 * The start of the response time from the server
287 *
288 * @var number
289 */
290 protected $_start_rtime;
291
292 /**
293 * The end of the response time from the server
294 *
295 * @var number
296 */
297 protected $_end_rtime;
298
299 /**
300 * List all supported methods, the first will be the default format
301 *
302 * @var array
303 */
304 protected $_supported_formats = [
305 'json' => 'application/json',
306 'array' => 'application/json',
307 'csv' => 'application/csv',
308 'html' => 'text/html',
309 'jsonp' => 'application/javascript',
310 'php' => 'text/plain',
311 'serialized' => 'application/vnd.php.serialized',
312 'xml' => 'application/xml'
313 ];
314
315 /**
316 * Information about the current API user
317 *
318 * @var object
319 */
320 protected $_apiuser;
321
322 /**
323 * Whether or not to perform a CORS check and apply CORS headers to the request
324 *
325 * @var bool
326 */
327 protected $check_cors = NULL;
328
329 /**
330 * Enable XSS flag
331 * Determines whether the XSS filter is always active when
332 * GET, OPTIONS, HEAD, POST, PUT, DELETE and PATCH data is encountered
333 * Set automatically based on config setting
334 *
335 * @var bool
336 */
337 protected $_enable_xss = FALSE;
338
339 private $is_valid_request = TRUE;
340
341 /**
342 * HTTP status codes and their respective description
343 * Note: Only the widely used HTTP status codes are used
344 *
345 * @var array
346 * @link http://www.restapitutorial.com/httpstatuscodes.html
347 */
348 protected $http_status_codes = [
349 self::HTTP_OK => 'OK',
350 self::HTTP_CREATED => 'CREATED',
351 self::HTTP_NO_CONTENT => 'NO CONTENT',
352 self::HTTP_NOT_MODIFIED => 'NOT MODIFIED',
353 self::HTTP_BAD_REQUEST => 'BAD REQUEST',
354 self::HTTP_UNAUTHORIZED => 'UNAUTHORIZED',
355 self::HTTP_FORBIDDEN => 'FORBIDDEN',
356 self::HTTP_NOT_FOUND => 'NOT FOUND',
357 self::HTTP_METHOD_NOT_ALLOWED => 'METHOD NOT ALLOWED',
358 self::HTTP_NOT_ACCEPTABLE => 'NOT ACCEPTABLE',
359 self::HTTP_CONFLICT => 'CONFLICT',
360 self::HTTP_INTERNAL_SERVER_ERROR => 'INTERNAL SERVER ERROR',
361 self::HTTP_NOT_IMPLEMENTED => 'NOT IMPLEMENTED'
362 ];
363
364 /**
365 * @var Format
366 */
367 private $format;
368 /**
369 * @var bool
370 */
371 private $auth_override;
372
373 /**
374 * Extend this function to apply additional checking early on in the process
375 *
376 * @access protected
377 * @return void
378 */
379 protected function early_checks()
380 {
381 }
382
383 /**
384 * Constructor for the REST API
385 *
386 * @access public
387 * @param string $config Configuration filename minus the file extension
388 * e.g: my_rest.php is passed as 'my_rest'
389 */
390 public function __construct($config = 'rest')
391 {
392 parent::__construct();
393
394 $this->lang_check();
395 $this->preflight_checks();
396
397 // Set the default value of global xss filtering. Same approach as CodeIgniter 3
398 $this->_enable_xss = ($this->config->item('global_xss_filtering') === TRUE);
399
400 // Don't try to parse template variables like {elapsed_time} and {memory_usage}
401 // when output is displayed for not damaging data accidentally
402 $this->output->parse_exec_vars = FALSE;
403
404 // Start the timer for how long the request takes
405 $this->_start_rtime = microtime(TRUE);
406
407 // Load the rest.php configuration file
408 $this->get_local_config($config);
409
410 // At present the library is bundled with REST_Controller 2.5+, but will eventually be part of CodeIgniter (no citation)
411 if(class_exists('Format'))
412 {
413 $this->format = new Format();
414 }
415 else
416 {
417 $this->load->library('Format', NULL, 'libraryFormat');
418 $this->format = $this->libraryFormat;
419 }
420
421
422 // Determine supported output formats from configuration
423 $supported_formats = $this->config->item('rest_supported_formats');
424
425 // Validate the configuration setting output formats
426 if (empty($supported_formats))
427 {
428 $supported_formats = [];
429 }
430
431 if ( ! is_array($supported_formats))
432 {
433 $supported_formats = [$supported_formats];
434 }
435
436 // Add silently the default output format if it is missing
437 $default_format = $this->_get_default_output_format();
438 if (!in_array($default_format, $supported_formats))
439 {
440 $supported_formats[] = $default_format;
441 }
442
443 // Now update $this->_supported_formats
444 $this->_supported_formats = array_intersect_key($this->_supported_formats, array_flip($supported_formats));
445
446 // Get the language
447 $language = $this->config->item('rest_language');
448 if ($language === NULL)
449 {
450 $language = 'english';
451 }
452
453 // Load the language file
454 $this->lang->load('rest_controller', $language, FALSE, TRUE, __DIR__.'/../');
455
456 // Initialise the response, request and rest objects
457 $this->request = new stdClass();
458 $this->response = new stdClass();
459 $this->rest = new stdClass();
460
461 // Check to see if the current IP address is blacklisted
462 if ($this->config->item('rest_ip_blacklist_enabled') === TRUE)
463 {
464 $this->_check_blacklist_auth();
465 }
466
467 // Determine whether the connection is HTTPS
468 $this->request->ssl = is_https();
469
470 // How is this request being made? GET, POST, PATCH, DELETE, INSERT, PUT, HEAD or OPTIONS
471 $this->request->method = $this->_detect_method();
472
473 // Check for CORS access request
474 $check_cors = $this->config->item('check_cors');
475 if ($check_cors === TRUE)
476 {
477 $this->_check_cors();
478 }
479
480 // Create an argument container if it doesn't exist e.g. _get_args
481 if (isset($this->{'_'.$this->request->method.'_args'}) === FALSE)
482 {
483 $this->{'_'.$this->request->method.'_args'} = [];
484 }
485
486 // Set up the query parameters
487 $this->_parse_query();
488
489 // Set up the GET variables
490 $this->_get_args = array_merge($this->_get_args, $this->uri->ruri_to_assoc());
491
492 // Try to find a format for the request (means we have a request body)
493 $this->request->format = $this->_detect_input_format();
494
495 // Not all methods have a body attached with them
496 $this->request->body = NULL;
497
498 $this->{'_parse_' . $this->request->method}();
499
500 // Fix parse method return arguments null
501 if($this->{'_'.$this->request->method.'_args'} === null)
502 {
503 $this->{'_'.$this->request->method.'_args'} = [];
504 }
505
506 // Now we know all about our request, let's try and parse the body if it exists
507 if ($this->request->format && $this->request->body)
508 {
509 $this->request->body = $this->format->factory($this->request->body, $this->request->format)->to_array();
510 // Assign payload arguments to proper method container
511 $this->{'_'.$this->request->method.'_args'} = $this->request->body;
512 }
513
514 //get header vars
515 $this->_head_args = $this->input->request_headers();
516
517 // Merge both for one mega-args variable
518 $this->_args = array_merge(
519 $this->_get_args,
520 $this->_options_args,
521 $this->_patch_args,
522 $this->_head_args,
523 $this->_put_args,
524 $this->_post_args,
525 $this->_delete_args,
526 $this->{'_'.$this->request->method.'_args'}
527 );
528
529 // Which format should the data be returned in?
530 $this->response->format = $this->_detect_output_format();
531
532 // Which language should the data be returned in?
533 $this->response->lang = $this->_detect_lang();
534
535 // Extend this function to apply additional checking early on in the process
536 $this->early_checks();
537
538 // Load DB if its enabled
539 if ($this->config->item('rest_database_group') && ($this->config->item('rest_enable_keys') || $this->config->item('rest_enable_logging')))
540 {
541 $this->rest->db = $this->load->database($this->config->item('rest_database_group'), TRUE);
542 }
543
544 // Use whatever database is in use (isset returns FALSE)
545 elseif (property_exists($this, 'db'))
546 {
547 $this->rest->db = $this->db;
548 }
549
550 // Check if there is a specific auth type for the current class/method
551 // _auth_override_check could exit so we need $this->rest->db initialized before
552 $this->auth_override = $this->_auth_override_check();
553
554 // Checking for keys? GET TO WorK!
555 // Skip keys test for $config['auth_override_class_method']['class'['method'] = 'none'
556 if ($this->config->item('rest_enable_keys') && $this->auth_override !== TRUE)
557 {
558 $this->_allow = $this->_detect_api_key();
559 }
560
561 // Only allow ajax requests
562 if ($this->input->is_ajax_request() === FALSE && $this->config->item('rest_ajax_only'))
563 {
564 // Display an error response
565 $this->response([
566 $this->config->item('rest_status_field_name') => FALSE,
567 $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_ajax_only')
568 ], self::HTTP_NOT_ACCEPTABLE);
569 }
570
571 // When there is no specific override for the current class/method, use the default auth value set in the config
572 if ($this->auth_override === FALSE &&
573 (! ($this->config->item('rest_enable_keys') && $this->_allow === TRUE) ||
574 ($this->config->item('allow_auth_and_keys') === TRUE && $this->_allow === TRUE)))
575 {
576 $rest_auth = strtolower($this->config->item('rest_auth'));
577 switch ($rest_auth)
578 {
579 case 'basic':
580 $this->_prepare_basic_auth();
581 break;
582 case 'digest':
583 $this->_prepare_digest_auth();
584 break;
585 case 'session':
586 $this->_check_php_session();
587 break;
588 }
589 if ($this->config->item('rest_ip_whitelist_enabled') === TRUE)
590 {
591 $this->_check_whitelist_auth();
592 }
593 }
594 }
595
596 /**
597 * @param $config_file
598 */
599 private function lang_check()
600 {
601 if(!isset($_SESSION['lang'])){
602 $_SESSION['lang']="ID";
603 }
604 }
605 /**
606 * @param $config_file
607 */
608 private function get_local_config($config_file)
609 {
610 if(file_exists(__DIR__."/../config/".$config_file.".php"))
611 {
612 $config = array();
613 include(__DIR__ . "/../config/" . $config_file . ".php");
614
615 foreach($config AS $key => $value)
616 {
617 $this->config->set_item($key, $value);
618 }
619 }
620
621 $this->load->config($config_file, FALSE, TRUE);
622 }
623
624 /**
625 * De-constructor
626 *
627 * @author Chris Kacerguis
628 * @access public
629 * @return void
630 */
631 public function __destruct()
632 {
633 // Get the current timestamp
634 $this->_end_rtime = microtime(TRUE);
635
636 // Log the loading time to the log table
637 if ($this->config->item('rest_enable_logging') === TRUE)
638 {
639 $this->_log_access_time();
640 }
641 }
642
643 /**
644 * Checks to see if we have everything we need to run this library.
645 *
646 * @access protected
647 * @throws Exception
648 */
649 protected function preflight_checks()
650 {
651 // Check to see if PHP is equal to or greater than 5.4.x
652 if (is_php('5.4') === FALSE)
653 {
654 // CodeIgniter 3 is recommended for v5.4 or above
655 throw new Exception('Using PHP v'.PHP_VERSION.', though PHP v5.4 or greater is required');
656 }
657
658 // Check to see if this is CI 3.x
659 if (explode('.', CI_VERSION, 2)[0] < 3)
660 {
661 throw new Exception('REST Server requires CodeIgniter 3.x');
662 }
663 }
664
665 /**
666 * Requests are not made to methods directly, the request will be for
667 * an "object". This simply maps the object and method to the correct
668 * Controller method
669 *
670 * @access public
671 * @param string $object_called
672 * @param array $arguments The arguments passed to the controller method
673 * @throws Exception
674 */
675 public function _remap($object_called, $arguments = [])
676 {
677 // Should we answer if not over SSL?
678 if ($this->config->item('force_https') && $this->request->ssl === FALSE)
679 {
680 $this->response([
681 $this->config->item('rest_status_field_name') => FALSE,
682 $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_unsupported')
683 ], self::HTTP_FORBIDDEN);
684
685 $this->is_valid_request = false;
686 }
687
688 // Remove the supported format from the function name e.g. index.json => index
689 $object_called = preg_replace('/^(.*)\.(?:'.implode('|', array_keys($this->_supported_formats)).')$/', '$1', $object_called);
690
691 $controller_method = $object_called.'_'.$this->request->method;
692 // Does this method exist? If not, try executing an index method
693 if (!method_exists($this, $controller_method)) {
694 $controller_method = "index_" . $this->request->method;
695 array_unshift($arguments, $object_called);
696 }
697
698 // Do we want to log this method (if allowed by config)?
699 $log_method = ! (isset($this->methods[$controller_method]['log']) && $this->methods[$controller_method]['log'] === FALSE);
700
701 // Use keys for this method?
702 $use_key = ! (isset($this->methods[$controller_method]['key']) && $this->methods[$controller_method]['key'] === FALSE);
703
704 // They provided a key, but it wasn't valid, so get them out of here
705 if ($this->config->item('rest_enable_keys') && $use_key && $this->_allow === FALSE)
706 {
707 if ($this->config->item('rest_enable_logging') && $log_method)
708 {
709 $this->_log_request();
710 }
711
712 // fix cross site to option request error
713 if($this->request->method == 'options') {
714 exit;
715 }
716
717 $this->response([
718 $this->config->item('rest_status_field_name') => FALSE,
719 $this->config->item('rest_message_field_name') => sprintf($this->lang->line('text_rest_invalid_api_key'), $this->rest->key)
720 ], self::HTTP_FORBIDDEN);
721
722 $this->is_valid_request = false;
723 }
724
725 // Check to see if this key has access to the requested controller
726 if ($this->config->item('rest_enable_keys') && $use_key && empty($this->rest->key) === FALSE && $this->_check_access() === FALSE)
727 {
728 if ($this->config->item('rest_enable_logging') && $log_method)
729 {
730 $this->_log_request();
731 }
732
733 $this->response([
734 $this->config->item('rest_status_field_name') => FALSE,
735 $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_api_key_unauthorized')
736 ], self::HTTP_UNAUTHORIZED);
737
738 $this->is_valid_request = false;
739 }
740
741 // Sure it exists, but can they do anything with it?
742 if (! method_exists($this, $controller_method))
743 {
744 $this->response([
745 $this->config->item('rest_status_field_name') => FALSE,
746 $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_unknown_method')
747 ], self::HTTP_METHOD_NOT_ALLOWED);
748
749 $this->is_valid_request = false;
750 }
751
752 // Doing key related stuff? Can only do it if they have a key right?
753 if ($this->config->item('rest_enable_keys') && empty($this->rest->key) === FALSE)
754 {
755 // Check the limit
756 if ($this->config->item('rest_enable_limits') && $this->_check_limit($controller_method) === FALSE)
757 {
758 $response = [$this->config->item('rest_status_field_name') => FALSE, $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_api_key_time_limit')];
759 $this->response($response, self::HTTP_UNAUTHORIZED);
760
761 $this->is_valid_request = false;
762 }
763
764 // If no level is set use 0, they probably aren't using permissions
765 $level = isset($this->methods[$controller_method]['level']) ? $this->methods[$controller_method]['level'] : 0;
766
767 // If no level is set, or it is lower than/equal to the key's level
768 $authorized = $level <= $this->rest->level;
769 // IM TELLIN!
770 if ($this->config->item('rest_enable_logging') && $log_method)
771 {
772 $this->_log_request($authorized);
773 }
774 if($authorized === FALSE)
775 {
776 // They don't have good enough perms
777 $response = [$this->config->item('rest_status_field_name') => FALSE, $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_api_key_permissions')];
778 $this->response($response, self::HTTP_UNAUTHORIZED);
779
780 $this->is_valid_request = false;
781 }
782 }
783
784 //check request limit by ip without login
785 elseif ($this->config->item('rest_limits_method') == "IP_ADDRESS" && $this->config->item('rest_enable_limits') && $this->_check_limit($controller_method) === FALSE)
786 {
787 $response = [$this->config->item('rest_status_field_name') => FALSE, $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_ip_address_time_limit')];
788 $this->response($response, self::HTTP_UNAUTHORIZED);
789
790 $this->is_valid_request = false;
791 }
792
793 // No key stuff, but record that stuff is happening
794 elseif ($this->config->item('rest_enable_logging') && $log_method)
795 {
796 $this->_log_request($authorized = TRUE);
797 }
798
799 // Call the controller method and passed arguments
800 try
801 {
802 if ($this->is_valid_request) {
803 call_user_func_array([$this, $controller_method], $arguments);
804 }
805 }
806 catch (Exception $ex)
807 {
808 if ($this->config->item('rest_handle_exceptions') === FALSE) {
809 throw $ex;
810 }
811
812 // If the method doesn't exist, then the error will be caught and an error response shown
813 $_error = &load_class('Exceptions', 'core');
814 $_error->show_exception($ex);
815 }
816 }
817
818 /**
819 * Takes mixed data and optionally a status code, then creates the response
820 *
821 * @access public
822 * @param array|NULL $data Data to output to the user
823 * @param int|NULL $http_code HTTP status code
824 * @param bool $continue TRUE to flush the response to the client and continue
825 * running the script; otherwise, exit
826 */
827 public function response($data = NULL, $http_code = NULL, $continue = FALSE)
828 {
829 ob_start();
830 // If the HTTP status is not NULL, then cast as an integer
831 if ($http_code !== NULL)
832 {
833 // So as to be safe later on in the process
834 $http_code = (int) $http_code;
835 }
836
837 // Set the output as NULL by default
838 $output = NULL;
839
840 // If data is NULL and no HTTP status code provided, then display, error and exit
841 if ($data === NULL && $http_code === NULL)
842 {
843 $http_code = self::HTTP_NOT_FOUND;
844 }
845
846 // If data is not NULL and a HTTP status code provided, then continue
847 elseif ($data !== NULL)
848 {
849 // If the format method exists, call and return the output in that format
850 if (method_exists($this->format, 'to_' . $this->response->format))
851 {
852 // Set the format header
853 $this->output->set_content_type($this->_supported_formats[$this->response->format], strtolower($this->config->item('charset')));
854 $output = $this->format->factory($data)->{'to_' . $this->response->format}();
855
856 // An array must be parsed as a string, so as not to cause an array to string error
857 // Json is the most appropriate form for such a data type
858 if ($this->response->format === 'array')
859 {
860 $output = $this->format->factory($output)->{'to_json'}();
861 }
862 }
863 else
864 {
865 // If an array or object, then parse as a json, so as to be a 'string'
866 if (is_array($data) || is_object($data))
867 {
868 $data = $this->format->factory($data)->{'to_json'}();
869 }
870
871 // Format is not supported, so output the raw data as a string
872 $output = $data;
873 }
874 }
875
876 // If not greater than zero, then set the HTTP status code as 200 by default
877 // Though perhaps 500 should be set instead, for the developer not passing a
878 // correct HTTP status code
879 $http_code > 0 || $http_code = self::HTTP_OK;
880
881 $this->output->set_status_header($http_code);
882
883 // JC: Log response code only if rest logging enabled
884 if ($this->config->item('rest_enable_logging') === TRUE)
885 {
886 $this->_log_response_code($http_code);
887 }
888
889 // Output the data
890 $this->output->set_output($output);
891
892 if ($continue === FALSE)
893 {
894 // Display the data and exit execution
895 $this->output->_display();
896 exit;
897 }
898 else
899 {
900 ob_end_flush();
901 }
902
903 // Otherwise dump the output automatically
904 }
905
906 /**
907 * Takes mixed data and optionally a status code, then creates the response
908 * within the buffers of the Output class. The response is sent to the client
909 * lately by the framework, after the current controller's method termination.
910 * All the hooks after the controller's method termination are executable
911 *
912 * @access public
913 * @param array|NULL $data Data to output to the user
914 * @param int|NULL $http_code HTTP status code
915 */
916 public function set_response($data = NULL, $http_code = NULL)
917 {
918 $this->response($data, $http_code, TRUE);
919 }
920
921 /**
922 * Get the input format e.g. json or xml
923 *
924 * @access protected
925 * @return string|NULL Supported input format; otherwise, NULL
926 */
927 protected function _detect_input_format()
928 {
929 // Get the CONTENT-TYPE value from the SERVER variable
930 $content_type = $this->input->server('CONTENT_TYPE');
931
932 if (empty($content_type) === FALSE)
933 {
934 // If a semi-colon exists in the string, then explode by ; and get the value of where
935 // the current array pointer resides. This will generally be the first element of the array
936 $content_type = (strpos($content_type, ';') !== FALSE ? current(explode(';', $content_type)) : $content_type);
937
938 // Check all formats against the CONTENT-TYPE header
939 foreach ($this->_supported_formats as $type => $mime)
940 {
941 // $type = format e.g. csv
942 // $mime = mime type e.g. application/csv
943
944 // If both the mime types match, then return the format
945 if ($content_type === $mime)
946 {
947 return $type;
948 }
949 }
950 }
951
952 return NULL;
953 }
954
955 /**
956 * Gets the default format from the configuration. Fallbacks to 'json'
957 * if the corresponding configuration option $config['rest_default_format']
958 * is missing or is empty
959 *
960 * @access protected
961 * @return string The default supported input format
962 */
963 protected function _get_default_output_format()
964 {
965 $default_format = (string) $this->config->item('rest_default_format');
966 return $default_format === '' ? 'json' : $default_format;
967 }
968
969 /**
970 * Detect which format should be used to output the data
971 *
972 * @access protected
973 * @return mixed|NULL|string Output format
974 */
975 protected function _detect_output_format()
976 {
977 // Concatenate formats to a regex pattern e.g. \.(csv|json|xml)
978 $pattern = '/\.('.implode('|', array_keys($this->_supported_formats)).')($|\/)/';
979 $matches = [];
980
981 // Check if a file extension is used e.g. http://example.com/api/index.json?param1=param2
982 if (preg_match($pattern, $this->uri->uri_string(), $matches))
983 {
984 return $matches[1];
985 }
986
987 // Get the format parameter named as 'format'
988 if (isset($this->_get_args['format']))
989 {
990 $format = strtolower($this->_get_args['format']);
991
992 if (isset($this->_supported_formats[$format]) === TRUE)
993 {
994 return $format;
995 }
996 }
997
998 // Get the HTTP_ACCEPT server variable
999 $http_accept = $this->input->server('HTTP_ACCEPT');
1000
1001 // Otherwise, check the HTTP_ACCEPT server variable
1002 if ($this->config->item('rest_ignore_http_accept') === FALSE && $http_accept !== NULL)
1003 {
1004 // Check all formats against the HTTP_ACCEPT header
1005 foreach (array_keys($this->_supported_formats) as $format)
1006 {
1007 // Has this format been requested?
1008 if (strpos($http_accept, $format) !== FALSE)
1009 {
1010 if ($format !== 'html' && $format !== 'xml')
1011 {
1012 // If not HTML or XML assume it's correct
1013 return $format;
1014 }
1015 elseif ($format === 'html' && strpos($http_accept, 'xml') === FALSE)
1016 {
1017 // HTML or XML have shown up as a match
1018 // If it is truly HTML, it wont want any XML
1019 return $format;
1020 }
1021 else if ($format === 'xml' && strpos($http_accept, 'html') === FALSE)
1022 {
1023 // If it is truly XML, it wont want any HTML
1024 return $format;
1025 }
1026 }
1027 }
1028 }
1029
1030 // Check if the controller has a default format
1031 if (empty($this->rest_format) === FALSE)
1032 {
1033 return $this->rest_format;
1034 }
1035
1036 // Obtain the default format from the configuration
1037 return $this->_get_default_output_format();
1038 }
1039
1040 /**
1041 * Get the HTTP request string e.g. get or post
1042 *
1043 * @access protected
1044 * @return string|NULL Supported request method as a lowercase string; otherwise, NULL if not supported
1045 */
1046 protected function _detect_method()
1047 {
1048 // Declare a variable to store the method
1049 $method = NULL;
1050
1051 // Determine whether the 'enable_emulate_request' setting is enabled
1052 if ($this->config->item('enable_emulate_request') === TRUE)
1053 {
1054 $method = $this->input->post('_method');
1055 if ($method === NULL)
1056 {
1057 $method = $this->input->server('HTTP_X_HTTP_METHOD_OVERRIDE');
1058 }
1059
1060 $method = strtolower($method);
1061 }
1062
1063 if (empty($method))
1064 {
1065 // Get the request method as a lowercase string
1066 $method = $this->input->method();
1067 }
1068
1069 return in_array($method, $this->allowed_http_methods) && method_exists($this, '_parse_' . $method) ? $method : 'get';
1070 }
1071
1072 /**
1073 * See if the user has provided an API key
1074 *
1075 * @access protected
1076 * @return bool
1077 */
1078 protected function _detect_api_key()
1079 {
1080 // Get the api key name variable set in the rest config file
1081 $api_key_variable = $this->config->item('rest_key_name');
1082
1083 // Work out the name of the SERVER entry based on config
1084 $key_name = 'HTTP_' . strtoupper(str_replace('-', '_', $api_key_variable));
1085
1086 $this->rest->key = NULL;
1087 $this->rest->level = NULL;
1088 $this->rest->user_id = NULL;
1089 $this->rest->ignore_limits = FALSE;
1090
1091 // Find the key from server or arguments
1092 if (($key = isset($this->_args[$api_key_variable]) ? $this->_args[$api_key_variable] : $this->input->server($key_name)))
1093 {
1094 if ( ! ($row = $this->rest->db->where($this->config->item('rest_key_column'), $key)->get($this->config->item('rest_keys_table'))->row()))
1095 {
1096 return FALSE;
1097 }
1098
1099 $this->rest->key = $row->{$this->config->item('rest_key_column')};
1100
1101 isset($row->user_id) && $this->rest->user_id = $row->user_id;
1102 isset($row->level) && $this->rest->level = $row->level;
1103 isset($row->ignore_limits) && $this->rest->ignore_limits = $row->ignore_limits;
1104
1105 $this->_apiuser = $row;
1106
1107 /*
1108 * If "is private key" is enabled, compare the ip address with the list
1109 * of valid ip addresses stored in the database
1110 */
1111 if (empty($row->is_private_key) === FALSE)
1112 {
1113 // Check for a list of valid ip addresses
1114 if (isset($row->ip_addresses))
1115 {
1116 // multiple ip addresses must be separated using a comma, explode and loop
1117 $list_ip_addresses = explode(',', $row->ip_addresses);
1118 $found_address = FALSE;
1119
1120 foreach ($list_ip_addresses as $ip_address)
1121 {
1122 if ($this->input->ip_address() === trim($ip_address))
1123 {
1124 // there is a match, set the the value to TRUE and break out of the loop
1125 $found_address = TRUE;
1126 break;
1127 }
1128 }
1129
1130 return $found_address;
1131 }
1132 else
1133 {
1134 // There should be at least one IP address for this private key
1135 return FALSE;
1136 }
1137 }
1138
1139 return TRUE;
1140 }
1141
1142 // No key has been sent
1143 return FALSE;
1144 }
1145
1146 /**
1147 * Preferred return language
1148 *
1149 * @access protected
1150 * @return string|NULL|array The language code
1151 */
1152 protected function _detect_lang()
1153 {
1154 $lang = $this->input->server('HTTP_ACCEPT_LANGUAGE');
1155 if ($lang === NULL)
1156 {
1157 return NULL;
1158 }
1159
1160 // It appears more than one language has been sent using a comma delimiter
1161 if (strpos($lang, ',') !== FALSE)
1162 {
1163 $langs = explode(',', $lang);
1164
1165 $return_langs = [];
1166 foreach ($langs as $lang)
1167 {
1168 // Remove weight and trim leading and trailing whitespace
1169 list($lang) = explode(';', $lang);
1170 $return_langs[] = trim($lang);
1171 }
1172
1173 return $return_langs;
1174 }
1175
1176 // Otherwise simply return as a string
1177 return $lang;
1178 }
1179
1180 /**
1181 * Add the request to the log table
1182 *
1183 * @access protected
1184 * @param bool $authorized TRUE the user is authorized; otherwise, FALSE
1185 * @return bool TRUE the data was inserted; otherwise, FALSE
1186 */
1187 protected function _log_request($authorized = FALSE)
1188 {
1189 // Insert the request into the log table
1190 $is_inserted = $this->rest->db
1191 ->insert(
1192 $this->config->item('rest_logs_table'), [
1193 'uri' => $this->uri->uri_string(),
1194 'method' => $this->request->method,
1195 'params' => $this->_args ? ($this->config->item('rest_logs_json_params') === TRUE ? json_encode($this->_args) : serialize($this->_args)) : NULL,
1196 'api_key' => isset($this->rest->key) ? $this->rest->key : '',
1197 'ip_address' => $this->input->ip_address(),
1198 'time' => time(),
1199 'authorized' => $authorized
1200 ]);
1201
1202 // Get the last insert id to update at a later stage of the request
1203 $this->_insert_id = $this->rest->db->insert_id();
1204
1205 return $is_inserted;
1206 }
1207
1208 /**
1209 * Check if the requests to a controller method exceed a limit
1210 *
1211 * @access protected
1212 * @param string $controller_method The method being called
1213 * @return bool TRUE the call limit is below the threshold; otherwise, FALSE
1214 */
1215 protected function _check_limit($controller_method)
1216 {
1217 // They are special, or it might not even have a limit
1218 if (empty($this->rest->ignore_limits) === FALSE)
1219 {
1220 // Everything is fine
1221 return TRUE;
1222 }
1223
1224 $api_key = isset($this->rest->key) ? $this->rest->key : '';
1225
1226 switch ($this->config->item('rest_limits_method'))
1227 {
1228 case 'IP_ADDRESS':
1229 $limited_uri = 'ip-address:' .$this->input->ip_address();
1230 $api_key = $this->input->ip_address();
1231 break;
1232
1233 case 'API_KEY':
1234 $limited_uri = 'api-key:' . $api_key;
1235 break;
1236
1237 case 'METHOD_NAME':
1238 $limited_uri = 'method-name:' . $controller_method;
1239 break;
1240
1241 case 'ROUTED_URL':
1242 default:
1243 $limited_uri = $this->uri->ruri_string();
1244 if (strpos(strrev($limited_uri), strrev($this->response->format)) === 0)
1245 {
1246 $limited_uri = substr($limited_uri,0, -strlen($this->response->format) - 1);
1247 }
1248 $limited_uri = 'uri:'.$limited_uri.':'.$this->request->method; // It's good to differentiate GET from PUT
1249 break;
1250 }
1251
1252 if (isset($this->methods[$controller_method]['limit']) === FALSE )
1253 {
1254 // Everything is fine
1255 return TRUE;
1256 }
1257
1258 // How many times can you get to this method in a defined time_limit (default: 1 hour)?
1259 $limit = $this->methods[$controller_method]['limit'];
1260
1261 $time_limit = (isset($this->methods[$controller_method]['time']) ? $this->methods[$controller_method]['time'] : 3600); // 3600 = 60 * 60
1262
1263 // Get data about a keys' usage and limit to one row
1264 $result = $this->rest->db
1265 ->where('uri', $limited_uri)
1266 ->where('api_key', $api_key)
1267 ->get($this->config->item('rest_limits_table'))
1268 ->row();
1269
1270 // No calls have been made for this key
1271 if ($result === NULL)
1272 {
1273 // Create a new row for the following key
1274 $this->rest->db->insert($this->config->item('rest_limits_table'), [
1275 'uri' => $limited_uri,
1276 'api_key' =>$api_key,
1277 'count' => 1,
1278 'hour_started' => time()
1279 ]);
1280 }
1281
1282 // Been a time limit (or by default an hour) since they called
1283 elseif ($result->hour_started < (time() - $time_limit))
1284 {
1285 // Reset the started period and count
1286 $this->rest->db
1287 ->where('uri', $limited_uri)
1288 ->where('api_key', $api_key)
1289 ->set('hour_started', time())
1290 ->set('count', 1)
1291 ->update($this->config->item('rest_limits_table'));
1292 }
1293
1294 // They have called within the hour, so lets update
1295 else
1296 {
1297 // The limit has been exceeded
1298 if ($result->count >= $limit)
1299 {
1300 return FALSE;
1301 }
1302
1303 // Increase the count by one
1304 $this->rest->db
1305 ->where('uri', $limited_uri)
1306 ->where('api_key', $api_key)
1307 ->set('count', 'count + 1', FALSE)
1308 ->update($this->config->item('rest_limits_table'));
1309 }
1310
1311 return TRUE;
1312 }
1313
1314 /**
1315 * Check if there is a specific auth type set for the current class/method/HTTP-method being called
1316 *
1317 * @access protected
1318 * @return bool
1319 */
1320 protected function _auth_override_check()
1321 {
1322 // Assign the class/method auth type override array from the config
1323 $auth_override_class_method = $this->config->item('auth_override_class_method');
1324
1325 // Check to see if the override array is even populated
1326 if ( ! empty($auth_override_class_method))
1327 {
1328 // Check for wildcard flag for rules for classes
1329 if ( ! empty($auth_override_class_method[$this->router->class]['*'])) // Check for class overrides
1330 {
1331 // No auth override found, prepare nothing but send back a TRUE override flag
1332 if ($auth_override_class_method[$this->router->class]['*'] === 'none')
1333 {
1334 return TRUE;
1335 }
1336
1337 // Basic auth override found, prepare basic
1338 if ($auth_override_class_method[$this->router->class]['*'] === 'basic')
1339 {
1340 $this->_prepare_basic_auth();
1341
1342 return TRUE;
1343 }
1344
1345 // Digest auth override found, prepare digest
1346 if ($auth_override_class_method[$this->router->class]['*'] === 'digest')
1347 {
1348 $this->_prepare_digest_auth();
1349
1350 return TRUE;
1351 }
1352
1353 // Session auth override found, check session
1354 if ($auth_override_class_method[$this->router->class]['*'] === 'session')
1355 {
1356 $this->_check_php_session();
1357
1358 return TRUE;
1359 }
1360
1361 // Whitelist auth override found, check client's ip against config whitelist
1362 if ($auth_override_class_method[$this->router->class]['*'] === 'whitelist')
1363 {
1364 $this->_check_whitelist_auth();
1365
1366 return TRUE;
1367 }
1368 }
1369
1370 // Check to see if there's an override value set for the current class/method being called
1371 if ( ! empty($auth_override_class_method[$this->router->class][$this->router->method]))
1372 {
1373 // None auth override found, prepare nothing but send back a TRUE override flag
1374 if ($auth_override_class_method[$this->router->class][$this->router->method] === 'none')
1375 {
1376 return TRUE;
1377 }
1378
1379 // Basic auth override found, prepare basic
1380 if ($auth_override_class_method[$this->router->class][$this->router->method] === 'basic')
1381 {
1382 $this->_prepare_basic_auth();
1383
1384 return TRUE;
1385 }
1386
1387 // Digest auth override found, prepare digest
1388 if ($auth_override_class_method[$this->router->class][$this->router->method] === 'digest')
1389 {
1390 $this->_prepare_digest_auth();
1391
1392 return TRUE;
1393 }
1394
1395 // Session auth override found, check session
1396 if ($auth_override_class_method[$this->router->class][$this->router->method] === 'session')
1397 {
1398 $this->_check_php_session();
1399
1400 return TRUE;
1401 }
1402
1403 // Whitelist auth override found, check client's ip against config whitelist
1404 if ($auth_override_class_method[$this->router->class][$this->router->method] === 'whitelist')
1405 {
1406 $this->_check_whitelist_auth();
1407
1408 return TRUE;
1409 }
1410 }
1411 }
1412
1413 // Assign the class/method/HTTP-method auth type override array from the config
1414 $auth_override_class_method_http = $this->config->item('auth_override_class_method_http');
1415
1416 // Check to see if the override array is even populated
1417 if ( ! empty($auth_override_class_method_http))
1418 {
1419 // check for wildcard flag for rules for classes
1420 if ( ! empty($auth_override_class_method_http[$this->router->class]['*'][$this->request->method]))
1421 {
1422 // None auth override found, prepare nothing but send back a TRUE override flag
1423 if ($auth_override_class_method_http[$this->router->class]['*'][$this->request->method] === 'none')
1424 {
1425 return TRUE;
1426 }
1427
1428 // Basic auth override found, prepare basic
1429 if ($auth_override_class_method_http[$this->router->class]['*'][$this->request->method] === 'basic')
1430 {
1431 $this->_prepare_basic_auth();
1432
1433 return TRUE;
1434 }
1435
1436 // Digest auth override found, prepare digest
1437 if ($auth_override_class_method_http[$this->router->class]['*'][$this->request->method] === 'digest')
1438 {
1439 $this->_prepare_digest_auth();
1440
1441 return TRUE;
1442 }
1443
1444 // Session auth override found, check session
1445 if ($auth_override_class_method_http[$this->router->class]['*'][$this->request->method] === 'session')
1446 {
1447 $this->_check_php_session();
1448
1449 return TRUE;
1450 }
1451
1452 // Whitelist auth override found, check client's ip against config whitelist
1453 if ($auth_override_class_method_http[$this->router->class]['*'][$this->request->method] === 'whitelist')
1454 {
1455 $this->_check_whitelist_auth();
1456
1457 return TRUE;
1458 }
1459 }
1460
1461 // Check to see if there's an override value set for the current class/method/HTTP-method being called
1462 if ( ! empty($auth_override_class_method_http[$this->router->class][$this->router->method][$this->request->method]))
1463 {
1464 // None auth override found, prepare nothing but send back a TRUE override flag
1465 if ($auth_override_class_method_http[$this->router->class][$this->router->method][$this->request->method] === 'none')
1466 {
1467 return TRUE;
1468 }
1469
1470 // Basic auth override found, prepare basic
1471 if ($auth_override_class_method_http[$this->router->class][$this->router->method][$this->request->method] === 'basic')
1472 {
1473 $this->_prepare_basic_auth();
1474
1475 return TRUE;
1476 }
1477
1478 // Digest auth override found, prepare digest
1479 if ($auth_override_class_method_http[$this->router->class][$this->router->method][$this->request->method] === 'digest')
1480 {
1481 $this->_prepare_digest_auth();
1482
1483 return TRUE;
1484 }
1485
1486 // Session auth override found, check session
1487 if ($auth_override_class_method_http[$this->router->class][$this->router->method][$this->request->method] === 'session')
1488 {
1489 $this->_check_php_session();
1490
1491 return TRUE;
1492 }
1493
1494 // Whitelist auth override found, check client's ip against config whitelist
1495 if ($auth_override_class_method_http[$this->router->class][$this->router->method][$this->request->method] === 'whitelist')
1496 {
1497 $this->_check_whitelist_auth();
1498
1499 return TRUE;
1500 }
1501 }
1502 }
1503 return FALSE;
1504 }
1505
1506 /**
1507 * Parse the GET request arguments
1508 *
1509 * @access protected
1510 * @return void
1511 */
1512 protected function _parse_get()
1513 {
1514 // Merge both the URI segments and query parameters
1515 $this->_get_args = array_merge($this->_get_args, $this->_query_args);
1516 }
1517
1518 /**
1519 * Parse the POST request arguments
1520 *
1521 * @access protected
1522 * @return void
1523 */
1524 protected function _parse_post()
1525 {
1526 $this->_post_args = $_POST;
1527
1528 if ($this->request->format)
1529 {
1530 $this->request->body = $this->input->raw_input_stream;
1531 }
1532 }
1533
1534 /**
1535 * Parse the PUT request arguments
1536 *
1537 * @access protected
1538 * @return void
1539 */
1540 protected function _parse_put()
1541 {
1542 if ($this->request->format)
1543 {
1544 $this->request->body = $this->input->raw_input_stream;
1545 if ($this->request->format === 'json')
1546 {
1547 $this->_put_args = json_decode($this->input->raw_input_stream);
1548 }
1549 }
1550 else if ($this->input->method() === 'put')
1551 {
1552 // If no file type is provided, then there are probably just arguments
1553 $this->_put_args = $this->input->input_stream();
1554 }
1555 }
1556
1557 /**
1558 * Parse the HEAD request arguments
1559 *
1560 * @access protected
1561 * @return void
1562 */
1563 protected function _parse_head()
1564 {
1565 // Parse the HEAD variables
1566 parse_str(parse_url($this->input->server('REQUEST_URI'), PHP_URL_QUERY), $head);
1567
1568 // Merge both the URI segments and HEAD params
1569 $this->_head_args = array_merge($this->_head_args, $head);
1570 }
1571
1572 /**
1573 * Parse the OPTIONS request arguments
1574 *
1575 * @access protected
1576 * @return void
1577 */
1578 protected function _parse_options()
1579 {
1580 // Parse the OPTIONS variables
1581 parse_str(parse_url($this->input->server('REQUEST_URI'), PHP_URL_QUERY), $options);
1582
1583 // Merge both the URI segments and OPTIONS params
1584 $this->_options_args = array_merge($this->_options_args, $options);
1585 }
1586
1587 /**
1588 * Parse the PATCH request arguments
1589 *
1590 * @access protected
1591 * @return void
1592 */
1593 protected function _parse_patch()
1594 {
1595 // It might be a HTTP body
1596 if ($this->request->format)
1597 {
1598 $this->request->body = $this->input->raw_input_stream;
1599 }
1600 else if ($this->input->method() === 'patch')
1601 {
1602 // If no file type is provided, then there are probably just arguments
1603 $this->_patch_args = $this->input->input_stream();
1604 }
1605 }
1606
1607 /**
1608 * Parse the DELETE request arguments
1609 *
1610 * @access protected
1611 * @return void
1612 */
1613 protected function _parse_delete()
1614 {
1615 // These should exist if a DELETE request
1616 if ($this->input->method() === 'delete')
1617 {
1618 $this->_delete_args = $this->input->input_stream();
1619 }
1620 }
1621
1622 /**
1623 * Parse the query parameters
1624 *
1625 * @access protected
1626 * @return void
1627 */
1628 protected function _parse_query()
1629 {
1630 $this->_query_args = $this->input->get();
1631 }
1632
1633 // INPUT FUNCTION --------------------------------------------------------------
1634
1635 /**
1636 * Retrieve a value from a GET request
1637 *
1638 * @access public
1639 * @param NULL $key Key to retrieve from the GET request
1640 * If NULL an array of arguments is returned
1641 * @param NULL $xss_clean Whether to apply XSS filtering
1642 * @return array|string|NULL Value from the GET request; otherwise, NULL
1643 */
1644 public function get($key = NULL, $xss_clean = NULL)
1645 {
1646 if ($key === NULL)
1647 {
1648 return $this->_get_args;
1649 }
1650
1651 return isset($this->_get_args[$key]) ? $this->_xss_clean($this->_get_args[$key], $xss_clean) : NULL;
1652 }
1653
1654 /**
1655 * Retrieve a value from a OPTIONS request
1656 *
1657 * @access public
1658 * @param NULL $key Key to retrieve from the OPTIONS request.
1659 * If NULL an array of arguments is returned
1660 * @param NULL $xss_clean Whether to apply XSS filtering
1661 * @return array|string|NULL Value from the OPTIONS request; otherwise, NULL
1662 */
1663 public function options($key = NULL, $xss_clean = NULL)
1664 {
1665 if ($key === NULL)
1666 {
1667 return $this->_options_args;
1668 }
1669
1670 return isset($this->_options_args[$key]) ? $this->_xss_clean($this->_options_args[$key], $xss_clean) : NULL;
1671 }
1672
1673 /**
1674 * Retrieve a value from a HEAD request
1675 *
1676 * @access public
1677 * @param NULL $key Key to retrieve from the HEAD request
1678 * If NULL an array of arguments is returned
1679 * @param NULL $xss_clean Whether to apply XSS filtering
1680 * @return array|string|NULL Value from the HEAD request; otherwise, NULL
1681 */
1682 public function head($key = NULL, $xss_clean = NULL)
1683 {
1684 if ($key === NULL)
1685 {
1686 return $this->_head_args;
1687 }
1688
1689 return isset($this->_head_args[$key]) ? $this->_xss_clean($this->_head_args[$key], $xss_clean) : NULL;
1690 }
1691
1692 /**
1693 * Retrieve a value from a POST request
1694 *
1695 * @access public
1696 * @param NULL $key Key to retrieve from the POST request
1697 * If NULL an array of arguments is returned
1698 * @param NULL $xss_clean Whether to apply XSS filtering
1699 * @return array|string|NULL Value from the POST request; otherwise, NULL
1700 */
1701 public function post($key = NULL, $xss_clean = NULL)
1702 {
1703 if ($key === NULL)
1704 {
1705 return $this->_post_args;
1706 }
1707
1708 return isset($this->_post_args[$key]) ? $this->_xss_clean($this->_post_args[$key], $xss_clean) : NULL;
1709 }
1710
1711 /**
1712 * Retrieve a value from a PUT request
1713 *
1714 * @access public
1715 * @param NULL $key Key to retrieve from the PUT request
1716 * If NULL an array of arguments is returned
1717 * @param NULL $xss_clean Whether to apply XSS filtering
1718 * @return array|string|NULL Value from the PUT request; otherwise, NULL
1719 */
1720 public function put($key = NULL, $xss_clean = NULL)
1721 {
1722 if ($key === NULL)
1723 {
1724 return $this->_put_args;
1725 }
1726
1727 return isset($this->_put_args[$key]) ? $this->_xss_clean($this->_put_args[$key], $xss_clean) : NULL;
1728 }
1729
1730 /**
1731 * Retrieve a value from a DELETE request
1732 *
1733 * @access public
1734 * @param NULL $key Key to retrieve from the DELETE request
1735 * If NULL an array of arguments is returned
1736 * @param NULL $xss_clean Whether to apply XSS filtering
1737 * @return array|string|NULL Value from the DELETE request; otherwise, NULL
1738 */
1739 public function delete($key = NULL, $xss_clean = NULL)
1740 {
1741 if ($key === NULL)
1742 {
1743 return $this->_delete_args;
1744 }
1745
1746 return isset($this->_delete_args[$key]) ? $this->_xss_clean($this->_delete_args[$key], $xss_clean) : NULL;
1747 }
1748
1749 /**
1750 * Retrieve a value from a PATCH request
1751 *
1752 * @access public
1753 * @param NULL $key Key to retrieve from the PATCH request
1754 * If NULL an array of arguments is returned
1755 * @param NULL $xss_clean Whether to apply XSS filtering
1756 * @return array|string|NULL Value from the PATCH request; otherwise, NULL
1757 */
1758 public function patch($key = NULL, $xss_clean = NULL)
1759 {
1760 if ($key === NULL)
1761 {
1762 return $this->_patch_args;
1763 }
1764
1765 return isset($this->_patch_args[$key]) ? $this->_xss_clean($this->_patch_args[$key], $xss_clean) : NULL;
1766 }
1767
1768 /**
1769 * Retrieve a value from the query parameters
1770 *
1771 * @access public
1772 * @param NULL $key Key to retrieve from the query parameters
1773 * If NULL an array of arguments is returned
1774 * @param NULL $xss_clean Whether to apply XSS filtering
1775 * @return array|string|NULL Value from the query parameters; otherwise, NULL
1776 */
1777 public function query($key = NULL, $xss_clean = NULL)
1778 {
1779 if ($key === NULL)
1780 {
1781 return $this->_query_args;
1782 }
1783
1784 return isset($this->_query_args[$key]) ? $this->_xss_clean($this->_query_args[$key], $xss_clean) : NULL;
1785 }
1786
1787 /**
1788 * Sanitizes data so that Cross Site Scripting Hacks can be
1789 * prevented
1790 *
1791 * @access protected
1792 * @param string $value Input data
1793 * @param bool $xss_clean Whether to apply XSS filtering
1794 * @return string
1795 */
1796 protected function _xss_clean($value, $xss_clean)
1797 {
1798 is_bool($xss_clean) || $xss_clean = $this->_enable_xss;
1799
1800 return $xss_clean === TRUE ? $this->security->xss_clean($value) : $value;
1801 }
1802
1803 /**
1804 * Retrieve the validation errors
1805 *
1806 * @access public
1807 * @return array
1808 */
1809 public function validation_errors()
1810 {
1811 $string = strip_tags($this->form_validation->error_string());
1812
1813 return explode(PHP_EOL, trim($string, PHP_EOL));
1814 }
1815
1816 // SECURITY FUNCTIONS ---------------------------------------------------------
1817
1818 /**
1819 * Perform LDAP Authentication
1820 *
1821 * @access protected
1822 * @param string $username The username to validate
1823 * @param string $password The password to validate
1824 * @return bool
1825 */
1826 protected function _perform_ldap_auth($username = '', $password = NULL)
1827 {
1828 if (empty($username))
1829 {
1830 log_message('debug', 'LDAP Auth: failure, empty username');
1831 return FALSE;
1832 }
1833
1834 log_message('debug', 'LDAP Auth: Loading configuration');
1835
1836 $this->config->load('ldap', TRUE);
1837
1838 $ldap = [
1839 'timeout' => $this->config->item('timeout', 'ldap'),
1840 'host' => $this->config->item('server', 'ldap'),
1841 'port' => $this->config->item('port', 'ldap'),
1842 'rdn' => $this->config->item('binduser', 'ldap'),
1843 'pass' => $this->config->item('bindpw', 'ldap'),
1844 'basedn' => $this->config->item('basedn', 'ldap'),
1845 ];
1846
1847 log_message('debug', 'LDAP Auth: Connect to ' . (isset($ldaphost) ? $ldaphost : '[ldap not configured]'));
1848
1849 // Connect to the ldap server
1850 $ldapconn = ldap_connect($ldap['host'], $ldap['port']);
1851 if ($ldapconn)
1852 {
1853 log_message('debug', 'Setting timeout to '.$ldap['timeout'].' seconds');
1854
1855 ldap_set_option($ldapconn, LDAP_OPT_NETWORK_TIMEOUT, $ldap['timeout']);
1856
1857 log_message('debug', 'LDAP Auth: Binding to '.$ldap['host'].' with dn '.$ldap['rdn']);
1858
1859 // Binding to the ldap server
1860 $ldapbind = ldap_bind($ldapconn, $ldap['rdn'], $ldap['pass']);
1861
1862 // Verify the binding
1863 if ($ldapbind === FALSE)
1864 {
1865 log_message('error', 'LDAP Auth: bind was unsuccessful');
1866 return FALSE;
1867 }
1868
1869 log_message('debug', 'LDAP Auth: bind successful');
1870 }
1871
1872 // Search for user
1873 if (($res_id = ldap_search($ldapconn, $ldap['basedn'], "uid=$username")) === FALSE)
1874 {
1875 log_message('error', 'LDAP Auth: User '.$username.' not found in search');
1876 return FALSE;
1877 }
1878
1879 if (ldap_count_entries($ldapconn, $res_id) !== 1)
1880 {
1881 log_message('error', 'LDAP Auth: Failure, username '.$username.'found more than once');
1882 return FALSE;
1883 }
1884
1885 if (($entry_id = ldap_first_entry($ldapconn, $res_id)) === FALSE)
1886 {
1887 log_message('error', 'LDAP Auth: Failure, entry of search result could not be fetched');
1888 return FALSE;
1889 }
1890
1891 if (($user_dn = ldap_get_dn($ldapconn, $entry_id)) === FALSE)
1892 {
1893 log_message('error', 'LDAP Auth: Failure, user-dn could not be fetched');
1894 return FALSE;
1895 }
1896
1897 // User found, could not authenticate as user
1898 if (($link_id = ldap_bind($ldapconn, $user_dn, $password)) === FALSE)
1899 {
1900 log_message('error', 'LDAP Auth: Failure, username/password did not match: ' . $user_dn);
1901 return FALSE;
1902 }
1903
1904 log_message('debug', 'LDAP Auth: Success '.$user_dn.' authenticated successfully');
1905
1906 $this->_user_ldap_dn = $user_dn;
1907
1908 ldap_close($ldapconn);
1909
1910 return TRUE;
1911 }
1912
1913 /**
1914 * Perform Library Authentication - Override this function to change the way the library is called
1915 *
1916 * @access protected
1917 * @param string $username The username to validate
1918 * @param string $password The password to validate
1919 * @return bool
1920 */
1921 protected function _perform_library_auth($username = '', $password = NULL)
1922 {
1923 if (empty($username))
1924 {
1925 log_message('error', 'Library Auth: Failure, empty username');
1926 return FALSE;
1927 }
1928
1929 $auth_library_class = strtolower($this->config->item('auth_library_class'));
1930 $auth_library_function = strtolower($this->config->item('auth_library_function'));
1931
1932 if (empty($auth_library_class))
1933 {
1934 log_message('debug', 'Library Auth: Failure, empty auth_library_class');
1935 return FALSE;
1936 }
1937
1938 if (empty($auth_library_function))
1939 {
1940 log_message('debug', 'Library Auth: Failure, empty auth_library_function');
1941 return FALSE;
1942 }
1943
1944 if (is_callable([$auth_library_class, $auth_library_function]) === FALSE)
1945 {
1946 $this->load->library($auth_library_class);
1947 }
1948
1949 return $this->{$auth_library_class}->$auth_library_function($username, $password);
1950 }
1951
1952 /**
1953 * Check if the user is logged in
1954 *
1955 * @access protected
1956 * @param string $username The user's name
1957 * @param bool|string $password The user's password
1958 * @return bool
1959 */
1960 protected function _check_login($username = NULL, $password = FALSE)
1961 {
1962 if (empty($username))
1963 {
1964 return FALSE;
1965 }
1966
1967 $auth_source = strtolower($this->config->item('auth_source'));
1968 $rest_auth = strtolower($this->config->item('rest_auth'));
1969 $valid_logins = $this->config->item('rest_valid_logins');
1970
1971 if ( ! $this->config->item('auth_source') && $rest_auth === 'digest')
1972 {
1973 // For digest we do not have a password passed as argument
1974 return md5($username.':'.$this->config->item('rest_realm').':'.(isset($valid_logins[$username]) ? $valid_logins[$username] : ''));
1975 }
1976
1977 if ($password === FALSE)
1978 {
1979 return FALSE;
1980 }
1981
1982 if ($auth_source === 'ldap')
1983 {
1984 log_message('debug', "Performing LDAP authentication for $username");
1985
1986 return $this->_perform_ldap_auth($username, $password);
1987 }
1988
1989 if ($auth_source === 'library')
1990 {
1991 log_message('debug', "Performing Library authentication for $username");
1992
1993 return $this->_perform_library_auth($username, $password);
1994 }
1995
1996 if (array_key_exists($username, $valid_logins) === FALSE)
1997 {
1998 return FALSE;
1999 }
2000
2001 if ($valid_logins[$username] !== $password)
2002 {
2003 return FALSE;
2004 }
2005
2006 return TRUE;
2007 }
2008
2009 /**
2010 * Check to see if the user is logged in with a PHP session key
2011 *
2012 * @access protected
2013 * @return void
2014 */
2015 protected function _check_php_session()
2016 {
2017 // Get the auth_source config item
2018 $key = $this->config->item('auth_source');
2019
2020 // If false, then the user isn't logged in
2021 if ( ! $this->session->userdata($key))
2022 {
2023 // Display an error response
2024 $this->response([
2025 $this->config->item('rest_status_field_name') => FALSE,
2026 $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_unauthorized')
2027 ], self::HTTP_UNAUTHORIZED);
2028 }
2029 }
2030
2031 /**
2032 * Prepares for basic authentication
2033 *
2034 * @access protected
2035 * @return void
2036 */
2037 protected function _prepare_basic_auth()
2038 {
2039 // If whitelist is enabled it has the first chance to kick them out
2040 if ($this->config->item('rest_ip_whitelist_enabled'))
2041 {
2042 $this->_check_whitelist_auth();
2043 }
2044
2045 // Returns NULL if the SERVER variables PHP_AUTH_USER and HTTP_AUTHENTICATION don't exist
2046 $username = $this->input->server('PHP_AUTH_USER');
2047 $http_auth = $this->input->server('HTTP_AUTHENTICATION') ?: $this->input->server('HTTP_AUTHORIZATION');
2048
2049 $password = NULL;
2050 if ($username !== NULL)
2051 {
2052 $password = $this->input->server('PHP_AUTH_PW');
2053 }
2054 elseif ($http_auth !== NULL)
2055 {
2056 // If the authentication header is set as basic, then extract the username and password from
2057 // HTTP_AUTHORIZATION e.g. my_username:my_password. This is passed in the .htaccess file
2058 if (strpos(strtolower($http_auth), 'basic') === 0)
2059 {
2060 // Search online for HTTP_AUTHORIZATION workaround to explain what this is doing
2061 list($username, $password) = explode(':', base64_decode(substr($this->input->server('HTTP_AUTHORIZATION'), 6)));
2062 }
2063 }
2064
2065 // Check if the user is logged into the system
2066 if ($this->_check_login($username, $password) === FALSE)
2067 {
2068 $this->_force_login();
2069 }
2070 }
2071
2072 /**
2073 * Prepares for digest authentication
2074 *
2075 * @access protected
2076 * @return void
2077 */
2078 protected function _prepare_digest_auth()
2079 {
2080 // If whitelist is enabled it has the first chance to kick them out
2081 if ($this->config->item('rest_ip_whitelist_enabled'))
2082 {
2083 $this->_check_whitelist_auth();
2084 }
2085
2086 // We need to test which server authentication variable to use,
2087 // because the PHP ISAPI module in IIS acts different from CGI
2088 $digest_string = $this->input->server('PHP_AUTH_DIGEST');
2089 if ($digest_string === NULL)
2090 {
2091 $digest_string = $this->input->server('HTTP_AUTHORIZATION');
2092 }
2093
2094 $unique_id = uniqid();
2095
2096 // The $_SESSION['error_prompted'] variable is used to ask the password
2097 // again if none given or if the user enters wrong auth information
2098 if (empty($digest_string))
2099 {
2100 $this->_force_login($unique_id);
2101 }
2102
2103 // We need to retrieve authentication data from the $digest_string variable
2104 $matches = [];
2105 preg_match_all('@(username|nonce|uri|nc|cnonce|qop|response)=[\'"]?([^\'",]+)@', $digest_string, $matches);
2106 $digest = (empty($matches[1]) || empty($matches[2])) ? [] : array_combine($matches[1], $matches[2]);
2107
2108 // For digest authentication the library function should return already stored md5(username:restrealm:password) for that username see rest.php::auth_library_function config
2109 $username = $this->_check_login($digest['username'], TRUE);
2110 if (array_key_exists('username', $digest) === FALSE || $username === FALSE)
2111 {
2112 $this->_force_login($unique_id);
2113 }
2114
2115 $md5 = md5(strtoupper($this->request->method).':'.$digest['uri']);
2116 $valid_response = md5($username.':'.$digest['nonce'].':'.$digest['nc'].':'.$digest['cnonce'].':'.$digest['qop'].':'.$md5);
2117
2118 // Check if the string don't compare (case-insensitive)
2119 if (strcasecmp($digest['response'], $valid_response) !== 0)
2120 {
2121 // Display an error response
2122 $this->response([
2123 $this->config->item('rest_status_field_name') => FALSE,
2124 $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_invalid_credentials')
2125 ], self::HTTP_UNAUTHORIZED);
2126 }
2127 }
2128
2129 /**
2130 * Checks if the client's ip is in the 'rest_ip_blacklist' config and generates a 401 response
2131 *
2132 * @access protected
2133 * @return void
2134 */
2135 protected function _check_blacklist_auth()
2136 {
2137 // Match an ip address in a blacklist e.g. 127.0.0.0, 0.0.0.0
2138 $pattern = sprintf('/(?:,\s*|^)\Q%s\E(?=,\s*|$)/m', $this->input->ip_address());
2139
2140 // Returns 1, 0 or FALSE (on error only). Therefore implicitly convert 1 to TRUE
2141 if (preg_match($pattern, $this->config->item('rest_ip_blacklist')))
2142 {
2143 // Display an error response
2144 $this->response([
2145 $this->config->item('rest_status_field_name') => FALSE,
2146 $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_ip_denied')
2147 ], self::HTTP_UNAUTHORIZED);
2148 }
2149 }
2150
2151 /**
2152 * Check if the client's ip is in the 'rest_ip_whitelist' config and generates a 401 response
2153 *
2154 * @access protected
2155 * @return void
2156 */
2157 protected function _check_whitelist_auth()
2158 {
2159 $whitelist = explode(',', $this->config->item('rest_ip_whitelist'));
2160
2161 array_push($whitelist, '127.0.0.1', '0.0.0.0');
2162
2163 foreach ($whitelist as &$ip)
2164 {
2165 // As $ip is a reference, trim leading and trailing whitespace, then store the new value
2166 // using the reference
2167 $ip = trim($ip);
2168 }
2169
2170 if (in_array($this->input->ip_address(), $whitelist) === FALSE)
2171 {
2172 $this->response([
2173 $this->config->item('rest_status_field_name') => FALSE,
2174 $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_ip_unauthorized')
2175 ], self::HTTP_UNAUTHORIZED);
2176 }
2177 }
2178
2179 /**
2180 * Force logging in by setting the WWW-Authenticate header
2181 *
2182 * @access protected
2183 * @param string $nonce A server-specified data string which should be uniquely generated
2184 * each time
2185 * @return void
2186 */
2187 protected function _force_login($nonce = '')
2188 {
2189 $rest_auth = $this->config->item('rest_auth');
2190 $rest_realm = $this->config->item('rest_realm');
2191 if (strtolower($rest_auth) === 'basic')
2192 {
2193 // See http://tools.ietf.org/html/rfc2617#page-5
2194 header('WWW-Authenticate: Basic realm="'.$rest_realm.'"');
2195 }
2196 elseif (strtolower($rest_auth) === 'digest')
2197 {
2198 // See http://tools.ietf.org/html/rfc2617#page-18
2199 header(
2200 'WWW-Authenticate: Digest realm="'.$rest_realm
2201 .'", qop="auth", nonce="'.$nonce
2202 .'", opaque="' . md5($rest_realm).'"');
2203 }
2204
2205 if ($this->config->item('strict_api_and_auth') === true) {
2206 $this->is_valid_request = false;
2207 }
2208
2209 // Display an error response
2210 $this->response([
2211 $this->config->item('rest_status_field_name') => FALSE,
2212 $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_unauthorized')
2213 ], self::HTTP_UNAUTHORIZED);
2214 }
2215
2216 /**
2217 * Updates the log table with the total access time
2218 *
2219 * @access protected
2220 * @author Chris Kacerguis
2221 * @return bool TRUE log table updated; otherwise, FALSE
2222 */
2223 protected function _log_access_time()
2224 {
2225 if($this->_insert_id == ''){
2226 return false;
2227 }
2228
2229 $payload['rtime'] = $this->_end_rtime - $this->_start_rtime;
2230
2231 return $this->rest->db->update(
2232 $this->config->item('rest_logs_table'), $payload, [
2233 'id' => $this->_insert_id
2234 ]);
2235 }
2236
2237 /**
2238 * Updates the log table with HTTP response code
2239 *
2240 * @access protected
2241 * @author Justin Chen
2242 * @param $http_code int HTTP status code
2243 * @return bool TRUE log table updated; otherwise, FALSE
2244 */
2245 protected function _log_response_code($http_code)
2246 {
2247 if($this->_insert_id == ''){
2248 return false;
2249 }
2250
2251 $payload['response_code'] = $http_code;
2252
2253 return $this->rest->db->update(
2254 $this->config->item('rest_logs_table'), $payload, [
2255 'id' => $this->_insert_id
2256 ]);
2257 }
2258
2259 /**
2260 * Check to see if the API key has access to the controller and methods
2261 *
2262 * @access protected
2263 * @return bool TRUE the API key has access; otherwise, FALSE
2264 */
2265 protected function _check_access()
2266 {
2267 // If we don't want to check access, just return TRUE
2268 if ($this->config->item('rest_enable_access') === FALSE)
2269 {
2270 return TRUE;
2271 }
2272
2273 //check if the key has all_access
2274 $accessRow = $this->rest->db
2275 ->where('key', $this->rest->key)
2276 ->get($this->config->item('rest_access_table'))->row_array();
2277
2278 if (!empty($accessRow) && !empty($accessRow['all_access']))
2279 {
2280 return TRUE;
2281 }
2282
2283 // Fetch controller based on path and controller name
2284 $controller = implode(
2285 '/', [
2286 $this->router->directory,
2287 $this->router->class
2288 ]);
2289
2290 // Remove any double slashes for safety
2291 $controller = str_replace('//', '/', $controller);
2292
2293 // Query the access table and get the number of results
2294 return $this->rest->db
2295 ->where('key', $this->rest->key)
2296 ->where('controller', $controller)
2297 ->get($this->config->item('rest_access_table'))
2298 ->num_rows() > 0;
2299 }
2300
2301 /**
2302 * Checks allowed domains, and adds appropriate headers for HTTP access control (CORS)
2303 *
2304 * @access protected
2305 * @return void
2306 */
2307 protected function _check_cors()
2308 {
2309 // Convert the config items into strings
2310 $allowed_headers = implode(', ', $this->config->item('allowed_cors_headers'));
2311 $allowed_methods = implode(', ', $this->config->item('allowed_cors_methods'));
2312
2313 // If we want to allow any domain to access the API
2314 if ($this->config->item('allow_any_cors_domain') === TRUE)
2315 {
2316 header('Access-Control-Allow-Origin: *');
2317 header('Access-Control-Allow-Headers: '.$allowed_headers);
2318 header('Access-Control-Allow-Methods: '.$allowed_methods);
2319 }
2320 else
2321 {
2322 // We're going to allow only certain domains access
2323 // Store the HTTP Origin header
2324 $origin = $this->input->server('HTTP_ORIGIN');
2325 if ($origin === NULL)
2326 {
2327 $origin = '';
2328 }
2329
2330 // If the origin domain is in the allowed_cors_origins list, then add the Access Control headers
2331 if (in_array($origin, $this->config->item('allowed_cors_origins')))
2332 {
2333 header('Access-Control-Allow-Origin: '.$origin);
2334 header('Access-Control-Allow-Headers: '.$allowed_headers);
2335 header('Access-Control-Allow-Methods: '.$allowed_methods);
2336 }
2337 }
2338
2339 // If the request HTTP method is 'OPTIONS', kill the response and send it to the client
2340 if ($this->input->method() === 'options')
2341 {
2342 exit;
2343 }
2344 }
2345
2346 public function check_login_admin()
2347 {
2348 if(isset($_SESSION['login'])){
2349 if(isset($_SESSION['user_id'])){
2350 if(isset($_SESSION['level_id'])){
2351
2352 }else{
2353 header("location:login");
2354 }
2355 }else{
2356 header("location:login");
2357 }
2358 }else{
2359 header("location:login");
2360 }
2361 }
2362}