1<?php 2/* 3** Zabbix 4** Copyright (C) 2001-2021 Zabbix SIA 5** 6** This program is free software; you can redistribute it and/or modify 7** it under the terms of the GNU General Public License as published by 8** the Free Software Foundation; either version 2 of the License, or 9** (at your option) any later version. 10** 11** This program is distributed in the hope that it will be useful, 12** but WITHOUT ANY WARRANTY; without even the implied warranty of 13** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14** GNU General Public License for more details. 15** 16** You should have received a copy of the GNU General Public License 17** along with this program; if not, write to the Free Software 18** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19**/ 20 21 22/** 23 * Class containing methods for operations with user macro. 24 */ 25class CUserMacro extends CApiService { 26 27 public const ACCESS_RULES = [ 28 'get' => ['min_user_type' => USER_TYPE_ZABBIX_USER], 29 'create' => ['min_user_type' => USER_TYPE_ZABBIX_ADMIN], 30 'update' => ['min_user_type' => USER_TYPE_ZABBIX_ADMIN], 31 'delete' => ['min_user_type' => USER_TYPE_ZABBIX_ADMIN], 32 'createglobal' => ['min_user_type' => USER_TYPE_SUPER_ADMIN], 33 'updateglobal' => ['min_user_type' => USER_TYPE_SUPER_ADMIN], 34 'deleteglobal' => ['min_user_type' => USER_TYPE_SUPER_ADMIN] 35 ]; 36 37 protected $tableName = 'hostmacro'; 38 protected $tableAlias = 'hm'; 39 protected $sortColumns = ['macro']; 40 41 /** 42 * Get UserMacros data. 43 * 44 * @param array $options 45 * @param array $options['groupids'] usermacrosgroup ids 46 * @param array $options['hostids'] host ids 47 * @param array $options['hostmacroids'] host macros ids 48 * @param array $options['globalmacroids'] global macros ids 49 * @param array $options['templateids'] template ids 50 * @param boolean $options['globalmacro'] only global macros 51 * @param boolean $options['selectGroups'] select groups 52 * @param boolean $options['selectHosts'] select hosts 53 * @param boolean $options['selectTemplates'] select templates 54 * 55 * @return array|boolean UserMacros data as array or false if error 56 */ 57 public function get($options = []) { 58 $result = []; 59 $userid = self::$userData['userid']; 60 61 $sqlParts = [ 62 'select' => ['macros' => 'hm.hostmacroid'], 63 'from' => ['hostmacro hm'], 64 'where' => [], 65 'order' => [], 66 'limit' => null 67 ]; 68 69 $sqlPartsGlobal = [ 70 'select' => ['macros' => 'gm.globalmacroid'], 71 'from' => ['globalmacro gm'], 72 'where' => [], 73 'order' => [], 74 'limit' => null 75 ]; 76 77 $defOptions = [ 78 'groupids' => null, 79 'hostids' => null, 80 'hostmacroids' => null, 81 'globalmacroids' => null, 82 'templateids' => null, 83 'globalmacro' => null, 84 'inherited' => null, 85 'editable' => false, 86 'nopermissions' => null, 87 // filter 88 'filter' => null, 89 'search' => null, 90 'searchByAny' => null, 91 'startSearch' => false, 92 'excludeSearch' => false, 93 'searchWildcardsEnabled' => null, 94 // output 95 'output' => API_OUTPUT_EXTEND, 96 'selectGroups' => null, 97 'selectHosts' => null, 98 'selectTemplates' => null, 99 'countOutput' => false, 100 'preservekeys' => false, 101 'sortfield' => '', 102 'sortorder' => '', 103 'limit' => null 104 ]; 105 $options = zbx_array_merge($defOptions, $options); 106 107 // editable + PERMISSION CHECK 108 if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN && !$options['nopermissions']) { 109 if ($options['editable'] && !is_null($options['globalmacro'])) { 110 return []; 111 } 112 else { 113 $permission = $options['editable'] ? PERM_READ_WRITE : PERM_READ; 114 115 $userGroups = getUserGroupsByUserId($userid); 116 117 $sqlParts['where'][] = 'EXISTS ('. 118 'SELECT NULL'. 119 ' FROM hosts_groups hgg'. 120 ' JOIN rights r'. 121 ' ON r.id=hgg.groupid'. 122 ' AND '.dbConditionInt('r.groupid', $userGroups). 123 ' WHERE hm.hostid=hgg.hostid'. 124 ' GROUP BY hgg.hostid'. 125 ' HAVING MIN(r.permission)>'.PERM_DENY. 126 ' AND MAX(r.permission)>='.zbx_dbstr($permission). 127 ')'; 128 } 129 } 130 131 // global macro 132 if (!is_null($options['globalmacro'])) { 133 $options['groupids'] = null; 134 $options['hostmacroids'] = null; 135 $options['triggerids'] = null; 136 $options['hostids'] = null; 137 $options['itemids'] = null; 138 $options['selectGroups'] = null; 139 $options['selectTemplates'] = null; 140 $options['selectHosts'] = null; 141 $options['inherited'] = null; 142 } 143 144 // globalmacroids 145 if (!is_null($options['globalmacroids'])) { 146 zbx_value2array($options['globalmacroids']); 147 $sqlPartsGlobal['where'][] = dbConditionInt('gm.globalmacroid', $options['globalmacroids']); 148 } 149 150 // hostmacroids 151 if (!is_null($options['hostmacroids'])) { 152 zbx_value2array($options['hostmacroids']); 153 $sqlParts['where'][] = dbConditionInt('hm.hostmacroid', $options['hostmacroids']); 154 } 155 156 // inherited 157 if (!is_null($options['inherited'])) { 158 $sqlParts['from']['hosts'] = 'hosts h'; 159 $sqlParts['where'][] = $options['inherited'] ? 'h.templateid IS NOT NULL' : 'h.templateid IS NULL'; 160 $sqlParts['where']['hmh'] = 'hm.hostid=h.hostid'; 161 } 162 163 // groupids 164 if (!is_null($options['groupids'])) { 165 zbx_value2array($options['groupids']); 166 167 $sqlParts['from']['hosts_groups'] = 'hosts_groups hg'; 168 $sqlParts['where'][] = dbConditionInt('hg.groupid', $options['groupids']); 169 $sqlParts['where']['hgh'] = 'hg.hostid=hm.hostid'; 170 } 171 172 // hostids 173 if (!is_null($options['hostids'])) { 174 zbx_value2array($options['hostids']); 175 176 $sqlParts['where'][] = dbConditionInt('hm.hostid', $options['hostids']); 177 } 178 179 // templateids 180 if (!is_null($options['templateids'])) { 181 zbx_value2array($options['templateids']); 182 183 $sqlParts['from']['macros_templates'] = 'hosts_templates ht'; 184 $sqlParts['where'][] = dbConditionInt('ht.templateid', $options['templateids']); 185 $sqlParts['where']['hht'] = 'hm.hostid=ht.hostid'; 186 } 187 188 // sorting 189 $sqlParts = $this->applyQuerySortOptions('hostmacro', 'hm', $options, $sqlParts); 190 $sqlPartsGlobal = $this->applyQuerySortOptions('globalmacro', 'gm', $options, $sqlPartsGlobal); 191 192 // limit 193 if (zbx_ctype_digit($options['limit']) && $options['limit']) { 194 $sqlParts['limit'] = $options['limit']; 195 $sqlPartsGlobal['limit'] = $options['limit']; 196 } 197 198 // init GLOBALS 199 if (!is_null($options['globalmacro'])) { 200 $sqlPartsGlobal = $this->applyQueryFilterOptions('globalmacro', 'gm', $options, $sqlPartsGlobal); 201 $sqlPartsGlobal = $this->applyQueryOutputOptions('globalmacro', 'gm', $options, $sqlPartsGlobal); 202 $res = DBselect(self::createSelectQueryFromParts($sqlPartsGlobal), $sqlPartsGlobal['limit']); 203 while ($macro = DBfetch($res)) { 204 if ($options['countOutput']) { 205 $result = $macro['rowscount']; 206 } 207 else { 208 $result[$macro['globalmacroid']] = $macro; 209 } 210 } 211 } 212 // init HOSTS 213 else { 214 $sqlParts = $this->applyQueryFilterOptions('hostmacro', 'hm', $options, $sqlParts); 215 $sqlParts = $this->applyQueryOutputOptions('hostmacro', 'hm', $options, $sqlParts); 216 $res = DBselect(self::createSelectQueryFromParts($sqlParts), $sqlParts['limit']); 217 while ($macro = DBfetch($res)) { 218 if ($options['countOutput']) { 219 $result = $macro['rowscount']; 220 } 221 else { 222 $result[$macro['hostmacroid']] = $macro; 223 } 224 } 225 } 226 227 if ($options['countOutput']) { 228 return $result; 229 } 230 231 if ($result) { 232 $result = $this->addRelatedObjects($options, $result); 233 $result = $this->unsetExtraFields($result, ['hostid', 'type'], $options['output']); 234 } 235 236 // removing keys (hash -> array) 237 if (!$options['preservekeys']) { 238 $result = zbx_cleanHashes($result); 239 } 240 241 return $result; 242 } 243 244 /** 245 * @param array $globalmacros 246 * 247 * @return array 248 */ 249 public function createGlobal(array $globalmacros) { 250 $this->validateCreateGlobal($globalmacros); 251 252 $globalmacroids = DB::insertBatch('globalmacro', $globalmacros); 253 254 foreach ($globalmacros as $index => &$globalmacro) { 255 $globalmacro['globalmacroid'] = $globalmacroids[$index]; 256 } 257 unset($globalmacro); 258 259 $this->addAuditBulk(AUDIT_ACTION_ADD, AUDIT_RESOURCE_MACRO, $globalmacros); 260 261 return ['globalmacroids' => $globalmacroids]; 262 } 263 264 /** 265 * @param array $globalmacros 266 * 267 * @throws APIException if the input is invalid. 268 */ 269 private function validateCreateGlobal(array &$globalmacros) { 270 if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) { 271 self::exception(ZBX_API_ERROR_PERMISSIONS, _('You do not have permission to perform this operation.')); 272 } 273 274 $api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['macro']], 'fields' => [ 275 'macro' => ['type' => API_USER_MACRO, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength('globalmacro', 'macro')], 276 'type' => ['type' => API_INT32, 'in' => implode(',', [ZBX_MACRO_TYPE_TEXT, ZBX_MACRO_TYPE_SECRET, ZBX_MACRO_TYPE_VAULT]), 'default' => ZBX_MACRO_TYPE_TEXT], 277 'value' => ['type' => API_MULTIPLE, 'flags' => API_REQUIRED, 'rules' => [ 278 ['if' => ['field' => 'type', 'in' => implode(',', [ZBX_MACRO_TYPE_TEXT, ZBX_MACRO_TYPE_SECRET])], 'type' => API_STRING_UTF8, 'length' => DB::getFieldLength('globalmacro', 'value')], 279 ['if' => ['field' => 'type', 'in' => implode(',', [ZBX_MACRO_TYPE_VAULT])], 'type' => API_VAULT_SECRET, 'length' => DB::getFieldLength('globalmacro', 'value')] 280 ]], 281 'description' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('globalmacro', 'description')] 282 ]]; 283 284 if (!CApiInputValidator::validate($api_input_rules, $globalmacros, '/', $error)) { 285 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 286 } 287 288 $this->checkDuplicates($globalmacros); 289 } 290 291 /** 292 * @param array $globalmacros 293 * 294 * @return array 295 */ 296 public function updateGlobal(array $globalmacros) { 297 $this->validateUpdateGlobal($globalmacros, $db_globalmacros); 298 299 $upd_globalmacros = []; 300 301 foreach ($globalmacros as $globalmacro) { 302 $db_globalmacro = $db_globalmacros[$globalmacro['globalmacroid']]; 303 304 $upd_globalmacro = DB::getUpdatedValues('globalmacro', $globalmacro, $db_globalmacro); 305 306 if ($upd_globalmacro) { 307 $upd_globalmacros[] = [ 308 'values'=> $upd_globalmacro, 309 'where'=> ['globalmacroid' => $globalmacro['globalmacroid']] 310 ]; 311 } 312 } 313 314 if ($upd_globalmacros) { 315 DB::update('globalmacro', $upd_globalmacros); 316 } 317 318 $this->addAuditBulk(AUDIT_ACTION_UPDATE, AUDIT_RESOURCE_MACRO, $globalmacros, $db_globalmacros); 319 320 return ['globalmacroids' => array_column($globalmacros, 'globalmacroid')]; 321 } 322 323 /** 324 * @param array $globalmacros 325 * @param array $db_globalmacros 326 * 327 * @throws APIException if the input is invalid 328 */ 329 private function validateUpdateGlobal(array &$globalmacros, array &$db_globalmacros = null) { 330 if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) { 331 self::exception(ZBX_API_ERROR_PERMISSIONS, _('You do not have permission to perform this operation.')); 332 } 333 334 $api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['globalmacroid'], ['macro']], 'fields' => [ 335 'globalmacroid' => ['type' => API_ID, 'flags' => API_REQUIRED], 336 'macro' => ['type' => API_USER_MACRO, 'length' => DB::getFieldLength('globalmacro', 'macro')], 337 'type' => ['type' => API_INT32, 'in' => implode(',', [ZBX_MACRO_TYPE_TEXT, ZBX_MACRO_TYPE_SECRET, ZBX_MACRO_TYPE_VAULT])], 338 'value' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('globalmacro', 'value')], 339 'description' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('globalmacro', 'description')] 340 ]]; 341 342 if (!CApiInputValidator::validate($api_input_rules, $globalmacros, '/', $error)) { 343 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 344 } 345 346 $db_globalmacros = DB::select('globalmacro', [ 347 'output' => ['globalmacroid', 'macro', 'value', 'description', 'type'], 348 'globalmacroids' => array_column($globalmacros, 'globalmacroid'), 349 'preservekeys' => true 350 ]); 351 352 if (count($globalmacros) != count($db_globalmacros)) { 353 self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!')); 354 } 355 356 $globalmacros = $this->extendObjectsByKey($globalmacros, $db_globalmacros, 'globalmacroid', ['type']); 357 358 foreach ($globalmacros as $index => &$globalmacro) { 359 $db_globalmacro = $db_globalmacros[$globalmacro['globalmacroid']]; 360 361 if ($globalmacro['type'] != $db_globalmacro['type']) { 362 if ($db_globalmacro['type'] == ZBX_MACRO_TYPE_SECRET) { 363 $globalmacro += ['value' => '']; 364 } 365 366 if ($globalmacro['type'] == ZBX_MACRO_TYPE_VAULT) { 367 $globalmacro += ['value' => $db_globalmacro['value']]; 368 } 369 } 370 371 if (array_key_exists('value', $globalmacro) && $globalmacro['type'] == ZBX_MACRO_TYPE_VAULT) { 372 if (!CApiInputValidator::validate(['type' => API_VAULT_SECRET], $globalmacro['value'], 373 '/'.($index + 1).'/value', $error)) { 374 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 375 } 376 } 377 } 378 unset($globalmacro); 379 380 $this->checkDuplicates($globalmacros, $db_globalmacros); 381 } 382 383 /** 384 * Check for duplicated macros. 385 * 386 * @param array $globalmacros 387 * @param string $globalmacros[]['globalmacroid'] (optional if $db_globalmacros is null) 388 * @param string $globalmacros[]['macro'] (optional if $db_globalmacros is not null) 389 * @param array|null $db_globalmacros 390 * 391 * @throws APIException if macros already exists. 392 */ 393 private function checkDuplicates(array $globalmacros, array $db_globalmacros = null): void { 394 $macros = []; 395 396 foreach ($globalmacros as $globalmacro) { 397 if ($db_globalmacros === null || (array_key_exists('macro', $globalmacro) 398 && CApiInputValidator::trimMacro($globalmacro['macro']) 399 !== CApiInputValidator::trimMacro($db_globalmacros[$globalmacro['globalmacroid']]['macro']))) { 400 $macros[] = $globalmacro['macro']; 401 } 402 } 403 404 if (!$macros) { 405 return; 406 } 407 408 $db_globalmacros = DB::select('globalmacro', [ 409 'output' => ['macro'] 410 ]); 411 412 $db_macros = []; 413 414 foreach ($db_globalmacros as $db_globalmacro) { 415 $db_macros[CApiInputValidator::trimMacro($db_globalmacro['macro'])] = true; 416 } 417 418 foreach ($macros as $macro) { 419 if (array_key_exists(CApiInputValidator::trimMacro($macro), $db_macros)) { 420 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Macro "%1$s" already exists.', $macro)); 421 } 422 } 423 } 424 425 /** 426 * @param array $globalmacroids 427 * 428 * @return array 429 */ 430 public function deleteGlobal(array $globalmacroids) { 431 $this->validateDeleteGlobal($globalmacroids, $db_globalmacros); 432 433 DB::delete('globalmacro', ['globalmacroid' => $globalmacroids]); 434 435 $this->addAuditBulk(AUDIT_ACTION_DELETE, AUDIT_RESOURCE_MACRO, $db_globalmacros); 436 437 return ['globalmacroids' => $globalmacroids]; 438 } 439 440 /** 441 * @param array $globalmacroids 442 * 443 * @throws APIException if the input is invalid. 444 */ 445 private function validateDeleteGlobal(array &$globalmacroids, array &$db_globalmacros = null) { 446 if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) { 447 self::exception(ZBX_API_ERROR_PERMISSIONS, _('You do not have permission to perform this operation.')); 448 } 449 450 $api_input_rules = ['type' => API_IDS, 'flags' => API_NOT_EMPTY, 'uniq' => true]; 451 452 if (!CApiInputValidator::validate($api_input_rules, $globalmacroids, '/', $error)) { 453 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 454 } 455 456 $db_globalmacros = DB::select('globalmacro', [ 457 'output' => ['globalmacroid', 'macro'], 458 'globalmacroids' => $globalmacroids, 459 'preservekeys' => true 460 ]); 461 462 if (count($globalmacroids) != count($db_globalmacros)) { 463 self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!')); 464 } 465 } 466 467 /** 468 * @param array $hostmacros 469 * 470 * @throws APIException if the input is invalid. 471 */ 472 protected function validateCreate(array &$hostmacros) { 473 $api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['hostid', 'macro']], 'fields' => [ 474 'hostid' => ['type' => API_ID, 'flags' => API_REQUIRED], 475 'macro' => ['type' => API_USER_MACRO, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength('hostmacro', 'macro')], 476 'type' => ['type' => API_INT32, 'in' => implode(',', [ZBX_MACRO_TYPE_TEXT, ZBX_MACRO_TYPE_SECRET, ZBX_MACRO_TYPE_VAULT]), 'default' => ZBX_MACRO_TYPE_TEXT], 477 'value' => ['type' => API_MULTIPLE, 'flags' => API_REQUIRED, 'rules' => [ 478 ['if' => ['field' => 'type', 'in' => implode(',', [ZBX_MACRO_TYPE_TEXT, ZBX_MACRO_TYPE_SECRET])], 'type' => API_STRING_UTF8, 'length' => DB::getFieldLength('hostmacro', 'value')], 479 ['if' => ['field' => 'type', 'in' => implode(',', [ZBX_MACRO_TYPE_VAULT])], 'type' => API_VAULT_SECRET, 'length' => DB::getFieldLength('hostmacro', 'value')] 480 ]], 481 'description' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('hostmacro', 'description')] 482 ]]; 483 484 if (!CApiInputValidator::validate($api_input_rules, $hostmacros, '/', $error)) { 485 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 486 } 487 488 $this->checkHostPermissions(array_unique(array_column($hostmacros, 'hostid'))); 489 $this->checkHostDuplicates($hostmacros); 490 } 491 492 /** 493 * @param array $hostmacros 494 * 495 * @return array 496 */ 497 public function create(array $hostmacros) { 498 $this->validateCreate($hostmacros); 499 500 $this->createReal($hostmacros); 501 502 if ($tpl_hostmacros = $this->getMacrosToInherit($hostmacros)) { 503 $this->inherit($tpl_hostmacros); 504 } 505 506 return ['hostmacroids' => array_column($hostmacros, 'hostmacroid')]; 507 } 508 509 /** 510 * Inserts hostmacros records into the database. 511 * 512 * @param array $hostmacros 513 */ 514 private function createReal(array &$hostmacros): void { 515 $hostmacroids = DB::insert('hostmacro', $hostmacros); 516 517 foreach ($hostmacros as $index => &$hostmacro) { 518 $hostmacro['hostmacroid'] = $hostmacroids[$index]; 519 } 520 unset($hostmacro); 521 } 522 523 /** 524 * @param array $hostmacros 525 * 526 * @throws APIException if the input is invalid. 527 */ 528 protected function validateUpdate(array &$hostmacros, array &$db_hostmacros = null) { 529 $api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['hostmacroid']], 'fields' => [ 530 'hostmacroid' => ['type' => API_ID, 'flags' => API_REQUIRED], 531 'macro' => ['type' => API_USER_MACRO, 'length' => DB::getFieldLength('hostmacro', 'macro')], 532 'type' => ['type' => API_INT32, 'in' => implode(',', [ZBX_MACRO_TYPE_TEXT, ZBX_MACRO_TYPE_SECRET, ZBX_MACRO_TYPE_VAULT])], 533 'value' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('hostmacro', 'value')], 534 'description' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('hostmacro', 'description')] 535 ]]; 536 537 if (!CApiInputValidator::validate($api_input_rules, $hostmacros, '/', $error)) { 538 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 539 } 540 541 $db_hostmacros = $this->get([ 542 'output' => ['hostmacroid', 'hostid', 'macro', 'type', 'description'], 543 'hostmacroids' => array_column($hostmacros, 'hostmacroid'), 544 'editable' => true, 545 'inherited' => false, 546 'preservekeys' => true 547 ]); 548 549 if (count($hostmacros) != count($db_hostmacros)) { 550 self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!')); 551 } 552 553 // CUserMacro::get does not return secret values. Loading directly from the database. 554 $options = [ 555 'output' => ['hostmacroid', 'value'], 556 'hostmacroids' => array_keys($db_hostmacros) 557 ]; 558 $db_hostmacro_values = DBselect(DB::makeSql('hostmacro', $options)); 559 560 while ($db_hostmacro_value = DBfetch($db_hostmacro_values)) { 561 $db_hostmacros[$db_hostmacro_value['hostmacroid']] += $db_hostmacro_value; 562 } 563 564 $hostmacros = $this->extendObjectsByKey($hostmacros, $db_hostmacros, 'hostmacroid', ['hostid', 'type']); 565 566 foreach ($hostmacros as $index => &$hostmacro) { 567 $db_hostmacro = $db_hostmacros[$hostmacro['hostmacroid']]; 568 569 if ($hostmacro['type'] != $db_hostmacro['type']) { 570 if ($db_hostmacro['type'] == ZBX_MACRO_TYPE_SECRET) { 571 $hostmacro += ['value' => '']; 572 } 573 574 if ($hostmacro['type'] == ZBX_MACRO_TYPE_VAULT) { 575 $hostmacro += ['value' => $db_hostmacro['value']]; 576 } 577 } 578 579 if (array_key_exists('value', $hostmacro) && $hostmacro['type'] == ZBX_MACRO_TYPE_VAULT) { 580 if (!CApiInputValidator::validate(['type' => API_VAULT_SECRET], $hostmacro['value'], 581 '/'.($index + 1).'/value', $error)) { 582 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 583 } 584 } 585 } 586 unset($hostmacro); 587 588 $api_input_rules = ['type' => API_OBJECTS, 'uniq' => [['hostid', 'macro']], 'fields' => [ 589 'hostid' => ['type' => API_ID], 590 'macro' => ['type' => API_USER_MACRO] 591 ]]; 592 593 if (!CApiInputValidator::validateUniqueness($api_input_rules, $hostmacros, '/', $error)) { 594 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 595 } 596 597 $this->checkHostDuplicates($hostmacros, $db_hostmacros); 598 } 599 600 /** 601 * Checks if any of the given host macros already exist on the corresponding hosts. If the macros are updated and 602 * the "hostmacroid" field is set, the method will only fail, if a macro with a different hostmacroid exists. 603 * Assumes the "macro", "hostid" and "hostmacroid" fields are valid. 604 * 605 * @param array $hostmacros 606 * @param string $hostmacros[]['hostmacroid'] (optional if $db_hostmacros is null) 607 * @param string $hostmacros[]['hostid'] 608 * @param string $hostmacros[]['macro'] (optional if $db_hostmacros is not null) 609 * @param array|null $db_hostmacros 610 * 611 * @throws APIException if any of the given macros already exist. 612 */ 613 private function checkHostDuplicates(array $hostmacros, array $db_hostmacros = null) { 614 $macro_names = []; 615 $existing_macros = []; 616 617 // Parse each macro, get unique names and, if context exists, narrow down the search. 618 foreach ($hostmacros as $index => $hostmacro) { 619 if ($db_hostmacros !== null && (!array_key_exists('macro', $hostmacro) 620 || CApiInputValidator::trimMacro($hostmacro['macro']) 621 === CApiInputValidator::trimMacro($db_hostmacros[$hostmacro['hostmacroid']]['macro']))) { 622 unset($hostmacros[$index]); 623 624 continue; 625 } 626 627 $trimmed_macro = CApiInputValidator::trimMacro($hostmacro['macro']); 628 [$macro_name] = explode(':', $trimmed_macro, 2); 629 $macro_name = !isset($trimmed_macro[strlen($macro_name)]) ? '{$'.$macro_name : '{$'.$macro_name.':'; 630 631 $macro_names[$macro_name] = true; 632 $existing_macros[$hostmacro['hostid']] = []; 633 } 634 635 if (!$existing_macros) { 636 return; 637 } 638 639 $options = [ 640 'output' => ['hostmacroid', 'hostid', 'macro'], 641 'filter' => ['hostid' => array_keys($existing_macros)], 642 'search' => ['macro' => array_keys($macro_names)], 643 'searchByAny' => true, 644 'startSearch' => true 645 ]; 646 647 $db_hostmacros = DBselect(DB::makeSql('hostmacro', $options)); 648 649 // Collect existing unique macro names and their contexts for each host. 650 while ($db_hostmacro = DBfetch($db_hostmacros)) { 651 $trimmed_macro = CApiInputValidator::trimMacro($db_hostmacro['macro']); 652 653 $existing_macros[$db_hostmacro['hostid']][$trimmed_macro] = $db_hostmacro['hostmacroid']; 654 } 655 656 // Compare each macro name and context to existing one. 657 foreach ($hostmacros as $hostmacro) { 658 $hostid = $hostmacro['hostid']; 659 $trimmed_macro = CApiInputValidator::trimMacro($hostmacro['macro']); 660 661 if (array_key_exists($trimmed_macro, $existing_macros[$hostid])) { 662 $hosts = DB::select('hosts', [ 663 'output' => ['name'], 664 'hostids' => $hostid 665 ]); 666 667 self::exception(ZBX_API_ERROR_PARAMETERS, 668 _s('Macro "%1$s" already exists on "%2$s".', $hostmacro['macro'], $hosts[0]['name']) 669 ); 670 } 671 } 672 } 673 674 /** 675 * Update host macros. 676 * 677 * @param array $hostmacros an array of host macros 678 * 679 * @return array 680 */ 681 public function update($hostmacros) { 682 $this->validateUpdate($hostmacros, $db_hostmacros); 683 684 $this->updateReal($hostmacros, $db_hostmacros); 685 686 if ($tpl_hostmacros = $this->getMacrosToInherit($hostmacros, $db_hostmacros)) { 687 $this->inherit($tpl_hostmacros); 688 } 689 690 return ['hostmacroids' => array_column($hostmacros, 'hostmacroid')]; 691 } 692 693 /** 694 * Updates hostmacros records in the database. 695 * 696 * @param array $hostmacros 697 * @param array $db_hostmacros 698 */ 699 private function updateReal(array $hostmacros, array $db_hostmacros) { 700 $upd_hostmacros = []; 701 702 foreach ($hostmacros as $hostmacro) { 703 $db_hostmacro = $db_hostmacros[$hostmacro['hostmacroid']]; 704 705 $upd_hostmacro = DB::getUpdatedValues('hostmacro', $hostmacro, $db_hostmacro); 706 707 if ($upd_hostmacro) { 708 $upd_hostmacros[] = [ 709 'values' => $upd_hostmacro, 710 'where' => ['hostmacroid' => $hostmacro['hostmacroid']] 711 ]; 712 } 713 } 714 715 if ($upd_hostmacros) { 716 DB::update('hostmacro', $upd_hostmacros); 717 } 718 } 719 720 /** 721 * @param array $hostmacroids 722 * @param array $db_hostmacros 723 * 724 * @throws APIException if the input is invalid. 725 */ 726 protected function validateDelete(array &$hostmacroids, array &$db_hostmacros = null) { 727 $api_input_rules = ['type' => API_IDS, 'flags' => API_NOT_EMPTY, 'uniq' => true]; 728 729 if (!CApiInputValidator::validate($api_input_rules, $hostmacroids, '/', $error)) { 730 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 731 } 732 733 $db_hostmacros = $this->get([ 734 'output' => ['hostmacroid', 'hostid', 'macro'], 735 'hostmacroids' => $hostmacroids, 736 'editable' => true, 737 'inherited' => false, 738 'preservekeys' => true 739 ]); 740 741 if (count($hostmacroids) != count($db_hostmacros)) { 742 self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!')); 743 } 744 } 745 746 /** 747 * Remove macros from hosts. 748 * 749 * @param array $hostmacroids 750 * 751 * @return array 752 */ 753 public function delete(array $hostmacroids) { 754 $this->validateDelete($hostmacroids, $db_hostmacros); 755 756 DB::delete('hostmacro', ['hostmacroid' => $hostmacroids]); 757 758 if ($tpl_hostmacros = $this->getMacrosToInherit($db_hostmacros)) { 759 $this->inherit($tpl_hostmacros, true); 760 } 761 762 return ['hostmacroids' => $hostmacroids]; 763 } 764 765 /** 766 * Checks if the current user has access to the given hosts and templates. Assumes the "hostid" field is valid. 767 * 768 * @param array $hostids An array of host or template IDs. 769 * 770 * @throws APIException if the user doesn't have write permissions for the given hosts. 771 */ 772 protected function checkHostPermissions(array $hostids) { 773 $count = API::Host()->get([ 774 'countOutput' => true, 775 'hostids' => $hostids, 776 'filter' => [ 777 'flags' => ZBX_FLAG_DISCOVERY_NORMAL 778 ], 779 'editable' => true 780 ]); 781 782 if ($count == count($hostids)) { 783 return; 784 } 785 786 $count += API::Template()->get([ 787 'countOutput' => true, 788 'templateids' => $hostids, 789 'editable' => true 790 ]); 791 792 if ($count == count($hostids)) { 793 return; 794 } 795 796 $count += API::HostPrototype()->get([ 797 'countOutput' => true, 798 'hostids' => $hostids, 799 'editable' => true, 800 'inherited' => false 801 ]); 802 803 if ($count != count($hostids)) { 804 self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!')); 805 } 806 } 807 808 /** 809 * Forms the array of hostmacros, which are support the inheritance, from the passed hostmacros array. 810 * 811 * @param array $hostmacros 812 * @param string $hostmacros[]['hostmacroid'] 813 * @param string $hostmacros[]['hostid'] 814 * @param string $hostmacros[]['macro'] (optional) 815 * @param string $hostmacros[]['value'] (optional) 816 * @param string $hostmacros[]['description'] (optional) 817 * @param int $hostmacros[]['type'] (optional) 818 * @param array|null $db_hostmacros Used to set the old macro name in case when it was 819 * updated. 820 * @param string $db_hostmacros[<hostmacroid>]['macro'] 821 * 822 * @return array 823 */ 824 private function getMacrosToInherit(array $hostmacros, array $db_hostmacros = null): array { 825 $templated_host_prototypeids = DBfetchColumn(DBselect( 826 'SELECT hd.hostid'. 827 ' FROM host_discovery hd,items i,hosts h'. 828 ' WHERE hd.parent_itemid=i.itemid'. 829 ' AND i.hostid=h.hostid'. 830 ' AND h.status='.HOST_STATUS_TEMPLATE. 831 ' AND '.dbConditionId('hd.hostid', array_unique(array_column($hostmacros, 'hostid'))) 832 ), 'hostid'); 833 834 if (!$templated_host_prototypeids) { 835 return []; 836 } 837 838 foreach ($hostmacros as $index => &$hostmacro) { 839 if (!in_array($hostmacro['hostid'], $templated_host_prototypeids)) { 840 unset($hostmacros[$index]); 841 842 continue; 843 } 844 845 if ($db_hostmacros) { 846 $db_hostmacro = $db_hostmacros[$hostmacro['hostmacroid']]; 847 $hostmacro += array_intersect_key($db_hostmacro, array_flip(['macro'])); 848 849 if ($hostmacro['macro'] !== $db_hostmacro['macro']) { 850 $hostmacro['macro_old'] = $db_hostmacro['macro']; 851 } 852 } 853 } 854 unset($hostmacro); 855 856 return $hostmacros; 857 } 858 859 /** 860 * Prepares and returns an array of child hostmacros, inherited from hostmacros $tpl_hostmacros on the all hosts. 861 * 862 * @param array $tpl_hostmacros 863 * @param string $tpl_hostmacros[]['hostmacroid'] 864 * @param string $tpl_hostmacros[]['hostid'] 865 * @param string $tpl_hostmacros[]['macro'] 866 * @param string $tpl_hostmacros[]['value'] (optional) 867 * @param string $tpl_hostmacros[]['description'] (optional) 868 * @param int $tpl_hostmacros[]['type'] (optional) 869 * @param string $tpl_hostmacros[]['macro_old'] (optional) 870 * @param array $ins_hostmacros 871 * @param array $upd_hostmacros 872 * @param array $db_hostmacros 873 */ 874 private function prepareInheritedObjects(array $tpl_hostmacros, array &$ins_hostmacros = null, 875 array &$upd_hostmacros = null, array &$db_hostmacros = null): void { 876 $ins_hostmacros = []; 877 $upd_hostmacros = []; 878 $db_hostmacros = []; 879 880 $templateids_hostids = []; 881 $hostids = []; 882 883 $options = [ 884 'output' => ['hostid', 'templateid'], 885 'filter' => ['templateid' => array_unique(array_column($tpl_hostmacros, 'hostid'))] 886 ]; 887 $chd_hosts = DBselect(DB::makeSql('hosts', $options)); 888 889 while ($chd_host = DBfetch($chd_hosts)) { 890 $templateids_hostids[$chd_host['templateid']][] = $chd_host['hostid']; 891 $hostids[] = $chd_host['hostid']; 892 } 893 894 if (!$templateids_hostids) { 895 return; 896 } 897 898 $macros = []; 899 foreach ($tpl_hostmacros as $tpl_hostmacro) { 900 if (array_key_exists('macro_old', $tpl_hostmacro)) { 901 $macros[$tpl_hostmacro['macro_old']] = true; 902 } 903 else { 904 $macros[$tpl_hostmacro['macro']] = true; 905 } 906 } 907 908 $chd_hostmacros = DB::select('hostmacro', [ 909 'output' => ['hostmacroid', 'hostid', 'macro', 'type', 'value', 'description'], 910 'filter' => ['hostid' => $hostids, 'macro' => array_keys($macros)], 911 'preservekeys' => true 912 ]); 913 914 $host_macros = array_fill_keys($hostids, []); 915 916 foreach ($chd_hostmacros as $hostmacroid => $hostmacro) { 917 $host_macros[$hostmacro['hostid']][$hostmacro['macro']] = $hostmacroid; 918 } 919 920 foreach ($tpl_hostmacros as $tpl_hostmacro) { 921 $templateid = $tpl_hostmacro['hostid']; 922 923 if (!array_key_exists($templateid, $templateids_hostids)) { 924 continue; 925 } 926 927 foreach ($templateids_hostids[$templateid] as $hostid) { 928 if (array_key_exists('macro_old', $tpl_hostmacro)) { 929 $hostmacroid = $host_macros[$hostid][$tpl_hostmacro['macro_old']]; 930 931 $upd_hostmacros[] = ['hostmacroid' => $hostmacroid, 'hostid' => $hostid] + $tpl_hostmacro; 932 $db_hostmacros[$hostmacroid] = $chd_hostmacros[$hostmacroid]; 933 934 unset($chd_hostmacros[$hostmacroid], $host_macros[$hostid][$tpl_hostmacro['macro_old']]); 935 } 936 elseif (array_key_exists($tpl_hostmacro['macro'], $host_macros[$hostid])) { 937 $hostmacroid = $host_macros[$hostid][$tpl_hostmacro['macro']]; 938 939 $upd_hostmacros[] = ['hostmacroid' => $hostmacroid, 'hostid' => $hostid] + $tpl_hostmacro; 940 $db_hostmacros[$hostmacroid] = $chd_hostmacros[$hostmacroid]; 941 942 unset($chd_hostmacros[$hostmacroid], $host_macros[$hostid][$tpl_hostmacro['macro']]); 943 } 944 else { 945 $ins_hostmacros[] = ['hostid' => $hostid] + $tpl_hostmacro; 946 } 947 } 948 } 949 } 950 951 /** 952 * Updates the macros for the children of host prototypes and propagates the inheritance to the child host 953 * prototypes. 954 * 955 * @param array $tpl_hostmacros 956 * @param string $tpl_hostmacros[]['hostmacroid'] 957 * @param string $tpl_hostmacros[]['hostid'] 958 * @param string $tpl_hostmacros[]['macro'] 959 * @param string $tpl_hostmacros[]['value'] (optional) 960 * @param string $tpl_hostmacros[]['description'] (optional) 961 * @param int $tpl_hostmacros[]['type'] (optional) 962 * @param string $tpl_hostmacros[]['macro_old'] (optional) 963 * @param bool $is_delete Whether the passed hostmacros are intended to delete. 964 */ 965 private function inherit(array $tpl_hostmacros, bool $is_delete = false): void { 966 $this->prepareInheritedObjects($tpl_hostmacros, $ins_hostmacros, $upd_hostmacros, $db_hostmacros); 967 968 if ($ins_hostmacros) { 969 $this->createReal($ins_hostmacros); 970 } 971 972 if ($upd_hostmacros) { 973 if ($is_delete) { 974 DB::delete('hostmacro', ['hostmacroid' => array_column($upd_hostmacros, 'hostmacroid')]); 975 } 976 else { 977 $this->updateReal($upd_hostmacros, $db_hostmacros); 978 } 979 } 980 981 if ($ins_hostmacros || $upd_hostmacros) { 982 $this->inherit(array_merge($ins_hostmacros, $upd_hostmacros), $is_delete); 983 } 984 } 985 986 protected function applyQueryOutputOptions($tableName, $tableAlias, array $options, array $sqlParts) { 987 // Added type to query because it required to check macro is secret or not. 988 if (!$this->outputIsRequested('type', $options['output'])) { 989 $options['output'][] = 'type'; 990 } 991 992 $sqlParts = parent::applyQueryOutputOptions($tableName, $tableAlias, $options, $sqlParts); 993 994 if ($options['output'] != API_OUTPUT_COUNT && $options['globalmacro'] === null) { 995 if ($options['selectGroups'] !== null || $options['selectHosts'] !== null || $options['selectTemplates'] !== null) { 996 $sqlParts = $this->addQuerySelect($this->fieldId('hostid'), $sqlParts); 997 } 998 } 999 1000 return $sqlParts; 1001 } 1002 1003 /** 1004 * @inheritdoc 1005 */ 1006 protected function applyQueryFilterOptions($table, $alias, array $options, $sql_parts) { 1007 if (is_array($options['search'])) { 1008 // Do not allow to search by value for macro of type ZBX_MACRO_TYPE_SECRET. 1009 if (array_key_exists('value', $options['search'])) { 1010 $sql_parts['where']['search'] = $alias.'.type!='.ZBX_MACRO_TYPE_SECRET; 1011 zbx_db_search($table.' '.$alias, [ 1012 'searchByAny' => false, 1013 'search' => ['value' => $options['search']['value']] 1014 ] + $options, $sql_parts 1015 ); 1016 unset($options['search']['value']); 1017 } 1018 1019 if ($options['search']) { 1020 zbx_db_search($table.' '.$alias, $options, $sql_parts); 1021 } 1022 } 1023 1024 if (is_array($options['filter'])) { 1025 // Do not allow to filter by value for macro of type ZBX_MACRO_TYPE_SECRET. 1026 if (array_key_exists('value', $options['filter'])) { 1027 $sql_parts['where']['filter'] = $alias.'.type!='.ZBX_MACRO_TYPE_SECRET; 1028 $this->dbFilter($table.' '.$alias, [ 1029 'searchByAny' => false, 1030 'filter' => ['value' => $options['filter']['value']] 1031 ] + $options, $sql_parts 1032 ); 1033 unset($options['filter']['value']); 1034 } 1035 1036 if ($options['filter']) { 1037 $this->dbFilter($table.' '.$alias, $options, $sql_parts); 1038 } 1039 } 1040 1041 return $sql_parts; 1042 } 1043 1044 protected function addRelatedObjects(array $options, array $result) { 1045 $result = parent::addRelatedObjects($options, $result); 1046 1047 if ($options['globalmacro'] === null) { 1048 $hostMacroIds = array_keys($result); 1049 1050 /* 1051 * Adding objects 1052 */ 1053 // adding groups 1054 if ($options['selectGroups'] !== null && $options['selectGroups'] != API_OUTPUT_COUNT) { 1055 $res = DBselect( 1056 'SELECT hm.hostmacroid,hg.groupid'. 1057 ' FROM hostmacro hm,hosts_groups hg'. 1058 ' WHERE '.dbConditionInt('hm.hostmacroid', $hostMacroIds). 1059 ' AND hm.hostid=hg.hostid' 1060 ); 1061 $relationMap = new CRelationMap(); 1062 while ($relation = DBfetch($res)) { 1063 $relationMap->addRelation($relation['hostmacroid'], $relation['groupid']); 1064 } 1065 1066 $groups = API::HostGroup()->get([ 1067 'output' => $options['selectGroups'], 1068 'groupids' => $relationMap->getRelatedIds(), 1069 'preservekeys' => true 1070 ]); 1071 $result = $relationMap->mapMany($result, $groups, 'groups'); 1072 } 1073 1074 // adding templates 1075 if ($options['selectTemplates'] !== null && $options['selectTemplates'] != API_OUTPUT_COUNT) { 1076 $relationMap = $this->createRelationMap($result, 'hostmacroid', 'hostid'); 1077 $templates = API::Template()->get([ 1078 'output' => $options['selectTemplates'], 1079 'templateids' => $relationMap->getRelatedIds(), 1080 'preservekeys' => true 1081 ]); 1082 $result = $relationMap->mapMany($result, $templates, 'templates'); 1083 } 1084 1085 // adding templates 1086 if ($options['selectHosts'] !== null && $options['selectHosts'] != API_OUTPUT_COUNT) { 1087 $relationMap = $this->createRelationMap($result, 'hostmacroid', 'hostid'); 1088 $templates = API::Host()->get([ 1089 'output' => $options['selectHosts'], 1090 'hostids' => $relationMap->getRelatedIds(), 1091 'preservekeys' => true 1092 ]); 1093 $result = $relationMap->mapMany($result, $templates, 'hosts'); 1094 } 1095 } 1096 1097 return $result; 1098 } 1099 1100 protected function unsetExtraFields(array $objects, array $fields, $output) { 1101 foreach ($objects as &$object) { 1102 if ($object['type'] == ZBX_MACRO_TYPE_SECRET) { 1103 unset($object['value']); 1104 } 1105 } 1106 unset($object); 1107 1108 return parent::unsetExtraFields($objects, $fields, $output); 1109 } 1110} 1111