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 auditlog records.
24 */
25class CAuditLog extends CApiService {
26
27	public const ACCESS_RULES = [
28		'get' => ['min_user_type' => USER_TYPE_SUPER_ADMIN]
29	];
30
31	/**
32	 * @var string Database table name.
33	 */
34	protected $tableName = 'auditlog';
35
36	/**
37	 * @var string Database table name alias.
38	 */
39	protected $tableAlias = 'a';
40
41	/**
42	 * @var array Database fields list allowed for sort operation.
43	 */
44	protected $sortColumns = ['auditid', 'userid', 'clock'];
45
46	/**
47	 * @var array Database table with auditlog details supported fields list.
48	 */
49	protected $details_fields = ['table_name', 'field_name', 'oldvalue', 'newvalue'];
50
51	/**
52	 * Method auditlog.get, returns audit log records according filtering criteria.
53	 *
54	 * @param array          $options                   Array of API request options.
55	 * @param int|array      $options['auditids']       Filter by auditids.
56	 * @param int|array      $options['userids']        Filter by userids.
57	 * @param int            $options['time_from']      Filter by timestamp, range start time, inclusive.
58	 * @param int            $options['time_till']      Filter by timestamp, range end time, inclusive.
59	 * @param string|array   $options['selectDetails']  Select additional details from auditlog_details.
60	 * @param string         $options['sortfield']      Sorting field: auditid, userid, clock.
61	 * @param string         $options['sortorder']      Sorting direction.
62	 * @param array          $options['filter']         Filter by fields value, exact match.
63	 * @param array          $options['search']         Filter by fields value, case insensitive search of substring.
64	 * @param bool           $options['countOutput']
65	 * @param bool           $options['excludeSearch']
66	 * @param int            $options['limit']
67	 * @param string|array   $options['output']
68	 * @param bool           $options['preservekeys']
69	 * @param bool           $options['searchByAny']
70	 * @param bool           $options['searchWildcardsEnabled']
71	 * @param bool           $options['startSearch']
72	 *
73	 * @throws APIException
74	 *
75	 * @return array|int
76	 */
77	public function get(array $options) {
78		if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) {
79			self::exception(ZBX_API_ERROR_PERMISSIONS, _('You do not have permission to perform this operation.'));
80		}
81
82		$result = [];
83		$fields = array_keys($this->getTableSchema($this->tableName())['fields']);
84		$actions = [
85			AUDIT_ACTION_ADD, AUDIT_ACTION_UPDATE, AUDIT_ACTION_DELETE, AUDIT_ACTION_LOGIN, AUDIT_ACTION_LOGOUT,
86			AUDIT_ACTION_ENABLE, AUDIT_ACTION_DISABLE, AUDIT_ACTION_EXECUTE
87		];
88		$resourcetype = [
89			AUDIT_RESOURCE_USER, AUDIT_RESOURCE_ZABBIX_CONFIG, AUDIT_RESOURCE_MEDIA_TYPE, AUDIT_RESOURCE_HOST,
90			AUDIT_RESOURCE_ACTION, AUDIT_RESOURCE_GRAPH, AUDIT_RESOURCE_GRAPH_ELEMENT, AUDIT_RESOURCE_USER_GROUP,
91			AUDIT_RESOURCE_TRIGGER, AUDIT_RESOURCE_HOST_GROUP, AUDIT_RESOURCE_ITEM,
92			AUDIT_RESOURCE_IMAGE, AUDIT_RESOURCE_VALUE_MAP, AUDIT_RESOURCE_IT_SERVICE, AUDIT_RESOURCE_MAP,
93			AUDIT_RESOURCE_SCENARIO, AUDIT_RESOURCE_DISCOVERY_RULE, AUDIT_RESOURCE_SCRIPT, AUDIT_RESOURCE_PROXY,
94			AUDIT_RESOURCE_MAINTENANCE, AUDIT_RESOURCE_REGEXP, AUDIT_RESOURCE_MACRO, AUDIT_RESOURCE_TEMPLATE,
95			AUDIT_RESOURCE_TRIGGER_PROTOTYPE, AUDIT_RESOURCE_ICON_MAP, AUDIT_RESOURCE_DASHBOARD,
96			AUDIT_RESOURCE_CORRELATION, AUDIT_RESOURCE_GRAPH_PROTOTYPE, AUDIT_RESOURCE_ITEM_PROTOTYPE,
97			AUDIT_RESOURCE_HOST_PROTOTYPE, AUDIT_RESOURCE_AUTOREGISTRATION, AUDIT_RESOURCE_MODULE,
98			AUDIT_RESOURCE_SETTINGS, AUDIT_RESOURCE_HOUSEKEEPING, AUDIT_RESOURCE_AUTHENTICATION,
99			AUDIT_RESOURCE_TEMPLATE_DASHBOARD, AUDIT_RESOURCE_AUTH_TOKEN, AUDIT_RESOURCE_SCHEDULED_REPORT
100		];
101
102		$api_input_rules = ['type' => API_OBJECT, 'fields' => [
103			// filter
104			'auditids' =>				['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE, 'default' => null],
105			'userids' =>				['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE, 'default' => null],
106			'filter' =>					['type' => API_OBJECT, 'flags' => API_ALLOW_NULL, 'default' => null, 'fields' => [
107				'auditid' =>				['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE],
108				'userid' =>					['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE],
109				'clock' =>					['type' => API_INTS32, 'flags' => API_ALLOW_NULL | API_NORMALIZE],
110				'action' =>					['type' => API_INTS32, 'flags' => API_ALLOW_NULL | API_NORMALIZE, 'in' => implode(',', $actions)],
111				'resourcetype' =>			['type' => API_INTS32, 'flags' => API_ALLOW_NULL | API_NORMALIZE, 'in' => implode(',', $resourcetype)],
112				'note' =>					['type' => API_STRINGS_UTF8, 'flags' => API_ALLOW_NULL | API_NORMALIZE],
113				'ip' =>						['type' => API_STRINGS_UTF8, 'flags' => API_ALLOW_NULL | API_NORMALIZE],
114				'resourceid' =>				['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE],
115				'resourcename' =>			['type' => API_STRINGS_UTF8, 'flags' => API_ALLOW_NULL | API_NORMALIZE],
116				'table_name' =>				['type' => API_STRINGS_UTF8, 'flags' => API_ALLOW_NULL | API_NORMALIZE],
117				'field_name' =>				['type' => API_STRINGS_UTF8, 'flags' => API_ALLOW_NULL | API_NORMALIZE]
118			]],
119			'search' =>					['type' => API_OBJECT, 'flags' => API_ALLOW_NULL, 'default' => null, 'fields' => [
120				'note' =>					['type' => API_STRINGS_UTF8, 'flags' => API_ALLOW_NULL | API_NORMALIZE],
121				'ip' =>						['type' => API_STRINGS_UTF8, 'flags' => API_ALLOW_NULL | API_NORMALIZE],
122				'resourcename' =>			['type' => API_STRINGS_UTF8, 'flags' => API_ALLOW_NULL | API_NORMALIZE],
123				'oldvalue' =>				['type' => API_STRINGS_UTF8, 'flags' => API_ALLOW_NULL | API_NORMALIZE],
124				'newvalue' =>				['type' => API_STRINGS_UTF8, 'flags' => API_ALLOW_NULL | API_NORMALIZE]
125			]],
126			'time_from' =>				['type' => API_INT32, 'flags' => API_ALLOW_NULL, 'default' => null],
127			'time_till' =>				['type' => API_INT32, 'flags' => API_ALLOW_NULL, 'default' => null],
128			'selectDetails' => 			['type' => API_OUTPUT, 'flags' => API_ALLOW_NULL, 'in' => implode(',', $this->details_fields), 'default' => null],
129			'searchByAny' =>			['type' => API_BOOLEAN, 'default' => false],
130			'startSearch' =>			['type' => API_FLAG, 'default' => false],
131			'excludeSearch' =>			['type' => API_FLAG, 'default' => false],
132			'searchWildcardsEnabled' =>	['type' => API_BOOLEAN, 'default' => false],
133			// output
134			'output' =>					['type' => API_OUTPUT, 'in' => implode(',', $fields), 'default' => $fields],
135			'countOutput' =>			['type' => API_FLAG, 'default' => false],
136			// sort and limit
137			'sortfield' =>				['type' => API_STRINGS_UTF8, 'flags' => API_NORMALIZE, 'in' => implode(',', $this->sortColumns), 'uniq' => true, 'default' => []],
138			'sortorder' =>				['type' => API_SORTORDER, 'default' => []],
139			'limit' =>					['type' => API_INT32, 'flags' => API_ALLOW_NULL, 'in' => '1:'.ZBX_MAX_INT32, 'default' => null],
140			// flags
141			'preservekeys' =>			['type' => API_BOOLEAN, 'default' => false]
142		]];
143
144		if (!CApiInputValidator::validate($api_input_rules, $options, '/', $error)) {
145			self::exception(ZBX_API_ERROR_PARAMETERS, $error);
146		}
147
148		$sql_parts = [
149			'select'	=> ['auditlog' => 'a.auditid'],
150			'from'		=> ['auditlog' => 'auditlog a'],
151			'where'		=> [],
152			'order'		=> [],
153			'limit'		=> null
154		];
155
156		if ($options['output'] === API_OUTPUT_EXTEND) {
157			$options['output'] = $fields;
158		}
159
160		if ($options['userids'] !== null) {
161			$sql_parts['where']['userid'] = dbConditionId('a.userid', $options['userids']);
162		}
163
164		if ($options['time_from'] !== null) {
165			$sql_parts['where'][] = 'a.clock>='.zbx_dbstr($options['time_from']);
166		}
167
168		if ($options['time_till'] !== null) {
169			$sql_parts['where'][] = 'a.clock<='.zbx_dbstr($options['time_till']);
170		}
171
172		$sql_parts = $this->applyQueryFilterOptions($this->tableName, $this->tableAlias, $options, $sql_parts);
173		$sql_parts = $this->applyQueryOutputOptions($this->tableName, $this->tableAlias, $options, $sql_parts);
174		$sql_parts = $this->applyQuerySortOptions($this->tableName, $this->tableAlias, $options, $sql_parts);
175		$res = DBselect(self::createSelectQueryFromParts($sql_parts), $options['limit']);
176
177		while ($audit = DBfetch($res)) {
178			if (!$options['countOutput']) {
179				$result[$audit['auditid']] = $audit;
180				continue;
181			}
182
183			$result = $audit['rowscount'];
184		}
185
186		if ($options['countOutput']) {
187			return $result;
188		}
189
190		if ($result && $options['selectDetails'] !== null) {
191			$result = $this->addRelatedObjects($options, $result);
192		}
193
194		if (!$options['preservekeys']) {
195			$result = array_values($result);
196		}
197
198		return $this->unsetExtraFields($result, ['auditid'], $options['output']);
199	}
200
201	/**
202	 * Add related objects from auditlog_details table if requested.
203	 *
204	 * @param array $options    Array of API request options.
205	 * @param array $result     Associative array of selected auditlog data, key is auditid property.
206	 *
207	 * @return array
208	 */
209	protected function addRelatedObjects(array $options, array $result): array {
210		$fields = [];
211
212		foreach ($this->details_fields as $field) {
213			if ($this->outputIsRequested($field, $options['selectDetails'])) {
214				$fields[] = $field;
215			}
216		};
217
218		foreach ($result as &$row) {
219			$row['details'] = [];
220		}
221		unset($row);
222
223		if ($fields) {
224			$relation_fields = ['auditid', 'auditdetailid'];
225			$auditlog_details = API::getApiService()->select('auditlog_details', [
226				'output' => array_merge($fields, $relation_fields),
227				'filter' => ['auditid' => array_keys($result)],
228				'preservekeys' => true
229			]);
230
231			$relation_map = $this->createRelationMap($auditlog_details, 'auditid', 'auditdetailid');
232			$auditlog_details = $this->unsetExtraFields($auditlog_details, $relation_fields, []);
233			$result = $relation_map->mapMany($result, $auditlog_details, 'details');
234		}
235
236		return $result;
237	}
238
239	/**
240	 * Apply filter and search options to $sql_parts query. Also add auditlog_details alias if filter or search requires
241	 * field from auditlog_details table.
242	 *
243	 * @param string $table        Table name.
244	 * @param string $alias        Table alias.
245	 * @param array  $options      Request options.
246	 * @param array  $sql_parts    Array of SQL query parts to be modified.
247	 *
248	 * @return array
249	 */
250	protected function applyQueryFilterOptions($table, $alias, array $options, array $sql_parts): array {
251		$filter = ($options['filter'] !== null)
252			? array_intersect_key($options['filter'], array_flip($this->details_fields))
253			: [];
254		$search = ($options['search'] !== null)
255			? array_intersect_key($options['search'], array_flip(['oldvalue', 'newvalue']))
256			: [];
257
258		if ($filter || $search) {
259			$details_options = ['filter' => $filter, 'search' => $search] + $options;
260			$sql_parts['where']['aad'] = 'a.auditid=ad.auditid';
261			$sql_parts['from']['auditlog_details'] = 'auditlog_details ad';
262			$sql_parts = parent::applyQueryFilterOptions('auditlog_details', 'ad', $details_options, $sql_parts);
263		}
264
265		return parent::applyQueryFilterOptions($table, $alias, $options, $sql_parts);
266	}
267}
268