new: [logging] Access log

pull/8749/head
Jakub Onderka 2022-11-11 17:09:09 +01:00
parent 4adbb2166b
commit 4aabc2d097
14 changed files with 818 additions and 58 deletions

View File

@ -33,6 +33,7 @@
Router::connect('/roles/admin_index/*', array('controller' => 'roles', 'action' => 'index', 'admin' => true));
Router::connect('/logs/admin_search/*', array('controller' => 'logs', 'action' => 'search', 'admin' => true));
Router::connect('/audit_logs/admin_index/*', array('controller' => 'audit_logs', 'action' => 'index', 'admin' => true));
Router::connect('/access_logs/admin_index/*', array('controller' => 'access_logs', 'action' => 'index', 'admin' => true));
Router::connect('/logs/admin_index/*', array('controller' => 'logs', 'action' => 'index', 'admin' => true));
Router::connect('/regexp/admin_index/*', array('controller' => 'regexp', 'action' => 'index', 'admin' => true));

View File

@ -0,0 +1,169 @@
<?php
App::uses('AppController', 'Controller');
/**
* @property AccessLog $AccessLog
*/
class AccessLogsController extends AppController
{
public $components = [
'RequestHandler',
];
public $paginate = [
'recursive' => -1,
'limit' => 60,
'fields' => ['id', 'created', 'user_id', 'org_id', 'authkey_id', 'ip', 'request_method', 'request_id', 'controller', 'action', 'url', 'response_code', 'memory_usage', 'duration'],
'contain' => [
'User' => ['fields' => ['id', 'email', 'org_id']],
'Organisation' => ['fields' => ['id', 'name', 'uuid']],
],
'order' => [
'AccessLog.id' => 'DESC'
],
];
public function admin_index()
{
$params = $this->IndexFilter->harvestParameters([
'created',
'ip',
'user',
'org',
'request_id',
'authkey_id',
'api_request',
'request_method',
'controller',
'action',
'url',
'response_code',
]);
$conditions = $this->__searchConditions($params);
if ($this->_isRest()) {
$list = $this->AccessLog->find('all', [
'conditions' => $conditions,
'contain' => $this->paginate['contain'],
]);
return $this->RestResponse->viewData($list, 'json');
}
$this->paginate['conditions'] = $conditions;
$list = $this->paginate();
$this->set('list', $list);
$this->set('title_for_layout', __('Access logs'));
}
public function admin_request($id)
{
$request = $this->AccessLog->find('first', [
'conditions' => ['AccessLog.id' => $id],
'fields' => ['AccessLog.request'],
]);
if (empty($request)) {
throw new NotFoundException(__('Access log not found'));
}
list($contentType, $encoding, $data) = explode("\n", $request['AccessLog']['request'], 3);
$contentType = explode(';', $contentType, 2)[0];
if ($contentType === 'application/x-www-form-urlencoded') {
parse_str($data, $output);
$data = var_export($output, true);
}
$this->set('request', $data);
}
/**
* @param array $params
* @return array
*/
private function __searchConditions(array $params)
{
$qbRules = [];
foreach ($params as $key => $value) {
if ($key === 'created') {
$qbRules[] = [
'id' => $key,
'operator' => is_array($value) ? 'between' : 'greater_or_equal',
'value' => $value,
];
} else {
if (is_array($value)) {
$value = implode('||', $value);
}
$qbRules[] = [
'id' => $key,
'value' => $value,
];
}
}
$this->set('qbRules', $qbRules);
$conditions = [];
if (isset($params['user'])) {
if (is_numeric($params['user'])) {
$conditions['AccessLog.user_id'] = $params['user'];
} else {
$user = $this->User->find('first', [
'conditions' => ['User.email' => $params['user']],
'fields' => ['id'],
]);
if (!empty($user)) {
$conditions['AccessLog.user_id'] = $user['User']['id'];
} else {
$conditions['AccessLog.user_id'] = -1;
}
}
}
if (isset($params['ip'])) {
$conditions['AccessLog.ip'] = inet_pton($params['ip']);
}
foreach (['authkey_id', 'request_id', 'controller', 'action'] as $field) {
if (isset($params[$field])) {
$conditions['AccessLog.' . $field] = $params[$field];
}
}
if (isset($params['url'])) {
$conditions['AccessLog.url LIKE'] = "%{$params['url']}%";
}
if (isset($params['request_method'])) {
$methodId = array_flip(AccessLog::REQUEST_TYPES)[$params['request_method']] ?? -1;
$conditions['AccessLog.request_method'] = $methodId;
}
if (isset($params['org'])) {
if (is_numeric($params['org'])) {
$conditions['AccessLog.org_id'] = $params['org'];
} else {
$org = $this->AccessLog->Organisation->fetchOrg($params['org']);
if ($org) {
$conditions['AccessLog.org_id'] = $org['id'];
} else {
$conditions['AccessLog.org_id'] = -1;
}
}
}
if (isset($params['created'])) {
$tempData = is_array($params['created']) ? $params['created'] : [$params['created']];
foreach ($tempData as $k => $v) {
$tempData[$k] = $this->AccessLog->resolveTimeDelta($v);
}
if (count($tempData) === 1) {
$conditions['AccessLog.created >='] = date("Y-m-d H:i:s", $tempData[0]);
} else {
if ($tempData[0] < $tempData[1]) {
$temp = $tempData[1];
$tempData[1] = $tempData[0];
$tempData[0] = $temp;
}
$conditions['AND'][] = ['AccessLog.created <=' => date("Y-m-d H:i:s", $tempData[0])];
$conditions['AND'][] = ['AccessLog.created >=' => date("Y-m-d H:i:s", $tempData[1])];
}
}
return $conditions;
}
}

