168 lines
4.8 KiB
PHP
168 lines
4.8 KiB
PHP
<?php
|
|
|
|
/**
|
|
* This file is part of the Froxlor project.
|
|
* Copyright (c) 2010 the Froxlor Team (see authors).
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, you can also view it online at
|
|
* https://files.froxlor.org/misc/COPYING.txt
|
|
*
|
|
* @copyright the authors
|
|
* @author Froxlor team <team@froxlor.org>
|
|
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
|
|
*/
|
|
|
|
namespace Froxlor\Api;
|
|
|
|
use Exception;
|
|
use Froxlor\Database\Database;
|
|
use Froxlor\System\IPTools;
|
|
|
|
class FroxlorRPC
|
|
{
|
|
/**
|
|
* validate a given request
|
|
*
|
|
* @param $request
|
|
* @return array
|
|
* @throws Exception
|
|
*/
|
|
public static function validateRequest($request): array
|
|
{
|
|
// make basic authentication
|
|
if (!isset($_SERVER['PHP_AUTH_USER']) || !self::validateAuth($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'])) {
|
|
if (@php_sapi_name() !== 'cli') {
|
|
header('WWW-Authenticate: Basic realm="API"');
|
|
}
|
|
throw new Exception('Unauthenticated. Please provide api user credentials.', 401);
|
|
}
|
|
|
|
// check if present
|
|
if (empty($request)) {
|
|
throw new Exception('Empty request body.', 400);
|
|
}
|
|
|
|
// decode json request
|
|
$decoded_request = json_decode($request, true);
|
|
|
|
// is it valid?
|
|
if (is_null($decoded_request)) {
|
|
throw new Exception('Invalid JSON Format.', 400);
|
|
}
|
|
|
|
return self::validateBody($decoded_request);
|
|
}
|
|
|
|
/**
|
|
* validates the given api credentials
|
|
*
|
|
* @param string $key
|
|
* @param string $secret
|
|
*
|
|
* @return bool
|
|
*/
|
|
private static function validateAuth(string $key, string $secret): bool
|
|
{
|
|
$sel_stmt = Database::prepare(
|
|
"
|
|
SELECT ak.*, a.api_allowed as admin_api_allowed, c.api_allowed as cust_api_allowed, c.deactivated
|
|
FROM `api_keys` ak
|
|
LEFT JOIN `panel_admins` a ON a.adminid = ak.adminid
|
|
LEFT JOIN `panel_customers` c ON c.customerid = ak.customerid
|
|
WHERE `apikey` = :ak AND `secret` = :as
|
|
"
|
|
);
|
|
$result = Database::pexecute_first($sel_stmt, [
|
|
'ak' => $key,
|
|
'as' => $secret
|
|
], true, true);
|
|
if ($result) {
|
|
if ($result['apikey'] == $key && $result['secret'] == $secret && ($result['valid_until'] == -1 || $result['valid_until'] >= time()) && (($result['customerid'] == 0 && $result['admin_api_allowed'] == 1) || ($result['customerid'] > 0 && $result['cust_api_allowed'] == 1 && $result['deactivated'] == 0))) {
|
|
// get user to check whether api call is allowed
|
|
if (!empty($result['allowed_from'])) {
|
|
// @todo allow specification and validating of whole subnets later
|
|
$ip_list = explode(",", $result['allowed_from']);
|
|
if (self::validateAllowedFrom($ip_list, $_SERVER['REMOTE_ADDR'])) {
|
|
return true;
|
|
}
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
throw new Exception('Invalid authorization credentials', 403);
|
|
}
|
|
|
|
/**
|
|
* validate if given remote_addr is within the list of allowed ip/ip-ranges
|
|
*
|
|
* @param array $allowed_from
|
|
* @param string $remote_addr
|
|
*
|
|
* @return bool
|
|
*/
|
|
public static function validateAllowedFrom(array $allowed_from, string $remote_addr): bool
|
|
{
|
|
// shorten IP for comparison
|
|
$remote_addr = inet_ntop(inet_pton($remote_addr));
|
|
// check for direct matches
|
|
if (in_array($remote_addr, $allowed_from)) {
|
|
return true;
|
|
}
|
|
// check for possible cidr ranges
|
|
foreach ($allowed_from as $ip) {
|
|
$ip_cidr = explode("/", $ip);
|
|
if (count($ip_cidr) == 2 && IPTools::ip_in_range($ip_cidr, $remote_addr)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* validates the given command
|
|
*
|
|
* @param array $request
|
|
*
|
|
* @return array
|
|
* @throws Exception
|
|
*/
|
|
private static function validateBody($request)
|
|
{
|
|
// check command exists
|
|
if (empty($request['command'])) {
|
|
throw new Exception("Please provide a command.", 400);
|
|
}
|
|
|
|
$command = explode(".", $request['command']);
|
|
|
|
if (count($command) != 2) {
|
|
throw new Exception("The given command is invalid.", 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\\Api\\Commands\\' . $command[0];
|
|
if (!class_exists($apiclass) || !@method_exists($apiclass, $command[1])) {
|
|
throw new Exception("Unknown command", 400);
|
|
}
|
|
return [
|
|
'command' => [
|
|
'class' => $command[0],
|
|
'method' => $command[1]
|
|
],
|
|
'params' => $request['params'] ?? null
|
|
];
|
|
}
|
|
}
|