You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
321 lines
8.5 KiB
321 lines
8.5 KiB
<?php
|
|
/*
|
|
** Zabbix
|
|
** Copyright (C) 2001-2023 Zabbix SIA
|
|
**
|
|
** 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, write to the Free Software
|
|
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
**/
|
|
|
|
|
|
/**
|
|
* This class should be used to call API services locally using the CApiService classes.
|
|
*/
|
|
class CLocalApiClient extends CApiClient {
|
|
|
|
/**
|
|
* Factory for creating API services.
|
|
*
|
|
* @var CRegistryFactory
|
|
*/
|
|
protected $serviceFactory;
|
|
|
|
/**
|
|
* Whether debug mode is enabled.
|
|
*
|
|
* @var bool
|
|
*/
|
|
protected $debug = false;
|
|
|
|
/**
|
|
* Set service factory.
|
|
*
|
|
* @param CRegistryFactory $factory
|
|
*/
|
|
public function setServiceFactory(CRegistryFactory $factory) {
|
|
$this->serviceFactory = $factory;
|
|
}
|
|
|
|
/**
|
|
* Call the given API service method and return the response.
|
|
*
|
|
* @param string $requestApi API name.
|
|
* @param string $requestMethod API method.
|
|
* @param array $params API parameters.
|
|
* @param array $auth
|
|
* @param int $auth['type'] CJsonRpc::AUTH_TYPE_PARAM, CJsonRpc::AUTH_TYPE_HEADER, CJsonRpc::AUTH_TYPE_COOKIE
|
|
* @param string $auth['auth'] Authentication token.
|
|
*
|
|
* @return CApiClientResponse
|
|
*/
|
|
public function callMethod(string $requestApi, string $requestMethod, array $params, array $auth) {
|
|
global $DB;
|
|
|
|
$api = strtolower($requestApi);
|
|
$method = strtolower($requestMethod);
|
|
|
|
$response = new CApiClientResponse();
|
|
|
|
// check API
|
|
if (!$this->isValidApi($api)) {
|
|
$response->errorCode = ZBX_API_ERROR_NO_METHOD;
|
|
$response->errorMessage = _s('Incorrect API "%1$s".', $requestApi);
|
|
|
|
return $response;
|
|
}
|
|
|
|
// check method
|
|
if (!$this->isValidMethod($api, $method)) {
|
|
$response->errorCode = ZBX_API_ERROR_NO_METHOD;
|
|
$response->errorMessage = _s('Incorrect method "%1$s.%2$s".', $requestApi, $requestMethod);
|
|
|
|
return $response;
|
|
}
|
|
|
|
$requiresAuthentication = $this->requiresAuthentication($api, $method);
|
|
|
|
// check that no authentication token is passed to methods that don't require it
|
|
if (!$requiresAuthentication) {
|
|
if ($auth['type'] == CJsonRpc::AUTH_TYPE_COOKIE) {
|
|
$auth['auth'] = null;
|
|
}
|
|
|
|
if ($auth['auth'] !== null) {
|
|
$error = $auth['type'] == CJsonRpc::AUTH_TYPE_HEADER
|
|
? _('The "%1$s.%2$s" method must be called without authorization header.')
|
|
: _('The "%1$s.%2$s" method must be called without the "auth" parameter.');
|
|
|
|
$response->errorCode = ZBX_API_ERROR_PARAMETERS;
|
|
$response->errorMessage = _params($error, [$requestApi, $requestMethod]);
|
|
|
|
return $response;
|
|
}
|
|
}
|
|
|
|
$newTransaction = false;
|
|
try {
|
|
// authenticate
|
|
if ($requiresAuthentication) {
|
|
$this->authenticate($auth['auth']);
|
|
|
|
// check permissions
|
|
if (APP::getMode() === APP::EXEC_MODE_API && !$this->isAllowedMethod($api, $method)) {
|
|
$response->errorCode = ZBX_API_ERROR_PERMISSIONS;
|
|
$response->errorMessage = _s('No permissions to call "%1$s.%2$s".', $requestApi, $requestMethod);
|
|
|
|
return $response;
|
|
}
|
|
}
|
|
|
|
// the nopermission parameter must not be available for external API calls.
|
|
unset($params['nopermissions']);
|
|
|
|
// if no transaction has been started yet - start one
|
|
if ($DB['TRANSACTIONS'] == 0) {
|
|
DBstart();
|
|
$newTransaction = true;
|
|
}
|
|
|
|
// call API method
|
|
$result = call_user_func_array([$this->serviceFactory->getObject($api), $method], [$params]);
|
|
|
|
// if the method was called successfully - commit the transaction
|
|
if ($newTransaction) {
|
|
DBend(true);
|
|
}
|
|
|
|
$response->data = $result;
|
|
}
|
|
catch (Exception $e) {
|
|
if ($newTransaction) {
|
|
// if we're calling user.login and authentication failed - commit the transaction to save the
|
|
// failed attempt data
|
|
if ($api === 'user' && $method === 'login') {
|
|
DBend(true);
|
|
}
|
|
// otherwise - revert the transaction
|
|
else {
|
|
DBend(false);
|
|
}
|
|
}
|
|
|
|
$response->errorCode = ($e instanceof APIException) ? $e->getCode() : ZBX_API_ERROR_INTERNAL;
|
|
$response->errorMessage = $e->getMessage();
|
|
|
|
// add debug data
|
|
if ($this->debug) {
|
|
$response->debug = $e->getTrace();
|
|
}
|
|
}
|
|
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* Checks if the authentication token is valid.
|
|
*
|
|
* @param string $auth
|
|
*
|
|
* @throws APIException
|
|
*/
|
|
protected function authenticate($auth) {
|
|
if (zbx_empty($auth)) {
|
|
throw new APIException(ZBX_API_ERROR_NO_AUTH, _('Not authorized.'));
|
|
}
|
|
|
|
$auth_data = strlen($auth) == 64 ? ['token' => $auth] : ['sessionid' => $auth];
|
|
|
|
$user = $this->serviceFactory->getObject('user')->checkAuthentication($auth_data);
|
|
if (array_key_exists('debug_mode', $user)) {
|
|
$this->debug = $user['debug_mode'];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns true if the given API is valid.
|
|
*
|
|
* @param string $api
|
|
*
|
|
* @return bool
|
|
*/
|
|
protected function isValidApi($api) {
|
|
return $this->serviceFactory->hasObject($api);
|
|
}
|
|
|
|
/**
|
|
* Returns true if the given method is valid.
|
|
*
|
|
* @param string $api
|
|
* @param string $method
|
|
*
|
|
* @return bool
|
|
*/
|
|
protected function isValidMethod(string $api, string $method): bool {
|
|
$api_service = $this->serviceFactory->getObject($api);
|
|
|
|
return array_key_exists($method, $api_service::ACCESS_RULES);
|
|
}
|
|
|
|
/**
|
|
* Returns true if calling the given method requires a valid authentication token.
|
|
*
|
|
* @param $api
|
|
* @param $method
|
|
*
|
|
* @return bool
|
|
*/
|
|
protected function requiresAuthentication($api, $method) {
|
|
return !(($api === 'user' && $method === 'login')
|
|
|| ($api === 'user' && $method === 'checkauthentication')
|
|
|| ($api === 'apiinfo' && $method === 'version')
|
|
|| ($api === 'settings' && $method === 'getglobal'));
|
|
}
|
|
|
|
/**
|
|
* Returns true if the current user is permitted to call the given API method, and false otherwise.
|
|
*
|
|
* @param string $api
|
|
* @param string $method
|
|
*
|
|
* @return bool
|
|
*/
|
|
protected function isAllowedMethod(string $api, string $method): bool {
|
|
$api_service = $this->serviceFactory->getObject($api);
|
|
$user_data = $api_service::$userData;
|
|
$method_rules = $api_service::ACCESS_RULES[$method];
|
|
|
|
if (!array_key_exists('min_user_type', $method_rules)
|
|
|| !in_array($user_data['type'], [USER_TYPE_ZABBIX_USER, USER_TYPE_ZABBIX_ADMIN, USER_TYPE_SUPER_ADMIN])
|
|
|| $user_data['type'] < $method_rules['min_user_type']) {
|
|
return false;
|
|
}
|
|
|
|
$exists_action_rule = array_key_exists('action', $method_rules);
|
|
|
|
$name_conditions = 'name LIKE '.zbx_dbstr('api%');
|
|
if ($exists_action_rule) {
|
|
$name_conditions = '('.
|
|
$name_conditions.
|
|
' OR name='.zbx_dbstr($method_rules['action']).
|
|
' OR name='.zbx_dbstr('actions.default_access').
|
|
')';
|
|
}
|
|
|
|
$db_rules = DBselect(
|
|
'SELECT type,name,value_str,value_int'.
|
|
' FROM role_rule'.
|
|
' WHERE roleid='.zbx_dbstr($user_data['roleid']).
|
|
' AND '.$name_conditions.
|
|
' ORDER by name'
|
|
);
|
|
|
|
$api_access_mode = false;
|
|
$api_methods = [];
|
|
$actions_default_access = true;
|
|
$is_action_allowed = null;
|
|
|
|
while ($db_rule = DBfetch($db_rules)) {
|
|
$rule_value = $db_rule[CRole::RULE_TYPE_FIELDS[$db_rule['type']]];
|
|
|
|
switch ($db_rule['name']) {
|
|
case 'api.access':
|
|
if ($rule_value == 0) {
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case 'api.mode':
|
|
$api_access_mode = (bool) $rule_value;
|
|
break;
|
|
|
|
case 'actions.default_access':
|
|
$actions_default_access = (bool) $rule_value;
|
|
break;
|
|
|
|
default:
|
|
if (strpos($db_rule['name'], 'api.method.') === 0) {
|
|
$api_methods[] = $rule_value;
|
|
}
|
|
elseif ($exists_action_rule && $db_rule['name'] === $method_rules['action']) {
|
|
$is_action_allowed = (bool) $rule_value;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($exists_action_rule) {
|
|
$is_action_allowed = ($is_action_allowed !== null) ? $is_action_allowed : $actions_default_access;
|
|
|
|
if (!$is_action_allowed) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!$api_methods) {
|
|
return true;
|
|
}
|
|
|
|
$api_method_masks = [
|
|
ZBX_ROLE_RULE_API_WILDCARD, ZBX_ROLE_RULE_API_WILDCARD_ALIAS, CRoleHelper::API_ANY_SERVICE.$method,
|
|
$api.CRoleHelper::API_ANY_METHOD
|
|
];
|
|
foreach ($api_methods as $api_method) {
|
|
if ($api_method === $api.'.'.$method || in_array($api_method, $api_method_masks)) {
|
|
return $api_access_mode;
|
|
}
|
|
}
|
|
|
|
return !$api_access_mode;
|
|
}
|
|
}
|