Overview

Packages

  • CodeIgniter
    • Libraries
  • None

Classes

  • Format

Functions

  • _perform_ldap_auth
  • Overview
  • Package
  • Function
  • Tree
  • Todo
   1: <?php defined('BASEPATH') or exit('No direct script access allowed');
   2: 
   3: /**
   4:  * CodeIgniter Rest Controller
   5:  *
   6:  * A fully RESTful server implementation for CodeIgniter using one library, one config file and one controller.
   7:  *
   8:  * @package         CodeIgniter
   9:  * @subpackage      Libraries
  10:  * @category        Libraries
  11:  * @author          Phil Sturgeon, Chris Kacerguis
  12:  * @license         MIT
  13:  * @link            https://github.com/chriskacerguis/codeigniter-restserver
  14:  * @version         3.0.0-pre
  15:  */
  16: abstract class REST_Controller extends CI_Controller
  17: {
  18:     /**
  19:      * This defines the rest format.
  20:      *
  21:      * Must be overridden it in a controller so that it is set.
  22:      *
  23:      * @var string|NULL
  24:      */
  25:     protected $rest_format          = NULL;
  26: 
  27:     /**
  28:      * Defines the list of method properties such as limit, log and level
  29:      *
  30:      * @var array
  31:      */
  32:     protected $methods              = array();
  33: 
  34:     /**
  35:      * List of allowed HTTP methods
  36:      *
  37:      * @var array
  38:      */
  39:     protected $allowed_http_methods = array('get', 'delete', 'post', 'put', 'options', 'patch', 'head');
  40: 
  41:     /**
  42:      * General request data and information.
  43:      * Stores accept, language, body, headers, etc.
  44:      *
  45:      * @var object
  46:      */
  47:     protected $request              = NULL;
  48: 
  49:     /**
  50:      * What is gonna happen in output?
  51:      *
  52:      * @var object
  53:      */
  54:     protected $response             = NULL;
  55: 
  56:     /**
  57:      * Stores DB, keys, key level, etc
  58:      *
  59:      * @var object
  60:      */
  61:     protected $rest                 = NULL;
  62: 
  63:     /**
  64:      * The arguments for the GET request method
  65:      *
  66:      * @var array
  67:      */
  68:     protected $_get_args            = array();
  69: 
  70:     /**
  71:      * The arguments for the POST request method
  72:      *
  73:      * @var array
  74:      */
  75:     protected $_post_args           = array();
  76: 
  77:     /**
  78:      * The insert_id of the log entry (if we have one)
  79:      *
  80:      * @var string
  81:     */
  82:     protected $_insert_id           = '';
  83: 
  84:     /**
  85:      * The arguments for the PUT request method
  86:      *
  87:      * @var array
  88:      */
  89:     protected $_put_args            = array();
  90: 
  91:     /**
  92:      * The arguments for the DELETE request method
  93:      *
  94:      * @var array
  95:      */
  96:     protected $_delete_args         = array();
  97: 
  98:     /**
  99:      * The arguments for the PATCH request method
 100:      *
 101:      * @var array
 102:      */
 103:     protected $_patch_args          = array();
 104: 
 105:     /**
 106:      * The arguments for the HEAD request method
 107:      *
 108:      * @var array
 109:      */
 110:     protected $_head_args           = array();
 111: 
 112:     /**
 113:      * The arguments for the OPTIONS request method
 114:      *
 115:      * @var array
 116:      */
 117:     protected $_options_args        = array();
 118: 
 119:     /**
 120:      * The arguments from GET, POST, PUT, DELETE request methods combined.
 121:      *
 122:      * @var array
 123:      */
 124:     protected $_args                = array();
 125: 
 126:     /**
 127:      * If the request is allowed based on the API key provided.
 128:      *
 129:      * @var boolean
 130:      */
 131:     protected $_allow               = TRUE;
 132: 
 133:     /**
 134:      * Determines if output compression is enabled
 135:      *
 136:      * @var boolean
 137:      */
 138:     protected $_zlib_oc             = FALSE;
 139: 
 140:     /**
 141:      * The LDAP Distinguished Name of the User post authentication
 142:      *
 143:      * @var string
 144:     */
 145:     protected $_user_ldap_dn        = '';
 146: 
 147:     /**
 148:      * The start of the response time from the server
 149:      *
 150:      * @var string
 151:     */
 152:     protected $_start_rtime         = '';
 153: 
 154:     /**
 155:      * The end of the response time from the server
 156:      *
 157:      * @var string
 158:     */
 159:     protected $_end_rtime           = '';
 160: 
 161:     /**
 162:      * List all supported methods, the first will be the default format
 163:      *
 164:      * @var array
 165:      */
 166:     protected $_supported_formats   = array(
 167:         'xml'           => 'application/xml',
 168:         'json'          => 'application/json',
 169:         'jsonp'         => 'application/javascript',
 170:         'serialized'    => 'application/vnd.php.serialized',
 171:         'php'           => 'text/plain',
 172:         'html'          => 'text/html',
 173:         'csv'           => 'application/csv'
 174:     );
 175: 
 176:     /**
 177:      * Information about the current API user
 178:      *
 179:      * @var object
 180:      */
 181:     protected $_apiuser;
 182: 
 183:     /**
 184:      * Developers can extend this class and add a check in here.
 185:      */
 186:     protected function early_checks()
 187:     {
 188: 
 189:     }
 190: 
 191:     /**
 192:      * Constructor function
 193:      * @todo Document more please.
 194:      */
 195:     public function __construct($config = 'rest')
 196:     {
 197:         parent::__construct();
 198: 
 199:         // Start the timer for how long the request takes
 200:         $this->_start_rtime = microtime(TRUE);
 201: 
 202:         // Lets grab the config and get ready to party
 203:         $this->load->config($config);
 204: 
 205:         // This library is bundled with REST_Controller 2.5+, but will eventually be part of CodeIgniter itself
 206:         $this->load->library('format');
 207: 
 208:         // init objects
 209:         $this->response     = new stdClass();
 210:         $this->rest         = new stdClass();
 211: 
 212:         $this->_zlib_oc     = @ini_get('zlib.output_compression');
 213: 
 214:         // let's learn about the request
 215:         $this->request      = new stdClass();
 216: 
 217:         // Check to see if this IP is Blacklisted
 218:         if ($this->config->item('rest_ip_blacklist_enabled')) {
 219:             $this->_check_blacklist_auth();
 220:         }
 221: 
 222:         // Is it over SSL?
 223:         $this->request->ssl     = $this->_detect_ssl();
 224: 
 225:         // How is this request being made? POST, DELETE, GET, PUT?
 226:         $this->request->method  = $this->_detect_method();
 227: 
 228:         // Create argument container, if nonexistent
 229:         if (!isset($this->{'_'.$this->request->method.'_args'})) {
 230:             $this->{'_'.$this->request->method.'_args'} = array();
 231:         }
 232: 
 233:         // Set up our GET variables
 234:         $this->_get_args        = array_merge($this->_get_args, $this->uri->ruri_to_assoc());
 235: 
 236:         // Try to find a format for the request (means we have a request body)
 237:         $this->request->format  = $this->_detect_input_format();
 238: 
 239:         // Some Methods cant have a body
 240:         $this->request->body    = NULL;
 241: 
 242:         $this->{'_parse_' . $this->request->method}();
 243: 
 244:         // Now we know all about our request, let's try and parse the body if it exists
 245:         if ($this->request->format and $this->request->body) {
 246:             $this->request->body = $this->format->factory($this->request->body, $this->request->format)->to_array();
 247:             // Assign payload arguments to proper method container
 248:             $this->{'_'.$this->request->method.'_args'} = $this->request->body;
 249:         }
 250: 
 251:         // Merge both for one mega-args variable
 252:         $this->_args = array_merge($this->_get_args,
 253:             $this->_options_args,
 254:             $this->_patch_args,
 255:             $this->_head_args ,
 256:             $this->_put_args,
 257:             $this->_post_args,
 258:             $this->_delete_args,
 259:             $this->{'_'.$this->request->method.'_args'}
 260:         );
 261: 
 262:         // Which format should the data be returned in?
 263:         $this->response         = new stdClass();
 264:         $this->response->format = $this->_detect_output_format();
 265: 
 266:         // Which format should the data be returned in?
 267:         $this->response->lang   = $this->_detect_lang();
 268: 
 269:         // Developers can extend this class and add a check in here
 270:         $this->early_checks();
 271: 
 272:         $this->rest             = new StdClass();
 273: 
 274:         // Load DB if its enabled
 275:         if (config_item('rest_database_group') and (config_item('rest_enable_keys') or config_item('rest_enable_logging'))) {
 276:             $this->rest->db     = $this->load->database(config_item('rest_database_group'), TRUE);
 277:         }
 278: 
 279:         // Use whatever database is in use (isset returns FALSE)
 280:         elseif (property_exists($this, "db")) {
 281:             $this->rest->db     = $this->db;
 282:         }
 283: 
 284:         // Check if there is a specific auth type for the current class/method
 285:         // _auth_override_check could exit so we need $this->rest->db initialized before
 286:         $this->auth_override    = $this->_auth_override_check();
 287: 
 288:         // Checking for keys? GET TO WorK!
 289:           // Skip keys test for $config['auth_override_class_method']['class'['method'] = 'none'
 290:         if (config_item('rest_enable_keys') and $this->auth_override !== TRUE) {
 291:             $this->_allow = $this->_detect_api_key();
 292:         }
 293: 
 294:         // only allow ajax requests
 295:         if (!$this->input->is_ajax_request() and config_item('rest_ajax_only')) {
 296:             $response = array(config_item('rest_status_field_name') => FALSE, config_item('rest_message_field_name') => 'Only AJAX requests are accepted.');
 297:             $this->response($response, 406); // Set status to 406 NOT ACCEPTABLE
 298:         }
 299: 
 300:         // When there is no specific override for the current class/method, use the default auth value set in the config
 301:         if ($this->auth_override !== TRUE && !(config_item('rest_enable_keys') && $this->_allow === TRUE)) {
 302:             $rest_auth = strtolower($this->config->item('rest_auth'));
 303:             switch ($rest_auth) {
 304:                 case 'basic':
 305:                     $this->_prepare_basic_auth();
 306:                     break;
 307:                 case 'digest':
 308:                     $this->_prepare_digest_auth();
 309:                     break;
 310:                 case 'session':
 311:                     $this->_check_php_session();
 312:                     break;
 313:             }
 314:             if ($this->config->item('rest_ip_whitelist_enabled')) {
 315:                 $this->_check_whitelist_auth();
 316:             }
 317:         }
 318:     }
 319: 
 320:     /**
 321:      * Destructor function
 322:      * @author Chris Kacerguis
 323:      */
 324:     public function __destruct()
 325:     {
 326:         // Record the "stop" time of the request
 327:         $this->_end_rtime = microtime(TRUE);
 328:         // CK: if, we are logging, log the access time here, as we are done!
 329:         if (config_item('rest_enable_logging')) {
 330:             $this->_log_access_time();
 331:         }
 332: 
 333:     }
 334: 
 335:     /**
 336:      * Remap
 337:      *
 338:      * Requests are not made to methods directly, the request will be for
 339:      * an "object". This simply maps the object and method to the correct
 340:      * Controller method.
 341:      *
 342:      * @param string $object_called
 343:      * @param array  $arguments     The arguments passed to the controller method.
 344:      */
 345:     public function _remap($object_called, $arguments)
 346:     {
 347:         // Should we answer if not over SSL?
 348:         if (config_item('force_https') and !$this->_detect_ssl()) {
 349:             $this->response(array(config_item('rest_status_field_name') => FALSE, config_item('rest_message_field_name') => 'Unsupported protocol'), 403);
 350:         }
 351: 
 352:         $pattern = '/^(.*)\.('.implode('|', array_keys($this->_supported_formats)).')$/';
 353:         $matches = array();
 354:         if (preg_match($pattern, $object_called, $matches)) {
 355:             $object_called = $matches[1];
 356:         }
 357: 
 358:         $controller_method = $object_called.'_'.$this->request->method;
 359: 
 360:         // Do we want to log this method (if allowed by config)?
 361:         $log_method = !(isset($this->methods[$controller_method]['log']) and $this->methods[$controller_method]['log'] == FALSE);
 362: 
 363:         // Use keys for this method?
 364:         $use_key = !(isset($this->methods[$controller_method]['key']) and $this->methods[$controller_method]['key'] == FALSE);
 365: 
 366:         // They provided a key, but it wasn't valid, so get them out of here.
 367:         if (config_item('rest_enable_keys') and $use_key and $this->_allow === FALSE) {
 368:             if (config_item('rest_enable_logging') and $log_method) {
 369:                 $this->_log_request();
 370:             }
 371: 
 372:             $this->response(array(config_item('rest_status_field_name') => FALSE, config_item('rest_message_field_name') => 'Invalid API Key '.$this->rest->key), 403);
 373:         }
 374: 
 375:         // Check to see if this key has access to the requested controller.
 376:         if (config_item('rest_enable_keys') and $use_key and !empty($this->rest->key) and !$this->_check_access()) {
 377:             if (config_item('rest_enable_logging') and $log_method) {
 378:                 $this->_log_request();
 379:             }
 380: 
 381:             $this->response(array(config_item('rest_status_field_name') => FALSE, config_item('rest_message_field_name') => 'This API key does not have access to the requested controller.'), 401);
 382:         }
 383: 
 384:         // Sure it exists, but can they do anything with it?
 385:         if ( ! method_exists($this, $controller_method)) {
 386:             $this->response(array(config_item('rest_status_field_name') => FALSE, config_item('rest_message_field_name') => 'Unknown method.'), 404);
 387:         }
 388: 
 389:         // Doing key related stuff? Can only do it if they have a key right?
 390:         if (config_item('rest_enable_keys') and !empty($this->rest->key)) {
 391:             // Check the limit
 392:             if (config_item('rest_enable_limits') and !$this->_check_limit($controller_method)) {
 393:                 $response = array(config_item('rest_status_field_name') => FALSE, config_item('rest_message_field_name') => 'This API key has reached the hourly limit for this method.');
 394:                 $this->response($response, 401);
 395:             }
 396: 
 397:             // If no level is set use 0, they probably aren't using permissions
 398:             $level = isset($this->methods[$controller_method]['level']) ? $this->methods[$controller_method]['level'] : 0;
 399: 
 400:             // If no level is set, or it is lower than/equal to the key's level
 401:             $authorized = $level <= $this->rest->level;
 402: 
 403:             // IM TELLIN!
 404:             if (config_item('rest_enable_logging') and $log_method) {
 405:                 $this->_log_request($authorized);
 406:             }
 407: 
 408:             // They don't have good enough perms
 409:             $response = array(config_item('rest_status_field_name') => FALSE, config_item('rest_message_field_name') => 'This API key does not have enough permissions.');
 410:             $authorized or $this->response($response, 401);
 411:         }
 412: 
 413:         // No key stuff, but record that stuff is happening
 414:         else if (config_item('rest_enable_logging') and $log_method) {
 415:             $this->_log_request($authorized = TRUE);
 416:         }
 417: 
 418:         // and...... GO!
 419:         $this->_fire_method(array($this, $controller_method), $arguments);
 420:     }
 421: 
 422:     /**
 423:      * Fire Method
 424:      *
 425:      * Fires the designated controller method with the given arguments.
 426:      *
 427:      * @param array $method The controller method to fire
 428:      * @param array $args   The arguments to pass to the controller method
 429:      */
 430:     protected function _fire_method($method, $args)
 431:     {
 432:         call_user_func_array($method, $args);
 433:     }
 434: 
 435:     /**
 436:      * Response
 437:      *
 438:      * Takes pure data and optionally a status code, then creates the response.
 439:      * Set $continue to TRUE to flush the response to the client and continue running the script.
 440:      *
 441:      * @param array    $data
 442:      * @param NULL|int $http_code
 443:      * @param bool $continue
 444:      */
 445:     public function response($data = NULL, $http_code = NULL, $continue = FALSE)
 446:     {
 447:         // If data is NULL and not code provide, error and bail
 448:         if ($data === NULL && $http_code === NULL) {
 449:             $http_code = 404;
 450: 
 451:             // create the output variable here in the case of $this->response(array());
 452:             $output = NULL;
 453:         }
 454: 
 455:         // If data is NULL but http code provided, keep the output empty
 456:         else if ($data === NULL && is_numeric($http_code)) {
 457:             $output = NULL;
 458:         }
 459: 
 460:         // Otherwise (if no data but 200 provided) or some data, carry on camping!
 461:         else {
 462:             // Is compression requested?
 463:             if ($this->config->item('compress_output') === TRUE && $this->_zlib_oc == FALSE) {
 464:                 if (extension_loaded('zlib')) {
 465:                     if (isset($_SERVER['HTTP_ACCEPT_ENCODING']) and strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE) {
 466:                         ob_start('ob_gzhandler');
 467:                     }
 468:                 }
 469:             }
 470: 
 471:             is_numeric($http_code) or $http_code = 200;
 472: 
 473:             // @deprecated the following statement can be deleted.
 474:             // If the format method exists, call and return the output in that format
 475:             if (method_exists($this, '_format_'.$this->response->format)) {
 476:                 // Set the correct format header
 477:                 header('Content-Type: '.$this->_supported_formats[$this->response->format] . '; charset=' . strtolower($this->config->item('charset')));
 478: 
 479:                 $output = $this->{'_format_'.$this->response->format}($data);
 480:             }
 481: 
 482:             // If the format method exists, call and return the output in that format
 483:             elseif (method_exists($this->format, 'to_'.$this->response->format)) {
 484:                 // Set the correct format header
 485:                 header('Content-Type: '.$this->_supported_formats[$this->response->format] . '; charset=' . strtolower($this->config->item('charset')));
 486: 
 487:                 $output = $this->format->factory($data)->{'to_'.$this->response->format}();
 488:             }
 489: 
 490:             // Format not supported, output directly
 491:             else {
 492:                 $output = $data;
 493:             }
 494:         }
 495: 
 496:         set_status_header($http_code);
 497: 
 498:         // If zlib.output_compression is enabled it will compress the output,
 499:         // but it will not modify the content-length header to compensate for
 500:         // the reduction, causing the browser to hang waiting for more data.
 501:         // We'll just skip content-length in those cases.
 502:         if ( ! $this->_zlib_oc && ! $this->config->item('compress_output')) {
 503:             header('Content-Length: ' . strlen($output));
 504:         }
 505: 
 506:         if($continue){
 507:             echo($output);
 508:             ob_end_flush();
 509:             ob_flush();
 510:             flush();
 511:         }else{
 512:             exit($output);
 513:         }
 514:     }
 515: 
 516:     /*
 517:      * Detect SSL use
 518:      *
 519:      * Detect whether SSL is being used or not
 520:      */
 521:     protected function _detect_ssl()
 522:     {
 523:             // $_SERVER['HTTPS'] (http://php.net/manual/en/reserved.variables.server.php)
 524:             // Set to a non-empty value if the script was queried through the HTTPS protocol
 525:             return (isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS']));
 526:     }
 527: 
 528: 
 529:     /*
 530:      * Detect input format
 531:      *
 532:      * Detect which format the HTTP Body is provided in
 533:      */
 534:     protected function _detect_input_format()
 535:     {
 536:         if ($this->input->server('CONTENT_TYPE')) {
 537:             // Check all formats against the HTTP_ACCEPT header
 538:             foreach ($this->_supported_formats as $format => $mime) {
 539:                 if (strpos($match = $this->input->server('CONTENT_TYPE'), ';')) {
 540:                     $match = current(explode(';', $match));
 541:                 }
 542: 
 543:                 if ($match == $mime) {
 544:                     return $format;
 545:                 }
 546:             }
 547:         }
 548: 
 549:         return NULL;
 550:     }
 551: 
 552:     /**
 553:      * Detect format
 554:      *
 555:      * Detect which format should be used to output the data.
 556:      *
 557:      * @return string The output format.
 558:      */
 559:     protected function _detect_output_format()
 560:     {
 561:         $pattern = '/\.('.implode('|', array_keys($this->_supported_formats)).')$/';
 562: 
 563:         // Check if a file extension is used when no get arguments provided
 564:         $matches = array();
 565:         if (!$this->_get_args and preg_match($pattern, $this->uri->uri_string(), $matches)) {
 566:             return $matches[1];
 567:         }
 568: 
 569:         // Check if a file extension is used
 570:         elseif ($this->_get_args and !is_array(end($this->_get_args)) and preg_match($pattern, end($this->_get_args), $matches)) {
 571:         //elseif ($this->_get_args and !is_array(end($this->_get_args)) and preg_match($pattern, end(array_keys($this->_get_args)), $matches)) {
 572:             // The key of the last argument
 573:             $last_key = end(array_keys($this->_get_args));
 574: 
 575:             // Remove the extension from arguments too
 576:             $this->_get_args[$last_key] = preg_replace($pattern, '', $this->_get_args[$last_key]);
 577:             $this->_args[$last_key]     = preg_replace($pattern, '', $this->_args[$last_key]);
 578: 
 579:             return $matches[1];
 580:         }
 581: 
 582:         // A format has been passed as an argument in the URL and it is supported
 583:         if (isset($this->_get_args['format']) and array_key_exists($this->_get_args['format'], $this->_supported_formats)) {
 584:             return $this->_get_args['format'];
 585:         }
 586: 
 587:         // Otherwise, check the HTTP_ACCEPT (if it exists and we are allowed)
 588:         if ($this->config->item('rest_ignore_http_accept') === FALSE and $this->input->server('HTTP_ACCEPT')) {
 589:             // Check all formats against the HTTP_ACCEPT header
 590:             foreach (array_keys($this->_supported_formats) as $format) {
 591:                 // Has this format been requested?
 592:                 if (strpos($this->input->server('HTTP_ACCEPT'), $format) !== FALSE) {
 593:                     // If not HTML or XML assume its right and send it on its way
 594:                     if ($format != 'html' and $format != 'xml') {
 595:                         return $format;
 596:                     }
 597: 
 598:                     // HTML or XML have shown up as a match
 599:                     else {
 600:                         // If it is truly HTML, it wont want any XML
 601:                         if ($format == 'html' and strpos($this->input->server('HTTP_ACCEPT'), 'xml') === FALSE) {
 602:                             return $format;
 603:                         }
 604: 
 605:                         // If it is truly XML, it wont want any HTML
 606:                         elseif ($format == 'xml' and strpos($this->input->server('HTTP_ACCEPT'), 'html') === FALSE) {
 607:                             return $format;
 608:                         }
 609:                     }
 610:                 }
 611:             }
 612:         } // End HTTP_ACCEPT checking
 613: 
 614:         // Well, none of that has worked! Let's see if the controller has a default
 615:         if ( ! empty($this->rest_format)) {
 616:             return $this->rest_format;
 617:         }
 618: 
 619:         // Just use the default format
 620:         return config_item('rest_default_format');
 621:     }
 622: 
 623:     /**
 624:      * Detect method
 625:      *
 626:      * Detect which HTTP method is being used
 627:      *
 628:      * @return string
 629:      */
 630:     protected function _detect_method()
 631:     {
 632:         $method = strtolower($this->input->server('REQUEST_METHOD'));
 633: 
 634:         if ($this->config->item('enable_emulate_request')) {
 635:             if ($this->input->post('_method')) {
 636:                 $method = strtolower($this->input->post('_method'));
 637:             } elseif ($this->input->server('HTTP_X_HTTP_METHOD_OVERRIDE')) {
 638:                 $method = strtolower($this->input->server('HTTP_X_HTTP_METHOD_OVERRIDE'));
 639:             }
 640:         }
 641: 
 642:         if (in_array($method, $this->allowed_http_methods) && method_exists($this, '_parse_' . $method)) {
 643:             return $method;
 644:         }
 645: 
 646:         return 'get';
 647:     }
 648: 
 649:     /**
 650:      * Detect API Key
 651:      *
 652:      * See if the user has provided an API key
 653:      *
 654:      * @return boolean
 655:      */
 656:     protected function _detect_api_key()
 657:     {
 658:         // Get the api key name variable set in the rest config file
 659:         $api_key_variable = config_item('rest_key_name');
 660: 
 661:         // Work out the name of the SERVER entry based on config
 662:         $key_name = 'HTTP_'.strtoupper(str_replace('-', '_', $api_key_variable));
 663: 
 664:         $this->rest->key = NULL;
 665:         $this->rest->level = NULL;
 666:         $this->rest->user_id = NULL;
 667:         $this->rest->ignore_limits = FALSE;
 668: 
 669:         // Find the key from server or arguments
 670:         if (($key = isset($this->_args[$api_key_variable]) ? $this->_args[$api_key_variable] : $this->input->server($key_name))) {
 671:             if ( ! ($row = $this->rest->db->where(config_item('rest_key_column'), $key)->get(config_item('rest_keys_table'))->row())) {
 672:                 return FALSE;
 673:             }
 674: 
 675:             $this->rest->key = $row->{config_item('rest_key_column')};
 676: 
 677:             isset($row->user_id) and $this->rest->user_id = $row->user_id;
 678:             isset($row->level) and $this->rest->level = $row->level;
 679:             isset($row->ignore_limits) and $this->rest->ignore_limits = $row->ignore_limits;
 680: 
 681:             $this->_apiuser =  $row;
 682: 
 683:             /*
 684:              * If "is private key" is enabled, compare the ip address with the list
 685:              * of valid ip addresses stored in the database.
 686:              */
 687:             if (!empty($row->is_private_key)) {
 688:                 // Check for a list of valid ip addresses
 689:                 if (isset($row->ip_addresses)) {
 690:                     // multiple ip addresses must be separated using a comma, explode and loop
 691:                     $list_ip_addresses = explode(",", $row->ip_addresses);
 692:                     $found_address = FALSE;
 693: 
 694:                     foreach ($list_ip_addresses as $ip_address) {
 695:                         if ($this->input->ip_address() == trim($ip_address)) {
 696:                             // there is a match, set the the value to TRUE and break out of the loop
 697:                             $found_address = TRUE;
 698:                             break;
 699:                         }
 700:                     }
 701: 
 702:                     return $found_address;
 703:                 } else {
 704:                     // There should be at least one IP address for this private key.
 705:                     return FALSE;
 706:                 }
 707:             }
 708: 
 709:             return $row;
 710:         }
 711: 
 712:         // No key has been sent
 713:         return FALSE;
 714:     }
 715: 
 716:     /**
 717:      * Detect language(s)
 718:      *
 719:      * What language do they want it in?
 720:      *
 721:      * @return NULL|string The language code.
 722:      */
 723:     protected function _detect_lang()
 724:     {
 725:         if ( ! $lang = $this->input->server('HTTP_ACCEPT_LANGUAGE')) {
 726:             return NULL;
 727:         }
 728: 
 729:         // They might have sent a few, make it an array
 730:         if (strpos($lang, ',') !== FALSE) {
 731:             $langs = explode(',', $lang);
 732: 
 733:             $return_langs = array();
 734:             foreach ($langs as $lang) {
 735:                 // Remove weight and strip space
 736:                 list($lang) = explode(';', $lang);
 737:                 $return_langs[] = trim($lang);
 738:             }
 739: 
 740:             return $return_langs;
 741:         }
 742: 
 743:         // Nope, just return the string
 744:         return $lang;
 745:     }
 746: 
 747:     /**
 748:      * Log request
 749:      *
 750:      * Record the entry for awesomeness purposes
 751:      *
 752:      * @param  boolean $authorized
 753:      * @return object
 754:      */
 755:     protected function _log_request($authorized = FALSE)
 756:     {
 757:         $status = $this->rest->db->insert(config_item('rest_logs_table'), array(
 758:                     'uri' => $this->uri->uri_string(),
 759:                     'method' => $this->request->method,
 760:                     'params' => $this->_args ? (config_item('rest_logs_json_params') ? json_encode($this->_args) : serialize($this->_args)) : NULL,
 761:                     'api_key' => isset($this->rest->key) ? $this->rest->key : '',
 762:                     'ip_address' => $this->input->ip_address(),
 763:                     'time' => function_exists('now') ? now() : time(),
 764:                     'authorized' => $authorized
 765:                 ));
 766: 
 767:         $this->_insert_id = $this->rest->db->insert_id();
 768: 
 769:         return $status;
 770:     }
 771: 
 772:     /**
 773:      * Limiting requests
 774:      *
 775:      * Check if the requests are coming in a tad too fast.
 776:      *
 777:      * @param  string  $controller_method The method being called.
 778:      * @return boolean
 779:      */
 780:     protected function _check_limit($controller_method)
 781:     {
 782:         // They are special, or it might not even have a limit
 783:         if ( ! empty($this->rest->ignore_limits) or !isset($this->methods[$controller_method]['limit'])) {
 784:             // On your way sonny-jim.
 785:             return TRUE;
 786:         }
 787: 
 788:         // How many times can you get to this method an hour?
 789:         $limit = $this->methods[$controller_method]['limit'];
 790: 
 791:         // Get data on a keys usage
 792:         $result = $this->rest->db
 793:                 ->where('uri', $this->uri->uri_string())
 794:                 ->where('api_key', $this->rest->key)
 795:                 ->get(config_item('rest_limits_table'))
 796:                 ->row();
 797: 
 798:         // No calls yet for this key
 799:         if ( ! $result ) {
 800:             // Right, set one up from scratch
 801:             $this->rest->db->insert(config_item('rest_limits_table'), array(
 802:                 'uri' => $this->uri->uri_string(),
 803:                 'api_key' => isset($this->rest->key) ? $this->rest->key : '',
 804:                 'count' => 1,
 805:                 'hour_started' => time()
 806:             ));
 807:         }
 808: 
 809:         // Been an hour since they called
 810:         else if ($result->hour_started < time() - (60 * 60)) {
 811:             // Reset the started period
 812:             $this->rest->db
 813:                     ->where('uri', $this->uri->uri_string())
 814:                     ->where('api_key', isset($this->rest->key) ? $this->rest->key : '')
 815:                     ->set('hour_started', time())
 816:                     ->set('count', 1)
 817:                     ->update(config_item('rest_limits_table'));
 818:         }
 819: 
 820:         // They have called within the hour, so lets update
 821:         else {
 822:             // Your luck is out, you've called too many times!
 823:             if ($result->count >= $limit) {
 824:                 return FALSE;
 825:             }
 826: 
 827:             $this->rest->db
 828:                     ->where('uri', $this->uri->uri_string())
 829:                     ->where('api_key', $this->rest->key)
 830:                     ->set('count', 'count + 1', FALSE)
 831:                     ->update(config_item('rest_limits_table'));
 832:         }
 833: 
 834:         return TRUE;
 835:     }
 836: 
 837:     /**
 838:      * Auth override check
 839:      *
 840:      * Check if there is a specific auth type set for the current class/method
 841:      * being called.
 842:      *
 843:      * @return boolean
 844:      */
 845:     protected function _auth_override_check()
 846:     {
 847: 
 848:         // Assign the class/method auth type override array from the config
 849:         $this->overrides_array = $this->config->item('auth_override_class_method');
 850: 
 851:         // Check to see if the override array is even populated, otherwise return FALSE
 852:         if (empty($this->overrides_array)) {
 853:             return FALSE;
 854:         }
 855: 
 856:         // check for wildcard flag for rules for classes
 857:         if(!empty($this->overrides_array[$this->router->class]['*'])){//check for class overides
 858:             // None auth override found, prepare nothing but send back a TRUE override flag
 859:             if ($this->overrides_array[$this->router->class]['*'] == 'none')
 860:             {
 861:                 return TRUE;
 862:             }
 863: 
 864:             // Basic auth override found, prepare basic
 865:             if ($this->overrides_array[$this->router->class]['*'] == 'basic')
 866:             {
 867:                 $this->_prepare_basic_auth();
 868:                 return TRUE;
 869:             }
 870: 
 871:             // Digest auth override found, prepare digest
 872:             if ($this->overrides_array[$this->router->class]['*'] == 'digest')
 873:             {
 874:                 $this->_prepare_digest_auth();
 875:                 return TRUE;
 876:             }
 877: 
 878:             // Whitelist auth override found, check client's ip against config whitelist
 879:             if ($this->overrides_array[$this->router->class]['*'] == 'whitelist')
 880:             {
 881:                 $this->_check_whitelist_auth();
 882:                 return TRUE;
 883:             }
 884:         }
 885: 
 886:         // Check to see if there's an override value set for the current class/method being called
 887:         if (empty($this->overrides_array[$this->router->class][$this->router->method])) {
 888:             return FALSE;
 889:         }
 890: 
 891:         // None auth override found, prepare nothing but send back a TRUE override flag
 892:         if ($this->overrides_array[$this->router->class][$this->router->method] == 'none') {
 893:             return TRUE;
 894:         }
 895: 
 896:         // Basic auth override found, prepare basic
 897:         if ($this->overrides_array[$this->router->class][$this->router->method] == 'basic') {
 898:             $this->_prepare_basic_auth();
 899: 
 900:             return TRUE;
 901:         }
 902: 
 903:         // Digest auth override found, prepare digest
 904:         if ($this->overrides_array[$this->router->class][$this->router->method] == 'digest') {
 905:             $this->_prepare_digest_auth();
 906: 
 907:             return TRUE;
 908:         }
 909: 
 910:         // Whitelist auth override found, check client's ip against config whitelist
 911:         if ($this->overrides_array[$this->router->class][$this->router->method] == 'whitelist') {
 912:             $this->_check_whitelist_auth();
 913: 
 914:             return TRUE;
 915:         }
 916: 
 917:         // Return FALSE when there is an override value set but it does not match
 918:         // 'basic', 'digest', or 'none'. (the value was misspelled)
 919:         return FALSE;
 920:     }
 921: 
 922:     /**
 923:      * Parse GET
 924:      */
 925:     protected function _parse_get()
 926:     {
 927:         // Fix for Issue #247
 928:         if ($this->input->is_cli_request()) {
 929:             $args = $_SERVER['argv'];
 930:             unset($args[0]);
 931:             $_SERVER['QUERY_STRING'] =  $_SERVER['PATH_INFO'] = $_SERVER['REQUEST_URI'] = '/' . implode('/', $args) . '/';
 932:         }
 933: 
 934:         // Grab proper GET variables
 935:         parse_str(parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY), $get);
 936: 
 937:         // Merge both the URI segments and GET params
 938:         $this->_get_args = array_merge($this->_get_args, $get);
 939:     }
 940: 
 941:     /**
 942:      * Parse POST
 943:      */
 944:     protected function _parse_post()
 945:     {
 946:         $this->_post_args = $_POST;
 947: 
 948:         $this->request->format and $this->request->body = file_get_contents('php://input');
 949:     }
 950: 
 951:     /**
 952:      * Parse PUT
 953:      */
 954:     protected function _parse_put()
 955:     {
 956:         // It might be a HTTP body
 957:         if ($this->request->format) {
 958:             $this->request->body = file_get_contents('php://input');
 959:         }
 960: 
 961:         // If no file type is provided, this is probably just arguments
 962:         else {
 963:             parse_str(file_get_contents('php://input'), $this->_put_args);
 964:         }
 965: 
 966:     }
 967: 
 968:     /**
 969:      * Parse HEAD
 970:      */
 971:     protected function _parse_head()
 972:     {
 973:         // Grab proper HEAD variables
 974:         parse_str(parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY), $head);
 975: 
 976:         // Merge both the URI segments and HEAD params
 977:         $this->_head_args = array_merge($this->_head_args, $head);
 978:     }
 979: 
 980:     /**
 981:      * Parse OPTIONS
 982:      */
 983:     protected function _parse_options()
 984:     {
 985:         // Grab proper OPTIONS variables
 986:         parse_str(parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY), $options);
 987: 
 988:         // Merge both the URI segments and OPTIONS params
 989:         $this->_options_args = array_merge($this->_options_args, $options);
 990:     }
 991: 
 992:     /**
 993:      * Parse PATCH
 994:      */
 995:     protected function _parse_patch()
 996:     {
 997:         // It might be a HTTP body
 998:         if ($this->request->format) {
 999:             $this->request->body = file_get_contents('php://input');
1000:         }
1001: 
1002:         // If no file type is provided, this is probably just arguments
1003:         else {
1004:             parse_str(file_get_contents('php://input'), $this->_patch_args);
1005:         }
1006:     }
1007: 
1008:     /**
1009:      * Parse DELETE
1010:      */
1011:     protected function _parse_delete()
1012:     {
1013:         // Set up out DELETE variables (which shouldn't really exist, but sssh!)
1014:         parse_str(file_get_contents('php://input'), $this->_delete_args);
1015:     }
1016: 
1017:     // INPUT FUNCTION --------------------------------------------------------------
1018: 
1019:     /**
1020:      * Retrieve a value from the GET request arguments.
1021:      *
1022:      * @param  string  $key       The key for the GET request argument to retrieve
1023:      * @param  boolean $xss_clean Whether the value should be XSS cleaned or not.
1024:      * @return string  The GET argument value.
1025:      */
1026:     public function get($key = NULL, $xss_clean = TRUE)
1027:     {
1028:         if ($key === NULL) {
1029:             return $this->_get_args;
1030:         }
1031: 
1032:         return array_key_exists($key, $this->_get_args) ? $this->_xss_clean($this->_get_args[$key], $xss_clean) : FALSE;
1033:     }
1034: 
1035:     /**
1036:      * This function retrieves a values from the OPTIONS request arguments
1037:      *
1038:      * @param  string  $key       The OPTIONS/GET argument key
1039:      * @param  boolean $xss_clean Whether the value should be XSS cleaned or not
1040:      * @return string  The OPTIONS/GET argument value
1041:      */
1042:     public function options($key = NULL, $xss_clean = TRUE)
1043:     {
1044:         if ($key === NULL) {
1045:             return $this->_options_args;
1046:         }
1047: 
1048:         return array_key_exists($key, $this->_options_args) ? $this->_xss_clean($this->_options_args[$key], $xss_clean) : FALSE;
1049:     }
1050: 
1051:     /**
1052:      * This function retrieves a values from the HEAD request arguments
1053:      *
1054:      * @param  string  $key       The HEAD/GET argument key
1055:      * @param  boolean $xss_clean Whether the value should be XSS cleaned or not
1056:      * @return string  The HEAD/GET argument value
1057:      */
1058:     public function head($key = NULL, $xss_clean = TRUE)
1059:     {
1060:         if ($key === NULL) {
1061:             return $this->head_args;
1062:         }
1063: 
1064:         return array_key_exists($key, $this->head_args) ? $this->_xss_clean($this->head_args[$key], $xss_clean) : FALSE;
1065:     }
1066: 
1067:     /**
1068:      * Retrieve a value from the POST request arguments.
1069:      *
1070:      * @param  string  $key       The key for the POST request argument to retrieve
1071:      * @param  boolean $xss_clean Whether the value should be XSS cleaned or not.
1072:      * @return string  The POST argument value.
1073:      */
1074:     public function post($key = NULL, $xss_clean = TRUE)
1075:     {
1076:         if ($key === NULL) {
1077:             return $this->_post_args;
1078:         }
1079: 
1080:         return array_key_exists($key, $this->_post_args) ? $this->_xss_clean($this->_post_args[$key], $xss_clean) : FALSE;
1081:     }
1082: 
1083:     /**
1084:      * Retrieve a value from the PUT request arguments.
1085:      *
1086:      * @param  string  $key       The key for the PUT request argument to retrieve
1087:      * @param  boolean $xss_clean Whether the value should be XSS cleaned or not.
1088:      * @return string  The PUT argument value.
1089:      */
1090:     public function put($key = NULL, $xss_clean = TRUE)
1091:     {
1092:         if ($key === NULL) {
1093:             return $this->_put_args;
1094:         }
1095: 
1096:         return array_key_exists($key, $this->_put_args) ? $this->_xss_clean($this->_put_args[$key], $xss_clean) : FALSE;
1097:     }
1098: 
1099:     /**
1100:      * Retrieve a value from the DELETE request arguments.
1101:      *
1102:      * @param  string  $key       The key for the DELETE request argument to retrieve
1103:      * @param  boolean $xss_clean Whether the value should be XSS cleaned or not.
1104:      * @return string  The DELETE argument value.
1105:      */
1106:     public function delete($key = NULL, $xss_clean = TRUE)
1107:     {
1108:         if ($key === NULL) {
1109:             return $this->_delete_args;
1110:         }
1111: 
1112:         return array_key_exists($key, $this->_delete_args) ? $this->_xss_clean($this->_delete_args[$key], $xss_clean) : FALSE;
1113:     }
1114: 
1115:     /**
1116:      * Retrieve a value from the PATCH request arguments.
1117:      *
1118:      * @param  string  $key       The key for the PATCH request argument to retrieve
1119:      * @param  boolean $xss_clean Whether the value should be XSS cleaned or not.
1120:      * @return string  The PATCH argument value.
1121:      */
1122:     public function patch($key = NULL, $xss_clean = TRUE)
1123:     {
1124:         if ($key === NULL) {
1125:             return $this->_patch_args;
1126:         }
1127: 
1128:         return array_key_exists($key, $this->_patch_args) ? $this->_xss_clean($this->_patch_args[$key], $xss_clean) : FALSE;
1129:     }
1130: 
1131:     /**
1132:      * Process to protect from XSS attacks.
1133:      *
1134:      * @param  string  $val     The input.
1135:      * @param  boolean $process Do clean or note the input.
1136:      * @return string
1137:      */
1138:     protected function _xss_clean($val, $process)
1139:     {
1140:         if (CI_VERSION < 2) {
1141:             return $process ? $this->input->xss_clean($val) : $val;
1142:         }
1143: 
1144:         return $process ? $this->security->xss_clean($val) : $val;
1145:     }
1146: 
1147:     /**
1148:      * Retrieve the validation errors.
1149:      *
1150:      * @return array
1151:      */
1152:     public function validation_errors()
1153:     {
1154:         $string = strip_tags($this->form_validation->error_string());
1155: 
1156:         return explode("\n", trim($string, "\n"));
1157:     }
1158: 
1159:     // SECURITY FUNCTIONS ---------------------------------------------------------
1160: 
1161:     /**
1162:      * Perform LDAP Authentication
1163:      *
1164:      * @param  string  $username The username to validate
1165:      * @param  string  $password The password to validate
1166:      * @return boolean
1167:      */
1168:     protected function _perform_ldap_auth($username = '', $password = NULL)
1169:     {
1170:         if (empty($username)) {
1171:             log_message('debug', 'LDAP Auth: failure, empty username');
1172: 
1173:             return FALSE;
1174:         }
1175: 
1176:         log_message('debug', 'LDAP Auth: Loading Config');
1177: 
1178:         $this->config->load('ldap.php', TRUE);
1179: 
1180:         $ldap = array(
1181:             'timeout' => $this->config->item('timeout', 'ldap');
1182:             'host'    => $this->config->item('server', 'ldap');
1183:             'port'    => $this->config->item('port', 'ldap');
1184:             'rdn'     => $this->config->item('binduser', 'ldap');
1185:             'pass'    => $this->config->item('bindpw', 'ldap');
1186:             'basedn'  => $this->config->item('basedn', 'ldap');
1187:           );
1188: 
1189:         log_message('debug', 'LDAP Auth: Connect to ' . $ldaphost);
1190: 
1191:         $ldapconfig['authrealm'] = $this->config->item('domain', 'ldap');
1192: 
1193:         // connect to ldap server
1194:         $ldapconn = ldap_connect($ldap['host'], $ldap['port']);
1195: 
1196:         if ($ldapconn) {
1197: 
1198:             log_message('debug', 'Setting timeout to ' . $ldap['timeout'] . ' seconds');
1199: 
1200:             ldap_set_option($ldapconn, LDAP_OPT_NETWORK_TIMEOUT, $ldap['timeout']);
1201: 
1202:             log_message('debug', 'LDAP Auth: Binding to ' . $ldap['host'] . ' with dn ' . $ldap['rdn']);
1203: 
1204:             // binding to ldap server
1205:             $ldapbind = ldap_bind($ldapconn, $ldap['rdn'], $ldap['pass']);
1206: 
1207:             // verify binding
1208:             if ($ldapbind) {
1209:                 log_message('debug', 'LDAP Auth: bind successful');
1210:             } else {
1211:                 log_message('error', 'LDAP Auth: bind unsuccessful');
1212: 
1213:                 return FALSE;
1214:             }
1215: 
1216:         }
1217: 
1218:         // search for user
1219:         if (($res_id = ldap_search( $ldapconn, $ldap['basedn'], "uid=$username")) == FALSE) {
1220:             log_message('error', 'LDAP Auth: User ' . $username . ' not found in search');
1221: 
1222:             return FALSE;
1223:         }
1224: 
1225:         if (ldap_count_entries($ldapconn, $res_id) != 1) {
1226:             log_message('error', 'LDAP Auth: failure, username ' . $username . 'found more than once');
1227: 
1228:             return FALSE;
1229:         }
1230: 
1231:         if (( $entry_id = ldap_first_entry($ldapconn, $res_id))== FALSE) {
1232:             log_message('error', 'LDAP Auth: failure, entry of searchresult could not be fetched');
1233: 
1234:             return FALSE;
1235:         }
1236: 
1237:         if (( $user_dn = ldap_get_dn($ldapconn, $entry_id)) == FALSE) {
1238:             log_message('error', 'LDAP Auth: failure, user-dn could not be fetched');
1239: 
1240:             return FALSE;
1241:         }
1242: 
1243:         // User found, could not authenticate as user
1244:         if (($link_id = ldap_bind($ldapconn, $user_dn, $password)) == FALSE) {
1245:             log_message('error', 'LDAP Auth: failure, username/password did not match: ' . $user_dn);
1246: 
1247:             return FALSE;
1248:         }
1249: 
1250:         log_message('debug', 'LDAP Auth: Success ' . $user_dn . ' authenticated successfully');
1251: 
1252:         $this->_user_ldap_dn = $user_dn;
1253:         ldap_close($ldapconn);
1254: 
1255:         return TRUE;
1256:     }
1257: 
1258:     /**
1259:      * Perform Library Authentication - Override this function to change the way the library is called
1260:      *
1261:      * @param  string  $username The username to validate
1262:      * @param  string  $password The password to validate
1263:      * @return boolean
1264:      */
1265:     protected function _perform_library_auth($username = '', $password = NULL)
1266:     {
1267:         if (empty($username)) {
1268:             log_message('error', 'Library Auth: failure, empty username');
1269:             return FALSE;
1270:         }
1271: 
1272:         $auth_library_class     = strtolower($this->config->item('auth_library_class'));
1273:         $auth_library_function  = strtolower($this->config->item('auth_library_function'));
1274: 
1275:         if (empty($auth_library_class)) {
1276:             log_message('debug', 'Library Auth: failure, empty auth_library_class');
1277:             return FALSE;
1278:         }
1279: 
1280:         if (empty($auth_library_function)) {
1281:             log_message('debug', 'Library Auth: failure, empty auth_library_function');
1282:             return FALSE;
1283:         }
1284: 
1285:         if (!is_callable(array($auth_library_class, $auth_library_function))) {
1286:             $this->load->library($auth_library_class);
1287:         }
1288: 
1289:         return $this->{$auth_library_class}->$auth_library_function($username, $password);
1290:     }
1291: 
1292:     /**
1293:      * Check if the user is logged in.
1294:      *
1295:      * @param  string  $username The user's name
1296:      * @param  string  $password The user's password
1297:      * @return boolean
1298:      */
1299:     protected function _check_login($username = '', $password = FALSE)
1300:     {
1301:         if (empty($username)) {
1302:             return FALSE;
1303:         }
1304: 
1305:         $auth_source = strtolower($this->config->item('auth_source'));
1306:         $rest_auth = strtolower($this->config->item('rest_auth'));
1307:         $valid_logins = $this->config->item('rest_valid_logins');
1308: 
1309:         if (!$this->config->item('auth_source') && $rest_auth == 'digest') { // for digest we do not have a password passed as argument
1310:             return md5($username.':'.$this->config->item('rest_realm').':'.(isset($valid_logins[$username])?$valid_logins[$username]:''));
1311:         }
1312: 
1313:         if ($password === FALSE) {
1314:             return FALSE;
1315:         }
1316: 
1317:         if ($auth_source == 'ldap') {
1318:             log_message('debug', 'performing LDAP authentication for $username');
1319: 
1320:             return $this->_perform_ldap_auth($username, $password);
1321:         }
1322: 
1323:         if ($auth_source == 'library') {
1324:             log_message('debug', 'performing Library authentication for '.$username);
1325: 
1326:             return $this->_perform_library_auth($username, $password);
1327:         }
1328: 
1329:         if (!array_key_exists($username, $valid_logins)) {
1330:             return FALSE;
1331:         }
1332: 
1333:         if ($valid_logins[$username] != $password) {
1334:             return FALSE;
1335:         }
1336: 
1337:         return TRUE;
1338:     }
1339: 
1340:     /**
1341:      * Check to see if the user is logged into the web app with a php session key.
1342:      */
1343:     protected function _check_php_session()
1344:     {
1345:         $key = $this->config->item('auth_source');
1346:         if (!$this->session->userdata($key)) {
1347:             $this->response(array('status' => FALSE, 'error' => 'Not Authorized'), 401);
1348:         }
1349:     }
1350: 
1351:     /**
1352:      * @todo document this.
1353:      */
1354:     protected function _prepare_basic_auth()
1355:     {
1356:         // If whitelist is enabled it has the first chance to kick them out
1357:         if (config_item('rest_ip_whitelist_enabled')) {
1358:             $this->_check_whitelist_auth();
1359:         }
1360: 
1361:         $username = NULL;
1362:         $password = NULL;
1363: 
1364:         // mod_php
1365:         if ($this->input->server('PHP_AUTH_USER')) {
1366:             $username = $this->input->server('PHP_AUTH_USER');
1367:             $password = $this->input->server('PHP_AUTH_PW');
1368:         }
1369: 
1370:         // most other servers
1371:         elseif ($this->input->server('HTTP_AUTHENTICATION')) {
1372:             if (strpos(strtolower($this->input->server('HTTP_AUTHENTICATION')), 'basic') === 0) {
1373:                 list($username, $password) = explode(':', base64_decode(substr($this->input->server('HTTP_AUTHORIZATION'), 6)));
1374:             }
1375:         }
1376: 
1377:         if ( ! $this->_check_login($username, $password)) {
1378:             $this->_force_login();
1379:         }
1380:     }
1381: 
1382:     /**
1383:      * @todo Document this.
1384:      */
1385:     protected function _prepare_digest_auth()
1386:     {
1387:         // If whitelist is enabled it has the first chance to kick them out
1388:         if (config_item('rest_ip_whitelist_enabled')) {
1389:             $this->_check_whitelist_auth();
1390:         }
1391: 
1392:         $uniqid = uniqid(""); // Empty argument for backward compatibility
1393:         // We need to test which server authentication variable to use
1394:         // because the PHP ISAPI module in IIS acts different from CGI
1395:         if ($this->input->server('PHP_AUTH_DIGEST')) {
1396:             $digest_string = $this->input->server('PHP_AUTH_DIGEST');
1397:         } elseif ($this->input->server('HTTP_AUTHORIZATION')) {
1398:             $digest_string = $this->input->server('HTTP_AUTHORIZATION');
1399:         } else {
1400:             $digest_string = "";
1401:         }
1402: 
1403:         // The $_SESSION['error_prompted'] variable is used to ask the password
1404:         // again if none given or if the user enters wrong auth information.
1405:         if (empty($digest_string)) {
1406:             $this->_force_login($uniqid);
1407:         }
1408: 
1409:         // We need to retrieve authentication informations from the $auth_data variable
1410:         $matches = array();
1411:         preg_match_all('@(username|nonce|uri|nc|cnonce|qop|response)=[\'"]?([^\'",]+)@', $digest_string, $matches);
1412:         $digest = (empty($matches[1]) || empty($matches[2])) ? array() : array_combine($matches[1], $matches[2]);
1413: 
1414:         // For digest authentication the library function should return already stored md5(username:restrealm:password) for that username @see rest.php::auth_library_function config
1415:         $A1 = $this->_check_login($digest['username'], TRUE);
1416:         if ( ! array_key_exists('username', $digest) or ! $A1 ) {
1417:             $this->_force_login($uniqid);
1418:         }
1419: 
1420:         $A2 = md5(strtoupper($this->request->method).':'.$digest['uri']);
1421:         $valid_response = md5($A1.':'.$digest['nonce'].':'.$digest['nc'].':'.$digest['cnonce'].':'.$digest['qop'].':'.$A2);
1422: 
1423:         if ($digest['response'] != $valid_response) {
1424:             $this->response(array(config_item('rest_status_field_name') => 0, config_item('rest_message_field_name') => 'Invalid credentials'), 401);
1425:             exit;
1426:         }
1427:     }
1428: 
1429:     /**
1430:      * Check if the client's ip is in the 'rest_ip_blacklist' config
1431:      */
1432:     protected function _check_blacklist_auth()
1433:     {
1434:         $blacklist = explode(',', config_item('rest_ip_blacklist'));
1435: 
1436:         foreach ($blacklist AS &$ip) {
1437:             $ip = trim($ip);
1438:         }
1439: 
1440:         if (in_array($this->input->ip_address(), $blacklist)) {
1441:             $this->response(array('status' => FALSE, 'error' => 'IP Denied'), 401);
1442:         }
1443:     }
1444: 
1445:     /**
1446:      * Check if the client's ip is in the 'rest_ip_whitelist' config
1447:      */
1448:     protected function _check_whitelist_auth()
1449:     {
1450:         $whitelist = explode(',', config_item('rest_ip_whitelist'));
1451: 
1452:         array_push($whitelist, '127.0.0.1', '0.0.0.0');
1453: 
1454:         foreach ($whitelist AS &$ip) {
1455:             $ip = trim($ip);
1456:         }
1457: 
1458:         if ( ! in_array($this->input->ip_address(), $whitelist)) {
1459:             $this->response(array(config_item('rest_status_field_name') => FALSE, config_item('rest_message_field_name') => 'IP not authorized'), 401);
1460:         }
1461:     }
1462: 
1463:     /**
1464:      * @todo Document this.
1465:      *
1466:      * @param string $nonce
1467:      */
1468:     protected function _force_login($nonce = '')
1469:     {
1470:         if (strtolower( $this->config->item('rest_auth') ) == 'basic') {
1471:             header('WWW-Authenticate: Basic realm="'.$this->config->item('rest_realm').'"');
1472:         } elseif (strtolower( $this->config->item('rest_auth') ) == 'digest') {
1473:             header('WWW-Authenticate: Digest realm="'.$this->config->item('rest_realm').'", qop="auth", nonce="'.$nonce.'", opaque="'.md5($this->config->item('rest_realm')).'"');
1474:         }
1475: 
1476:         $this->response(array(config_item('rest_status_field_name') => FALSE, config_item('rest_message_field_name') => 'Not authorized'), 401);
1477:     }
1478: 
1479:     /**
1480:      * Force it into an array
1481:      *
1482:      * @param  object|array $data
1483:      * @return array
1484:      */
1485:     protected function _force_loopable($data)
1486:     {
1487:         // Force it to be something useful
1488:         if ( ! is_array($data) and !is_object($data)) {
1489:             $data = (array) $data;
1490:         }
1491: 
1492:         return $data;
1493:     }
1494: 
1495:     /**
1496:      * updates the log with the access time
1497:      *
1498:      * @author Chris Kacerguis
1499:      * @return boolean
1500:      */
1501: 
1502:     protected function _log_access_time()
1503:     {
1504:         $payload['rtime'] = $this->_end_rtime - $this->_start_rtime;
1505: 
1506:         return $this->rest->db->update(config_item('rest_logs_table'), $payload, array('id' => $this->_insert_id));
1507:     }
1508: 
1509:     /**
1510:      * Check to see if the API key has access to the controller and methods
1511:      *
1512:      * @return boolean
1513:      */
1514:     protected function _check_access()
1515:     {
1516:         // if we don't want to check acccess, just return TRUE
1517:         if (config_item('rest_enable_access') === FALSE) {
1518:             return TRUE;
1519:         }
1520: 
1521:         // Fetch controller based on path and controller name
1522:         $controller = implode( '/', array($this->router->fetch_directory(), $this->router->fetch_class()) );
1523: 
1524:         // Remove any double slashes for safety
1525:         $controller = str_replace('//', '/', $controller);
1526: 
1527:         // Build access table query
1528:         $this->rest->db->select();
1529:         $this->rest->db->where('key', $this->rest->key);
1530:         $this->rest->db->where('controller', $controller);
1531: 
1532:         $query = $this->rest->db->get(config_item('rest_access_table'));
1533: 
1534:         if ($query->num_rows > 0) {
1535:             return TRUE;
1536:         }
1537: 
1538:         return FALSE;
1539:     }
1540: 
1541: }
1542: 
API documentation generated by ApiGen