Merge branch 'develop' of github.com:MISP/MISP into migration-warninglists

pull/7386/head
mokaddem 2021-05-05 15:18:07 +02:00
commit 6e5c6494eb
No known key found for this signature in database
GPG Key ID: 164C473F627A06FA
77 changed files with 2509 additions and 240 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,150 @@
<?php
/**
* @property Log $Log
* @property AuditLog $AuditLog
* @property Server $Server
*/
class LogShell extends AppShell
{
public $uses = ['Log', 'AuditLog', 'Server'];
public function getOptionParser()
{
$parser = parent::getOptionParser();
$parser->addSubcommand('auditStatistics', [
'help' => __('Show statistics from audit logs.'),
]);
$parser->addSubcommand('statistics', [
'help' => __('Show statistics from logs.'),
]);
$parser->addSubcommand('export', [
'help' => __('Export logs to compressed file in JSON Lines format (one JSON encoded line per entry).'),
'parser' => array(
'arguments' => array(
'file' => ['help' => __('Path to output file'), 'required' => true],
),
),
]);
return $parser;
}
public function export()
{
list($path) = $this->args;
if (file_exists($path)) {
$this->error("File $path already exists");
}
$file = gzopen($path, 'wb4'); // Compression level 4 is best compromise between time and size
if ($file === false) {
$this->error("Could not open $path for writing");
}
$rows = $this->Log->query("SELECT TABLE_ROWS FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'logs';");
/** @var ProgressShellHelper $progress */
$progress = $this->helper('progress');
$progress->init([
'total' => $rows[0]['TABLES']['TABLE_ROWS'], // just estimate, but fast
'width' => 50,
]);
$lastId = 0;
while (true) {
$logs = $this->Log->find('all', [
'conditions' => ['id >' => $lastId], // much faster than offset
'recursive' => -1,
'limit' => 100000,
'order' => ['id ASC'],
]);
if (empty($logs)) {
break;
}
$lines = '';
foreach ($logs as $log) {
$log = $log['Log'];
foreach (['id', 'model_id', 'user_id'] as $field) {
$log[$field] = (int)$log[$field]; // Convert to int to save space
}
if (empty($log['description'])) {
unset($log['description']);
}
if (empty($log['ip'])) {
unset($log['ip']);
}
$log['created'] = strtotime($log['created']); // to save space
if ($log['id'] > $lastId) {
$lastId = $log['id'];
}
$lines .= json_encode($log, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR) . "\n";
}
if (gzwrite($file, $lines) === false) {
$this->error("Could not write data to $path");
}
$progress->increment(count($logs));
$progress->draw();
}
gzclose($file);
$this->out('Done');
}
public function statistics()
{
$count = $this->Log->find('count');
$first = $this->Log->find('first', [
'recursive' => -1,
'fields' => ['created'],
'order' => ['id ASC'],
]);
$last = $this->Log->find('first', [
'recursive' => -1,
'fields' => ['created'],
'order' => ['id DESC'],
]);
$this->out(str_pad(__('Count:'), 20) . $count);
$this->out(str_pad(__('First:'), 20) . $first['Log']['created']);
$this->out(str_pad(__('Last:'), 20) . $last['Log']['created']);
$usage = $this->Server->dbSpaceUsage()['logs'];
$this->out(str_pad(__('Data size:'), 20) . CakeNumber::toReadableSize($usage['data_in_bytes']));
$this->out(str_pad(__('Index size:'), 20) . CakeNumber::toReadableSize($usage['index_in_bytes']));
$this->out(str_pad(__('Reclaimable size:'), 20) . CakeNumber::toReadableSize($usage['reclaimable_in_bytes']), 2);
}
public function auditStatistics()
{
$count = $this->AuditLog->find('count');
$first = $this->AuditLog->find('first', [
'recursive' => -1,
'fields' => ['created'],
'order' => ['id ASC'],
]);
$last = $this->AuditLog->find('first', [
'recursive' => -1,
'fields' => ['created'],
'order' => ['id DESC'],
]);
$this->out(str_pad(__('Count:'), 20) . $count);
$this->out(str_pad(__('First:'), 20) . $first['AuditLog']['created']);
$this->out(str_pad(__('Last:'), 20) . $last['AuditLog']['created']);
$usage = $this->Server->dbSpaceUsage()['audit_logs'];
$this->out(str_pad(__('Data size:'), 20) . CakeNumber::toReadableSize($usage['data_in_bytes']));
$this->out(str_pad(__('Index size:'), 20) . CakeNumber::toReadableSize($usage['index_in_bytes']));
$this->out(str_pad(__('Reclaimable size:'), 20) . CakeNumber::toReadableSize($usage['reclaimable_in_bytes']), 2);
// Just to fetch compressionStats
$this->AuditLog->find('column', [
'fields' => ['change'],
]);
$this->out('Change field:');
$this->out('-------------');
$this->out(str_pad(__('Compressed items:'), 20) . $this->AuditLog->compressionStats['compressed']);
$this->out(str_pad(__('Uncompressed size:'), 20) . CakeNumber::toReadableSize($this->AuditLog->compressionStats['bytes_uncompressed']));
$this->out(str_pad(__('Compressed size:'), 20) . CakeNumber::toReadableSize($this->AuditLog->compressionStats['bytes_compressed']));
}
}

View File

@ -25,7 +25,7 @@ class AppController extends Controller
public $helpers = array('OrgImg', 'FontAwesome', 'UserName', 'DataPathCollector');
private $__queryVersion = '128';
private $__queryVersion = '129';
public $pyMispVersion = '2.4.142';
public $phpmin = '7.2';
public $phprec = '7.4';

View File

