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 http tests. 24 */ 25class CHttpTest extends CApiService { 26 27 protected $tableName = 'httptest'; 28 protected $tableAlias = 'ht'; 29 protected $sortColumns = ['httptestid', 'name']; 30 31 /** 32 * Get data about web scenarios. 33 * 34 * @param array $options 35 * 36 * @return array 37 */ 38 public function get($options = []) { 39 $result = []; 40 41 $sqlParts = [ 42 'select' => ['httptests' => 'ht.httptestid'], 43 'from' => ['httptest' => 'httptest ht'], 44 'where' => [], 45 'group' => [], 46 'order' => [], 47 'limit' => null 48 ]; 49 50 $defOptions = [ 51 'httptestids' => null, 52 'applicationids' => null, 53 'hostids' => null, 54 'groupids' => null, 55 'templateids' => null, 56 'editable' => false, 57 'inherited' => null, 58 'templated' => null, 59 'monitored' => null, 60 'nopermissions' => null, 61 // filter 62 'filter' => null, 63 'search' => null, 64 'searchByAny' => null, 65 'startSearch' => false, 66 'excludeSearch' => false, 67 // output 68 'output' => API_OUTPUT_EXTEND, 69 'expandName' => null, 70 'expandStepName' => null, 71 'selectHosts' => null, 72 'selectSteps' => null, 73 'countOutput' => false, 74 'groupCount' => false, 75 'preservekeys' => false, 76 'sortfield' => '', 77 'sortorder' => '', 78 'limit' => null 79 ]; 80 $options = zbx_array_merge($defOptions, $options); 81 82 // editable + PERMISSION CHECK 83 if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN && !$options['nopermissions']) { 84 $permission = $options['editable'] ? PERM_READ_WRITE : PERM_READ; 85 $userGroups = getUserGroupsByUserId(self::$userData['userid']); 86 87 $sqlParts['where'][] = 'EXISTS ('. 88 'SELECT NULL'. 89 ' FROM hosts_groups hgg'. 90 ' JOIN rights r'. 91 ' ON r.id=hgg.groupid'. 92 ' AND '.dbConditionInt('r.groupid', $userGroups). 93 ' WHERE ht.hostid=hgg.hostid'. 94 ' GROUP BY hgg.hostid'. 95 ' HAVING MIN(r.permission)>'.PERM_DENY. 96 ' AND MAX(r.permission)>='.zbx_dbstr($permission). 97 ')'; 98 } 99 100 // httptestids 101 if (!is_null($options['httptestids'])) { 102 zbx_value2array($options['httptestids']); 103 104 $sqlParts['where']['httptestid'] = dbConditionInt('ht.httptestid', $options['httptestids']); 105 } 106 107 // templateids 108 if (!is_null($options['templateids'])) { 109 zbx_value2array($options['templateids']); 110 111 if (!is_null($options['hostids'])) { 112 zbx_value2array($options['hostids']); 113 $options['hostids'] = array_merge($options['hostids'], $options['templateids']); 114 } 115 else { 116 $options['hostids'] = $options['templateids']; 117 } 118 } 119 // hostids 120 if (!is_null($options['hostids'])) { 121 zbx_value2array($options['hostids']); 122 123 $sqlParts['where']['hostid'] = dbConditionInt('ht.hostid', $options['hostids']); 124 125 if ($options['groupCount']) { 126 $sqlParts['group']['hostid'] = 'ht.hostid'; 127 } 128 } 129 130 // groupids 131 if (!is_null($options['groupids'])) { 132 zbx_value2array($options['groupids']); 133 134 $sqlParts['from']['hosts_groups'] = 'hosts_groups hg'; 135 $sqlParts['where'][] = dbConditionInt('hg.groupid', $options['groupids']); 136 $sqlParts['where'][] = 'hg.hostid=ht.hostid'; 137 138 if ($options['groupCount']) { 139 $sqlParts['group']['hg'] = 'hg.groupid'; 140 } 141 } 142 143 // applicationids 144 if (!is_null($options['applicationids'])) { 145 zbx_value2array($options['applicationids']); 146 147 $sqlParts['where'][] = dbConditionId('ht.applicationid', $options['applicationids']); 148 } 149 150 // inherited 151 if (isset($options['inherited'])) { 152 $sqlParts['where'][] = $options['inherited'] ? 'ht.templateid IS NOT NULL' : 'ht.templateid IS NULL'; 153 } 154 155 // templated 156 if (isset($options['templated'])) { 157 $sqlParts['from']['hosts'] = 'hosts h'; 158 $sqlParts['where']['ha'] = 'h.hostid=ht.hostid'; 159 if ($options['templated']) { 160 $sqlParts['where'][] = 'h.status='.HOST_STATUS_TEMPLATE; 161 } 162 else { 163 $sqlParts['where'][] = 'h.status<>'.HOST_STATUS_TEMPLATE; 164 } 165 } 166 167 // monitored 168 if (!is_null($options['monitored'])) { 169 $sqlParts['from']['hosts'] = 'hosts h'; 170 $sqlParts['where']['hht'] = 'h.hostid=ht.hostid'; 171 172 if ($options['monitored']) { 173 $sqlParts['where'][] = 'h.status='.HOST_STATUS_MONITORED; 174 $sqlParts['where'][] = 'ht.status='.ITEM_STATUS_ACTIVE; 175 } 176 else { 177 $sqlParts['where'][] = '(h.status<>'.HOST_STATUS_MONITORED.' OR ht.status<>'.ITEM_STATUS_ACTIVE.')'; 178 } 179 } 180 181 // search 182 if (is_array($options['search'])) { 183 zbx_db_search('httptest ht', $options, $sqlParts); 184 } 185 186 // filter 187 if (is_array($options['filter'])) { 188 if (array_key_exists('delay', $options['filter']) && $options['filter']['delay'] !== null) { 189 $options['filter']['delay'] = getTimeUnitFilters($options['filter']['delay']); 190 } 191 192 $this->dbFilter('httptest ht', $options, $sqlParts); 193 } 194 195 // limit 196 if (zbx_ctype_digit($options['limit']) && $options['limit']) { 197 $sqlParts['limit'] = $options['limit']; 198 } 199 200 $sqlParts = $this->applyQueryOutputOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts); 201 $sqlParts = $this->applyQuerySortOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts); 202 $res = DBselect(self::createSelectQueryFromParts($sqlParts), $sqlParts['limit']); 203 while ($httpTest = DBfetch($res)) { 204 if ($options['countOutput']) { 205 if ($options['groupCount']) { 206 $result[] = $httpTest; 207 } 208 else { 209 $result = $httpTest['rowscount']; 210 } 211 } 212 else { 213 $result[$httpTest['httptestid']] = $httpTest; 214 } 215 } 216 217 if ($options['countOutput']) { 218 return $result; 219 } 220 221 if ($result) { 222 $result = $this->addRelatedObjects($options, $result); 223 224 // expandName 225 $nameRequested = (is_array($options['output']) && in_array('name', $options['output'])) 226 || $options['output'] == API_OUTPUT_EXTEND; 227 $expandName = $options['expandName'] !== null && $nameRequested; 228 229 // expandStepName 230 $stepNameRequested = $options['selectSteps'] == API_OUTPUT_EXTEND 231 || (is_array($options['selectSteps']) && in_array('name', $options['selectSteps'])); 232 $expandStepName = $options['expandStepName'] !== null && $stepNameRequested; 233 234 if ($expandName || $expandStepName) { 235 $result = resolveHttpTestMacros($result, $expandName, $expandStepName); 236 } 237 238 $result = $this->unsetExtraFields($result, ['hostid'], $options['output']); 239 } 240 241 // removing keys (hash -> array) 242 if (!$options['preservekeys']) { 243 $result = zbx_cleanHashes($result); 244 } 245 246 return $result; 247 } 248 249 /** 250 * Create web scenario. 251 * 252 * @param $httptests 253 * 254 * @return array 255 */ 256 public function create($httptests) { 257 $this->validateCreate($httptests); 258 259 $httptests = Manager::HttpTest()->persist($httptests); 260 261 $this->addAuditBulk(AUDIT_ACTION_ADD, AUDIT_RESOURCE_SCENARIO, $httptests); 262 263 return ['httptestids' => zbx_objectValues($httptests, 'httptestid')]; 264 } 265 266 /** 267 * @param array $httpTests 268 * 269 * @throws APIException if the input is invalid. 270 */ 271 protected function validateCreate(array &$httptests) { 272 $api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['hostid', 'name']], 'fields' => [ 273 'hostid' => ['type' => API_ID, 'flags' => API_REQUIRED], 274 'name' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('httptest', 'name')], 275 'applicationid' => ['type' => API_ID], 276 'delay' => ['type' => API_TIME_UNIT, 'flags' => API_NOT_EMPTY | API_ALLOW_USER_MACRO, 'in' => '1:'.SEC_PER_DAY], 277 'retries' => ['type' => API_INT32, 'in' => '1:10'], 278 'agent' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('httptest', 'agent')], 279 'http_proxy' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('httptest', 'http_proxy')], 280 'variables' => ['type' => API_OBJECTS, 'uniq' => [['name']], 'fields' => [ 281 'name' => ['type' => API_VARIABLE_NAME, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength('httptest_field', 'name')], 282 'value' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength('httptest_field', 'value')] 283 ]], 284 'headers' => ['type' => API_OBJECTS, 'fields' => [ 285 'name' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('httptest_field', 'name')], 286 'value' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('httptest_field', 'value')] 287 ]], 288 'status' => ['type' => API_INT32, 'in' => implode(',', [HTTPTEST_STATUS_ACTIVE, HTTPTEST_STATUS_DISABLED])], 289 'authentication' => ['type' => API_INT32, 'in' => implode(',', [HTTPTEST_AUTH_NONE, HTTPTEST_AUTH_BASIC, HTTPTEST_AUTH_NTLM, HTTPTEST_AUTH_KERBEROS])], 290 'http_user' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('httptest', 'http_user')], 291 'http_password' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('httptest', 'http_password')], 292 'verify_peer' => ['type' => API_INT32, 'in' => implode(',', [HTTPTEST_VERIFY_PEER_OFF, HTTPTEST_VERIFY_PEER_ON])], 293 'verify_host' => ['type' => API_INT32, 'in' => implode(',', [HTTPTEST_VERIFY_HOST_OFF, HTTPTEST_VERIFY_HOST_ON])], 294 'ssl_cert_file' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('httptest', 'ssl_cert_file')], 295 'ssl_key_file' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('httptest', 'ssl_key_file')], 296 'ssl_key_password' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('httptest', 'ssl_key_password')], 297 'steps' => ['type' => API_OBJECTS, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'uniq' => [['name'], ['no']], 'fields' => [ 298 'name' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('httpstep', 'name')], 299 'no' => ['type' => API_INT32, 'flags' => API_REQUIRED], 300 'url' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('httpstep', 'url')], 301 'query_fields' => ['type' => API_OBJECTS, 'fields' => [ 302 'name' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('httpstep_field', 'name')], 303 'value' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength('httpstep_field', 'value')] 304 ]], 305 'posts' => ['type' => API_HTTP_POST, 'length' => DB::getFieldLength('httpstep', 'posts'), 'name-length' => DB::getFieldLength('httpstep_field', 'name'), 'value-length' => DB::getFieldLength('httpstep_field', 'value')], 306 'variables' => ['type' => API_OBJECTS, 'uniq' => [['name']], 'fields' => [ 307 'name' => ['type' => API_VARIABLE_NAME, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength('httpstep_field', 'name')], 308 'value' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength('httpstep_field', 'value')] 309 ]], 310 'headers' => ['type' => API_OBJECTS, 'fields' => [ 311 'name' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('httpstep_field', 'name')], 312 'value' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('httpstep_field', 'value')] 313 ]], 314 'follow_redirects' => ['type' => API_INT32, 'in' => implode(',', [HTTPTEST_STEP_FOLLOW_REDIRECTS_OFF, HTTPTEST_STEP_FOLLOW_REDIRECTS_ON])], 315 'retrieve_mode' => ['type' => API_INT32, 'in' => implode(',', [HTTPTEST_STEP_RETRIEVE_MODE_CONTENT, HTTPTEST_STEP_RETRIEVE_MODE_HEADERS, HTTPTEST_STEP_RETRIEVE_MODE_BOTH])], 316 'timeout' => ['type' => API_TIME_UNIT, 'flags' => API_NOT_EMPTY | API_ALLOW_USER_MACRO, 'in' => '1:'.SEC_PER_HOUR], 317 'required' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('httpstep', 'required')], 318 'status_codes' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('httpstep', 'status_codes')] 319 ]] 320 ]]; 321 if (!CApiInputValidator::validate($api_input_rules, $httptests, '/', $error)) { 322 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 323 } 324 325 $names_by_hostid = []; 326 327 foreach ($httptests as $httptest) { 328 $names_by_hostid[$httptest['hostid']][] = $httptest['name']; 329 } 330 331 $this->checkHostPermissions(array_keys($names_by_hostid)); 332 $this->checkDuplicates($names_by_hostid); 333 $this->checkApplications($httptests, __FUNCTION__); 334 $this->validateAuthParameters($httptests, __FUNCTION__); 335 $this->validateSslParameters($httptests, __FUNCTION__); 336 $this->validateSteps($httptests, __FUNCTION__); 337 } 338 339 /** 340 * @param $httptests 341 * 342 * @return array 343 */ 344 public function update($httptests) { 345 $this->validateUpdate($httptests, $db_httptests); 346 347 Manager::HttpTest()->persist($httptests); 348 349 foreach ($db_httptests as &$db_httptest) { 350 unset($db_httptest['headers'], $db_httptest['variables'], $db_httptest['steps']); 351 } 352 unset($db_httptest); 353 354 $this->addAuditBulk(AUDIT_ACTION_UPDATE, AUDIT_RESOURCE_SCENARIO, $httptests, $db_httptests); 355 356 return ['httptestids' => zbx_objectValues($httptests, 'httptestid')]; 357 } 358 359 /** 360 * @param array $httptests 361 * @param array $db_httptests 362 * 363 * @throws APIException if the input is invalid. 364 */ 365 protected function validateUpdate(array &$httptests, array &$db_httptests = null) { 366 $api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['httptestid']], 'fields' => [ 367 'httptestid' => ['type' => API_ID, 'flags' => API_REQUIRED], 368 'name' => ['type' => API_STRING_UTF8, 'flags' => API_NOT_EMPTY, 'length' => DB::getFieldLength('httptest', 'name')], 369 'applicationid' => ['type' => API_ID], 370 'delay' => ['type' => API_TIME_UNIT, 'flags' => API_NOT_EMPTY | API_ALLOW_USER_MACRO, 'in' => '1:'.SEC_PER_DAY], 371 'retries' => ['type' => API_INT32, 'in' => '1:10'], 372 'agent' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('httptest', 'agent')], 373 'http_proxy' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('httptest', 'http_proxy')], 374 'variables' => ['type' => API_OBJECTS, 'uniq' => [['name']], 'fields' => [ 375 'name' => ['type' => API_VARIABLE_NAME, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength('httptest_field', 'name')], 376 'value' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength('httptest_field', 'value')] 377 ]], 378 'headers' => ['type' => API_OBJECTS, 'fields' => [ 379 'name' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('httptest_field', 'name')], 380 'value' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('httptest_field', 'value')] 381 ]], 382 'status' => ['type' => API_INT32, 'in' => implode(',', [HTTPTEST_STATUS_ACTIVE, HTTPTEST_STATUS_DISABLED])], 383 'authentication' => ['type' => API_INT32, 'in' => implode(',', [HTTPTEST_AUTH_NONE, HTTPTEST_AUTH_BASIC, HTTPTEST_AUTH_NTLM, HTTPTEST_AUTH_KERBEROS])], 384 'http_user' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('httptest', 'http_user')], 385 'http_password' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('httptest', 'http_password')], 386 'verify_peer' => ['type' => API_INT32, 'in' => implode(',', [HTTPTEST_VERIFY_PEER_OFF, HTTPTEST_VERIFY_PEER_ON])], 387 'verify_host' => ['type' => API_INT32, 'in' => implode(',', [HTTPTEST_VERIFY_HOST_OFF, HTTPTEST_VERIFY_HOST_ON])], 388 'ssl_cert_file' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('httptest', 'ssl_cert_file')], 389 'ssl_key_file' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('httptest', 'ssl_key_file')], 390 'ssl_key_password' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('httptest', 'ssl_key_password')], 391 'steps' => ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY, 'uniq' => [['httpstepid'], ['name'], ['no']], 'fields' => [ 392 'httpstepid' => ['type' => API_ID], 393 'name' => ['type' => API_STRING_UTF8, 'flags' => API_NOT_EMPTY, 'length' => DB::getFieldLength('httpstep', 'name')], 394 'no' => ['type' => API_INT32], 395 'url' => ['type' => API_STRING_UTF8, 'flags' => API_NOT_EMPTY, 'length' => DB::getFieldLength('httpstep', 'url')], 396 'query_fields' => ['type' => API_OBJECTS, 'fields' => [ 397 'name' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('httpstep_field', 'name')], 398 'value' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength('httpstep_field', 'value')] 399 ]], 400 'posts' => ['type' => API_HTTP_POST, 'length' => DB::getFieldLength('httpstep', 'posts'), 'name-length' => DB::getFieldLength('httpstep_field', 'name'), 'value-length' => DB::getFieldLength('httpstep_field', 'value')], 401 'variables' => ['type' => API_OBJECTS, 'uniq' => [['name']], 'fields' => [ 402 'name' => ['type' => API_VARIABLE_NAME, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength('httpstep_field', 'name')], 403 'value' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength('httpstep_field', 'value')] 404 ]], 405 'headers' => ['type' => API_OBJECTS, 'fields' => [ 406 'name' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('httpstep_field', 'name')], 407 'value' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('httpstep_field', 'value')] 408 ]], 409 'follow_redirects' => ['type' => API_INT32, 'in' => implode(',', [HTTPTEST_STEP_FOLLOW_REDIRECTS_OFF, HTTPTEST_STEP_FOLLOW_REDIRECTS_ON])], 410 'retrieve_mode' => ['type' => API_INT32, 'in' => implode(',', [HTTPTEST_STEP_RETRIEVE_MODE_CONTENT, HTTPTEST_STEP_RETRIEVE_MODE_HEADERS, HTTPTEST_STEP_RETRIEVE_MODE_BOTH])], 411 'timeout' => ['type' => API_TIME_UNIT, 'flags' => API_NOT_EMPTY | API_ALLOW_USER_MACRO, 'in' => '1:'.SEC_PER_HOUR], 412 'required' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('httpstep', 'required')], 413 'status_codes' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('httpstep', 'status_codes')] 414 ]] 415 ]]; 416 if (!CApiInputValidator::validate($api_input_rules, $httptests, '/', $error)) { 417 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 418 } 419 420 // permissions 421 $db_httptests = $this->get([ 422 'output' => ['httptestid', 'hostid', 'name', 'applicationid', 'delay', 'retries', 'agent', 'http_proxy', 423 'status', 'authentication', 'http_user', 'http_password', 'verify_peer', 'verify_host', 424 'ssl_cert_file', 'ssl_key_file', 'ssl_key_password', 'templateid' 425 ], 426 'selectSteps' => ['httpstepid', 'name', 'no', 'url', 'timeout', 'posts', 'required', 427 'status_codes', 'follow_redirects', 'retrieve_mode', 'post_type' 428 ], 429 'httptestids' => zbx_objectValues($httptests, 'httptestid'), 430 'editable' => true, 431 'preservekeys' => true 432 ]); 433 434 foreach ($db_httptests as &$db_httptest) { 435 $db_httptest['headers'] = []; 436 $db_httptest['variables'] = []; 437 $db_httptest['steps'] = zbx_toHash($db_httptest['steps'], 'httpstepid'); 438 } 439 unset($db_httptest); 440 441 $names_by_hostid = []; 442 443 foreach ($httptests as $httptest) { 444 if (!array_key_exists($httptest['httptestid'], $db_httptests)) { 445 self::exception(ZBX_API_ERROR_PERMISSIONS, 446 _('No permissions to referred object or it does not exist!') 447 ); 448 } 449 450 $db_httptest = $db_httptests[$httptest['httptestid']]; 451 452 if (array_key_exists('name', $httptest)) { 453 if ($db_httptest['templateid'] != 0) { 454 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 455 'Cannot update a templated web scenario "%1$s": %2$s.', $httptest['name'], 456 _s('unexpected parameter "%1$s"', 'name') 457 )); 458 } 459 460 if ($httptest['name'] !== $db_httptest['name']) { 461 $names_by_hostid[$db_httptest['hostid']][] = $httptest['name']; 462 } 463 } 464 } 465 466 $httptests = $this->extendObjectsByKey($httptests, $db_httptests, 'httptestid', ['hostid', 'name']); 467 468 // uniqueness 469 foreach ($httptests as &$httptest) { 470 $db_httptest = $db_httptests[$httptest['httptestid']]; 471 472 if (array_key_exists('steps', $httptest)) { 473 // unexpected patameters for templated web scenario steps 474 if ($db_httptest['templateid'] != 0) { 475 foreach ($httptest['steps'] as $httpstep) { 476 foreach (['name', 'no'] as $field_name) { 477 if (array_key_exists($field_name, $httpstep)) { 478 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 479 'Cannot update step for a templated web scenario "%1$s": %2$s.', $httptest['name'], 480 _s('unexpected parameter "%1$s"', $field_name) 481 )); 482 } 483 } 484 } 485 } 486 487 $httptest['steps'] = 488 $this->extendObjectsByKey($httptest['steps'], $db_httptest['steps'], 'httpstepid', ['name']); 489 } 490 } 491 unset($httptest); 492 493 $api_input_rules = ['type' => API_OBJECTS, 'uniq' => [['hostid', 'name']], 'fields' => [ 494 'steps' => ['type' => API_OBJECTS, 'uniq' => [['name']]] 495 ]]; 496 if (!CApiInputValidator::validateUniqueness($api_input_rules, $httptests, '/', $error)) { 497 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 498 } 499 500 // validation 501 if ($names_by_hostid) { 502 $this->checkDuplicates($names_by_hostid); 503 } 504 $this->checkApplications($httptests, __FUNCTION__, $db_httptests); 505 $this->validateAuthParameters($httptests, __FUNCTION__, $db_httptests); 506 $this->validateSslParameters($httptests, __FUNCTION__, $db_httptests); 507 $this->validateSteps($httptests, __FUNCTION__, $db_httptests); 508 509 return $httptests; 510 } 511 512 /** 513 * Delete web scenario. 514 * 515 * @param array $httptestids 516 * @param bool $nopermissions 517 * 518 * @return array 519 */ 520 public function delete(array $httptestids, $nopermissions = false) { 521 // TODO: remove $nopermissions hack 522 523 $api_input_rules = ['type' => API_IDS, 'flags' => API_NOT_EMPTY, 'uniq' => true]; 524 if (!CApiInputValidator::validate($api_input_rules, $httptestids, '/', $error)) { 525 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 526 } 527 528 $db_httptests = $this->get([ 529 'output' => ['httptestid', 'name', 'templateid'], 530 'httptestids' => $httptestids, 531 'editable' => true, 532 'preservekeys' => true 533 ]); 534 535 if (!$nopermissions) { 536 foreach ($httptestids as $httptestid) { 537 if (!array_key_exists($httptestid, $db_httptests)) { 538 self::exception(ZBX_API_ERROR_PERMISSIONS, 539 _('No permissions to referred object or it does not exist!') 540 ); 541 } 542 543 $db_httptest = $db_httptests[$httptestid]; 544 545 if ($db_httptest['templateid'] != 0) { 546 self::exception(ZBX_API_ERROR_PARAMETERS, 547 _s('Cannot delete templated web scenario "%1$s".', $db_httptest['name']) 548 ); 549 } 550 } 551 } 552 553 $parent_httptestids = $httptestids; 554 $child_httptestids = []; 555 do { 556 $parent_httptestids = array_keys(DB::select('httptest', [ 557 'output' => [], 558 'filter' => ['templateid' => $parent_httptestids], 559 'preservekeys' => true 560 ])); 561 562 $child_httptestids = array_merge($child_httptestids, $parent_httptestids); 563 } 564 while ($parent_httptestids); 565 566 $del_httptestids = array_merge($httptestids, $child_httptestids); 567 $del_itemids = []; 568 569 $db_httptestitems = DBselect( 570 'SELECT hti.itemid'. 571 ' FROM httptestitem hti'. 572 ' WHERE '.dbConditionInt('hti.httptestid', $del_httptestids) 573 ); 574 while ($db_httptestitem = DBfetch($db_httptestitems)) { 575 $del_itemids[] = $db_httptestitem['itemid']; 576 } 577 578 $db_httpstepitems = DBselect( 579 'SELECT hsi.itemid'. 580 ' FROM httpstepitem hsi,httpstep hs'. 581 ' WHERE hsi.httpstepid=hs.httpstepid'. 582 ' AND '.dbConditionInt('hs.httptestid', $del_httptestids) 583 ); 584 while ($db_httpstepitem = DBfetch($db_httpstepitems)) { 585 $del_itemids[] = $db_httpstepitem['itemid']; 586 } 587 588 if ($del_itemids) { 589 CItemManager::delete($del_itemids); 590 } 591 592 DB::delete('httptest', ['httptestid' => $del_httptestids]); 593 594 $this->addAuditBulk(AUDIT_ACTION_DELETE, AUDIT_RESOURCE_SCENARIO, $db_httptests); 595 596 return ['httptestids' => $httptestids]; 597 } 598 599 /** 600 * Checks if the current user has access to the given hosts and templates. 601 * 602 * @param array $hostids an array of host or template IDs 603 * 604 * @throws APIException if the user doesn't have write permissions for the given hosts. 605 */ 606 private function checkHostPermissions(array $hostids) { 607 if ($hostids) { 608 $count = API::Host()->get([ 609 'countOutput' => true, 610 'hostids' => $hostids, 611 'editable' => true 612 ]); 613 614 if ($count == count($hostids)) { 615 return; 616 } 617 618 $count += API::Template()->get([ 619 'countOutput' => true, 620 'templateids' => $hostids, 621 'editable' => true 622 ]); 623 624 if ($count != count($hostids)) { 625 self::exception(ZBX_API_ERROR_PERMISSIONS, 626 _('No permissions to referred object or it does not exist!') 627 ); 628 } 629 } 630 } 631 632 /** 633 * Check for duplicated web scenarios. 634 * 635 * @param array $names_by_hostid 636 * 637 * @throws APIException if web scenario already exists. 638 */ 639 private function checkDuplicates(array $names_by_hostid) { 640 $sql_where = []; 641 foreach ($names_by_hostid as $hostid => $names) { 642 $sql_where[] = '(ht.hostid='.$hostid.' AND '.dbConditionString('ht.name', $names).')'; 643 } 644 645 $db_httptests = DBfetchArray( 646 DBselect('SELECT ht.name FROM httptest ht WHERE '.implode(' OR ', $sql_where), 1) 647 ); 648 649 if ($db_httptests) { 650 self::exception(ZBX_API_ERROR_PARAMETERS, 651 _s('Web scenario "%1$s" already exists.', $db_httptests[0]['name']) 652 ); 653 } 654 } 655 656 /** 657 * Check that application belongs to web scenario host. 658 * 659 * @param array $httptests 660 * @param string $method 661 * @param array $db_httptests 662 * 663 * @throws APIException if application does not exists or belongs to another host. 664 */ 665 private function checkApplications(array $httptests, $method, array $db_httptests = null) { 666 $applicationids = []; 667 668 foreach ($httptests as $index => $httptest) { 669 if (array_key_exists('applicationid', $httptest) && $httptest['applicationid'] != 0 670 && ($method === 'validateCreate' 671 || $httptest['applicationid'] != $db_httptests[$httptest['httptestid']]['applicationid'])) { 672 $applicationids[$httptest['applicationid']] = true; 673 } 674 } 675 676 if (!$applicationids) { 677 return; 678 } 679 680 $db_applications = DB::select('applications', [ 681 'output' => ['applicationid', 'hostid', 'name', 'flags'], 682 'applicationids' => array_keys($applicationids), 683 'preservekeys' => true 684 ]); 685 686 foreach ($httptests as $index => $httptest) { 687 if (array_key_exists('applicationid', $httptest) && $httptest['applicationid'] != 0 688 && ($method === 'validateCreate' 689 || $httptest['applicationid'] != $db_httptests[$httptest['httptestid']]['applicationid'])) { 690 if (!array_key_exists($httptest['applicationid'], $db_applications)) { 691 self::exception(ZBX_API_ERROR_PARAMETERS, 692 _s('Application with applicationid "%1$s" does not exist.', $httptest['applicationid']) 693 ); 694 } 695 696 $db_application = $db_applications[$httptest['applicationid']]; 697 698 if ($db_application['flags'] == ZBX_FLAG_DISCOVERY_CREATED) { 699 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 700 'Cannot add a discovered application "%1$s" to a web scenario.', $db_application['name'] 701 )); 702 } 703 704 $hostid = ($method === 'validateCreate') 705 ? $httptest['hostid'] 706 : $db_httptests[$httptest['httptestid']]['hostid']; 707 708 if (bccomp($db_application['hostid'], $hostid) != 0) { 709 self::exception(ZBX_API_ERROR_PARAMETERS, 710 _('The web scenario application belongs to a different host than the web scenario host.') 711 ); 712 } 713 } 714 } 715 } 716 717 /** 718 * @param array $httptests 719 * @param string $method 720 * @param array $db_httptests 721 * 722 * @throws APIException 723 */ 724 protected function validateSteps(array &$httptests, $method, array $db_httptests = null) { 725 if ($method === 'validateUpdate') { 726 foreach ($httptests as $httptest) { 727 if (!array_key_exists('steps', $httptest)) { 728 continue; 729 } 730 731 $db_httptest = $db_httptests[$httptest['httptestid']]; 732 733 if ($db_httptest['templateid'] != 0) { 734 if (count($httptest['steps']) != count($db_httptest['steps'])) { 735 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect templated web scenario step count.')); 736 } 737 738 foreach ($httptest['steps'] as $httpstep) { 739 if (!array_key_exists('httpstepid', $httpstep)) { 740 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 741 'Cannot update step for a templated web scenario "%1$s": %2$s.', $httptest['name'], 742 _s('the parameter "%1$s" is missing', 'httpstepid') 743 )); 744 } 745 elseif (!array_key_exists($httpstep['httpstepid'], $db_httptest['steps'])) { 746 self::exception(ZBX_API_ERROR_PARAMETERS, 747 _('No permissions to referred object or it does not exist!') 748 ); 749 } 750 } 751 } 752 } 753 } 754 755 $this->checkStatusCodes($httptests); 756 $this->validateRetrieveMode($httptests, $method, $db_httptests); 757 } 758 759 /** 760 * Validate http response code range. 761 * Range can be empty string or list of comma separated numeric strings or user macros. 762 * 763 * Examples: '100-199, 301, 404, 500-550, {$MACRO}-200, {$MACRO}-{$MACRO}' 764 * 765 * @param array $httptests 766 * 767 * @throws APIException if the status code range is invalid. 768 */ 769 private function checkStatusCodes(array $httptests) { 770 $ranges_parser = new CRangesParser(['usermacros' => true]); 771 772 foreach ($httptests as $httptest) { 773 if (!array_key_exists('steps', $httptest)) { 774 continue; 775 } 776 777 foreach ($httptest['steps'] as $httpstep) { 778 if (!array_key_exists('status_codes', $httpstep) || $httpstep['status_codes'] === '') { 779 continue; 780 } 781 782 if ($ranges_parser->parse($httpstep['status_codes']) != CParser::PARSE_SUCCESS) { 783 self::exception(ZBX_API_ERROR_PARAMETERS, 784 _s('Invalid response code "%1$s".', $httpstep['status_codes']) 785 ); 786 } 787 } 788 } 789 } 790 791 protected function applyQueryOutputOptions($tableName, $tableAlias, array $options, array $sqlParts) { 792 $sqlParts = parent::applyQueryOutputOptions($tableName, $tableAlias, $options, $sqlParts); 793 794 if (!$options['countOutput']) { 795 // make sure we request the hostid to be able to expand macros 796 if ($options['expandName'] !== null || $options['expandStepName'] !== null || $options['selectHosts'] !== null) { 797 $sqlParts = $this->addQuerySelect($this->fieldId('hostid'), $sqlParts); 798 } 799 } 800 801 return $sqlParts; 802 } 803 804 protected function addRelatedObjects(array $options, array $result) { 805 $result = parent::addRelatedObjects($options, $result); 806 807 $httpTestIds = array_keys($result); 808 809 // adding headers and variables 810 $fields = [ 811 ZBX_HTTPFIELD_HEADER => 'headers', 812 ZBX_HTTPFIELD_VARIABLE => 'variables' 813 ]; 814 foreach ($fields as $type => $field) { 815 if (!$this->outputIsRequested($field, $options['output'])) { 816 unset($fields[$type]); 817 } 818 } 819 820 if ($fields) { 821 $db_httpfields = DB::select('httptest_field', [ 822 'output' => ['httptestid', 'name', 'value', 'type'], 823 'filter' => [ 824 'httptestid' => $httpTestIds, 825 'type' => array_keys($fields) 826 ], 827 'sortfield' => ['httptest_fieldid'] 828 ]); 829 830 foreach ($result as &$httptest) { 831 foreach ($fields as $field) { 832 $httptest[$field] = []; 833 } 834 } 835 unset($httptest); 836 837 foreach ($db_httpfields as $db_httpfield) { 838 $result[$db_httpfield['httptestid']][$fields[$db_httpfield['type']]][] = [ 839 'name' => $db_httpfield['name'], 840 'value' => $db_httpfield['value'] 841 ]; 842 } 843 } 844 845 // adding hosts 846 if ($options['selectHosts'] !== null && $options['selectHosts'] != API_OUTPUT_COUNT) { 847 $relationMap = $this->createRelationMap($result, 'httptestid', 'hostid'); 848 $hosts = API::Host()->get([ 849 'output' => $options['selectHosts'], 850 'hostid' => $relationMap->getRelatedIds(), 851 'nopermissions' => true, 852 'templated_hosts' => true, 853 'preservekeys' => true 854 ]); 855 $result = $relationMap->mapMany($result, $hosts, 'hosts'); 856 } 857 858 // adding steps 859 if ($options['selectSteps'] !== null) { 860 if ($options['selectSteps'] != API_OUTPUT_COUNT) { 861 $fields = [ 862 ZBX_HTTPFIELD_HEADER => 'headers', 863 ZBX_HTTPFIELD_VARIABLE => 'variables', 864 ZBX_HTTPFIELD_QUERY_FIELD => 'query_fields', 865 ZBX_HTTPFIELD_POST_FIELD => 'posts' 866 ]; 867 foreach ($fields as $type => $field) { 868 if (!$this->outputIsRequested($field, $options['selectSteps'])) { 869 unset($fields[$type]); 870 } 871 } 872 873 $db_httpsteps = API::getApiService()->select('httpstep', [ 874 'output' => $this->outputExtend($options['selectSteps'], ['httptestid', 'httpstepid', 'post_type']), 875 'filter' => ['httptestid' => $httpTestIds], 876 'preservekeys' => true 877 ]); 878 $relationMap = $this->createRelationMap($db_httpsteps, 'httptestid', 'httpstepid'); 879 880 if ($fields) { 881 foreach ($db_httpsteps as &$db_httpstep) { 882 foreach ($fields as $type => $field) { 883 if ($type != ZBX_HTTPFIELD_POST_FIELD || $db_httpstep['post_type'] == ZBX_POSTTYPE_FORM) { 884 $db_httpstep[$field] = []; 885 } 886 } 887 } 888 unset($db_httpstep); 889 890 $db_httpstep_fields = DB::select('httpstep_field', [ 891 'output' => ['httpstepid', 'name', 'value', 'type'], 892 'filter' => [ 893 'httpstepid' => array_keys($db_httpsteps), 894 'type' => array_keys($fields) 895 ], 896 'sortfield' => ['httpstep_fieldid'] 897 ]); 898 899 foreach ($db_httpstep_fields as $db_httpstep_field) { 900 $db_httpstep = &$db_httpsteps[$db_httpstep_field['httpstepid']]; 901 902 if ($db_httpstep_field['type'] != ZBX_HTTPFIELD_POST_FIELD 903 || $db_httpstep['post_type'] == ZBX_POSTTYPE_FORM) { 904 $db_httpstep[$fields[$db_httpstep_field['type']]][] = [ 905 'name' => $db_httpstep_field['name'], 906 'value' => $db_httpstep_field['value'] 907 ]; 908 } 909 } 910 unset($db_httpstep); 911 } 912 913 $db_httpsteps = $this->unsetExtraFields($db_httpsteps, ['httptestid', 'httpstepid', 'post_type'], 914 $options['selectSteps'] 915 ); 916 $result = $relationMap->mapMany($result, $db_httpsteps, 'steps'); 917 } 918 else { 919 $dbHttpSteps = DBselect( 920 'SELECT hs.httptestid,COUNT(hs.httpstepid) AS stepscnt'. 921 ' FROM httpstep hs'. 922 ' WHERE '.dbConditionInt('hs.httptestid', $httpTestIds). 923 ' GROUP BY hs.httptestid' 924 ); 925 while ($dbHttpStep = DBfetch($dbHttpSteps)) { 926 $result[$dbHttpStep['httptestid']]['steps'] = $dbHttpStep['stepscnt']; 927 } 928 } 929 } 930 931 return $result; 932 } 933 934 /** 935 * @param array $httptests 936 * @param string $method 937 * @param array $db_httptests 938 * 939 * @throws APIException if auth parameters are invalid. 940 */ 941 private function validateAuthParameters(array &$httptests, $method, array $db_httptests = null) { 942 foreach ($httptests as &$httptest) { 943 if (array_key_exists('authentication', $httptest) || array_key_exists('http_user', $httptest) 944 || array_key_exists('http_password', $httptest)) { 945 $httptest += [ 946 'authentication' => ($method === 'validateUpdate') 947 ? $db_httptests[$httptest['httptestid']]['authentication'] 948 : HTTPTEST_AUTH_NONE 949 ]; 950 951 if ($httptest['authentication'] == HTTPTEST_AUTH_NONE) { 952 foreach (['http_user', 'http_password'] as $field_name) { 953 $httptest += [$field_name => '']; 954 955 if ($httptest[$field_name] !== '') { 956 self::exception(ZBX_API_ERROR_PARAMETERS, 957 _s('Incorrect value for field "%1$s": %2$s.', $field_name, _('should be empty')) 958 ); 959 } 960 } 961 } 962 } 963 } 964 unset($httptest); 965 } 966 967 /** 968 * @param array $httptests 969 * @param string $method 970 * @param array $db_httptests 971 * 972 * @throws APIException if SSL cert is present but SSL key is not. 973 */ 974 private function validateSslParameters(array &$httptests, $method, array $db_httptests = null) { 975 foreach ($httptests as &$httptest) { 976 if (array_key_exists('ssl_key_password', $httptest) 977 || array_key_exists('ssl_key_file', $httptest) 978 || array_key_exists('ssl_cert_file', $httptest)) { 979 if ($method === 'validateCreate') { 980 $httptest += [ 981 'ssl_key_password' => '', 982 'ssl_key_file' => '', 983 'ssl_cert_file' => '' 984 ]; 985 } 986 else { 987 $db_httptest = $db_httptests[$httptest['httptestid']]; 988 $httptest += [ 989 'ssl_key_password' => $db_httptest['ssl_key_password'], 990 'ssl_key_file' => $db_httptest['ssl_key_file'], 991 'ssl_cert_file' => $db_httptest['ssl_cert_file'] 992 ]; 993 } 994 995 if ($httptest['ssl_key_password'] != '' && $httptest['ssl_key_file'] == '') { 996 self::exception(ZBX_API_ERROR_PARAMETERS, 997 _s('Empty SSL key file for web scenario "%1$s".', $httptest['name']) 998 ); 999 } 1000 1001 if ($httptest['ssl_key_file'] != '' && $httptest['ssl_cert_file'] == '') { 1002 self::exception(ZBX_API_ERROR_PARAMETERS, 1003 _s('Empty SSL certificate file for web scenario "%1$s".', $httptest['name']) 1004 ); 1005 } 1006 } 1007 } 1008 unset($httptest); 1009 } 1010 1011 /** 1012 * @param array $httptests 1013 * @param string $method 1014 * @param array $db_httptests 1015 * 1016 * @throws APIException if parameters is invalid. 1017 */ 1018 private function validateRetrieveMode(array &$httptests, $method, array $db_httptests = null) { 1019 foreach ($httptests as &$httptest) { 1020 if (!array_key_exists('steps', $httptest)) { 1021 continue; 1022 } 1023 1024 foreach ($httptest['steps'] as &$httpstep) { 1025 if (array_key_exists('retrieve_mode', $httpstep) 1026 || array_key_exists('posts', $httpstep) 1027 || array_key_exists('required', $httpstep)) { 1028 1029 if ($method === 'validateCreate' || !array_key_exists('httpstepid', $httpstep)) { 1030 $httpstep += [ 1031 'retrieve_mode' => HTTPTEST_STEP_RETRIEVE_MODE_CONTENT, 1032 'posts' => '', 1033 'required' => '' 1034 ]; 1035 } 1036 else { 1037 $db_httptest = $db_httptests[$httptest['httptestid']]; 1038 $db_httpstep = $db_httptest['steps'][$httpstep['httpstepid']]; 1039 $httpstep += [ 1040 'retrieve_mode' => $db_httpstep['retrieve_mode'], 1041 'required' => $db_httpstep['required'], 1042 'posts' => ($db_httpstep['retrieve_mode'] != HTTPTEST_STEP_RETRIEVE_MODE_HEADERS) 1043 ? $db_httpstep['posts'] 1044 : '' 1045 ]; 1046 } 1047 1048 if ($httpstep['retrieve_mode'] == HTTPTEST_STEP_RETRIEVE_MODE_HEADERS) { 1049 if ($httpstep['posts'] !== '' && $httpstep['posts'] !== []) { 1050 $field_name = $httpstep['required'] !== '' ? 'required' : 'posts'; 1051 1052 self::exception(ZBX_API_ERROR_PARAMETERS, 1053 _s('Incorrect value for field "%1$s": %2$s.', 'posts', _('should be empty')) 1054 ); 1055 } 1056 } 1057 } 1058 } 1059 unset($httpstep); 1060 } 1061 unset($httptest); 1062 } 1063} 1064