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