REST stands for Representational State Transfer is this a way to communicate to computers over HTTP protocol like browser to servers
Sharing data between two computer over HTTP is always a requirements of an application.
Modern REST API uses JSON Data for transaction of data between two computers
I wil use Phil Sturgeon, Chris Kacerguis RestServer Library.
#Requirments
PHP 7.2 or greater
Codeigniter 3.1.11 or greater
Lets Start Installing;
if you do not have codeigniter then download from codeigniter
unzip and place it inside www directory of your computer. I will name it blog-v1
Open that blog-v1 directory then open libraries inside application directory
and add the following file REST_controller.php and Format.php
<?php
namespace Restserver\Libraries;
use CI_Controller;
use Exception;
use stdClass;
defined('BASEPATH') OR exit('No direct script access allowed');
/**
* CodeIgniter Rest Controller
* A fully RESTful server implementation for CodeIgniter using one library, one config file and one controller.
*
* @package CodeIgniter
* @subpackage Libraries
* @category Libraries
* @author Phil Sturgeon, Chris Kacerguis
* @license MIT
* @link https://github.com/chriskacerguis/codeigniter-restserver
* @version 3.0.0
*/
abstract class REST_Controller extends CI_Controller {
// Note: Only the widely used HTTP status codes are documented
// Informational
const HTTP_CONTINUE = 100;
const HTTP_SWITCHING_PROTOCOLS = 101;
const HTTP_PROCESSING = 102; // RFC2518
// Success
/**
* The request has succeeded
*/
const HTTP_OK = 200;
/**
* The server successfully created a new resource
*/
const HTTP_CREATED = 201;
const HTTP_ACCEPTED = 202;
const HTTP_NON_AUTHORITATIVE_INFORMATION = 203;
/**
* The server successfully processed the request, though no content is returned
*/
const HTTP_NO_CONTENT = 204;
const HTTP_RESET_CONTENT = 205;
const HTTP_PARTIAL_CONTENT = 206;
const HTTP_MULTI_STATUS = 207; // RFC4918
const HTTP_ALREADY_REPORTED = 208; // RFC5842
const HTTP_IM_USED = 226; // RFC3229
// Redirection
const HTTP_MULTIPLE_CHOICES = 300;
const HTTP_MOVED_PERMANENTLY = 301;
const HTTP_FOUND = 302;
const HTTP_SEE_OTHER = 303;
/**
* The resource has not been modified since the last request
*/
const HTTP_NOT_MODIFIED = 304;
const HTTP_USE_PROXY = 305;
const HTTP_RESERVED = 306;
const HTTP_TEMPORARY_REDIRECT = 307;
const HTTP_PERMANENTLY_REDIRECT = 308; // RFC7238
// Client Error
/**
* The request cannot be fulfilled due to multiple errors
*/
const HTTP_BAD_REQUEST = 400;
/**
* The user is unauthorized to access the requested resource
*/
const HTTP_UNAUTHORIZED = 401;
const HTTP_PAYMENT_REQUIRED = 402;
/**
* The requested resource is unavailable at this present time
*/
const HTTP_FORBIDDEN = 403;
/**
* The requested resource could not be found
*
* Note: This is sometimes used to mask if there was an UNAUTHORIZED (401) or
* FORBIDDEN (403) error, for security reasons
*/
const HTTP_NOT_FOUND = 404;
/**
* The request method is not supported by the following resource
*/
const HTTP_METHOD_NOT_ALLOWED = 405;
/**
* The request was not acceptable
*/
const HTTP_NOT_ACCEPTABLE = 406;
const HTTP_PROXY_AUTHENTICATION_REQUIRED = 407;
const HTTP_REQUEST_TIMEOUT = 408;
/**
* The request could not be completed due to a conflict with the current state
* of the resource
*/
const HTTP_CONFLICT = 409;
const HTTP_GONE = 410;
const HTTP_LENGTH_REQUIRED = 411;
const HTTP_PRECONDITION_FAILED = 412;
const HTTP_REQUEST_ENTITY_TOO_LARGE = 413;
const HTTP_REQUEST_URI_TOO_LONG = 414;
const HTTP_UNSUPPORTED_MEDIA_TYPE = 415;
const HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = 416;
const HTTP_EXPECTATION_FAILED = 417;
const HTTP_I_AM_A_TEAPOT = 418; // RFC2324
const HTTP_UNPROCESSABLE_ENTITY = 422; // RFC4918
const HTTP_LOCKED = 423; // RFC4918
const HTTP_FAILED_DEPENDENCY = 424; // RFC4918
const HTTP_RESERVED_FOR_WEBDAV_ADVANCED_COLLECTIONS_EXPIRED_PROPOSAL = 425; // RFC2817
const HTTP_UPGRADE_REQUIRED = 426; // RFC2817
const HTTP_PRECONDITION_REQUIRED = 428; // RFC6585
const HTTP_TOO_MANY_REQUESTS = 429; // RFC6585
const HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431; // RFC6585
// Server Error
/**
* The server encountered an unexpected error
*
* Note: This is a generic error message when no specific message
* is suitable
*/
const HTTP_INTERNAL_SERVER_ERROR = 500;
/**
* The server does not recognise the request method
*/
const HTTP_NOT_IMPLEMENTED = 501;
const HTTP_BAD_GATEWAY = 502;
const HTTP_SERVICE_UNAVAILABLE = 503;
const HTTP_GATEWAY_TIMEOUT = 504;
const HTTP_VERSION_NOT_SUPPORTED = 505;
const HTTP_VARIANT_ALSO_NEGOTIATES_EXPERIMENTAL = 506; // RFC2295
const HTTP_INSUFFICIENT_STORAGE = 507; // RFC4918
const HTTP_LOOP_DETECTED = 508; // RFC5842
const HTTP_NOT_EXTENDED = 510; // RFC2774
const HTTP_NETWORK_AUTHENTICATION_REQUIRED = 511;
/**
* This defines the rest format
* Must be overridden it in a controller so that it is set
*
* @var string|NULL
*/
protected $rest_format = NULL;
/**
* Defines the list of method properties such as limit, log and level
*
* @var array
*/
protected $methods = [];
/**
* List of allowed HTTP methods
*
* @var array
*/
protected $allowed_http_methods = ['get', 'delete', 'post', 'put', 'options', 'patch', 'head'];
/**
* Contains details about the request
* Fields: body, format, method, ssl
* Note: This is a dynamic object (stdClass)
*
* @var object
*/
protected $request = NULL;
/**
* Contains details about the response
* Fields: format, lang
* Note: This is a dynamic object (stdClass)
*
* @var object
*/
protected $response = NULL;
/**
* Contains details about the REST API
* Fields: db, ignore_limits, key, level, user_id
* Note: This is a dynamic object (stdClass)
*
* @var object
*/
protected $rest = NULL;
/**
* The arguments for the GET request method
*
* @var array
*/
protected $_get_args = [];
/**
* The arguments for the POST request method
*
* @var array
*/
protected $_post_args = [];
/**
* The arguments for the PUT request method
*
* @var array
*/
protected $_put_args = [];
/**
* The arguments for the DELETE request method
*
* @var array
*/
protected $_delete_args = [];
/**
* The arguments for the PATCH request method
*
* @var array
*/
protected $_patch_args = [];
/**
* The arguments for the HEAD request method
*
* @var array
*/
protected $_head_args = [];
/**
* The arguments for the OPTIONS request method
*
* @var array
*/
protected $_options_args = [];
/**
* The arguments for the query parameters
*
* @var array
*/
protected $_query_args = [];
/**
* The arguments from GET, POST, PUT, DELETE, PATCH, HEAD and OPTIONS request methods combined
*
* @var array
*/
protected $_args = [];
/**
* The insert_id of the log entry (if we have one)
*
* @var string
*/
protected $_insert_id = '';
/**
* If the request is allowed based on the API key provided
*
* @var bool
*/
protected $_allow = TRUE;
/**
* The LDAP Distinguished Name of the User post authentication
*
* @var string
*/
protected $_user_ldap_dn = '';
/**
* The start of the response time from the server
*
* @var number
*/
protected $_start_rtime;
/**
* The end of the response time from the server
*
* @var number
*/
protected $_end_rtime;
/**
* List all supported methods, the first will be the default format
*
* @var array
*/
protected $_supported_formats = [
'json' => 'application/json',
'array' => 'application/json',
'csv' => 'application/csv',
'html' => 'text/html',
'jsonp' => 'application/javascript',
'php' => 'text/plain',
'serialized' => 'application/vnd.php.serialized',
'xml' => 'application/xml'
];
/**
* Information about the current API user
*
* @var object
*/
protected $_apiuser;
/**
* Whether or not to perform a CORS check and apply CORS headers to the request
*
* @var bool
*/
protected $check_cors = NULL;
/**
* Enable XSS flag
* Determines whether the XSS filter is always active when
* GET, OPTIONS, HEAD, POST, PUT, DELETE and PATCH data is encountered
* Set automatically based on config setting
*
* @var bool
*/
protected $_enable_xss = FALSE;
private $is_valid_request = TRUE;
/**
* HTTP status codes and their respective description
* Note: Only the widely used HTTP status codes are used
*
* @var array
* @link http://www.restapitutorial.com/httpstatuscodes.html
*/
protected $http_status_codes = [
self::HTTP_OK => 'OK',
self::HTTP_CREATED => 'CREATED',
self::HTTP_NO_CONTENT => 'NO CONTENT',
self::HTTP_NOT_MODIFIED => 'NOT MODIFIED',
self::HTTP_BAD_REQUEST => 'BAD REQUEST',
self::HTTP_UNAUTHORIZED => 'UNAUTHORIZED',
self::HTTP_FORBIDDEN => 'FORBIDDEN',
self::HTTP_NOT_FOUND => 'NOT FOUND',
self::HTTP_METHOD_NOT_ALLOWED => 'METHOD NOT ALLOWED',
self::HTTP_NOT_ACCEPTABLE => 'NOT ACCEPTABLE',
self::HTTP_CONFLICT => 'CONFLICT',
self::HTTP_INTERNAL_SERVER_ERROR => 'INTERNAL SERVER ERROR',
self::HTTP_NOT_IMPLEMENTED => 'NOT IMPLEMENTED'
];
/**
* @var Format
*/
private $format;
/**
* @var bool
*/
private $auth_override;
/**
* Extend this function to apply additional checking early on in the process
*
* @access protected
* @return void
*/
protected function early_checks()
{
}
/**
* Constructor for the REST API
*
* @access public
* @param string $config Configuration filename minus the file extension
* e.g: my_rest.php is passed as 'my_rest'
*/
public function __construct($config = 'rest')
{
parent::__construct();
$this->preflight_checks();
// Set the default value of global xss filtering. Same approach as CodeIgniter 3
$this->_enable_xss = ($this->config->item('global_xss_filtering') === TRUE);
// Don't try to parse template variables like {elapsed_time} and {memory_usage}
// when output is displayed for not damaging data accidentally
$this->output->parse_exec_vars = FALSE;
// Log the loading time to the log table
if ($this->config->item('rest_enable_logging') === TRUE)
{
// Start the timer for how long the request takes
$this->_start_rtime = microtime(TRUE);
}
// Load the rest.php configuration file
$this->get_local_config($config);
// At present the library is bundled with REST_Controller 2.5+, but will eventually be part of CodeIgniter (no citation)
//if(class_exists('Format'))
//{
// $this->format = new Format();
//}
//else
//{
// $this->load->library('Format', NULL, 'libraryFormat');
// $this->format = $this->libraryFormat;
//}
// Determine supported output formats from configuration
$supported_formats = $this->config->item('rest_supported_formats');
// Validate the configuration setting output formats
if (empty($supported_formats))
{
$supported_formats = [];
}
if ( ! is_array($supported_formats))
{
$supported_formats = [$supported_formats];
}
// Add silently the default output format if it is missing
$default_format = $this->_get_default_output_format();
if (!in_array($default_format, $supported_formats))
{
$supported_formats[] = $default_format;
}
// Now update $this->_supported_formats
$this->_supported_formats = array_intersect_key($this->_supported_formats, array_flip($supported_formats));
// Get the language
$language = $this->config->item('rest_language');
if ($language === NULL)
{
$language = 'english';
}
// Load the language file
$this->lang->load('rest_controller', $language, FALSE, TRUE, __DIR__.'/../');
// Initialise the response, request and rest objects
$this->request = new stdClass();
$this->response = new stdClass();
$this->rest = new stdClass();
// Check to see if the current IP address is blacklisted
if ($this->config->item('rest_ip_blacklist_enabled') === TRUE)
{
$this->_check_blacklist_auth();
}
// Determine whether the connection is HTTPS
$this->request->ssl = is_https();
// How is this request being made? GET, POST, PATCH, DELETE, INSERT, PUT, HEAD or OPTIONS
$this->request->method = $this->_detect_method();
// Check for CORS access request
$check_cors = $this->config->item('check_cors');
if ($check_cors === TRUE)
{
$this->_check_cors();
}
// Create an argument container if it doesn't exist e.g. _get_args
if (isset($this->{'_'.$this->request->method.'_args'}) === FALSE)
{
$this->{'_'.$this->request->method.'_args'} = [];
}
// Set up the query parameters
$this->_parse_query();
// Set up the GET variables
$this->_get_args = array_merge($this->_get_args, $this->uri->ruri_to_assoc());
// Try to find a format for the request (means we have a request body)
$this->request->format = $this->_detect_input_format();
// Not all methods have a body attached with them
$this->request->body = NULL;
$this->{'_parse_' . $this->request->method}();
// Fix parse method return arguments null
if($this->{'_'.$this->request->method.'_args'} === null)
{
$this->{'_'.$this->request->method.'_args'} = [];
}
// Which format should the data be returned in?
$this->response->format = $this->_detect_output_format();
// Which language should the data be returned in?
$this->response->lang = $this->_detect_lang();
// Now we know all about our request, let's try and parse the body if it exists
if ($this->request->format && $this->request->body)
{
$this->request->body = Format::factory($this->request->body, $this->request->format)->to_array();
// Assign payload arguments to proper method container
$this->{'_'.$this->request->method.'_args'} = $this->request->body;
}
//get header vars
$this->_head_args = $this->input->request_headers();
// Merge both for one mega-args variable
$this->_args = array_merge(
$this->_get_args,
$this->_options_args,
$this->_patch_args,
$this->_head_args,
$this->_put_args,
$this->_post_args,
$this->_delete_args,
$this->{'_'.$this->request->method.'_args'}
);
// Extend this function to apply additional checking early on in the process
$this->early_checks();
// Load DB if its enabled
if ($this->config->item('rest_database_group') && ($this->config->item('rest_enable_keys') || $this->config->item('rest_enable_logging')))
{
$this->rest->db = $this->load->database($this->config->item('rest_database_group'), TRUE);
}
// Use whatever database is in use (isset returns FALSE)
elseif (property_exists($this, 'db'))
{
$this->rest->db = $this->db;
}
// Check if there is a specific auth type for the current class/method
// _auth_override_check could exit so we need $this->rest->db initialized before
$this->auth_override = $this->_auth_override_check();
// Checking for keys? GET TO WorK!
// Skip keys test for $config['auth_override_class_method']['class'['method'] = 'none'
if ($this->config->item('rest_enable_keys') && $this->auth_override !== TRUE)
{
$this->_allow = $this->_detect_api_key();
}
// Only allow ajax requests
if ($this->input->is_ajax_request() === FALSE && $this->config->item('rest_ajax_only'))
{
// Display an error response
$this->response([
$this->config->item('rest_status_field_name') => FALSE,
$this->config->item('rest_message_field_name') => $this->lang->line('text_rest_ajax_only')
], self::HTTP_NOT_ACCEPTABLE);
}
// When there is no specific override for the current class/method, use the default auth value set in the config
if ($this->auth_override === FALSE &&
(! ($this->config->item('rest_enable_keys') && $this->_allow === TRUE) ||
($this->config->item('allow_auth_and_keys') === TRUE && $this->_allow === TRUE)))
{
$rest_auth = strtolower($this->config->item('rest_auth'));
switch ($rest_auth)
{
case 'basic':
$this->_prepare_basic_auth();
break;
case 'digest':
$this->_prepare_digest_auth();
break;
case 'session':
$this->_check_php_session();
break;
}
}
}
/**
* @param $config_file
*/
private function get_local_config($config_file)
{
if(file_exists(__DIR__."/../config/".$config_file.".php"))
{
$config = array();
include(__DIR__ . "/../config/" . $config_file . ".php");
foreach($config AS $key => $value)
{
$this->config->set_item($key, $value);
}
}
$this->load->config($config_file, FALSE, TRUE);
}
/**
* De-constructor
*
* @author Chris Kacerguis
* @access public
* @return void
*/
public function __destruct()
{
// Log the loading time to the log table
if ($this->config->item('rest_enable_logging') === TRUE)
{
// Get the current timestamp
$this->_end_rtime = microtime(TRUE);
$this->_log_access_time();
}
}
/**
* Checks to see if we have everything we need to run this library.
*
* @access protected
* @throws Exception
*/
protected function preflight_checks()
{
// Check to see if PHP is equal to or greater than 5.4.x
if (is_php('5.4') === FALSE)
{
// CodeIgniter 3 is recommended for v5.4 or above
throw new Exception('Using PHP v'.PHP_VERSION.', though PHP v5.4 or greater is required');
}
// Check to see if this is CI 3.x
if (explode('.', CI_VERSION, 2)[0] < 3)
{
throw new Exception('REST Server requires CodeIgniter 3.x');
}
}
/**
* Requests are not made to methods directly, the request will be for
* an "object". This simply maps the object and method to the correct
* Controller method
*
* @access public
* @param string $object_called
* @param array $arguments The arguments passed to the controller method
* @throws Exception
*/
public function _remap($object_called, $arguments = [])
{
// Should we answer if not over SSL?
if ($this->config->item('force_https') && $this->request->ssl === FALSE)
{
$this->response([
$this->config->item('rest_status_field_name') => FALSE,
$this->config->item('rest_message_field_name') => $this->lang->line('text_rest_unsupported')
], self::HTTP_FORBIDDEN);
}
// Remove the supported format from the function name e.g. index.json => index
$object_called = preg_replace('/^(.*)\.(?:'.implode('|', array_keys($this->_supported_formats)).')$/', '$1', $object_called);
$controller_method = $object_called.'_'.$this->request->method;
// Does this method exist? If not, try executing an index method
if (!method_exists($this, $controller_method)) {
$controller_method = "index_" . $this->request->method;
array_unshift($arguments, $object_called);
}
// Do we want to log this method (if allowed by config)?
$log_method = ! (isset($this->methods[$controller_method]['log']) && $this->methods[$controller_method]['log'] === FALSE);
// Use keys for this method?
$use_key = ! (isset($this->methods[$controller_method]['key']) && $this->methods[$controller_method]['key'] === FALSE);
// They provided a key, but it wasn't valid, so get them out of here
if ($this->config->item('rest_enable_keys') && $use_key && $this->_allow === FALSE)
{
if ($this->config->item('rest_enable_logging') && $log_method)
{
$this->_log_request();
}
// fix cross site to option request error
if($this->request->method == 'options') {
exit;
}
$this->response([
$this->config->item('rest_status_field_name') => FALSE,
$this->config->item('rest_message_field_name') => sprintf($this->lang->line('text_rest_invalid_api_key'), $this->rest->key)
], self::HTTP_FORBIDDEN);
}
// Check to see if this key has access to the requested controller
if ($this->config->item('rest_enable_keys') && $use_key && empty($this->rest->key) === FALSE && $this->_check_access() === FALSE)
{
if ($this->config->item('rest_enable_logging') && $log_method)
{
$this->_log_request();
}
$this->response([
$this->config->item('rest_status_field_name') => FALSE,
$this->config->item('rest_message_field_name') => $this->lang->line('text_rest_api_key_unauthorized')
], self::HTTP_UNAUTHORIZED);
}
// Sure it exists, but can they do anything with it?
if (! method_exists($this, $controller_method))
{
$this->response([
$this->config->item('rest_status_field_name') => FALSE,
$this->config->item('rest_message_field_name') => $this->lang->line('text_rest_unknown_method')
], self::HTTP_METHOD_NOT_ALLOWED);
}
// Doing key related stuff? Can only do it if they have a key right?
if ($this->config->item('rest_enable_keys') && empty($this->rest->key) === FALSE)
{
// Check the limit
if ($this->config->item('rest_enable_limits') && $this->_check_limit($controller_method) === FALSE)
{
$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')];
$this->response($response, self::HTTP_UNAUTHORIZED);
}
// If no level is set use 0, they probably aren't using permissions
$level = isset($this->methods[$controller_method]['level']) ? $this->methods[$controller_method]['level'] : 0;
// If no level is set, or it is lower than/equal to the key's level
$authorized = $level <= $this->rest->level;
// IM TELLIN!
if ($this->config->item('rest_enable_logging') && $log_method)
{
$this->_log_request($authorized);
}
if($authorized === FALSE)
{
// They don't have good enough perms
$response = [$this->config->item('rest_status_field_name') => FALSE, $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_api_key_permissions')];
$this->response($response, self::HTTP_UNAUTHORIZED);
}
}
//check request limit by ip without login
elseif ($this->config->item('rest_limits_method') == "IP_ADDRESS" && $this->config->item('rest_enable_limits') && $this->_check_limit($controller_method) === FALSE)
{
$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')];
$this->response($response, self::HTTP_UNAUTHORIZED);
}
// No key stuff, but record that stuff is happening
elseif ($this->config->item('rest_enable_logging') && $log_method)
{
$this->_log_request($authorized = TRUE);
}
// Call the controller method and passed arguments
try
{
if ($this->is_valid_request) {
call_user_func_array([$this, $controller_method], $arguments);
}
}
catch (Exception $ex)
{
if ($this->config->item('rest_handle_exceptions') === FALSE) {
throw $ex;
}
// If the method doesn't exist, then the error will be caught and an error response shown
$_error = &load_class('Exceptions', 'core');
$_error->show_exception($ex);
}
}
/**
* Takes mixed data and optionally a status code, then creates the response
*
* @access public
* @param array|NULL $data Data to output to the user
* @param int|NULL $http_code HTTP status code
* @param bool $continue TRUE to flush the response to the client and continue
* running the script; otherwise, exit
*/
public function response($data = NULL, $http_code = NULL, $continue = FALSE)
{
//if profiling enabled then print profiling data
$isProfilingEnabled = $this->config->item('enable_profiling');
if(!$isProfilingEnabled){
ob_start();
// If the HTTP status is not NULL, then cast as an integer
if ($http_code !== NULL)
{
// So as to be safe later on in the process
$http_code = (int) $http_code;
}
// Set the output as NULL by default
$output = NULL;
// If data is NULL and no HTTP status code provided, then display, error and exit
if ($data === NULL && $http_code === NULL)
{
$http_code = self::HTTP_NOT_FOUND;
}
// If data is not NULL and a HTTP status code provided, then continue
elseif ($data !== NULL)
{
// If the format method exists, call and return the output in that format
if (method_exists(Format::class, 'to_' . $this->response->format))
{
// Set the format header
$this->output->set_content_type($this->_supported_formats[$this->response->format], strtolower($this->config->item('charset')));
$output = Format::factory($data)->{'to_' . $this->response->format}();
// An array must be parsed as a string, so as not to cause an array to string error
// Json is the most appropriate form for such a data type
if ($this->response->format === 'array')
{
$output = Format::factory($output)->{'to_json'}();
}
}
else
{
// If an array or object, then parse as a json, so as to be a 'string'
if (is_array($data) || is_object($data))
{
$data = Format::factory($data)->{'to_json'}();
}
// Format is not supported, so output the raw data as a string
$output = $data;
}
}
// If not greater than zero, then set the HTTP status code as 200 by default
// Though perhaps 500 should be set instead, for the developer not passing a
// correct HTTP status code
$http_code > 0 || $http_code = self::HTTP_OK;
$this->output->set_status_header($http_code);
// JC: Log response code only if rest logging enabled
if ($this->config->item('rest_enable_logging') === TRUE)
{
$this->_log_response_code($http_code);
}
// Output the data
$this->output->set_output($output);
if ($continue === FALSE)
{
// Display the data and exit execution
$this->output->_display();
exit;
}
else
{
ob_end_flush();
}
// Otherwise dump the output automatically
}
else{
echo json_encode($data);
}
}
/**
* Takes mixed data and optionally a status code, then creates the response
* within the buffers of the Output class. The response is sent to the client
* lately by the framework, after the current controller's method termination.
* All the hooks after the controller's method termination are executable
*
* @access public
* @param array|NULL $data Data to output to the user
* @param int|NULL $http_code HTTP status code
*/
public function set_response($data = NULL, $http_code = NULL)
{
$this->response($data, $http_code, TRUE);
}
/**
* Get the input format e.g. json or xml
*
* @access protected
* @return string|NULL Supported input format; otherwise, NULL
*/
protected function _detect_input_format()
{
// Get the CONTENT-TYPE value from the SERVER variable
$content_type = $this->input->server('CONTENT_TYPE');
if (empty($content_type) === FALSE)
{
// If a semi-colon exists in the string, then explode by ; and get the value of where
// the current array pointer resides. This will generally be the first element of the array
$content_type = (strpos($content_type, ';') !== FALSE ? current(explode(';', $content_type)) : $content_type);
// Check all formats against the CONTENT-TYPE header
foreach ($this->_supported_formats as $type => $mime)
{
// $type = format e.g. csv
// $mime = mime type e.g. application/csv
// If both the mime types match, then return the format
if ($content_type === $mime)
{
return $type;
}
}
}
return NULL;
}
/**
* Gets the default format from the configuration. Fallbacks to 'json'
* if the corresponding configuration option $config['rest_default_format']
* is missing or is empty
*
* @access protected
* @return string The default supported input format
*/
protected function _get_default_output_format()
{
$default_format = (string) $this->config->item('rest_default_format');
return $default_format === '' ? 'json' : $default_format;
}
/**
* Detect which format should be used to output the data
*
* @access protected
* @return mixed|NULL|string Output format
*/
protected function _detect_output_format()
{
// Concatenate formats to a regex pattern e.g. \.(csv|json|xml)
$pattern = '/\.('.implode('|', array_keys($this->_supported_formats)).')($|\/)/';
$matches = [];
// Check if a file extension is used e.g. http://example.com/api/index.json?param1=param2
if (preg_match($pattern, $this->uri->uri_string(), $matches))
{
return $matches[1];
}
// Get the format parameter named as 'format'
if (isset($this->_get_args['format']))
{
$format = strtolower($this->_get_args['format']);
if (isset($this->_supported_formats[$format]) === TRUE)
{
return $format;
}
}
// Get the HTTP_ACCEPT server variable
$http_accept = $this->input->server('HTTP_ACCEPT');
// Otherwise, check the HTTP_ACCEPT server variable
if ($this->config->item('rest_ignore_http_accept') === FALSE && $http_accept !== NULL)
{
// Check all formats against the HTTP_ACCEPT header
foreach (array_keys($this->_supported_formats) as $format)
{
// Has this format been requested?
if (strpos($http_accept, $format) !== FALSE)
{
if ($format !== 'html' && $format !== 'xml')
{
// If not HTML or XML assume it's correct
return $format;
}
elseif ($format === 'html' && strpos($http_accept, 'xml') === FALSE)
{
// HTML or XML have shown up as a match
// If it is truly HTML, it wont want any XML
return $format;
}
else if ($format === 'xml' && strpos($http_accept, 'html') === FALSE)
{
// If it is truly XML, it wont want any HTML
return $format;
}
}
}
}
// Check if the controller has a default format
if (empty($this->rest_format) === FALSE)
{
return $this->rest_format;
}
// Obtain the default format from the configuration
return $this->_get_default_output_format();
}
/**
* Get the HTTP request string e.g. get or post
*
* @access protected
* @return string|NULL Supported request method as a lowercase string; otherwise, NULL if not supported
*/
protected function _detect_method()
{
// Declare a variable to store the method
$method = NULL;
// Determine whether the 'enable_emulate_request' setting is enabled
if ($this->config->item('enable_emulate_request') === TRUE)
{
$method = $this->input->post('_method');
if ($method === NULL)
{
$method = $this->input->server('HTTP_X_HTTP_METHOD_OVERRIDE');
}
$method = strtolower($method);
}
if (empty($method))
{
// Get the request method as a lowercase string
$method = $this->input->method();
}
return in_array($method, $this->allowed_http_methods) && method_exists($this, '_parse_' . $method) ? $method : 'get';
}
/**
* See if the user has provided an API key
*
* @access protected
* @return bool
*/
protected function _detect_api_key()
{
// Get the api key name variable set in the rest config file
$api_key_variable = $this->config->item('rest_key_name');
// Work out the name of the SERVER entry based on config
$key_name = 'HTTP_' . strtoupper(str_replace('-', '_', $api_key_variable));
$this->rest->key = NULL;
$this->rest->level = NULL;
$this->rest->user_id = NULL;
$this->rest->ignore_limits = FALSE;
// Find the key from server or arguments
if (($key = isset($this->_args[$api_key_variable]) ? $this->_args[$api_key_variable] : $this->input->server($key_name)))
{
if ( ! ($row = $this->rest->db->where($this->config->item('rest_key_column'), $key)->get($this->config->item('rest_keys_table'))->row()))
{
return FALSE;
}
$this->rest->key = $row->{$this->config->item('rest_key_column')};
isset($row->user_id) && $this->rest->user_id = $row->user_id;
isset($row->level) && $this->rest->level = $row->level;
isset($row->ignore_limits) && $this->rest->ignore_limits = $row->ignore_limits;
$this->_apiuser = $row;
/*
* If "is private key" is enabled, compare the ip address with the list
* of valid ip addresses stored in the database
*/
if (empty($row->is_private_key) === FALSE)
{
// Check for a list of valid ip addresses
if (isset($row->ip_addresses))
{
// multiple ip addresses must be separated using a comma, explode and loop
$list_ip_addresses = explode(',', $row->ip_addresses);
$ip_address = $this->input->ip_address();
$found_address = FALSE;
foreach ($list_ip_addresses as $ip_address)
{
if ($ip_address === trim($ip_address))
{
// there is a match, set the the value to TRUE and break out of the loop
$found_address = TRUE;
break;
}
}
return $found_address;
}
else
{
// There should be at least one IP address for this private key
return FALSE;
}
}
return TRUE;
}
// No key has been sent
return FALSE;
}
/**
* Preferred return language
*
* @access protected
* @return string|NULL|array The language code
*/
protected function _detect_lang()
{
$lang = $this->input->server('HTTP_ACCEPT_LANGUAGE');
if ($lang === NULL)
{
return NULL;
}
// It appears more than one language has been sent using a comma delimiter
if (strpos($lang, ',') !== FALSE)
{
$langs = explode(',', $lang);
$return_langs = [];
foreach ($langs as $lang)
{
// Remove weight and trim leading and trailing whitespace
list($lang) = explode(';', $lang);
$return_langs[] = trim($lang);
}
return $return_langs;
}
// Otherwise simply return as a string
return $lang;
}
/**
* Add the request to the log table
*
* @access protected
* @param bool $authorized TRUE the user is authorized; otherwise, FALSE
* @return bool TRUE the data was inserted; otherwise, FALSE
*/
protected function _log_request($authorized = FALSE)
{
// Insert the request into the log table
$is_inserted = $this->rest->db
->insert(
$this->config->item('rest_logs_table'), [
'uri' => $this->uri->uri_string(),
'method' => $this->request->method,
'params' => $this->_args ? ($this->config->item('rest_logs_json_params') === TRUE ? json_encode($this->_args) : serialize($this->_args)) : NULL,
'api_key' => isset($this->rest->key) ? $this->rest->key : '',
'ip_address' => $this->input->ip_address(),
'time' => time(),
'authorized' => $authorized
]);
// Get the last insert id to update at a later stage of the request
$this->_insert_id = $this->rest->db->insert_id();
return $is_inserted;
}
/**
* Check if the requests to a controller method exceed a limit
*
* @access protected
* @param string $controller_method The method being called
* @return bool TRUE the call limit is below the threshold; otherwise, FALSE
*/
protected function _check_limit($controller_method)
{
// They are special, or it might not even have a limit
if (empty($this->rest->ignore_limits) === FALSE)
{
// Everything is fine
return TRUE;
}
$api_key = isset($this->rest->key) ? $this->rest->key : '';
switch ($this->config->item('rest_limits_method'))
{
case 'IP_ADDRESS':
$api_key = $this->input->ip_address();
$limited_uri = 'ip-address:' . $api_key;
break;
case 'API_KEY':
$limited_uri = 'api-key:' . $api_key;
break;
case 'METHOD_NAME':
$limited_uri = 'method-name:' . $controller_method;
break;
case 'ROUTED_URL':
default:
$limited_uri = $this->uri->ruri_string();
if (strpos(strrev($limited_uri), strrev($this->response->format)) === 0)
{
$limited_uri = substr($limited_uri,0, -strlen($this->response->format) - 1);
}
$limited_uri = 'uri:'.$limited_uri.':'.$this->request->method; // It's good to differentiate GET from PUT
break;
}
if (isset($this->methods[$controller_method]['limit']) === FALSE )
{
// Everything is fine
return TRUE;
}
// How many times can you get to this method in a defined time_limit (default: 1 hour)?
$limit = $this->methods[$controller_method]['limit'];
$time_limit = (isset($this->methods[$controller_method]['time']) ? $this->methods[$controller_method]['time'] : 3600); // 3600 = 60 * 60
// Get data about a keys' usage and limit to one row
$result = $this->rest->db
->where('uri', $limited_uri)
->where('api_key', $api_key)
->get($this->config->item('rest_limits_table'))
->row();
// No calls have been made for this key
if ($result === NULL)
{
// Create a new row for the following key
$this->rest->db->insert($this->config->item('rest_limits_table'), [
'uri' => $limited_uri,
'api_key' =>$api_key,
'count' => 1,
'hour_started' => time()
]);
}
// Been a time limit (or by default an hour) since they called
elseif ($result->hour_started < (time() - $time_limit))
{
// Reset the started period and count
$this->rest->db
->where('uri', $limited_uri)
->where('api_key', $api_key)
->set('hour_started', time())
->set('count', 1)
->update($this->config->item('rest_limits_table'));
}
// They have called within the hour, so lets update
else
{
// The limit has been exceeded
if ($result->count >= $limit)
{
return FALSE;
}
// Increase the count by one
$this->rest->db
->where('uri', $limited_uri)
->where('api_key', $api_key)
->set('count', 'count + 1', FALSE)
->update($this->config->item('rest_limits_table'));
}
return TRUE;
}
/**
* Check if there is a specific auth type set for the current class/method/HTTP-method being called
*
* @access protected
* @return bool
*/
protected function _auth_override_check()
{
// Assign the class/method auth type override array from the config
$auth_override_class_method = $this->config->item('auth_override_class_method');
// Check to see if the override array is even populated
if ( ! empty($auth_override_class_method))
{
// Check for wildcard flag for rules for classes
if ( ! empty($auth_override_class_method[$this->router->class]['*'])) // Check for class overrides
{
// No auth override found, prepare nothing but send back a TRUE override flag
if ($auth_override_class_method[$this->router->class]['*'] === 'none')
{
return TRUE;
}
// Basic auth override found, prepare basic
if ($auth_override_class_method[$this->router->class]['*'] === 'basic')
{
$this->_prepare_basic_auth();
return TRUE;
}
// Digest auth override found, prepare digest
if ($auth_override_class_method[$this->router->class]['*'] === 'digest')
{
$this->_prepare_digest_auth();
return TRUE;
}
// Session auth override found, check session
if ($auth_override_class_method[$this->router->class]['*'] === 'session')
{
$this->_check_php_session();
return TRUE;
}
// Whitelist auth override found, check client's ip against config whitelist
if ($auth_override_class_method[$this->router->class]['*'] === 'whitelist')
{
$this->_check_whitelist_auth();
return TRUE;
}
}
// Check to see if there's an override value set for the current class/method being called
if ( ! empty($auth_override_class_method[$this->router->class][$this->router->method]))
{
// None auth override found, prepare nothing but send back a TRUE override flag
if ($auth_override_class_method[$this->router->class][$this->router->method] === 'none')
{
return TRUE;
}
// Basic auth override found, prepare basic
if ($auth_override_class_method[$this->router->class][$this->router->method] === 'basic')
{
$this->_prepare_basic_auth();
return TRUE;
}
// Digest auth override found, prepare digest
if ($auth_override_class_method[$this->router->class][$this->router->method] === 'digest')
{
$this->_prepare_digest_auth();
return TRUE;
}
// Session auth override found, check session
if ($auth_override_class_method[$this->router->class][$this->router->method] === 'session')
{
$this->_check_php_session();
return TRUE;
}
// Whitelist auth override found, check client's ip against config whitelist
if ($auth_override_class_method[$this->router->class][$this->router->method] === 'whitelist')
{
$this->_check_whitelist_auth();
return TRUE;
}
}
}
// Assign the class/method/HTTP-method auth type override array from the config
$auth_override_class_method_http = $this->config->item('auth_override_class_method_http');
// Check to see if the override array is even populated
if ( ! empty($auth_override_class_method_http))
{
// check for wildcard flag for rules for classes
if ( ! empty($auth_override_class_method_http[$this->router->class]['*'][$this->request->method]))
{
// None auth override found, prepare nothing but send back a TRUE override flag
if ($auth_override_class_method_http[$this->router->class]['*'][$this->request->method] === 'none')
{
return TRUE;
}
// Basic auth override found, prepare basic
if ($auth_override_class_method_http[$this->router->class]['*'][$this->request->method] === 'basic')
{
$this->_prepare_basic_auth();
return TRUE;
}
// Digest auth override found, prepare digest
if ($auth_override_class_method_http[$this->router->class]['*'][$this->request->method] === 'digest')
{
$this->_prepare_digest_auth();
return TRUE;
}
// Session auth override found, check session
if ($auth_override_class_method_http[$this->router->class]['*'][$this->request->method] === 'session')
{
$this->_check_php_session();
return TRUE;
}
// Whitelist auth override found, check client's ip against config whitelist
if ($auth_override_class_method_http[$this->router->class]['*'][$this->request->method] === 'whitelist')
{
$this->_check_whitelist_auth();
return TRUE;
}
}
// Check to see if there's an override value set for the current class/method/HTTP-method being called
if ( ! empty($auth_override_class_method_http[$this->router->class][$this->router->method][$this->request->method]))
{
// None auth override found, prepare nothing but send back a TRUE override flag
if ($auth_override_class_method_http[$this->router->class][$this->router->method][$this->request->method] === 'none')
{
return TRUE;
}
// Basic auth override found, prepare basic
if ($auth_override_class_method_http[$this->router->class][$this->router->method][$this->request->method] === 'basic')
{
$this->_prepare_basic_auth();
return TRUE;
}
// Digest auth override found, prepare digest
if ($auth_override_class_method_http[$this->router->class][$this->router->method][$this->request->method] === 'digest')
{
$this->_prepare_digest_auth();
return TRUE;
}
// Session auth override found, check session
if ($auth_override_class_method_http[$this->router->class][$this->router->method][$this->request->method] === 'session')
{
$this->_check_php_session();
return TRUE;
}
// Whitelist auth override found, check client's ip against config whitelist
if ($auth_override_class_method_http[$this->router->class][$this->router->method][$this->request->method] === 'whitelist')
{
$this->_check_whitelist_auth();
return TRUE;
}
}
}
return FALSE;
}
/**
* Parse the GET request arguments
*
* @access protected
* @return void
*/
protected function _parse_get()
{
// Merge both the URI segments and query parameters
$this->_get_args = array_merge($this->_get_args, $this->_query_args);
}
/**
* Parse the POST request arguments
*
* @access protected
* @return void
*/
protected function _parse_post()
{
$this->_post_args = $_POST;
if ($this->request->format)
{
$this->request->body = $this->input->raw_input_stream;
}
}
/**
* Parse the PUT request arguments
*
* @access protected
* @return void
*/
protected function _parse_put()
{
if ($this->request->format)
{
$this->request->body = $this->input->raw_input_stream;
if ($this->request->format === 'json')
{
$this->_put_args = json_decode($this->input->raw_input_stream);
}
}
else if ($this->input->method() === 'put')
{
// If no file type is provided, then there are probably just arguments
$this->_put_args = $this->input->input_stream();
}
}
/**
* Parse the HEAD request arguments
*
* @access protected
* @return void
*/
protected function _parse_head()
{
// Parse the HEAD variables
parse_str(parse_url($this->input->server('REQUEST_URI'), PHP_URL_QUERY), $head);
// Merge both the URI segments and HEAD params
$this->_head_args = array_merge($this->_head_args, $head);
}
/**
* Parse the OPTIONS request arguments
*
* @access protected
* @return void
*/
protected function _parse_options()
{
// Parse the OPTIONS variables
parse_str(parse_url($this->input->server('REQUEST_URI'), PHP_URL_QUERY), $options);
// Merge both the URI segments and OPTIONS params
$this->_options_args = array_merge($this->_options_args, $options);
}
/**
* Parse the PATCH request arguments
*
* @access protected
* @return void
*/
protected function _parse_patch()
{
// It might be a HTTP body
if ($this->request->format)
{
$this->request->body = $this->input->raw_input_stream;
}
else if ($this->input->method() === 'patch')
{
// If no file type is provided, then there are probably just arguments
$this->_patch_args = $this->input->input_stream();
}
}
/**
* Parse the DELETE request arguments
*
* @access protected
* @return void
*/
protected function _parse_delete()
{
// These should exist if a DELETE request
if ($this->input->method() === 'delete')
{
$this->_delete_args = $this->input->input_stream();
}
}
/**
* Parse the query parameters
*
* @access protected
* @return void
*/
protected function _parse_query()
{
$this->_query_args = $this->input->get();
}
// INPUT FUNCTION --------------------------------------------------------------
/**
* Retrieve a value from a GET request
*
* @access public
* @param NULL $key Key to retrieve from the GET request
* If NULL an array of arguments is returned
* @param NULL $xss_clean Whether to apply XSS filtering
* @return array|string|NULL Value from the GET request; otherwise, NULL
*/
public function get($key = NULL, $xss_clean = NULL)
{
if ($key === NULL)
{
return $this->_get_args;
}
return isset($this->_get_args[$key]) ? $this->_xss_clean($this->_get_args[$key], $xss_clean) : NULL;
}
/**
* Retrieve a value from a OPTIONS request
*
* @access public
* @param NULL $key Key to retrieve from the OPTIONS request.
* If NULL an array of arguments is returned
* @param NULL $xss_clean Whether to apply XSS filtering
* @return array|string|NULL Value from the OPTIONS request; otherwise, NULL
*/
public function options($key = NULL, $xss_clean = NULL)
{
if ($key === NULL)
{
return $this->_options_args;
}
return isset($this->_options_args[$key]) ? $this->_xss_clean($this->_options_args[$key], $xss_clean) : NULL;
}
/**
* Retrieve a value from a HEAD request
*
* @access public
* @param NULL $key Key to retrieve from the HEAD request
* If NULL an array of arguments is returned
* @param NULL $xss_clean Whether to apply XSS filtering
* @return array|string|NULL Value from the HEAD request; otherwise, NULL
*/
public function head($key = NULL, $xss_clean = NULL)
{
if ($key === NULL)
{
return $this->_head_args;
}
return isset($this->_head_args[$key]) ? $this->_xss_clean($this->_head_args[$key], $xss_clean) : NULL;
}
/**
* Retrieve a value from a POST request
*
* @access public
* @param NULL $key Key to retrieve from the POST request
* If NULL an array of arguments is returned
* @param NULL $xss_clean Whether to apply XSS filtering
* @return array|string|NULL Value from the POST request; otherwise, NULL
*/
public function post($key = NULL, $xss_clean = NULL)
{
if ($key === NULL)
{
return $this->_post_args;
}
return isset($this->_post_args[$key]) ? $this->_xss_clean($this->_post_args[$key], $xss_clean) : NULL;
}
/**
* Retrieve a value from a PUT request
*
* @access public
* @param NULL $key Key to retrieve from the PUT request
* If NULL an array of arguments is returned
* @param NULL $xss_clean Whether to apply XSS filtering
* @return array|string|NULL Value from the PUT request; otherwise, NULL
*/
public function put($key = NULL, $xss_clean = NULL)
{
if ($key === NULL)
{
return $this->_put_args;
}
return isset($this->_put_args[$key]) ? $this->_xss_clean($this->_put_args[$key], $xss_clean) : NULL;
}
/**
* Retrieve a value from a DELETE request
*
* @access public
* @param NULL $key Key to retrieve from the DELETE request
* If NULL an array of arguments is returned
* @param NULL $xss_clean Whether to apply XSS filtering
* @return array|string|NULL Value from the DELETE request; otherwise, NULL
*/
public function delete($key = NULL, $xss_clean = NULL)
{
if ($key === NULL)
{
return $this->_delete_args;
}
return isset($this->_delete_args[$key]) ? $this->_xss_clean($this->_delete_args[$key], $xss_clean) : NULL;
}
/**
* Retrieve a value from a PATCH request
*
* @access public
* @param NULL $key Key to retrieve from the PATCH request
* If NULL an array of arguments is returned
* @param NULL $xss_clean Whether to apply XSS filtering
* @return array|string|NULL Value from the PATCH request; otherwise, NULL
*/
public function patch($key = NULL, $xss_clean = NULL)
{
if ($key === NULL)
{
return $this->_patch_args;
}
return isset($this->_patch_args[$key]) ? $this->_xss_clean($this->_patch_args[$key], $xss_clean) : NULL;
}
/**
* Retrieve a value from the query parameters
*
* @access public
* @param NULL $key Key to retrieve from the query parameters
* If NULL an array of arguments is returned
* @param NULL $xss_clean Whether to apply XSS filtering
* @return array|string|NULL Value from the query parameters; otherwise, NULL
*/
public function query($key = NULL, $xss_clean = NULL)
{
if ($key === NULL)
{
return $this->_query_args;
}
return isset($this->_query_args[$key]) ? $this->_xss_clean($this->_query_args[$key], $xss_clean) : NULL;
}
/**
* Sanitizes data so that Cross Site Scripting Hacks can be
* prevented
*
* @access protected
* @param string $value Input data
* @param bool $xss_clean Whether to apply XSS filtering
* @return string
*/
protected function _xss_clean($value, $xss_clean)
{
is_bool($xss_clean) || $xss_clean = $this->_enable_xss;
return $xss_clean === TRUE ? $this->security->xss_clean($value) : $value;
}
/**
* Retrieve the validation errors
*
* @access public
* @return array
*/
public function validation_errors()
{
$string = strip_tags($this->form_validation->error_string());
return explode(PHP_EOL, trim($string, PHP_EOL));
}
// SECURITY FUNCTIONS ---------------------------------------------------------
/**
* Perform LDAP Authentication
*
* @access protected
* @param string $username The username to validate
* @param string $password The password to validate
* @return bool
*/
protected function _perform_ldap_auth($username = '', $password = NULL)
{
if (empty($username))
{
log_message('debug', 'LDAP Auth: failure, empty username');
return FALSE;
}
log_message('debug', 'LDAP Auth: Loading configuration');
$this->config->load('ldap', TRUE);
$ldap = [
'timeout' => $this->config->item('timeout', 'ldap'),
'host' => $this->config->item('server', 'ldap'),
'port' => $this->config->item('port', 'ldap'),
'rdn' => $this->config->item('binduser', 'ldap'),
'pass' => $this->config->item('bindpw', 'ldap'),
'basedn' => $this->config->item('basedn', 'ldap'),
];
log_message('debug', 'LDAP Auth: Connect to ' . (isset($ldaphost) ? $ldaphost : '[ldap not configured]'));
// Connect to the ldap server
$ldapconn = ldap_connect($ldap['host'], $ldap['port']);
if ($ldapconn)
{
log_message('debug', 'Setting timeout to '.$ldap['timeout'].' seconds');
ldap_set_option($ldapconn, LDAP_OPT_NETWORK_TIMEOUT, $ldap['timeout']);
log_message('debug', 'LDAP Auth: Binding to '.$ldap['host'].' with dn '.$ldap['rdn']);
// Binding to the ldap server
$ldapbind = ldap_bind($ldapconn, $ldap['rdn'], $ldap['pass']);
// Verify the binding
if ($ldapbind === FALSE)
{
log_message('error', 'LDAP Auth: bind was unsuccessful');
return FALSE;
}
log_message('debug', 'LDAP Auth: bind successful');
}
// Search for user
if (($res_id = ldap_search($ldapconn, $ldap['basedn'], "uid=$username")) === FALSE)
{
log_message('error', 'LDAP Auth: User '.$username.' not found in search');
return FALSE;
}
if (ldap_count_entries($ldapconn, $res_id) !== 1)
{
log_message('error', 'LDAP Auth: Failure, username '.$username.'found more than once');
return FALSE;
}
if (($entry_id = ldap_first_entry($ldapconn, $res_id)) === FALSE)
{
log_message('error', 'LDAP Auth: Failure, entry of search result could not be fetched');
return FALSE;
}
if (($user_dn = ldap_get_dn($ldapconn, $entry_id)) === FALSE)
{
log_message('error', 'LDAP Auth: Failure, user-dn could not be fetched');
return FALSE;
}
// User found, could not authenticate as user
if (($link_id = ldap_bind($ldapconn, $user_dn, $password)) === FALSE)
{
log_message('error', 'LDAP Auth: Failure, username/password did not match: ' . $user_dn);
return FALSE;
}
log_message('debug', 'LDAP Auth: Success '.$user_dn.' authenticated successfully');
$this->_user_ldap_dn = $user_dn;
ldap_close($ldapconn);
return TRUE;
}
/**
* Perform Library Authentication - Override this function to change the way the library is called
*
* @access protected
* @param string $username The username to validate
* @param string $password The password to validate
* @return bool
*/
protected function _perform_library_auth($username = '', $password = NULL)
{
if (empty($username))
{
log_message('error', 'Library Auth: Failure, empty username');
return FALSE;
}
$auth_library_class = strtolower($this->config->item('auth_library_class'));
$auth_library_function = strtolower($this->config->item('auth_library_function'));
if (empty($auth_library_class))
{
log_message('debug', 'Library Auth: Failure, empty auth_library_class');
return FALSE;
}
if (empty($auth_library_function))
{
log_message('debug', 'Library Auth: Failure, empty auth_library_function');
return FALSE;
}
if (is_callable([$auth_library_class, $auth_library_function]) === FALSE)
{
$this->load->library($auth_library_class);
}
return $this->{$auth_library_class}->$auth_library_function($username, $password);
}
/**
* Check if the user is logged in
*
* @access protected
* @param string $username The user's name
* @param bool|string $password The user's password
* @return bool
*/
protected function _check_login($username = NULL, $password = FALSE)
{
if (empty($username))
{
return FALSE;
}
$auth_source = strtolower($this->config->item('auth_source'));
$rest_auth = strtolower($this->config->item('rest_auth'));
$valid_logins = $this->config->item('rest_valid_logins');
if ( ! $this->config->item('auth_source') && $rest_auth === 'digest')
{
// For digest we do not have a password passed as argument
return md5($username.':'.$this->config->item('rest_realm').':'.(isset($valid_logins[$username]) ? $valid_logins[$username] : ''));
}
if ($password === FALSE)
{
return FALSE;
}
if ($auth_source === 'ldap')
{
log_message('debug', "Performing LDAP authentication for $username");
return $this->_perform_ldap_auth($username, $password);
}
if ($auth_source === 'library')
{
log_message('debug', "Performing Library authentication for $username");
return $this->_perform_library_auth($username, $password);
}
if (array_key_exists($username, $valid_logins) === FALSE)
{
return FALSE;
}
if ($valid_logins[$username] !== $password)
{
return FALSE;
}
return TRUE;
}
/**
* Check to see if the user is logged in with a PHP session key
*
* @access protected
* @return void
*/
protected function _check_php_session()
{
// If whitelist is enabled it has the first chance to kick them out
if ($this->config->item('rest_ip_whitelist_enabled'))
{
$this->_check_whitelist_auth();
}
// Get the auth_source config item
$key = $this->config->item('auth_source');
// If false, then the user isn't logged in
if ( ! $this->session->userdata($key))
{
// Display an error response
$this->response([
$this->config->item('rest_status_field_name') => FALSE,
$this->config->item('rest_message_field_name') => $this->lang->line('text_rest_unauthorized')
], self::HTTP_UNAUTHORIZED);
}
}
/**
* Prepares for basic authentication
*
* @access protected
* @return void
*/
protected function _prepare_basic_auth()
{
// If whitelist is enabled it has the first chance to kick them out
if ($this->config->item('rest_ip_whitelist_enabled'))
{
$this->_check_whitelist_auth();
}
// Returns NULL if the SERVER variables PHP_AUTH_USER and HTTP_AUTHENTICATION don't exist
$username = $this->input->server('PHP_AUTH_USER');
$http_auth = $this->input->server('HTTP_AUTHENTICATION') ?: $this->input->server('HTTP_AUTHORIZATION');
$password = NULL;
if ($username !== NULL)
{
$password = $this->input->server('PHP_AUTH_PW');
}
elseif ($http_auth !== NULL)
{
// If the authentication header is set as basic, then extract the username and password from
// HTTP_AUTHORIZATION e.g. my_username:my_password. This is passed in the .htaccess file
if (strpos(strtolower($http_auth), 'basic') === 0)
{
// Search online for HTTP_AUTHORIZATION workaround to explain what this is doing
list($username, $password) = explode(':', base64_decode(substr($this->input->server('HTTP_AUTHORIZATION'), 6)));
}
}
// Check if the user is logged into the system
if ($this->_check_login($username, $password) === FALSE)
{
$this->_force_login();
}
}
/**
* Prepares for digest authentication
*
* @access protected
* @return void
*/
protected function _prepare_digest_auth()
{
// If whitelist is enabled it has the first chance to kick them out
if ($this->config->item('rest_ip_whitelist_enabled'))
{
$this->_check_whitelist_auth();
}
// We need to test which server authentication variable to use,
// because the PHP ISAPI module in IIS acts different from CGI
$digest_string = $this->input->server('PHP_AUTH_DIGEST');
if ($digest_string === NULL)
{
$digest_string = $this->input->server('HTTP_AUTHORIZATION');
}
$unique_id = uniqid();
// The $_SESSION['error_prompted'] variable is used to ask the password
// again if none given or if the user enters wrong auth information
if (empty($digest_string))
{
$this->_force_login($unique_id);
}
// We need to retrieve authentication data from the $digest_string variable
$matches = [];
preg_match_all('@(username|nonce|uri|nc|cnonce|qop|response)=[\'"]?([^\'",]+)@', $digest_string, $matches);
$digest = (empty($matches[1]) || empty($matches[2])) ? [] : array_combine($matches[1], $matches[2]);
// For digest authentication the library function should return already stored md5(username:restrealm:password) for that username see rest.php::auth_library_function config
if (isset($digest['username']) === FALSE || $this->_check_login($digest['username'], TRUE) === FALSE)
{
$this->_force_login($unique_id);
}
$md5 = md5(strtoupper($this->request->method).':'.$digest['uri']);
$valid_response = md5($username.':'.$digest['nonce'].':'.$digest['nc'].':'.$digest['cnonce'].':'.$digest['qop'].':'.$md5);
// Check if the string don't compare (case-insensitive)
if (strcasecmp($digest['response'], $valid_response) !== 0)
{
// Display an error response
$this->response([
$this->config->item('rest_status_field_name') => FALSE,
$this->config->item('rest_message_field_name') => $this->lang->line('text_rest_invalid_credentials')
], self::HTTP_UNAUTHORIZED);
}
}
/**
* Checks if the client's ip is in the 'rest_ip_blacklist' config and generates a 401 response
*
* @access protected
* @return void
*/
protected function _check_blacklist_auth()
{
// Match an ip address in a blacklist e.g. 127.0.0.0, 0.0.0.0
$pattern = sprintf('/(?:,\s*|^)\Q%s\E(?=,\s*|$)/m', $this->input->ip_address());
// Returns 1, 0 or FALSE (on error only). Therefore implicitly convert 1 to TRUE
if (preg_match($pattern, $this->config->item('rest_ip_blacklist')))
{
// Display an error response
$this->response([
$this->config->item('rest_status_field_name') => FALSE,
$this->config->item('rest_message_field_name') => $this->lang->line('text_rest_ip_denied')
], self::HTTP_UNAUTHORIZED);
}
}
/**
* Check if the client's ip is in the 'rest_ip_whitelist' config and generates a 401 response
*
* @access protected
* @return void
*/
protected function _check_whitelist_auth()
{
$whitelist = explode(',', $this->config->item('rest_ip_whitelist'));
array_push($whitelist, '127.0.0.1', '0.0.0.0');
foreach ($whitelist as &$ip)
{
// As $ip is a reference, trim leading and trailing whitespace, then store the new value
// using the reference
$ip = trim($ip);
}
if (in_array($this->input->ip_address(), $whitelist) === FALSE)
{
$this->response([
$this->config->item('rest_status_field_name') => FALSE,
$this->config->item('rest_message_field_name') => $this->lang->line('text_rest_ip_unauthorized')
], self::HTTP_UNAUTHORIZED);
}
}
/**
* Force logging in by setting the WWW-Authenticate header
*
* @access protected
* @param string $nonce A server-specified data string which should be uniquely generated
* each time
* @return void
*/
protected function _force_login($nonce = '')
{
$rest_auth = strtolower($this->config->item('rest_auth'));
$rest_realm = $this->config->item('rest_realm');
if ($rest_auth === 'basic')
{
// See http://tools.ietf.org/html/rfc2617#page-5
header('WWW-Authenticate: Basic realm="'.$rest_realm.'"');
}
elseif ($rest_auth === 'digest')
{
// See http://tools.ietf.org/html/rfc2617#page-18
header(
'WWW-Authenticate: Digest realm="'.$rest_realm
.'", qop="auth", nonce="'.$nonce
.'", opaque="' . md5($rest_realm).'"');
}
if ($this->config->item('strict_api_and_auth') === true) {
$this->is_valid_request = false;
}
// Display an error response
$this->response([
$this->config->item('rest_status_field_name') => FALSE,
$this->config->item('rest_message_field_name') => $this->lang->line('text_rest_unauthorized')
], self::HTTP_UNAUTHORIZED);
}
/**
* Updates the log table with the total access time
*
* @access protected
* @author Chris Kacerguis
* @return bool TRUE log table updated; otherwise, FALSE
*/
protected function _log_access_time()
{
if($this->_insert_id == ''){
return false;
}
$payload['rtime'] = $this->_end_rtime - $this->_start_rtime;
return $this->rest->db->update(
$this->config->item('rest_logs_table'), $payload, [
'id' => $this->_insert_id
]);
}
/**
* Updates the log table with HTTP response code
*
* @access protected
* @author Justin Chen
* @param $http_code int HTTP status code
* @return bool TRUE log table updated; otherwise, FALSE
*/
protected function _log_response_code($http_code)
{
if($this->_insert_id == ''){
return false;
}
$payload['response_code'] = $http_code;
return $this->rest->db->update(
$this->config->item('rest_logs_table'), $payload, [
'id' => $this->_insert_id
]);
}
/**
* Check to see if the API key has access to the controller and methods
*
* @access protected
* @return bool TRUE the API key has access; otherwise, FALSE
*/
protected function _check_access()
{
// If we don't want to check access, just return TRUE
if ($this->config->item('rest_enable_access') === FALSE)
{
return TRUE;
}
// Fetch controller based on path and controller name
$controller = implode(
'/', [
$this->router->directory,
$this->router->class
]);
// Remove any double slashes for safety
$controller = str_replace('//', '/', $controller);
//check if the key has all_access
$accessRow = $this->rest->db
->where('key', $this->rest->key)
->where('controller', $controller)
->get($this->config->item('rest_access_table'))->row_array();
if (!empty($accessRow) && !empty($accessRow['all_access']))
{
return TRUE;
}
return false;
}
/**
* Checks allowed domains, and adds appropriate headers for HTTP access control (CORS)
*
* @access protected
* @return void
*/
protected function _check_cors()
{
// Convert the config items into strings
$allowed_headers = implode(', ', $this->config->item('allowed_cors_headers'));
$allowed_methods = implode(', ', $this->config->item('allowed_cors_methods'));
// If we want to allow any domain to access the API
if ($this->config->item('allow_any_cors_domain') === TRUE)
{
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Headers: '.$allowed_headers);
header('Access-Control-Allow-Methods: '.$allowed_methods);
}
else
{
// We're going to allow only certain domains access
// Store the HTTP Origin header
$origin = $this->input->server('HTTP_ORIGIN');
if ($origin === NULL)
{
$origin = '';
}
// If the origin domain is in the allowed_cors_origins list, then add the Access Control headers
if (in_array($origin, $this->config->item('allowed_cors_origins')))
{
header('Access-Control-Allow-Origin: '.$origin);
header('Access-Control-Allow-Headers: '.$allowed_headers);
header('Access-Control-Allow-Methods: '.$allowed_methods);
}
}
// If there are headers that should be forced in the CORS check, add them now
if (is_array($this->config->item('forced_cors_headers')))
{
foreach ($this->config->item('forced_cors_headers') as $header => $value)
{
header($header . ': ' . $value);
}
}
// If the request HTTP method is 'OPTIONS', kill the response and send it to the client
if ($this->input->method() === 'options')
{
// Load DB if needed for logging
if (!isset($this->rest->db) && $this->config->item('rest_enable_logging'))
{
$this->rest->db = $this->load->database($this->config->item('rest_database_group'), TRUE);
}
exit;
}
}
}
Now create Format.php file and past followig code
<?php
namespace Restserver\Libraries;
use Exception;
defined('BASEPATH') OR exit('No direct script access allowed');
/**
* Format class
* Help convert between various formats such as XML, JSON, CSV, etc.
*
* @author Phil Sturgeon, Chris Kacerguis, @softwarespot
* @license http://www.dbad-license.org/
*/
class Format {
/**
* Array output format
*/
const ARRAY_FORMAT = 'array';
/**
* Comma Separated Value (CSV) output format
*/
const CSV_FORMAT = 'csv';
/**
* Json output format
*/
const JSON_FORMAT = 'json';
/**
* HTML output format
*/
const HTML_FORMAT = 'html';
/**
* PHP output format
*/
const PHP_FORMAT = 'php';
/**
* Serialized output format
*/
const SERIALIZED_FORMAT = 'serialized';
/**
* XML output format
*/
const XML_FORMAT = 'xml';
/**
* Default format of this class
*/
const DEFAULT_FORMAT = self::JSON_FORMAT; // Couldn't be DEFAULT, as this is a keyword
/**
* CodeIgniter instance
*
* @var object
*/
private $_CI;
/**
* Data to parse
*
* @var mixed
*/
protected $_data = [];
/**
* Type to convert from
*
* @var string
*/
protected $_from_type = NULL;
/**
* DO NOT CALL THIS DIRECTLY, USE factory()
*
* @param NULL $data
* @param NULL $from_type
* @throws Exception
*/
public function __construct($data = NULL, $from_type = NULL)
{
// Get the CodeIgniter reference
$this->_CI = &get_instance();
// Load the inflector helper
$this->_CI->load->helper('inflector');
// If the provided data is already formatted we should probably convert it to an array
if ($from_type !== NULL)
{
if (method_exists($this, '_from_'.$from_type))
{
$data = call_user_func([$this, '_from_'.$from_type], $data);
}
else
{
throw new Exception('Format class does not support conversion from "'.$from_type.'".');
}
}
// Set the member variable to the data passed
$this->_data = $data;
}
/**
* Create an instance of the format class
* e.g: echo $this->format->factory(['foo' => 'bar'])->to_csv();
*
* @param mixed $data Data to convert/parse
* @param string $from_type Type to convert from e.g. json, csv, html
*
* @return object Instance of the format class
*/
public static function factory($data, $from_type = NULL)
{
// $class = __CLASS__;
// return new $class();
return new static($data, $from_type);
}
// FORMATTING OUTPUT ---------------------------------------------------------
/**
* Format data as an array
*
* @param mixed|NULL $data Optional data to pass, so as to override the data passed
* to the constructor
* @return array Data parsed as an array; otherwise, an empty array
*/
public function to_array($data = NULL)
{
// If no data is passed as a parameter, then use the data passed
// via the constructor
if ($data === NULL && func_num_args() === 0)
{
$data = $this->_data;
}
// Cast as an array if not already
if (is_array($data) === FALSE)
{
$data = (array) $data;
}
$array = [];
foreach ((array) $data as $key => $value)
{
if (is_object($value) === TRUE || is_array($value) === TRUE)
{
$array[$key] = $this->to_array($value);
}
else
{
$array[$key] = $value;
}
}
return $array;
}
/**
* Format data as XML
*
* @param mixed|NULL $data Optional data to pass, so as to override the data passed
* to the constructor
* @param NULL $structure
* @param string $basenode
* @return mixed
*/
public function to_xml($data = NULL, $structure = NULL, $basenode = 'xml')
{
if ($data === NULL && func_num_args() === 0)
{
$data = $this->_data;
}
if ($structure === NULL)
{
$structure = simplexml_load_string("<?xml version='1.0' encoding='utf-8'?><$basenode />");
}
// Force it to be something useful
if (is_array($data) === FALSE && is_object($data) === FALSE)
{
$data = (array) $data;
}
foreach ($data as $key => $value)
{
//change false/true to 0/1
if (is_bool($value))
{
$value = (int) $value;
}
// no numeric keys in our xml please!
if (is_numeric($key))
{
// make string key...
$key = (singular($basenode) != $basenode) ? singular($basenode) : 'item';
}
// replace anything not alpha numeric
$key = preg_replace('/[^a-z_\-0-9]/i', '', $key);
if ($key === '_attributes' && (is_array($value) || is_object($value)))
{
$attributes = $value;
if (is_object($attributes))
{
$attributes = get_object_vars($attributes);
}
foreach ($attributes as $attribute_name => $attribute_value)
{
$structure->addAttribute($attribute_name, $attribute_value);
}
}
// if there is another array found recursively call this function
elseif (is_array($value) || is_object($value))
{
$node = $structure->addChild($key);
// recursive call.
$this->to_xml($value, $node, $key);
}
else
{
// add single node.
$value = htmlspecialchars(html_entity_decode($value, ENT_QUOTES, 'UTF-8'), ENT_QUOTES, 'UTF-8');
$structure->addChild($key, $value);
}
}
return $structure->asXML();
}
/**
* Format data as HTML
*
* @param mixed|NULL $data Optional data to pass, so as to override the data passed
* to the constructor
* @return mixed
*/
public function to_html($data = NULL)
{
// If no data is passed as a parameter, then use the data passed
// via the constructor
if ($data === NULL && func_num_args() === 0)
{
$data = $this->_data;
}
// Cast as an array if not already
if (is_array($data) === FALSE)
{
$data = (array) $data;
}
// Check if it's a multi-dimensional array
if (isset($data[0]) && count($data) !== count($data, COUNT_RECURSIVE))
{
// Multi-dimensional array
$headings = array_keys($data[0]);
}
else
{
// Single array
$headings = array_keys($data);
$data = [$data];
}
// Load the table library
$this->_CI->load->library('table');
$this->_CI->table->set_heading($headings);
foreach ($data as $row)
{
// Suppressing the "array to string conversion" notice
// Keep the "evil" @ here
$row = @array_map('strval', $row);
$this->_CI->table->add_row($row);
}
return $this->_CI->table->generate();
}
/**
* @link http://www.metashock.de/2014/02/create-csv-file-in-memory-php/
* @param mixed|NULL $data Optional data to pass, so as to override the data passed
* to the constructor
* @param string $delimiter The optional delimiter parameter sets the field
* delimiter (one character only). NULL will use the default value (,)
* @param string $enclosure The optional enclosure parameter sets the field
* enclosure (one character only). NULL will use the default value (")
* @return string A csv string
*/
public function to_csv($data = NULL, $delimiter = ',', $enclosure = '"')
{
// Use a threshold of 1 MB (1024 * 1024)
$handle = fopen('php://temp/maxmemory:1048576', 'w');
if ($handle === FALSE)
{
return NULL;
}
// If no data is passed as a parameter, then use the data passed
// via the constructor
if ($data === NULL && func_num_args() === 0)
{
$data = $this->_data;
}
// If NULL, then set as the default delimiter
if ($delimiter === NULL)
{
$delimiter = ',';
}
// If NULL, then set as the default enclosure
if ($enclosure === NULL)
{
$enclosure = '"';
}
// Cast as an array if not already
if (is_array($data) === FALSE)
{
$data = (array) $data;
}
// Check if it's a multi-dimensional array
if (isset($data[0]) && count($data) !== count($data, COUNT_RECURSIVE))
{
// Multi-dimensional array
$headings = array_keys($data[0]);
}
else
{
// Single array
$headings = array_keys($data);
$data = [$data];
}
// Apply the headings
fputcsv($handle, $headings, $delimiter, $enclosure);
foreach ($data as $record)
{
// If the record is not an array, then break. This is because the 2nd param of
// fputcsv() should be an array
if (is_array($record) === FALSE)
{
break;
}
// Suppressing the "array to string conversion" notice.
// Keep the "evil" @ here.
$record = @ array_map('strval', $record);
// Returns the length of the string written or FALSE
fputcsv($handle, $record, $delimiter, $enclosure);
}
// Reset the file pointer
rewind($handle);
// Retrieve the csv contents
$csv = stream_get_contents($handle);
// Close the handle
fclose($handle);
// Convert UTF-8 encoding to UTF-16LE which is supported by MS Excel
$csv = mb_convert_encoding($csv, 'UTF-16LE', 'UTF-8');
return $csv;
}
/**
* Encode data as json
*
* @param mixed|NULL $data Optional data to pass, so as to override the data passed
* to the constructor
* @return string Json representation of a value
*/
public function to_json($data = NULL)
{
// If no data is passed as a parameter, then use the data passed
// via the constructor
if ($data === NULL && func_num_args() === 0)
{
$data = $this->_data;
}
// Get the callback parameter (if set)
$callback = $this->_CI->input->get('callback');
if (empty($callback) === TRUE)
{
return json_encode($data, JSON_UNESCAPED_UNICODE);
}
// We only honour a jsonp callback which are valid javascript identifiers
elseif (preg_match('/^[a-z_\$][a-z0-9\$_]*(\.[a-z_\$][a-z0-9\$_]*)*$/i', $callback))
{
// Return the data as encoded json with a callback
return $callback.'('.json_encode($data, JSON_UNESCAPED_UNICODE).');';
}
// An invalid jsonp callback function provided.
// Though I don't believe this should be hardcoded here
$data['warning'] = 'INVALID JSONP CALLBACK: '.$callback;
return json_encode($data, JSON_UNESCAPED_UNICODE);
}
/**
* Encode data as a serialized array
*
* @param mixed|NULL $data Optional data to pass, so as to override the data passed
* to the constructor
* @return string Serialized data
*/
public function to_serialized($data = NULL)
{
// If no data is passed as a parameter, then use the data passed
// via the constructor
if ($data === NULL && func_num_args() === 0)
{
$data = $this->_data;
}
return serialize($data);
}
/**
* Format data using a PHP structure
*
* @param mixed|NULL $data Optional data to pass, so as to override the data passed
* to the constructor
* @return mixed String representation of a variable
*/
public function to_php($data = NULL)
{
// If no data is passed as a parameter, then use the data passed
// via the constructor
if ($data === NULL && func_num_args() === 0)
{
$data = $this->_data;
}
return var_export($data, TRUE);
}
// INTERNAL FUNCTIONS
/**
* @param string $data XML string
* @return array XML element object; otherwise, empty array
*/
protected function _from_xml($data)
{
return $data ? (array) simplexml_load_string($data, 'SimpleXMLElement', LIBXML_NOCDATA) : [];
}
/**
* @param string $data CSV string
* @param string $delimiter The optional delimiter parameter sets the field
* delimiter (one character only). NULL will use the default value (,)
* @param string $enclosure The optional enclosure parameter sets the field
* enclosure (one character only). NULL will use the default value (")
* @return array A multi-dimensional array with the outer array being the number of rows
* and the inner arrays the individual fields
*/
protected function _from_csv($data, $delimiter = ',', $enclosure = '"')
{
// If NULL, then set as the default delimiter
if ($delimiter === NULL)
{
$delimiter = ',';
}
// If NULL, then set as the default enclosure
if ($enclosure === NULL)
{
$enclosure = '"';
}
return str_getcsv($data, $delimiter, $enclosure);
}
/**
* @param string $data Encoded json string
* @return mixed Decoded json string with leading and trailing whitespace removed
*/
protected function _from_json($data)
{
return json_decode(trim($data));
}
/**
* @param string $data Data to unserialize
* @return mixed Unserialized data
*/
protected function _from_serialize($data)
{
return unserialize(trim($data));
}
/**
* @param string $data Data to trim leading and trailing whitespace
* @return string Data with leading and trailing whitespace removed
*/
protected function _from_php($data)
{
return trim($data);
}
}
After that your folder structure will look like
Now open config directory inside application directory and create file rest.php pase the following code
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
/*
|--------------------------------------------------------------------------
| HTTP protocol
|--------------------------------------------------------------------------
|
| Set to force the use of HTTPS for REST API calls
|
*/
$config['force_https'] = FALSE;
/*
|--------------------------------------------------------------------------
| REST Output Format
|--------------------------------------------------------------------------
|
| The default format of the response
|
| 'array': Array data structure
| 'csv': Comma separated file
| 'json': Uses json_encode(). Note: If a GET query string
| called 'callback' is passed, then jsonp will be returned
| 'html' HTML using the table library in CodeIgniter
| 'php': Uses var_export()
| 'serialized': Uses serialize()
| 'xml': Uses simplexml_load_string()
|
*/
$config['rest_default_format'] = 'json';
/*
|--------------------------------------------------------------------------
| REST Supported Output Formats
|--------------------------------------------------------------------------
|
| The following setting contains a list of the supported/allowed formats.
| You may remove those formats that you don't want to use.
| If the default format $config['rest_default_format'] is missing within
| $config['rest_supported_formats'], it will be added silently during
| REST_Controller initialization.
|
*/
$config['rest_supported_formats'] = [
'json',
'array',
'csv',
'html',
'jsonp',
'php',
'serialized',
'xml',
];
/*
|--------------------------------------------------------------------------
| REST Status Field Name
|--------------------------------------------------------------------------
|
| The field name for the status inside the response
|
*/
$config['rest_status_field_name'] = 'status';
/*
|--------------------------------------------------------------------------
| REST Message Field Name
|--------------------------------------------------------------------------
|
| The field name for the message inside the response
|
*/
$config['rest_message_field_name'] = 'error';
/*
|--------------------------------------------------------------------------
| Enable Emulate Request
|--------------------------------------------------------------------------
|
| Should we enable emulation of the request (e.g. used in Mootools request)
|
*/
$config['enable_emulate_request'] = TRUE;
/*
|--------------------------------------------------------------------------
| REST Realm
|--------------------------------------------------------------------------
|
| Name of the password protected REST API displayed on login dialogs
|
| e.g: My Secret REST API
|
*/
$config['rest_realm'] = 'REST API';
/*
|--------------------------------------------------------------------------
| REST Login
|--------------------------------------------------------------------------
|
| Set to specify the REST API requires to be logged in
|
| FALSE No login required
| 'basic' Unsecured login
| 'digest' More secured login
| 'session' Check for a PHP session variable. See 'auth_source' to set the
| authorization key
|
*/
$config['rest_auth'] = FALSE;
/*
|--------------------------------------------------------------------------
| REST Login Source
|--------------------------------------------------------------------------
|
| Is login required and if so, the user store to use
|
| '' Use config based users or wildcard testing
| 'ldap' Use LDAP authentication
| 'library' Use a authentication library
|
| Note: If 'rest_auth' is set to 'session' then change 'auth_source' to the name of the session variable
|
*/
$config['auth_source'] = 'ldap';
/*
|--------------------------------------------------------------------------
| Allow Authentication and API Keys
|--------------------------------------------------------------------------
|
| Where you wish to have Basic, Digest or Session login, but also want to use API Keys (for limiting
| requests etc), set to TRUE;
|
*/
$config['allow_auth_and_keys'] = TRUE;
$config['strict_api_and_auth'] = TRUE; // force the use of both api and auth before a valid api request is made
/*
|--------------------------------------------------------------------------
| REST Login Class and Function
|--------------------------------------------------------------------------
|
| If library authentication is used define the class and function name
|
| The function should accept two parameters: class->function($username, $password)
| In other cases override the function _perform_library_auth in your controller
|
| For digest authentication the library function should return already a stored
| md5(username:restrealm:password) for that username
|
| e.g: md5('admin:REST API:1234') = '1e957ebc35631ab22d5bd6526bd14ea2'
|
*/
$config['auth_library_class'] = '';
$config['auth_library_function'] = '';
/*
|--------------------------------------------------------------------------
| Override auth types for specific class/method
|--------------------------------------------------------------------------
|
| Set specific authentication types for methods within a class (controller)
|
| Set as many config entries as needed. Any methods not set will use the default 'rest_auth' config value.
|
| e.g:
|
| $config['auth_override_class_method']['deals']['view'] = 'none';
| $config['auth_override_class_method']['deals']['insert'] = 'digest';
| $config['auth_override_class_method']['accounts']['user'] = 'basic';
| $config['auth_override_class_method']['dashboard']['*'] = 'none|digest|basic';
|
| 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)
| Acceptable values are; 'none', 'digest' and 'basic'.
|
*/
// $config['auth_override_class_method']['deals']['view'] = 'none';
// $config['auth_override_class_method']['deals']['insert'] = 'digest';
// $config['auth_override_class_method']['accounts']['user'] = 'basic';
// $config['auth_override_class_method']['dashboard']['*'] = 'basic';
// ---Uncomment list line for the wildard unit test
// $config['auth_override_class_method']['wildcard_test_cases']['*'] = 'basic';
/*
|--------------------------------------------------------------------------
| Override auth types for specific 'class/method/HTTP method'
|--------------------------------------------------------------------------
|
| example:
|
| $config['auth_override_class_method_http']['deals']['view']['get'] = 'none';
| $config['auth_override_class_method_http']['deals']['insert']['post'] = 'none';
| $config['auth_override_class_method_http']['deals']['*']['options'] = 'none';
*/
// ---Uncomment list line for the wildard unit test
// $config['auth_override_class_method_http']['wildcard_test_cases']['*']['options'] = 'basic';
/*
|--------------------------------------------------------------------------
| REST Login Usernames
|--------------------------------------------------------------------------
|
| Array of usernames and passwords for login, if ldap is configured this is ignored
|
*/
$config['rest_valid_logins'] = ['admin' => '1234'];
/*
|--------------------------------------------------------------------------
| Global IP White-listing
|--------------------------------------------------------------------------
|
| Limit connections to your REST server to White-listed IP addresses
|
| Usage:
| 1. Set to TRUE and select an auth option for extreme security (client's IP
| address must be in white-list and they must also log in)
| 2. Set to TRUE with auth set to FALSE to allow White-listed IPs access with no login
| 3. Set to FALSE but set 'auth_override_class_method' to 'white-list' to
| restrict certain methods to IPs in your white-list
|
*/
$config['rest_ip_whitelist_enabled'] = FALSE;
/*
|--------------------------------------------------------------------------
| REST Handle Exceptions
|--------------------------------------------------------------------------
|
| Handle exceptions caused by the controller
|
*/
$config['rest_handle_exceptions'] = TRUE;
/*
|--------------------------------------------------------------------------
| REST IP White-list
|--------------------------------------------------------------------------
|
| Limit connections to your REST server with a comma separated
| list of IP addresses
|
| e.g: '123.456.789.0, 987.654.32.1'
|
| 127.0.0.1 and 0.0.0.0 are allowed by default
|
*/
$config['rest_ip_whitelist'] = '';
/*
|--------------------------------------------------------------------------
| Global IP Blacklisting
|--------------------------------------------------------------------------
|
| Prevent connections to the REST server from blacklisted IP addresses
|
| Usage:
| 1. Set to TRUE and add any IP address to 'rest_ip_blacklist'
|
*/
$config['rest_ip_blacklist_enabled'] = FALSE;
/*
|--------------------------------------------------------------------------
| REST IP Blacklist
|--------------------------------------------------------------------------
|
| Prevent connections from the following IP addresses
|
| e.g: '123.456.789.0, 987.654.32.1'
|
*/
$config['rest_ip_blacklist'] = '';
/*
|--------------------------------------------------------------------------
| REST Database Group
|--------------------------------------------------------------------------
|
| Connect to a database group for keys, logging, etc. It will only connect
| if you have any of these features enabled
|
*/
$config['rest_database_group'] = 'default';
/*
|--------------------------------------------------------------------------
| REST API Keys Table Name
|--------------------------------------------------------------------------
|
| The table name in your database that stores API keys
|
*/
$config['rest_keys_table'] = 'keys';
/*
|--------------------------------------------------------------------------
| REST Enable Keys
|--------------------------------------------------------------------------
|
| When set to TRUE, the REST API will look for a column name called 'key'.
| If no key is provided, the request will result in an error. To override the
| column name see 'rest_key_column'
|
| Default table schema:
| CREATE TABLE `keys` (
| `id` INT(11) NOT NULL AUTO_INCREMENT,
| `user_id` INT(11) NOT NULL,
| `key` VARCHAR(40) NOT NULL,
| `level` INT(2) NOT NULL,
| `ignore_limits` TINYINT(1) NOT NULL DEFAULT '0',
| `is_private_key` TINYINT(1) NOT NULL DEFAULT '0',
| `ip_addresses` TEXT NULL DEFAULT NULL,
| `date_created` INT(11) NOT NULL,
| PRIMARY KEY (`id`)
| ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
*/
$config['rest_enable_keys'] = FALSE;
/*
|--------------------------------------------------------------------------
| REST Table Key Column Name
|--------------------------------------------------------------------------
|
| If not using the default table schema in 'rest_enable_keys', specify the
| column name to match e.g. my_key
|
*/
$config['rest_key_column'] = 'key';
/*
|--------------------------------------------------------------------------
| REST API Limits method
|--------------------------------------------------------------------------
|
| Specify the method used to limit the API calls
|
| Available methods are :
| $config['rest_limits_method'] = 'IP_ADDRESS'; // Put a limit per ip address
| $config['rest_limits_method'] = 'API_KEY'; // Put a limit per api key
| $config['rest_limits_method'] = 'METHOD_NAME'; // Put a limit on method calls
| $config['rest_limits_method'] = 'ROUTED_URL'; // Put a limit on the routed URL
|
*/
$config['rest_limits_method'] = 'ROUTED_URL';
/*
|--------------------------------------------------------------------------
| REST Key Length
|--------------------------------------------------------------------------
|
| Length of the created keys. Check your default database schema on the
| maximum length allowed
|
| Note: The maximum length is 40
|
*/
$config['rest_key_length'] = 40;
/*
|--------------------------------------------------------------------------
| REST API Key Variable
|--------------------------------------------------------------------------
|
| Custom header to specify the API key
| Note: Custom headers with the X- prefix are deprecated as of
| 2012/06/12. See RFC 6648 specification for more details
|
*/
$config['rest_key_name'] = 'X-API-KEY';
/*
|--------------------------------------------------------------------------
| REST Enable Logging
|--------------------------------------------------------------------------
|
| When set to TRUE, the REST API will log actions based on the column names 'key', 'date',
| 'time' and 'ip_address'. This is a general rule that can be overridden in the
| $this->method array for each controller
|
| Default table schema:
| CREATE TABLE `logs` (
| `id` INT(11) NOT NULL AUTO_INCREMENT,
| `uri` VARCHAR(255) NOT NULL,
| `method` VARCHAR(6) NOT NULL,
| `params` TEXT DEFAULT NULL,
| `api_key` VARCHAR(40) NOT NULL,
| `ip_address` VARCHAR(45) NOT NULL,
| `time` INT(11) NOT NULL,
| `rtime` FLOAT DEFAULT NULL,
| `authorized` VARCHAR(1) NOT NULL,
| `response_code` smallint(3) DEFAULT '0',
| PRIMARY KEY (`id`)
| ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
*/
$config['rest_enable_logging'] = FALSE;
/*
|--------------------------------------------------------------------------
| REST API Logs Table Name
|--------------------------------------------------------------------------
|
| If not using the default table schema in 'rest_enable_logging', specify the
| table name to match e.g. my_logs
|
*/
$config['rest_logs_table'] = 'logs';
/*
|--------------------------------------------------------------------------
| REST Method Access Control
|--------------------------------------------------------------------------
| When set to TRUE, the REST API will check the access table to see if
| the API key can access that controller. 'rest_enable_keys' must be enabled
| to use this
|
| Default table schema:
| CREATE TABLE `access` (
| `id` INT(11) unsigned NOT NULL AUTO_INCREMENT,
| `key` VARCHAR(40) NOT NULL DEFAULT '',
| `all_access` TINYINT(1) NOT NULL DEFAULT '0',
| `controller` VARCHAR(50) NOT NULL DEFAULT '',
| `date_created` DATETIME DEFAULT NULL,
| `date_modified` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
| PRIMARY KEY (`id`)
| ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
*/
$config['rest_enable_access'] = FALSE;
/*
|--------------------------------------------------------------------------
| REST API Access Table Name
|--------------------------------------------------------------------------
|
| If not using the default table schema in 'rest_enable_access', specify the
| table name to match e.g. my_access
|
*/
$config['rest_access_table'] = 'access';
/*
|--------------------------------------------------------------------------
| REST API Param Log Format
|--------------------------------------------------------------------------
|
| When set to TRUE, the REST API log parameters will be stored in the database as JSON
| Set to FALSE to log as serialized PHP
|
*/
$config['rest_logs_json_params'] = FALSE;
/*
|--------------------------------------------------------------------------
| REST Enable Limits
|--------------------------------------------------------------------------
|
| When set to TRUE, the REST API will count the number of uses of each method
| by an API key each hour. This is a general rule that can be overridden in the
| $this->method array in each controller
|
| Default table schema:
| CREATE TABLE `limits` (
| `id` INT(11) NOT NULL AUTO_INCREMENT,
| `uri` VARCHAR(255) NOT NULL,
| `count` INT(10) NOT NULL,
| `hour_started` INT(11) NOT NULL,
| `api_key` VARCHAR(40) NOT NULL,
| PRIMARY KEY (`id`)
| ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
| To specify the limits within the controller's __construct() method, add per-method
| limits with:
|
| $this->method['METHOD_NAME']['limit'] = [NUM_REQUESTS_PER_HOUR];
|
| See application/controllers/api/example.php for examples
*/
$config['rest_enable_limits'] = FALSE;
/*
|--------------------------------------------------------------------------
| REST API Limits Table Name
|--------------------------------------------------------------------------
|
| If not using the default table schema in 'rest_enable_limits', specify the
| table name to match e.g. my_limits
|
*/
$config['rest_limits_table'] = 'limits';
/*
|--------------------------------------------------------------------------
| REST Ignore HTTP Accept
|--------------------------------------------------------------------------
|
| Set to TRUE to ignore the HTTP Accept and speed up each request a little.
| Only do this if you are using the $this->rest_format or /format/xml in URLs
|
*/
$config['rest_ignore_http_accept'] = FALSE;
/*
|--------------------------------------------------------------------------
| REST AJAX Only
|--------------------------------------------------------------------------
|
| Set to TRUE to allow AJAX requests only. Set to FALSE to accept HTTP requests
|
| Note: If set to TRUE and the request is not AJAX, a 505 response with the
| error message 'Only AJAX requests are accepted.' will be returned.
|
| Hint: This is good for production environments
|
*/
$config['rest_ajax_only'] = FALSE;
/*
|--------------------------------------------------------------------------
| REST Language File
|--------------------------------------------------------------------------
|
| Language file to load from the language directory
|
*/
$config['rest_language'] = 'english';
/*
|--------------------------------------------------------------------------
| CORS Check
|--------------------------------------------------------------------------
|
| Set to TRUE to enable Cross-Origin Resource Sharing (CORS). Useful if you
| are hosting your API on a different domain from the application that
| will access it through a browser
|
*/
$config['check_cors'] = FALSE;
/*
|--------------------------------------------------------------------------
| CORS Allowable Headers
|--------------------------------------------------------------------------
|
| If using CORS checks, set the allowable headers here
|
*/
$config['allowed_cors_headers'] = [
'Origin',
'X-Requested-With',
'Content-Type',
'Accept',
'Access-Control-Request-Method'
];
/*
|--------------------------------------------------------------------------
| CORS Allowable Methods
|--------------------------------------------------------------------------
|
| If using CORS checks, you can set the methods you want to be allowed
|
*/
$config['allowed_cors_methods'] = [
'GET',
'POST',
'OPTIONS',
'PUT',
'PATCH',
'DELETE'
];
/*
|--------------------------------------------------------------------------
| CORS Allow Any Domain
|--------------------------------------------------------------------------
|
| Set to TRUE to enable Cross-Origin Resource Sharing (CORS) from any
| source domain
|
*/
$config['allow_any_cors_domain'] = FALSE;
/*
|--------------------------------------------------------------------------
| CORS Allowable Domains
|--------------------------------------------------------------------------
|
| Used if $config['check_cors'] is set to TRUE and $config['allow_any_cors_domain']
| is set to FALSE. Set all the allowable domains within the array
|
| e.g. $config['allowed_origins'] = ['http://www.example.com', 'https://spa.example.com']
|
*/
$config['allowed_cors_origins'] = [];
/*
|--------------------------------------------------------------------------
| CORS Forced Headers
|--------------------------------------------------------------------------
|
| If using CORS checks, always include the headers and values specified here
| in the OPTIONS client preflight.
| Example:
| $config['forced_cors_headers'] = [
| 'Access-Control-Allow-Credentials' => 'true'
| ];
|
| Added because of how Sencha Ext JS framework requires the header
| Access-Control-Allow-Credentials to be set to true to allow the use of
| credentials in the REST Proxy.
| See documentation here:
| http://docs.sencha.com/extjs/6.5.2/classic/Ext.data.proxy.Rest.html#cfg-withCredentials
|
*/
$config['forced_cors_headers'] = [];
For response language we will use language file so create a language file in application/language/english directory file name should be rest_controller_lang.php
<?php
/*
* English language
*/
$lang['text_rest_invalid_api_key'] = 'Invalid API key %s'; // %s is the REST API key
$lang['text_rest_invalid_credentials'] = 'Invalid credentials';
$lang['text_rest_ip_denied'] = 'IP denied';
$lang['text_rest_ip_unauthorized'] = 'IP unauthorized';
$lang['text_rest_unauthorized'] = 'Unauthorized';
$lang['text_rest_ajax_only'] = 'Only AJAX requests are allowed';
$lang['text_rest_api_key_unauthorized'] = 'This API key does not have access to the requested controller';
$lang['text_rest_api_key_permissions'] = 'This API key does not have enough permissions';
$lang['text_rest_api_key_time_limit'] = 'This API key has reached the time limit for this method';
$lang['text_rest_ip_address_time_limit'] = 'This IP Address has reached the time limit for this method';
$lang['text_rest_unknown_method'] = 'Unknown method';
$lang['text_rest_unsupported'] = 'Unsupported protocol';
Now we have done all the setups now create a Controller inside controller directory I will call it Blog.php
Now api will look like
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
use Restserver\Libraries\REST_Controller; // using namaspace
require_once APPPATH . '/libraries/REST_Controller.php'; //including libraray
require_once APPPATH . '/libraries/Format.php'; // response code format
class Blog extends REST_Controller
{
public function __construct()
{
parent::__construct();
$this->load->model('Blog_Model', 'blog_model');
}
/**
* Handling get Requests
*
*/
public function blog_get()
{
$id = $this->get('id'); //getting query param
$blogs = $this->blog_model->getBlogs($id);
if ( ! $blogs) {
$this->response([
'status' => false,
'message' => 'Data not found', // you can use lnguage file data here.
], 404); // you can use http methods for your purposes
/**
* for more info on http code visite https://httpstatuses.com
*/
}
else {
$this->response([
'status' => true,
'message' => 'Blogs get successfully', // you can use lnguage file data here.
'blogs' => $blogs
], 200);
}
}
/**
* handling post requests
*/
public function blog_post()
{
//you can validate your data here.
$data['title'] = $this->post('title');
$data['description'] = $this->post('description');
$response = $this->blog_model->createBlog($data);
if ( ! $response) {
$this->response([
'status' => false,
'message' => 'Unable to create blog',
], 500);
}
else {
$this->response([
'status' => true,
'message' => 'Blog created successfully',
'blog' => $data
], 200);
}
}
/**
* handling put request for update
*/
public function blog_put()
{
$id = $this->put('id');
// data to update
$data['title'] = $this->put('title');
$data['description'] = $this->put('description');
$response = $this->blog_model->updateBlog($id, $data);
if ( ! $response) {
$this->response([
'status' => false,
'message' => 'Unable to update blog',
], 404);
}
else {
$this->response([
'status' => true,
'message' => 'Blog updated successfully',
'blog' => $data
], 200);
}
}
/**
*handling delete requests
*/
public function blog_delete($id=NULL)
{
$response = $this->blog_model->deleteBlog($id);
if ( ! $response) {
$this->response([
'status' => false,
'message' => 'Unable to delete blog',
], 404);
}
else {
$this->response([
'status' => true,
'message' => 'Blog deleted successfully',
], 200);
}
}
}
create modal relatated to controller. I will call it Blog_Model.php
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Blog_Model extends CI_Model
{
public function getBlogs($id)
{
if ($id) {
$this->db->where('bid', $id);
}
$q = $this->db->get('blogs');
return $q->result();
}
public function createBlog($data)
{
$this->db->insert('blogs', $data);
return $this->db->insert_id();
}
public function updateBlog($id, $data)
{
$this->db->where('bid', $id);
$this->db->update('blogs', $data);
return $this->db->affected_rows();
}
public function deleteBlog($id)
{
$this->db->where('bid', $id);
$this->db->delete('blogs');
return $this->db->affected_rows();
}
}
Now finally create routes for api to call them in config/routes.php
// ROUTES
$route['api/blog/(:num)'] = 'Blog/blog/$1';
Download the whole project from github
If you liked this article, then please subscribe to our YouTube Channel for useful videos. You can also find us on Twitter and Facebook.
please provide a full source code if you have ya any youtube video that can more helofull
Thank you for your comment. We already edit this article with requested information. Please read and download source code. Happy coding 🙂