new: [eventReport] Report from event

pull/6590/head
mokaddem 2020-11-12 09:00:05 +01:00
parent c216642767
commit e3d42ffe2a
No known key found for this signature in database
GPG Key ID: 164C473F627A06FA
5 changed files with 320 additions and 0 deletions

View File

@ -330,6 +330,45 @@ class EventReportsController extends AppController
$this->render('ajax/importReportFromUrl');
}
public function reportFromEvent($eventId)
{
$event = $this->__canModifyReport($eventId);
if ($this->request->is('post') || $this->request->is('put')) {
$filters = $this->EventReport->jsonDecode($this->data['EventReport']['filters']);
$options['conditions'] = $filters;
$options['event_id'] = $eventId;
App::uses('ReportFromEvent', 'EventReport');
$optionFields = array_keys((new ReportFromEvent())->acceptedOptions);
foreach ($optionFields as $field) {
if (isset($this->data[$field])) {
$options[$field] = $this->data[$field];
}
}
$markdown = $this->EventReport->getReportFromEvent($this->Auth->user(), $options);
if (!empty($markdown)) {
$report = [
'name' => __('Event report (%s)', time()),
'distribution' => 5,
'content' => $markdown
];
$errors = $this->EventReport->addReport($this->Auth->user(), $report, $eventId);
} else {
$errors[] = __('Could not generate markdown from the event');
}
$redirectTarget = array('controller' => 'events', 'action' => 'view', $eventId);
if (!empty($errors)) {
return $this->__getFailResponseBasedOnContext($errors, array(), 'add', $this->EventReport->id, $redirectTarget);
} else {
$successMessage = __('Report saved.');
$report = $this->EventReport->simpleFetchById($this->Auth->user(), $this->EventReport->id);
return $this->__getSuccessResponseBasedOnContext($successMessage, $report, 'add', false, $redirectTarget);
}
}
$this->set('event_id', $eventId);
$this->layout = 'ajax';
$this->render('ajax/reportFromEvent');
}
private function __generateIndexConditions($filters = [])
{
$aclConditions = $this->EventReport->buildACLConditions($this->Auth->user());

View File

@ -0,0 +1,162 @@
<?php
class ReportFromEvent
{
public $acceptedOptions = [
'raw' => false, // if set to true, MISP elements will be put verbatim into the report instead of their reference
'include_event_metadata' => true,
'include_correlations' => true,
'include_attack_matrix' => true,
];
public function construct($eventModel, $user, $options)
{
$this->__eventModel = $eventModel;
$this->__user = $user;
$this->__options = array_merge($this->acceptedOptions, $options);
return true;
}
private function getEvent()
{
$options = [
'eventid' => $this->__options['event_id'],
];
$this->event = $this->__eventModel->fetchEvent($this->__user, $options);
if (empty($this->event)) {
throw new NotFoundException(__('Invalid event'));
}
$this->event = $this->event[0];
$this->__eventModel->removeGalaxyClusterTags($this->event);
}
public function generate()
{
$this->getEvent();
$report = '';
if ($this->__options['include_event_metadata']) {
$report .= $this->getMarkdownForEventMetadata();
}
if ($this->__options['include_correlations']) {
$report .= $this->mdHeader('4', __('Correlations'));
$report .= $this->getMarkdownForEventCorrelations();
}
$report .= $this->mdHeader('3', __('Objects'));
$report .= $this->getMarkdownForObjects();
$report .= $this->mdHeader('3', __('Attributes'));
$report .= $this->getMarkdownForAttributes();
if ($this->__options['include_attack_matrix']) {
$report .= $this->mdHeader('3', __('ATT&CK Matrix'));
$report .= $this->getMarkdownForAttackMatrix();
}
return $report;
}
private function getMarkdownForEventMetadata()
{
$markdown = $this->mdHeader('2', $this->event['Event']['info']);
$markdown .= $this->mdList([
__('Date') => $this->event['Event']['date'],
__('Last update') => date('Y-m-d H:i:s', $this->event['Event']['timestamp']),
__('Threat level') => $this->event['ThreatLevel']['name'],
__('Attribute count') => $this->event['Event']['attribute_count'],
], 'key');
$markdown .= $this->mdHeader('4', __('Tags'));
$markdown .= $this->getMarkdownForTags(Hash::extract($this->event['EventTag'], '{n}.Tag.name'));
$markdown .= $this->mdHeader('4', __('Galaxies'));
$markdown .= $this->getMarkdownForGalaxy($this->event['Galaxy']);
return $markdown;
}
private function getMarkdownForTags($tags, $level=1)
{
if ($this->__options['raw']) {
$markdown = $this->mdList($tags, false, $level);
} else {
$markdown = $this->mdList(array_map(function ($tag) {
return sprintf('@[tag](%s)', $tag);
}, $tags), false, $level);
}
return $markdown;
}
private function getMarkdownForGalaxy($galaxies)
{
$markdown = '';
foreach ($galaxies as $galaxy) {
$markdown .= $this->mdList([
__('Name') => $galaxy['name'],
__('Description') => $galaxy['description'],
], 'key');
if ($this->__options['raw']) {
foreach ($galaxy['GalaxyCluster'] as $cluster) {
$markdown .= $this->mdList([
__('Name') => $cluster['value'],
__('Description') => $cluster['description'],
], 'key', 2);
}
} else {
$markdown .= $this->getMarkdownForTags(Hash::extract($galaxy['GalaxyCluster'], '{n}.tag_name'), 2);
}
}
return $markdown;
}
private function getMarkdownForObjects()
{
$markdown = $this->mdList(array_map(function ($uuid) {
return sprintf('@[object](%s)', $uuid);
}, Hash::extract($this->event['Object'], '{n}.uuid')), false);
return $markdown;
}
private function getMarkdownForAttributes()
{
$markdown = $this->mdList(array_map(function ($uuid) {
return sprintf('@[attribute](%s)', $uuid);
}, Hash::extract($this->event['Attribute'], '{n}.uuid')), false);
return $markdown;
}
private function getMarkdownForEventCorrelations()
{
$correlations = !empty($this->event['RelatedEvent']) ? $this->event['RelatedEvent'] : [];
$markdown = $this->mdList(Hash::extract($correlations, '{n}.Event.info'), false, 2);
return $markdown;
}
private function getMarkdownForAttackMatrix()
{
return '@[galaxymatrix](c4e851fa-775f-11e7-8163-b774922098cd)';
}
private function mdHeader($level, $content)
{
return str_repeat('#', $level) . ' ' . $content . PHP_EOL;
}
private function mdTable($headers, $rows)
{
$table = '| ' . implode(' | ', $headers) . ' |' . PHP_EOL;
$table .= '----------';
foreach ($rows as $row) {
$table = '| ' . implode(' | ', $row) . ' |' . PHP_EOL;
}
return $table;
}
private function mdList($items, $prefix=false, $level=1)
{
$list = '';
foreach ($items as $k => $item) {
if ($prefix == 'index') {
$list .= sprintf('%s%s. %s' . PHP_EOL, str_repeat(' ', $level), $k, $item);
} elseif ($prefix == 'key') {
$list .= sprintf('%s- *%s*: %s' . PHP_EOL, str_repeat(' ', $level), $k, $item);
} else {
$list .= sprintf('%s- %s' . PHP_EOL, str_repeat(' ', $level), $item);
}
}
return $list;
}
}

View File

@ -896,4 +896,13 @@ class EventReport extends AppModel
$this->EventTag->attachTagToEvent($eventId, $tagId);
}
}
public function getReportFromEvent($user, $options)
{
App::uses('ReportFromEvent', 'EventReport');
$reportGenerator = new ReportFromEvent();
$reportGenerator->construct($this->Event, $user, $options);
$report = $reportGenerator->generate();
return $report;
}
}