@ -0,0 +1,586 @@
<?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',
'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']));
}
public function fullChange($id)
{
$log = $this->AuditLog->find('first', [
'conditions' => ['id' => $id],
'recursive' => -1,
'fields' => ['change', 'action'],
]);
if (empty($log)) {
throw new Exception('Log not found.');
}
$this->set('log', $log);
}
public function returnDates($org = 'all')
{
if (!$this->Auth->user('Role')['perm_sharing_group'] && !empty(Configure::read('Security.hide_organisation_index_from_users'))) {
if ($org !== 'all' && $org !== $this->Auth->user('Organisation')['name']) {
throw new MethodNotAllowedException('Invalid organisation.');
}
}
// Fetching dates can be slow, so to allow concurrent requests, we can close sessions to release session lock
session_write_close();
$data = $this->AuditLog->returnDates($org);
return $this->RestResponse->viewData($data, $this->response->type());
}
/**
* @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', 'AuthKey', 'ObjectTemplate', 'Role'] 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);
}
}
$links = [
'ObjectTemplate' => 'objectTemplates',
'AuthKey' => 'auth_keys',
'GalaxyCluster' => 'galaxy_clusters',
'Galaxy' => 'galaxies',
'Organisation' => 'organisation',
'Warninglist' => 'warninglists',
'User' => 'admin/user',
'Role' => 'roles',
];
foreach ($auditLogs as $k => $auditLog) {
$auditLog = $auditLog['AuditLog'];
$modelId = (int)$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;
default:
if (isset($existingObjects[$auditLog['model']][$modelId])) {
$url = '/' . $links[$auditLog['model']] . '/view/' . $modelId;
} else {
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,12 @@ 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'),

View File

@ -610,11 +610,14 @@ class RestResponseComponent extends Component
}
/**
* Detect if request comes from automatic tool, like other MISP instance or PyMISP
* Detect if request comes from automatic tool (like other MISP instance or PyMISP) or AJAX
* @return bool
*/
public function isAutomaticTool()
{
if ($this->Controller->request->is('ajax')) {
return true;
}
$userAgent = CakeRequest::header('User-Agent');
return $userAgent && (substr($userAgent, 0, 6) === 'PyMISP' || substr($userAgent, 0, 4) === 'MISP');
}

View File

@ -5454,10 +5454,10 @@ class EventsController extends AppController
$editors = array_unique($editors);
if ($event['Event']['timestamp'] > $timestamp && empty($editors)) {
$message = __('<b>Warning<b>: This event view is outdated. Please reload page to see latest changes.');
$message = __('<b>Warning</b>: This event view is outdated. Please reload page to see latest changes.');
$this->set('class', 'alert');
} else if ($event['Event']['timestamp'] > $timestamp) {
$message = __('<b>Warning<b>: This event view is outdated, because is currently being edited by: %s. Please reload page to see latest changes.', h(implode(', ', $editors)));
$message = __('<b>Warning</b>: This event view is outdated, because is currently being edited by: %s. Please reload page to see latest changes.', h(implode(', ', $editors)));
$this->set('class', 'alert');
} else if (empty($editors)) {
return new CakeResponse(['status' => 204]);

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

@ -1813,14 +1813,14 @@ class UsersController extends AppController
{
// set all of the data up for the heatmaps
$params = array(
'fields' => array('name'),
'fields' => array('id', 'name'),
'recursive' => -1,
'conditions' => array()
);
if (!$this->_isSiteAdmin() && !empty(Configure::read('Security.hide_organisation_index_from_users'))) {
$params['conditions'] = array('Organisation.id' => $this->Auth->user('org_id'));
}
$orgs = $this->User->Organisation->find('all', $params);
$orgs = $this->User->Organisation->find('list', $params);
$local_orgs_params = $params;
$local_orgs_params['conditions']['Organisation.local'] = 1;
@ -1848,7 +1848,7 @@ class UsersController extends AppController
$stats['correlation_count'] = $this->Correlation->find('count', array('recursive' => -1));
$stats['correlation_count'] = $stats['correlation_count'] / 2;
$stats['proposal_count'] = $this->User->Event->ShadowAttribute->find('count', array('recursive' => -1));
$stats['proposal_count'] = $this->User->Event->ShadowAttribute->find('count', array('recursive' => -1, 'conditions' => array('deleted' => 0)));
$stats['user_count'] = $this->User->find('count', array('recursive' => -1));
$stats['user_count_pgp'] = $this->User->find('count', array('recursive' => -1, 'conditions' => array('User.gpgkey !=' => '')));
@ -1869,16 +1869,17 @@ class UsersController extends AppController
'stats' => $stats
);
return $this->RestResponse->viewData($data, $this->response->type());
} else {
$this->set('stats', $stats);
$this->set('orgs', $orgs);
$this->set('start', strtotime(date('Y-m-d H:i:s') . ' -5 months'));
$this->set('end', strtotime(date('Y-m-d H:i:s')));
$this->set('startDateCal', $year . ', ' . $month . ', 01');
$range = '[5, 10, 50, 100]';
$this->set('range', $range);
$this->render('statistics_data');
}
$this->set('stats', $stats);
$this->set('orgs', $orgs);
$this->set('start', strtotime(date('Y-m-d H:i:s') . ' -5 months'));
$this->set('end', strtotime(date('Y-m-d H:i:s')));
$this->set('startDateCal', $year . ', ' . $month . ', 01');
$range = '[5, 10, 50, 100]';
$this->set('range', $range);
$this->set('activityUrl', $this->baseurl . (Configure::read('MISP.log_new_audit') ? '/audit_logs' : '/logs') . '/returnDates');
$this->render('statistics_data');
}
private function __statisticsSightings($params = array())

View File

@ -283,6 +283,9 @@ class WarninglistsController extends AppController
throw new NotFoundException(__('No valid data received.'));
}
$data = $this->request->data;
if (is_array($data) && isset($data['Warninglist'])) {
$data = $data['Warninglist'];
}
if (!is_array($data)) {
$data = array($data);
}
@ -301,9 +304,15 @@ class WarninglistsController extends AppController
}
}
}
return $this->RestResponse->viewData($hits, $this->response->type());
if ($this->_isRest()) {
return $this->RestResponse->viewData($hits, $this->response->type());
}
$this->set('hits', $hits);
$this->set('data', $data);
} else {
return $this->RestResponse->describe('Warninglists', 'checkValue', false, $this->response->type());
if ($this->_isRest()) {
return $this->RestResponse->describe('Warninglists', 'checkValue', false, $this->response->type());
}
}
}
}

View File

