You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
zabbix/ui/app/controllers/CControllerAuditLogList.php

399 lines
12 KiB

1 year ago
<?php declare(strict_types = 0);
/*
** Zabbix
** Copyright (C) 2001-2023 Zabbix SIA
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**/
class CControllerAuditLogList extends CController {
/**
* @var string Time from.
*/
private $from;
/**
* @var string Time till.
*/
private $to;
protected function init() {
$this->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');
}
}
}