View File

@ -31,6 +31,15 @@
'fa-icon' => 'link',
'requirement' => $canModify && $importModuleEnabled,
),
array(
'onClick' => 'openGenericModal',
'onClickParams' => [$baseurl . '/eventReports/reportFromEvent/' . h($event_id)],
'active' => true,
'text' => __('Generate report from Event'),
'title' => __('Based on filters, create a report summarizing the event'),
'fa-icon' => 'list-alt',
'requirement' => $canModify,
),
)
),
array(

View File

@ -0,0 +1,101 @@
<?php
echo $this->element('genericElements/Form/genericForm', array(
'form' => $this->Form,
'data' => array(
'title' => __('Create report from event', h($event_id)),
'description' => __('Generate a report based on filtering criterias.'),
'model' => 'EventReport',
'fields' => array(
array(
'type' => 'textarea',
'field' => 'filters',
'class' => 'input span6',
'div' => 'text',
'label' => __('REST search filters'),
'title' => __('Provide the filtering criterias for attributes to be taken into account in the report')
),
array(
'type' => 'checkbox',
'field' => 'include_event_metadata',
),
array(
'type' => 'checkbox',
'field' => 'include_correlations',
),
array(
'type' => 'checkbox',
'field' => 'include_attack_matrix',
),
),
'submit' => array(
'action' => $this->request->params['action'],
'ajaxSubmit' => 'submitReportFromEvent()'
)
)
));
echo $this->element('genericElements/assetLoader', array(
'js' => array(
'codemirror/codemirror',
'codemirror/modes/javascript',
'codemirror/addons/closebrackets',
'codemirror/addons/lint',
'codemirror/addons/jsonlint',
'codemirror/addons/json-lint',
),
'css' => array(
'codemirror',
'codemirror/show-hint',
'codemirror/lint',
)
));
?>
<script>
var cm
function setupCodemirror() {
var cmOptions = {
mode: "application/json",
theme:'default',
gutters: ["CodeMirror-lint-markers"],
lint: true,
lineNumbers: true,
indentUnit: 4,
showCursorWhenSelecting: true,
lineWrapping: true,
autoCloseBrackets: true,
}
var defaultEditorContent = {
value: '',
type: '',
category: '',
tags: '',
}
cm = CodeMirror.fromTextArea(document.getElementById('EventReportFilters'), cmOptions);
cm.setValue(JSON.stringify(defaultEditorContent, null, 4))
}
setTimeout(setupCodemirror, 350);
function submitReportFromEvent() {
cm.save()
submitPopoverForm('<?= h($event_id) ?>', 'addEventReport', 0, 1)
}
</script>
<style>
.CodeMirror-wrap {
border: 1px solid #cccccc;
width: 500px;
height: 150px;
margin-bottom: 10px;
resize: auto;
}
.cm-trailingspace {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAACCAYAAAB/qH1jAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QUXCToH00Y1UgAAACFJREFUCNdjPMDBUc/AwNDAAAFMTAwMDA0OP34wQgX/AQBYgwYEx4f9lQAAAABJRU5ErkJggg==);
background-position: bottom left;
background-repeat: repeat-x;
}
.CodeMirror-gutters {
z-index: 2;
}
</style>