@ -30,7 +30,7 @@ class UsageDataWidget
$this->Correlation = ClassRegistry::init('Correlation');
$correlationsCount = $this->Correlation->find('count', array('recursive' => -1)) / 2;
$proposalsCount = $this->Event->ShadowAttribute->find('count', array('recursive' => -1));
$proposalsCount = $this->Event->ShadowAttribute->find('count', array('recursive' => -1, 'conditions' => array('deleted' => 0)));
$usersCount = $this->User->find('count', array('recursive' => -1));
$usersCountPgp = $this->User->find('count', array('recursive' => -1, 'conditions' => array('User.gpgkey !=' => '')));

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(

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

@ -0,0 +1,366 @@
<?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']);
}
}
/**
* @param string|int $org
* @return array
*/
public function returnDates($org = 'all')
{
$conditions = [];
if ($org !== 'all') {
$org = $this->Organisation->fetchOrg($org);
if (empty($org)) {
throw new NotFoundException('Invalid organisation.');
}
$conditions['org_id'] = $org['id'];
}
$dataSource = ConnectionManager::getDataSource('default')->config['datasource'];
if ($dataSource === 'Database/Mysql' || $dataSource === 'Database/MysqlObserver') {
$validDates = $this->find('all', [
'recursive' => -1,
'fields' => ['DISTINCT UNIX_TIMESTAMP(DATE(created)) AS Date', 'count(id) AS count'],
'conditions' => $conditions,
'group' => ['Date'],
'order' => ['Date'],
]);
} elseif ($dataSource === 'Database/Postgres') {
if (!empty($conditions['org_id'])) {
$condOrg = 'WHERE org_id = "' . $conditions['org_id'] . '"';
} else {
$condOrg = '';
}
$sql = 'SELECT DISTINCT EXTRACT(EPOCH FROM CAST(created AS DATE)) AS "Date", COUNT(id) AS count
FROM audit_logs
' . $condOrg . '
GROUP BY "Date" ORDER BY "Date"';
$validDates = $this->query($sql);
}
$data = [];
foreach ($validDates as $k => $date) {
$data[(int)$date[0]['Date']] = (int)$date[0]['count'];
}
return $data;
}
}

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,357 @@
<?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
'warninglist_entry_count' => true, // Warninglist
];
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;
$this->modelInfo['AuthKey'] = function (array $new, array $old) {
$start = isset($new['authkey_start']) ? $new['authkey_start'] : $old['authkey_start'];
$end = isset($new['authkey_end']) ? $new['authkey_end'] : $old['authkey_end'];
return "$start********************************$end";
};
}
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',
@ -3154,6 +3155,21 @@ class Event extends AppModel
));
return true;
}
$banStatus = $this->getEventRepublishBanStatus($id);
if ($banStatus['active']) {
$this->Log = ClassRegistry::init('Log');
$this->Log->create();
$this->Log->save(array(
'org' => 'SYSTEM',
'model' => 'Event',
'model_id' => $id,
'email' => $user['email'],
'action' => 'publish',
'title' => __('E-mail alerts not sent out during publishing'),
'change' => $banStatus['message'],
));
return !$banStatus['error'];
}
if (Configure::read('MISP.background_jobs')) {
$job = ClassRegistry::init('Job');
$job->create();
@ -7304,4 +7320,59 @@ class Event extends AppModel
}
return [];
}
public function getEventRepublishBanStatus($eventID)
{
$banStatus = [
'error' => false,
'active' => false,
'message' => __('Event publish is not banned')
];
if (Configure::read('MISP.event_alert_republish_ban')) {
$event = $this->find('first', array(
'conditions' => array('Event.id' => $eventID),
'recursive' => -1,
'fields' => array('Event.uuid')
));
if (empty($event)) {
$banStatus['error'] = true;
$banStatus['active'] = true;
$banStatus['message'] = __('Event not found');
return $banStatus;
}
$banThresholdMinutes = intval(Configure::read('MISP.event_alert_republish_ban_threshold'));
$banThresholdSeconds = 60 * $banThresholdMinutes;
$redis = $this->setupRedis();
if ($redis === false) {
$banStatus['error'] = true;
$banStatus['active'] = true;
$banStatus['message'] = __('Reason: Could not reach redis to chech republish emailing ban status.');
return $banStatus;
}
$redisKey = "misp:event_alert_republish_ban:{$event['Event']['uuid']}";
$banLiftTimestamp = $redis->get($redisKey);
if (!empty($banLiftTimestamp)) {
$remainingMinutes = (intval($banLiftTimestamp) - time()) / 60;
$banStatus['active'] = true;
if (Configure::read('MISP.event_alert_republish_ban_refresh_on_retry')) {
$redis->multi(Redis::PIPELINE)
->set($redisKey, time() + $banThresholdSeconds)
->expire($redisKey, $banThresholdSeconds)
->exec();
$banStatus['message'] = __('Reason: Event is banned from sending out emails. Ban has been refreshed and will be lifted in %smin', $banThresholdMinutes);
} else {
$banStatus['message'] = __('Reason: Event is banned from sending out emails. Ban will be lifted in %smin %ssec.', floor($remainingMinutes), $remainingMinutes % 60);
}
return $banStatus;
} else {
$redis->multi(Redis::PIPELINE)
->set($redisKey, time() + $banThresholdSeconds)
->expire($redisKey, $banThresholdSeconds)
->exec();
return $banStatus;
}
}
$banStatus['message'] = __('Emailing republishing ban setting is not enabled');
return $banStatus;
}
}

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

