<?php declare(strict_types = 0); /* ** Copyright (C) 2001-2025 Zabbix SIA ** ** This program is free software: you can redistribute it and/or modify it under the terms of ** the GNU Affero General Public License as published by the Free Software Foundation, version 3. ** ** 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 Affero General Public License for more details. ** ** You should have received a copy of the GNU Affero General Public License along with this program. ** If not, see <https://www.gnu.org/licenses/>. **/ /** * Class containing methods for operations with proxy groups. */ class CProxyGroup extends CApiService { public const ACCESS_RULES = [ 'get' => ['min_user_type' => USER_TYPE_ZABBIX_USER], 'create' => ['min_user_type' => USER_TYPE_SUPER_ADMIN], 'update' => ['min_user_type' => USER_TYPE_SUPER_ADMIN], 'delete' => ['min_user_type' => USER_TYPE_SUPER_ADMIN] ]; protected $tableName = 'proxy_group'; protected $tableAlias = 'pg'; protected $sortColumns = ['proxy_groupid', 'name']; public const OUTPUT_FIELDS = ['proxy_groupid', 'name', 'failover_delay', 'min_online', 'description', 'state']; /** * @param array $options * * @throws APIException * * @return array|string */ public function get(array $options = []) { $api_input_rules = ['type' => API_OBJECT, 'fields' => [ 'proxy_groupids' => ['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE, 'default' => null], 'proxyids' => ['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE, 'default' => null], 'filter' => ['type' => API_FILTER, 'flags' => API_ALLOW_NULL, 'default' => null, 'fields' => ['proxy_groupid', 'name', 'failover_delay', 'min_online', 'state']], 'search' => ['type' => API_FILTER, 'flags' => API_ALLOW_NULL, 'default' => null, 'fields' => ['name', 'description', 'failover_delay', 'min_online']], 'searchByAny' => ['type' => API_BOOLEAN, 'default' => false], 'startSearch' => ['type' => API_BOOLEAN, 'default' => false], 'excludeSearch' => ['type' => API_BOOLEAN, 'default' => false], 'searchWildcardsEnabled' => ['type' => API_BOOLEAN, 'default' => false], // output 'output' => ['type' => API_OUTPUT, 'in' => implode(',', self::OUTPUT_FIELDS), 'default' => API_OUTPUT_EXTEND], 'countOutput' => ['type' => API_BOOLEAN, 'default' => false], 'selectProxies' => ['type' => API_OUTPUT, 'flags' => API_ALLOW_NULL | API_ALLOW_COUNT, 'in' => implode(',', array_diff(CProxy::OUTPUT_FIELDS, ['proxy_groupid'])), 'default' => null], // sort and limit 'sortfield' => ['type' => API_STRINGS_UTF8, 'flags' => API_NORMALIZE, 'in' => implode(',', $this->sortColumns), 'uniq' => true, 'default' => []], 'sortorder' => ['type' => API_SORTORDER, 'default' => []], 'limit' => ['type' => API_INT32, 'flags' => API_ALLOW_NULL, 'in' => '1:'.ZBX_MAX_INT32, 'default' => null], // flags 'editable' => ['type' => API_BOOLEAN, 'default' => false], 'preservekeys' => ['type' => API_BOOLEAN, 'default' => false], 'nopermissions' => ['type' => API_BOOLEAN, 'default' => false] ]]; if (!CApiInputValidator::validate($api_input_rules, $options, '/', $error)) { self::exception(ZBX_API_ERROR_PARAMETERS, $error); } // editable + PERMISSION CHECK if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN && !$options['nopermissions']) { $permission = $options['editable'] ? PERM_READ_WRITE : PERM_READ; if ($permission == PERM_READ_WRITE) { return $options['countOutput'] ? '0' : []; } } if ($options['output'] === API_OUTPUT_EXTEND) { $options['output'] = self::OUTPUT_FIELDS; } $resource = DBselect($this->createSelectQuery($this->tableName, $options), $options['limit']); $db_proxy_groups = []; while ($row = DBfetch($resource)) { if ($options['countOutput']) { return $row['rowscount']; } $db_proxy_groups[$row['proxy_groupid']] = $row; } if ($db_proxy_groups) { $db_proxy_groups = $this->addRelatedObjects($options, $db_proxy_groups); $db_proxy_groups = $this->unsetExtraFields($db_proxy_groups, ['proxy_groupid'], $options['output']); if (!$options['preservekeys']) { $db_proxy_groups = array_values($db_proxy_groups); } } return $db_proxy_groups; } protected function applyQueryFilterOptions($table_name, $table_alias, array $options, array $sql_parts): array { $sql_parts = parent::applyQueryFilterOptions($table_name, $table_alias, $options, $sql_parts); // proxyids if ($options['proxyids'] !== null) { $sql_parts['from']['proxy'] = 'proxy p'; $sql_parts['where'][] = dbConditionId('p.proxyid', $options['proxyids']); $sql_parts['where']['pgp'] = 'pg.proxy_groupid=p.proxy_groupid'; } if ($options['filter'] !== null && array_key_exists('state', $options['filter']) && $options['filter']['state'] !== null) { $this->dbFilter('proxy_group_rtdata pgr', ['filter' => ['state' => $options['filter']['state']]] + $options, $sql_parts ); $sql_parts['left_join']['proxy_group_rtdata'] = [ 'alias' => 'pgr', 'table' => 'proxy_group_rtdata', 'using' => 'proxy_groupid' ]; $sql_parts['left_table'] = ['alias' => $this->tableAlias, 'table' => $this->tableName]; } return $sql_parts; } protected function applyQueryOutputOptions($table_name, $table_alias, array $options, array $sql_parts): array { $sql_parts = parent::applyQueryOutputOptions($table_name, $table_alias, $options, $sql_parts); if (!$options['countOutput'] && $this->outputIsRequested('state', $options['output'])) { $sql_parts = $this->addQuerySelect('pgr.state', $sql_parts); $sql_parts['left_join']['proxy_group_rtdata'] = [ 'alias' => 'pgr', 'table' => 'proxy_group_rtdata', 'using' => 'proxy_groupid' ]; $sql_parts['left_table'] = ['alias' => $this->tableAlias, 'table' => $this->tableName]; } return $sql_parts; } /** * @param array $options * @param array $result * * @return array */ protected function addRelatedObjects(array $options, array $result): array { $result = parent::addRelatedObjects($options, $result); $this->addRelatedProxies($options, $result); return $result; } private function addRelatedProxies(array $options, array &$result): void { if ($options['selectProxies'] === null) { return; } if ($options['selectProxies'] !== API_OUTPUT_COUNT) { foreach ($result as &$row) { $row['proxies'] = []; } unset($row); $db_proxies = API::Proxy()->get([ 'output' => $this->outputExtend($options['selectProxies'], ['proxy_groupid']), 'proxy_groupids' => array_keys($result) ]); foreach ($db_proxies as $db_proxy) { $result[$db_proxy['proxy_groupid']]['proxies'][] = array_diff_key($db_proxy, array_flip(['proxy_groupid'])); } } else { $db_proxies = DBFetchArrayAssoc(DBselect( 'SELECT p.proxy_groupid,COUNT(p.proxyid) AS rowscount'. ' FROM proxy p'. ' WHERE '.dbConditionId('p.proxy_groupid', array_keys($result)). ' GROUP BY p.proxy_groupid' ), 'proxy_groupid'); foreach ($result as $proxy_groupid => $proxy_group) { $result[$proxy_groupid]['proxies'] = array_key_exists($proxy_groupid, $db_proxies) ? $db_proxies[$proxy_groupid]['rowscount'] : '0'; } } } /** * @param array $proxy_groups * * @throws APIException * * @return array */ public function create(array $proxy_groups): array { if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) { self::exception(ZBX_API_ERROR_PERMISSIONS, _s('No permissions to call "%1$s.%2$s".', 'proxygroup', __FUNCTION__) ); } self::validateCreate($proxy_groups); $proxy_groupids = DB::insert('proxy_group', $proxy_groups); $proxy_group_rtdata = []; foreach ($proxy_groups as $index => &$proxy_group) { $proxy_group['proxy_groupid'] = $proxy_groupids[$index]; $proxy_group_rtdata[] = ['proxy_groupid' => $proxy_groupids[$index]]; } unset($proxy_group); DB::insert('proxy_group_rtdata', $proxy_group_rtdata, false); self::addAuditLog(CAudit::ACTION_ADD, CAudit::RESOURCE_PROXY_GROUP, $proxy_groups); return ['proxy_groupids' => $proxy_groupids]; } /** * @param array $proxy_groups * * @throws APIException */ private static function validateCreate(array &$proxy_groups): void { $api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['name']], 'fields' => [ 'name' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('proxy_group', 'name')], 'failover_delay' => ['type' => API_TIME_UNIT, 'flags' => API_NOT_EMPTY | API_ALLOW_USER_MACRO, 'in' => '10:'.(15 * SEC_PER_MIN), 'length' => DB::getFieldLength('proxy_group', 'failover_delay')], 'min_online' => ['type' => API_NUMBER, 'in' => '1:1000', 'length' => DB::getFieldLength('proxy_group', 'min_online')], 'description' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('proxy_group', 'description')] ]]; if (!CApiInputValidator::validate($api_input_rules, $proxy_groups, '/', $error)) { self::exception(ZBX_API_ERROR_PARAMETERS, $error); } self::checkDuplicates($proxy_groups); } /** * @param array $proxy_groups * * @throws APIException * * @return array */ public function update(array $proxy_groups): array { if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) { self::exception(ZBX_API_ERROR_PERMISSIONS, _s('No permissions to call "%1$s.%2$s".', 'proxygroup', __FUNCTION__) ); } $this->validateUpdate($proxy_groups, $db_proxy_groups); $upd_proxy_groups = []; foreach ($proxy_groups as $proxy_group) { $upd_proxy_group = DB::getUpdatedValues('proxy_group', $proxy_group, $db_proxy_groups[$proxy_group['proxy_groupid']] ); if ($upd_proxy_group) { $upd_proxy_groups[] = [ 'values' => $upd_proxy_group, 'where' => ['proxy_groupid' => $proxy_group['proxy_groupid']] ]; } } if ($upd_proxy_groups) { DB::update('proxy_group', $upd_proxy_groups); } self::addAuditLog(CAudit::ACTION_UPDATE, CAudit::RESOURCE_PROXY_GROUP, $proxy_groups, $db_proxy_groups); return ['proxy_groupids' => array_column($proxy_groups, 'proxy_groupid')]; } /** * @param array $proxy_groups * @param array|null $db_proxy_groups * * @throws APIException */ private function validateUpdate(array &$proxy_groups, ?array &$db_proxy_groups): void { $api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE | API_ALLOW_UNEXPECTED, 'uniq' => [['proxy_groupid']], 'fields' => [ 'proxy_groupid' => ['type' => API_ID, 'flags' => API_REQUIRED] ]]; if (!CApiInputValidator::validate($api_input_rules, $proxy_groups, '/', $error)) { self::exception(ZBX_API_ERROR_PARAMETERS, $error); } $db_proxy_groups = DB::select('proxy_group', [ 'output' => ['proxy_groupid', 'name', 'failover_delay', 'min_online', 'description'], 'proxy_groupids' => array_column($proxy_groups, 'proxy_groupid'), 'preservekeys' => true ]); if (count($db_proxy_groups) != count($proxy_groups)) { self::exception(ZBX_API_ERROR_PARAMETERS, _('No permissions to referred object or it does not exist!')); } $api_input_rules = ['type' => API_OBJECTS, 'uniq' => [['name']], 'fields' => [ 'proxy_groupid' => ['type' => API_ANY], 'name' => ['type' => API_STRING_UTF8, 'flags' => API_NOT_EMPTY, 'length' => DB::getFieldLength('proxy_group', 'name')], 'failover_delay' => ['type' => API_TIME_UNIT, 'flags' => API_NOT_EMPTY | API_ALLOW_USER_MACRO, 'in' => '10:'.(15 * SEC_PER_MIN), 'length' => DB::getFieldLength('proxy_group', 'failover_delay')], 'min_online' => ['type' => API_NUMBER, 'in' => '1:1000', 'length' => DB::getFieldLength('proxy_group', 'min_online')], 'description' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('proxy_group', 'description')] ]]; if (!CApiInputValidator::validate($api_input_rules, $proxy_groups, '/', $error)) { self::exception(ZBX_API_ERROR_PARAMETERS, $error); } self::checkDuplicates($proxy_groups, $db_proxy_groups); } /** * @param array $proxy_groups * @param array|null $db_proxy_groups * * @throws APIException */ private static function checkDuplicates(array $proxy_groups, ?array $db_proxy_groups = null): void { $names = []; foreach ($proxy_groups as $proxy_group) { if (!array_key_exists('name', $proxy_group)) { continue; } if ($db_proxy_groups === null || $proxy_group['name'] !== $db_proxy_groups[$proxy_group['proxy_groupid']]['name']) { $names[] = $proxy_group['name']; } } if (!$names) { return; } $duplicates = DB::select('proxy_group', [ 'output' => ['name'], 'filter' => ['name' => $names], 'limit' => 1 ]); if ($duplicates) { self::exception(ZBX_API_ERROR_PARAMETERS, _s('Proxy group "%1$s" already exists.', $duplicates[0]['name'])); } } /** * @param array $proxy_groupids * * @throws APIException * * @return array */ public function delete(array $proxy_groupids): array { if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) { self::exception(ZBX_API_ERROR_PERMISSIONS, _s('No permissions to call "%1$s.%2$s".', 'proxygroup', __FUNCTION__) ); } self::validateDelete($proxy_groupids, $db_proxy_groups); DB::delete('proxy_group_rtdata', ['proxy_groupid' => $proxy_groupids]); DB::delete('proxy_group', ['proxy_groupid' => $proxy_groupids]); self::addAuditLog(CAudit::ACTION_DELETE, CAudit::RESOURCE_PROXY_GROUP, $db_proxy_groups); return ['proxy_groupids' => $proxy_groupids]; } /** * @param array $proxy_groupids * @param array|null $db_proxy_groups * * @throws APIException */ private static function validateDelete(array $proxy_groupids, ?array &$db_proxy_groups): void { $api_input_rules = ['type' => API_IDS, 'flags' => API_NOT_EMPTY, 'uniq' => true]; if (!CApiInputValidator::validate($api_input_rules, $proxy_groupids, '/', $error)) { self::exception(ZBX_API_ERROR_PARAMETERS, $error); } $db_proxy_groups = DB::select('proxy_group', [ 'output' => ['proxy_groupid', 'name'], 'proxy_groupids' => $proxy_groupids, 'preservekeys' => true ]); if (count($db_proxy_groups) != count($proxy_groupids)) { self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!')); } self::checkUsedInProxies($db_proxy_groups); self::checkUsedInHosts($db_proxy_groups); } /** * @param array $proxy_groups * * @throws APIException */ private static function checkUsedInProxies(array $proxy_groups): void { $db_proxies = DB::select('proxy', [ 'output' => ['proxy_groupid', 'name'], 'filter' => ['proxy_groupid' => array_keys($proxy_groups)], 'limit' => 1 ]); if ($db_proxies) { self::exception(ZBX_API_ERROR_PARAMETERS, _s('Proxy group "%1$s" is used by proxy "%2$s".', $proxy_groups[$db_proxies[0]['proxy_groupid']]['name'], $db_proxies[0]['name'] )); } } /** * @param array $proxy_groups * * @throws APIException */ private static function checkUsedInHosts(array $proxy_groups): void { $db_hosts = DB::select('hosts', [ 'output' => ['proxy_groupid', 'name'], 'filter' => ['proxy_groupid' => array_keys($proxy_groups)], 'limit' => 1 ]); if ($db_hosts) { self::exception(ZBX_API_ERROR_PARAMETERS, _s('Host "%1$s" is monitored by proxy group "%2$s".', $db_hosts[0]['name'], $proxy_groups[$db_hosts[0]['proxy_groupid']]['name'] )); } } }