Guest User

Rest_Controller

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