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 users. 24 */ 25class CUser extends CApiService { 26 27 protected $tableName = 'users'; 28 protected $tableAlias = 'u'; 29 protected $sortColumns = ['userid', 'alias']; 30 31 /** 32 * Get users data. 33 * 34 * @param array $options 35 * @param array $options['usrgrpids'] filter by UserGroup IDs 36 * @param array $options['userids'] filter by User IDs 37 * @param bool $options['type'] filter by User type [USER_TYPE_ZABBIX_USER: 1, USER_TYPE_ZABBIX_ADMIN: 2, USER_TYPE_SUPER_ADMIN: 3] 38 * @param bool $options['selectUsrgrps'] extend with UserGroups data for each User 39 * @param bool $options['getAccess'] extend with access data for each User 40 * @param bool $options['count'] output only count of objects in result. (result returned in property 'rowscount') 41 * @param string $options['pattern'] filter by Host name containing only give pattern 42 * @param int $options['limit'] output will be limited to given number 43 * @param string $options['sortfield'] output will be sorted by given property ['userid', 'alias'] 44 * @param string $options['sortorder'] output will be sorted in given order ['ASC', 'DESC'] 45 * 46 * @return array 47 */ 48 public function get($options = []) { 49 $result = []; 50 51 $sqlParts = [ 52 'select' => ['users' => 'u.userid'], 53 'from' => ['users' => 'users u'], 54 'where' => [], 55 'order' => [], 56 'limit' => null 57 ]; 58 59 $defOptions = [ 60 'usrgrpids' => null, 61 'userids' => null, 62 'mediaids' => null, 63 'mediatypeids' => null, 64 // filter 65 'filter' => null, 66 'search' => null, 67 'searchByAny' => null, 68 'startSearch' => false, 69 'excludeSearch' => false, 70 'searchWildcardsEnabled' => null, 71 // output 72 'output' => API_OUTPUT_EXTEND, 73 'editable' => false, 74 'selectUsrgrps' => null, 75 'selectMedias' => null, 76 'selectMediatypes' => null, 77 'getAccess' => null, 78 'countOutput' => false, 79 'preservekeys' => false, 80 'sortfield' => '', 81 'sortorder' => '', 82 'limit' => null 83 ]; 84 $options = zbx_array_merge($defOptions, $options); 85 86 // permission check 87 if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) { 88 if (!$options['editable']) { 89 $sqlParts['from']['users_groups'] = 'users_groups ug'; 90 $sqlParts['where']['uug'] = 'u.userid=ug.userid'; 91 $sqlParts['where'][] = 'ug.usrgrpid IN ('. 92 ' SELECT uug.usrgrpid'. 93 ' FROM users_groups uug'. 94 ' WHERE uug.userid='.self::$userData['userid']. 95 ')'; 96 } 97 else { 98 $sqlParts['where'][] = 'u.userid='.self::$userData['userid']; 99 } 100 } 101 102 // userids 103 if ($options['userids'] !== null) { 104 zbx_value2array($options['userids']); 105 106 $sqlParts['where'][] = dbConditionInt('u.userid', $options['userids']); 107 } 108 109 // usrgrpids 110 if ($options['usrgrpids'] !== null) { 111 zbx_value2array($options['usrgrpids']); 112 113 $sqlParts['from']['users_groups'] = 'users_groups ug'; 114 $sqlParts['where'][] = dbConditionInt('ug.usrgrpid', $options['usrgrpids']); 115 $sqlParts['where']['uug'] = 'u.userid=ug.userid'; 116 } 117 118 // mediaids 119 if ($options['mediaids'] !== null) { 120 zbx_value2array($options['mediaids']); 121 122 $sqlParts['from']['media'] = 'media m'; 123 $sqlParts['where'][] = dbConditionInt('m.mediaid', $options['mediaids']); 124 $sqlParts['where']['mu'] = 'm.userid=u.userid'; 125 } 126 127 // mediatypeids 128 if ($options['mediatypeids'] !== null) { 129 zbx_value2array($options['mediatypeids']); 130 131 $sqlParts['from']['media'] = 'media m'; 132 $sqlParts['where'][] = dbConditionInt('m.mediatypeid', $options['mediatypeids']); 133 $sqlParts['where']['mu'] = 'm.userid=u.userid'; 134 } 135 136 // filter 137 if (is_array($options['filter'])) { 138 if (array_key_exists('autologout', $options['filter']) && $options['filter']['autologout'] !== null) { 139 $options['filter']['autologout'] = getTimeUnitFilters($options['filter']['autologout']); 140 } 141 142 if (array_key_exists('refresh', $options['filter']) && $options['filter']['refresh'] !== null) { 143 $options['filter']['refresh'] = getTimeUnitFilters($options['filter']['refresh']); 144 } 145 146 if (isset($options['filter']['passwd'])) { 147 self::exception(ZBX_API_ERROR_PARAMETERS, _('It is not possible to filter by user password.')); 148 } 149 150 $this->dbFilter('users u', $options, $sqlParts); 151 } 152 153 // search 154 if (is_array($options['search'])) { 155 if (isset($options['search']['passwd'])) { 156 self::exception(ZBX_API_ERROR_PARAMETERS, _('It is not possible to search by user password.')); 157 } 158 159 zbx_db_search('users u', $options, $sqlParts); 160 } 161 162 // limit 163 if (zbx_ctype_digit($options['limit']) && $options['limit']) { 164 $sqlParts['limit'] = $options['limit']; 165 } 166 167 $userIds = []; 168 169 $sqlParts = $this->applyQueryOutputOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts); 170 $sqlParts = $this->applyQuerySortOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts); 171 $res = DBselect(self::createSelectQueryFromParts($sqlParts), $sqlParts['limit']); 172 173 while ($user = DBfetch($res)) { 174 unset($user['passwd']); 175 176 if ($options['countOutput']) { 177 $result = $user['rowscount']; 178 } 179 else { 180 $userIds[$user['userid']] = $user['userid']; 181 182 $result[$user['userid']] = $user; 183 } 184 } 185 186 if ($options['countOutput']) { 187 return $result; 188 } 189 190 /* 191 * Adding objects 192 */ 193 if ($options['getAccess'] !== null) { 194 foreach ($result as $userid => $user) { 195 $result[$userid] += ['gui_access' => 0, 'debug_mode' => 0, 'users_status' => 0]; 196 } 197 198 $access = DBselect( 199 'SELECT ug.userid,MAX(g.gui_access) AS gui_access,'. 200 ' MAX(g.debug_mode) AS debug_mode,MAX(g.users_status) AS users_status'. 201 ' FROM usrgrp g,users_groups ug'. 202 ' WHERE '.dbConditionInt('ug.userid', $userIds). 203 ' AND g.usrgrpid=ug.usrgrpid'. 204 ' GROUP BY ug.userid' 205 ); 206 207 while ($userAccess = DBfetch($access)) { 208 $result[$userAccess['userid']] = zbx_array_merge($result[$userAccess['userid']], $userAccess); 209 } 210 } 211 212 if ($result) { 213 $result = $this->addRelatedObjects($options, $result); 214 } 215 216 // removing keys 217 if (!$options['preservekeys']) { 218 $result = zbx_cleanHashes($result); 219 } 220 221 return $result; 222 } 223 224 /** 225 * @param array $users 226 * 227 * @return array 228 */ 229 public function create(array $users) { 230 $this->validateCreate($users); 231 232 $ins_users = []; 233 234 foreach ($users as $user) { 235 unset($user['usrgrps'], $user['user_medias']); 236 $ins_users[] = $user; 237 } 238 $userids = DB::insert('users', $ins_users); 239 240 foreach ($users as $index => &$user) { 241 $user['userid'] = $userids[$index]; 242 } 243 unset($user); 244 245 $this->updateUsersGroups($users, __FUNCTION__); 246 $this->updateMedias($users, __FUNCTION__); 247 248 $this->addAuditBulk(AUDIT_ACTION_ADD, AUDIT_RESOURCE_USER, $users); 249 250 return ['userids' => $userids]; 251 } 252 253 /** 254 * @param array $users 255 * 256 * @throws APIException if the input is invalid. 257 */ 258 private function validateCreate(array &$users) { 259 if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) { 260 self::exception(ZBX_API_ERROR_PARAMETERS, _('You do not have permissions to create users.')); 261 } 262 263 $valid_themes = THEME_DEFAULT.','.implode(',', array_keys(Z::getThemes())); 264 $supported_locales = array_keys(getLocales()); 265 266 $api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['alias']], 'fields' => [ 267 'alias' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('users', 'alias')], 268 'name' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('users', 'name')], 269 'surname' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('users', 'surname')], 270 'passwd' => ['type' => API_STRING_UTF8, 'flags' => API_NOT_EMPTY, 'length' => 255], 271 'url' => ['type' => API_URL, 'length' => DB::getFieldLength('users', 'url')], 272 'autologin' => ['type' => API_INT32, 'in' => '0,1'], 273 'autologout' => ['type' => API_TIME_UNIT, 'flags' => API_NOT_EMPTY, 'in' => '0,90:'.SEC_PER_DAY], 274 'lang' => ['type' => API_STRING_UTF8, 'flags' => API_NOT_EMPTY, 'in' => implode(',', $supported_locales)], 275 'theme' => ['type' => API_STRING_UTF8, 'in' => $valid_themes, 'length' => DB::getFieldLength('users', 'theme')], 276 'type' => ['type' => API_INT32, 'in' => implode(',', [USER_TYPE_ZABBIX_USER, USER_TYPE_ZABBIX_ADMIN, USER_TYPE_SUPER_ADMIN])], 277 'refresh' => ['type' => API_TIME_UNIT, 'flags' => API_NOT_EMPTY, 'in' => '0:'.SEC_PER_HOUR], 278 'rows_per_page' => ['type' => API_INT32, 'in' => '1:999999'], 279 'usrgrps' => ['type' => API_OBJECTS, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'uniq' => [['usrgrpid']], 'fields' => [ 280 'usrgrpid' => ['type' => API_ID, 'flags' => API_REQUIRED] 281 ]], 282 'user_medias' => ['type' => API_OBJECTS, 'fields' => [ 283 'mediatypeid' => ['type' => API_ID, 'flags' => API_REQUIRED], 284 'sendto' => ['type' => API_STRINGS_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY | API_NORMALIZE], 285 'active' => ['type' => API_INT32, 'in' => implode(',', [MEDIA_STATUS_ACTIVE, MEDIA_STATUS_DISABLED])], 286 'severity' => ['type' => API_INT32, 'in' => '0:63'], 287 'period' => ['type' => API_TIME_PERIOD, 'flags' => API_ALLOW_USER_MACRO, 'length' => DB::getFieldLength('media', 'period')] 288 ]] 289 ]]; 290 if (!CApiInputValidator::validate($api_input_rules, $users, '/', $error)) { 291 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 292 } 293 294 foreach ($users as &$user) { 295 $user = $this->checkLoginOptions($user); 296 297 /* 298 * If user is created without a password (e.g. for GROUP_GUI_ACCESS_LDAP), store an empty string 299 * as his password in database. 300 */ 301 $user['passwd'] = (array_key_exists('passwd', $user)) ? md5($user['passwd']) : ''; 302 } 303 unset($user); 304 305 $this->checkDuplicates(zbx_objectValues($users, 'alias')); 306 $this->checkLanguages(zbx_objectValues($users, 'lang')); 307 $this->checkUserGroups($users, []); 308 $db_mediatypes = $this->checkMediaTypes($users); 309 $this->validateMediaRecipients($users, $db_mediatypes); 310 } 311 312 /** 313 * @param array $users 314 * 315 * @return array 316 */ 317 public function update(array $users) { 318 $this->validateUpdate($users, $db_users); 319 320 $upd_users = []; 321 322 foreach ($users as $user) { 323 $db_user = $db_users[$user['userid']]; 324 325 $upd_user = []; 326 327 // strings 328 $field_names = ['alias', 'name', 'surname', 'autologout', 'passwd', 'refresh', 'url', 'lang', 'theme']; 329 foreach ($field_names as $field_name) { 330 if (array_key_exists($field_name, $user) && $user[$field_name] !== $db_user[$field_name]) { 331 $upd_user[$field_name] = $user[$field_name]; 332 } 333 } 334 335 // integers 336 foreach (['autologin', 'type', 'rows_per_page'] as $field_name) { 337 if (array_key_exists($field_name, $user) && $user[$field_name] != $db_user[$field_name]) { 338 $upd_user[$field_name] = $user[$field_name]; 339 } 340 } 341 342 if ($upd_user) { 343 $upd_users[] = [ 344 'values' => $upd_user, 345 'where' => ['userid' => $user['userid']] 346 ]; 347 } 348 } 349 350 if ($upd_users) { 351 DB::update('users', $upd_users); 352 } 353 354 $this->updateUsersGroups($users, __FUNCTION__); 355 $this->updateMedias($users, __FUNCTION__); 356 357 $this->addAuditBulk(AUDIT_ACTION_UPDATE, AUDIT_RESOURCE_USER, $users, $db_users); 358 359 return ['userids' => zbx_objectValues($users, 'userid')]; 360 } 361 362 /** 363 * @param array $users 364 * @param array $db_users 365 * 366 * @throws APIException if the input is invalid. 367 */ 368 private function validateUpdate(array &$users, array &$db_users = null) { 369 $valid_themes = THEME_DEFAULT.','.implode(',', array_keys(Z::getThemes())); 370 $supported_locales = array_keys(getLocales()); 371 372 $api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['userid'], ['alias']], 'fields' => [ 373 'userid' => ['type' => API_ID, 'flags' => API_REQUIRED], 374 'alias' => ['type' => API_STRING_UTF8, 'flags' => API_NOT_EMPTY, 'length' => DB::getFieldLength('users', 'alias')], 375 'name' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('users', 'name')], 376 'surname' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('users', 'surname')], 377 'passwd' => ['type' => API_STRING_UTF8, 'flags' => API_NOT_EMPTY, 'length' => 255], 378 'url' => ['type' => API_URL, 'length' => DB::getFieldLength('users', 'url')], 379 'autologin' => ['type' => API_INT32, 'in' => '0,1'], 380 'autologout' => ['type' => API_TIME_UNIT, 'flags' => API_NOT_EMPTY, 'in' => '0,90:'.SEC_PER_DAY], 381 'lang' => ['type' => API_STRING_UTF8, 'flags' => API_NOT_EMPTY, 'in' => implode(',', $supported_locales)], 382 'theme' => ['type' => API_STRING_UTF8, 'in' => $valid_themes, 'length' => DB::getFieldLength('users', 'theme')], 383 'type' => ['type' => API_INT32, 'in' => implode(',', [USER_TYPE_ZABBIX_USER, USER_TYPE_ZABBIX_ADMIN, USER_TYPE_SUPER_ADMIN])], 384 'refresh' => ['type' => API_TIME_UNIT, 'flags' => API_NOT_EMPTY, 'in' => '0:'.SEC_PER_HOUR], 385 'rows_per_page' => ['type' => API_INT32, 'in' => '1:999999'], 386 'usrgrps' => ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY, 'uniq' => [['usrgrpid']], 'fields' => [ 387 'usrgrpid' => ['type' => API_ID, 'flags' => API_REQUIRED] 388 ]], 389 'user_medias' => ['type' => API_OBJECTS, 'fields' => [ 390 'mediatypeid' => ['type' => API_ID, 'flags' => API_REQUIRED], 391 'sendto' => ['type' => API_STRINGS_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY | API_NORMALIZE], 392 'active' => ['type' => API_INT32, 'in' => implode(',', [MEDIA_STATUS_ACTIVE, MEDIA_STATUS_DISABLED])], 393 'severity' => ['type' => API_INT32, 'in' => '0:63'], 394 'period' => ['type' => API_TIME_PERIOD, 'flags' => API_ALLOW_USER_MACRO, 'length' => DB::getFieldLength('media', 'period')] 395 ]] 396 ]]; 397 if (!CApiInputValidator::validate($api_input_rules, $users, '/', $error)) { 398 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 399 } 400 401 $db_users = $this->get([ 402 'output' => [], 403 'userids' => zbx_objectValues($users, 'userid'), 404 'editable' => true, 405 'preservekeys' => true 406 ]); 407 408 // 'passwd' can't be received by the user.get method 409 $db_users = DB::select('users', [ 410 'output' => ['userid', 'alias', 'name', 'surname', 'passwd', 'url', 'autologin', 'autologout', 'lang', 411 'theme', 'type', 'refresh', 'rows_per_page' 412 ], 413 'userids' => array_keys($db_users), 414 'preservekeys' => true 415 ]); 416 417 $aliases = []; 418 419 foreach ($users as &$user) { 420 if (!array_key_exists($user['userid'], $db_users)) { 421 self::exception(ZBX_API_ERROR_PERMISSIONS, 422 _('No permissions to referred object or it does not exist!') 423 ); 424 } 425 426 $db_user = $db_users[$user['userid']]; 427 428 if (array_key_exists('alias', $user) && $user['alias'] !== $db_user['alias']) { 429 if ($db_user['alias'] === ZBX_GUEST_USER) { 430 self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot rename guest user.')); 431 } 432 433 $aliases[] = $user['alias']; 434 } 435 436 $user = $this->checkLoginOptions($user); 437 438 if (array_key_exists('passwd', $user)) { 439 if ($db_user['alias'] == ZBX_GUEST_USER) { 440 self::exception(ZBX_API_ERROR_PARAMETERS, _('Not allowed to set password for user "guest".')); 441 } 442 443 $user['passwd'] = md5($user['passwd']); 444 } 445 } 446 unset($user); 447 448 if ($aliases) { 449 $this->checkDuplicates($aliases); 450 } 451 $this->checkLanguages(zbx_objectValues($users, 'lang')); 452 $this->checkUserGroups($users, $db_users); 453 $db_mediatypes = $this->checkMediaTypes($users); 454 $this->validateMediaRecipients($users, $db_mediatypes); 455 $this->checkHimself($users); 456 } 457 458 /** 459 * Check for duplicated users. 460 * 461 * @param array $aliases 462 * 463 * @throws APIException if user already exists. 464 */ 465 private function checkDuplicates(array $aliases) { 466 $db_users = DB::select('users', [ 467 'output' => ['alias'], 468 'filter' => ['alias' => $aliases], 469 'limit' => 1 470 ]); 471 472 if ($db_users) { 473 self::exception(ZBX_API_ERROR_PARAMETERS, 474 _s('User with alias "%s" already exists.', $db_users[0]['alias']) 475 ); 476 } 477 } 478 479 /** 480 * Check for valid user groups. 481 * 482 * @param array $users 483 * @param array $users[]['passwd'] (optional) 484 * @param array $users[]['usrgrps'] (optional) 485 * @param array $db_users 486 * @param array $db_users[]['passwd'] 487 * 488 * @throws APIException if user groups is not exists. 489 */ 490 private function checkUserGroups(array $users, array $db_users) { 491 $usrgrpids = []; 492 493 foreach ($users as $user) { 494 if (array_key_exists('usrgrps', $user)) { 495 foreach ($user['usrgrps'] as $usrgrp) { 496 $usrgrpids[$usrgrp['usrgrpid']] = true; 497 } 498 } 499 } 500 501 if (!$usrgrpids) { 502 return; 503 } 504 505 $usrgrpids = array_keys($usrgrpids); 506 507 $db_usrgrps = DB::select('usrgrp', [ 508 'output' => ['gui_access'], 509 'usrgrpids' => $usrgrpids, 510 'preservekeys' => true 511 ]); 512 513 foreach ($usrgrpids as $usrgrpid) { 514 if (!array_key_exists($usrgrpid, $db_usrgrps)) { 515 self::exception(ZBX_API_ERROR_PARAMETERS, _s('User group with ID "%1$s" is not available.', $usrgrpid)); 516 } 517 } 518 519 foreach ($users as $user) { 520 if (array_key_exists('passwd', $user)) { 521 $passwd = $user['passwd']; 522 } 523 elseif (array_key_exists('userid', $user) && array_key_exists($user['userid'], $db_users)) { 524 $passwd = $db_users[$user['userid']]['passwd']; 525 } 526 else { 527 $passwd = ''; 528 } 529 530 if ($passwd === '') { 531 $gui_access = self::getGroupGuiAccess($user, $db_usrgrps); 532 533 // Do not allow empty password for users with GROUP_GUI_ACCESS_INTERNAL. 534 if (in_array(GROUP_GUI_ACCESS_INTERNAL, $gui_access)) { 535 self::exception(ZBX_API_ERROR_PARAMETERS, 536 _s('Incorrect value for field "%1$s": %2$s.', 'passwd', _('cannot be empty')) 537 ); 538 } 539 } 540 } 541 } 542 543 /** 544 * Check if specified language has dependent locale installed. 545 * 546 * @param array $languages 547 * 548 * @throws APIException if language locale is not installed. 549 */ 550 private function checkLanguages(array $languages) { 551 foreach ($languages as $lang) { 552 if ($lang !== 'en_GB' && !setlocale(LC_MONETARY , zbx_locale_variants($lang))) { 553 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Language "%1$s" is not supported.', $lang)); 554 } 555 } 556 } 557 558 /** 559 * Get list of all current authentication options available to user. 560 * 561 * @param array $user 562 * @param string $user['usrgrps'][]['usrgrpid'] 563 * @param array $db_usrgrps 564 * @param int $db_usrgrps[usrgrpid]['gui_access'] 565 * 566 * @return array 567 */ 568 private static function getGroupGuiAccess($user, $db_usrgrps) { 569 $gui_access_arr = []; 570 $usrgrps = zbx_objectValues($user['usrgrps'], 'usrgrpid'); 571 572 $config = select_config(); 573 $system_gui_access = array_search($config['authentication_type'], [ 574 GROUP_GUI_ACCESS_INTERNAL => ZBX_AUTH_INTERNAL, 575 GROUP_GUI_ACCESS_LDAP => ZBX_AUTH_LDAP 576 ]); 577 578 foreach($usrgrps as $usergrp) { 579 if (array_key_exists($usergrp, $db_usrgrps)) { 580 $gui_access = (int) $db_usrgrps[$usergrp]['gui_access']; 581 $index = ($gui_access == GROUP_GUI_ACCESS_SYSTEM) ? $system_gui_access : $gui_access; 582 $gui_access_arr[$index] = ''; 583 } 584 } 585 586 return array_keys($gui_access_arr); 587 } 588 589 /** 590 * Check for valid media types. 591 * 592 * @param array $users Array of users. 593 * @param array $users[]['user_medias'] (optional) Array of user medias. 594 * 595 * @throws APIException if user media type does not exist. 596 * 597 * @return array Returns valid media types. 598 */ 599 private function checkMediaTypes(array $users) { 600 $mediatypeids = []; 601 602 foreach ($users as $user) { 603 if (array_key_exists('user_medias', $user)) { 604 foreach ($user['user_medias'] as $media) { 605 $mediatypeids[$media['mediatypeid']] = true; 606 } 607 } 608 } 609 610 if (!$mediatypeids) { 611 return []; 612 } 613 614 $mediatypeids = array_keys($mediatypeids); 615 616 $db_mediatypes = DB::select('media_type', [ 617 'output' => ['mediatypeid', 'type'], 618 'mediatypeids' => $mediatypeids, 619 'preservekeys' => true 620 ]); 621 622 foreach ($mediatypeids as $mediatypeid) { 623 if (!array_key_exists($mediatypeid, $db_mediatypes)) { 624 self::exception(ZBX_API_ERROR_PARAMETERS, 625 _s('Media type with ID "%1$s" is not available.', $mediatypeid) 626 ); 627 } 628 } 629 630 return $db_mediatypes; 631 } 632 633 /** 634 * Check if the passed 'sendto' value is a valid input according to the mediatype. Currently validates 635 * only e-mail media types. 636 * 637 * @param array $users Array of users. 638 * @param string $users[]['user_medias'][]['mediatypeid'] Media type ID. 639 * @param array|string $users[]['user_medias'][]['sendto'] Address where to send the alert. 640 * @param array $db_mediatypes List of available media types. 641 * 642 * @throws APIException if e-mail is not valid or exceeds maximum DB field length. 643 */ 644 private function validateMediaRecipients(array $users, array $db_mediatypes) { 645 if ($db_mediatypes) { 646 $email_mediatypes = []; 647 648 foreach ($db_mediatypes as $db_mediatype) { 649 if ($db_mediatype['type'] == MEDIA_TYPE_EMAIL) { 650 $email_mediatypes[$db_mediatype['mediatypeid']] = true; 651 } 652 } 653 654 $max_length = DB::getFieldLength('media', 'sendto'); 655 $email_validator = new CEmailValidator(); 656 657 foreach ($users as $user) { 658 if (array_key_exists('user_medias', $user)) { 659 foreach ($user['user_medias'] as $media) { 660 /* 661 * For non-email media types only one value allowed. Since value is normalized, need to validate 662 * if array contains only one item. If there are more than one string, error message is 663 * displayed, indicating that passed value is not a string. 664 */ 665 if (!array_key_exists($media['mediatypeid'], $email_mediatypes) 666 && count($media['sendto']) > 1) { 667 self::exception(ZBX_API_ERROR_PARAMETERS, 668 _s('Invalid parameter "%1$s": %2$s.', 'sendto', _('a character string is expected')) 669 ); 670 } 671 672 /* 673 * If input value is an array with empty string, ApiInputValidator identifies it as valid since 674 * values are normalized. That's why value must be revalidated. 675 */ 676 foreach ($media['sendto'] as $sendto) { 677 if ($sendto === '') { 678 self::exception(ZBX_API_ERROR_PARAMETERS, 679 _s('Invalid parameter "%1$s": %2$s.', 'sendto', _('cannot be empty')) 680 ); 681 } 682 } 683 684 /* 685 * If media type is email, validate each given string against email pattern. 686 * Additionally, total length of emails must be checked, because all media type emails are 687 * separated by newline and stored as a string in single database field. Newline characters 688 * consumes extra space, so additional validation must be made. 689 */ 690 if (array_key_exists($media['mediatypeid'], $email_mediatypes)) { 691 foreach ($media['sendto'] as $sendto) { 692 if (!$email_validator->validate($sendto)) { 693 self::exception(ZBX_API_ERROR_PARAMETERS, 694 _s('Invalid email address for media type with ID "%1$s".', 695 $media['mediatypeid'] 696 ) 697 ); 698 } 699 elseif (strlen(implode("\n", $media['sendto'])) > $max_length) { 700 self::exception(ZBX_API_ERROR_PARAMETERS, 701 _s('Maximum total length of email address exceeded for media type with ID "%1$s".', 702 $media['mediatypeid'] 703 ) 704 ); 705 } 706 } 707 } 708 } 709 } 710 } 711 } 712 } 713 714 /** 715 * Additional check to exclude an opportunity to deactivate himself. 716 * 717 * @param array $users 718 * @param array $users[]['usrgrps'] (optional) 719 * 720 * @throws APIException 721 */ 722 private function checkHimself(array $users) { 723 foreach ($users as $user) { 724 if (bccomp($user['userid'], self::$userData['userid']) == 0) { 725 if (array_key_exists('type', $user)) { 726 self::exception(ZBX_API_ERROR_PARAMETERS, _('User cannot change their user type.')); 727 } 728 729 if (array_key_exists('usrgrps', $user)) { 730 $db_usrgrps = DB::select('usrgrp', [ 731 'output' => ['gui_access', 'users_status'], 732 'usrgrpids' => zbx_objectValues($user['usrgrps'], 'usrgrpid') 733 ]); 734 735 foreach ($db_usrgrps as $db_usrgrp) { 736 if ($db_usrgrp['gui_access'] == GROUP_GUI_ACCESS_DISABLED 737 || $db_usrgrp['users_status'] == GROUP_STATUS_DISABLED) { 738 self::exception(ZBX_API_ERROR_PARAMETERS, 739 _('User cannot add himself to a disabled group or a group with disabled GUI access.') 740 ); 741 } 742 } 743 } 744 745 break; 746 } 747 } 748 } 749 750 /** 751 * Additional check to exclude an opportunity to enable auto-login and auto-logout options together.. 752 * 753 * @param array $user 754 * @param int $user[]['autologin'] (optional) 755 * @param string $user[]['autologout'] (optional) 756 * 757 * @throws APIException 758 */ 759 private function checkLoginOptions(array $user) { 760 if (!array_key_exists('autologout', $user) && array_key_exists('autologin', $user) && $user['autologin'] != 0) { 761 $user['autologout'] = '0'; 762 } 763 764 if (!array_key_exists('autologin', $user) && array_key_exists('autologout', $user) 765 && timeUnitToSeconds($user['autologout']) != 0) { 766 $user['autologin'] = 0; 767 } 768 769 if (array_key_exists('autologin', $user) && array_key_exists('autologout', $user) 770 && $user['autologin'] != 0 && timeUnitToSeconds($user['autologout']) != 0) { 771 self::exception(ZBX_API_ERROR_PARAMETERS, 772 _('Auto-login and auto-logout options cannot be enabled together.') 773 ); 774 } 775 776 return $user; 777 } 778 779 /** 780 * Update table "users_groups". 781 * 782 * @param array $users 783 * @param string $method 784 */ 785 private function updateUsersGroups(array $users, $method) { 786 $users_groups = []; 787 788 foreach ($users as $user) { 789 if (array_key_exists('usrgrps', $user)) { 790 $users_groups[$user['userid']] = []; 791 792 foreach ($user['usrgrps'] as $usrgrp) { 793 $users_groups[$user['userid']][$usrgrp['usrgrpid']] = true; 794 } 795 } 796 } 797 798 if (!$users_groups) { 799 return; 800 } 801 802 $db_users_groups = ($method === 'update') 803 ? DB::select('users_groups', [ 804 'output' => ['id', 'usrgrpid', 'userid'], 805 'filter' => ['userid' => array_keys($users_groups)] 806 ]) 807 : []; 808 809 $ins_users_groups = []; 810 $del_ids = []; 811 812 foreach ($db_users_groups as $db_user_group) { 813 if (array_key_exists($db_user_group['usrgrpid'], $users_groups[$db_user_group['userid']])) { 814 unset($users_groups[$db_user_group['userid']][$db_user_group['usrgrpid']]); 815 } 816 else { 817 $del_ids[] = $db_user_group['id']; 818 } 819 } 820 821 foreach ($users_groups as $userid => $usrgrpids) { 822 foreach (array_keys($usrgrpids) as $usrgrpid) { 823 $ins_users_groups[] = [ 824 'userid' => $userid, 825 'usrgrpid' => $usrgrpid 826 ]; 827 } 828 } 829 830 if ($ins_users_groups) { 831 DB::insertBatch('users_groups', $ins_users_groups); 832 } 833 834 if ($del_ids) { 835 DB::delete('users_groups', ['id' => $del_ids]); 836 } 837 } 838 839 /** 840 * Auxiliary function for updateMedias(). 841 * 842 * @param array $medias 843 * @param string $mediatypeid 844 * @param string $sendto 845 * 846 * @return int 847 */ 848 private function getSimilarMedia(array $medias, $mediatypeid, $sendto) { 849 foreach ($medias as $index => $media) { 850 if (bccomp($media['mediatypeid'], $mediatypeid) == 0 && $media['sendto'] === $sendto) { 851 return $index; 852 } 853 } 854 855 return -1; 856 } 857 858 /** 859 * Update table "media". 860 * 861 * @param array $users 862 * @param string $method 863 */ 864 private function updateMedias(array $users, $method) { 865 $medias = []; 866 867 foreach ($users as $user) { 868 if (array_key_exists('user_medias', $user)) { 869 $medias[$user['userid']] = []; 870 871 foreach ($user['user_medias'] as $media) { 872 $media['sendto'] = implode("\n", $media['sendto']); 873 $medias[$user['userid']][] = $media; 874 } 875 } 876 } 877 878 if (!$medias) { 879 return; 880 } 881 882 $db_medias = ($method === 'update') 883 ? DB::select('media', [ 884 'output' => ['mediaid', 'userid', 'mediatypeid', 'sendto', 'active', 'severity', 'period'], 885 'filter' => ['userid' => array_keys($medias)] 886 ]) 887 : []; 888 889 $ins_medias = []; 890 $upd_medias = []; 891 $del_mediaids = []; 892 893 foreach ($db_medias as $db_media) { 894 $index = $this->getSimilarMedia($medias[$db_media['userid']], $db_media['mediatypeid'], 895 $db_media['sendto'] 896 ); 897 898 if ($index != -1) { 899 $media = $medias[$db_media['userid']][$index]; 900 901 $upd_media = []; 902 903 if (array_key_exists('active', $media) && $media['active'] != $db_media['active']) { 904 $upd_media['active'] = $media['active']; 905 } 906 if (array_key_exists('severity', $media) && $media['severity'] != $db_media['severity']) { 907 $upd_media['severity'] = $media['severity']; 908 } 909 if (array_key_exists('period', $media) && $media['period'] !== $db_media['period']) { 910 $upd_media['period'] = $media['period']; 911 } 912 913 if ($upd_media) { 914 $upd_medias[] = [ 915 'values' => $upd_media, 916 'where' => ['mediaid' => $db_media['mediaid']] 917 ]; 918 } 919 920 unset($medias[$db_media['userid']][$index]); 921 } 922 else { 923 $del_mediaids[] = $db_media['mediaid']; 924 } 925 } 926 927 foreach ($medias as $userid => $user_medias) { 928 foreach ($user_medias as $media) { 929 $ins_medias[] = ['userid' => $userid] + $media; 930 } 931 } 932 933 if ($ins_medias) { 934 DB::insert('media', $ins_medias); 935 } 936 937 if ($upd_medias) { 938 DB::update('media', $upd_medias); 939 } 940 941 if ($del_mediaids) { 942 DB::delete('media', ['mediaid' => $del_mediaids]); 943 } 944 } 945 946 /** 947 * @param array $userids 948 * 949 * @return array 950 */ 951 public function delete(array $userids) { 952 $this->validateDelete($userids, $db_users); 953 954 DB::delete('media', ['userid' => $userids]); 955 DB::delete('profiles', ['userid' => $userids]); 956 DB::delete('users_groups', ['userid' => $userids]); 957 DB::delete('users', ['userid' => $userids]); 958 959 $this->addAuditBulk(AUDIT_ACTION_DELETE, AUDIT_RESOURCE_USER, $db_users); 960 961 return ['userids' => $userids]; 962 } 963 964 /** 965 * @param array $userids 966 * @param array $db_users 967 * 968 * @throws APIException if the input is invalid. 969 */ 970 private function validateDelete(array &$userids, array &$db_users = null) { 971 $api_input_rules = ['type' => API_IDS, 'flags' => API_NOT_EMPTY, 'uniq' => true]; 972 if (!CApiInputValidator::validate($api_input_rules, $userids, '/', $error)) { 973 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 974 } 975 976 $db_users = $this->get([ 977 'output' => ['userid', 'alias'], 978 'userids' => $userids, 979 'editable' => true, 980 'preservekeys' => true 981 ]); 982 983 foreach ($userids as $userid) { 984 if (!array_key_exists($userid, $db_users)) { 985 self::exception(ZBX_API_ERROR_PERMISSIONS, 986 _('No permissions to referred object or it does not exist!') 987 ); 988 } 989 990 $db_user = $db_users[$userid]; 991 992 if (bccomp($userid, self::$userData['userid']) == 0) { 993 self::exception(ZBX_API_ERROR_PARAMETERS, _('User is not allowed to delete himself.')); 994 } 995 996 if ($db_user['alias'] == ZBX_GUEST_USER) { 997 self::exception(ZBX_API_ERROR_PARAMETERS, 998 _s('Cannot delete Zabbix internal user "%1$s", try disabling that user.', ZBX_GUEST_USER) 999 ); 1000 } 1001 } 1002 1003 // Check if deleted users used in actions. 1004 $db_actions = DBselect( 1005 'SELECT a.name,om.userid'. 1006 ' FROM opmessage_usr om,operations o,actions a'. 1007 ' WHERE om.operationid=o.operationid'. 1008 ' AND o.actionid=a.actionid'. 1009 ' AND '.dbConditionInt('om.userid', $userids), 1010 1 1011 ); 1012 1013 if ($db_action = DBfetch($db_actions)) { 1014 self::exception(ZBX_API_ERROR_PARAMETERS, _s('User "%1$s" is used in "%2$s" action.', 1015 $db_users[$db_action['userid']]['alias'], $db_action['name'] 1016 )); 1017 } 1018 1019 // Check if deleted users have a map. 1020 $db_maps = API::Map()->get([ 1021 'output' => ['name', 'userid'], 1022 'userids' => $userids, 1023 'limit' => 1 1024 ]); 1025 1026 if ($db_maps) { 1027 self::exception(ZBX_API_ERROR_PARAMETERS, 1028 _s('User "%1$s" is map "%2$s" owner.', $db_users[$db_maps[0]['userid']]['alias'], $db_maps[0]['name']) 1029 ); 1030 } 1031 1032 // Check if deleted users have a screen. 1033 $db_screens = API::Screen()->get([ 1034 'output' => ['name', 'userid'], 1035 'userids' => $userids, 1036 'limit' => 1 1037 ]); 1038 1039 if ($db_screens) { 1040 self::exception(ZBX_API_ERROR_PARAMETERS, 1041 _s('User "%1$s" is screen "%2$s" owner.', $db_users[$db_screens[0]['userid']]['alias'], 1042 $db_screens[0]['name'] 1043 ) 1044 ); 1045 } 1046 1047 // Check if deleted users have a slide show. 1048 $db_slideshows = DB::select('slideshows', [ 1049 'output' => ['name', 'userid'], 1050 'filter' => ['userid' => $userids], 1051 'limit' => 1 1052 ]); 1053 1054 if ($db_slideshows) { 1055 self::exception(ZBX_API_ERROR_PARAMETERS, 1056 _s('User "%1$s" is slide show "%2$s" owner.', $db_users[$db_slideshows[0]['userid']]['alias'], 1057 $db_slideshows[0]['name'] 1058 ) 1059 ); 1060 } 1061 1062 // Check if deleted users have dashboards. 1063 $db_dashboards = API::Dashboard()->get([ 1064 'output' => ['name', 'userid'], 1065 'filter' => ['userid' => $userids], 1066 'limit' => 1 1067 ]); 1068 1069 if ($db_dashboards) { 1070 self::exception(ZBX_API_ERROR_PARAMETERS, 1071 _s('User "%1$s" is dashboard "%2$s" owner.', $db_users[$db_dashboards[0]['userid']]['alias'], 1072 $db_dashboards[0]['name'] 1073 ) 1074 ); 1075 } 1076 } 1077 1078 /** 1079 * Authenticate a user using LDAP. 1080 * 1081 * The $user array must have the following attributes: 1082 * - user - user name 1083 * - password - user password 1084 * 1085 * @param array $user 1086 * 1087 * @return bool 1088 */ 1089 protected function ldapLogin(array $user) { 1090 $config = select_config(); 1091 $cnf = []; 1092 1093 foreach ($config as $id => $value) { 1094 if (strpos($id, 'ldap_') !== false) { 1095 $cnf[str_replace('ldap_', '', $id)] = $config[$id]; 1096 } 1097 } 1098 1099 $ldap_status = (new CFrontendSetup())->checkPhpLdapModule(); 1100 1101 if ($ldap_status['result'] != CFrontendSetup::CHECK_OK) { 1102 self::exception(ZBX_API_ERROR_PARAMETERS, $ldap_status['error']); 1103 } 1104 1105 $ldapValidator = new CLdapAuthValidator(['conf' => $cnf]); 1106 1107 if ($ldapValidator->validate($user)) { 1108 return true; 1109 } 1110 else { 1111 self::exception($ldapValidator->isConnectionError() 1112 ? ZBX_API_ERROR_PARAMETERS 1113 : ZBX_API_ERROR_PERMISSIONS, 1114 $ldapValidator->getError() 1115 ); 1116 } 1117 } 1118 1119 public function logout($user) { 1120 $api_input_rules = ['type' => API_OBJECT, 'fields' => []]; 1121 if (!CApiInputValidator::validate($api_input_rules, $user, '/', $error)) { 1122 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 1123 } 1124 1125 $sessionid = self::$userData['sessionid']; 1126 1127 $db_sessions = DB::select('sessions', [ 1128 'output' => ['userid'], 1129 'filter' => [ 1130 'sessionid' => $sessionid, 1131 'status' => ZBX_SESSION_ACTIVE 1132 ], 1133 'limit' => 1 1134 ]); 1135 1136 if (!$db_sessions) { 1137 self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot logout.')); 1138 } 1139 1140 DB::delete('sessions', [ 1141 'status' => ZBX_SESSION_PASSIVE, 1142 'userid' => $db_sessions[0]['userid'] 1143 ]); 1144 DB::update('sessions', [ 1145 'values' => ['status' => ZBX_SESSION_PASSIVE], 1146 'where' => ['sessionid' => $sessionid] 1147 ]); 1148 1149 $this->addAuditDetails(AUDIT_ACTION_LOGOUT, AUDIT_RESOURCE_USER); 1150 1151 self::$userData = null; 1152 1153 return true; 1154 } 1155 1156 /** 1157 * @param array $user 1158 * 1159 * @return string|array 1160 */ 1161 public function login(array $user) { 1162 $api_input_rules = ['type' => API_OBJECT, 'fields' => [ 1163 'user' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED, 'length' => 255], 1164 'password' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED, 'length' => 255], 1165 'userData' => ['type' => API_FLAG] 1166 ]]; 1167 if (!CApiInputValidator::validate($api_input_rules, $user, '/', $error)) { 1168 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 1169 } 1170 1171 $config = select_config(); 1172 $group_to_auth_map = [ 1173 GROUP_GUI_ACCESS_SYSTEM => $config['authentication_type'], 1174 GROUP_GUI_ACCESS_INTERNAL => ZBX_AUTH_INTERNAL, 1175 GROUP_GUI_ACCESS_LDAP => ZBX_AUTH_LDAP, 1176 GROUP_GUI_ACCESS_DISABLED => $config['authentication_type'] 1177 ]; 1178 1179 $db_user = $this->findByAlias($user['user'], ($config['ldap_case_sensitive'] == ZBX_AUTH_CASE_SENSITIVE), 1180 $config['authentication_type'], true 1181 ); 1182 1183 if ($db_user['attempt_failed'] >= ZBX_LOGIN_ATTEMPTS) { 1184 $sec_left = ZBX_LOGIN_BLOCK - (time() - $db_user['attempt_clock']); 1185 1186 if ($sec_left > 0) { 1187 self::exception(ZBX_API_ERROR_PERMISSIONS, 1188 _('Incorrect user name or password or account is temporarily blocked.') 1189 ); 1190 } 1191 } 1192 1193 try { 1194 switch ($group_to_auth_map[$db_user['gui_access']]) { 1195 case ZBX_AUTH_LDAP: 1196 $this->ldapLogin($user); 1197 break; 1198 1199 case ZBX_AUTH_INTERNAL: 1200 if (md5($user['password']) !== $db_user['passwd']) { 1201 self::exception(ZBX_API_ERROR_PERMISSIONS, 1202 _('Incorrect user name or password or account is temporarily blocked.') 1203 ); 1204 } 1205 break; 1206 1207 default: 1208 self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions for system access.')); 1209 break; 1210 } 1211 } 1212 catch (APIException $e) { 1213 if ($e->getCode() == ZBX_API_ERROR_PERMISSIONS) { 1214 ++$db_user['attempt_failed']; 1215 } 1216 1217 DB::update('users', [ 1218 'values' => [ 1219 'attempt_failed' => $db_user['attempt_failed'], 1220 'attempt_clock' => time(), 1221 'attempt_ip' => substr($db_user['userip'], 0, 39) 1222 ], 1223 'where' => ['userid' => $db_user['userid']] 1224 ]); 1225 1226 $this->addAuditDetails(AUDIT_ACTION_LOGIN, AUDIT_RESOURCE_USER, _('Login failed.'), $db_user['userid'], 1227 $db_user['userip'] 1228 ); 1229 1230 if ($e->getCode() == ZBX_API_ERROR_PERMISSIONS && $db_user['attempt_failed'] >= ZBX_LOGIN_ATTEMPTS) { 1231 self::exception(ZBX_API_ERROR_PERMISSIONS, 1232 _('Incorrect user name or password or account is temporarily blocked.') 1233 ); 1234 } 1235 1236 self::exception(ZBX_API_ERROR_PERMISSIONS, $e->getMessage()); 1237 } 1238 1239 // Start session. 1240 unset($db_user['passwd']); 1241 $db_user = $this->createSession($user, $db_user); 1242 self::$userData = $db_user; 1243 1244 $this->addAuditDetails(AUDIT_ACTION_LOGIN, AUDIT_RESOURCE_USER); 1245 1246 return array_key_exists('userData', $user) && $user['userData'] ? $db_user : $db_user['sessionid']; 1247 } 1248 1249 /** 1250 * Method is ONLY for internal use! 1251 * Login user by alias. Return array with user data. 1252 * 1253 * @param string $alias Authenticated user alias value. 1254 * @param bool $api_call Check is method called via API call or from local php file. 1255 * 1256 * @return array 1257 */ 1258 public function loginHttp($alias, $api_call = true) { 1259 if ($api_call) { 1260 return self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect method "%1$s.%2$s".', 'user', 'loginHttp')); 1261 } 1262 1263 $config = select_config(); 1264 $db_user = $this->findByAlias($alias, ($config['http_case_sensitive'] == ZBX_AUTH_CASE_SENSITIVE), 1265 $config['authentication_type'], false 1266 ); 1267 1268 unset($db_user['passwd']); 1269 $db_user = $this->createSession([ 1270 'user' => $alias, 1271 'password' => mt_rand() 1272 ], $db_user); 1273 self::$userData = $db_user; 1274 1275 $this->addAuditDetails(AUDIT_ACTION_LOGIN, AUDIT_RESOURCE_USER); 1276 return $db_user; 1277 } 1278 1279 /** 1280 * Check if session id is authenticated. 1281 * 1282 * @param array $session 1283 * @param string $session[]['sessionid'] (required) session id to be checked 1284 * @param bool $session[]['extend'] (optional) extend session (update lastaccess time) 1285 * 1286 * @return array 1287 */ 1288 public function checkAuthentication(array $session) { 1289 $api_input_rules = ['type' => API_OBJECT, 'fields' => [ 1290 'sessionid' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength('sessions', 'sessionid')], 1291 'extend' => ['type' => API_BOOLEAN, 'default' => true] 1292 ]]; 1293 if (!CApiInputValidator::validate($api_input_rules, $session, '/', $error)) { 1294 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 1295 } 1296 1297 $sessionid = $session['sessionid']; 1298 1299 // access DB only once per page load 1300 if (self::$userData !== null && self::$userData['sessionid'] === $sessionid) { 1301 return self::$userData; 1302 } 1303 1304 $time = time(); 1305 1306 $db_sessions = DB::select('sessions', [ 1307 'output' => ['userid', 'lastaccess'], 1308 'sessionids' => $sessionid, 1309 'filter' => ['status' => ZBX_SESSION_ACTIVE] 1310 ]); 1311 1312 if (!$db_sessions) { 1313 self::exception(ZBX_API_ERROR_PARAMETERS, _('Session terminated, re-login, please.')); 1314 } 1315 1316 $db_session = $db_sessions[0]; 1317 1318 $db_users = DB::select('users', [ 1319 'output' => ['userid', 'alias', 'name', 'surname', 'url', 'autologin', 'autologout', 'lang', 'refresh', 1320 'type', 'theme', 'attempt_failed', 'attempt_ip', 'attempt_clock', 'rows_per_page' 1321 ], 1322 'userids' => $db_session['userid'] 1323 ]); 1324 1325 if (!$db_users) { 1326 self::exception(ZBX_API_ERROR_PARAMETERS, _('Session terminated, re-login, please.')); 1327 } 1328 1329 $db_user = $db_users[0]; 1330 1331 $usrgrps = $this->getUserGroupsData($db_user['userid']); 1332 1333 $db_user['sessionid'] = $sessionid; 1334 $db_user['debug_mode'] = $usrgrps['debug_mode']; 1335 $db_user['userip'] = $usrgrps['userip']; 1336 $db_user['gui_access'] = $usrgrps['gui_access']; 1337 1338 $autologout = timeUnitToSeconds($db_user['autologout']); 1339 1340 // Check system permissions. 1341 if (($autologout != 0 && $db_session['lastaccess'] + $autologout <= $time) 1342 || $usrgrps['users_status'] == GROUP_STATUS_DISABLED) { 1343 DB::delete('sessions', [ 1344 'status' => ZBX_SESSION_PASSIVE, 1345 'userid' => $db_user['userid'] 1346 ]); 1347 DB::update('sessions', [ 1348 'values' => ['status' => ZBX_SESSION_PASSIVE], 1349 'where' => ['sessionid' => $sessionid] 1350 ]); 1351 1352 self::exception(ZBX_API_ERROR_PARAMETERS, _('Session terminated, re-login, please.')); 1353 } 1354 1355 if ($session['extend'] && $time != $db_session['lastaccess']) { 1356 DB::update('sessions', [ 1357 'values' => ['lastaccess' => $time], 1358 'where' => ['sessionid' => $sessionid] 1359 ]); 1360 } 1361 1362 self::$userData = $db_user; 1363 1364 return $db_user; 1365 } 1366 1367 private function getUserGroupsData($userid) { 1368 $usrgrps = [ 1369 'debug_mode' => GROUP_DEBUG_MODE_DISABLED, 1370 'userip' => (array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER) && $_SERVER['HTTP_X_FORWARDED_FOR'] !== '') 1371 ? $_SERVER['HTTP_X_FORWARDED_FOR'] 1372 : $_SERVER['REMOTE_ADDR'], 1373 'users_status' => GROUP_STATUS_ENABLED, 1374 'gui_access' => GROUP_GUI_ACCESS_SYSTEM 1375 ]; 1376 1377 $db_usrgrps = DBselect( 1378 'SELECT g.debug_mode,g.users_status,g.gui_access'. 1379 ' FROM usrgrp g,users_groups ug'. 1380 ' WHERE g.usrgrpid=ug.usrgrpid'. 1381 ' AND ug.userid='.$userid 1382 ); 1383 1384 while ($db_usrgrp = DBfetch($db_usrgrps)) { 1385 if ($db_usrgrp['debug_mode'] == GROUP_DEBUG_MODE_ENABLED) { 1386 $usrgrps['debug_mode'] = GROUP_DEBUG_MODE_ENABLED; 1387 } 1388 if ($db_usrgrp['users_status'] == GROUP_STATUS_DISABLED) { 1389 $usrgrps['users_status'] = GROUP_STATUS_DISABLED; 1390 } 1391 if ($db_usrgrp['gui_access'] > $usrgrps['gui_access']) { 1392 $usrgrps['gui_access'] = $db_usrgrp['gui_access']; 1393 } 1394 } 1395 1396 return $usrgrps; 1397 } 1398 1399 protected function addRelatedObjects(array $options, array $result) { 1400 $result = parent::addRelatedObjects($options, $result); 1401 1402 $userIds = zbx_objectValues($result, 'userid'); 1403 1404 // adding usergroups 1405 if ($options['selectUsrgrps'] !== null && $options['selectUsrgrps'] != API_OUTPUT_COUNT) { 1406 $relationMap = $this->createRelationMap($result, 'userid', 'usrgrpid', 'users_groups'); 1407 1408 $dbUserGroups = API::UserGroup()->get([ 1409 'output' => $options['selectUsrgrps'], 1410 'usrgrpids' => $relationMap->getRelatedIds(), 1411 'preservekeys' => true 1412 ]); 1413 1414 $result = $relationMap->mapMany($result, $dbUserGroups, 'usrgrps'); 1415 } 1416 1417 // adding medias 1418 if ($options['selectMedias'] !== null && $options['selectMedias'] != API_OUTPUT_COUNT) { 1419 $db_medias = API::getApiService()->select('media', [ 1420 'output' => $this->outputExtend($options['selectMedias'], ['userid', 'mediaid', 'mediatypeid']), 1421 'filter' => ['userid' => $userIds], 1422 'preservekeys' => true 1423 ]); 1424 1425 // 'sendto' parameter in media types with 'type' == MEDIA_TYPE_EMAIL are returned as array. 1426 if (($options['selectMedias'] === API_OUTPUT_EXTEND || in_array('sendto', $options['selectMedias'])) 1427 && $db_medias) { 1428 $db_email_medias = DB::select('media_type', [ 1429 'output' => [], 1430 'filter' => [ 1431 'mediatypeid' => zbx_objectValues($db_medias, 'mediatypeid'), 1432 'type' => MEDIA_TYPE_EMAIL 1433 ], 1434 'preservekeys' => true 1435 ]); 1436 1437 foreach ($db_medias as &$db_media) { 1438 if (array_key_exists($db_media['mediatypeid'], $db_email_medias)) { 1439 $db_media['sendto'] = explode("\n", $db_media['sendto']); 1440 } 1441 } 1442 unset($db_media); 1443 } 1444 1445 $relationMap = $this->createRelationMap($db_medias, 'userid', 'mediaid'); 1446 1447 $db_medias = $this->unsetExtraFields($db_medias, ['userid', 'mediaid', 'mediatypeid'], 1448 $options['selectMedias'] 1449 ); 1450 $result = $relationMap->mapMany($result, $db_medias, 'medias'); 1451 } 1452 1453 // adding media types 1454 if ($options['selectMediatypes'] !== null && $options['selectMediatypes'] != API_OUTPUT_COUNT) { 1455 $relationMap = $this->createRelationMap($result, 'userid', 'mediatypeid', 'media'); 1456 $mediaTypes = API::Mediatype()->get([ 1457 'output' => $options['selectMediatypes'], 1458 'mediatypeids' => $relationMap->getRelatedIds(), 1459 'preservekeys' => true 1460 ]); 1461 $result = $relationMap->mapMany($result, $mediaTypes, 'mediatypes'); 1462 } 1463 1464 return $result; 1465 } 1466 1467 /** 1468 * Initialize session for user. Returns user data array with valid sessionid. 1469 * 1470 * @param array $user Authentication credentials. 1471 * @param string $user['user'] User alias value. 1472 * @param string $user['password'] User password, is used in sessionid generation. 1473 * @param array $db_user User data from database. 1474 * 1475 * @return array 1476 */ 1477 private function createSession($user, $db_user) { 1478 $db_user['sessionid'] = md5(microtime().md5($user['password']).$user['user'].mt_rand()); 1479 1480 DB::insert('sessions', [[ 1481 'sessionid' => $db_user['sessionid'], 1482 'userid' => $db_user['userid'], 1483 'lastaccess' => time(), 1484 'status' => ZBX_SESSION_ACTIVE 1485 ]], false); 1486 1487 if ($db_user['attempt_failed'] != 0) { 1488 DB::update('users', [ 1489 'values' => ['attempt_failed' => 0], 1490 'where' => ['userid' => $db_user['userid']] 1491 ]); 1492 } 1493 1494 return $db_user; 1495 } 1496 1497 /** 1498 * Find user by alias. Return user data from database. 1499 * 1500 * @param string $alias User alias to search for. 1501 * @param bool $case_sensitive Perform case sensitive search. 1502 * @param int $default_auth System default authentication type. 1503 * @param bool $do_group_check Is actual only when $case_sensitive equals false. In HTTP authentication case 1504 * user alias string is case insensitive string even for groups with frontend 1505 * access GROUP_GUI_ACCESS_INTERNAL. 1506 * 1507 * @return array 1508 */ 1509 private function findByAlias($alias, $case_sensitive, $default_auth, $do_group_check) { 1510 $db_users = []; 1511 $group_to_auth_map = [ 1512 GROUP_GUI_ACCESS_SYSTEM => $default_auth, 1513 GROUP_GUI_ACCESS_INTERNAL => ZBX_AUTH_INTERNAL, 1514 GROUP_GUI_ACCESS_LDAP => ZBX_AUTH_LDAP, 1515 GROUP_GUI_ACCESS_DISABLED => $default_auth 1516 ]; 1517 $fields = ['userid', 'alias', 'name', 'surname', 'url', 'autologin', 'autologout', 'lang', 'refresh', 1518 'type', 'theme', 'attempt_failed', 'attempt_ip', 'attempt_clock', 'rows_per_page', 'passwd' 1519 ]; 1520 1521 if ($case_sensitive) { 1522 $db_users = DB::select('users', [ 1523 'output' => $fields, 1524 'filter' => ['alias' => $alias] 1525 ]); 1526 } 1527 else { 1528 $db_users_rows = DBfetchArray(DBselect( 1529 'SELECT '.implode(',', $fields). 1530 ' FROM users'. 1531 ' WHERE LOWER(alias)='.zbx_dbstr(strtolower($alias)) 1532 )); 1533 1534 if ($do_group_check) { 1535 // Users with ZBX_AUTH_INTERNAL access attribute 'alias' is always case sensitive. 1536 foreach($db_users_rows as $db_user_row) { 1537 $permissions = $this->getUserGroupsData($db_user_row['userid']); 1538 1539 if ($group_to_auth_map[$permissions['gui_access']] != ZBX_AUTH_INTERNAL 1540 || $db_user_row['alias'] === $alias) { 1541 $db_users[] = $db_user_row; 1542 } 1543 } 1544 } 1545 else { 1546 $db_users = $db_users_rows; 1547 } 1548 } 1549 1550 if (!$db_users) { 1551 self::exception(ZBX_API_ERROR_PARAMETERS, 1552 _('Incorrect user name or password or account is temporarily blocked.') 1553 ); 1554 } 1555 elseif (count($db_users) > 1) { 1556 self::exception(ZBX_API_ERROR_PARAMETERS, 1557 _s('Authentication failed: %1$s.', _('supplied credentials are not unique')) 1558 ); 1559 } 1560 1561 $db_user = reset($db_users); 1562 $usrgrps = $this->getUserGroupsData($db_user['userid']); 1563 1564 if ($usrgrps['users_status'] == GROUP_STATUS_DISABLED) { 1565 self::exception(ZBX_API_ERROR_PARAMETERS, _('No permissions for system access.')); 1566 } 1567 1568 $db_user['debug_mode'] = $usrgrps['debug_mode']; 1569 $db_user['userip'] = $usrgrps['userip']; 1570 $db_user['gui_access'] = $usrgrps['gui_access']; 1571 1572 return $db_user; 1573 } 1574} 1575