@ -155,11 +155,11 @@ class Log extends AppModel
$conditions = array();
$this->Organisation = ClassRegistry::init('Organisation');
if ($org !== 'all') {
$org = $this->Organisation->find('first', array('fields' => array('name'), 'recursive' => -1, 'conditions' => array('UPPER(Organisation.name) LIKE' => strtoupper($org))));
$org = $this->Organisation->fetchOrg($org);
if (empty($org)) {
return MethodNotAllowedException('Invalid organisation.');
throw new MethodNotAllowedException('Invalid organisation.');
}
$conditions['org'] = $org['Organisation']['name'];
$conditions['org'] = $org['name'];
}
$conditions['AND']['NOT'] = array('action' => array('login', 'logout', 'changepw'));
if ($dataSource == 'Database/Mysql' || $dataSource == 'Database/MysqlObserver') {
@ -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

@ -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";
@ -2628,24 +2631,32 @@ class Server extends AppModel
public function dbSpaceUsage()
{
$inMb = function ($value) {
return round($value / 1024 / 1024, 2) . " MB";
};
$result = [];
$dataSource = $this->getDataSource()->config['datasource'];
if ($dataSource == 'Database/Mysql' || $dataSource == 'Database/MysqlObserver') {
if ($dataSource === 'Database/Mysql' || $dataSource === 'Database/MysqlObserver') {
$sql = sprintf(
'select TABLE_NAME, sum((DATA_LENGTH+INDEX_LENGTH)/1024/1024) AS used, sum(DATA_FREE)/1024/1024 AS reclaimable from information_schema.tables where table_schema = %s group by TABLE_NAME;',
'select TABLE_NAME, DATA_LENGTH, INDEX_LENGTH, DATA_FREE from information_schema.tables where table_schema = %s group by TABLE_NAME;',
"'" . $this->getDataSource()->config['database'] . "'"
);
$sqlResult = $this->query($sql);
$result = array();
foreach ($sqlResult as $temp) {
foreach ($temp[0] as $k => $v) {
$temp[0][$k] = round($v, 2) . 'MB';
}
$temp[0]['table'] = $temp['tables']['TABLE_NAME'];
$result[] = $temp[0];
$result[$temp['tables']['TABLE_NAME']] = [
'table' => $temp['tables']['TABLE_NAME'],
'used' => $inMb($temp['tables']['DATA_LENGTH'] + $temp['tables']['INDEX_LENGTH']),
'reclaimable' => $inMb($temp['tables']['DATA_FREE']),
'data_in_bytes' => (int) $temp['tables']['DATA_LENGTH'],
'index_in_bytes' => (int) $temp['tables']['INDEX_LENGTH'],
'reclaimable_in_bytes' => (int) $temp['tables']['DATA_FREE'],
];
}
return $result;
}
else if ($dataSource == 'Database/Postgres') {
else if ($dataSource === 'Database/Postgres') {
$sql = sprintf(
'select TABLE_NAME as table, pg_total_relation_size(%s||%s||TABLE_NAME) as used from information_schema.tables where table_schema = %s group by TABLE_NAME;',
"'" . $this->getDataSource()->config['database'] . "'",
@ -2653,19 +2664,18 @@ class Server extends AppModel
"'" . $this->getDataSource()->config['database'] . "'"
);
$sqlResult = $this->query($sql);
$result = array();
foreach ($sqlResult as $temp) {
foreach ($temp[0] as $k => $v) {
if ($k == "table") {
continue;
}
$temp[0][$k] = round($v / 1024 / 1024, 2) . 'MB';
$temp[0][$k] = $inMb($v);
}
$temp[0]['reclaimable'] = '0MB';
$temp[0]['reclaimable'] = '0 MB';
$result[] = $temp[0];
}
return $result;
}
return $result;
}
public function redisInfo()
@ -3553,19 +3563,13 @@ class Server extends AppModel
{
$this->ResqueStatus = new ResqueStatus\ResqueStatus(Resque::redis());
$workers = $this->ResqueStatus->getWorkers();
if (function_exists('posix_getpwuid')) {
$currentUser = posix_getpwuid(posix_geteuid());
$currentUser = $currentUser['name'];
} else {
$currentUser = trim(shell_exec('whoami'));
}
$killed = array();
foreach ($workers as $pid => $worker) {
if (!is_numeric($pid)) {
throw new MethodNotAllowedException('Non numeric PID found!');
}
$pidTest = substr_count(trim(shell_exec('ps -p ' . $pid)), PHP_EOL) > 0 ? true : false;
if ($worker['user'] == $currentUser && !$pidTest) {
$pidTest = file_exists('/proc/' . addslashes($pid));
if (!$pidTest) {
$this->ResqueStatus->removeWorker($pid);
$this->__logRemoveWorker($user, $pid, $worker['queue'], true);
if (empty($killed[$worker['queue']])) {
@ -4441,7 +4445,7 @@ class Server extends AppModel
'OR' => [
'Correlation.attribute_id = Attribute.id',
]
]
]
],
@ -5331,6 +5335,33 @@ class Server extends AppModel
'type' => 'string',
'null' => false,
),
'event_alert_republish_ban' => array(
'level' => 1,
'description' => __('Enable this setting to start blocking alert e-mails for events that have already been published since a specified amount of time. This threshold is defined by MISP.event_alert_republish_ban_threshold'),
'value' => false,
'errorMessage' => '',
'test' => 'testBool',
'type' => 'boolean',
'null' => false,
),
'event_alert_republish_ban_threshold' => array(
'level' => 1,
'description' => __('If the MISP.event_alert_republish_ban setting is set, this setting will control how long no alerting by email will be done. Expected format: integer, in minutes'),
'value' => 5,
'errorMessage' => '',
'test' => 'testForNumeric',
'type' => 'numeric',
'null' => false,
),
'event_alert_republish_ban_refresh_on_retry' => array(
'level' => 1,
'description' => __('If the MISP.event_alert_republish_ban setting is set, this setting will control if a ban time should be reset if emails are tried to be sent during the ban.'),
'value' => false,
'errorMessage' => '',
'test' => 'testBool',
'type' => 'boolean',
'null' => false,
),
'org_alert_threshold' => array(
'level' => 1,
'description' => __('Set a value to limit the number of email alerts that events can generate per creator organisation (for example, if an organisation pushes out 2000 events in one shot, only alert on the first 20).'),
@ -5524,6 +5555,15 @@ class Server extends AppModel
'type' => 'numeric',
'null' => true,
],
'warning_for_all' => [
'level' => 1,
'description' => __('Enable warning list triggers regardless of the IDS flag value'),
'value' => false,
'errorMessage' => '',
'test' => 'testBool',
'type' => 'boolean',
'null' => true
],
),
'GnuPG' => array(
'branch' => 1,

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,357 @@
<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"><?= $this->Time->time($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');
} else 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['AuditLog']['user_id']));
}
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>';
}
?></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" data-search="action" data-search-value="<?= h($item['AuditLog']['action']) ?>"><?= 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 ondblclick="showFullChange(<?= h($item['AuditLog']['id']) ?>)"><?= $this->element('AuditLog/change', ['item' => $item]) ?></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 ?>;
function showFullChange(id) {
$.get(baseurl + "/audit_logs/fullChange/" + id, function(data) {
var $popoverFormLarge = $('#popover_form_large');
$popoverFormLarge.html(data);
$popoverFormLarge.find("span.json").each(function () {
$(this).html(syntaxHighlightJson($(this).text()));
});
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');
$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,55 @@
<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"><?= $this->Time->time($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><?= $this->element('AuditLog/change', ['item' => $item]) ?></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

@ -0,0 +1,3 @@
<div style="padding: 1em; background: white; word-wrap: break-word;">
<?= $this->element('AuditLog/change', ['item' => $log, 'full' => true]) ?>
</div>

View File

@ -0,0 +1,52 @@
<?php
$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,
];
$full = isset($full) ? $full : false;
$formatValue = function($field, $value) use ($full) {
if ((strpos($field, 'timestamp') !== false || in_array($field, ['expiration', 'created', 'date_created'], true)) && 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 ($full && is_string($value) && !empty($value) && ($value[0] === '{' || $value[0] === '[') && json_decode($value) !== null) {
return '<span class="json">' . h($value) . '</span>';
}
if (!$full && mb_strlen($value) > 64) {
$value = mb_substr($value, 0, 64) . '...';
}
return h(json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
};
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>';
}
}
}

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;
@ -519,6 +519,12 @@ $divider = $this->element('/genericElements/SideMenu/side_menu_divider');
'message' => __('Are you sure you want to update all warninglists?')
));
}
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'check_value',
'url' => $baseurl . '/warninglists/checkValue',
'text' => __('Search in Warninglists')
));
break;
case 'noticelist':
@ -1024,6 +1030,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

