disableCsrfValidation(); } protected function checkInput(): bool { $fields = [ 'page' => 'ge 1', 'filter_actions' => 'array', 'filter_resourcetype' => 'in -1,'.implode(',', array_keys(self::getResourcesList())), 'filter_rst' => 'in 1', 'filter_set' => 'in 1', 'filter_userids' => 'array_db users.userid', 'filter_resourceid' => 'string', 'filter_recordsetid' => 'string', 'from' => 'range_time', 'to' => 'range_time' ]; $ret = $this->validateInput($fields); if (!$ret) { $this->setResponse(new CControllerResponseFatal()); } else { if ($this->hasInput('from') || $this->hasInput('to')) { validateTimeSelectorPeriod( $this->hasInput('from') ? $this->getInput('from') : null, $this->hasInput('to') ? $this->getInput('to') : null ); } $this->from = $this->getInput('from', CProfile::get('web.auditlog.filter.from', 'now-'.CSettingsHelper::get(CSettingsHelper::PERIOD_DEFAULT) )); $this->to = $this->getInput('to', CProfile::get('web.auditlog.filter.to', 'now')); } return $ret; } protected function checkPermissions(): bool { return $this->checkAccess(CRoleHelper::UI_REPORTS_AUDIT); } protected function doAction(): void { if ($this->hasInput('filter_set')) { $this->updateProfiles(); } elseif ($this->hasInput('filter_rst')) { $this->deleteProfiles(); } $data = [ 'page' => $this->getInput('page', 1), 'userids' => CProfile::getArray('web.auditlog.filter.userids', []), 'resourcetype' => CProfile::get('web.auditlog.filter.resourcetype', -1), 'auditlog_actions' => CProfile::getArray('web.auditlog.filter.actions', []), 'resourceid' => CProfile::get('web.auditlog.filter.resourceid', ''), 'recordsetid' => CProfile::get('web.auditlog.filter.recordsetid', ''), 'action' => $this->getAction(), 'actions' => self::getActionsList(), 'resources' => self::getResourcesList(), 'timeline' => getTimeSelectorPeriod([ 'profileIdx' => 'web.auditlog.filter', 'profileIdx2' => 0, 'from' => $this->from, 'to' => $this->to ]), 'auditlogs' => [], 'active_tab' => CProfile::get('web.auditlog.filter.active', 1) ]; $users = []; $non_existent_userids = [0]; $filter = [ 'action' => $data['auditlog_actions'] ]; if ($data['resourcetype'] != -1) { $filter['resourcetype'] = $data['resourcetype']; } if ($data['resourceid'] !== '') { $filter['resourceid'] = $data['resourceid']; } if ($data['recordsetid'] !== '') { $filter['recordsetid'] = $data['recordsetid']; } $params = [ 'output' => ['auditid', 'userid', 'username', 'clock', 'action', 'resourcetype', 'ip', 'resourceid', 'resourcename', 'details', 'recordsetid' ], 'filter' => $filter, 'sortfield' => 'auditid', 'sortorder' => ZBX_SORT_DOWN, 'limit' => CSettingsHelper::get(CSettingsHelper::SEARCH_LIMIT) + 1 ]; if ($data['timeline']['from_ts'] !== null) { $params['time_from'] = $data['timeline']['from_ts']; } if ($data['timeline']['to_ts'] !== null) { $params['time_till'] = $data['timeline']['to_ts']; } if ($data['userids']) { $users = API::User()->get([ 'output' => ['userid', 'username', 'name', 'surname'], 'userids' => $data['userids'], 'preservekeys' => true ]); if (in_array('0', $data['userids'])) { $users[0] = ['userid' => '0', 'username' => 'System', 'name' => '', 'surname' => '']; } $data['userids'] = $this->sanitizeUsersForMultiselect($users); if ($users) { $params['userids'] = array_column($users, 'userid'); } $users = array_map(function(array $value): string { return $value['username']; }, $users); } $data['auditlogs'] = API::AuditLog()->get($params); $data['paging'] = CPagerHelper::paginate($data['page'], $data['auditlogs'], ZBX_SORT_UP, (new CUrl('zabbix.php'))->setArgument('action', $this->getAction()) ); $data['auditlogs'] = $this->sanitizeDetails($data['auditlogs']); if (!$users && $data['auditlogs']) { $db_users = API::User()->get([ 'output' => ['username'], 'userids' => array_unique(array_column($data['auditlogs'], 'userid')), 'preservekeys' => true ]); $users = []; foreach ($data['auditlogs'] as $auditlog) { if (!array_key_exists($auditlog['userid'], $db_users)) { $non_existent_userids[$auditlog['userid']] = true; continue; } $users[$auditlog['userid']] = $db_users[$auditlog['userid']]['username']; } } $data['users'] = $users; $data['non_existent_userids'] = array_keys($non_existent_userids); natsort($data['actions']); natsort($data['resources']); $data['resources'] = [-1 => _('All')] + $data['resources']; $response = new CControllerResponseData($data); $response->setTitle(_('Audit log')); $this->setResponse($response); } /** * Return associated list of available actions and labels. * * @return array */ private static function getActionsList(): array { return [ CAudit::ACTION_LOGIN_SUCCESS => _('Login'), CAudit::ACTION_LOGIN_FAILED => _('Failed login'), CAudit::ACTION_LOGOUT => _('Logout'), CAudit::ACTION_ADD => _('Add'), CAudit::ACTION_UPDATE => _('Update'), CAudit::ACTION_DELETE => _('Delete'), CAudit::ACTION_EXECUTE => _('Execute'), CAudit::ACTION_HISTORY_CLEAR => _('History clear'), CAudit::ACTION_CONFIG_REFRESH => _('Configuration refresh'), CAudit::ACTION_PUSH => _('Push') ]; } /** * Return associated list of available resources and labels. * * @return array */ private static function getResourcesList(): array { return [ CAudit::RESOURCE_ACTION => _('Action'), CAudit::RESOURCE_AUTH_TOKEN => _('API token'), CAudit::RESOURCE_AUTHENTICATION => _('Authentication'), CAudit::RESOURCE_AUTOREGISTRATION => _('Autoregistration'), CAudit::RESOURCE_CONNECTOR => _('Connector'), CAudit::RESOURCE_CORRELATION => _('Event correlation'), CAudit::RESOURCE_DASHBOARD => _('Dashboard'), CAudit::RESOURCE_DISCOVERY_RULE => _('Discovery rule'), CAudit::RESOURCE_GRAPH => _('Graph'), CAudit::RESOURCE_GRAPH_PROTOTYPE => _('Graph prototype'), CAudit::RESOURCE_HA_NODE => _('High availability node'), CAudit::RESOURCE_HISTORY => _('History'), CAudit::RESOURCE_HOST => _('Host'), CAudit::RESOURCE_HOST_GROUP => _('Host group'), CAudit::RESOURCE_HOST_PROTOTYPE => _('Host prototype'), CAudit::RESOURCE_HOUSEKEEPING => _('Housekeeping'), CAudit::RESOURCE_ICON_MAP => _('Icon mapping'), CAudit::RESOURCE_IMAGE => _('Image'), CAudit::RESOURCE_IT_SERVICE => _('Service'), CAudit::RESOURCE_ITEM => _('Item'), CAudit::RESOURCE_ITEM_PROTOTYPE => _('Item prototype'), CAudit::RESOURCE_LLD_RULE => _('LLD rule'), CAudit::RESOURCE_MACRO => _('Macro'), CAudit::RESOURCE_MAINTENANCE => _('Maintenance'), CAudit::RESOURCE_MAP => _('Map'), CAudit::RESOURCE_MEDIA_TYPE => _('Media type'), CAudit::RESOURCE_MODULE => _('Module'), CAudit::RESOURCE_PROXY => _('Proxy'), CAudit::RESOURCE_REGEXP => _('Regular expression'), CAudit::RESOURCE_SCENARIO => _('Web scenario'), CAudit::RESOURCE_SCHEDULED_REPORT => _('Scheduled report'), CAudit::RESOURCE_SCRIPT => _('Script'), CAudit::RESOURCE_SETTINGS => _('Settings'), CAudit::RESOURCE_SLA => _('SLA'), CAudit::RESOURCE_TEMPLATE => _('Template'), CAudit::RESOURCE_TEMPLATE_DASHBOARD => _('Template dashboard'), CAudit::RESOURCE_TRIGGER => _('Trigger'), CAudit::RESOURCE_TRIGGER_PROTOTYPE => _('Trigger prototype'), CAudit::RESOURCE_TEMPLATE_GROUP => _('Template group'), CAudit::RESOURCE_USER => _('User'), CAudit::RESOURCE_USERDIRECTORY => _('User directory'), CAudit::RESOURCE_USER_GROUP => _('User group'), CAudit::RESOURCE_USER_ROLE => _('User role'), CAudit::RESOURCE_VALUE_MAP => _('Value map') ]; } private function updateProfiles(): void { CProfile::updateArray('web.auditlog.filter.userids', $this->getInput('filter_userids', []), PROFILE_TYPE_ID); CProfile::updateArray('web.auditlog.filter.actions', $this->getInput('filter_actions', []), PROFILE_TYPE_INT); CProfile::update('web.auditlog.filter.resourcetype', $this->getInput('filter_resourcetype', -1), PROFILE_TYPE_INT ); CProfile::update('web.auditlog.filter.resourceid', $this->getInput('filter_resourceid', ''), PROFILE_TYPE_STR); CProfile::update('web.auditlog.filter.recordsetid', $this->getInput('filter_recordsetid', ''), PROFILE_TYPE_STR ); CProfile::update('web.auditlog.filter.from', $this->from, PROFILE_TYPE_STR); CProfile::update('web.auditlog.filter.to', $this->to, PROFILE_TYPE_STR); } private function deleteProfiles(): void { CProfile::deleteIdx('web.auditlog.filter.userids'); CProfile::deleteIdx('web.auditlog.filter.actions'); CProfile::delete('web.auditlog.filter.resourcetype'); CProfile::delete('web.auditlog.filter.resourceid'); CProfile::delete('web.auditlog.filter.recordsetid'); } private function sanitizeUsersForMultiselect(array $users): array { $users = array_map(function(array $value): array { return ['id' => $value['userid'], 'name' => getUserFullname($value)]; }, $users); CArrayHelper::sort($users, ['name']); return $users; } private function sanitizeDetails(array $auditlogs): array { foreach ($auditlogs as &$auditlog) { $auditlog['short_details'] = ''; $auditlog['details_button'] = 0; if ($auditlog['resourcename'] != '') { $auditlog['short_details'] .= _('Description').': '.$auditlog['resourcename']; } if (!in_array($auditlog['action'], [CAudit::ACTION_ADD, CAudit::ACTION_UPDATE, CAudit::ACTION_EXECUTE, CAudit::ACTION_PUSH])) { continue; } $details = json_decode($auditlog['details'], true); if (!is_array($details) || count($details) == 0) { $auditlog['details'] = ''; continue; } // Add space after description string. if ($auditlog['short_details'] != '') { $auditlog['short_details'] .= "\n\n"; } $details = $this->formatDetails($details); $short_details = array_slice($details, 0, 2); // We cut string and show "Details" button if audit detail string more than 255 symbols. foreach ($short_details as &$detail) { // Remove all line breaks from short details. $detail = str_replace(["\r\n", "\n"], " ", $detail); if (mb_strlen($detail) > 255) { $detail = mb_substr($detail, 0, 252).'...'; $auditlog['details_button'] = 1; } } unset($detail); $auditlog['details'] = implode("\n", $details); $auditlog['short_details'] .= implode("\n", $short_details); if (!$auditlog['details_button'] && count($details) > 2) { $auditlog['details_button'] = 1; } $details = json_decode($auditlog['details'], true); if (!is_array($details) || count($details) == 0) { continue; } $details = $this->formatDetails($details); $auditlog['details'] = implode("\n", $details); } unset($auditlog); return $auditlogs; } private function formatDetails(array $details): array { ksort($details); $new_details = []; foreach ($details as $key => $detail) { $new_details[] = $this->makeDetailString($key, $detail); } return $new_details; } private function makeDetailString(string $key, array $detail): string { switch ($detail[0]) { case CAudit::DETAILS_ACTION_ADD: return array_key_exists(1, $detail) ? $key.': '.$detail[1] : $key.': '._('Added'); case CAudit::DETAILS_ACTION_DELETE: return $key.': '._('Deleted'); case CAudit::DETAILS_ACTION_UPDATE: return array_key_exists(1, $detail) ? sprintf('%s: %s => %s', $key, $detail[2], $detail[1]) : $key.': '._('Updated'); } } }