View File

@ -665,27 +665,22 @@ class AppController extends Controller
{
$userMonitoringEnabled = Configure::read('Security.user_monitoring_enabled');
if ($userMonitoringEnabled) {
$redis = $this->User->setupRedis();
$userMonitoringEnabled = $redis && $redis->sismember('misp:monitored_users', $user['id']);
try {
$userMonitoringEnabled = RedisTool::init()->sismember('misp:monitored_users', $user['id']);
} catch (Exception $e) {
$userMonitoringEnabled = false;
}
}
if (Configure::read('MISP.log_paranoid') || $userMonitoringEnabled) {
$change = 'HTTP method: ' . $_SERVER['REQUEST_METHOD'] . PHP_EOL . 'Target: ' . $this->request->here;
if (
(
$this->request->is('post') ||
$this->request->is('put')
) &&
(
!empty(Configure::read('MISP.log_paranoid_include_post_body')) ||
$userMonitoringEnabled
)
) {
$payload = $this->request->input();
$change .= PHP_EOL . 'Request body: ' . $payload;
}
$this->Log = ClassRegistry::init('Log');
$this->Log->createLogEntry($user, 'request', 'User', $user['id'], 'Paranoid log entry', $change);
$shouldBeLogged = $userMonitoringEnabled ||
Configure::read('MISP.log_paranoid') ||
(Configure::read('MISP.log_paranoid_api') && $user['logged_by_authkey']);
if ($shouldBeLogged) {
$includeRequestBody = !empty(Configure::read('MISP.log_paranoid_include_post_body')) || $userMonitoringEnabled;
/** @var AccessLog $accessLog */
$accessLog = ClassRegistry::init('AccessLog');
$accessLog->logRequest($user, $this->_remoteIp(), $this->request, $includeRequestBody);
}
}

View File

@ -386,16 +386,20 @@ class ACLComponent extends Component
'testForStolenAttributes' => array(),
'pruneUpdateLogs' => array()
),
'auditLogs' => [
'admin_index' => ['perm_audit'],
'fullChange' => ['perm_audit'],
'eventIndex' => ['*'],
'returnDates' => ['*'],
],
'modules' => array(
'index' => array('perm_auth'),
'queryEnrichment' => array('perm_auth'),
),
'auditLogs' => [
'admin_index' => ['perm_audit'],
'fullChange' => ['perm_audit'],
'eventIndex' => ['*'],
'returnDates' => ['*'],
],
'accessLogs' => [
'admin_index' => [],
'admin_request' => [],
],
'modules' => array(
'index' => array('perm_auth'),
'queryEnrichment' => array('perm_auth'),
),
'news' => array(
'add' => array(),
'edit' => array(),

230
app/Model/AccessLog.php Normal file
View File

@ -0,0 +1,230 @@
<?php
App::uses('AppModel', 'Model');
/**
* @property Organisation $Organisation
* @property User $User
*/
class AccessLog extends AppModel
{
const BROTLI_HEADER = "\xce\xb2\xcf\x81",
ZSTD_HEADER = "\x28\xb5\x2f\xfd";
const COMPRESS_MIN_LENGTH = 200;
const REQUEST_TYPES = [
0 => 'Unknown',
1 => 'GET',
2 => 'HEAD',
3 => 'POST',
4 => 'PUT',
5 => 'DELETE',
6 => 'OPTIONS',
7 => 'TRACE',
8 => 'PATCH',
];
public $actsAs = [
'Containable',
];
public $compressionStats = [
'compressed' => 0,
'bytes_compressed' => 0,
'bytes_uncompressed' => 0,
];
public $belongsTo = [
'User' => [
'className' => 'User',
'foreignKey' => 'user_id',
],
'Organisation' => [
'className' => 'Organisation',
'foreignKey' => 'org_id',
],
];
public function afterFind($results, $primary = false)
{
foreach ($results as &$result) {
if (isset($result['AccessLog']['ip'])) {
$result['AccessLog']['ip'] = inet_ntop($result['AccessLog']['ip']);
}
if (isset($result['AccessLog']['request_method'])) {
$result['AccessLog']['request_method'] = self::REQUEST_TYPES[$result['AccessLog']['request_method']];
}
if (!empty($result['AccessLog']['request'])) {
$result['AccessLog']['request'] = $this->decodeRequest($result['AccessLog']['request']);
}
}
return $results;
}
public function beforeSave($options = [])
{
$accessLog = &$this->data['AccessLog'];
$this->externalLog($accessLog);
if (Configure::read('MISP.log_paranoid_skip_db')) {
return;
}
// Truncate
foreach (['request_id', 'user_agent', 'url'] as $field) {
if (isset($accessLog[$field]) && strlen($accessLog[$field]) > 255) {
$accessLog[$field] = substr($accessLog[$field], 0, 255);
}
}
if (isset($accessLog['ip'])) {
$accessLog['ip'] = inet_pton($accessLog['ip']);
}
if (isset($accessLog['request_method'])) {
$requestMethodIds = array_flip(self::REQUEST_TYPES);
$accessLog['request_method'] = $requestMethodIds[$accessLog['request_method']] ?? 0;
}
if (isset($accessLog['request'])) {
$accessLog['request'] = $this->encodeRequest($accessLog['request']);
}
}
/**
* @param array $user
* @param string $remoteIp
* @param CakeRequest $request
* @param bool $includeRequestBody
* @return bool
* @throws Exception
*/
public function logRequest(array $user, $remoteIp, CakeRequest $request, $includeRequestBody = true)
{
$requestTime = $_SERVER['REQUEST_TIME_FLOAT'] ?? microtime(true);
$now = DateTime::createFromFormat('U.u', $requestTime);
$logClientIp = Configure::read('MISP.log_client_ip');
$dataToSave = [
'created' => $now->format('Y-m-d H:i:s.u'),
'request_id' => $_SERVER['HTTP_X_REQUEST_ID'] ?? null,
'user_id' => (int)$user['id'],
'org_id' => (int)$user['org_id'],
'authkey_id' => isset($user['authkey_id']) ? (int)$user['authkey_id'] : null,
'ip' => $logClientIp ? $remoteIp : null,
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? null,
'request_method' => $_SERVER['REQUEST_METHOD'],
'controller' => $request->params['controller'],
'action' => $request->params['action'],
'url' => $request->here,
];
if ($includeRequestBody && $request->is(['post', 'put', 'delete'])) {
$requestContentType = $_SERVER['CONTENT_TYPE'] ?? null;
$requestEncoding = $_SERVER['HTTP_CONTENT_ENCODING'] ?? null;
$dataToSave['request'] = "$requestContentType\n$requestEncoding\n{$request->input()}";
}
// Save data on shutdown
register_shutdown_function(function () use ($dataToSave, $requestTime) {
session_write_close(); // close session to allow concurrent requests
$this->saveOnShutdown($dataToSave, $requestTime);
});
return true;
}
/**
* @param array $data
* @param float $requestTime
* @return bool
* @throws Exception
*/
private function saveOnShutdown(array $data, $requestTime)
{
$data['response_code'] = http_response_code();
$data['memory_usage'] = memory_get_peak_usage();
$data['duration'] = (int)((microtime(true) - $requestTime) * 1000);
try {
return $this->save($data, ['atomic' => false]);
} catch (Exception $e) {
$this->logException("Could not insert access log to database", $e, LOG_WARNING);
return false;
}
}
/**
* @param array $data
* @return void
*/
public function externalLog(array $data)
{
if ($this->pubToZmq('audit')) {
$this->getPubSubTool()->publish($data, 'audit', 'log');
}
$this->publishKafkaNotification('audit', $data, 'log');
if (Configure::read('Plugin.ElasticSearch_logging_enable')) {
// send off our logs to distributed /dev/null
$logIndex = Configure::read("Plugin.ElasticSearch_log_index");
$elasticSearchClient = $this->getElasticSearchTool();
$elasticSearchClient->pushDocument($logIndex, "log", $data);
}
}
/**
* @param string $request
* @return string
*/
private function decodeRequest($request)
{
$header = substr($request, 0, 4);
if ($header === self::BROTLI_HEADER) {
$this->compressionStats['compressed']++;
if (function_exists('brotli_uncompress')) {
$this->compressionStats['bytes_compressed'] += strlen($request);
$request = brotli_uncompress(substr($request, 4));
$this->compressionStats['bytes_uncompressed'] += strlen($request);
if ($request === false) {
return 'Compressed';
}
} else {
return 'Compressed';
}
} elseif ($header === self::ZSTD_HEADER) {
$this->compressionStats['compressed']++;
if (function_exists('zstd_uncompress')) {
$this->compressionStats['bytes_compressed'] += strlen($request);
$request = zstd_uncompress($request);
$this->compressionStats['bytes_uncompressed'] += strlen($request);
if ($request === false) {
return 'Compressed';
}
} else {
return 'Compressed';
}
}
return $request;
}
/**
* @param string $request
* @return string
*/
private function encodeRequest($request)
{
$compressionEnabled = Configure::read('MISP.log_new_audit_compress') &&
(function_exists('brotli_compress') || function_exists('zstd_compress'));
if ($compressionEnabled && strlen($request) >= self::COMPRESS_MIN_LENGTH) {
if (function_exists('zstd_compress')) {
return zstd_compress($request, 4);
} else {
return self::BROTLI_HEADER . brotli_compress($request, 4, BROTLI_TEXT);
}
}
return $request;
}
}

View File

@ -85,7 +85,7 @@ class AppModel extends Model
81 => false, 82 => false, 83 => false, 84 => false, 85 => false, 86 => false,
87 => false, 88 => false, 89 => false, 90 => false, 91 => false, 92 => false,
93 => false, 94 => false, 95 => true, 96 => false, 97 => true, 98 => false,
99 => false
99 => false, 100 => false,
);
const ADVANCED_UPDATES_DESCRIPTION = array(
@ -1882,6 +1882,28 @@ class AppModel extends Model
$sqlArray[] = "ALTER TABLE `event_tags` ADD `relationship_type` varchar(191) NULL DEFAULT '';";
$sqlArray[] = "ALTER TABLE `attribute_tags` ADD `relationship_type` varchar(191) NULL DEFAULT '';";
break;
case 100:
$sqlArray[] = "CREATE TABLE IF NOT EXISTS `access_logs` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`created` datetime(4) NOT NULL,
`user_id` int(11) NOT NULL,
`org_id` int(11) NOT NULL,
`authkey_id` int(11) DEFAULT NULL,
`ip` varbinary(16) DEFAULT NULL,
`request_method` tinyint NOT NULL,
`user_agent` varchar(255) DEFAULT NULL,
`request_id` varchar(255) DEFAULT NULL,
`controller` varchar(20) NOT NULL,
`action` varchar(20) NOT NULL,
`url` varchar(255) NOT NULL,
`request` blob,
`response_code` smallint NOT NULL,
`memory_usage` int(11) NOT NULL,
`duration` int(11) NOT NULL,
PRIMARY KEY (`id`),
INDEX `user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;";
break;
case 'fixNonEmptySharingGroupID':
$sqlArray[] = 'UPDATE `events` SET `sharing_group_id` = 0 WHERE `distribution` != 4;';
$sqlArray[] = 'UPDATE `attributes` SET `sharing_group_id` = 0 WHERE `distribution` != 4;';

View File

@ -145,9 +145,6 @@ class Log extends AppModel
}
}
$this->logData($this->data);
if ($this->data['Log']['action'] === 'request' && !empty(Configure::read('MISP.log_paranoid_skip_db'))) {
return false;
}
return true;
}
@ -243,9 +240,6 @@ class Log extends AppModel
]]);
if (!$result) {
if ($action === 'request' && !empty(Configure::read('MISP.log_paranoid_skip_db'))) {
return null;
}
if (!empty(Configure::read('MISP.log_skip_db_logs_completely'))) {
return null;
}
@ -349,9 +343,8 @@ class Log extends AppModel
public function logData($data)
{
if (Configure::read('Plugin.ZeroMQ_enable') && Configure::read('Plugin.ZeroMQ_audit_notifications_enable')) {
$pubSubTool = $this->getPubSubTool();
$pubSubTool->publish($data, 'audit', 'log');
if ($this->pubToZmq('audit')) {
$this->getPubSubTool()->publish($data, 'audit', 'log');
}
$this->publishKafkaNotification('audit', $data, 'log');
@ -363,11 +356,6 @@ class Log extends AppModel
$elasticSearchClient->pushDocument($logIndex, "log", $data);
}
// Do not save request action logs to syslog, because they contain no information
if ($data['Log']['action'] === 'request') {
return true;
}
// write to syslogd as well if enabled
if ($this->syslog === null) {
if (Configure::read('Security.syslog')) {

View File

@ -5576,6 +5576,14 @@ class Server extends AppModel
'type' => 'boolean',
'null' => true
),
'log_paranoid_api' => array(
'level' => 0,
'description' => __('If this functionality is enabled all API requests will be logged.'),
'value' => false,
'test' => 'testBoolFalse',
'type' => 'boolean',
'null' => true
),
'log_paranoid_skip_db' => array(
'level' => 0,
'description' => __('You can decide to skip the logging of the paranoid logs to the database.'),

View File

@ -0,0 +1,336 @@
<div class="logs index">
<h2><?= __('Access logs') ?></h2>
<div>
<div id="builder"></div>
<div style="display: flex; justify-content: flex-end; margin-top: 5px;">
<button id="qbSubmit" type="button" class="btn btn-success" style="margin-right: 5px;"> <i class="fa fa-filter"></i> <?= __('Filter'); ?></button>
<button id="qbClear" type="button" class="btn btn-xs btn-danger" title="<?= __('Clear filtering rules'); ?>"> <i class="fa fa-times"></i> <?= __('Clear'); ?></button>
</div>
</div>
<?php
echo $this->Html->script('moment.min');
echo $this->Html->script('doT');
echo $this->Html->script('extendext');
echo $this->Html->css('query-builder.default');
echo $this->Html->script('query-builder');
?>
<script>
var qbOptions = {
plugins: {
'unique-filter': null,
'filter-description' : {
mode: 'inline'
},
},
conditions: ['AND'],
allow_empty: true,
filters: [
{
id: 'created',
label: 'Created',
type: 'date',
operators: ['greater_or_equal', 'between'],
validation: {
format: 'YYYY-MM-DD'
},
plugin: 'datepicker',
plugin_config: {
format: 'yyyy-mm-dd',
todayBtn: 'linked',
todayHighlight: true,
autoclose: true
}
},
{
input: "text",
type: "string",
operators: [
"equal",
],
unique: true,
id: "ip",
label: "IP",
},
{
input: "text",
type: "string",
operators: [
"equal",
],
unique: true,
id: "user",
label: "User",
description: "User ID or mail",
},
{
input: "text",
type: "integer",
operators: [
"equal",
],
unique: true,
id: "authkey_id",
label: "Authentication key ID",
},
{
input: "text",
type: "string",
operators: [
"equal",
],
unique: true,
id: "request_id",
label: "Request ID",
description: "Request ID from X-Request-ID HTTP header",
},
{
input: "text",
type: "string",
operators: [
"equal",
],
unique: true,
id: "org",
label: "Organisation",
description: "Organisation ID, UUID or name",
},
{
input: "text",
type: "string",
operators: [
"equal",
],
unique: true,
id: "request_method",
label: "HTTP request method",
values: ["GET", "HEAD", "POST", "PUT", "DELETE"],
},
{
input: "text",
type: "integer",
operators: [
"equal",
],
unique: true,
id: "response_code",
label: "HTTP response code",
},
{
input: "text",
type: "string",
operators: [
"equal",
],
unique: true,
id: "controller",
label: "Controller",
},
{
input: "text",
type: "string",
operators: [
"equal",
],
unique: true,
id: "action",
label: "Action",
},
{
input: "text",
type: "string",
operators: [
"contains",
],
unique: true,
id: "url",
label: "URL",
}
],
rules: {
condition: 'AND',
not: false,
rules: <?= JsonTool::encode($qbRules) ?>,
flags: {
no_add_group: true,
condition_readonly: true,
}
},
icons: {
add_group: 'fa fa-plus-square',
add_rule: 'fa fa-plus-circle',
remove_group: 'fa fa-minus-square',
remove_rule: 'fa fa-minus-circle',
error: 'fa fa-exclamation-triangle'
}
};
$(function() {
var $builder = $('#builder');
// Fix for Bootstrap Datepicker
$builder.on('afterUpdateRuleValue.queryBuilder', function (e, rule) {
if (rule.filter.plugin === 'datepicker') {
rule.$el.find('.rule-value-container input').datepicker('update');
}
});
var queryBuilder = $builder.queryBuilder(qbOptions);
queryBuilder = queryBuilder[0].queryBuilder;
$('#qbClear').off('click').on('click', function () {
queryBuilder.reset();
});
// Submit on enter
$builder.on('keyup', 'input[type=text], select', function (event) {
if (event.keyCode === 13) {
$('#qbSubmit').click();
}
});
$('#qbSubmit').off('click').on('click', function () {
var rules = queryBuilder.getRules({skip_empty: true});
passedArgs = [];
for (var key in rules.rules) {
var rule = rules.rules[key];
var k = rule.id;
var v = rule.value;
if (Array.isArray(v)) {
v = v.join('||');
}
passedArgs[k] = v;
}
var url = here;
for (var key in passedArgs) {
if (typeof key === 'number') {
url += "/" + passedArgs[key];
} else if (key !== 'page') {
url += "/" + key + ":" + encodeURIComponent(passedArgs[key]);
}
}
window.location.href = url;
});
});
</script>
<div class="pagination">
<ul>
<?php
$paginator = $this->LightPaginator->prev('&laquo; ' . __('previous'), array('tag' => 'li', 'escape' => false), null, array('tag' => 'li', 'class' => 'prev disabled', 'escape' => false, 'disabledTag' => 'span'));
$paginator .= $this->LightPaginator->numbers(array('modulus' => 20, 'separator' => '', 'tag' => 'li', 'currentClass' => 'active', 'currentTag' => 'span'));
$paginator .= $this->LightPaginator->next(__('next') . ' &raquo;', array('tag' => 'li', 'escape' => false), null, array('tag' => 'li', 'class' => 'next disabled', 'escape' => false, 'disabledTag' => 'span'));
echo $paginator;
?>
</ul>
</div>
<table class="table table-striped table-hover table-condensed">
<tr>
<th><?= $this->LightPaginator->sort('created') ?></th>
<th><?= $this->LightPaginator->sort('user_id', __('User')) ?></th>
<th><?= $this->LightPaginator->sort('ip', __('IP')) ?></th>
<th><?= $this->LightPaginator->sort('org_id', __('Org')) ?></th>
<th><?= $this->LightPaginator->sort('request_method', __('Method')) ?></th>
<th><?= $this->LightPaginator->sort('url', __('URL')) ?></th>
<th><?= $this->LightPaginator->sort('response_code', __('Code')) ?></th>
<th><?= $this->LightPaginator->sort('memory_usage', __('Memory')) ?></th>
<th><?= $this->LightPaginator->sort('duration', __('Duration')) ?></th>
</tr>
<?php foreach ($list as $item): ?>
<tr>
<td class="short"><?= $this->Time->time($item['AccessLog']['created']); ?></td>
<td class="short" data-search="user" data-search-value="<?= h($item['AccessLog']['user_id']) ?>"><?php
if (isset($item['User']['email'])) {
echo '<a href="' . $baseurl . '/admin/users/view/' . h($item['User']['id']) . '">' . h($item['User']['email']) . '</a>';
} else {
echo __('<i>Deleted user #%s</i>', h($item['AccessLog']['user_id']));
}
if (!empty($item['AccessLog']['authkey_id'])) {
echo ' <i class="fas fa-cogs" title="' . __('Request trough API by auth key #%s', h($item['AccessLog']['authkey_id'])) . '"></i>';
}
?></td>
<td class="short" data-search="ip" data-search-value="<?= h($item['AccessLog']['ip']) ?>"><?= h($item['AccessLog']['ip']) ?></td>
<td class="short" data-search="org" data-search-value="<?= h($item['AccessLog']['org_id']) ?>">
<?php if (isset($item['Organisation']) && $item['Organisation']['id']) {
echo $this->OrgImg->getOrgLogo($item, 24);
} else if ($item['AccessLog']['org_id'] != 0) {
echo __('<i>Deleted org #%s</i>', h($item['AccessLog']['org_id']));
}
?>
</td>
<td class="short" data-search="request_method" data-search-value="<?= h($item['AccessLog']['request_method']) ?>">
<?= h($item['AccessLog']['request_method']) ?>
<?= in_array($item['AccessLog']['request_method'], ['POST', 'PUT']) ? ' <a href="#" class="far fa-file request" data-log-id="' . h($item['AccessLog']['id']) . '"></i>' : '' ?>
</td>
<td class="short" title="<?= __('Controller: %s, action: %s', h($item['AccessLog']['controller']), h($item['AccessLog']['action'])) ?>"><?= h($item['AccessLog']['url']) ?></td>
<td class="short" data-search="response_code" data-search-value="<?= h($item['AccessLog']['response_code']) ?>"><?= h($item['AccessLog']['response_code']) ?></td>
<td class="short"><?= CakeNumber::toReadableSize($item['AccessLog']['memory_usage']) ?></td>
<td class="short"><?= $item['AccessLog']['duration'] ?> ms</td>
</tr>
<?php endforeach; ?>
</table>
<p>
</p>
<div class="pagination">
<ul>
<?= $paginator ?>
</ul>
</div>
</div>
<script>
var passedArgs = <?= $passedArgs ?>;
$('.request').click(function (e) {
e.preventDefault();
var id = $(this).data('log-id');
$.get(baseurl + "/admin/access_logs/request/" + id, function(data) {
var $popoverFormLarge = $('#popover_form_large');
$popoverFormLarge.html(data);
openPopup($popoverFormLarge);
});
return false;
});
$('td[data-search]').mouseenter(function() {
var $td = $(this);
if ($td.data('search-value').length === 0) {
return;
}
$td.find('#quickEditButton').remove(); // clean all similar if exist
var $div = $('<div id="quickEditButton"></div>');
$div.addClass('quick-edit-row-div');
var $span = $('<span></span>');
$span.addClass('fa-as-icon fa fa-search-plus');
$span.css('font-size', '12px');
$span.prop('title', 'Filter by this value');
$div.append($span);
$td.append($div);
$span.click(function() {
if ($td.data('search') === 'model') {
var val = $td.data('search-value').split(":");
passedArgs['model'] = encodeURIComponent(val[0]);
passedArgs['model_id'] = encodeURIComponent(val[1]);
} else {
passedArgs[$td.data('search')] = encodeURIComponent($td.data('search-value'));
}
var url = here;
for (var key in passedArgs) {
if (typeof key === 'number') {
url += "/" + passedArgs[key];
} else if (key !== 'page') {
url += "/" + key + ":" + passedArgs[key];
}
}
window.location.href = url;
});
$td.off('mouseleave').on('mouseleave', function() {
$div.remove();
});
});
</script>
<?= $this->element('/genericElements/SideMenu/side_menu', ['menuList' => 'logs', 'menuItem' => 'listAccessLogs']);

View File

@ -0,0 +1 @@
<div style="padding: 1em; background: white; word-wrap: break-word; white-space: pre-wrap"><?= h($request) ?></div>

View File

@ -14,7 +14,7 @@
echo $this->Html->css('query-builder.default');
echo $this->Html->script('query-builder');
?>
<script type="text/javascript">
<script>
var qbOptions = {
plugins: {
'unique-filter': null,
@ -50,7 +50,7 @@
unique: true,
id: "action",
label: "Action",
values: <?= json_encode($actions) ?>
values: <?= JsonTool::encode($actions) ?>
},
{
input: "select",
@ -61,7 +61,7 @@
unique: true,
id: "model",
label: "Model type",
values: <?= json_encode($models) ?>
values: <?= JsonTool::encode($models) ?>
},
{
input: "text",
@ -161,7 +161,7 @@
rules: {
condition: 'AND',
not: false,
rules: <?= json_encode($qbRules) ?>,
rules: <?= JsonTool::encode($qbRules) ?>,
flags: {
no_add_group: true,
condition_readonly: true,
@ -294,7 +294,7 @@
</ul>
</div>
</div>
<script type="text/javascript">
<script>
var passedArgs = <?= $passedArgs ?>;
$('.fullChange').dblclick(function () {
@ -322,6 +322,7 @@
var $span = $('<span></span>');
$span.addClass('fa-as-icon fa fa-search-plus');
$span.css('font-size', '12px');
$span.prop('title', 'Filter by this value');
$div.append($span);
$td.append($div);

View File

@ -1092,14 +1092,19 @@ $divider = $this->element('/genericElements/SideMenu/side_menu_divider');
case 'logs':
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'url' => $baseurl . '/admin/logs/index',
'text' => __('List Logs')
'text' => __('Application Logs')
));
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'listAuditLogs',
'url' => $baseurl . '/admin/audit_logs/index',
'text' => __('List Audit Logs'),
'text' => __('Audit Logs'),
'requirement' => Configure::read('MISP.log_new_audit'),
));
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'listAccessLogs',
'url' => $baseurl . '/admin/access_logs/index',
'text' => __('Access Logs'),
));
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'url' => $baseurl . '/admin/logs/search',
'text' => __('Search Logs')

View File

@ -481,20 +481,20 @@
'requirement' => $isAclAudit,
'children' => array(
array(
'text' => __('List Logs'),
'text' => __('Application Logs'),
'url' => $baseurl . '/admin/logs/index'
),
array(
'text' => __('List Audit Logs'),
'text' => __('Audit Logs'),
'url' => $baseurl . '/admin/audit_logs/index',
'requirement' => Configure::read('MISP.log_new_audit'),
),
array(
'text' => __('Search Logs'),
'url' => $baseurl . '/admin/logs/search'
)
)
'text' => __('Access Logs'),
'url' => $baseurl . '/admin/access_logs/index',
),
)
),
array(
'type' => 'root',
'text' => __('API'),

View File

@ -9150,5 +9150,5 @@
"uuid": false
}
},
"db_version": "99"
"db_version": "100"
}