@ -1,125 +0,0 @@
<?php
echo $this->Html->script('d3');
echo $this->Html->script('cal-heatmap');
echo $this->Html->css('cal-heatmap');
?>
<div class = "index">
<h2><?php echo __('Statistics');?></h2>
<p><?php echo __('Some statistics about this instance. The changes since the beginning of this month are noted in brackets wherever applicable');?></p>
<div style="width:250px;">
<dl>
<dt><?php echo __('Events');?></dt>
<dd><?php echo h($stats[0]);
if ($stats[1]) echo ' <span style="color:green">(+' . h($stats[1]) . ')</span>&nbsp;';
else echo ' <span style="color:red">(0)</span>&nbsp;';?>
</dd>
<dt><?php echo __('Attributes'); ?></dt>
<dd><?php echo h($stats[2]);
if ($stats[1]) echo ' <span style="color:green">(+' . h($stats[3]) . ')</span>&nbsp;';
else echo ' <span style="color:red">(0)</span>&nbsp;';?>
</dd>
<dt><?php echo __('Correlations found'); ?></dt>
<dd><?php echo h($stats[4]); ?>&nbsp;</dd>
<dt><?php echo __('Proposals active'); ?></dt>
<dd><?php echo h($stats[5]); ?>&nbsp;</dd>
<dt><?php echo __('Users'); ?></dt>
<dd><?php echo h($stats[6]); ?>&nbsp;</dd>
<dt><?php echo __('Organisations'); ?></dt>
<dd><?php echo h($stats[7]); ?>&nbsp;</dd>
<dt><?php echo __('Discussion threads'); ?></dt>
<dd><?php echo h($stats[8]);
if ($stats[9]) echo ' <span style="color:green">(+' . h($stats[9]) . ')</span>&nbsp;';
else echo ' <span style="color:red">(0)</span>&nbsp;';?>
</dd>
<dt><?php echo __('Discussion posts'); ?></dt>
<dd><?php echo h($stats[10]);
if ($stats[11]) echo ' <span style="color:green">(+' . h($stats[11]) . ')</span>&nbsp;';
else echo ' <span style="color:red">(0)</span>&nbsp;';?>
</dd>
</dl>
</div>
<br />
<h3><?php echo __('Activity Heatmap');?></h3>
<p><?php echo __('A heatmap showing user activity for each day during this month and the 4 months that preceded it. Use the buttons below to only show the heatmap of a specific organisation.');?></p>
<div id="orgs">
<select onchange="updateCalendar(this.options[this.selectedIndex].value);">
<option value="all"><?php echo __('All organisations');?></option>
<?php
foreach ($orgs as $org):
?>
<option value="<?php echo h($org['Organisation']['name']); ?>"><?php echo h($org['Organisation']['name']); ?></option>
<?php
endforeach;
?>
</select>
</div>
<div>
<table>
<tr>
<td style="vertical-align:top;">
<div style="margin-right:5px;margin-top:40px;"><button id="goLeft" class="btn" onClick="goLeft();" title="<?php echo __('Go left');?>"><span class="icon-arrow-left"></span></button></div>
</td>
<td>
<div id="cal-heatmap"></div>
</td>
<td style="vertical-align:top;">
<div style="margin-left:5px;margin-top:40px;"><button id="goRight" class="btn" onClick="goRight();" title="<?php echo __('Go right');?>"><span class="icon-arrow-right"></span></button></div>
</td>
</tr>
</table>
</div>
<script type="text/javascript">
var cal = new CalHeatMap();
var orgSelected = "all";
cal.init({
range: 5,
domain:"month",
subDomain:"x_day",
start: new Date(<?php echo $startDateCal; ?>),
data: "<?php echo Configure::read('MISP.baseurl'); ?>/logs/returnDates.json",
highlight: "now",
domainDynamicDimension: false,
cellSize: 20,
cellPadding: 1,
domainGutter: 10,
legend: <?php echo $range;?>,
legendCellSize: 15,
});
function updateCalendar(org) {
if (org == "all") {
cal.update("<?php echo Configure::read('MISP.baseurl'); ?>/logs/returnDates/all.json");
orgSelected = "all";
} else {
cal.update("<?php echo Configure::read('MISP.baseurl'); ?>/logs/returnDates/"+org+".json");
orgSelected = org;
}
}
function goRight() {
cal.options.data = "<?php echo Configure::read('MISP.baseurl'); ?>/logs/returnDates/"+orgSelected+".json";
cal.next();
}
function goLeft() {
cal.options.data = "<?php echo Configure::read('MISP.baseurl'); ?>/logs/returnDates/"+orgSelected+".json";
cal.previous();
}
</script>
<?php
if (preg_match('/(?i)msie [2-9]/',$_SERVER['HTTP_USER_AGENT']) && !strpos($_SERVER['HTTP_USER_AGENT'], 'Opera')) {
if (preg_match('%(?i)Trident/(.*?).0%', $_SERVER['HTTP_USER_AGENT'], $matches) && isset($matches[1]) && $matches[1] > 5) {
?>
<br /><br /><p style="color:red;font-size:11px;"><?php echo __('The above graph will not work correctly in Compatibility mode. Please make sure that it is disabled in your Internet Explorer settings.');?></p>
<?php
} else {
?>
<br /><br /><p style="color:red;font-size:11px;"><?php echo __('The above graph will not work correctly on Internet Explorer 9.0 and earlier. Please download Chrome, Firefox or upgrade to a newer version of Internet Explorer.');?></p>
<?php
}
}
?>
</div>
<?php
echo $this->element('/genericElements/SideMenu/side_menu', array('menuList' => 'globalActions', 'menuItem' => 'statistics'));
?>

View File

