Projekt

Obecné

Profil

Stáhnout (8.75 KB) Statistiky
| Větev: | Tag: | Revize:
74a7dbca Michal Kliment
<?php defined('SYSPATH') or die('No direct script access.');
/*
* This file is part of open source system FreenetIS
* and it is release under GPLv3 licence.
*
* More info about licence can be found:
* http://www.gnu.org/licenses/gpl-3.0.html
*
* More info about project can be found:
* http://www.freenetis.org/
*
*/

require_once APPPATH . '/vendors/phpax-rs/loader.php';
require_once APPPATH . '/vendors/php-http-auth-server/loader.php';

/**
* FreenetIS REST API builded using PHPAX-RS framework.
* All requested passed to this controller are handled by PHPAX-RS REST
* end points classes that are stored in ./api_endpoints folder and their
* names end with "_Api".
*
* @author Ondřej Fibich <ondrej.fibich@gmail.com>
* @package Controller
* @since 1.2
*/
class Api_Controller extends Controller
{
/**
* Base API path (relative path to this constroller).
*/
const API_BASE_PATH = '/api';
/**
* API end point class name suffix.
*/
const API_ENDPOINT_SUFFIX = '_Api';
/**
* API directory relative to controllers dir that contains all endpoints.
*/
const API_ENPOIND_DIR = 'api_endpoints';

/**
* @var \phpaxrs\PhpaxRs
*/
private static $phpaxRs = NULL;
/**
* Initilize singleton instance of PHPAX-RS framework runtime and adds
* available serializators and end points.
*/
public function __construct()
{
parent::__construct();
// access control
if (!module::e('api'))
{
self::error(PAGE);
}
// init PHPAX-RS
if (self::$phpaxRs === NULL)
{
$base_path = rtrim(Settings::get('suffix'), '/') . '/'
. Config::get('lang') . self::API_BASE_PATH;
self::$phpaxRs = new \phpaxrs\PhpaxRs($base_path);
// add serializators
self::$phpaxRs->add_serializator('application/json',
'\phpaxrs\serializator\JsonSerializator');
// add end points
$ds = DIRECTORY_SEPARATOR;
self::add_end_points(__DIR__ . $ds . self::API_ENPOIND_DIR);
}
}
/**
* We do not need preprocessor.
*
* @return boolean
*/
protected function is_preprocesor_enabled()
{
return FALSE;
}
/**
* Maps every request to PHPAX-RS.
*
* @param string $method
* @param array $args
*/
public function _remap($method, $args)
{
$base_url = url::base() . url::current();
$part_request_url = '/';
if ($method === 'index') { // index method should be ignored in path
$method = NULL;
}
if (!empty($method))
{
$part_request_url .= $method . '/';
if (!empty($args))
{
$part_request_url .= implode('/', $args);
}
$part_request_url = '/' . ltrim($part_request_url, '/');
}
// authorization and authentification
if ($this->check_http_auth(request::method(), $part_request_url))
{
// serve request using PHPAX-RS
$response = self::$phpaxRs->serve($base_url);
// render response
self::$phpaxRs->render($response);
}
}

/**
* Register all end point classes from the given directory.
*
* @param string $dir
*/
private static function add_end_points($dir)
{
if (!is_dir($dir))
{
self::warning(PARAMETER, 'Invalid API end points directory');
}
$files = scandir($dir);
if (is_array($files))
{
foreach ($files as $file)
{
if (is_file($dir . DIRECTORY_SEPARATOR . $file) &&
text::ends_with($file, EXT))
{
$class = substr($file, 0, strlen($file) - strlen(EXT));
$path = trim(strtolower(str_replace('_', '/', $class)), '/');
$class_full = $class . self::API_ENDPOINT_SUFFIX;
self::$phpaxRs->add_endpoint('/' . $path, $class_full);
}
}
}
}
/**
* Check authorization and authentification using HTTP Digest Authentication
* and API account. If auth failed than the whole script is terminated with
* error response with proper HTTP response status code.
*
* @param string $http_method request HTTP method name
* @param string $req_url_part request URL without API base URL
* @return boolean auth success?
*/
private function check_http_auth($http_method, $req_url_part)
{
$sn = Settings::get('domain')
. str_replace('/', ' ', Settings::get('suffix')) . ' API';
$aam = new ApiAccountManager();
$auth_type = Settings::get('api_auth_type');
$handler = \phphttpauthserver\HttpAuth::factory($auth_type, $aam, $sn);
$auth_rsp = $handler->auth();
// authentification failed?
if (!$auth_rsp->isPassed())
{
self::trigger_auth_error(401, $auth_rsp->getErrors(),
$auth_rsp->getHeaders());
}
// proceed with authorization
$api_account = $aam->get_user_account($auth_rsp->getUsername());
// API account not found
if (!$api_account->id)
{
self::trigger_auth_error(500, 'API account not found');
}
// log access
try
{
$log_type = Api_account_log_Model::get_rq_type_of($http_method);
$description = mb_strtoupper($http_method) . ' ' . $req_url_part;
$api_account->create_request_log($log_type, $description)
->save_throwable();
}
catch (InvalidArgumentException $ex)
{
self::trigger_auth_error(405); // HTTP method not accepted
}
catch (Exception $ex)
{
self::trigger_auth_error(500, 'API log failed, cause: ' . $ex);
}
// API account not allowed?
if (!$api_account->enabled)
{
self::trigger_auth_error(403);
}
// Reaonly access -> only GET HTTP method allowed for request
if ($api_account->readonly && mb_strtoupper($http_method) !== 'GET')
{
self::trigger_auth_error(403);
}
// API account access restricted to allowed template paths?
if (!empty($api_account->allowed_paths))
{
$allowed_tpaths = explode(',', $api_account->allowed_paths);
if (!empty($allowed_tpaths) &&
!url_tpath::match_one_of($allowed_tpaths, $req_url_part))
{
self::trigger_auth_error(403);
}
}
// OK allowed
return TRUE;
}
/**
* Renders authorization or authentification error and exists.
*
* @staticvar array $status_codes allowed status codes
* @param integer $status_code HTTP status code (one of allowed)
* @param array|string $errors array of errors
* @param array $headers HTTP headers as associative array
* @throws ErrorException on invalid state or arguments
*/
private static function trigger_auth_error($status_code, $errors = array(),
$headers = array())
{
// status codes names
static $status_codes = array
(
401 => 'Unauthorized',
403 => 'Forbidden',
405 => 'Method Not Allowed',
500 => 'Internal Server Error'
);
// render status code
if (headers_sent())
{
throw ErrorException('E' . $status_code . ': headers already send');
}
if (!array_key_exists($status_code, $status_codes))
{
throw ErrorException('Invalid status code: ' . $status_code);
}
header('HTTP/1.1 ' . $status_code . ' ' . $status_codes[$status_code]);
// add headers
if (!empty($headers))
{
foreach ($headers as $key => $value)
{
header($key . ': ' . $value);
}
}
// output errors
if (!empty($errors))
{
if (is_string($errors))
{
$errors = array($errors);
}
echo implode("\n", $errors) . "\n";
}
exit();
}

}

/**
* Account manager implementation using API account model.
*
* @link Api_account_Model
*/
class ApiAccountManager implements \phphttpauthserver\IAccountManager
{
/**
* API account model used for obtaining account informations.
*
* @var Api_account_Model
*/
private $api_account_model;
public function __construct()
{
$this->api_account_model = new Api_account_Model();
}
/*
* @override
*/
public function getUserPassword($username)
{
try
{
$ua = $this->api_account_model->find_by_username($username);
if ($ua === NULL)
{
return FALSE;
}
return $ua->token;
}
catch (Exception $ex)
{
Log::add_exception($ex);
return FALSE;
}
}
/**
* Gets API account that has given username or returns NULL if not found
* or some error occures.
*
* @param string $username
* @return Api_account_Model|null
*/
public function get_user_account($username)
{
try
{
return $this->api_account_model->find_by_username($username);
}
catch (Exception $ex)
{
Log::add_exception($ex);
return NULL;
}
}
}