new: [log] Audit log

pull/6914/head
Jakub Onderka 2021-01-22 13:01:23 +01:00
parent 25ceea7f4e
commit ad1b373766
62 changed files with 2008 additions and 18 deletions

View File

@ -32,6 +32,7 @@
Router::connect('/users/admin_index/*', array('controller' => 'users', 'action' => 'index', 'admin' => true));
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('/logs/admin_index/*', array('controller' => 'logs', 'action' => 'index', 'admin' => true));
Router::connect('/regexp/admin_index/*', array('controller' => 'regexp', 'action' => 'index', 'admin' => true));

View File

@ -445,6 +445,7 @@ class EventShell extends AppShell
$inputData = $tempFile->read();
$inputData = json_decode($inputData, true);
$tempFile->delete();
Configure::write('CurrentUserId', $inputData['user']['id']);
$this->Event->processFreeTextData(
$inputData['user'],
$inputData['attributes'],
@ -465,6 +466,7 @@ class EventShell extends AppShell
$tempFile = new File(APP . 'tmp/cache/ingest' . DS . $inputFile);
$inputData = json_decode($tempFile->read(), true);
$tempFile->delete();
Configure::write('CurrentUserId', $inputData['user']['id']);
$this->Event->processModuleResultsData(
$inputData['user'],
$inputData['misp_format'],
@ -530,6 +532,7 @@ class EventShell extends AppShell
if (empty($user)) {
$this->error("User with ID $userId does not exists.");
}
Configure::write('CurrentUserId', $user['id']); // for audit logging purposes
return $user;
}

View File

@ -0,0 +1,571 @@
<?php
App::uses('AppController', 'Controller');
App::uses('AuditLog', 'Model');
/**
* @property AuditLog $AuditLog
*/
class AuditLogsController extends AppController
{
public $components = [
'Security',
'RequestHandler',
];
/** @var array */
private $actions;
/** @var string[] */
private $models = [
'AdminSetting',
'Attribute',
'Allowedlist',
'AuthKey',
'Cerebrate',
'CorrelationExclusion',
'Event',
'EventBlocklist',
'Feed',
'DecayingModel',
'Object',
'ObjectTemplate',
'ObjectTemplateElement',
'Organisation',
'OrgBlocklist',
'Post',
'Regexp',
'Role',
'Server',
'ShadowAttribute',
'SharingGroup',
'Tag',
'TagCollection',
'TagCollectionTag',
'Task',
'Taxonomy',
'Template',
'Thread',
'User',
'UserSetting',
'Galaxy',
'GalaxyCluster',
'GalaxyClusterBlocklist',
'GalaxyClusterRelation',
'News',
'Warninglist',
];
public $paginate = [
'recursive' => -1,
'limit' => 60,
'fields' => ['id', 'created', 'user_id', 'org_id', 'action', 'model', 'model_id', 'model_title', 'event_id', 'change'],
'contain' => [
'User' => ['fields' => ['id', 'email', 'org_id']],
'Organisation' => ['fields' => ['id', 'name', 'uuid']],
],
'order' => [
'AuditLog.id' => 'DESC'
],
];
public function __construct($id = false, $table = null, $ds = null)
{
parent::__construct($id, $table, $ds);
$this->actions = [
AuditLog::ACTION_ADD => __('Add'),
AuditLog::ACTION_EDIT => __('Edit'),
AuditLog::ACTION_SOFT_DELETE => __('Soft delete'),
AuditLog::ACTION_DELETE => __('Delete'),
AuditLog::ACTION_UNDELETE => __('Undelete'),
AuditLog::ACTION_TAG => __('Tag'),
AuditLog::ACTION_TAG_LOCAL => __('Tag'),
AuditLog::ACTION_REMOVE_TAG => __('Remove tag'),
AuditLog::ACTION_REMOVE_TAG_LOCAL => __('Remove tag'),
AuditLog::ACTION_GALAXY => __('Galaxy cluster'),
AuditLog::ACTION_GALAXY_LOCAL => __('Galaxy cluster'),
AuditLog::ACTION_REMOVE_GALAXY => __('Remove galaxy cluster'),
AuditLog::ACTION_REMOVE_GALAXY_LOCAL => __('Remove galaxy cluster'),
AuditLog::ACTION_PUBLISH => __('Publish'),
AuditLog::ACTION_PUBLISH_SIGHTINGS => __('Publish sightings'),
];
}
public function admin_index()
{
$this->paginate['fields'][] = 'ip';
$this->paginate['fields'][] = 'request_type';
$this->paginate['fields'][] = 'authkey_id';
if ($this->_isRest()) {
$this->paginate['fields'][] = 'request_id';
}
$this->paginate['conditions'] = $this->__searchConditions();
$list = $this->paginate();
if ($this->_isRest()) {
return $this->RestResponse->viewData($list, 'json');
}
$list = $this->__appendModelLinks($list);
foreach ($list as $k => $item) {
$list[$k]['AuditLog']['action_human'] = $this->actions[$item['AuditLog']['action']];
}
$this->set('list', $list);
$this->set('actions', [
AuditLog::ACTION_ADD => __('Add'),
AuditLog::ACTION_EDIT => __('Edit'),
AuditLog::ACTION_SOFT_DELETE => __('Soft delete'),
AuditLog::ACTION_DELETE => __('Delete'),
AuditLog::ACTION_UNDELETE => __('Undelete'),
AuditLog::ACTION_TAG . '||' . AuditLog::ACTION_TAG_LOCAL => __('Tag'),
AuditLog::ACTION_REMOVE_TAG . '||' . AuditLog::ACTION_REMOVE_TAG_LOCAL => __('Remove tag'),
AuditLog::ACTION_GALAXY . '||' . AuditLog::ACTION_GALAXY_LOCAL => __('Galaxy cluster'),
AuditLog::ACTION_REMOVE_GALAXY . '||' . AuditLog::ACTION_REMOVE_GALAXY_LOCAL => __('Remove galaxy cluster'),
AuditLog::ACTION_PUBLISH => __('Publish'),
AuditLog::ACTION_PUBLISH_SIGHTINGS => $this->actions[AuditLog::ACTION_PUBLISH_SIGHTINGS],
]);
$models = $this->models;
sort($models);
$this->set('models', $models);
$this->set('title_for_layout', __('Audit logs'));
}
public function eventIndex($eventId, $org = null)
{
$this->loadModel('Event');
$event = $this->Event->fetchSimpleEvent($this->Auth->user(), $eventId);
if (empty($event)) {
throw new NotFoundException('Invalid event.');
}
$this->paginate['conditions'] = $this->__createEventIndexConditions($event);
if ($org) {
$org = $this->AuditLog->Organisation->fetchOrg($org);
if ($org) {
$this->paginate['conditions']['AND']['org_id'] = $org['id'];
} else {
$this->paginate['conditions']['AND']['org_id'] = -1;
}
}
$list = $this->paginate();
if (!$this->_isSiteAdmin()) {
// Remove all user info about users from different org
$this->loadModel('User');
$orgUserIds = $this->User->find('column', array(
'conditions' => ['User.org_id' => $this->Auth->user('org_id')],
'fields' => ['User.id'],
));
foreach ($list as $k => $item) {
if ($item['AuditLog']['user_id'] == 0) {
continue;
}
if (!in_array($item['User']['id'], $orgUserIds)) {
unset($list[$k]['User']);
unset($list[$k]['AuditLog']['user_id']);
}
}
}
if ($this->_isRest()) {
return $this->RestResponse->viewData($list, 'json');
}
foreach ($list as $k => $item) {
$list[$k]['AuditLog']['action_human'] = $this->actions[$item['AuditLog']['action']];
}
$this->set('list', $list);
$this->set('event', $event);
$this->set('mayModify', $this->__canModifyEvent($event));
$this->set('title_for_layout', __('Audit logs for event #%s', $event['Event']['id']));
}
/**
* @return array
*/
private function __searchConditions()
{
$params = $this->IndexFilter->harvestParameters([
'ip',
'user',
'request_id',
'authkey_id',
'model',
'model_id',
'event_id',
'model_title',
'action',
'org',
'created',
'request_type',
]);
$qbRules = [];
foreach ($params as $key => $value) {
if ($key === 'model' && strpos($value, ':') !== false) {
$parts = explode(':', $value);
$qbRules[] = [
'id' => 'model',
'value' => $parts[0],
];
$qbRules[] = [
'id' => 'model_id',
'value' => $parts[1],
];
} elseif ($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 (strtoupper($params['user']) === 'SYSTEM') {
$conditions['AuditLog.user_id'] = 0;
} else if (is_numeric($params['user'])) {
$conditions['AuditLog.user_id'] = $params['user'];
} else {
$user = $this->User->find('first', [
'conditions' => ['User.email' => $params['user']],
'fields' => ['id'],
]);
if (!empty($user)) {
$conditions['AuditLog.user_id'] = $user['User']['id'];
} else {
$conditions['AuditLog.user_id'] = -1;
}
}
}
if (isset($params['ip'])) {
$conditions['AuditLog.ip'] = inet_pton($params['ip']);
}
if (isset($params['authkey_id'])) {
$conditions['AuditLog.authkey_id'] = $params['authkey_id'];
}
if (isset($params['request_id'])) {
$conditions['AuditLog.request_id'] = $params['request_id'];
}
if (isset($params['request_type'])) {
$conditions['AuditLog.request_type'] = $params['request_type'];
}
if (isset($params['model'])) {
$conditions['AuditLog.model'] = $params['model'];
}
if (isset($params['model_id'])) {
$conditions['AuditLog.model_id'] = $params['model_id'];
}
if (isset($params['event_id'])) {
$conditions['AuditLog.event_id'] = $params['event_id'];
}
if (isset($params['model_title'])) {
$conditions['AuditLog.model_title LIKE'] = '%' . $params['model_title'] . '%';
}
if (isset($params['action'])) {
$conditions['AuditLog.action'] = $params['action'];
}
if (isset($params['org'])) {
if (is_numeric($params['org'])) {
$conditions['AuditLog.org_id'] = $params['org'];
} else {
$org = $this->AuditLog->Organisation->fetchOrg($params['org']);
if ($org) {
$conditions['AuditLog.org_id'] = $org['id'];
} else {
$conditions['AuditLog.org_id'] = -1;
}
}
}
if (isset($params['created'])) {
$tempData = is_array($params['created']) ? $params['created'] : [$params['created']];
foreach ($tempData as $k => $v) {
$tempData[$k] = $this->AuditLog->resolveTimeDelta($v);
}
if (count($tempData) === 1) {
$conditions['AuditLog.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'][] = ['AuditLog.created <=' => date("Y-m-d H:i:s", $tempData[0])];
$conditions['AND'][] = ['AuditLog.created >=' => date("Y-m-d H:i:s", $tempData[1])];
}
}
return $conditions;
}
/**
* Create conditions that will include just events parts that user can see.
* @param array $event
* @return array
*/
private function __createEventIndexConditions(array $event)
{
if ($this->_isSiteAdmin() || $event['Event']['orgc_id'] == $this->Auth->user('org_id')) {
// Site admins and event owners can see all changes
return ['event_id' => $event['Event']['id']];
}
$event = $this->Event->fetchEvent($this->Auth->user(), [
'eventid' => $event['Event']['id'],
'sgReferenceOnly' => 1,
'deleted' => [0, 1],
'deleted_proposals' => 1,
'noSightings' => true,
'includeEventCorrelations' => false,
'excludeGalaxy' => true,
])[0];
$attributeIds = [];
$objectIds = [];
$proposalIds = array_column($event['ShadowAttribute'], 'id');
$objectReferenceId = [];
foreach ($event['Attribute'] as $aa) {
$attributeIds[] = $aa['id'];
if (!empty($aa['ShadowAttribute'])) {
foreach ($aa['ShadowAttribute'] as $sa) {
$proposalIds[] = $sa['id'];
}
}
}
unset($event['Attribute']);
foreach ($event['Object'] as $ob) {
foreach ($ob['Attribute'] as $aa) {
$attributeIds[] = $aa['id'];
if (!empty($aa['ShadowAttribute'])) {
foreach ($aa['ShadowAttribute'] as $sa) {
$proposalIds[] = $sa['id'];
}
}
}
foreach ($ob['ObjectReference'] as $or) {
$objectReferenceId[] = $or['id'];
}
$objectIds[] = $ob['id'];
}
unset($event['Object']);
$conditions = [];
$conditions['AND']['event_id'] = $event['Event']['id'];
$conditions['AND']['OR'][] = ['model' => 'Event'];
$parts = [
'Attribute' => $attributeIds,
'ShadowAttribute' => $proposalIds,
'Object' => $objectIds,
'ObjectReference' => $objectReferenceId,
'EventReport' => array_column($event['EventReport'], 'id'),
];
foreach ($parts as $model => $modelIds) {
if (!empty($modelIds)) {
$conditions['AND']['OR'][] = [
'AND' => [
'model' => $model,
'model_id' => $modelIds,
],
];
}
}
return $conditions;
}
/**
* Generate link to model view if exists and use has permission to access it.
* @param array $auditLogs
* @return array
*/
private function __appendModelLinks(array $auditLogs)
{
$models = [];
foreach ($auditLogs as $auditLog) {
if (isset($models[$auditLog['AuditLog']['model']])) {
$models[$auditLog['AuditLog']['model']][] = $auditLog['AuditLog']['model_id'];
} else {
$models[$auditLog['AuditLog']['model']] = [$auditLog['AuditLog']['model_id']];
}
}
$eventIds = isset($models['Event']) ? $models['Event'] : [];
if (isset($models['ObjectReference'])) {
$this->loadModel('ObjectReference');
$objectReferences = $this->ObjectReference->find('list', [
'conditions' => ['ObjectReference.id' => array_unique($models['ObjectReference'])],
'fields' => ['ObjectReference.id', 'ObjectReference.object_id'],
]);
}
if (isset($models['Object']) || isset($objectReferences)) {
$objectIds = array_unique(array_merge(
isset($models['Object']) ? $models['Object'] : [],
isset($objectReferences) ? array_values($objectReferences) : []
));
$this->loadModel('MispObject');
$conditions = $this->MispObject->buildConditions($this->Auth->user());
$conditions['Object.id'] = $objectIds;
$objects = $this->MispObject->find('all', [
'conditions' => $conditions,
'contain' => ['Event'],
'fields' => ['Object.id', 'Object.event_id', 'Object.uuid', 'Object.deleted'],
]);
$objects = array_column(array_column($objects, 'Object'), null, 'id');
$eventIds = array_merge($eventIds, array_column($objects, 'event_id'));
}
if (isset($models['Attribute'])) {
$this->loadModel('Attribute');
$attributes = $this->Attribute->fetchAttributesSimple($this->Auth->user(), [
'conditions' => ['Attribute.id' => array_unique($models['Attribute'])],
'fields' => ['Attribute.id', 'Attribute.event_id', 'Attribute.uuid', 'Attribute.deleted'],
]);
$attributes = array_column(array_column($attributes, 'Attribute'), null, 'id');
$eventIds = array_merge($eventIds, array_column($attributes, 'event_id'));
}
if (isset($models['ShadowAttribute'])) {
$this->loadModel('ShadowAttribute');
$conditions = $this->ShadowAttribute->buildConditions($this->Auth->user());
$conditions['AND'][] = ['ShadowAttribute.id' => array_unique($models['ShadowAttribute'])];
$shadowAttributes = $this->ShadowAttribute->find('all', [
'conditions' => $conditions,
'fields' => ['ShadowAttribute.id', 'ShadowAttribute.event_id', 'ShadowAttribute.uuid', 'ShadowAttribute.deleted'],
'contain' => ['Event', 'Attribute'],
]);
$shadowAttributes = array_column(array_column($shadowAttributes, 'ShadowAttribute'), null, 'id');
$eventIds = array_merge($eventIds, array_column($shadowAttributes, 'event_id'));
}
if (!empty($eventIds)) {
$this->loadModel('Event');
$events = $this->Event->fetchSimpleEvents($this->Auth->user(), [
'conditions' => ['Event.id' => array_unique($eventIds)],
]);
$events = array_column(array_column($events, 'Event'), null, 'id');
}
$existingObjects = [];
foreach (['User', 'Organisation', 'Galaxy', 'GalaxyCluster', 'Warninglist'] as $modelName) {
if (isset($models[$modelName])) {
$this->loadModel($modelName);
$data = $this->{$modelName}->find('column', [
'conditions' => ['id' => array_unique($models[$modelName])],
'fields' => ['id'],
]);
$existingObjects[$modelName] = array_flip($data);
}
}
foreach ($auditLogs as $k => $auditLog) {
$auditLog = $auditLog['AuditLog'];
$modelId = $auditLog['model_id'];
$url = null;
$eventInfo = null;
switch ($auditLog['model']) {
case 'Event':
if (isset($events[$modelId])) {
$url = '/events/view/' . $modelId;
$eventInfo = $events[$modelId]['info'];
}
break;
case 'ObjectReference':
if (isset($objectReferences[$modelId]) && isset($objects[$objectReferences[$modelId]])) {
$url = '/events/view/' . $objects[$objectReferences[$modelId]]['event_id'] . '/focus:' . $objects[$objectReferences[$modelId]]['uuid'];
if ($objects[$objectReferences[$modelId]]['deleted']) {
$url .= '/deleted:2';
}
if (isset($events[$objects[$objectReferences[$modelId]]['event_id']])) {
$eventInfo = $events[$objects[$objectReferences[$modelId]]['event_id']]['info'];
}
}
break;
case 'Object':
if (isset($objects[$modelId])) {
$url = '/events/view/' . $objects[$modelId]['event_id'] . '/focus:' . $objects[$modelId]['uuid'];
if ($objects[$modelId]['deleted']) {
$url .= '/deleted:2';
}
if (isset($events[$objects[$modelId]['event_id']])) {
$eventInfo = $events[$objects[$modelId]['event_id']]['info'];
}
}
break;
case 'Attribute':
if (isset($attributes[$modelId])) {
$url = '/events/view/' . $attributes[$modelId]['event_id'] . '/focus:' . $attributes[$modelId]['uuid'];
if ($attributes[$modelId]['deleted']) {
$url .= '/deleted:2';
}
if (isset($events[$attributes[$modelId]['event_id']])) {
$eventInfo = $events[$attributes[$modelId]['event_id']]['info'];
}
}
break;
case 'ShadowAttribute':
if (isset($shadowAttributes[$modelId])) {
$url = '/events/view/' . $shadowAttributes[$modelId]['event_id'] . '/focus:' . $shadowAttributes[$modelId]['uuid'];
if (isset($events[$shadowAttributes[$modelId]['event_id']])) {
$eventInfo = $events[$shadowAttributes[$modelId]['event_id']]['info'];
}
}
break;
case 'User':
if (isset($existingObjects['User'][$modelId])) {
$url = '/admin/users/view/' . $modelId;
}
break;
case 'Warninglist':
if (isset($existingObjects['Warninglist'][$modelId])) {
$url = '/warninglists/view/' . $modelId;
}
break;
case 'Organisation':
if (isset($existingObjects['Organisation'][$modelId])) {
$url = '/organisation/view/' . $modelId;
}
break;
case 'Galaxy':
if (isset($existingObjects['Galaxy'][$modelId])) {
$url = '/galaxies/view/' . $modelId;
}
break;
case 'GalaxyCluster':
if (isset($existingObjects['GalaxyCluster'][$modelId])) {
$url = '/galaxy_clusters/view/' . $modelId;
}
break;
default:
continue 2;
}
if ($url) {
$auditLogs[$k]['AuditLog']['model_link'] = $this->baseurl . $url;
}
if ($eventInfo) {
$auditLogs[$k]['AuditLog']['event_info'] = $eventInfo;
}
}
return $auditLogs;
}
}

View File

@ -377,6 +377,10 @@ class ACLComponent extends Component
'testForStolenAttributes' => array(),
'pruneUpdateLogs' => array()
),
'auditLogs' => [
'admin_index' => ['perm_audit'],
'eventIndex' => ['*'],
],
'modules' => array(
'index' => array('perm_auth'),
'queryEnrichment' => array('perm_auth'),

View File

@ -118,6 +118,8 @@ class LogsController extends AppController
'deleted_proposals' => 1,
'noSightings' => true,
'noEventReports' => true,
'includeEventCorrelations' => false,
'excludeGalaxy' => true,
));
if (empty($event)) {
throw new NotFoundException('Invalid event.');
@ -125,7 +127,7 @@ class LogsController extends AppController
$event = $event[0];
$attribute_ids = array();
$object_ids = array();
$proposal_ids = array();
$proposal_ids = array_column($event['ShadowAttribute'], 'id');;
if (!empty($event['Attribute'])) {
foreach ($event['Attribute'] as $aa) {
$attribute_ids[] = $aa['id'];

View File

@ -6,6 +6,7 @@ class AdminSetting extends AppModel
public $useTable = 'admin_settings';
public $actsAs = array(
'AuditLog',
'SysLogLogable.SysLogLogable' => array(
'userModel' => 'User',
'userKey' => 'user_id',

View File

@ -9,6 +9,7 @@ class Allowedlist extends AppModel
public $displayField = 'name';
public $actsAs = array(
'AuditLog',
'Trim',
'SysLogLogable.SysLogLogable' => array( // TODO Audit, logable
'roleModel' => 'Role',

View File

@ -89,7 +89,8 @@ class AppModel extends Model
45 => false, 46 => false, 47 => false, 48 => false, 49 => false, 50 => false,
51 => false, 52 => false, 53 => false, 54 => false, 55 => false, 56 => false,
57 => false, 58 => false, 59 => false, 60 => false, 61 => false, 62 => false,
63 => true, 64 => false, 65 => false, 66 => false, 67 => false, 68 => false
63 => true, 64 => false, 65 => false, 66 => false, 67 => false, 68 => false,
69 => false,
);
public $advanced_updates_description = array(
@ -1578,6 +1579,27 @@ class AppModel extends Model
case 68:
$sqlArray[] = "ALTER TABLE `correlation_exclusions` ADD `comment` text DEFAULT NULL;";
break;
case 69:
$sqlArray[] = "CREATE TABLE IF NOT EXISTS `audit_logs` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`created` datetime 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_type` tinyint NOT NULL,
`request_id` varchar(255) DEFAULT NULL,
`action` varchar(20) NOT NULL,
`model` varchar(80) NOT NULL,
`model_id` int(11) NOT NULL,
`model_title` text DEFAULT NULL,
`event_id` int(11) NULL,
`change` blob,
PRIMARY KEY (`id`),
INDEX `event_id` (`event_id`),
INDEX `model_id` (`model_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

@ -23,6 +23,7 @@ class Attribute extends AppModel
public $name = 'Attribute'; // TODO general
public $actsAs = array(
'AuditLog',
'SysLogLogable.SysLogLogable' => array( // TODO Audit, logable
'userModel' => 'User',
'userKey' => 'user_id',

View File

@ -7,7 +7,7 @@ App::uses('AppModel', 'Model');
*/
class AttributeTag extends AppModel
{
public $actsAs = array('Containable');
public $actsAs = array('AuditLog', 'Containable');
public $validate = array(
'attribute_id' => array(

323
app/Model/AuditLog.php Normal file
View File

@ -0,0 +1,323 @@
<?php
App::uses('AppModel', 'Model');
/**
* @property Event $Event
* @property User $User
* @property Organisation $Organisation
*/
class AuditLog extends AppModel
{
const BROTLI_HEADER = "\xce\xb2\xcf\x81";
const BROTLI_MIN_LENGTH = 200;
const ACTION_ADD = 'add',
ACTION_EDIT = 'edit',
ACTION_SOFT_DELETE = 'soft_delete',
ACTION_DELETE = 'delete',
ACTION_UNDELETE = 'undelete',
ACTION_TAG = 'tag',
ACTION_TAG_LOCAL = 'tag_local',
ACTION_REMOVE_TAG = 'remove_tag',
ACTION_REMOVE_TAG_LOCAL = 'remove_local_tag',
ACTION_GALAXY = 'galaxy',
ACTION_GALAXY_LOCAL = 'galaxy_local',
ACTION_REMOVE_GALAXY = 'remove_galaxy',
ACTION_REMOVE_GALAXY_LOCAL = 'remove_local_galaxy',
ACTION_PUBLISH = 'publish',
ACTION_PUBLISH_SIGHTINGS = 'publish_sightings';
const REQUEST_TYPE_DEFAULT = 0,
REQUEST_TYPE_API = 1,
REQUEST_TYPE_CLI = 2;
public $actsAs = [
'Containable',
];
/** @var array|null */
private $user = null;
/** @var bool */
private $compressionEnabled;
/**
* Null when not defined, false when not enabled
* @var Syslog|null|false
*/
private $syslog;
public $compressionStats = [
'compressed' => 0,
'bytes_compressed' => 0,
'bytes_uncompressed' => 0,
];
public $belongsTo = [
'User' => [
'className' => 'User',
'foreignKey' => 'user_id',
],
'Event' => [
'className' => 'Event',
'foreignKey' => 'event_id',
],
'Organisation' => [
'className' => 'Organisation',
'foreignKey' => 'org_id',
],
];
public function __construct($id = false, $table = null, $ds = null)
{
parent::__construct($id, $table, $ds);
$this->compressionEnabled = Configure::read('MISP.log_new_audit_compress') && function_exists('brotli_compress');
}
public function afterFind($results, $primary = false)
{
foreach ($results as $key => $result) {
if (isset($result['AuditLog']['ip'])) {
$results[$key]['AuditLog']['ip'] = inet_ntop($result['AuditLog']['ip']);
}
if (isset($result['AuditLog']['change']) && $result['AuditLog']['change']) {
$results[$key]['AuditLog']['change'] = $this->decodeChange($result['AuditLog']['change']);
}
if (isset($result['AuditLog']['action']) && isset($result['AuditLog']['model']) && isset($result['AuditLog']['model_id'])) {
$results[$key]['AuditLog']['title'] = $this->generateUserFriendlyTitle($result['AuditLog']);
}
}
return $results;
}
/**
* @param array $auditLog
* @return string
*/
private function generateUserFriendlyTitle(array $auditLog)
{
if (in_array($auditLog['action'], [self::ACTION_TAG, self::ACTION_TAG_LOCAL, self::ACTION_REMOVE_TAG, self::ACTION_REMOVE_TAG_LOCAL], true)) {
$attached = ($auditLog['action'] === self::ACTION_TAG || $auditLog['action'] === self::ACTION_TAG_LOCAL);
$local = ($auditLog['action'] === self::ACTION_TAG_LOCAL || $auditLog['action'] === self::ACTION_REMOVE_TAG_LOCAL) ? 'local' : 'global';
if ($attached) {
return __('Attached %s tag "%s" to %s #%s', $local, $auditLog['model_title'], strtolower($auditLog['model']), $auditLog['model_id']);
} else {
return __('Detached %s tag "%s" from %s #%s', $local, $auditLog['model_title'], strtolower($auditLog['model']), $auditLog['model_id']);
}
}
if (in_array($auditLog['action'], [self::ACTION_GALAXY, self::ACTION_GALAXY_LOCAL, self::ACTION_REMOVE_GALAXY, self::ACTION_REMOVE_GALAXY_LOCAL], true)) {
$attached = ($auditLog['action'] === self::ACTION_GALAXY || $auditLog['action'] === self::ACTION_GALAXY_LOCAL);
$local = ($auditLog['action'] === self::ACTION_GALAXY_LOCAL || $auditLog['action'] === self::ACTION_REMOVE_GALAXY_LOCAL) ? 'local' : 'global';
if ($attached) {
return __('Attached %s galaxy cluster "%s" to %s #%s', $local, $auditLog['model_title'], strtolower($auditLog['model']), $auditLog['model_id']);
} else {
return __('Detached %s galaxy cluster "%s" from %s #%s', $local, $auditLog['model_title'], strtolower($auditLog['model']), $auditLog['model_id']);
}
}
if (in_array($auditLog['model'], ['Attribute', 'Object', 'ShadowAttribute'], true)) {
$modelName = $auditLog['model'] === 'ShadowAttribute' ? 'Proposal' : $auditLog['model'];
$title = __('%s from Event #%s', $modelName, $auditLog['event_id']);
} else {
$title = "{$auditLog['model']} #{$auditLog['model_id']}";
}
if (isset($auditLog['model_title']) && $auditLog['model_title']) {
$title .= ": {$auditLog['model_title']}";
}
return $title;
}
/**
* @param string $change
* @return array|string
* @throws JsonException
*/
private function decodeChange($change)
{
if (substr($change, 0, 4) === self::BROTLI_HEADER) {
$this->compressionStats['compressed']++;
if (function_exists('brotli_uncompress')) {
$this->compressionStats['bytes_compressed'] += strlen($change);
$change = brotli_uncompress(substr($change, 4));
$this->compressionStats['bytes_uncompressed'] += strlen($change);
if ($change === false) {
return 'Compressed';
}
} else {
return 'Compressed';
}
}
return $this->jsonDecode($change);
}
public function beforeValidate($options = array())
{
if (isset($this->data['AuditLog']['change']) && !is_array($this->data['AuditLog']['change'])) {
$this->invalidate('change', 'Change field must be array');
}
}
public function beforeSave($options = array())
{
if (!isset($this->data['AuditLog']['ip']) && Configure::read('MISP.log_client_ip')) {
$ipHeader = Configure::read('MISP.log_client_ip_header') ?: 'REMOTE_ADDR';
if (isset($_SERVER[$ipHeader])) {
$this->data['AuditLog']['ip'] = inet_pton($_SERVER[$ipHeader]);
}
}
if (!isset($this->data['AuditLog']['user_id'])) {
$this->data['AuditLog']['user_id'] = $this->userInfo()['id'];
}
if (!isset($this->data['AuditLog']['org_id'])) {
$this->data['AuditLog']['org_id'] = $this->userInfo()['org_id'];
}
if (!isset($this->data['AuditLog']['request_type'])) {
$this->data['AuditLog']['request_type'] = $this->userInfo()['request_type'];
}
if (!isset($this->data['AuditLog']['authkey_id'])) {
$this->data['AuditLog']['authkey_id'] = $this->userInfo()['authkey_id'];
}
if (!isset($this->data['AuditLog']['request_id'] ) && isset($_SERVER['HTTP_X_REQUEST_ID'])) {
$this->data['AuditLog']['request_id'] = $_SERVER['HTTP_X_REQUEST_ID'];
}
// Truncate request_id
if (isset($this->data['AuditLog']['request_id']) && strlen($this->data['AuditLog']['request_id']) > 255) {
$this->data['AuditLog']['request_id'] = substr($this->data['AuditLog']['request_id'], 0, 255);
}
// Truncate model title
if (isset($this->data['AuditLog']['model_title']) && mb_strlen($this->data['AuditLog']['model_title']) > 255) {
$this->data['AuditLog']['model_title'] = mb_substr($this->data['AuditLog']['model_title'], 0, 252) . '...';
}
$this->logData($this->data);
if (isset($this->data['AuditLog']['change'])) {
$change = json_encode($this->data['AuditLog']['change'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if ($this->compressionEnabled && strlen($change) >= self::BROTLI_MIN_LENGTH) {
$change = self::BROTLI_HEADER . brotli_compress($change, 4, BROTLI_TEXT);
}
$this->data['AuditLog']['change'] = $change;
}
}
/**
* @param array $data
* @return bool
*/
private function logData(array $data)
{
if (Configure::read('Plugin.ZeroMQ_enable') && Configure::read('Plugin.ZeroMQ_audit_notifications_enable')) {
$pubSubTool = $this->getPubSubTool();
$pubSubTool->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);
}
// write to syslogd as well if enabled
if ($this->syslog === null) {
if (Configure::read('Security.syslog')) {
$options = [];
$syslogToStdErr = Configure::read('Security.syslog_to_stderr');
if ($syslogToStdErr !== null) {
$options['to_stderr'] = $syslogToStdErr;
}
$syslogIdent = Configure::read('Security.syslog_ident');
if ($syslogIdent) {
$options['ident'] = $syslogIdent;
}
$this->syslog = new SysLog($options);
} else {
$this->syslog = false;
}
}
if ($this->syslog) {
$entry = $data['AuditLog']['action'];
$title = $this->generateUserFriendlyTitle($data['AuditLog']);
if ($title) {
$entry .= " -- $title";
}
$this->syslog->write('info', $entry);
}
return true;
}
/**
* @return array
*/
private function userInfo()
{
if ($this->user !== null) {
return $this->user;
}
$this->user = ['id' => 0, 'org_id' => 0, 'authkey_id' => 0, 'request_type' => self::REQUEST_TYPE_DEFAULT];
$isShell = defined('CAKEPHP_SHELL') && CAKEPHP_SHELL;
if ($isShell) {
// do not start session for shell commands and fetch user info from configuration
$this->user['request_type'] = self::REQUEST_TYPE_CLI;
$currentUserId = Configure::read('CurrentUserId');
if (!empty($currentUserId)) {
$this->user['id'] = $currentUserId;
$userFromDb = $this->User->find('first', [
'conditions' => ['User.id' => $currentUserId],
'fields' => ['User.org_id'],
]);
$this->user['org_id'] = $userFromDb['User']['org_id'];
}
} else {
App::uses('AuthComponent', 'Controller/Component');
$authUser = AuthComponent::user();
if (!empty($authUser)) {
$this->user['id'] = $authUser['id'];
$this->user['org_id'] = $authUser['org_id'];
if (isset($authUser['logged_by_authkey']) && $authUser['logged_by_authkey']) {
$this->user['request_type'] = self::REQUEST_TYPE_API;
}
if (isset($authUser['authkey_id'])) {
$this->user['authkey_id'] = $authUser['authkey_id'];
}
}
}
return $this->user;
}
public function insert(array $data)
{
try {
$this->create();
} catch (Exception $e) {
return; // Table is missing when updating, so this is intentional
}
if ($this->save($data) === false) {
throw new Exception($this->validationErrors);
}
}
public function recompress()
{
$changes = $this->find('all', [
'fields' => ['AuditLog.id', 'AuditLog.change'],
'recursive' => -1,
'conditions' => ['length(AuditLog.change) >=' => self::BROTLI_MIN_LENGTH],
]);
foreach ($changes as $change) {
$this->save($change, true, ['id', 'change']);
}
}
}

View File

@ -11,6 +11,7 @@ class AuthKey extends AppModel
public $recursive = -1;
public $actsAs = array(
'AuditLog',
'SysLogLogable.SysLogLogable' => array(
'userModel' => 'User',
'userKey' => 'user_id',

View File

@ -0,0 +1,351 @@
<?php
App::uses('AuditLog', 'Model');
class AuditLogBehavior extends ModelBehavior
{
/** @var array */
private $config;
/** @var array|null */
private $old;
/** @var AuditLog|null */
private $AuditLog;
/** @var bool */
private $enabled;
private $skipFields = [
'id' => true,
'lastpushedid' => true,
'timestamp' => true,
'revision' => true,
'modified' => true,
'date_modified' => true, // User
'current_login' => true, // User
'last_login' => true, // User
'newsread' => true, // User
'proposal_email_lock' => true, // Event
];
private $modelInfo = [
'Event' => 'info',
'User' => 'email',
'Object' => 'name',
'EventReport' => 'name',
'Server' => 'name',
'Feed' => 'name',
'Role' => 'name',
'SharingGroup' => 'name',
'Tag' => 'name',
'TagCollection' => 'name',
'Taxonomy' => 'namespace',
'Organisation' => 'name',
'AdminSetting' => 'setting',
'UserSetting' => 'setting',
'Galaxy' => 'name',
'GalaxyCluster' => 'value',
'Warninglist' => 'name',
];
public function __construct()
{
parent::__construct();
$this->enabled = Configure::read('MISP.log_new_audit');
}
public function setup(Model $model, $config = [])
{
$this->config = $config;
// Generate model info for attribute and proposals
$attributeInfo = function (array $new, array $old) {
$category = isset($new['category']) ? $new['category'] : $old['category'];
$type = isset($new['type']) ? $new['type'] : $old['type'];
$value1 = trim(isset($new['value1']) ? $new['value1'] : $old['value1']);
$value2 = trim(isset($new['value2']) ? $new['value2'] : $old['value2']);
$value = $value1 . (empty($value2) ? '' : '|' . $value2);
return "$category/$type $value";
};
$this->modelInfo['Attribute'] = $attributeInfo;
$this->modelInfo['ShadowAttribute'] = $attributeInfo;
}
public function beforeSave(Model $model, $options = [])
{
if (!$this->enabled) {
return true;
}
if ($model->id) {
$this->old = $model->find('first', [
'conditions' => [$model->alias . '.' . $model->primaryKey => $model->id],
'recursive' => -1,
'callbacks' => false,
]);
} else {
$this->old = null;
}
return true;
}
public function afterSave(Model $model, $created, $options = [])
{
if (!$this->enabled) {
return;
}
if ($model->id) {
$id = $model->id;
} else if ($model->insertId) {
$id = $model->insertId;
} else {
$id = null;
}
if ($created) {
$action = AuditLog::ACTION_ADD;
} else {
$action = AuditLog::ACTION_EDIT;
if (isset($model->data[$model->alias]['deleted'])) {
if ($model->data[$model->alias]['deleted']) {
$action = AuditLog::ACTION_SOFT_DELETE;
} else if (!$model->data[$model->alias]['deleted'] && $this->old[$model->alias]['deleted']) {
$action = AuditLog::ACTION_UNDELETE;
}
}
}
$changedFields = $this->changedFields($model, isset($options['fieldList']) ? $options['fieldList'] : null);
if (empty($changedFields)) {
return;
}
if ($model->name === 'Event') {
$eventId = $id;
} else if (isset($model->data[$model->alias]['event_id'])) {
$eventId = $model->data[$model->alias]['event_id'];
} else if (isset($this->old[$model->alias]['event_id'])) {
$eventId = $this->old[$model->alias]['event_id'];
} else {
$eventId = null;
}
$modelTitle = null;
if (isset($this->modelInfo[$model->name])) {
$modelTitleField = $this->modelInfo[$model->name];
if (is_callable($modelTitleField)) {
$modelTitle = $modelTitleField($model->data[$model->alias], isset($this->old[$model->alias]) ? $this->old[$model->alias] : []);
} else if (isset($model->data[$model->alias][$modelTitleField])) {
$modelTitle = $model->data[$model->alias][$modelTitleField];
} else if ($this->old[$model->alias][$modelTitleField]) {
$modelTitle = $this->old[$model->alias][$modelTitleField];
}
}
$modelName = $model->name === 'MispObject' ? 'Object' : $model->name;
if ($modelName === 'AttributeTag' || $modelName === 'EventTag') {
$action = $model->data[$model->alias]['local'] ? AuditLog::ACTION_TAG_LOCAL : AuditLog::ACTION_TAG;
$tagInfo = $this->getTagInfo($model, $model->data[$model->alias]['tag_id']);
if ($tagInfo) {
$modelTitle = $tagInfo['tag_name'];
if ($tagInfo['is_galaxy']) {
$action = $model->data[$model->alias]['local'] ? AuditLog::ACTION_GALAXY_LOCAL : AuditLog::ACTION_GALAXY;
if ($tagInfo['galaxy_cluster_name']) {
$modelTitle = $tagInfo['galaxy_cluster_name'];
}
}
}
$id = $modelName === 'AttributeTag' ? $model->data[$model->alias]['attribute_id'] : $model->data[$model->alias]['event_id'];
$modelName = $modelName === 'AttributeTag' ? 'Attribute' : 'Event';
}
if ($modelName === 'Event') {
if (isset($changedFields['published'][1]) && $changedFields['published'][1]) {
$action = AuditLog::ACTION_PUBLISH;
} else if (isset($changedFields['sighting_timestamp'][1]) && $changedFields['sighting_timestamp'][1]) {
$action = AuditLog::ACTION_PUBLISH_SIGHTINGS;
}
}
$this->auditLog()->insert([
'action' => $action,
'model' => $modelName,
'model_id' => $id,
'model_title' => $modelTitle,
'event_id' => $eventId,
'change' => $changedFields,
]);
}
public function beforeDelete(Model $model, $cascade = true)
{
if (!$this->enabled) {
return true;
}
$model->recursive = -1;
$model->read();
return true;
}
public function afterDelete(Model $model)
{
if (!$this->enabled) {
return;
}
if ($model->name === 'Event') {
$eventId = $model->id;
} else {
$eventId = isset($model->data[$model->alias]['event_id']) ? $model->data[$model->alias]['event_id'] : null;
}
$modelTitle = null;
if (isset($this->modelInfo[$model->name])) {
$modelTitleField = $this->modelInfo[$model->name];
if (is_callable($modelTitleField)) {
$modelTitle = $modelTitleField($model->data[$model->alias], []);
} else if (isset($model->data[$model->alias][$modelTitleField])) {
$modelTitle = $model->data[$model->alias][$modelTitleField];
}
}
$modelName = $model->name === 'MispObject' ? 'Object' : $model->name;
$action = AuditLog::ACTION_DELETE;
$id = $model->id;
if ($modelName === 'AttributeTag' || $modelName === 'EventTag') {
$action = $model->data[$model->alias]['local'] ? AuditLog::ACTION_REMOVE_TAG_LOCAL : AuditLog::ACTION_REMOVE_TAG;
$tagInfo = $this->getTagInfo($model, $model->data[$model->alias]['tag_id']);
if ($tagInfo) {
$modelTitle = $tagInfo['tag_name'];
if ($tagInfo['is_galaxy']) {
$action = $model->data[$model->alias]['local'] ? AuditLog::ACTION_REMOVE_GALAXY_LOCAL : AuditLog::ACTION_REMOVE_GALAXY;
if ($tagInfo['galaxy_cluster_name']) {
$modelTitle = $tagInfo['galaxy_cluster_name'];
}
}
}
$id = $modelName === 'AttributeTag' ? $model->data[$model->alias]['attribute_id'] : $model->data[$model->alias]['event_id'];
$modelName = $modelName === 'AttributeTag' ? 'Attribute' : 'Event';
}
$this->auditLog()->insert([
'action' => $action,
'model' => $modelName,
'model_id' => $id,
'model_title' => $modelTitle,
'event_id' => $eventId,
'change' => $this->changedFields($model),
]);
}
/**
* @param Model $model
* @param int $tagId
* @return array|null
*/
private function getTagInfo(Model $model, $tagId)
{
$tag = $model->Tag->find('first', [
'conditions' => ['Tag.id' => $tagId],
'recursive' => -1,
'fields' => ['Tag.name', 'Tag.is_galaxy'],
]);
if (empty($tag)) {
return null;
}
$galaxyClusterName = null;
if ($tag['Tag']['is_galaxy']) {
if (!isset($this->GalaxyCluster)) {
$this->GalaxyCluster = ClassRegistry::init('GalaxyCluster');
}
$galaxyCluster = $this->GalaxyCluster->find('first', [
'conditions' => ['GalaxyCluster.tag_name' => $tag['Tag']['name']],
'recursive' => -1,
'fields' => ['GalaxyCluster.value'],
]);
if (!empty($galaxyCluster)) {
$galaxyClusterName = $galaxyCluster['GalaxyCluster']['value'];
}
}
return [
'tag_name' => $tag['Tag']['name'],
'is_galaxy' => $tag['Tag']['is_galaxy'],
'galaxy_cluster_name' => $galaxyClusterName,
];
}
/**
* @param Model $model
* @param array|null $fieldsToSave
* @return array
*/
private function changedFields(Model $model, $fieldsToSave = null)
{
$dbFields = $model->schema();
$changedFields = [];
foreach ($model->data[$model->alias] as $key => $value) {
if (isset($this->skipFields[$key])) {
continue;
}
if (!isset($dbFields[$key])) {
continue;
}
if ($fieldsToSave && !in_array($key, $fieldsToSave, true)) {
continue;
}
if (isset($model->data[$model->alias][$model->primaryKey]) && isset($this->old[$model->alias][$key])) {
$old = $this->old[$model->alias][$key];
} else {
$old = null;
}
// Normalize
if (is_bool($old)) {
$old = $old ? 1 : 0;
}
if (is_bool($value)) {
$value = $value ? 1 : 0;
}
$dbType = $dbFields[$key]['type'];
if ($dbType === 'integer' || $dbType === 'tinyinteger' || $dbType === 'biginteger' || $dbType === 'boolean') {
$value = (int)$value;
if ($old !== null) {
$old = (int)$old;
}
}
if ($value == $old) {
continue;
}
if ($key === 'password' || $key === 'authkey') {
$value = '*****';
if ($old !== null) {
$old = $value;
}
}
if ($old === null) {
$changedFields[$key] = $value;
} else {
$changedFields[$key] = [$old, $value];
}
}
return $changedFields;
}
/**
* @return AuditLog
*/
private function auditLog()
{
if ($this->AuditLog === null) {
$this->AuditLog = ClassRegistry::init('AuditLog');
}
return $this->AuditLog;
}
}

View File

@ -5,6 +5,7 @@ App::uses('AppModel', 'Model');
class Cerebrate extends AppModel
{
public $actsAs = [
'AuditLog',
'SysLogLogable.SysLogLogable' => [
'roleModel' => 'Role',
'roleKey' => 'role_id',

View File

@ -9,6 +9,7 @@ class CorrelationExclusion extends AppModel
public $key = 'misp:correlation_exclusions';
public $actsAs = array(
'AuditLog',
'SysLogLogable.SysLogLogable' => array(
'userModel' => 'User',
'userKey' => 'user_id',

View File

@ -17,6 +17,7 @@ App::uses('SendEmailTemplate', 'Tools');
class Event extends AppModel
{
public $actsAs = array(
'AuditLog',
'SysLogLogable.SysLogLogable' => array(
'userModel' => 'User',
'userKey' => 'user_id',

View File

@ -8,6 +8,7 @@ class EventBlocklist extends AppModel
public $recursive = -1;
public $actsAs = array(
'AuditLog',
'SysLogLogable.SysLogLogable' => array( // TODO Audit, logable
'userModel' => 'User',
'userKey' => 'user_id',

View File

@ -3,7 +3,7 @@ App::uses('AppModel', 'Model');
class EventDelegation extends AppModel
{
public $actsAs = array('Containable');
public $actsAs = array('AuditLog', 'Containable');
public $validate = array(
'event_id' => array(

View File

@ -7,6 +7,7 @@ App::uses('AppModel', 'Model');
class EventReport extends AppModel
{
public $actsAs = array(
'AuditLog',
'Containable',
'SysLogLogable.SysLogLogable' => array(
'userModel' => 'User',

View File

@ -6,7 +6,7 @@ App::uses('AppModel', 'Model');
*/
class EventTag extends AppModel
{
public $actsAs = array('Containable');
public $actsAs = array('AuditLog', 'Containable');
public $validate = array(
'event_id' => array(

View File

@ -5,7 +5,9 @@ App::uses('TmpFileTool', 'Tools');
class Feed extends AppModel
{
public $actsAs = array('SysLogLogable.SysLogLogable' => array(
public $actsAs = array(
'AuditLog',
'SysLogLogable.SysLogLogable' => array(
'change' => 'full'
),
'Trim',

View File

@ -7,6 +7,7 @@ class Galaxy extends AppModel
public $recursive = -1;
public $actsAs = array(
'AuditLog',
'SysLogLogable.SysLogLogable' => array( // TODO Audit, logable
'userModel' => 'User',
'userKey' => 'user_id',

View File

@ -13,6 +13,7 @@ class GalaxyCluster extends AppModel
public $recursive = -1;
public $actsAs = array(
'AuditLog',
'SysLogLogable.SysLogLogable' => array( // TODO Audit, logable
'userModel' => 'User',
'userKey' => 'user_id',

View File

@ -7,6 +7,7 @@ class GalaxyClusterBlocklist extends AppModel
public $recursive = -1;
public $actsAs = array(
'AuditLog',
'SysLogLogable.SysLogLogable' => array( // TODO Audit, logable
'userModel' => 'User',
'userKey' => 'user_id',

View File

@ -8,6 +8,7 @@ class GalaxyClusterRelation extends AppModel
public $recursive = -1;
public $actsAs = array(
'AuditLog',
'SysLogLogable.SysLogLogable' => array( // TODO Audit, logable
'userModel' => 'User',
'userKey' => 'user_id',

View File

@ -4,7 +4,7 @@ App::uses('AppModel', 'Model');
class GalaxyClusterRelationTag extends AppModel
{
public $useTable = 'galaxy_cluster_relation_tags';
public $actsAs = array('Containable');
public $actsAs = array('AuditLog', 'Containable');
public $validate = array(
'galaxy_cluster_relation_id' => array(

View File

@ -7,6 +7,7 @@ class GalaxyElement extends AppModel
public $recursive = -1;
public $actsAs = array(
'AuditLog',
'Containable',
);

View File

@ -206,6 +206,9 @@ class Log extends AppModel
*/
public function createLogEntry($user, $action, $model, $modelId = 0, $title = '', $change = '')
{
if (in_array($action, ['tag', 'galaxy', 'publish', 'publish_sightings'], true) && Configure::read('MISP.log_new_audit')) {
return; // Do not store tag changes when new audit is enabled
}
if ($user === 'SYSTEM') {
$user = array('Organisation' => array('name' => 'SYSTEM'), 'email' => 'SYSTEM', 'id' => 0);
} else if (!is_array($user)) {

View File

@ -15,6 +15,7 @@ class MispObject extends AppModel
public $useTable = 'objects';
public $actsAs = array(
'AuditLog',
'Containable',
'SysLogLogable.SysLogLogable' => array( // TODO Audit, logable
'userModel' => 'User',

View File

@ -3,7 +3,7 @@ App::uses('AppModel', 'Model');
class News extends AppModel
{
public $actsAs = array('Containable');
public $actsAs = array('AuditLog', 'Containable');
public $validate = array(
'message' => array(

View File

@ -5,6 +5,7 @@ App::uses('AppModel', 'Model');
class ObjectReference extends AppModel
{
public $actsAs = array(
'AuditLog',
'Containable',
'SysLogLogable.SysLogLogable' => array( // TODO Audit, logable
'userModel' => 'User',

View File

@ -5,6 +5,7 @@ App::uses('AppModel', 'Model');
class ObjectRelationship extends AppModel
{
public $actsAs = array(
'AuditLog',
'Containable',
'SysLogLogable.SysLogLogable' => array( // TODO Audit, logable
'userModel' => 'User',

View File

@ -5,6 +5,7 @@ App::uses('AppModel', 'Model');
class ObjectTemplate extends AppModel
{
public $actsAs = array(
'AuditLog',
'Containable',
'SysLogLogable.SysLogLogable' => array( // TODO Audit, logable
'userModel' => 'User',

View File

@ -5,6 +5,7 @@ App::uses('AppModel', 'Model');
class ObjectTemplateElement extends AppModel
{
public $actsAs = array(
'AuditLog',
'Containable'
);

View File

@ -7,6 +7,7 @@ class OrgBlocklist extends AppModel
public $recursive = -1;
public $actsAs = array(
'AuditLog',
'SysLogLogable.SysLogLogable' => array( // TODO Audit, logable
'userModel' => 'User',
'userKey' => 'user_id',

View File

@ -12,6 +12,7 @@ class Organisation extends AppModel
public $recursive = -1;
public $actsAs = array(
'AuditLog',
'Containable',
'SysLogLogable.SysLogLogable' => array( // TODO Audit, logable
'roleModel' => 'Organisation',

View File

@ -8,6 +8,7 @@ App::uses('AppModel', 'Model');
class Post extends AppModel
{
public $actsAs = array(
'AuditLog',
'Containable',
'SysLogLogable.SysLogLogable' => array( // TODO Audit, logable
'roleModel' => 'Post',

View File

@ -9,6 +9,7 @@ App::uses('AppModel', 'Model');
class Regexp extends AppModel
{
public $actsAs = array(
'AuditLog',
'SysLogLogable.SysLogLogable' => array( // TODO Audit, logable
'roleModel' => 'Role',
'roleKey' => 'role_id',

View File

@ -39,6 +39,7 @@ class Role extends AppModel
);
public $actsAs = array(
'AuditLog',
'Trim',
'SysLogLogable.SysLogLogable' => array( // TODO Audit, logable
'roleModel' => 'Role',

View File

@ -14,7 +14,9 @@ class Server extends AppModel
public $name = 'Server';
public $actsAs = array('SysLogLogable.SysLogLogable' => array(
public $actsAs = array(
'AuditLog',
'SysLogLogable.SysLogLogable' => array(
'userModel' => 'User',
'userKey' => 'user_id',
'change' => 'full'
@ -469,6 +471,7 @@ class Server extends AppModel
public function pull($user, $id = null, $technique=false, $server, $jobId = false, $force = false)
{
if ($jobId) {
Configure::write('CurrentUserId', $user['id']);
$job = ClassRegistry::init('Job');
$job->read(null, $jobId);
$email = "Scheduled job";

View File

@ -20,6 +20,7 @@ class ShadowAttribute extends AppModel
public $recursive = -1;
public $actsAs = array(
'AuditLog',
'SysLogLogable.SysLogLogable' => array( // TODO Audit, logable
'userModel' => 'User',
'userKey' => 'user_id',

View File

@ -9,6 +9,7 @@ App::uses('AppModel', 'Model');
class SharingGroup extends AppModel
{
public $actsAs = array(
'AuditLog',
'Containable',
'SysLogLogable.SysLogLogable' => array( // TODO Audit, logable
'roleModel' => 'SharingGroup',

View File

@ -2,7 +2,7 @@
App::uses('AppModel', 'Model');
class SharingGroupOrg extends AppModel
{
public $actsAs = array('Containable');
public $actsAs = array('AuditLog', 'Containable');
public $belongsTo = array(
'SharingGroup' => array(

View File

@ -3,7 +3,7 @@ App::uses('AppModel', 'Model');
class SharingGroupServer extends AppModel
{
public $actsAs = array('Containable');
public $actsAs = array('AuditLog', 'Containable');
public $belongsTo = array(
'SharingGroup' => array(

View File

@ -15,6 +15,7 @@ class Sightingdb extends AppModel
);
public $actsAs = array(
'AuditLog',
'SysLogLogable.SysLogLogable' => array(
'userModel' => 'User',
'userKey' => 'user_id',

View File

@ -17,6 +17,7 @@ class SightingdbOrg extends AppModel
);
public $actsAs = array(
'AuditLog',
'SysLogLogable.SysLogLogable' => array(
'userModel' => 'User',
'userKey' => 'user_id',

View File

@ -12,6 +12,7 @@ class Tag extends AppModel
public $displayField = 'name';
public $actsAs = array(
'AuditLog',
'SysLogLogable.SysLogLogable' => array( // TODO Audit, logable
'roleModel' => 'Tag',
'roleKey' => 'tag_id',

View File

@ -9,6 +9,7 @@ class TagCollection extends AppModel
public $displayField = 'name';
public $actsAs = array(
'AuditLog',
'Trim',
'SysLogLogable.SysLogLogable' => array(
'roleModel' => 'Role',

View File

@ -7,6 +7,7 @@ class TagCollectionTag extends AppModel
public $useTable = 'tag_collection_tags';
public $actsAs = array(
'AuditLog',
'Trim',
'SysLogLogable.SysLogLogable' => array(
'roleModel' => 'Role',

View File

@ -11,6 +11,7 @@ class Taxonomy extends AppModel
public $recursive = -1;
public $actsAs = array(
'AuditLog',
'Containable',
);

View File

@ -5,6 +5,7 @@ App::uses('AppModel', 'Model');
class Thread extends AppModel
{
public $actsAs = array(
'AuditLog',
'Containable',
'SysLogLogable.SysLogLogable' => array( // TODO Audit, logable
'roleModel' => 'Thread',

View File

@ -216,6 +216,7 @@ class User extends AppModel
);
public $actsAs = array(
'AuditLog',
'SysLogLogable.SysLogLogable' => array(
'userModel' => 'User',
'userKey' => 'user_id',

View File

@ -7,6 +7,7 @@ class UserSetting extends AppModel
public $recursive = -1;
public $actsAs = array(
'AuditLog',
'SysLogLogable.SysLogLogable' => array(
'userModel' => 'User',
'userKey' => 'user_id',

View File

@ -13,6 +13,7 @@ class Warninglist extends AppModel
public $recursive = -1;
public $actsAs = array(
'AuditLog',
'Containable',
);

View File

@ -2,9 +2,15 @@
App::import('Lib', 'SysLog.SysLog'); // Audit, syslogd, extra
class SysLogLogableBehavior extends LogableBehavior {
class SysLogLogableBehavior extends LogableBehavior
{
public function __construct()
{
parent::__construct();
$this->defaults['enabled'] = !Configure::read('MISP.log_new_audit');
}
function afterSave(Model $Model, $created, $options = array()) {
function afterSave(Model $Model, $created, $options = array()) {
if (!$this->settings[$Model->alias]['enabled']) {
return true;
}
@ -264,6 +270,10 @@ class SysLogLogableBehavior extends LogableBehavior {
$this->settings[$Model->alias] = array_merge($this->defaults, $config);
$this->settings[$Model->alias]['ignore'][] = $Model->primaryKey;
if (!$this->settings[$Model->alias]['enabled']) {
return;
}
$this->Log = ClassRegistry::init('Log');
if ($this->settings[$Model->alias]['userModel'] != $Model->alias) {
$this->UserModel = ClassRegistry::init($this->settings[$Model->alias]['userModel']);

View File

@ -0,0 +1,390 @@
<?php
$formatValue = function($field, $value) {
if (strpos($field, 'timestamp') !== false && is_numeric($value)) {
$date = date('Y-m-d H:i:s', $value);
if ($date !== false) {
return '<span title="Original value: ' . h($value) . '">' . h($date) . '</span>';
}
} else if ($field === 'last_seen' || $field === 'first_seen') {
$ls_sec = intval($value / 1000000); // $ls is in micro (10^6)
$ls_micro = $value % 1000000;
$ls_micro = str_pad($ls_micro, 6, "0", STR_PAD_LEFT);
$ls = $ls_sec . '.' . $ls_micro;
$date = DateTime::createFromFormat('U.u', $ls)->format('Y-m-d\TH:i:s.u');
return '<span title="Original value: ' . h($value) . '">' . h($date) . '</span>';
}
if (mb_strlen($value) > 64) {
$value = mb_substr($value, 0, 64) . '...';
}
return h(json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
};
$removeActions = [
AuditLog::ACTION_DELETE => true,
AuditLog::ACTION_REMOVE_GALAXY_LOCAL => true,
AuditLog::ACTION_REMOVE_GALAXY => true,
AuditLog::ACTION_REMOVE_TAG => true,
AuditLog::ACTION_REMOVE_TAG_LOCAL => true,
];
?><div class="logs index">
<h2><?= __('Audit 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-with-locales');
echo $this->Html->script('doT');
echo $this->Html->script('extendext');
echo $this->Html->css('query-builder.default');
echo $this->Html->script('query-builder');
?>
<script type="text/javascript">
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: "select",
type: "string",
operators: [
"equal",
],
unique: true,
id: "action",
label: "Action",
values: <?= json_encode($actions) ?>
},
{
input: "select",
type: "string",
operators: [
"equal",
],
unique: true,
id: "model",
label: "Model type",
values: <?= json_encode($models) ?>
},
{
input: "text",
type: "integer",
operators: [
"equal",
],
unique: true,
id: "model_id",
label: "Model ID",
},
{
input: "text",
type: "integer",
operators: [
"equal",
],
unique: true,
id: "event_id",
label: "Belongs to event with ID",
},
{
input: "text",
type: "string",
operators: [
"contains",
],
unique: true,
id: "model_title",
label: "Model title",
},
{
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: "select",
type: "integer",
operators: [
"equal",
],
unique: true,
id: "request_type",
label: "Request type",
values: {0: "Browser", 1: "API", 2: "CLI or background job"}
},
{
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",
}
],
rules: {
condition: 'AND',
not: false,
rules: <?= json_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->Paginator->prev('&laquo; ' . __('previous'), array('tag' => 'li', 'escape' => false), null, array('tag' => 'li', 'class' => 'prev disabled', 'escape' => false, 'disabledTag' => 'span'));
$paginator .= $this->Paginator->numbers(array('modulus' => 20, 'separator' => '', 'tag' => 'li', 'currentClass' => 'active', 'currentTag' => 'span'));
$paginator .= $this->Paginator->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->Paginator->sort('created') ?></th>
<th><?= $this->Paginator->sort('user_id', __('User')) ?></th>
<th><?= $this->Paginator->sort('ip', __('IP')) ?></th>
<th><?= $this->Paginator->sort('org_id', __('Org')) ?></th>
<th><?= $this->Paginator->sort('action') ?></th>
<th>Model</th>
<th>Title</th>
<th>Change</th>
</tr>
<?php foreach ($list as $item): ?>
<tr>
<td class="short"><?= h($item['AuditLog']['created']); ?></td>
<td class="short" data-search="user" data-search-value="<?= h($item['AuditLog']['user_id']) ?>"><?php
if (isset($item['AuditLog']['user_id']) && $item['AuditLog']['user_id'] == 0) {
echo __('SYSTEM') . ' <i class="fas fa-terminal" title="' . __('Action done by CLI') .'"></i>';
} else if (isset($item['User']['email'])) {
echo '<a href="' . $baseurl . '/admin/users/view/' . h($item['User']['id']) . '">' . h($item['User']['email']) . '</a>';
if ($item['AuditLog']['request_type'] == AuditLog::REQUEST_TYPE_CLI) {
echo ' <i class="fas fa-terminal" title="' . __('Action done by CLI or background job') .'"></i>';
} else if ($item['AuditLog']['request_type'] == AuditLog::REQUEST_TYPE_API) {
$key = $item['AuditLog']['authkey_id'] ? ' ' . __('by auth key %s', h($item['AuditLog']['authkey_id'])) : '';
echo ' <i class="fas fa-cogs" title="' . __('Action done trough API') . $key . '"></i>';
}
} else {
echo __('<i>Deleted user #%s</i>', h($item['AuditLog']['user_id']));
} ?></td>
<td class="short" data-search="ip" data-search-value="<?= h($item['AuditLog']['ip']) ?>"><?= h($item['AuditLog']['ip']) ?></td>
<td class="short" data-search="org" data-search-value="<?= h($item['AuditLog']['org_id']) ?>">
<?php if (isset($item['Organisation']) && $item['Organisation']['id']) {
echo $this->OrgImg->getOrgLogo($item, 24);
} else if ($item['AuditLog']['org_id'] != 0) {
echo __('<i>Deleted org #%s</i>', h($item['AuditLog']['org_id']));
}
?>
</td>
<td class="short"><?= h($item['AuditLog']['action_human']) ?></td>
<td class="short" data-search="model" data-search-value="<?= h($item['AuditLog']['model']) . ':' . h($item['AuditLog']['model_id']) ?>">
<?php $title = isset($item['AuditLog']['event_info']) ? ' title="' . __('Event #%s: %s', $item['AuditLog']['event_id'], h($item['AuditLog']['event_info'])) . '"' : '' ?>
<?= isset($item['AuditLog']['model_link']) ? '<a href="' . h($item['AuditLog']['model_link']) . '"' . $title . '>' : '' ?>
<?= h($item['AuditLog']['model']) . ' #' . h($item['AuditLog']['model_id']) ?>
<?= isset($item['AuditLog']['model_link']) ? '</a>' : '' ?>
</td>
<td class="limitedWidth"><?= h($item['AuditLog']['title']) ?></td>
<td><?php
if (is_array($item['AuditLog']['change'])) {
foreach ($item['AuditLog']['change'] as $field => $values) {
echo '<span class="json_key">' . h($field) . ':</span> ';
if (isset($removeActions[$item['AuditLog']['action']])) {
echo '<span class="json_string">' . $formatValue($field, $values) . '</span> <i class="fas fa-arrow-right json_null"></i> <i class="fas fa-times json_string"></i><br>';
} else {
if (is_array($values)) {
echo '<span class="json_string">' . $formatValue($field, $values[0]) . '</span> ';
$value = $values[1];
} else {
$value = $values;
}
echo '<i class="fas fa-arrow-right json_null"></i> <span class="json_string">' . $formatValue($field, $value) . '</span><br>';
}
}
}
?></td>
</tr>
<?php endforeach; ?>
</table>
<p>
<?= $this->Paginator->counter(array(
'format' => __('Page {:page} of {:pages}, showing {:current} records out of {:count} total, starting on record {:start}, ending on {:end}')
));
?>
</p>
<div class="pagination">
<ul>
<?= $paginator ?>
</ul>
</div>
</div>
<script type="text/javascript">
var passedArgs = <?= $passedArgs ?>;
$('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');
$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' => 'listAuditLogs']);

View File

@ -0,0 +1,88 @@
<?php
$formatValue = function($value) {
if (mb_strlen($value) > 64) {
$value = mb_substr($value, 0, 64) . '...';
}
return h(json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
};
$removeActions = [
AuditLog::ACTION_DELETE => true,
AuditLog::ACTION_REMOVE_GALAXY_LOCAL => true,
AuditLog::ACTION_REMOVE_GALAXY => true,
AuditLog::ACTION_REMOVE_TAG => true,
AuditLog::ACTION_REMOVE_TAG_LOCAL => true,
];
?><div class="logs index">
<h2><?= __('Audit logs for event #%s', $event['Event']['id']) ?></h2>
<div class="pagination">
<ul>
<?php
$paginator = $this->Paginator->prev('&laquo; ' . __('previous'), array('tag' => 'li', 'escape' => false), null, array('tag' => 'li', 'class' => 'prev disabled', 'escape' => false, 'disabledTag' => 'span'));
$paginator .= $this->Paginator->numbers(array('modulus' => 20, 'separator' => '', 'tag' => 'li', 'currentClass' => 'active', 'currentTag' => 'span'));
$paginator .= $this->Paginator->next(__('next') . ' &raquo;', array('tag' => 'li', 'escape' => false), null, array('tag' => 'li', 'class' => 'next disabled', 'escape' => false, 'disabledTag' => 'span'));
echo $paginator;
?>
<li><a href="<?= $baseurl . '/logs/event_index/' . h($event['Event']['id']) ?>"><?= __('Older logs') ?></a></li>
</ul>
</div>
<table class="table table-striped table-hover table-condensed">
<tr>
<th><?= $this->Paginator->sort('created');?></th>
<th><?= $this->Paginator->sort('user_id', __('User'));?></th>
<th><?= $this->Paginator->sort('org_id', __('Org'));?></th>
<th><?= $this->Paginator->sort('action');?></th>
<th>Model</th>
<th>Title</th>
<th>Change</th>
</tr>
<?php foreach ($list as $item): ?>
<tr>
<td class="short"><?= h($item['AuditLog']['created']); ?></td>
<td class="short"><?php
if (isset($item['AuditLog']['user_id']) && $item['AuditLog']['user_id'] == 0) {
echo __('SYSTEM');
} else if (isset($item['User']['email'])) {
echo h($item['User']['email']);
} ?></td>
<td class="short"><?= isset($item['Organisation']) ? $this->OrgImg->getOrgLogo($item, 24) : '' ?></td>
<td class="short"><?= h($item['AuditLog']['action_human']) ?></td>
<td class="short"><?= h($item['AuditLog']['model']) . ' #' . h($item['AuditLog']['model_id']) ?></td>
<td class="limitedWidth"><?= h($item['AuditLog']['title']) ?></td>
<td><?php
if (is_array($item['AuditLog']['change'])) {
foreach ($item['AuditLog']['change'] as $field => $values) {
echo '<span class="json_key">' . h($field) . ':</span> ';
if (isset($removeActions[$item['AuditLog']['action']])) {
echo '<span class="json_string">' . $formatValue($values) . '</span> <i class="fas fa-arrow-right json_null"></i> <i class="fas fa-times json_string"></i><br>';
} else {
if (is_array($values)) {
echo '<span class="json_string">' . $formatValue($values[0]) . '</span> ';
$value = $values[1];
} else {
$value = $values;
}
echo '<i class="fas fa-arrow-right json_null"></i> <span class="json_string">' . $formatValue($value) . '</span><br>';
}
}
}
?></td>
</tr>
<?php endforeach; ?>
</table>
<p>
<?= $this->Paginator->counter(array(
'format' => __('Page {:page} of {:pages}, showing {:current} records out of {:count} total, starting on record {:start}, ending on {:end}')
));
?>
</p>
<div class="pagination">
<ul>
<?= $paginator ?>
<li><a href="<?= $baseurl . '/logs/event_index/' . h($event['Event']['id']) ?>"><?= __('Older logs') ?></a></li>
</ul>
</div>
</div>
<?= $this->element('/genericElements/SideMenu/side_menu', ['menuList' => 'event', 'menuItem' => 'eventLog', 'event' => $event, 'mayModify' => $mayModify]);

View File

@ -94,7 +94,7 @@ $divider = $this->element('/genericElements/SideMenu/side_menu_divider');
));
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'eventLog',
'url' => $baseurl . '/logs/event_index/' . $eventId,
'url' => $baseurl . (Configure::read('MISP.log_new_audit') ? '/audit_logs/eventIndex/' : '/logs/event_index/') . $eventId,
'text' => __('View Event History')
));
echo $divider;
@ -1024,6 +1024,12 @@ $divider = $this->element('/genericElements/SideMenu/side_menu_divider');
'url' => $baseurl . '/admin/logs/index',
'text' => __('List Logs')
));
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'listAuditLogs',
'url' => $baseurl . '/admin/audit_logs/index',
'text' => __('List Audit Logs'),
'requirement' => Configure::read('MISP.log_new_audit'),
));
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'url' => $baseurl . '/admin/logs/search',
'text' => __('Search Logs')

View File

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

View File

@ -82,8 +82,12 @@
$contributorsContent = [];
foreach ($contributors as $organisationId => $name) {
$org = ['Organisation' => ['id' => $organisationId, 'name' => $name]];
$link = $baseurl . "/logs/event_index/" . $event['Event']['id'] . '/' . h($name);
$contributorsContent[] = $this->OrgImg->getNameWithImg($org, $link);
if (Configure::read('MISP.log_new_audit')) {
$link = $baseurl . "/audit_logs/eventIndex/" . h($event['Event']['id']) . '/' . h($organisationId);
} else {
$link = $baseurl . "/logs/event_index/" . h($event['Event']['id']) . '/' . h($name);
}
$contributorsContent[] = $this->OrgImg->getNameWithImg($org, $link);
}
$table_data[] = array(
'key' => __('Contributors'),

View File

@ -2732,3 +2732,8 @@ Query builder
margin-top: 0;
}
/* Fix text input for query builder */
.query-builder .rule-value-container input[type="text"] {
padding: 4px !important;
height: 30px;
}

View File

@ -384,6 +384,162 @@
"extra": ""
}
],
"audit_logs": [
{
"column_name": "id",
"is_nullable": "NO",
"data_type": "int",
"character_maximum_length": null,
"numeric_precision": "10",
"collation_name": null,
"column_type": "int(11)",
"column_default": null,
"extra": "auto_increment"
},
{
"column_name": "created",
"is_nullable": "NO",
"data_type": "datetime",
"character_maximum_length": null,
"numeric_precision": null,
"collation_name": null,
"column_type": "datetime",
"column_default": null,
"extra": ""
},
{
"column_name": "user_id",
"is_nullable": "NO",
"data_type": "int",
"character_maximum_length": null,
"numeric_precision": "10",
"collation_name": null,
"column_type": "int(11)",
"column_default": null,
"extra": ""
},
{
"column_name": "org_id",
"is_nullable": "NO",
"data_type": "int",
"character_maximum_length": null,
"numeric_precision": "10",
"collation_name": null,
"column_type": "int(11)",
"column_default": null,
"extra": ""
},
{
"column_name": "authkey_id",
"is_nullable": "NO",
"data_type": "int",
"character_maximum_length": null,
"numeric_precision": "10",
"collation_name": null,
"column_type": "int(11)",
"column_default": null,
"extra": ""
},
{
"column_name": "ip",
"is_nullable": "YES",
"data_type": "varbinary",
"character_maximum_length": "16",
"numeric_precision": null,
"collation_name": null,
"column_type": "varbinary(16)",
"column_default": "NULL",
"extra": ""
},
{
"column_name": "request_type",
"is_nullable": "NO",
"data_type": "tinyint",
"character_maximum_length": null,
"numeric_precision": "3",
"collation_name": null,
"column_type": "tinyint(4)",
"column_default": null,
"extra": ""
},
{
"column_name": "request_id",
"is_nullable": "YES",
"data_type": "varchar",
"character_maximum_length": "255",
"numeric_precision": null,
"collation_name": "utf8mb4_unicode_ci",
"column_type": "varchar(255)",
"column_default": "NULL",
"extra": ""
},
{
"column_name": "action",
"is_nullable": "NO",
"data_type": "varchar",
"character_maximum_length": "20",
"numeric_precision": null,
"collation_name": "utf8mb4_unicode_ci",
"column_type": "varchar(20)",
"column_default": null,
"extra": ""
},
{
"column_name": "model",
"is_nullable": "NO",
"data_type": "varchar",
"character_maximum_length": "80",
"numeric_precision": null,
"collation_name": "utf8mb4_unicode_ci",
"column_type": "varchar(80)",
"column_default": null,
"extra": ""
},
{
"column_name": "model_id",
"is_nullable": "NO",
"data_type": "int",
"character_maximum_length": null,
"numeric_precision": "10",
"collation_name": null,
"column_type": "int(11)",
"column_default": null,
"extra": ""
},
{
"column_name": "model_title",
"is_nullable": "YES",
"data_type": "text",
"character_maximum_length": "65535",
"numeric_precision": null,
"collation_name": "utf8mb4_unicode_ci",
"column_type": "text",
"column_default": "NULL",
"extra": ""
},
{
"column_name": "event_id",
"is_nullable": "YES",
"data_type": "int",
"character_maximum_length": null,
"numeric_precision": "10",
"collation_name": null,
"column_type": "int(11)",
"column_default": "NULL",
"extra": ""
},
{
"column_name": "change",
"is_nullable": "YES",
"data_type": "blob",
"character_maximum_length": "65535",
"numeric_precision": null,
"collation_name": null,
"column_type": "blob",
"column_default": "NULL",
"extra": ""
}
],
"auth_keys": [
{
"column_name": "id",
@ -7591,6 +7747,11 @@
"event_id": false,
"tag_id": false
},
"audit_logs": {
"id": true,
"event_id": false,
"model_id": false
},
"auth_keys": {
"id": true,
"authkey_start": false,
@ -7997,5 +8158,5 @@
"id": true
}
},
"db_version": "68"
"db_version": "69"
}