├── .github └── ISSUE_TEMPLATE │ └── bug_report.md ├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── language ├── bulgarian │ ├── index.html │ └── rest_controller_lang.php ├── dutch │ ├── index.html │ └── rest_controller_lang.php ├── english │ ├── index.html │ └── rest_controller_lang.php ├── french │ ├── index.html │ └── rest_controller_lang.php ├── german │ ├── index.html │ └── rest_controller_lang.php ├── greek │ └── rest_controller_lang.php ├── index.html ├── indonesia │ ├── index.html │ └── rest_controller_lang.php ├── italian │ ├── index.html │ └── rest_controller_lang.php ├── korean │ ├── index.html │ └── rest_controller_lang.php ├── portuguese-brazilian │ ├── index.html │ └── rest_controller_lang.php ├── romanian │ ├── index.html │ └── rest_controller_lang.php ├── serbian_cyr │ ├── index.html │ └── rest_controller_lang.php ├── serbian_lat │ ├── index.html │ └── rest_controller_lang.php ├── simplified-chinese │ ├── index.html │ └── rest_controller_lang.php ├── spanish │ ├── index.html │ └── rest_controller_lang.php ├── traditional-chinese │ ├── index.html │ └── rest_controller_lang.php └── turkish │ ├── index.html │ └── rest_controller_lang.php └── src ├── Format.php ├── RestController.php ├── auth ├── apikey.php ├── basic.php └── ldap.php ├── index.html └── rest.php /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Please provide either a cleanly formatted code snippet or a link to repo / gist with code that I can use to reproduce: 15 | 16 | ```php 17 | public function set_response($data = null, $http_code = null) 18 | { 19 | $this->response($data, $http_code, true); 20 | } 21 | ``` 22 | 23 | **Expected behavior** 24 | A clear and concise description of what you expected to happen. 25 | 26 | **Screenshots / Error Messages** 27 | If applicable, add screenshots and/or error messages to help explain your problem. 28 | 29 | **Environment (please complete the following information):** 30 | - PHP Version: [e.g. 7.2.1] 31 | - CodeIgniter Version [e.g. 4.0.1] 32 | - Version [e.g. 22] 33 | 34 | **Additional context** 35 | Add any other context about the problem here. 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | vendor 3 | .idea -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2012 - 2015 Phil Sturgeon, Chris Kacerguis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CodeIgniter RestServer 2 | 3 | [![StyleCI](https://github.styleci.io/repos/230589/shield?branch=master)](https://github.styleci.io/repos/230589) 4 | 5 | A fully RESTful server implementation for CodeIgniter using one library, one config file and one controller. 6 | 7 | ## Important!! 8 | 9 | CodeIgniter 4 includes REST support out of the box and therefore does not require the RestServer. 10 | 11 | See the documentation here: [RESTful Resource Handling](https://codeigniter4.github.io/userguide/incoming/restful.html) 12 | 13 | ## Requirements 14 | 15 | - PHP 7.2 or greater 16 | - CodeIgniter 3.1.11+ 17 | 18 | ## Installation 19 | 20 | ```sh 21 | composer require chriskacerguis/codeigniter-restserver 22 | ``` 23 | 24 | ## Usage 25 | 26 | CodeIgniter Rest Server is available on [Packagist](https://packagist.org/packages/chriskacerguis/codeigniter-restserver) (using semantic versioning), and installation via composer is the recommended way to install Codeigniter Rest Server. Just add this line to your `composer.json` file: 27 | 28 | ```json 29 | "chriskacerguis/codeigniter-restserver": "^3.1" 30 | ``` 31 | 32 | or run 33 | 34 | ```sh 35 | composer require chriskacerguis/codeigniter-restserver 36 | ``` 37 | 38 | Note that you will need to copy `rest.php` to your `config` directory (e.g. `application/config`) 39 | 40 | Step 1: Add this to your controller (should be before any of your code) 41 | 42 | ```php 43 | use chriskacerguis\RestServer\RestController; 44 | ``` 45 | 46 | Step 2: Extend your controller 47 | 48 | ```php 49 | class Example extends RestController 50 | ``` 51 | 52 | ## Basic GET example 53 | 54 | Here is a basic example. This controller, which should be saved as `Api.php`, can be called in two ways: 55 | 56 | * `http://domain/api/users/` will return the list of all users 57 | * `http://domain/api/users/id/1` will only return information about the user with id = 1 58 | 59 | ```php 60 | 0, 'name' => 'John', 'email' => 'john@example.com'], 78 | ['id' => 1, 'name' => 'Jim', 'email' => 'jim@example.com'], 79 | ]; 80 | 81 | $id = $this->get( 'id' ); 82 | 83 | if ( $id === null ) 84 | { 85 | // Check if the users data store contains users 86 | if ( $users ) 87 | { 88 | // Set the response and exit 89 | $this->response( $users, 200 ); 90 | } 91 | else 92 | { 93 | // Set the response and exit 94 | $this->response( [ 95 | 'status' => false, 96 | 'message' => 'No users were found' 97 | ], 404 ); 98 | } 99 | } 100 | else 101 | { 102 | if ( array_key_exists( $id, $users ) ) 103 | { 104 | $this->response( $users[$id], 200 ); 105 | } 106 | else 107 | { 108 | $this->response( [ 109 | 'status' => false, 110 | 'message' => 'No such user found' 111 | ], 404 ); 112 | } 113 | } 114 | } 115 | } 116 | ``` 117 | 118 | ## Extending supported formats 119 | 120 | If you need to be able to support more formats for replies, you can extend the 121 | `Format` class to add the required `to_...` methods 122 | 123 | 1. Extend the `RestController` class (in `libraries/MY_REST_Controller.php`) 124 | ```php 125 | format = new Format(); 137 | } 138 | } 139 | ``` 140 | 141 | 2. Extend the `Format` class (can be created as a CodeIgniter library in `libraries/Format.php`). 142 | Following is an example to add support for PDF output 143 | 144 | ```php 145 | _data; 155 | } 156 | 157 | if (is_array($data) || substr($data, 0, 4) != '%PDF') { 158 | $html = $this->to_html($data); 159 | 160 | // Use your PDF lib of choice. For example mpdf 161 | $mpdf = new \Mpdf\Mpdf(); 162 | $mpdf->WriteHTML($html); 163 | return $mpdf->Output('', 'S'); 164 | } 165 | 166 | return $data; 167 | } 168 | } 169 | ``` 170 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chriskacerguis/codeigniter-restserver", 3 | "description": "CI Rest Server", 4 | "type": "library", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Chris Kacerguis", 9 | "email": "chriskacerguis@gmail.com" 10 | } 11 | ], 12 | "minimum-stability": "dev", 13 | "autoload": { 14 | "psr-4": {"chriskacerguis\\RestServer\\": "src/"} 15 | }, 16 | "require": {} 17 | } 18 | -------------------------------------------------------------------------------- /language/bulgarian/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 403 Forbidden 5 | 6 | 7 | 8 |

Directory access is forbidden.

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /language/bulgarian/rest_controller_lang.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 403 Forbidden 5 | 6 | 7 | 8 |

Directory access is forbidden.

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /language/dutch/rest_controller_lang.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 403 Forbidden 5 | 6 | 7 | 8 |

Directory access is forbidden.

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /language/english/rest_controller_lang.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 403 Forbidden 5 | 6 | 7 | 8 |

Directory access is forbidden.

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /language/french/rest_controller_lang.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 403 Forbidden 5 | 6 | 7 | 8 |

Directory access is forbidden.

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /language/german/rest_controller_lang.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 403 Forbidden 5 | 6 | 7 | 8 |

Directory access is forbidden.

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /language/indonesia/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 403 Forbidden 5 | 6 | 7 | 8 |

Directory access is forbidden.

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /language/indonesia/rest_controller_lang.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 403 Forbidden 5 | 6 | 7 | 8 |

Directory access is forbidden.

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /language/italian/rest_controller_lang.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 403 Forbidden 5 | 6 | 7 | 8 |

Directory access is forbidden.

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /language/korean/rest_controller_lang.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 403 Forbidden 5 | 6 | 7 | 8 |

Directory access is forbidden.

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /language/portuguese-brazilian/rest_controller_lang.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 403 Forbidden 5 | 6 | 7 | 8 |

Directory access is forbidden.

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /language/romanian/rest_controller_lang.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 403 Forbidden 5 | 6 | 7 | 8 |

Directory access is forbidden.

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /language/serbian_cyr/rest_controller_lang.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 403 Forbidden 5 | 6 | 7 | 8 |

Directory access is forbidden.

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /language/serbian_lat/rest_controller_lang.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 403 Forbidden 5 | 6 | 7 | 8 |

Directory access is forbidden.

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /language/simplified-chinese/rest_controller_lang.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 403 Forbidden 5 | 6 | 7 | 8 |

Directory access is forbidden.

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /language/spanish/rest_controller_lang.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 403 Forbidden 5 | 6 | 7 | 8 |

Directory access is forbidden.

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /language/traditional-chinese/rest_controller_lang.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 403 Forbidden 5 | 6 | 7 | 8 |

Directory access is forbidden.

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /language/turkish/rest_controller_lang.php: -------------------------------------------------------------------------------- 1 | _CI = &get_instance(); 91 | 92 | // Load the inflector helper 93 | $this->_CI->load->helper('inflector'); 94 | 95 | // If the provided data is already formatted we should probably convert it to an array 96 | if ($from_type !== null) { 97 | if (method_exists($this, '_from_'.$from_type)) { 98 | $data = call_user_func([$this, '_from_'.$from_type], $data); 99 | } else { 100 | throw new Exception('Format class does not support conversion from "'.$from_type.'".'); 101 | } 102 | } 103 | 104 | // Set the member variable to the data passed 105 | $this->_data = $data; 106 | } 107 | 108 | /** 109 | * Create an instance of the format class 110 | * e.g: echo $this->format->factory(['foo' => 'bar'])->to_csv();. 111 | * 112 | * @param mixed $data Data to convert/parse 113 | * @param string $from_type Type to convert from e.g. json, csv, html 114 | * 115 | * @return object Instance of the format class 116 | */ 117 | public static function factory($data, $from_type = null) 118 | { 119 | // $class = __CLASS__; 120 | // return new $class(); 121 | 122 | return new static($data, $from_type); 123 | } 124 | 125 | // FORMATTING OUTPUT --------------------------------------------------------- 126 | 127 | /** 128 | * Format data as an array. 129 | * 130 | * @param mixed|null $data Optional data to pass, so as to override the data passed 131 | * to the constructor 132 | * 133 | * @return array Data parsed as an array; otherwise, an empty array 134 | */ 135 | public function to_array($data = null) 136 | { 137 | // If no data is passed as a parameter, then use the data passed 138 | // via the constructor 139 | if ($data === null && func_num_args() === 0) { 140 | $data = $this->_data; 141 | } 142 | 143 | // Cast as an array if not already 144 | if (is_array($data) === false) { 145 | $data = (array) $data; 146 | } 147 | 148 | $array = []; 149 | foreach ((array) $data as $key => $value) { 150 | if (is_object($value) === true || is_array($value) === true) { 151 | $array[$key] = $this->to_array($value); 152 | } else { 153 | $array[$key] = $value; 154 | } 155 | } 156 | 157 | return $array; 158 | } 159 | 160 | /** 161 | * Format data as XML. 162 | * 163 | * @param mixed|null $data Optional data to pass, so as to override the data passed 164 | * to the constructor 165 | * @param null $structure 166 | * @param string $basenode 167 | * 168 | * @return mixed 169 | */ 170 | public function to_xml($data = null, $structure = null, $basenode = 'xml') 171 | { 172 | if ($data === null && func_num_args() === 0) { 173 | $data = $this->_data; 174 | } 175 | 176 | if ($structure === null) { 177 | $structure = simplexml_load_string("<$basenode />"); 178 | } 179 | 180 | // Force it to be something useful 181 | if (is_array($data) === false && is_object($data) === false) { 182 | $data = (array) $data; 183 | } 184 | 185 | foreach ($data as $key => $value) { 186 | //change false/true to 0/1 187 | if (is_bool($value)) { 188 | $value = (int) $value; 189 | } 190 | 191 | // no numeric keys in our xml please! 192 | if (is_numeric($key)) { 193 | // make string key... 194 | $key = (singular($basenode) != $basenode) ? singular($basenode) : 'item'; 195 | } 196 | 197 | // replace anything not alpha numeric 198 | $key = preg_replace('/[^a-z_\-0-9]/i', '', $key); 199 | 200 | if ($key === '_attributes' && (is_array($value) || is_object($value))) { 201 | $attributes = $value; 202 | if (is_object($attributes)) { 203 | $attributes = get_object_vars($attributes); 204 | } 205 | 206 | foreach ($attributes as $attribute_name => $attribute_value) { 207 | $structure->addAttribute($attribute_name, $attribute_value); 208 | } 209 | } 210 | // if there is another array found recursively call this function 211 | elseif (is_array($value) || is_object($value)) { 212 | $node = $structure->addChild($key); 213 | 214 | // recursive call. 215 | $this->to_xml($value, $node, $key); 216 | } else { 217 | // add single node. 218 | $value = htmlspecialchars(html_entity_decode($value ?? '', ENT_QUOTES, 'UTF-8'), ENT_QUOTES, 'UTF-8'); 219 | 220 | $structure->addChild($key, $value); 221 | } 222 | } 223 | 224 | return $structure->asXML(); 225 | } 226 | 227 | /** 228 | * Format data as HTML. 229 | * 230 | * @param mixed|null $data Optional data to pass, so as to override the data passed 231 | * to the constructor 232 | * 233 | * @return mixed 234 | */ 235 | public function to_html($data = null) 236 | { 237 | // If no data is passed as a parameter, then use the data passed 238 | // via the constructor 239 | if ($data === null && func_num_args() === 0) { 240 | $data = $this->_data; 241 | } 242 | 243 | // Cast as an array if not already 244 | if (is_array($data) === false) { 245 | $data = (array) $data; 246 | } 247 | 248 | // Check if it's a multi-dimensional array 249 | if (isset($data[0]) && count($data) !== count($data, COUNT_RECURSIVE)) { 250 | // Multi-dimensional array 251 | $headings = array_keys($data[0]); 252 | } else { 253 | // Single array 254 | $headings = array_keys($data); 255 | $data = [$data]; 256 | } 257 | 258 | // Load the table library 259 | $this->_CI->load->library('table'); 260 | 261 | $this->_CI->table->set_heading($headings); 262 | 263 | foreach ($data as $row) { 264 | // Suppressing the "array to string conversion" notice 265 | // Keep the "evil" @ here 266 | $row = @array_map('strval', $row); 267 | 268 | $this->_CI->table->add_row($row); 269 | } 270 | 271 | return $this->_CI->table->generate(); 272 | } 273 | 274 | /** 275 | * @link http://www.metashock.de/2014/02/create-csv-file-in-memory-php/ 276 | * 277 | * @param mixed|null $data Optional data to pass, so as to override the data passed 278 | * to the constructor 279 | * @param string $delimiter The optional delimiter parameter sets the field 280 | * delimiter (one character only). NULL will use the default value (,) 281 | * @param string $enclosure The optional enclosure parameter sets the field 282 | * enclosure (one character only). NULL will use the default value (") 283 | * 284 | * @return string A csv string 285 | */ 286 | public function to_csv($data = null, $delimiter = ',', $enclosure = '"') 287 | { 288 | // Use a threshold of 1 MB (1024 * 1024) 289 | $handle = fopen('php://temp/maxmemory:1048576', 'w'); 290 | if ($handle === false) { 291 | return; 292 | } 293 | 294 | // If no data is passed as a parameter, then use the data passed 295 | // via the constructor 296 | if ($data === null && func_num_args() === 0) { 297 | $data = $this->_data; 298 | } 299 | 300 | // If NULL, then set as the default delimiter 301 | if ($delimiter === null) { 302 | $delimiter = ','; 303 | } 304 | 305 | // If NULL, then set as the default enclosure 306 | if ($enclosure === null) { 307 | $enclosure = '"'; 308 | } 309 | 310 | // Cast as an array if not already 311 | if (is_array($data) === false) { 312 | $data = (array) $data; 313 | } 314 | 315 | // Check if it's a multi-dimensional array 316 | if (isset($data[0]) && count($data) !== count($data, COUNT_RECURSIVE)) { 317 | // Multi-dimensional array 318 | $headings = array_keys($data[0]); 319 | } else { 320 | // Single array 321 | $headings = array_keys($data); 322 | $data = [$data]; 323 | } 324 | 325 | // Apply the headings 326 | fputcsv($handle, $headings, $delimiter, $enclosure); 327 | 328 | foreach ($data as $record) { 329 | // If the record is not an array, then break. This is because the 2nd param of 330 | // fputcsv() should be an array 331 | if (is_array($record) === false) { 332 | break; 333 | } 334 | 335 | // Suppressing the "array to string conversion" notice. 336 | // Keep the "evil" @ here. 337 | $record = @array_map('strval', $record); 338 | 339 | // Returns the length of the string written or FALSE 340 | fputcsv($handle, $record, $delimiter, $enclosure); 341 | } 342 | 343 | // Reset the file pointer 344 | rewind($handle); 345 | 346 | // Retrieve the csv contents 347 | $csv = stream_get_contents($handle); 348 | 349 | // Close the handle 350 | fclose($handle); 351 | 352 | // Convert UTF-8 encoding to UTF-16LE which is supported by MS Excel 353 | $csv = mb_convert_encoding($csv, 'UTF-16LE', 'UTF-8'); 354 | 355 | return $csv; 356 | } 357 | 358 | /** 359 | * Encode data as json. 360 | * 361 | * @param mixed|null $data Optional data to pass, so as to override the data passed 362 | * to the constructor 363 | * 364 | * @return string Json representation of a value 365 | */ 366 | public function to_json($data = null) 367 | { 368 | // If no data is passed as a parameter, then use the data passed 369 | // via the constructor 370 | if ($data === null && func_num_args() === 0) { 371 | $data = $this->_data; 372 | } 373 | 374 | // Get the callback parameter (if set) 375 | $callback = $this->_CI->input->get('callback'); 376 | 377 | if (empty($callback) === true) { 378 | return json_encode($data, JSON_UNESCAPED_UNICODE); 379 | } 380 | 381 | // We only honour a jsonp callback which are valid javascript identifiers 382 | elseif (preg_match('/^[a-z_\$][a-z0-9\$_]*(\.[a-z_\$][a-z0-9\$_]*)*$/i', $callback)) { 383 | // Return the data as encoded json with a callback 384 | return $callback.'('.json_encode($data, JSON_UNESCAPED_UNICODE).');'; 385 | } 386 | 387 | // An invalid jsonp callback function provided. 388 | // Though I don't believe this should be hardcoded here 389 | $data['warning'] = 'INVALID JSONP CALLBACK: '.$callback; 390 | 391 | return json_encode($data, JSON_UNESCAPED_UNICODE); 392 | } 393 | 394 | /** 395 | * Encode data as a serialized array. 396 | * 397 | * @param mixed|null $data Optional data to pass, so as to override the data passed 398 | * to the constructor 399 | * 400 | * @return string Serialized data 401 | */ 402 | public function to_serialized($data = null) 403 | { 404 | // If no data is passed as a parameter, then use the data passed 405 | // via the constructor 406 | if ($data === null && func_num_args() === 0) { 407 | $data = $this->_data; 408 | } 409 | 410 | return serialize($data); 411 | } 412 | 413 | /** 414 | * Format data using a PHP structure. 415 | * 416 | * @param mixed|null $data Optional data to pass, so as to override the data passed 417 | * to the constructor 418 | * 419 | * @return mixed String representation of a variable 420 | */ 421 | public function to_php($data = null) 422 | { 423 | // If no data is passed as a parameter, then use the data passed 424 | // via the constructor 425 | if ($data === null && func_num_args() === 0) { 426 | $data = $this->_data; 427 | } 428 | 429 | return var_export($data, true); 430 | } 431 | 432 | // INTERNAL FUNCTIONS 433 | 434 | /** 435 | * @param string $data XML string 436 | * 437 | * @return array XML element object; otherwise, empty array 438 | */ 439 | protected function _from_xml($data) 440 | { 441 | return $data ? (array) simplexml_load_string($data, 'SimpleXMLElement', LIBXML_NOCDATA) : []; 442 | } 443 | 444 | /** 445 | * @param string $data CSV string 446 | * @param string $delimiter The optional delimiter parameter sets the field 447 | * delimiter (one character only). NULL will use the default value (,) 448 | * @param string $enclosure The optional enclosure parameter sets the field 449 | * enclosure (one character only). NULL will use the default value (") 450 | * 451 | * @return array A multi-dimensional array with the outer array being the number of rows 452 | * and the inner arrays the individual fields 453 | */ 454 | protected function _from_csv($data, $delimiter = ',', $enclosure = '"') 455 | { 456 | // If NULL, then set as the default delimiter 457 | if ($delimiter === null) { 458 | $delimiter = ','; 459 | } 460 | 461 | // If NULL, then set as the default enclosure 462 | if ($enclosure === null) { 463 | $enclosure = '"'; 464 | } 465 | 466 | return str_getcsv($data, $delimiter, $enclosure); 467 | } 468 | 469 | /** 470 | * @param string $data Encoded json string 471 | * 472 | * @return mixed Decoded json string with leading and trailing whitespace removed 473 | */ 474 | protected function _from_json($data) 475 | { 476 | return json_decode(trim($data)); 477 | } 478 | 479 | /** 480 | * @param string $data Data to unserialize 481 | * 482 | * @return mixed Unserialized data 483 | */ 484 | protected function _from_serialize($data) 485 | { 486 | return unserialize(trim($data)); 487 | } 488 | 489 | /** 490 | * @param string $data Data to trim leading and trailing whitespace 491 | * 492 | * @return string Data with leading and trailing whitespace removed 493 | */ 494 | protected function _from_php($data) 495 | { 496 | return trim($data); 497 | } 498 | } 499 | -------------------------------------------------------------------------------- /src/RestController.php: -------------------------------------------------------------------------------- 1 | 'application/json', 181 | 'array' => 'application/json', 182 | 'csv' => 'application/csv', 183 | 'html' => 'text/html', 184 | 'jsonp' => 'application/javascript', 185 | 'php' => 'text/plain', 186 | 'serialized' => 'application/vnd.php.serialized', 187 | 'xml' => 'application/xml', 188 | ]; 189 | 190 | /** 191 | * Information about the current API user. 192 | * 193 | * @var object 194 | */ 195 | protected $_apiuser; 196 | 197 | /** 198 | * Whether or not to perform a CORS check and apply CORS headers to the request. 199 | * 200 | * @var bool 201 | */ 202 | protected $check_cors = null; 203 | 204 | /** 205 | * Enable XSS flag 206 | * Determines whether the XSS filter is always active when 207 | * GET, OPTIONS, HEAD, POST, PUT, DELETE and PATCH data is encountered 208 | * Set automatically based on config setting. 209 | * 210 | * @var bool 211 | */ 212 | protected $_enable_xss = false; 213 | 214 | private $is_valid_request = true; 215 | 216 | /** 217 | * Common HTTP status codes and their respective description. 218 | * 219 | * @link http://www.restapitutorial.com/httpstatuscodes.html 220 | */ 221 | const HTTP_OK = 200; 222 | const HTTP_CREATED = 201; 223 | const HTTP_NOT_MODIFIED = 304; 224 | const HTTP_BAD_REQUEST = 400; 225 | const HTTP_UNAUTHORIZED = 401; 226 | const HTTP_FORBIDDEN = 403; 227 | const HTTP_NOT_FOUND = 404; 228 | const HTTP_METHOD_NOT_ALLOWED = 405; 229 | const HTTP_NOT_ACCEPTABLE = 406; 230 | const HTTP_INTERNAL_ERROR = 500; 231 | 232 | /** 233 | * @var Format 234 | */ 235 | protected $format; 236 | 237 | /** 238 | * @var bool 239 | */ 240 | protected $auth_override; 241 | 242 | /** 243 | * Extend this function to apply additional checking early on in the process. 244 | * 245 | * @return void 246 | */ 247 | protected function early_checks() 248 | { 249 | } 250 | 251 | /** 252 | * Constructor for the REST API. 253 | * 254 | * @param string $config Configuration filename minus the file extension 255 | * e.g: my_rest.php is passed as 'my_rest' 256 | */ 257 | public function __construct($config = 'rest') 258 | { 259 | parent::__construct(); 260 | 261 | // Set the default value of global xss filtering. Same approach as CodeIgniter 3 262 | $this->_enable_xss = ($this->config->item('global_xss_filtering') === true); 263 | 264 | // Don't try to parse template variables like {elapsed_time} and {memory_usage} 265 | // when output is displayed for not damaging data accidentally 266 | $this->output->parse_exec_vars = false; 267 | 268 | // Load the rest.php configuration file 269 | $this->get_local_config($config); 270 | 271 | // Log the loading time to the log table 272 | if ($this->config->item('rest_enable_logging') === true) { 273 | // Start the timer for how long the request takes 274 | $this->_start_rtime = microtime(true); 275 | } 276 | 277 | // Determine supported output formats from configuration 278 | $supported_formats = $this->config->item('rest_supported_formats'); 279 | 280 | // Validate the configuration setting output formats 281 | if (empty($supported_formats)) { 282 | $supported_formats = []; 283 | } 284 | 285 | if (!is_array($supported_formats)) { 286 | $supported_formats = [$supported_formats]; 287 | } 288 | 289 | // Add silently the default output format if it is missing 290 | $default_format = $this->_get_default_output_format(); 291 | if (!in_array($default_format, $supported_formats)) { 292 | $supported_formats[] = $default_format; 293 | } 294 | 295 | // Now update $this->_supported_formats 296 | $this->_supported_formats = array_intersect_key($this->_supported_formats, array_flip($supported_formats)); 297 | 298 | // Get the language 299 | $language = $this->config->item('rest_language'); 300 | if ($language === null) { 301 | $language = 'english'; 302 | } 303 | 304 | // Load the language file 305 | $this->lang->load('rest_controller', $language, false, true, __DIR__.'/../'); 306 | 307 | // Initialise the response, request and rest objects 308 | $this->request = new stdClass(); 309 | $this->response = new stdClass(); 310 | $this->rest = new stdClass(); 311 | 312 | // Check to see if the current IP address is blacklisted 313 | if ($this->config->item('rest_ip_blacklist_enabled') === true) { 314 | $this->_check_blacklist_auth(); 315 | } 316 | 317 | // Determine whether the connection is HTTPS 318 | $this->request->ssl = is_https(); 319 | 320 | // How is this request being made? GET, POST, PATCH, DELETE, INSERT, PUT, HEAD or OPTIONS 321 | $this->request->method = $this->_detect_method(); 322 | 323 | // Check for CORS access request 324 | $check_cors = $this->config->item('check_cors'); 325 | if ($check_cors === true) { 326 | $this->_check_cors(); 327 | } 328 | 329 | // Create an argument container if it doesn't exist e.g. _get_args 330 | if (isset($this->{'_'.$this->request->method.'_args'}) === false) { 331 | $this->{'_'.$this->request->method.'_args'} = []; 332 | } 333 | 334 | // Set up the query parameters 335 | $this->_parse_query(); 336 | 337 | // Set up the GET variables 338 | $this->_get_args = array_merge($this->_get_args, $this->uri->ruri_to_assoc()); 339 | 340 | // Try to find a format for the request (means we have a request body) 341 | $this->request->format = $this->_detect_input_format(); 342 | 343 | // Not all methods have a body attached with them 344 | $this->request->body = null; 345 | 346 | $this->{'_parse_'.$this->request->method}(); 347 | 348 | // Fix parse method return arguments null 349 | if ($this->{'_'.$this->request->method.'_args'} === null) { 350 | $this->{'_'.$this->request->method.'_args'} = []; 351 | } 352 | 353 | // Which format should the data be returned in? 354 | $this->response->format = $this->_detect_output_format(); 355 | 356 | // Which language should the data be returned in? 357 | $this->response->lang = $this->_detect_lang(); 358 | 359 | // Now we know all about our request, let's try and parse the body if it exists 360 | if ($this->request->format && $this->request->body) { 361 | $this->request->body = Format::factory($this->request->body, $this->request->format)->to_array(); 362 | 363 | // Assign payload arguments to proper method container 364 | $this->{'_'.$this->request->method.'_args'} = $this->request->body; 365 | } 366 | 367 | //get header vars 368 | $this->_head_args = $this->input->request_headers(); 369 | 370 | // Merge both for one mega-args variable 371 | $this->_args = array_merge( 372 | $this->_get_args, 373 | $this->_options_args, 374 | $this->_patch_args, 375 | $this->_head_args, 376 | $this->_put_args, 377 | $this->_post_args, 378 | $this->_delete_args, 379 | $this->{'_'.$this->request->method.'_args'} 380 | ); 381 | 382 | // Extend this function to apply additional checking early on in the process 383 | $this->early_checks(); 384 | 385 | // Load DB if its enabled 386 | if ($this->config->item('rest_database_group') && ($this->config->item('rest_enable_keys') || $this->config->item('rest_enable_logging'))) { 387 | $this->rest->db = $this->load->database($this->config->item('rest_database_group'), true); 388 | } 389 | 390 | // Use whatever database is in use (isset returns FALSE) 391 | elseif (property_exists($this, 'db')) { 392 | $this->rest->db = $this->db; 393 | } 394 | 395 | // Check if there is a specific auth type for the current class/method 396 | // _auth_override_check could exit so we need $this->rest->db initialized before 397 | $this->auth_override = $this->_auth_override_check(); 398 | 399 | // Checking for keys? GET TO WorK! 400 | // Skip keys test for $config['auth_override_class_method']['class'['method'] = 'none' 401 | if ($this->config->item('rest_enable_keys') && $this->auth_override !== true) { 402 | $this->_allow = $this->_detect_api_key(); 403 | } 404 | 405 | // Only allow ajax requests 406 | if ($this->input->is_ajax_request() === false && $this->config->item('rest_ajax_only')) { 407 | // Display an error response 408 | $this->response([ 409 | $this->config->item('rest_status_field_name') => false, 410 | $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_ajax_only'), 411 | ], self::HTTP_NOT_ACCEPTABLE); 412 | } 413 | 414 | // When there is no specific override for the current class/method, use the default auth value set in the config 415 | if ($this->auth_override === false && 416 | (!($this->config->item('rest_enable_keys') && $this->_allow === true) || 417 | ($this->config->item('allow_auth_and_keys') === true && $this->_allow === true))) { 418 | $rest_auth = strtolower($this->config->item('rest_auth')); 419 | switch ($rest_auth) { 420 | case 'basic': 421 | $this->_prepare_basic_auth(); 422 | break; 423 | case 'digest': 424 | $this->_prepare_digest_auth(); 425 | break; 426 | case 'session': 427 | $this->_check_php_session(); 428 | break; 429 | } 430 | } 431 | } 432 | 433 | /** 434 | * Does the auth stuff. 435 | */ 436 | private function do_auth($method = false) 437 | { 438 | // If we don't want to do auth, then just return true 439 | if ($method === false) { 440 | return true; 441 | } 442 | 443 | if (file_exists(__DIR__.'/auth/'.$method.'.php')) { 444 | include __DIR__.'/auth/'.$method.'.php'; 445 | } 446 | } 447 | 448 | /** 449 | * @param $config_file 450 | */ 451 | private function get_local_config($config_file) 452 | { 453 | if (file_exists(APPPATH.'config/'.$config_file.'.php')) { 454 | $this->load->config($config_file, false); 455 | } else { 456 | if (file_exists(__DIR__.'/'.$config_file.'.php')) { 457 | $config = []; 458 | include __DIR__.'/'.$config_file.'.php'; 459 | foreach ($config as $key => $value) { 460 | $this->config->set_item($key, $value); 461 | } 462 | } 463 | } 464 | } 465 | 466 | /** 467 | * De-constructor. 468 | * 469 | * @author Chris Kacerguis 470 | * 471 | * @return void 472 | */ 473 | public function __destruct() 474 | { 475 | // Log the loading time to the log table 476 | if ($this->config->item('rest_enable_logging') === true) { 477 | // Get the current timestamp 478 | $this->_end_rtime = microtime(true); 479 | 480 | $this->_log_access_time(); 481 | } 482 | } 483 | 484 | /** 485 | * Requests are not made to methods directly, the request will be for 486 | * an "object". This simply maps the object and method to the correct 487 | * Controller method. 488 | * 489 | * @param string $object_called 490 | * @param array $arguments The arguments passed to the controller method 491 | * 492 | * @throws Exception 493 | */ 494 | public function _remap($object_called, $arguments = []) 495 | { 496 | // Should we answer if not over SSL? 497 | if ($this->config->item('force_https') && $this->request->ssl === false) { 498 | $this->response([ 499 | $this->config->item('rest_status_field_name') => false, 500 | $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_unsupported'), 501 | ], self::HTTP_FORBIDDEN); 502 | } 503 | 504 | // Remove the supported format from the function name e.g. index.json => index 505 | $object_called = preg_replace('/^(.*)\.(?:'.implode('|', array_keys($this->_supported_formats)).')$/', '$1', $object_called); 506 | 507 | $controller_method = $object_called.'_'.$this->request->method; 508 | // Does this method exist? If not, try executing an index method 509 | if (!method_exists($this, $controller_method)) { 510 | $controller_method = 'index_'.$this->request->method; 511 | array_unshift($arguments, $object_called); 512 | } 513 | 514 | // Do we want to log this method (if allowed by config)? 515 | $log_method = !(isset($this->methods[$controller_method]['log']) && $this->methods[$controller_method]['log'] === false); 516 | 517 | // Use keys for this method? 518 | $use_key = !(isset($this->methods[$controller_method]['key']) && $this->methods[$controller_method]['key'] === false); 519 | 520 | // They provided a key, but it wasn't valid, so get them out of here 521 | if ($this->config->item('rest_enable_keys') && $use_key && $this->_allow === false) { 522 | if ($this->config->item('rest_enable_logging') && $log_method) { 523 | $this->_log_request(); 524 | } 525 | 526 | // fix cross site to option request error 527 | if ($this->request->method == 'options') { 528 | exit; 529 | } 530 | 531 | $this->response([ 532 | $this->config->item('rest_status_field_name') => false, 533 | $this->config->item('rest_message_field_name') => sprintf($this->lang->line('text_rest_invalid_api_key'), $this->rest->key), 534 | ], self::HTTP_FORBIDDEN); 535 | } 536 | 537 | // Check to see if this key has access to the requested controller 538 | if ($this->config->item('rest_enable_keys') && $use_key && empty($this->rest->key) === false && $this->_check_access() === false) { 539 | if ($this->config->item('rest_enable_logging') && $log_method) { 540 | $this->_log_request(); 541 | } 542 | 543 | $this->response([ 544 | $this->config->item('rest_status_field_name') => false, 545 | $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_api_key_unauthorized'), 546 | ], self::HTTP_UNAUTHORIZED); 547 | } 548 | 549 | // Sure it exists, but can they do anything with it? 550 | if (!method_exists($this, $controller_method)) { 551 | $this->response([ 552 | $this->config->item('rest_status_field_name') => false, 553 | $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_unknown_method'), 554 | ], self::HTTP_METHOD_NOT_ALLOWED); 555 | } 556 | 557 | // Doing key related stuff? Can only do it if they have a key right? 558 | if ($this->config->item('rest_enable_keys') && empty($this->rest->key) === false) { 559 | // Check the limit 560 | if ($this->config->item('rest_enable_limits') && $this->_check_limit($controller_method) === false) { 561 | $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')]; 562 | $this->response($response, self::HTTP_UNAUTHORIZED); 563 | } 564 | 565 | // If no level is set use 0, they probably aren't using permissions 566 | $level = isset($this->methods[$controller_method]['level']) ? $this->methods[$controller_method]['level'] : 0; 567 | 568 | // If no level is set, or it is lower than/equal to the key's level 569 | $authorized = $level <= $this->rest->level; 570 | // IM TELLIN! 571 | if ($this->config->item('rest_enable_logging') && $log_method) { 572 | $this->_log_request($authorized); 573 | } 574 | if ($authorized === false) { 575 | // They don't have good enough perms 576 | $response = [$this->config->item('rest_status_field_name') => false, $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_api_key_permissions')]; 577 | $this->response($response, self::HTTP_UNAUTHORIZED); 578 | } 579 | } 580 | 581 | //check request limit by ip without login 582 | elseif ($this->config->item('rest_limits_method') == 'IP_ADDRESS' && $this->config->item('rest_enable_limits') && $this->_check_limit($controller_method) === false) { 583 | $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')]; 584 | $this->response($response, self::HTTP_UNAUTHORIZED); 585 | } 586 | 587 | // No key stuff, but record that stuff is happening 588 | elseif ($this->config->item('rest_enable_logging') && $log_method) { 589 | $this->_log_request($authorized = true); 590 | } 591 | 592 | // Call the controller method and passed arguments 593 | try { 594 | if ($this->is_valid_request) { 595 | call_user_func_array([$this, $controller_method], $arguments); 596 | } 597 | } catch (Exception $ex) { 598 | if ($this->config->item('rest_handle_exceptions') === false) { 599 | throw $ex; 600 | } 601 | 602 | // If the method doesn't exist, then the error will be caught and an error response shown 603 | $_error = &load_class('Exceptions', 'core'); 604 | $_error->show_exception($ex); 605 | } 606 | } 607 | 608 | /** 609 | * Takes mixed data and optionally a status code, then creates the response. 610 | * 611 | * @param array|null $data Data to output to the user 612 | * @param int|null $http_code HTTP status code 613 | * @param bool $continue TRUE to flush the response to the client and continue 614 | * running the script; otherwise, exit 615 | */ 616 | public function response($data = null, $http_code = null, $continue = false) 617 | { 618 | //if profiling enabled then print profiling data 619 | $isProfilingEnabled = $this->config->item('enable_profiling'); 620 | if (!$isProfilingEnabled) { 621 | ob_start(); 622 | // If the HTTP status is not NULL, then cast as an integer 623 | if ($http_code !== null) { 624 | // So as to be safe later on in the process 625 | $http_code = (int) $http_code; 626 | } 627 | 628 | // Set the output as NULL by default 629 | $output = null; 630 | 631 | // If data is NULL and no HTTP status code provided, then display, error and exit 632 | if ($data === null && $http_code === null) { 633 | $http_code = self::HTTP_NOT_FOUND; 634 | } 635 | 636 | // If data is not NULL and a HTTP status code provided, then continue 637 | elseif ($data !== null) { 638 | // If the format method exists, call and return the output in that format 639 | $formatter = null; 640 | if ($this->format && method_exists($this->format, 'to_'.$this->response->format)) { 641 | $formatter = $this->format::factory($data); 642 | } elseif (method_exists(Format::class, 'to_'.$this->response->format)) { 643 | $formatter = Format::factory($data); 644 | } 645 | 646 | if ($formatter !== null) { 647 | // CORB protection 648 | // First, get the output content. 649 | $output = $formatter->{'to_'.$this->response->format}(); 650 | 651 | // Set the format header 652 | // Then, check if the client asked for a callback, and if the output contains this callback : 653 | if (isset($this->_get_args['callback']) && $this->response->format == 'json' && preg_match('/^'.$this->_get_args['callback'].'/', $output)) { 654 | $this->output->set_content_type($this->_supported_formats['jsonp'], strtolower($this->config->item('charset'))); 655 | } else { 656 | $this->output->set_content_type($this->_supported_formats[$this->response->format], strtolower($this->config->item('charset'))); 657 | } 658 | 659 | // An array must be parsed as a string, so as not to cause an array to string error 660 | // Json is the most appropriate form for such a data type 661 | if ($this->response->format === 'array') { 662 | $output = Format::factory($output)->{'to_json'}(); 663 | } 664 | } else { 665 | // If an array or object, then parse as a json, so as to be a 'string' 666 | if (is_array($data) || is_object($data)) { 667 | $data = Format::factory($data)->{'to_json'}(); 668 | } 669 | 670 | // Format is not supported, so output the raw data as a string 671 | $output = $data; 672 | } 673 | } 674 | 675 | // If not greater than zero, then set the HTTP status code as 200 by default 676 | // Though perhaps 500 should be set instead, for the developer not passing a 677 | // correct HTTP status code 678 | $http_code > 0 || $http_code = self::HTTP_OK; 679 | 680 | $this->output->set_status_header($http_code); 681 | 682 | // JC: Log response code only if rest logging enabled 683 | if ($this->config->item('rest_enable_logging') === true) { 684 | $this->_log_response_code($http_code); 685 | } 686 | 687 | // Output the data 688 | $this->output->set_output($output); 689 | 690 | if ($continue === false) { 691 | // Display the data and exit execution 692 | $this->output->_display(); 693 | exit; 694 | } else { 695 | if (is_callable('fastcgi_finish_request')) { 696 | // Terminates connection and returns response to client on PHP-FPM. 697 | $this->output->_display(); 698 | ob_end_flush(); 699 | fastcgi_finish_request(); 700 | ignore_user_abort(true); 701 | } else { 702 | // Legacy compatibility. 703 | ob_end_flush(); 704 | } 705 | } 706 | ob_end_flush(); 707 | // Otherwise dump the output automatically 708 | } else { 709 | echo json_encode($data); 710 | } 711 | } 712 | 713 | /** 714 | * Takes mixed data and optionally a status code, then creates the response 715 | * within the buffers of the Output class. The response is sent to the client 716 | * lately by the framework, after the current controller's method termination. 717 | * All the hooks after the controller's method termination are executable. 718 | * 719 | * @param array|null $data Data to output to the user 720 | * @param int|null $http_code HTTP status code 721 | */ 722 | public function set_response($data = null, $http_code = null) 723 | { 724 | $this->response($data, $http_code, true); 725 | } 726 | 727 | /** 728 | * Get the input format e.g. json or xml. 729 | * 730 | * @return string|null Supported input format; otherwise, NULL 731 | */ 732 | protected function _detect_input_format() 733 | { 734 | // Get the CONTENT-TYPE value from the SERVER variable 735 | $content_type = $this->input->server('CONTENT_TYPE'); 736 | 737 | if (empty($content_type) === false) { 738 | // If a semi-colon exists in the string, then explode by ; and get the value of where 739 | // the current array pointer resides. This will generally be the first element of the array 740 | $content_type = (strpos($content_type, ';') !== false ? current(explode(';', $content_type)) : $content_type); 741 | 742 | // Check all formats against the CONTENT-TYPE header 743 | foreach ($this->_supported_formats as $type => $mime) { 744 | // $type = format e.g. csv 745 | // $mime = mime type e.g. application/csv 746 | 747 | // If both the mime types match, then return the format 748 | if ($content_type === $mime) { 749 | return $type; 750 | } 751 | } 752 | } 753 | } 754 | 755 | /** 756 | * Gets the default format from the configuration. Fallbacks to 'json' 757 | * if the corresponding configuration option $config['rest_default_format'] 758 | * is missing or is empty. 759 | * 760 | * @return string The default supported input format 761 | */ 762 | protected function _get_default_output_format() 763 | { 764 | $default_format = (string) $this->config->item('rest_default_format'); 765 | 766 | return $default_format === '' ? 'json' : $default_format; 767 | } 768 | 769 | /** 770 | * Detect which format should be used to output the data. 771 | * 772 | * @return mixed|null|string Output format 773 | */ 774 | protected function _detect_output_format() 775 | { 776 | // Concatenate formats to a regex pattern e.g. \.(csv|json|xml) 777 | $pattern = '/\.('.implode('|', array_keys($this->_supported_formats)).')($|\/)/'; 778 | $matches = []; 779 | 780 | // Check if a file extension is used e.g. http://example.com/api/index.json?param1=param2 781 | if (preg_match($pattern, $this->uri->uri_string(), $matches)) { 782 | return $matches[1]; 783 | } 784 | 785 | // Get the format parameter named as 'format' 786 | if (isset($this->_get_args['format'])) { 787 | $format = strtolower($this->_get_args['format']); 788 | 789 | if (isset($this->_supported_formats[$format]) === true) { 790 | return $format; 791 | } 792 | } 793 | 794 | // Get the HTTP_ACCEPT server variable 795 | $http_accept = $this->input->server('HTTP_ACCEPT'); 796 | 797 | // Otherwise, check the HTTP_ACCEPT server variable 798 | if ($this->config->item('rest_ignore_http_accept') === false && $http_accept !== null) { 799 | // Check all formats against the HTTP_ACCEPT header 800 | foreach (array_keys($this->_supported_formats) as $format) { 801 | // Has this format been requested? 802 | if (strpos($http_accept, $format) !== false) { 803 | if ($format !== 'html' && $format !== 'xml') { 804 | // If not HTML or XML assume it's correct 805 | return $format; 806 | } elseif ($format === 'html' && strpos($http_accept, 'xml') === false) { 807 | // HTML or XML have shown up as a match 808 | // If it is truly HTML, it wont want any XML 809 | return $format; 810 | } elseif ($format === 'xml' && strpos($http_accept, 'html') === false) { 811 | // If it is truly XML, it wont want any HTML 812 | return $format; 813 | } 814 | } 815 | } 816 | } 817 | 818 | // Check if the controller has a default format 819 | if (empty($this->rest_format) === false) { 820 | return $this->rest_format; 821 | } 822 | 823 | // Obtain the default format from the configuration 824 | return $this->_get_default_output_format(); 825 | } 826 | 827 | /** 828 | * Get the HTTP request string e.g. get or post. 829 | * 830 | * @return string|null Supported request method as a lowercase string; otherwise, NULL if not supported 831 | */ 832 | protected function _detect_method() 833 | { 834 | // Declare a variable to store the method 835 | $method = null; 836 | 837 | // Determine whether the 'enable_emulate_request' setting is enabled 838 | if ($this->config->item('enable_emulate_request') === true) { 839 | $method = $this->input->post('_method'); 840 | if ($method === null) { 841 | $method = $this->input->server('HTTP_X_HTTP_METHOD_OVERRIDE'); 842 | } 843 | 844 | if ($method !== null) { 845 | $method = strtolower($method); 846 | } 847 | } 848 | 849 | if (empty($method)) { 850 | // Get the request method as a lowercase string 851 | $method = $this->input->method(); 852 | } 853 | 854 | return in_array($method, $this->allowed_http_methods) && method_exists($this, '_parse_'.$method) ? $method : 'get'; 855 | } 856 | 857 | /** 858 | * See if the user has provided an API key. 859 | * 860 | * @return bool 861 | */ 862 | protected function _detect_api_key() 863 | { 864 | // Get the api key name variable set in the rest config file 865 | $api_key_variable = $this->config->item('rest_key_name'); 866 | 867 | // Work out the name of the SERVER entry based on config 868 | $key_name = 'HTTP_'.strtoupper(str_replace('-', '_', $api_key_variable)); 869 | 870 | $this->rest->key = null; 871 | $this->rest->level = null; 872 | $this->rest->user_id = null; 873 | $this->rest->ignore_limits = false; 874 | 875 | // Find the key from server or arguments 876 | if ($key = isset($this->_args[$api_key_variable]) ? $this->_args[$api_key_variable] : $this->input->server($key_name)) { 877 | $this->rest->key = $key; 878 | 879 | if (!($row = $this->rest->db->where($this->config->item('rest_key_column'), $key)->get($this->config->item('rest_keys_table'))->row())) { 880 | return false; 881 | } 882 | 883 | if ($this->config->item('rest_keys_expire') === true && $row->{$this->config->item('rest_keys_expiry_column')} < time()) { 884 | return false; 885 | } 886 | 887 | isset($row->user_id) && $this->rest->user_id = $row->user_id; 888 | isset($row->level) && $this->rest->level = $row->level; 889 | isset($row->ignore_limits) && $this->rest->ignore_limits = $row->ignore_limits; 890 | 891 | $this->_apiuser = $row; 892 | 893 | /* 894 | * If "is private key" is enabled, compare the ip address with the list 895 | * of valid ip addresses stored in the database 896 | */ 897 | if (empty($row->is_private_key) === false) { 898 | // Check for a list of valid ip addresses 899 | if (isset($row->ip_addresses)) { 900 | // multiple ip addresses must be separated using a comma, explode and loop 901 | $list_ip_addresses = explode(',', $row->ip_addresses); 902 | $ip_address = $this->input->ip_address(); 903 | $found_address = false; 904 | 905 | foreach ($list_ip_addresses as $list_ip) { 906 | if ($ip_address === trim($list_ip)) { 907 | // there is a match, set the the value to TRUE and break out of the loop 908 | $found_address = true; 909 | break; 910 | } 911 | } 912 | 913 | return $found_address; 914 | } else { 915 | // There should be at least one IP address for this private key 916 | return false; 917 | } 918 | } 919 | 920 | return true; 921 | } 922 | 923 | // No key has been sent 924 | return false; 925 | } 926 | 927 | /** 928 | * Preferred return language. 929 | * 930 | * @return string|null|array The language code 931 | */ 932 | protected function _detect_lang() 933 | { 934 | $lang = $this->input->server('HTTP_ACCEPT_LANGUAGE'); 935 | if ($lang === null) { 936 | return; 937 | } 938 | 939 | // It appears more than one language has been sent using a comma delimiter 940 | if (strpos($lang, ',') !== false) { 941 | $langs = explode(',', $lang); 942 | 943 | $return_langs = []; 944 | foreach ($langs as $lang) { 945 | // Remove weight and trim leading and trailing whitespace 946 | list($lang) = explode(';', $lang); 947 | $return_langs[] = trim($lang); 948 | } 949 | 950 | return $return_langs; 951 | } 952 | 953 | // Otherwise simply return as a string 954 | return $lang; 955 | } 956 | 957 | /** 958 | * Add the request to the log table. 959 | * 960 | * @param bool $authorized TRUE the user is authorized; otherwise, FALSE 961 | * 962 | * @return bool TRUE the data was inserted; otherwise, FALSE 963 | */ 964 | protected function _log_request($authorized = false) 965 | { 966 | // Insert the request into the log table 967 | $is_inserted = $this->rest->db 968 | ->insert( 969 | $this->config->item('rest_logs_table'), 970 | [ 971 | 'uri' => $this->uri->uri_string(), 972 | 'method' => $this->request->method, 973 | 'params' => $this->_args ? ($this->config->item('rest_logs_json_params') === true ? json_encode($this->_args) : serialize($this->_args)) : null, 974 | 'api_key' => isset($this->rest->key) ? $this->rest->key : '', 975 | 'ip_address' => $this->input->ip_address(), 976 | 'time' => time(), 977 | 'authorized' => $authorized, 978 | ] 979 | ); 980 | 981 | // Get the last insert id to update at a later stage of the request 982 | $this->_insert_id = $this->rest->db->insert_id(); 983 | 984 | return $is_inserted; 985 | } 986 | 987 | /** 988 | * Check if the requests to a controller method exceed a limit. 989 | * 990 | * @param string $controller_method The method being called 991 | * 992 | * @return bool TRUE the call limit is below the threshold; otherwise, FALSE 993 | */ 994 | protected function _check_limit($controller_method) 995 | { 996 | // They are special, or it might not even have a limit 997 | if (empty($this->rest->ignore_limits) === false) { 998 | // Everything is fine 999 | return true; 1000 | } 1001 | 1002 | $api_key = isset($this->rest->key) ? $this->rest->key : ''; 1003 | 1004 | switch ($this->config->item('rest_limits_method')) { 1005 | case 'IP_ADDRESS': 1006 | $api_key = $this->input->ip_address(); 1007 | $limited_uri = 'ip-address:'.$api_key; 1008 | break; 1009 | 1010 | case 'API_KEY': 1011 | $limited_uri = 'api-key:'.$api_key; 1012 | break; 1013 | 1014 | case 'METHOD_NAME': 1015 | $limited_uri = 'method-name:'.$controller_method; 1016 | break; 1017 | 1018 | case 'ROUTED_URL': 1019 | default: 1020 | $limited_uri = $this->uri->ruri_string(); 1021 | if (strpos(strrev($limited_uri), strrev($this->response->format)) === 0) { 1022 | $limited_uri = substr($limited_uri, 0, -strlen($this->response->format) - 1); 1023 | } 1024 | $limited_uri = 'uri:'.$limited_uri.':'.$this->request->method; // It's good to differentiate GET from PUT 1025 | break; 1026 | } 1027 | 1028 | if (isset($this->methods[$controller_method]['limit']) === false) { 1029 | // Everything is fine 1030 | return true; 1031 | } 1032 | 1033 | // How many times can you get to this method in a defined time_limit (default: 1 hour)? 1034 | $limit = $this->methods[$controller_method]['limit']; 1035 | 1036 | $time_limit = (isset($this->methods[$controller_method]['time']) ? $this->methods[$controller_method]['time'] : 3600); // 3600 = 60 * 60 1037 | 1038 | // Get data about a keys' usage and limit to one row 1039 | $result = $this->rest->db 1040 | ->where('uri', $limited_uri) 1041 | ->where('api_key', $api_key) 1042 | ->get($this->config->item('rest_limits_table')) 1043 | ->row(); 1044 | 1045 | // No calls have been made for this key 1046 | if ($result === null) { 1047 | // Create a new row for the following key 1048 | $this->rest->db->insert($this->config->item('rest_limits_table'), [ 1049 | 'uri' => $limited_uri, 1050 | 'api_key' => $api_key, 1051 | 'count' => 1, 1052 | 'hour_started' => time(), 1053 | ]); 1054 | } 1055 | 1056 | // Been a time limit (or by default an hour) since they called 1057 | elseif ($result->hour_started < (time() - $time_limit)) { 1058 | // Reset the started period and count 1059 | $this->rest->db 1060 | ->where('uri', $limited_uri) 1061 | ->where('api_key', $api_key) 1062 | ->set('hour_started', time()) 1063 | ->set('count', 1) 1064 | ->update($this->config->item('rest_limits_table')); 1065 | } 1066 | 1067 | // They have called within the hour, so lets update 1068 | else { 1069 | // The limit has been exceeded 1070 | if ($result->count >= $limit) { 1071 | return false; 1072 | } 1073 | 1074 | // Increase the count by one 1075 | $this->rest->db 1076 | ->where('uri', $limited_uri) 1077 | ->where('api_key', $api_key) 1078 | ->set('count', 'count + 1', false) 1079 | ->update($this->config->item('rest_limits_table')); 1080 | } 1081 | 1082 | return true; 1083 | } 1084 | 1085 | /** 1086 | * Check if there is a specific auth type set for the current class/method/HTTP-method being called. 1087 | * 1088 | * @return bool 1089 | */ 1090 | protected function _auth_override_check() 1091 | { 1092 | // Assign the class/method auth type override array from the config 1093 | $auth_override_class_method = $this->config->item('auth_override_class_method'); 1094 | 1095 | // Check to see if the override array is even populated 1096 | if (!empty($auth_override_class_method)) { 1097 | // Check for wildcard flag for rules for classes 1098 | if (!empty($auth_override_class_method[$this->router->class]['*'])) { // Check for class overrides 1099 | // No auth override found, prepare nothing but send back a TRUE override flag 1100 | if ($auth_override_class_method[$this->router->class]['*'] === 'none') { 1101 | return true; 1102 | } 1103 | 1104 | // Basic auth override found, prepare basic 1105 | if ($auth_override_class_method[$this->router->class]['*'] === 'basic') { 1106 | $this->_prepare_basic_auth(); 1107 | 1108 | return true; 1109 | } 1110 | 1111 | // Digest auth override found, prepare digest 1112 | if ($auth_override_class_method[$this->router->class]['*'] === 'digest') { 1113 | $this->_prepare_digest_auth(); 1114 | 1115 | return true; 1116 | } 1117 | 1118 | // Session auth override found, check session 1119 | if ($auth_override_class_method[$this->router->class]['*'] === 'session') { 1120 | $this->_check_php_session(); 1121 | 1122 | return true; 1123 | } 1124 | 1125 | // Whitelist auth override found, check client's ip against config whitelist 1126 | if ($auth_override_class_method[$this->router->class]['*'] === 'whitelist') { 1127 | $this->_check_whitelist_auth(); 1128 | 1129 | return true; 1130 | } 1131 | } 1132 | 1133 | // Check to see if there's an override value set for the current class/method being called 1134 | if (!empty($auth_override_class_method[$this->router->class][$this->router->method])) { 1135 | // None auth override found, prepare nothing but send back a TRUE override flag 1136 | if ($auth_override_class_method[$this->router->class][$this->router->method] === 'none') { 1137 | return true; 1138 | } 1139 | 1140 | // Basic auth override found, prepare basic 1141 | if ($auth_override_class_method[$this->router->class][$this->router->method] === 'basic') { 1142 | $this->_prepare_basic_auth(); 1143 | 1144 | return true; 1145 | } 1146 | 1147 | // Digest auth override found, prepare digest 1148 | if ($auth_override_class_method[$this->router->class][$this->router->method] === 'digest') { 1149 | $this->_prepare_digest_auth(); 1150 | 1151 | return true; 1152 | } 1153 | 1154 | // Session auth override found, check session 1155 | if ($auth_override_class_method[$this->router->class][$this->router->method] === 'session') { 1156 | $this->_check_php_session(); 1157 | 1158 | return true; 1159 | } 1160 | 1161 | // Whitelist auth override found, check client's ip against config whitelist 1162 | if ($auth_override_class_method[$this->router->class][$this->router->method] === 'whitelist') { 1163 | $this->_check_whitelist_auth(); 1164 | 1165 | return true; 1166 | } 1167 | } 1168 | } 1169 | 1170 | // Assign the class/method/HTTP-method auth type override array from the config 1171 | $auth_override_class_method_http = $this->config->item('auth_override_class_method_http'); 1172 | 1173 | // Check to see if the override array is even populated 1174 | if (!empty($auth_override_class_method_http)) { 1175 | // check for wildcard flag for rules for classes 1176 | if (!empty($auth_override_class_method_http[$this->router->class]['*'][$this->request->method])) { 1177 | // None auth override found, prepare nothing but send back a TRUE override flag 1178 | if ($auth_override_class_method_http[$this->router->class]['*'][$this->request->method] === 'none') { 1179 | return true; 1180 | } 1181 | 1182 | // Basic auth override found, prepare basic 1183 | if ($auth_override_class_method_http[$this->router->class]['*'][$this->request->method] === 'basic') { 1184 | $this->_prepare_basic_auth(); 1185 | 1186 | return true; 1187 | } 1188 | 1189 | // Digest auth override found, prepare digest 1190 | if ($auth_override_class_method_http[$this->router->class]['*'][$this->request->method] === 'digest') { 1191 | $this->_prepare_digest_auth(); 1192 | 1193 | return true; 1194 | } 1195 | 1196 | // Session auth override found, check session 1197 | if ($auth_override_class_method_http[$this->router->class]['*'][$this->request->method] === 'session') { 1198 | $this->_check_php_session(); 1199 | 1200 | return true; 1201 | } 1202 | 1203 | // Whitelist auth override found, check client's ip against config whitelist 1204 | if ($auth_override_class_method_http[$this->router->class]['*'][$this->request->method] === 'whitelist') { 1205 | $this->_check_whitelist_auth(); 1206 | 1207 | return true; 1208 | } 1209 | } 1210 | 1211 | // Check to see if there's an override value set for the current class/method/HTTP-method being called 1212 | if (!empty($auth_override_class_method_http[$this->router->class][$this->router->method][$this->request->method])) { 1213 | // None auth override found, prepare nothing but send back a TRUE override flag 1214 | if ($auth_override_class_method_http[$this->router->class][$this->router->method][$this->request->method] === 'none') { 1215 | return true; 1216 | } 1217 | 1218 | // Basic auth override found, prepare basic 1219 | if ($auth_override_class_method_http[$this->router->class][$this->router->method][$this->request->method] === 'basic') { 1220 | $this->_prepare_basic_auth(); 1221 | 1222 | return true; 1223 | } 1224 | 1225 | // Digest auth override found, prepare digest 1226 | if ($auth_override_class_method_http[$this->router->class][$this->router->method][$this->request->method] === 'digest') { 1227 | $this->_prepare_digest_auth(); 1228 | 1229 | return true; 1230 | } 1231 | 1232 | // Session auth override found, check session 1233 | if ($auth_override_class_method_http[$this->router->class][$this->router->method][$this->request->method] === 'session') { 1234 | $this->_check_php_session(); 1235 | 1236 | return true; 1237 | } 1238 | 1239 | // Whitelist auth override found, check client's ip against config whitelist 1240 | if ($auth_override_class_method_http[$this->router->class][$this->router->method][$this->request->method] === 'whitelist') { 1241 | $this->_check_whitelist_auth(); 1242 | 1243 | return true; 1244 | } 1245 | } 1246 | } 1247 | 1248 | return false; 1249 | } 1250 | 1251 | /** 1252 | * Parse the GET request arguments. 1253 | * 1254 | * @return void 1255 | */ 1256 | protected function _parse_get() 1257 | { 1258 | // Merge both the URI segments and query parameters 1259 | $this->_get_args = array_merge($this->_get_args, $this->_query_args); 1260 | } 1261 | 1262 | /** 1263 | * Parse the POST request arguments. 1264 | * 1265 | * @return void 1266 | */ 1267 | protected function _parse_post() 1268 | { 1269 | $this->_post_args = $_POST; 1270 | 1271 | if ($this->request->format) { 1272 | $this->request->body = $this->input->raw_input_stream; 1273 | } 1274 | } 1275 | 1276 | /** 1277 | * Parse the PUT request arguments. 1278 | * 1279 | * @return void 1280 | */ 1281 | protected function _parse_put() 1282 | { 1283 | if ($this->request->format) { 1284 | $this->request->body = $this->input->raw_input_stream; 1285 | if ($this->request->format === 'json') { 1286 | $this->_put_args = json_decode($this->input->raw_input_stream); 1287 | } 1288 | } elseif ($this->input->method() === 'put') { 1289 | // If no file type is provided, then there are probably just arguments 1290 | $this->_put_args = $this->input->input_stream(); 1291 | } 1292 | } 1293 | 1294 | /** 1295 | * Parse the HEAD request arguments. 1296 | * 1297 | * @return void 1298 | */ 1299 | protected function _parse_head() 1300 | { 1301 | // Parse the HEAD variables 1302 | parse_str(parse_url($this->input->server('REQUEST_URI'), PHP_URL_QUERY), $head); 1303 | 1304 | // Merge both the URI segments and HEAD params 1305 | $this->_head_args = array_merge($this->_head_args, $head); 1306 | } 1307 | 1308 | /** 1309 | * Parse the OPTIONS request arguments. 1310 | * 1311 | * @return void 1312 | */ 1313 | protected function _parse_options() 1314 | { 1315 | // Parse the OPTIONS variables 1316 | parse_str(parse_url($this->input->server('REQUEST_URI'), PHP_URL_QUERY), $options); 1317 | 1318 | // Merge both the URI segments and OPTIONS params 1319 | $this->_options_args = array_merge($this->_options_args, $options); 1320 | } 1321 | 1322 | /** 1323 | * Parse the PATCH request arguments. 1324 | * 1325 | * @return void 1326 | */ 1327 | protected function _parse_patch() 1328 | { 1329 | // It might be a HTTP body 1330 | if ($this->request->format) { 1331 | $this->request->body = $this->input->raw_input_stream; 1332 | } elseif ($this->input->method() === 'patch') { 1333 | // If no file type is provided, then there are probably just arguments 1334 | $this->_patch_args = $this->input->input_stream(); 1335 | } 1336 | } 1337 | 1338 | /** 1339 | * Parse the DELETE request arguments. 1340 | * 1341 | * @return void 1342 | */ 1343 | protected function _parse_delete() 1344 | { 1345 | // These should exist if a DELETE request 1346 | if ($this->input->method() === 'delete') { 1347 | $this->_delete_args = $this->input->input_stream(); 1348 | } 1349 | } 1350 | 1351 | /** 1352 | * Parse the query parameters. 1353 | * 1354 | * @return void 1355 | */ 1356 | protected function _parse_query() 1357 | { 1358 | $this->_query_args = $this->input->get(); 1359 | } 1360 | 1361 | // INPUT FUNCTION -------------------------------------------------------------- 1362 | 1363 | /** 1364 | * Retrieve a value from a GET request. 1365 | * 1366 | * @param null $key Key to retrieve from the GET request 1367 | * If NULL an array of arguments is returned 1368 | * @param null $xss_clean Whether to apply XSS filtering 1369 | * 1370 | * @return array|string|null Value from the GET request; otherwise, NULL 1371 | */ 1372 | public function get($key = null, $xss_clean = null) 1373 | { 1374 | if ($key === null) { 1375 | return $this->_get_args; 1376 | } 1377 | 1378 | return isset($this->_get_args[$key]) ? $this->_xss_clean($this->_get_args[$key], $xss_clean) : null; 1379 | } 1380 | 1381 | /** 1382 | * Retrieve a value from a OPTIONS request. 1383 | * 1384 | * @param null $key Key to retrieve from the OPTIONS request. 1385 | * If NULL an array of arguments is returned 1386 | * @param null $xss_clean Whether to apply XSS filtering 1387 | * 1388 | * @return array|string|null Value from the OPTIONS request; otherwise, NULL 1389 | */ 1390 | public function options($key = null, $xss_clean = null) 1391 | { 1392 | if ($key === null) { 1393 | return $this->_options_args; 1394 | } 1395 | 1396 | return isset($this->_options_args[$key]) ? $this->_xss_clean($this->_options_args[$key], $xss_clean) : null; 1397 | } 1398 | 1399 | /** 1400 | * Retrieve a value from a HEAD request. 1401 | * 1402 | * @param null $key Key to retrieve from the HEAD request 1403 | * If NULL an array of arguments is returned 1404 | * @param null $xss_clean Whether to apply XSS filtering 1405 | * 1406 | * @return array|string|null Value from the HEAD request; otherwise, NULL 1407 | */ 1408 | public function head($key = null, $xss_clean = null) 1409 | { 1410 | if ($key === null) { 1411 | return $this->_head_args; 1412 | } 1413 | 1414 | return isset($this->_head_args[$key]) ? $this->_xss_clean($this->_head_args[$key], $xss_clean) : null; 1415 | } 1416 | 1417 | /** 1418 | * Retrieve a value from a POST request. 1419 | * 1420 | * @param null $key Key to retrieve from the POST request 1421 | * If NULL an array of arguments is returned 1422 | * @param null $xss_clean Whether to apply XSS filtering 1423 | * 1424 | * @return array|string|null Value from the POST request; otherwise, NULL 1425 | */ 1426 | public function post($key = null, $xss_clean = null) 1427 | { 1428 | if ($key === null) { 1429 | foreach (new RecursiveIteratorIterator(new RecursiveArrayIterator($this->_post_args), RecursiveIteratorIterator::CATCH_GET_CHILD) as $key => $value) { 1430 | $this->_post_args[$key] = $this->_xss_clean($this->_post_args[$key], $xss_clean); 1431 | } 1432 | 1433 | return $this->_post_args; 1434 | } 1435 | 1436 | return isset($this->_post_args[$key]) ? $this->_xss_clean($this->_post_args[$key], $xss_clean) : null; 1437 | } 1438 | 1439 | /** 1440 | * Retrieve a value from a PUT request. 1441 | * 1442 | * @param null $key Key to retrieve from the PUT request 1443 | * If NULL an array of arguments is returned 1444 | * @param null $xss_clean Whether to apply XSS filtering 1445 | * 1446 | * @return array|string|null Value from the PUT request; otherwise, NULL 1447 | */ 1448 | public function put($key = null, $xss_clean = null) 1449 | { 1450 | if ($key === null) { 1451 | return $this->_put_args; 1452 | } 1453 | 1454 | return isset($this->_put_args[$key]) ? $this->_xss_clean($this->_put_args[$key], $xss_clean) : null; 1455 | } 1456 | 1457 | /** 1458 | * Retrieve a value from a DELETE request. 1459 | * 1460 | * @param null $key Key to retrieve from the DELETE request 1461 | * If NULL an array of arguments is returned 1462 | * @param null $xss_clean Whether to apply XSS filtering 1463 | * 1464 | * @return array|string|null Value from the DELETE request; otherwise, NULL 1465 | */ 1466 | public function delete($key = null, $xss_clean = null) 1467 | { 1468 | if ($key === null) { 1469 | return $this->_delete_args; 1470 | } 1471 | 1472 | return isset($this->_delete_args[$key]) ? $this->_xss_clean($this->_delete_args[$key], $xss_clean) : null; 1473 | } 1474 | 1475 | /** 1476 | * Retrieve a value from a PATCH request. 1477 | * 1478 | * @param null $key Key to retrieve from the PATCH request 1479 | * If NULL an array of arguments is returned 1480 | * @param null $xss_clean Whether to apply XSS filtering 1481 | * 1482 | * @return array|string|null Value from the PATCH request; otherwise, NULL 1483 | */ 1484 | public function patch($key = null, $xss_clean = null) 1485 | { 1486 | if ($key === null) { 1487 | return $this->_patch_args; 1488 | } 1489 | 1490 | return isset($this->_patch_args[$key]) ? $this->_xss_clean($this->_patch_args[$key], $xss_clean) : null; 1491 | } 1492 | 1493 | /** 1494 | * Retrieve a value from the query parameters. 1495 | * 1496 | * @param null $key Key to retrieve from the query parameters 1497 | * If NULL an array of arguments is returned 1498 | * @param null $xss_clean Whether to apply XSS filtering 1499 | * 1500 | * @return array|string|null Value from the query parameters; otherwise, NULL 1501 | */ 1502 | public function query($key = null, $xss_clean = null) 1503 | { 1504 | if ($key === null) { 1505 | return $this->_query_args; 1506 | } 1507 | 1508 | return isset($this->_query_args[$key]) ? $this->_xss_clean($this->_query_args[$key], $xss_clean) : null; 1509 | } 1510 | 1511 | /** 1512 | * Sanitizes data so that Cross Site Scripting Hacks can be 1513 | * prevented. 1514 | * 1515 | * @param string $value Input data 1516 | * @param bool $xss_clean Whether to apply XSS filtering 1517 | * 1518 | * @return string 1519 | */ 1520 | protected function _xss_clean($value, $xss_clean) 1521 | { 1522 | is_bool($xss_clean) || $xss_clean = $this->_enable_xss; 1523 | 1524 | return $xss_clean === true ? $this->security->xss_clean($value) : $value; 1525 | } 1526 | 1527 | /** 1528 | * Retrieve the validation errors. 1529 | * 1530 | * @return array 1531 | */ 1532 | public function validation_errors() 1533 | { 1534 | $string = strip_tags($this->form_validation->error_string()); 1535 | 1536 | return explode(PHP_EOL, trim($string, PHP_EOL)); 1537 | } 1538 | 1539 | // SECURITY FUNCTIONS --------------------------------------------------------- 1540 | 1541 | /** 1542 | * Perform LDAP Authentication. 1543 | * 1544 | * @param string $username The username to validate 1545 | * @param string $password The password to validate 1546 | * 1547 | * @return bool 1548 | */ 1549 | protected function _perform_ldap_auth($username = '', $password = null) 1550 | { 1551 | if (empty($username)) { 1552 | log_message('debug', 'LDAP Auth: failure, empty username'); 1553 | 1554 | return false; 1555 | } 1556 | 1557 | log_message('debug', 'LDAP Auth: Loading configuration'); 1558 | 1559 | $this->config->load('ldap', true); 1560 | 1561 | $ldap = [ 1562 | 'timeout' => $this->config->item('timeout', 'ldap'), 1563 | 'host' => $this->config->item('server', 'ldap'), 1564 | 'port' => $this->config->item('port', 'ldap'), 1565 | 'rdn' => $this->config->item('binduser', 'ldap'), 1566 | 'pass' => $this->config->item('bindpw', 'ldap'), 1567 | 'basedn' => $this->config->item('basedn', 'ldap'), 1568 | ]; 1569 | 1570 | log_message('debug', 'LDAP Auth: Connect to '.(isset($ldap['host']) ? $ldap['host'] : '[ldap not configured]')); 1571 | 1572 | // Connect to the ldap server 1573 | $ldapconn = ldap_connect($ldap['host'], $ldap['port']); 1574 | if ($ldapconn) { 1575 | log_message('debug', 'Setting timeout to '.$ldap['timeout'].' seconds'); 1576 | 1577 | ldap_set_option($ldapconn, LDAP_OPT_NETWORK_TIMEOUT, $ldap['timeout']); 1578 | 1579 | log_message('debug', 'LDAP Auth: Binding to '.$ldap['host'].' with dn '.$ldap['rdn']); 1580 | 1581 | // Binding to the ldap server 1582 | $ldapbind = ldap_bind($ldapconn, $ldap['rdn'], $ldap['pass']); 1583 | 1584 | // Verify the binding 1585 | if ($ldapbind === false) { 1586 | log_message('error', 'LDAP Auth: bind was unsuccessful'); 1587 | 1588 | return false; 1589 | } 1590 | 1591 | log_message('debug', 'LDAP Auth: bind successful'); 1592 | } 1593 | 1594 | // Search for user 1595 | if (($res_id = ldap_search($ldapconn, $ldap['basedn'], "uid=$username")) === false) { 1596 | log_message('error', 'LDAP Auth: User '.$username.' not found in search'); 1597 | 1598 | return false; 1599 | } 1600 | 1601 | if (ldap_count_entries($ldapconn, $res_id) !== 1) { 1602 | log_message('error', 'LDAP Auth: Failure, username '.$username.'found more than once'); 1603 | 1604 | return false; 1605 | } 1606 | 1607 | if (($entry_id = ldap_first_entry($ldapconn, $res_id)) === false) { 1608 | log_message('error', 'LDAP Auth: Failure, entry of search result could not be fetched'); 1609 | 1610 | return false; 1611 | } 1612 | 1613 | if (($user_dn = ldap_get_dn($ldapconn, $entry_id)) === false) { 1614 | log_message('error', 'LDAP Auth: Failure, user-dn could not be fetched'); 1615 | 1616 | return false; 1617 | } 1618 | 1619 | // User found, could not authenticate as user 1620 | if (($link_id = ldap_bind($ldapconn, $user_dn, $password)) === false) { 1621 | log_message('error', 'LDAP Auth: Failure, username/password did not match: '.$user_dn); 1622 | 1623 | return false; 1624 | } 1625 | 1626 | log_message('debug', 'LDAP Auth: Success '.$user_dn.' authenticated successfully'); 1627 | 1628 | $this->_user_ldap_dn = $user_dn; 1629 | 1630 | ldap_close($ldapconn); 1631 | 1632 | return true; 1633 | } 1634 | 1635 | /** 1636 | * Perform Library Authentication - Override this function to change the way the library is called. 1637 | * 1638 | * @param string $username The username to validate 1639 | * @param string $password The password to validate 1640 | * 1641 | * @return bool 1642 | */ 1643 | protected function _perform_library_auth($username = '', $password = null) 1644 | { 1645 | if (empty($username)) { 1646 | log_message('error', 'Library Auth: Failure, empty username'); 1647 | 1648 | return false; 1649 | } 1650 | 1651 | $auth_library_class = strtolower($this->config->item('auth_library_class')); 1652 | $auth_library_function = strtolower($this->config->item('auth_library_function')); 1653 | 1654 | if (empty($auth_library_class)) { 1655 | log_message('debug', 'Library Auth: Failure, empty auth_library_class'); 1656 | 1657 | return false; 1658 | } 1659 | 1660 | if (empty($auth_library_function)) { 1661 | log_message('debug', 'Library Auth: Failure, empty auth_library_function'); 1662 | 1663 | return false; 1664 | } 1665 | 1666 | if (is_callable([$auth_library_class, $auth_library_function]) === false) { 1667 | $this->load->library($auth_library_class); 1668 | } 1669 | 1670 | return $this->{$auth_library_class}->$auth_library_function($username, $password); 1671 | } 1672 | 1673 | /** 1674 | * Check if the user is logged in. 1675 | * 1676 | * @param string $username The user's name 1677 | * @param bool|string $password The user's password 1678 | * 1679 | * @return bool 1680 | */ 1681 | protected function _check_login($username = null, $password = false) 1682 | { 1683 | if (empty($username)) { 1684 | return false; 1685 | } 1686 | 1687 | $auth_source = strtolower($this->config->item('auth_source')); 1688 | $rest_auth = strtolower($this->config->item('rest_auth')); 1689 | $valid_logins = $this->config->item('rest_valid_logins'); 1690 | 1691 | if (!$this->config->item('auth_source') && $rest_auth === 'digest') { 1692 | // For digest we do not have a password passed as argument 1693 | return md5($username.':'.$this->config->item('rest_realm').':'.(isset($valid_logins[$username]) ? $valid_logins[$username] : '')); 1694 | } 1695 | 1696 | if ($password === false) { 1697 | return false; 1698 | } 1699 | 1700 | if ($auth_source === 'ldap') { 1701 | log_message('debug', "Performing LDAP authentication for $username"); 1702 | 1703 | return $this->_perform_ldap_auth($username, $password); 1704 | } 1705 | 1706 | if ($auth_source === 'library') { 1707 | log_message('debug', "Performing Library authentication for $username"); 1708 | 1709 | return $this->_perform_library_auth($username, $password); 1710 | } 1711 | 1712 | if (array_key_exists($username, $valid_logins) === false) { 1713 | return false; 1714 | } 1715 | 1716 | if ($valid_logins[$username] !== $password) { 1717 | return false; 1718 | } 1719 | 1720 | return true; 1721 | } 1722 | 1723 | /** 1724 | * Check to see if the user is logged in with a PHP session key. 1725 | * 1726 | * @return void 1727 | */ 1728 | protected function _check_php_session() 1729 | { 1730 | // If whitelist is enabled it has the first chance to kick them out 1731 | if ($this->config->item('rest_ip_whitelist_enabled')) { 1732 | $this->_check_whitelist_auth(); 1733 | } 1734 | 1735 | // Load library session of CodeIgniter 1736 | $this->load->library('session'); 1737 | 1738 | // Get the auth_source config item 1739 | $key = $this->config->item('auth_source'); 1740 | 1741 | // If false, then the user isn't logged in 1742 | if (!$this->session->userdata($key)) { 1743 | // Display an error response 1744 | $this->response([ 1745 | $this->config->item('rest_status_field_name') => false, 1746 | $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_unauthorized'), 1747 | ], self::HTTP_UNAUTHORIZED); 1748 | } 1749 | } 1750 | 1751 | /** 1752 | * Prepares for basic authentication. 1753 | * 1754 | * @return void 1755 | */ 1756 | protected function _prepare_basic_auth() 1757 | { 1758 | // If whitelist is enabled it has the first chance to kick them out 1759 | if ($this->config->item('rest_ip_whitelist_enabled')) { 1760 | $this->_check_whitelist_auth(); 1761 | } 1762 | 1763 | // Returns NULL if the SERVER variables PHP_AUTH_USER and HTTP_AUTHENTICATION don't exist 1764 | $username = $this->input->server('PHP_AUTH_USER'); 1765 | $http_auth = $this->input->server('HTTP_AUTHENTICATION') ?: $this->input->server('HTTP_AUTHORIZATION'); 1766 | 1767 | $password = null; 1768 | if ($username !== null) { 1769 | $password = $this->input->server('PHP_AUTH_PW'); 1770 | } elseif ($http_auth !== null) { 1771 | // If the authentication header is set as basic, then extract the username and password from 1772 | // HTTP_AUTHORIZATION e.g. my_username:my_password. This is passed in the .htaccess file 1773 | if (strpos(strtolower($http_auth), 'basic') === 0) { 1774 | // Search online for HTTP_AUTHORIZATION workaround to explain what this is doing 1775 | list($username, $password) = explode(':', base64_decode(substr($this->input->server('HTTP_AUTHORIZATION'), 6))); 1776 | } 1777 | } 1778 | 1779 | // Check if the user is logged into the system 1780 | if ($this->_check_login($username, $password) === false) { 1781 | $this->_force_login(); 1782 | } 1783 | } 1784 | 1785 | /** 1786 | * Prepares for digest authentication. 1787 | * 1788 | * @return void 1789 | */ 1790 | protected function _prepare_digest_auth() 1791 | { 1792 | // If whitelist is enabled it has the first chance to kick them out 1793 | if ($this->config->item('rest_ip_whitelist_enabled')) { 1794 | $this->_check_whitelist_auth(); 1795 | } 1796 | 1797 | // We need to test which server authentication variable to use, 1798 | // because the PHP ISAPI module in IIS acts different from CGI 1799 | $digest_string = $this->input->server('PHP_AUTH_DIGEST'); 1800 | if ($digest_string === null) { 1801 | $digest_string = $this->input->server('HTTP_AUTHORIZATION'); 1802 | } 1803 | 1804 | $unique_id = uniqid(); 1805 | 1806 | // The $_SESSION['error_prompted'] variable is used to ask the password 1807 | // again if none given or if the user enters wrong auth information 1808 | if (empty($digest_string)) { 1809 | $this->_force_login($unique_id); 1810 | } 1811 | 1812 | // We need to retrieve authentication data from the $digest_string variable 1813 | $matches = []; 1814 | preg_match_all('@(username|nonce|uri|nc|cnonce|qop|response)=[\'"]?([^\'",]+)@', $digest_string, $matches); 1815 | $digest = (empty($matches[1]) || empty($matches[2])) ? [] : array_combine($matches[1], $matches[2]); 1816 | 1817 | // For digest authentication the library function should return already stored md5(username:restrealm:password) for that username see rest.php::auth_library_function config 1818 | $username = $this->_check_login($digest['username'], true); 1819 | if (isset($digest['username']) === false || $username === false) { 1820 | $this->_force_login($unique_id); 1821 | } 1822 | 1823 | $md5 = md5(strtoupper($this->request->method).':'.$digest['uri']); 1824 | $valid_response = md5($username.':'.$digest['nonce'].':'.$digest['nc'].':'.$digest['cnonce'].':'.$digest['qop'].':'.$md5); 1825 | 1826 | // Check if the string don't compare (case-insensitive) 1827 | if (strcasecmp($digest['response'], $valid_response) !== 0) { 1828 | // Display an error response 1829 | $this->response([ 1830 | $this->config->item('rest_status_field_name') => false, 1831 | $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_invalid_credentials'), 1832 | ], self::HTTP_UNAUTHORIZED); 1833 | } 1834 | } 1835 | 1836 | /** 1837 | * Checks if the client's ip is in the 'rest_ip_blacklist' config and generates a 401 response. 1838 | * 1839 | * @return void 1840 | */ 1841 | protected function _check_blacklist_auth() 1842 | { 1843 | // Match an ip address in a blacklist e.g. 127.0.0.0, 0.0.0.0 1844 | $pattern = sprintf('/(?:,\s*|^)\Q%s\E(?=,\s*|$)/m', $this->input->ip_address()); 1845 | 1846 | // Returns 1, 0 or FALSE (on error only). Therefore implicitly convert 1 to TRUE 1847 | if (preg_match($pattern, $this->config->item('rest_ip_blacklist'))) { 1848 | // Display an error response 1849 | $this->response([ 1850 | $this->config->item('rest_status_field_name') => false, 1851 | $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_ip_denied'), 1852 | ], self::HTTP_UNAUTHORIZED); 1853 | } 1854 | } 1855 | 1856 | /** 1857 | * Check if the client's ip is in the 'rest_ip_whitelist' config and generates a 401 response. 1858 | * 1859 | * @return void 1860 | */ 1861 | protected function _check_whitelist_auth() 1862 | { 1863 | $whitelist = explode(',', $this->config->item('rest_ip_whitelist')); 1864 | 1865 | array_push($whitelist, '127.0.0.1', '0.0.0.0'); 1866 | 1867 | foreach ($whitelist as &$ip) { 1868 | // As $ip is a reference, trim leading and trailing whitespace, then store the new value 1869 | // using the reference 1870 | $ip = trim($ip); 1871 | } 1872 | 1873 | if (in_array($this->input->ip_address(), $whitelist) === false) { 1874 | $this->response([ 1875 | $this->config->item('rest_status_field_name') => false, 1876 | $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_ip_unauthorized'), 1877 | ], self::HTTP_UNAUTHORIZED); 1878 | } 1879 | } 1880 | 1881 | /** 1882 | * Force logging in by setting the WWW-Authenticate header. 1883 | * 1884 | * @param string $nonce A server-specified data string which should be uniquely generated 1885 | * each time 1886 | * 1887 | * @return void 1888 | */ 1889 | protected function _force_login($nonce = '') 1890 | { 1891 | $rest_auth = strtolower($this->config->item('rest_auth')); 1892 | $rest_realm = $this->config->item('rest_realm'); 1893 | if ($rest_auth === 'basic') { 1894 | // See http://tools.ietf.org/html/rfc2617#page-5 1895 | header('WWW-Authenticate: Basic realm="'.$rest_realm.'"'); 1896 | } elseif ($rest_auth === 'digest') { 1897 | // See http://tools.ietf.org/html/rfc2617#page-18 1898 | header( 1899 | 'WWW-Authenticate: Digest realm="'.$rest_realm 1900 | .'", qop="auth", nonce="'.$nonce 1901 | .'", opaque="'.md5($rest_realm).'"' 1902 | ); 1903 | } 1904 | 1905 | if ($this->config->item('strict_api_and_auth') === true) { 1906 | $this->is_valid_request = false; 1907 | } 1908 | 1909 | // Display an error response 1910 | $this->response([ 1911 | $this->config->item('rest_status_field_name') => false, 1912 | $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_unauthorized'), 1913 | ], self::HTTP_UNAUTHORIZED); 1914 | } 1915 | 1916 | /** 1917 | * Updates the log table with the total access time. 1918 | * 1919 | * @author Chris Kacerguis 1920 | * 1921 | * @return bool TRUE log table updated; otherwise, FALSE 1922 | */ 1923 | protected function _log_access_time() 1924 | { 1925 | if ($this->_insert_id == '') { 1926 | return false; 1927 | } 1928 | 1929 | $payload['rtime'] = $this->_end_rtime - $this->_start_rtime; 1930 | 1931 | return $this->rest->db->update( 1932 | $this->config->item('rest_logs_table'), 1933 | $payload, 1934 | [ 1935 | 'id' => $this->_insert_id, 1936 | ] 1937 | ); 1938 | } 1939 | 1940 | /** 1941 | * Updates the log table with HTTP response code. 1942 | * 1943 | * @author Justin Chen 1944 | * 1945 | * @param $http_code int HTTP status code 1946 | * 1947 | * @return bool TRUE log table updated; otherwise, FALSE 1948 | */ 1949 | protected function _log_response_code($http_code) 1950 | { 1951 | if ($this->_insert_id == '') { 1952 | return false; 1953 | } 1954 | 1955 | $payload['response_code'] = $http_code; 1956 | 1957 | return $this->rest->db->update( 1958 | $this->config->item('rest_logs_table'), 1959 | $payload, 1960 | [ 1961 | 'id' => $this->_insert_id, 1962 | ] 1963 | ); 1964 | } 1965 | 1966 | /** 1967 | * Check to see if the API key has access to the controller and methods. 1968 | * 1969 | * @return bool TRUE the API key has access; otherwise, FALSE 1970 | */ 1971 | protected function _check_access() 1972 | { 1973 | // If we don't want to check access, just return TRUE 1974 | if ($this->config->item('rest_enable_access') === false) { 1975 | return true; 1976 | } 1977 | 1978 | // Fetch controller based on path and controller name 1979 | $controller = implode( 1980 | '/', 1981 | [ 1982 | $this->router->directory, 1983 | $this->router->class, 1984 | ] 1985 | ); 1986 | 1987 | // Remove any double slashes for safety 1988 | $controller = str_replace('//', '/', $controller); 1989 | 1990 | //check if the key has all_access 1991 | $accessRow = $this->rest->db 1992 | ->where('key', $this->rest->key) 1993 | ->where('controller', $controller) 1994 | ->get($this->config->item('rest_access_table'))->row_array(); 1995 | 1996 | if (!empty($accessRow) && !empty($accessRow['all_access'])) { 1997 | return true; 1998 | } 1999 | 2000 | return false; 2001 | } 2002 | 2003 | /** 2004 | * Checks allowed domains, and adds appropriate headers for HTTP access control (CORS). 2005 | * 2006 | * @return void 2007 | */ 2008 | protected function _check_cors() 2009 | { 2010 | // Convert the config items into strings 2011 | $allowed_headers = implode(', ', $this->config->item('allowed_cors_headers')); 2012 | $allowed_methods = implode(', ', $this->config->item('allowed_cors_methods')); 2013 | 2014 | // If we want to allow any domain to access the API 2015 | if ($this->config->item('allow_any_cors_domain') === true) { 2016 | header('Access-Control-Allow-Origin: *'); 2017 | header('Access-Control-Allow-Headers: '.$allowed_headers); 2018 | header('Access-Control-Allow-Methods: '.$allowed_methods); 2019 | } else { 2020 | // We're going to allow only certain domains access 2021 | // Store the HTTP Origin header 2022 | $origin = $this->input->server('HTTP_ORIGIN'); 2023 | if ($origin === null) { 2024 | $origin = ''; 2025 | } 2026 | 2027 | // If the origin domain is in the allowed_cors_origins list, then add the Access Control headers 2028 | if (in_array($origin, $this->config->item('allowed_cors_origins'))) { 2029 | header('Access-Control-Allow-Origin: '.$origin); 2030 | header('Access-Control-Allow-Headers: '.$allowed_headers); 2031 | header('Access-Control-Allow-Methods: '.$allowed_methods); 2032 | } 2033 | } 2034 | 2035 | // If there are headers that should be forced in the CORS check, add them now 2036 | if (is_array($this->config->item('forced_cors_headers'))) { 2037 | foreach ($this->config->item('forced_cors_headers') as $header => $value) { 2038 | header($header.': '.$value); 2039 | } 2040 | } 2041 | 2042 | // If the request HTTP method is 'OPTIONS', kill the response and send it to the client 2043 | if ($this->input->method() === 'options') { 2044 | // Load DB if needed for logging 2045 | if (!isset($this->rest->db) && $this->config->item('rest_enable_logging')) { 2046 | $this->rest->db = $this->load->database($this->config->item('rest_database_group'), true); 2047 | } 2048 | exit; 2049 | } 2050 | } 2051 | } 2052 | -------------------------------------------------------------------------------- /src/auth/apikey.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriskacerguis/codeigniter-restserver/60b2009982b27ff29e7b7554d2de776519e4ca8e/src/auth/apikey.php -------------------------------------------------------------------------------- /src/auth/basic.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriskacerguis/codeigniter-restserver/60b2009982b27ff29e7b7554d2de776519e4ca8e/src/auth/basic.php -------------------------------------------------------------------------------- /src/auth/ldap.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriskacerguis/codeigniter-restserver/60b2009982b27ff29e7b7554d2de776519e4ca8e/src/auth/ldap.php -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 403 Forbidden 5 | 6 | 7 | 8 |

Directory access is forbidden.

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/rest.php: -------------------------------------------------------------------------------- 1 | function($username, $password) 151 | | In other cases override the function _perform_library_auth in your controller 152 | | 153 | | For digest authentication the library function should return already a stored 154 | | md5(username:restrealm:password) for that username 155 | | 156 | | e.g: md5('admin:REST API:1234') = '1e957ebc35631ab22d5bd6526bd14ea2' 157 | | 158 | */ 159 | $config['auth_library_class'] = ''; 160 | $config['auth_library_function'] = ''; 161 | 162 | /* 163 | |-------------------------------------------------------------------------- 164 | | Override auth types for specific class/method 165 | |-------------------------------------------------------------------------- 166 | | 167 | | Set specific authentication types for methods within a class (controller) 168 | | 169 | | Set as many config entries as needed. Any methods not set will use the default 'rest_auth' config value. 170 | | 171 | | e.g: 172 | | 173 | | $config['auth_override_class_method']['deals']['view'] = 'none'; 174 | | $config['auth_override_class_method']['deals']['insert'] = 'digest'; 175 | | $config['auth_override_class_method']['accounts']['user'] = 'basic'; 176 | | $config['auth_override_class_method']['dashboard']['*'] = 'none|digest|basic'; 177 | | 178 | | Here 'deals', 'accounts' and 'dashboard' are controller names, 'view', 'insert' and 'user' are methods within. An asterisk may also be used to specify an authentication method for an entire classes methods. Ex: $config['auth_override_class_method']['dashboard']['*'] = 'basic'; (NOTE: leave off the '_get' or '_post' from the end of the method name) 179 | | Acceptable values are; 'none', 'digest' and 'basic'. 180 | | 181 | */ 182 | // $config['auth_override_class_method']['deals']['view'] = 'none'; 183 | // $config['auth_override_class_method']['deals']['insert'] = 'digest'; 184 | // $config['auth_override_class_method']['accounts']['user'] = 'basic'; 185 | // $config['auth_override_class_method']['dashboard']['*'] = 'basic'; 186 | 187 | // ---Uncomment list line for the wildard unit test 188 | // $config['auth_override_class_method']['wildcard_test_cases']['*'] = 'basic'; 189 | 190 | /* 191 | |-------------------------------------------------------------------------- 192 | | Override auth types for specific 'class/method/HTTP method' 193 | |-------------------------------------------------------------------------- 194 | | 195 | | example: 196 | | 197 | | $config['auth_override_class_method_http']['deals']['view']['get'] = 'none'; 198 | | $config['auth_override_class_method_http']['deals']['insert']['post'] = 'none'; 199 | | $config['auth_override_class_method_http']['deals']['*']['options'] = 'none'; 200 | */ 201 | 202 | // ---Uncomment list line for the wildard unit test 203 | // $config['auth_override_class_method_http']['wildcard_test_cases']['*']['options'] = 'basic'; 204 | 205 | /* 206 | |-------------------------------------------------------------------------- 207 | | REST Login Usernames 208 | |-------------------------------------------------------------------------- 209 | | 210 | | Array of usernames and passwords for login, if ldap is configured this is ignored 211 | | 212 | */ 213 | $config['rest_valid_logins'] = ['admin' => '1234']; 214 | 215 | /* 216 | |-------------------------------------------------------------------------- 217 | | Global IP White-listing 218 | |-------------------------------------------------------------------------- 219 | | 220 | | Limit connections to your REST server to White-listed IP addresses 221 | | 222 | | Usage: 223 | | 1. Set to TRUE and select an auth option for extreme security (client's IP 224 | | address must be in white-list and they must also log in) 225 | | 2. Set to TRUE with auth set to FALSE to allow White-listed IPs access with no login 226 | | 3. Set to FALSE but set 'auth_override_class_method' to 'white-list' to 227 | | restrict certain methods to IPs in your white-list 228 | | 229 | */ 230 | $config['rest_ip_whitelist_enabled'] = false; 231 | 232 | /* 233 | |-------------------------------------------------------------------------- 234 | | REST Handle Exceptions 235 | |-------------------------------------------------------------------------- 236 | | 237 | | Handle exceptions caused by the controller 238 | | 239 | */ 240 | $config['rest_handle_exceptions'] = true; 241 | 242 | /* 243 | |-------------------------------------------------------------------------- 244 | | REST IP White-list 245 | |-------------------------------------------------------------------------- 246 | | 247 | | Limit connections to your REST server with a comma separated 248 | | list of IP addresses 249 | | 250 | | e.g: '123.456.789.0, 987.654.32.1' 251 | | 252 | | 127.0.0.1 and 0.0.0.0 are allowed by default 253 | | 254 | */ 255 | $config['rest_ip_whitelist'] = ''; 256 | 257 | /* 258 | |-------------------------------------------------------------------------- 259 | | Global IP Blacklisting 260 | |-------------------------------------------------------------------------- 261 | | 262 | | Prevent connections to the REST server from blacklisted IP addresses 263 | | 264 | | Usage: 265 | | 1. Set to TRUE and add any IP address to 'rest_ip_blacklist' 266 | | 267 | */ 268 | $config['rest_ip_blacklist_enabled'] = false; 269 | 270 | /* 271 | |-------------------------------------------------------------------------- 272 | | REST IP Blacklist 273 | |-------------------------------------------------------------------------- 274 | | 275 | | Prevent connections from the following IP addresses 276 | | 277 | | e.g: '123.456.789.0, 987.654.32.1' 278 | | 279 | */ 280 | $config['rest_ip_blacklist'] = ''; 281 | 282 | /* 283 | |-------------------------------------------------------------------------- 284 | | REST Database Group 285 | |-------------------------------------------------------------------------- 286 | | 287 | | Connect to a database group for keys, logging, etc. It will only connect 288 | | if you have any of these features enabled 289 | | 290 | */ 291 | $config['rest_database_group'] = 'default'; 292 | 293 | /* 294 | |-------------------------------------------------------------------------- 295 | | REST API Keys Table Name 296 | |-------------------------------------------------------------------------- 297 | | 298 | | The table name in your database that stores API keys 299 | | 300 | */ 301 | $config['rest_keys_table'] = 'keys'; 302 | 303 | /* 304 | |-------------------------------------------------------------------------- 305 | | REST Enable Keys 306 | |-------------------------------------------------------------------------- 307 | | 308 | | When set to TRUE, the REST API will look for a column name called 'key'. 309 | | If no key is provided, the request will result in an error. To override the 310 | | column name see 'rest_key_column' 311 | | 312 | | Default table schema: 313 | | CREATE TABLE `keys` ( 314 | | `id` INT(11) NOT NULL AUTO_INCREMENT, 315 | | `user_id` INT(11) NOT NULL, 316 | | `key` VARCHAR(40) NOT NULL, 317 | | `level` INT(2) NOT NULL, 318 | | `ignore_limits` TINYINT(1) NOT NULL DEFAULT '0', 319 | | `is_private_key` TINYINT(1) NOT NULL DEFAULT '0', 320 | | `ip_addresses` TEXT NULL DEFAULT NULL, 321 | | `date_created` INT(11) NOT NULL, 322 | | `expires` INT(11) NOT NULL 323 | | PRIMARY KEY (`id`) 324 | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 325 | | 326 | | For PostgreSQL 327 | | CREATE TABLE keys ( 328 | | id SERIAL, 329 | | user_id INT NOT NULL, 330 | | key VARCHAR(40) NOT NULL, 331 | | level INT NOT NULL, 332 | | ignore_limits SMALLINT NOT NULL DEFAULT '0', 333 | | is_private_key SMALLINT NOT NULL DEFAULT '0', 334 | | ip_addresses TEXT NULL DEFAULT NULL, 335 | | date_created INT NOT NULL, 336 | | expires INT NOT NULL, 337 | | PRIMARY KEY (id) 338 | | ) ; 339 | | | 340 | */ 341 | $config['rest_enable_keys'] = false; 342 | 343 | /* 344 | |-------------------------------------------------------------------------- 345 | | REST Table Key Column Name 346 | |-------------------------------------------------------------------------- 347 | | 348 | | If not using the default table schema in 'rest_enable_keys', specify the 349 | | column name to match e.g. my_key 350 | | 351 | */ 352 | $config['rest_key_column'] = 'key'; 353 | /* 354 | |-------------------------------------------------------------------------- 355 | | REST Table Key Expiry Config and Column Name 356 | |-------------------------------------------------------------------------- 357 | | 358 | | Configure wether or not api keys should expire, and the column name to 359 | | match e.g. expires 360 | | Note: the value in the column will be treated as a unix timestamp and 361 | | compared with php function time() 362 | | 363 | */ 364 | $config['rest_keys_expire'] = false; 365 | $config['rest_keys_expiry_column'] = 'expires'; 366 | 367 | /* 368 | |-------------------------------------------------------------------------- 369 | | REST API Limits method 370 | |-------------------------------------------------------------------------- 371 | | 372 | | Specify the method used to limit the API calls 373 | | 374 | | Available methods are : 375 | | $config['rest_limits_method'] = 'IP_ADDRESS'; // Put a limit per ip address 376 | | $config['rest_limits_method'] = 'API_KEY'; // Put a limit per api key 377 | | $config['rest_limits_method'] = 'METHOD_NAME'; // Put a limit on method calls 378 | | $config['rest_limits_method'] = 'ROUTED_URL'; // Put a limit on the routed URL 379 | | 380 | */ 381 | $config['rest_limits_method'] = 'ROUTED_URL'; 382 | 383 | /* 384 | |-------------------------------------------------------------------------- 385 | | REST Key Length 386 | |-------------------------------------------------------------------------- 387 | | 388 | | Length of the created keys. Check your default database schema on the 389 | | maximum length allowed 390 | | 391 | | Note: The maximum length is 40 392 | | 393 | */ 394 | $config['rest_key_length'] = 40; 395 | 396 | /* 397 | |-------------------------------------------------------------------------- 398 | | REST API Key Variable 399 | |-------------------------------------------------------------------------- 400 | | 401 | | Custom header to specify the API key 402 | 403 | | Note: Custom headers with the X- prefix are deprecated as of 404 | | 2012/06/12. See RFC 6648 specification for more details 405 | | 406 | */ 407 | $config['rest_key_name'] = 'X-API-KEY'; 408 | 409 | /* 410 | |-------------------------------------------------------------------------- 411 | | REST Enable Logging 412 | |-------------------------------------------------------------------------- 413 | | 414 | | When set to TRUE, the REST API will log actions based on the column names 'key', 'date', 415 | | 'time' and 'ip_address'. This is a general rule that can be overridden in the 416 | | $this->method array for each controller 417 | | 418 | | Default table schema: 419 | | CREATE TABLE `logs` ( 420 | | `id` INT(11) NOT NULL AUTO_INCREMENT, 421 | | `uri` VARCHAR(255) NOT NULL, 422 | | `method` VARCHAR(6) NOT NULL, 423 | | `params` TEXT DEFAULT NULL, 424 | | `api_key` VARCHAR(40) NOT NULL, 425 | | `ip_address` VARCHAR(45) NOT NULL, 426 | | `time` INT(11) NOT NULL, 427 | | `rtime` FLOAT DEFAULT NULL, 428 | | `authorized` VARCHAR(1) NOT NULL, 429 | | `response_code` smallint(3) DEFAULT '0', 430 | | PRIMARY KEY (`id`) 431 | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 432 | | 433 | | For PostgreSQL 434 | | CREATE TABLE logs ( 435 | | id SERIAL, 436 | | uri VARCHAR(255) NOT NULL, 437 | | method VARCHAR(6) NOT NULL, 438 | | params TEXT DEFAULT NULL, 439 | | api_key VARCHAR(40) NOT NULL, 440 | | ip_address VARCHAR(45) NOT NULL, 441 | | time INT NOT NULL, 442 | | rtime DOUBLE PRECISION DEFAULT NULL, 443 | | authorized boolean NOT NULL, 444 | | response_code smallint DEFAULT '0', 445 | | PRIMARY KEY (id) 446 | | ) ; 447 | */ 448 | $config['rest_enable_logging'] = false; 449 | 450 | /* 451 | |-------------------------------------------------------------------------- 452 | | REST API Logs Table Name 453 | |-------------------------------------------------------------------------- 454 | | 455 | | If not using the default table schema in 'rest_enable_logging', specify the 456 | | table name to match e.g. my_logs 457 | | 458 | */ 459 | $config['rest_logs_table'] = 'logs'; 460 | 461 | /* 462 | |-------------------------------------------------------------------------- 463 | | REST Method Access Control 464 | |-------------------------------------------------------------------------- 465 | | When set to TRUE, the REST API will check the access table to see if 466 | | the API key can access that controller. 'rest_enable_keys' must be enabled 467 | | to use this 468 | | 469 | | Default table schema: 470 | | CREATE TABLE `access` ( 471 | | `id` INT(11) unsigned NOT NULL AUTO_INCREMENT, 472 | | `key` VARCHAR(40) NOT NULL DEFAULT '', 473 | | `all_access` TINYINT(1) NOT NULL DEFAULT '0', 474 | | `controller` VARCHAR(50) NOT NULL DEFAULT '', 475 | | `date_created` DATETIME DEFAULT NULL, 476 | | `date_modified` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 477 | | PRIMARY KEY (`id`) 478 | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 479 | | 480 | | For PostgreSQL 481 | | CREATE TABLE access ( 482 | | id SERIAL, 483 | | key VARCHAR(40) NOT NULL DEFAULT '', 484 | | all_access SMALLINT NOT NULL DEFAULT '0', 485 | | controller VARCHAR(50) NOT NULL DEFAULT '', 486 | | date_created TIMESTAMP(0) DEFAULT NULL, 487 | | date_modified TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP, 488 | | PRIMARY KEY (id) 489 | | ) ; 490 | | CREATE OR REPLACE FUNCTION upd_timestamp() RETURNS TRIGGER 491 | | LANGUAGE plpgsql 492 | | AS 493 | | $$ 494 | | BEGIN 495 | | NEW.modified = CURRENT_TIMESTAMP; 496 | | RETURN NEW; 497 | | END; 498 | | $$; 499 | | CREATE TRIGGER trigger_access 500 | | BEFORE UPDATE 501 | | ON access 502 | | FOR EACH ROW 503 | | EXECUTE PROCEDURE upd_timestamp(); 504 | | 505 | */ 506 | $config['rest_enable_access'] = false; 507 | 508 | /* 509 | |-------------------------------------------------------------------------- 510 | | REST API Access Table Name 511 | |-------------------------------------------------------------------------- 512 | | 513 | | If not using the default table schema in 'rest_enable_access', specify the 514 | | table name to match e.g. my_access 515 | | 516 | */ 517 | $config['rest_access_table'] = 'access'; 518 | 519 | /* 520 | |-------------------------------------------------------------------------- 521 | | REST API Param Log Format 522 | |-------------------------------------------------------------------------- 523 | | 524 | | When set to TRUE, the REST API log parameters will be stored in the database as JSON 525 | | Set to FALSE to log as serialized PHP 526 | | 527 | */ 528 | $config['rest_logs_json_params'] = false; 529 | 530 | /* 531 | |-------------------------------------------------------------------------- 532 | | REST Enable Limits 533 | |-------------------------------------------------------------------------- 534 | | 535 | | When set to TRUE, the REST API will count the number of uses of each method 536 | | by an API key each hour. This is a general rule that can be overridden in the 537 | | $this->method array in each controller 538 | | 539 | | Default table schema: 540 | | CREATE TABLE `limits` ( 541 | | `id` INT(11) NOT NULL AUTO_INCREMENT, 542 | | `uri` VARCHAR(255) NOT NULL, 543 | | `count` INT(10) NOT NULL, 544 | | `hour_started` INT(11) NOT NULL, 545 | | `api_key` VARCHAR(40) NOT NULL, 546 | | PRIMARY KEY (`id`) 547 | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 548 | | 549 | | For PostgreSQL 550 | | CREATE TABLE limits ( 551 | | id SERIAL, 552 | | uri VARCHAR(255) NOT NULL, 553 | | count INT NOT NULL, 554 | | hour_started INT NOT NULL, 555 | | api_key VARCHAR(40) NOT NULL, 556 | | PRIMARY KEY (id) 557 | | ) ; 558 | | 559 | | To specify the limits within the controller's __construct() method, add per-method 560 | | limits with: 561 | | 562 | | $this->methods['METHOD_NAME']['limit'] = [NUM_REQUESTS_PER_HOUR]; 563 | | 564 | | See application/controllers/api/example.php for examples 565 | */ 566 | $config['rest_enable_limits'] = false; 567 | 568 | /* 569 | |-------------------------------------------------------------------------- 570 | | REST API Limits Table Name 571 | |-------------------------------------------------------------------------- 572 | | 573 | | If not using the default table schema in 'rest_enable_limits', specify the 574 | | table name to match e.g. my_limits 575 | | 576 | */ 577 | $config['rest_limits_table'] = 'limits'; 578 | 579 | /* 580 | |-------------------------------------------------------------------------- 581 | | REST Ignore HTTP Accept 582 | |-------------------------------------------------------------------------- 583 | | 584 | | Set to TRUE to ignore the HTTP Accept and speed up each request a little. 585 | | Only do this if you are using the $this->rest_format or /format/xml in URLs 586 | | 587 | */ 588 | $config['rest_ignore_http_accept'] = false; 589 | 590 | /* 591 | |-------------------------------------------------------------------------- 592 | | REST AJAX Only 593 | |-------------------------------------------------------------------------- 594 | | 595 | | Set to TRUE to allow AJAX requests only. Set to FALSE to accept HTTP requests 596 | | 597 | | Note: If set to TRUE and the request is not AJAX, a 505 response with the 598 | | error message 'Only AJAX requests are accepted.' will be returned. 599 | | 600 | | Hint: This is good for production environments 601 | | 602 | */ 603 | $config['rest_ajax_only'] = false; 604 | 605 | /* 606 | |-------------------------------------------------------------------------- 607 | | REST Language File 608 | |-------------------------------------------------------------------------- 609 | | 610 | | Language file to load from the language directory 611 | | 612 | */ 613 | $config['rest_language'] = 'english'; 614 | 615 | /* 616 | |-------------------------------------------------------------------------- 617 | | CORS Check 618 | |-------------------------------------------------------------------------- 619 | | 620 | | Set to TRUE to enable Cross-Origin Resource Sharing (CORS). Useful if you 621 | | are hosting your API on a different domain from the application that 622 | | will access it through a browser 623 | | 624 | */ 625 | $config['check_cors'] = false; 626 | 627 | /* 628 | |-------------------------------------------------------------------------- 629 | | CORS Allowable Headers 630 | |-------------------------------------------------------------------------- 631 | | 632 | | If using CORS checks, set the allowable headers here 633 | | 634 | */ 635 | $config['allowed_cors_headers'] = [ 636 | 'Origin', 637 | 'X-Requested-With', 638 | 'Content-Type', 639 | 'Accept', 640 | 'Access-Control-Request-Method', 641 | ]; 642 | 643 | /* 644 | |-------------------------------------------------------------------------- 645 | | CORS Allowable Methods 646 | |-------------------------------------------------------------------------- 647 | | 648 | | If using CORS checks, you can set the methods you want to be allowed 649 | | 650 | */ 651 | $config['allowed_cors_methods'] = [ 652 | 'GET', 653 | 'POST', 654 | 'OPTIONS', 655 | 'PUT', 656 | 'PATCH', 657 | 'DELETE', 658 | ]; 659 | 660 | /* 661 | |-------------------------------------------------------------------------- 662 | | CORS Allow Any Domain 663 | |-------------------------------------------------------------------------- 664 | | 665 | | Set to TRUE to enable Cross-Origin Resource Sharing (CORS) from any 666 | | source domain 667 | | 668 | */ 669 | $config['allow_any_cors_domain'] = false; 670 | 671 | /* 672 | |-------------------------------------------------------------------------- 673 | | CORS Allowable Domains 674 | |-------------------------------------------------------------------------- 675 | | 676 | | Used if $config['check_cors'] is set to TRUE and $config['allow_any_cors_domain'] 677 | | is set to FALSE. Set all the allowable domains within the array 678 | | 679 | | e.g. $config['allowed_origins'] = ['http://www.example.com', 'https://spa.example.com'] 680 | | 681 | */ 682 | $config['allowed_cors_origins'] = []; 683 | 684 | /* 685 | |-------------------------------------------------------------------------- 686 | | CORS Forced Headers 687 | |-------------------------------------------------------------------------- 688 | | 689 | | If using CORS checks, always include the headers and values specified here 690 | | in the OPTIONS client preflight. 691 | | Example: 692 | | $config['forced_cors_headers'] = [ 693 | | 'Access-Control-Allow-Credentials' => 'true' 694 | | ]; 695 | | 696 | | Added because of how Sencha Ext JS framework requires the header 697 | | Access-Control-Allow-Credentials to be set to true to allow the use of 698 | | credentials in the REST Proxy. 699 | | See documentation here: 700 | | http://docs.sencha.com/extjs/6.5.2/classic/Ext.data.proxy.Rest.html#cfg-withCredentials 701 | | 702 | */ 703 | $config['forced_cors_headers'] = []; 704 | --------------------------------------------------------------------------------