start api implementation
Signed-off-by: Michael Kaufmann (d00p) <d00p@froxlor.org>
This commit is contained in:
69
api.php
Normal file
69
api.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
require __DIR__ . '/lib/classes/api/api_includes.inc.php';
|
||||
|
||||
// check whether API interface is enabled after all
|
||||
if (Settings::Get('api.enabled') != 1) {
|
||||
// not enabled
|
||||
header("Status: 404 Not found", 404);
|
||||
header($_SERVER["SERVER_PROTOCOL"] . " 404 Not found", 404);
|
||||
exit();
|
||||
}
|
||||
|
||||
// we're talking json here
|
||||
header("Content-Type:application/json");
|
||||
|
||||
// get our request
|
||||
$request = isset($_GET['request']) ? $_GET['request'] : null;
|
||||
if (empty($request)) {
|
||||
$request = isset($_POST['request']) ? $_POST['request'] : null;
|
||||
}
|
||||
|
||||
// check if present
|
||||
if (empty($request)) {
|
||||
json_response(400, "Invalid request");
|
||||
}
|
||||
|
||||
// decode json request
|
||||
$decoded_request = json_decode(stripslashes($request), true);
|
||||
|
||||
// is it valid?
|
||||
if (is_null($decoded_request)) {
|
||||
json_response(400, "Invalid JSON");
|
||||
}
|
||||
|
||||
// validate content
|
||||
try {
|
||||
$request = FroxlorRPC::validateRequest($decoded_request);
|
||||
// now actually do it
|
||||
$cls = $request['command']['class'];
|
||||
$method = $request['command']['method'];
|
||||
$apiObj = new $cls($decoded_request['header'], $request['params']);
|
||||
// call the method with the params if any
|
||||
echo $apiObj->$method();
|
||||
} catch (Exception $e) {
|
||||
json_response($e->getCode(), $e->getMessage());
|
||||
}
|
||||
|
||||
exit();
|
||||
|
||||
/**
|
||||
* output json result
|
||||
*
|
||||
* @param int $status
|
||||
* @param string $status_message
|
||||
* @param mixed $data
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function json_response($status, $status_message, $data = null)
|
||||
{
|
||||
header("HTTP/1.1 " . $status);
|
||||
|
||||
$response['status'] = $status;
|
||||
$response['status_message'] = $status_message;
|
||||
$response['data'] = $data;
|
||||
|
||||
$json_response = json_encode($response, JSON_PRETTY_PRINT);
|
||||
echo $json_response;
|
||||
exit();
|
||||
}
|
||||
@@ -1042,3 +1042,18 @@ CREATE TABLE `panel_plans` (
|
||||
KEY adminid (adminid)
|
||||
) ENGINE=MyISAM CHARSET=utf8 COLLATE=utf8_general_ci;
|
||||
|
||||
|
||||
DROP TABLE IF EXISTS `api_keys`;
|
||||
CREATE TABLE `api_keys` (
|
||||
`id` int(11) NOT NULL auto_increment,
|
||||
`adminid` int(11) NOT NULL default '0',
|
||||
`customerid` int(11) NOT NULL default '0',
|
||||
`apikey` varchar(500) NOT NULL default '',
|
||||
`secret` varchar(500) NOT NULL default '',
|
||||
`allowed_from` text NOT NULL,
|
||||
`valid_until` int(15) NOT NULL default '0',
|
||||
PRIMARY KEY (id),
|
||||
KEY adminid (adminid),
|
||||
KEY customerid (customerid)
|
||||
) ENGINE=MyISAM CHARSET=utf8 COLLATE=utf8_general_ci;
|
||||
|
||||
|
||||
129
lib/classes/api/abstract.ApiCommand.php
Normal file
129
lib/classes/api/abstract.ApiCommand.php
Normal file
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
abstract class ApiCommand
|
||||
{
|
||||
|
||||
private $is_admin = false;
|
||||
|
||||
private $user_data = null;
|
||||
|
||||
private $logger = null;
|
||||
|
||||
private $cmd_params = null;
|
||||
|
||||
public function __construct($header = null, $params = null, $userinfo = null)
|
||||
{
|
||||
$this->cmd_params = $params;
|
||||
if (! empty($header)) {
|
||||
$this->readUserData($header);
|
||||
} elseif (! empty($userinfo)) {
|
||||
$this->user_data = $userinfo;
|
||||
$this->is_admin = ($userinfo['adminsession'] == 1 && $userinfo['adminid'] > 0) ? true : false;
|
||||
} else {
|
||||
throw new Exception("Invalid user data", 500);
|
||||
}
|
||||
$this->logger = FroxlorLogger::getInstanceOf($this->user_data);
|
||||
}
|
||||
|
||||
public static function getLocal($userinfo = null, $params = null)
|
||||
{
|
||||
return new static(null, $params, $userinfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* admin flag
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function isAdmin()
|
||||
{
|
||||
return $this->is_admin;
|
||||
}
|
||||
|
||||
/**
|
||||
* return field from user-table
|
||||
*
|
||||
* @param string $detail
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getUserDetail($detail = null)
|
||||
{
|
||||
return (isset($this->user_data[$detail]) ? $this->user_data[$detail] : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* receive field from parameter-list
|
||||
*
|
||||
* @param string $param
|
||||
*
|
||||
* @throws Exception
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getParam($param = null)
|
||||
{
|
||||
if (isset($this->cmd_params[$param])) {
|
||||
return $this->cmd_params[$param];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* return logger instance
|
||||
*
|
||||
* @return FroxlorLogger
|
||||
*/
|
||||
protected function logger()
|
||||
{
|
||||
return $this->logger;
|
||||
}
|
||||
|
||||
protected function response($status, $status_message, $data = null)
|
||||
{
|
||||
header("HTTP/1.1 " . $status);
|
||||
|
||||
$response['status'] = $status;
|
||||
$response['status_message'] = $status_message;
|
||||
$response['data'] = $data;
|
||||
|
||||
$json_response = json_encode($response, JSON_PRETTY_PRINT);
|
||||
return $json_response;
|
||||
}
|
||||
|
||||
public abstract function list();
|
||||
|
||||
public abstract function get();
|
||||
|
||||
public abstract function add();
|
||||
|
||||
public abstract function update();
|
||||
|
||||
public abstract function delete();
|
||||
|
||||
private function readUserData($header = null)
|
||||
{
|
||||
$sel_stmt = Database::prepare("SELECT * FROM `api_keys` WHERE `apikey` = :ak AND `secret` = :as");
|
||||
$result = Database::pexecute_first($sel_stmt, array(
|
||||
'ak' => $header['apikey'],
|
||||
'as' => $header['secret']
|
||||
), true, true);
|
||||
if ($result) {
|
||||
// admin or customer?
|
||||
if ($result['customerid'] == 0) {
|
||||
$this->is_admin = true;
|
||||
$table = 'panel_admins';
|
||||
$key = "adminid";
|
||||
} else {
|
||||
$this->is_admin = false;
|
||||
$table = 'panel_customers';
|
||||
$key = "customerid";
|
||||
}
|
||||
$sel_stmt = Database::prepare("SELECT * FROM `" . $table . "` WHERE `" . $key . "` = :id");
|
||||
$this->user_data = Database::pexecute_first($sel_stmt, array(
|
||||
'id' => ($this->is_admin ? $result['adminid'] : $result['customerid'])
|
||||
), true, true);
|
||||
return true;
|
||||
}
|
||||
throw new Exception("Invalid API credentials", 400);
|
||||
}
|
||||
}
|
||||
6
lib/classes/api/api_includes.inc.php
Normal file
6
lib/classes/api/api_includes.inc.php
Normal file
@@ -0,0 +1,6 @@
|
||||
<?php
|
||||
if (! defined('FROXLOR_INSTALL_DIR')) {
|
||||
define('FROXLOR_INSTALL_DIR', dirname(dirname(dirname(__DIR__))));
|
||||
require_once FROXLOR_INSTALL_DIR . '/lib/tables.inc.php';
|
||||
require_once FROXLOR_INSTALL_DIR . '/lib/functions.php';
|
||||
}
|
||||
100
lib/classes/api/class.FroxlorRPC.php
Normal file
100
lib/classes/api/class.FroxlorRPC.php
Normal file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
class FroxlorRPC
|
||||
{
|
||||
|
||||
/**
|
||||
* validate a given request
|
||||
*
|
||||
* @param array $request
|
||||
*
|
||||
* @throws Exception
|
||||
* @return array
|
||||
*/
|
||||
public static function validateRequest($request)
|
||||
{
|
||||
// check header
|
||||
if (! isset($request['header']) || empty($request['header'])) {
|
||||
throw new Exception("Invalid request header", 400);
|
||||
}
|
||||
|
||||
// check authorization
|
||||
if (! isset($request['header']['apikey']) || empty($request['header']['apikey']) || ! isset($request['header']['secret']) || empty($request['header']['secret'])) {
|
||||
throw new Exception("No authorization credentials given", 400);
|
||||
}
|
||||
self::validateAuth($request['header']['apikey'], $request['header']['secret']);
|
||||
|
||||
// check command
|
||||
return self::validateBody($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* validates the given api credentials
|
||||
*
|
||||
* @param string $key
|
||||
* @param string $secret
|
||||
*
|
||||
* @throws Exception
|
||||
* @return boolean
|
||||
*/
|
||||
private static function validateAuth($key, $secret)
|
||||
{
|
||||
$sel_stmt = Database::prepare("SELECT * FROM `api_keys` WHERE `apikey` = :ak AND `secret` = :as");
|
||||
$result = Database::pexecute_first($sel_stmt, array(
|
||||
'ak' => $key,
|
||||
'as' => $secret
|
||||
), true, true);
|
||||
if ($result) {
|
||||
if ($result['apikey'] == $key && $result['secret'] == $secret && ($result['valid_until'] == -1 || $result['valid_until'] >= time())) {
|
||||
if (!empty($result['allowed_from'])) {
|
||||
$ip_list = explode(",", $result['allowed_from']);
|
||||
$access_ip = $_SERVER['REMOTE_ADDR'];
|
||||
// @fixme finish me
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
throw new Exception("Invalid authorization credentials", 400);
|
||||
}
|
||||
|
||||
/**
|
||||
* validates the given command
|
||||
*
|
||||
* @param array $body
|
||||
*
|
||||
* @throws Exception
|
||||
* @return boolean
|
||||
*/
|
||||
private static function validateBody($request)
|
||||
{
|
||||
// check body
|
||||
if (! isset($request['body']) || empty($request['body'])) {
|
||||
throw new Exception("Invalid request body", 400);
|
||||
}
|
||||
|
||||
// check command exists
|
||||
if (! isset($request['body']['command']) || empty($request['body']['command'])) {
|
||||
throw new Exception("No command given", 400);
|
||||
}
|
||||
|
||||
$command = explode(".", $request['body']['command']);
|
||||
|
||||
if (count($command) != 2) {
|
||||
throw new Exception("Invalid command", 400);
|
||||
}
|
||||
// simply check for file-existance, as we do not want to use our autoloader because this way
|
||||
// it will recognize non-api classes+methods as valid commands
|
||||
$apiclass = FROXLOR_INSTALL_DIR . '/lib/classes/api/commands/class.' . $command[0] . '.php';
|
||||
if (! file_exists($apiclass) || ! @method_exists($command[0], $command[1])) {
|
||||
// there will be an exception from the autoloader for class_exists hence the try-catch-block
|
||||
throw new Exception("Unknown command", 400);
|
||||
}
|
||||
return array(
|
||||
'command' => array(
|
||||
'class' => $command[0],
|
||||
'method' => $command[1]
|
||||
),
|
||||
'params' => isset($request['body']['params']) ? $request['body']['params'] : null
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -67,11 +67,11 @@ class Database {
|
||||
* @param array $params (optional)
|
||||
* @param bool $showerror suppress errordisplay (default true)
|
||||
*/
|
||||
public static function pexecute(&$stmt, $params = null, $showerror = true) {
|
||||
public static function pexecute(&$stmt, $params = null, $showerror = true, $json_response = false) {
|
||||
try {
|
||||
$stmt->execute($params);
|
||||
} catch (PDOException $e) {
|
||||
self::_showerror($e, $showerror);
|
||||
self::_showerror($e, $showerror, $json_response);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,8 +86,8 @@ class Database {
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function pexecute_first(&$stmt, $params = null, $showerror = true) {
|
||||
self::pexecute($stmt, $params, $showerror);
|
||||
public static function pexecute_first(&$stmt, $params = null, $showerror = true, $json_response = false) {
|
||||
self::pexecute($stmt, $params, $showerror, $json_response);
|
||||
return $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
@@ -309,7 +309,7 @@ class Database {
|
||||
* @param PDOException $error
|
||||
* @param bool $showerror if set to false, the error will be logged but we go on
|
||||
*/
|
||||
private static function _showerror($error, $showerror = true) {
|
||||
private static function _showerror($error, $showerror = true, $json_response = false) {
|
||||
global $userinfo, $theme, $linker;
|
||||
|
||||
// include userdata.inc.php
|
||||
@@ -367,6 +367,10 @@ class Database {
|
||||
@fwrite($errlog, "|TRACE\n".$error_trace."\n");
|
||||
@fclose($errlog);
|
||||
|
||||
if ($showerror && $json_response) {
|
||||
throw new Exception($error_message, 500);
|
||||
}
|
||||
|
||||
if ($showerror) {
|
||||
if (empty($sql['debug'])) {
|
||||
$error_trace = '';
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
* @author Florian Lippert <flo@syscp.org>
|
||||
* @author Ron Brand <ron.brand@web.de>
|
||||
*/
|
||||
function standard_error($errors = '', $replacer = '') {
|
||||
function standard_error($errors = '', $replacer = '', $throw_exception = false) {
|
||||
|
||||
global $userinfo, $s, $header, $footer, $lng, $theme;
|
||||
|
||||
@@ -60,6 +60,9 @@ function standard_error($errors = '', $replacer = '') {
|
||||
}
|
||||
}
|
||||
|
||||
if ($throw_exception) {
|
||||
throw new Exception($error);
|
||||
}
|
||||
eval("echo \"" . getTemplate('misc/error', '1') . "\";");
|
||||
exit;
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
function validate($str, $fieldname, $pattern = '', $lng = '', $emptydefault = array()) {
|
||||
function validate($str, $fieldname, $pattern = '', $lng = '', $emptydefault = array(), $throw_exception = false) {
|
||||
|
||||
global $log, $theme;
|
||||
|
||||
@@ -74,6 +74,6 @@ function validate($str, $fieldname, $pattern = '', $lng = '', $emptydefault = ar
|
||||
$lng = 'stringformaterror';
|
||||
}
|
||||
|
||||
standard_error($lng, $fieldname);
|
||||
standard_error($lng, $fieldname, $throw_exception);
|
||||
exit;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
* @return mixed ip address on success, standard_error on failure
|
||||
* @deprecated use validate_ip2
|
||||
*/
|
||||
function validate_ip($ip, $return_bool = false, $lng = 'invalidip') {
|
||||
function validate_ip($ip, $return_bool = false, $lng = 'invalidip', $throw_exception = false) {
|
||||
|
||||
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === false
|
||||
&& filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) === false
|
||||
@@ -32,7 +32,7 @@ function validate_ip($ip, $return_bool = false, $lng = 'invalidip') {
|
||||
if ($return_bool) {
|
||||
return false;
|
||||
} else {
|
||||
standard_error($lng, $ip);
|
||||
standard_error($lng, $ip, $throw_exception);
|
||||
exit();
|
||||
}
|
||||
} else {
|
||||
@@ -53,7 +53,7 @@ function validate_ip($ip, $return_bool = false, $lng = 'invalidip') {
|
||||
*
|
||||
* @return string|bool ip address on success, false on failure
|
||||
*/
|
||||
function validate_ip2($ip, $return_bool = false, $lng = 'invalidip', $allow_localhost = false, $allow_priv = false, $allow_cidr = false) {
|
||||
function validate_ip2($ip, $return_bool = false, $lng = 'invalidip', $allow_localhost = false, $allow_priv = false, $allow_cidr = false, $throw_exception = false) {
|
||||
|
||||
$cidr = "";
|
||||
if ($allow_cidr) {
|
||||
@@ -69,7 +69,7 @@ function validate_ip2($ip, $return_bool = false, $lng = 'invalidip', $allow_loca
|
||||
if ($return_bool) {
|
||||
return false;
|
||||
} else {
|
||||
standard_error($lng, $ip);
|
||||
standard_error($lng, $ip, $throw_exception);
|
||||
exit();
|
||||
}
|
||||
}
|
||||
@@ -91,7 +91,7 @@ function validate_ip2($ip, $return_bool = false, $lng = 'invalidip', $allow_loca
|
||||
if ($return_bool) {
|
||||
return false;
|
||||
} else {
|
||||
standard_error($lng, $ip);
|
||||
standard_error($lng, $ip, $throw_exception);
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user