@ -57,13 +57,9 @@
<div id="orgs">
<select onchange="updateCalendar(this.options[this.selectedIndex].value);">
<option value="all"><?php echo __('All organisations');?></option>
<?php
foreach ($orgs as $org):
?>
<option value="<?php echo h($org['Organisation']['name']); ?>"><?php echo h($org['Organisation']['name']); ?></option>
<?php
endforeach;
?>
<?php foreach ($orgs as $orgId => $orgName): ?>
<option value="<?php echo h($orgId); ?>"><?php echo h($orgName); ?></option>
<?php endforeach; ?>
</select>
</div>
<div>
@ -89,7 +85,7 @@ cal.init({
domain:"month",
subDomain:"x_day",
start: new Date(<?php echo $startDateCal; ?>),
data: "<?php echo Configure::read('MISP.baseurl'); ?>/logs/returnDates.json",
data: "<?= $activityUrl ?>.json",
highlight: "now",
domainDynamicDimension: false,
cellSize: 20,
@ -101,21 +97,21 @@ cal.init({
function updateCalendar(org) {
if (org == "all") {
cal.update("<?php echo Configure::read('MISP.baseurl'); ?>/logs/returnDates/all.json");
cal.update("<?= $activityUrl ?>/all.json");
orgSelected = "all";
} else {
cal.update("<?php echo Configure::read('MISP.baseurl'); ?>/logs/returnDates/"+org+".json");
cal.update("<?= $activityUrl ?>/"+org+".json");
orgSelected = org;
}
}
function goRight() {
cal.options.data = "<?php echo Configure::read('MISP.baseurl'); ?>/logs/returnDates/"+orgSelected+".json";
cal.options.data = "<?= $activityUrl ?>/"+orgSelected+".json";
cal.next();
}
function goLeft() {
cal.options.data = "<?php echo Configure::read('MISP.baseurl'); ?>/logs/returnDates/"+orgSelected+".json";
cal.options.data = "<?= $activityUrl ?>/"+orgSelected+".json";
cal.previous();
}
</script>
@ -133,6 +129,4 @@ if (preg_match('/(?i)msie [2-9]/',$_SERVER['HTTP_USER_AGENT']) && !strpos($_SERV
}
?>
</div>
<?php
echo $this->element('/genericElements/SideMenu/side_menu', array('menuList' => 'globalActions', 'menuItem' => 'statistics'));
?>
<?= $this->element('/genericElements/SideMenu/side_menu', array('menuList' => 'globalActions', 'menuItem' => 'statistics'));

View File

@ -0,0 +1,31 @@
<div class="warninglist view">
<h2><?= __('Search in Warninglists') ?></h2>
<?php
echo $this->Form->create('Warninglist');
echo sprintf('<div class="input-append">%s%s</div>',
$this->Form->input('', array(
'label' => false,
'div' => false,
'type' => 'text',
'class' => 'input-xlarge',
)),
$this->Form->button(__('Search'), array('class' => 'btn btn-primary', 'placeholder' => __('Enter a value to search for')))
);
echo $this->Form->end();
?>
<?php if(!empty($hits)): ?>
<?php foreach ($hits as $value => $lists): ?>
<?= __('Result for <i>%s</i>:', h($value))?>
<ul>
<?php foreach ($lists as $list): ?>
<li><a href="<?= $baseurl . '/warninglists/view/' . h($list['id']) ?>"><?= h($list['name']) ?></a></li>
<?php endforeach; ?>
</ul>
<?php endforeach; ?>
<?php elseif (!empty($data)): ?>
<?= __('No hits for: <i>%s</i>', h($data)) ?>
<?php endif; ?>
</div>
<?= $this->element('/genericElements/SideMenu/side_menu', array('menuList' => 'warninglist', 'menuItem' => 'check_value')); ?>

@ -1 +1 @@
Subproject commit ef9989dbe85d798b62ebfb8acfe23559292cde6f
Subproject commit 94ec98d544177c9b00c2bc0fe7913a1a5270db0c

View File

@ -401,7 +401,8 @@ class StixBuilder():
coa_args = {'id': coa_id, 'type': 'course-of-action', 'created_by_ref': self.identity_id}
coa_args['labels'] = self.create_object_labels(misp_object['name'], misp_object['meta-category'], to_ids)
for attribute in misp_object['Attribute']:
self.parse_galaxies(attribute['Galaxy'], coa_id)
if attribute.get('Galaxy'):
self.parse_galaxies(attribute['Galaxy'], coa_id)
relation = attribute['object_relation']
if relation in ('name', 'description'):
coa_args[relation] = attribute['value']
@ -471,7 +472,8 @@ class StixBuilder():
def add_indicator(self, attribute):
indicator_id = "indicator--{}".format(attribute['uuid'])
self.parse_galaxies(attribute['Galaxy'], indicator_id)
if attribute.get('Galaxy'):
self.parse_galaxies(attribute['Galaxy'], indicator_id)
category = attribute['category']
killchain = self.create_killchain(category)
labels, markings = self.create_labels(attribute)
@ -502,7 +504,8 @@ class StixBuilder():
def add_observed_data(self, attribute):
observed_data_id = "observed-data--{}".format(attribute['uuid'])
self.parse_galaxies(attribute['Galaxy'], observed_data_id)
if attribute.get('Galaxy'):
self.parse_galaxies(attribute['Galaxy'], observed_data_id)
timestamp = self.get_datetime_from_timestamp(attribute['timestamp'])
labels, markings = self.create_labels(attribute)
observable = self.define_observable(attribute)
@ -746,10 +749,8 @@ class StixBuilder():
def fetch_custom_values(self, attributes, object_id):
values = defaultdict(list)
for attribute in attributes:
try:
if attribute.get('Galaxy'):
self.parse_galaxies(attribute['Galaxy'], object_id)
except KeyError:
pass
attribute_type = '{}_{}'.format(attribute['type'], attribute['object_relation'].replace('.', '_DOT_'))
values[attribute_type].append(attribute['value'])
return {attribute_type: value[0] if len(value) == 1 else value for attribute_type, value in values.items()}
@ -1111,7 +1112,8 @@ class StixBuilder():
observable = {}
object_num = 0
for attribute in attributes:
self.parse_galaxies(attribute['Galaxy'], object_id)
if attribute.get('Galaxy'):
self.parse_galaxies(attribute['Galaxy'], object_id)
relation = attribute['object_relation']
try:
stix_type = misp2stix2_mapping.asnObjectMapping[relation]
@ -1132,7 +1134,8 @@ class StixBuilder():
mapping = misp2stix2_mapping.objectsMapping['asn']['pattern']
pattern = []
for attribute in attributes:
self.parse_galaxies(attribute['Galaxy'], object_id)
if attribute.get('Galaxy'):
self.parse_galaxies(attribute['Galaxy'], object_id)
relation = attribute['object_relation']
try:
stix_type = misp2stix2_mapping.asnObjectMapping[relation]
@ -1148,7 +1151,8 @@ class StixBuilder():
def resolve_credential_observable(self, attributes, object_id):
user_account = misp2stix2_mapping.objectsMapping['credential']['observable']
for attribute in attributes:
self.parse_galaxies(attribute['Galaxy'], object_id)
if attribute.get('Galaxy'):
self.parse_galaxies(attribute['Galaxy'], object_id)
relation = attribute['object_relation']
try:
stix_type = misp2stix2_mapping.credentialObjectMapping[relation]
@ -1161,7 +1165,8 @@ class StixBuilder():
mapping = misp2stix2_mapping.objectsMapping['credential']['pattern']
pattern = []
for attribute in attributes:
self.parse_galaxies(attribute['Galaxy'], object_id)
if attribute.get('Galaxy'):
self.parse_galaxies(attribute['Galaxy'], object_id)
relation = attribute['object_relation']
try:
stix_type = misp2stix2_mapping.credentialObjectMapping[relation]
@ -1172,7 +1177,8 @@ class StixBuilder():
def resolve_domain_ip_observable(self, attributes, object_id):
for attribute in attributes:
self.parse_galaxies(attribute['Galaxy'], object_id)
if attribute.get('Galaxy'):
self.parse_galaxies(attribute['Galaxy'], object_id)
if attribute['type'] == 'ip-dst':
ip_value = attribute['value']
elif attribute['type'] == 'domain':
@ -1184,7 +1190,8 @@ class StixBuilder():
mapping = misp2stix2_mapping.objectsMapping['domain-ip']['pattern']
pattern = []
for attribute in attributes:
self.parse_galaxies(attribute['Galaxy'], object_id)
if attribute.get('Galaxy'):
self.parse_galaxies(attribute['Galaxy'], object_id)
try:
stix_type = misp2stix2_mapping.domainIpObjectMapping[attribute['type']]
except KeyError:
@ -1198,7 +1205,8 @@ class StixBuilder():
additional_header = {}
object_num = 0
for attribute in attributes:
self.parse_galaxies(attribute['Galaxy'], object_id)
if attribute.get('Galaxy'):
self.parse_galaxies(attribute['Galaxy'], object_id)
relation = attribute['object_relation']
attribute_value = attribute['value']
try:
@ -1241,7 +1249,8 @@ class StixBuilder():
pattern = []
n = 0
for attribute in attributes:
self.parse_galaxies(attribute['Galaxy'], object_id)
if attribute.get('Galaxy'):
self.parse_galaxies(attribute['Galaxy'], object_id)
relation = attribute['object_relation']
try:
mapping = misp2stix2_mapping.emailObjectMapping[relation]
@ -1343,7 +1352,8 @@ class StixBuilder():
ip_address = {}
domain = {}
for attribute in attributes:
self.parse_galaxies(attribute['Galaxy'], object_id)
if attribute.get('Galaxy'):
self.parse_galaxies(attribute['Galaxy'], object_id)
relation = attribute['object_relation']
attribute_value = attribute['value']
if relation == 'ip':
@ -1395,7 +1405,8 @@ class StixBuilder():
def resolve_ip_port_pattern(self, attributes, object_id):
pattern = []
for attribute in attributes:
self.parse_galaxies(attribute['Galaxy'], object_id)
if attribute.get('Galaxy'):
self.parse_galaxies(attribute['Galaxy'], object_id)
relation = attribute['object_relation']
attribute_value = attribute['value']
if relation == 'domain':
@ -1463,7 +1474,8 @@ class StixBuilder():
states = []
tmp_attributes = {}
for attribute in attributes:
self.parse_galaxies(attribute['Galaxy'], object_id)
if attribute.get('Galaxy'):
self.parse_galaxies(attribute['Galaxy'], object_id)
relation = attribute['object_relation']
if relation == 'state':
states.append(attribute['value'])
@ -1477,7 +1489,8 @@ class StixBuilder():
current_process['type'] = 'process'
n = 0
for attribute in attributes:
self.parse_galaxies(attribute['Galaxy'], object_id)
if attribute.get('Galaxy'):
self.parse_galaxies(attribute['Galaxy'], object_id)
relation = attribute['object_relation']
if relation == 'parent-pid':
str_n = str(n)
@ -1506,7 +1519,8 @@ class StixBuilder():
mapping = misp2stix2_mapping.objectsMapping['process']['pattern']
pattern = []
for attribute in attributes:
self.parse_galaxies(attribute['Galaxy'], object_id)
if attribute.get('Galaxy'):
self.parse_galaxies(attribute['Galaxy'], object_id)
try:
pattern.append(mapping.format(misp2stix2_mapping.processMapping[attribute['object_relation']], attribute['value']))
except KeyError:
@ -1518,7 +1532,8 @@ class StixBuilder():
values = {}
registry_value_types = ('data', 'data-type', 'name')
for attribute in attributes:
self.parse_galaxies(attribute['Galaxy'], object_id)
if attribute.get('Galaxy'):
self.parse_galaxies(attribute['Galaxy'], object_id)
relation = attribute['object_relation']
try:
stix_type = misp2stix2_mapping.regkeyMapping[relation]
@ -1540,7 +1555,8 @@ class StixBuilder():
fields = ('key', 'value')
registry_value_types = ('data', 'data-type', 'name')
for attribute in attributes:
self.parse_galaxies(attribute['Galaxy'], object_id)
if attribute.get('Galaxy'):
self.parse_galaxies(attribute['Galaxy'], object_id)
relation = attribute['object_relation']
try:
stix_type = misp2stix2_mapping.regkeyMapping[relation]
@ -1607,7 +1623,8 @@ class StixBuilder():
def resolve_url_observable(self, attributes, object_id):
url_args = {}
for attribute in attributes:
self.parse_galaxies(attribute['Galaxy'], object_id)
if attribute.get('Galaxy'):
self.parse_galaxies(attribute['Galaxy'], object_id)
if attribute['type'] == 'url':
# If we have the url (WE SHOULD), we return the observable supported atm with the url value
observable = {'0': {'type': 'url', 'value': attribute['value']}}
@ -1632,7 +1649,8 @@ class StixBuilder():
def resolve_url_pattern(self, attributes, object_id):
pattern = []
for attribute in attributes:
self.parse_galaxies(attribute['Galaxy'], object_id)
if attribute.get('Galaxy'):
self.parse_galaxies(attribute['Galaxy'], object_id)
attribute_type = attribute['type']
try:
stix_type = misp2stix2_mapping.urlMapping[attribute_type]
@ -1687,7 +1705,8 @@ class StixBuilder():
def parse_user_account_attributes(self, attributes, object_id):
tmp_attributes = defaultdict(list)
for attribute in attributes:
self.parse_galaxies(attribute['Galaxy'], object_id)
if attribute.get('Galaxy'):
self.parse_galaxies(attribute['Galaxy'], object_id)
relation = attribute['object_relation']
if relation == 'group':
tmp_attributes[relation].append(attribute['value'])
@ -1704,7 +1723,8 @@ class StixBuilder():
hashes = {}
attributes2parse = defaultdict(list)
for attribute in attributes:
self.parse_galaxies(attribute['Galaxy'], object_id)
if attribute.get('Galaxy'):
self.parse_galaxies(attribute['Galaxy'], object_id)
relation = attribute['object_relation']
if relation in ("x509-fingerprint-md5", "x509-fingerprint-sha1", "x509-fingerprint-sha256"):
hashes[relation.split('-')[2]] = attribute['value']
@ -1724,7 +1744,8 @@ class StixBuilder():
mapping = misp2stix2_mapping.objectsMapping['x509']['pattern']
pattern = []
for attribute in attributes:
self.parse_galaxies(attribute['Galaxy'], object_id)
if attribute.get('Galaxy'):
self.parse_galaxies(attribute['Galaxy'], object_id)
relation = attribute['object_relation']
if relation in ("x509-fingerprint-md5", "x509-fingerprint-sha1", "x509-fingerprint-sha256"):
stix_type = f"hashes.'{relation.split('-')[2]}'"
@ -1754,7 +1775,8 @@ class StixBuilder():
attributes_dict = defaultdict(list)
for attribute in attributes:
attributes_dict[attribute['object_relation']].append(self._parse_attribute(attribute))
self.parse_galaxies(attribute['Galaxy'], object_id)
if attribute.get('Galaxy'):
self.parse_galaxies(attribute['Galaxy'], object_id)
return {key: value[0] if key not in multiple_fields and len(value) == 1 else value for key, value in attributes_dict.items()}
@staticmethod
@ -1800,7 +1822,7 @@ class StixBuilder():
@staticmethod
def handle_time_fields(attribute, timestamp, stix_type):
to_return = {'created': timestamp, 'modified': timestamp}
iso_timestamp = timestamp.isoformat(timespec='milliseconds')
iso_timestamp = f"{timestamp.isoformat(timespec='milliseconds')}Z"
for misp_field, stix_field in zip(('first_seen', 'last_seen'), _time_fields[stix_type]):
to_return[stix_field] = datetime.strptime(attribute[misp_field].split('+')[0], '%Y-%m-%dT%H:%M:%S.%f') if attribute.get(misp_field) else iso_timestamp
return to_return

@ -1 +1 @@
Subproject commit c2400b392a85fae1ae0c92cbde4ca510b23b6189
Subproject commit a993b89aaab3551cfc8f5656cc1673e532f24378

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

@ -153,10 +153,12 @@ function buildMISPElementHints() {
MISPElementHints['object'] = []
Object.keys(proxyMISPElements['object']).forEach(function(uuid) {
var object = proxyMISPElements['object'][uuid]
var topPriorityValue = getTopPriorityValue(object)
MISPElementHints['object'].push(
[object.name, uuid],
[object.id, uuid],
[object.uuid, uuid],
[topPriorityValue, uuid],
)
})
MISPElementHints['galaxymatrix'] = []
@ -181,6 +183,7 @@ function hintMISPElements(cm, options) {
var authorizedMISPElements = ['attribute', 'object', 'galaxymatrix', 'tag']
var availableScopes = ['attribute', 'object', 'galaxymatrix', 'tag']
var reMISPElement = RegExp('@\\[(?<scope>' + authorizedMISPElements.join('|') + ')\\]\\((?<elementid>[^\\)]+)?\\)');
var reMISPScope = RegExp('@\\[(?<scope>\\S+)\\]\\(\\)');
var reExtendedWord = /\S/
var hintList = []
var scope, elementID, element
@ -205,6 +208,25 @@ function hintMISPElements(cm, options) {
}
}
var resScope = reMISPScope.exec(word)
if (resScope !== null) {
var partialScope = resScope.groups.scope
availableScopes.forEach(function(scope) {
if (scope.startsWith(partialScope) && scope !== partialScope) {
hintList.push({
text: '@[' + scope + ']()'
})
}
});
if (hintList.length > 0) {
return {
list: hintList,
from: CodeMirror.Pos(cursor.line, start),
to: CodeMirror.Pos(cursor.line, end)
}
}
}
var res = reMISPElement.exec(word)
if (res !== null) {
scope = res.groups.scope
@ -288,13 +310,7 @@ function renderHintElement(scope, element) {
.text(element.value)
)
} else if (scope == 'object') {
var associatedTemplate = element.template_uuid + '.' + element.template_version
var objectTemplate = proxyMISPElements['objectTemplates'][associatedTemplate]
var topPriorityValue = element.Attribute.length
if (objectTemplate !== undefined) {
var temp = getPriorityValue(element, objectTemplate)
topPriorityValue = temp !== false ? temp : topPriorityValue
}
var topPriorityValue = getTopPriorityValue(element)
$node = $('<span/>').addClass('hint-object')
$node.append($('<i/>').addClass('').text('[' + element['meta-category'] + '] '))
.append($('<span/>').addClass('bold').text(element.name + ' '))
@ -1416,6 +1432,17 @@ function getPriorityValue(mispObject, objectTemplate) {
return false
}
function getTopPriorityValue(object) {
var associatedTemplate = object.template_uuid + '.' + object.template_version
var objectTemplate = proxyMISPElements['objectTemplates'][associatedTemplate]
var topPriorityValue = object.Attribute.length
if (objectTemplate !== undefined) {
var temp = getPriorityValue(object, objectTemplate)
topPriorityValue = temp !== false ? temp : topPriorityValue
}
return topPriorityValue
}
function constructTag(tagName) {
var tagData = proxyMISPElements['tag'][tagName]
var $info = 'No information about this tag'

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"
}