From c6519b2939fdc038766c1f55708079e265d20db6 Mon Sep 17 00:00:00 2001 From: Jakub Onderka Date: Thu, 2 Sep 2021 10:19:06 +0200 Subject: [PATCH] new: [UI] Allow to filter attributes by specific warninglist --- app/Controller/EventsController.php | 170 +++++++----------- app/Model/Event.php | 67 ++++++- .../View/eventFilteringQueryBuilder.ctp | 68 +++---- app/View/Elements/eventattributetoolbar.ctp | 1 - app/webroot/js/misp.js | 13 +- 5 files changed, 173 insertions(+), 146 deletions(-) diff --git a/app/Controller/EventsController.php b/app/Controller/EventsController.php index ba45e7482..1d46ded13 100644 --- a/app/Controller/EventsController.php +++ b/app/Controller/EventsController.php @@ -32,10 +32,10 @@ class EventsController extends AppController 'sort', 'direction', 'focus', 'extended', 'overrideLimit', 'filterColumnsOverwrite', 'attributeFilter', 'extended', 'page', 'searchFor', 'proposal', 'correlation', 'warning', 'deleted', 'includeRelatedTags', 'includeDecayScore', 'distribution', 'taggedAttributes', 'galaxyAttachedAttributes', 'objectType', 'attributeType', 'focus', 'extended', 'overrideLimit', - 'filterColumnsOverwrite', 'feed', 'server', 'toIDS', 'sighting', 'includeSightingdb' + 'filterColumnsOverwrite', 'feed', 'server', 'toIDS', 'sighting', 'includeSightingdb', 'warninglistId' ); - public $defaultFilteringRules = array( + public $defaultFilteringRules = array( 'searchFor' => '', 'attributeFilter' => 'all', 'proposal' => 0, @@ -50,7 +50,8 @@ class EventsController extends AppController 'distribution' => array(0, 1, 2, 3, 4, 5), 'sighting' => 0, 'taggedAttributes' => '', - 'galaxyAttachedAttributes' => '' + 'galaxyAttachedAttributes' => '', + 'warninglistId' => '', ); public $paginationFunctions = array('index', 'proposalEventIndex'); @@ -1237,58 +1238,19 @@ class EventsController extends AppController } $this->params->params['paging'] = array($this->modelClass => $params); $this->set('event', $event); - $dataForView = array( - 'Attribute' => array('attrDescriptions' => 'fieldDescriptions', 'distributionDescriptions' => 'distributionDescriptions', 'distributionLevels' => 'distributionLevels', 'shortDist' => 'shortDist'), - 'Event' => array('eventDescriptions' => 'fieldDescriptions', 'analysisDescriptions' => 'analysisDescriptions', 'analysisLevels' => 'analysisLevels') - ); - foreach ($dataForView as $m => $variables) { - if ($m === 'Event') { - $currentModel = $this->Event; - } elseif ($m === 'Attribute') { - $currentModel = $this->Event->Attribute; - } - foreach ($variables as $alias => $variable) { - $this->set($alias, $currentModel->{$variable}); - } - } - if (Configure::read('Plugin.Enrichment_services_enable')) { - $this->loadModel('Module'); - $modules = $this->Module->getEnabledModules($this->Auth->user()); - if (!empty($modules) && is_array($modules)) { - foreach ($modules as $k => $v) { - if (isset($v['restrict'])) { - if (!$this->_isSiteAdmin() && $v['restrict'] != $this->Auth->user('org_id')) { - unset($modules[$k]); - } - } - } - } - $this->set('modules', $modules); - } - if (Configure::read('Plugin.Cortex_services_enable')) { - $this->loadModel('Module'); - $cortex_modules = $this->Module->getEnabledModules($this->Auth->user(), false, 'Cortex'); - $this->set('cortex_modules', $cortex_modules); - } + $deleted = 0; if (isset($filters['deleted'])) { $deleted = $filters['deleted'] != 2 ? 1 : 0; } $this->set('includeSightingdb', (!empty($filters['includeSightingdb']) && Configure::read('Plugin.Sightings_sighting_db_enable'))); $this->set('deleted', $deleted); - $this->set('typeGroups', array_keys($this->Event->Attribute->typeGroupings)); $this->set('attributeFilter', isset($filters['attributeFilter']) ? $filters['attributeFilter'] : 'all'); $this->set('filters', $filters); $advancedFiltering = $this->__checkIfAdvancedFiltering($filters); $this->set('advancedFilteringActive', $advancedFiltering['active'] ? 1 : 0); $this->set('advancedFilteringActiveRules', $advancedFiltering['activeRules']); - $this->set('defaultFilteringRules', $this->defaultFilteringRules); - $orgTable = $this->Event->Orgc->find('list', array( - 'fields' => array('Orgc.id', 'Orgc.name') - )); - $this->set('orgTable', $orgTable); - $this->disableCache(); - $this->layout = 'ajax'; + $this->response->disableCache(); $uriArray = explode('/', $this->params->here); foreach ($uriArray as $k => $v) { if (strpos($v, ':')) { @@ -1304,6 +1266,7 @@ class EventsController extends AppController } $this->set('currentUri', $this->params->here); $this->layout = false; + $this->__eventViewCommon($this->Auth->user()); $this->render('/Elements/eventattribute'); } @@ -1330,20 +1293,6 @@ class EventsController extends AppController $attributeCount = isset($event['Attribute']) ? count($event['Attribute']) : 0; $objectCount = isset($event['Object']) ? count($event['Object']) : 0; $oldest_timestamp = false; - if (!empty($event['Object'])) { - foreach ($event['Object'] as $k => $object) { - if (!empty($object['Attribute'])) { - foreach ($object['Attribute'] as $attribute) { - if ($oldest_timestamp == false || $oldest_timestamp > $attribute['timestamp']) { - $oldest_timestamp = $attribute['timestamp']; - } - } - $attributeCount += count($object['Attribute']); - } - } - } - $this->set('attribute_count', $attributeCount); - $this->set('object_count', $objectCount); // set the data for the contributors / history field $contributors = $this->Event->ShadowAttribute->getEventContributors($event['Event']['id']); $this->set('contributors', $contributors); @@ -1354,8 +1303,9 @@ class EventsController extends AppController } if (!$proposalStatus && !empty($event['Attribute'])) { foreach ($event['Attribute'] as $temp) { - if (isset($temp['ShadowAttribute']) && !empty($temp['ShadowAttribute'])) { + if (!empty($temp['ShadowAttribute'])) { $proposalStatus = true; + break; } } } @@ -1376,11 +1326,6 @@ class EventsController extends AppController $this->__setDeletable($pivot, $event['Event']['id'], true); $this->set('allPivots', $this->Session->read('pivot_thread')); $this->set('pivot', $pivot); - // set data for the view, the event is already set in view() - $dataForView = array( - 'Attribute' => array('attrDescriptions' => 'fieldDescriptions', 'distributionDescriptions' => 'distributionDescriptions', 'distributionLevels' => 'distributionLevels', 'shortDist' => 'shortDist'), - 'Event' => array('eventDescriptions' => 'fieldDescriptions', 'analysisDescriptions' => 'analysisDescriptions', 'analysisLevels' => 'analysisLevels') - ); // workaround to get number of correlation per related event $relatedEventCorrelationCount = array(); @@ -1395,17 +1340,6 @@ class EventsController extends AppController $relatedEventCorrelationCount[$key] = count($relatedEventCorrelationCount[$key]); } - foreach ($dataForView as $m => $variables) { - if ($m === 'Event') { - $currentModel = $this->Event; - } elseif ($m === 'Attribute') { - $currentModel = $this->Event->Attribute; - } - foreach ($variables as $alias => $variable) { - $this->set($alias, $currentModel->{$variable}); - } - } - $this->Event->removeGalaxyClusterTags($event); $tagConflicts = $this->Taxonomy->checkIfTagInconsistencies($event['EventTag']); @@ -1421,7 +1355,7 @@ class EventsController extends AppController $modificationMap = array($modDate => 1); foreach ($event['Attribute'] as $k => $attribute) { - if ($oldest_timestamp == false || $oldest_timestamp > $attribute['timestamp']) { + if ($oldest_timestamp === false || $oldest_timestamp > $attribute['timestamp']) { $oldest_timestamp = $attribute['timestamp']; } $modDate = date("Y-m-d", $attribute['timestamp']); @@ -1446,7 +1380,12 @@ class EventsController extends AppController $modDate = date("Y-m-d", $object['timestamp']); $modificationMap[$modDate] = !isset($modificationMap[$modDate])? 1 : $modificationMap[$modDate] + 1; if (!empty($object['Attribute'])) { + $attributeCount += count($object['Attribute']); foreach ($object['Attribute'] as $k2 => $attribute) { + if ($oldest_timestamp === false || $oldest_timestamp > $attribute['timestamp']) { + $oldest_timestamp = $attribute['timestamp']; + } + $modDate = date("Y-m-d", $attribute['timestamp']); $modificationMap[$modDate] = !isset($modificationMap[$modDate])? 1 : $modificationMap[$modDate] + 1; @@ -1478,7 +1417,7 @@ class EventsController extends AppController sort($startDate); $startDate = $startDate[0]; $this->set('startDate', $startDate); - $today = strtotime(date('Y-m-d', time())); + $today = strtotime(date('Y-m-d')); if (($today - 172800) > $startDate) { $startDate = date('Y-m-d', $today - 172800); } @@ -1527,26 +1466,7 @@ class EventsController extends AppController 'contain' => array('Org', 'RequesterOrg') ))); } - if (Configure::read('Plugin.Enrichment_services_enable')) { - $this->loadModel('Module'); - $modules = $this->Module->getEnabledModules($user); - if (is_array($modules)) { - foreach ($modules as $k => $v) { - if (isset($v['restrict'])) { - if ($this->_isSiteAdmin() && $v['restrict'] != $user['org_id']) { - unset($modules[$k]); - } - } - } - } - $this->set('modules', $modules); - } - if (Configure::read('Plugin.Cortex_services_enable')) { - $this->loadModel('Module'); - $cortex_modules = $this->Module->getEnabledModules($user, false, 'Cortex'); - $this->set('cortex_modules', $cortex_modules); - } - $this->set('typeGroups', array_keys($this->Event->Attribute->typeGroupings)); + $attributeUri = $this->baseurl . '/events/viewEventAttributes/' . $event['Event']['id']; foreach ($this->params->named as $k => $v) { if (!is_numeric($k)) { @@ -1559,25 +1479,69 @@ class EventsController extends AppController } } } - $orgTable = $this->Event->Orgc->find('list', array( - 'fields' => array('Orgc.id', 'Orgc.name') - )); + if (!empty($filters['includeSightingdb']) && Configure::read('Plugin.Sightings_sighting_db_enable')) { $this->set('sightingdbs', $this->Sightingdb->getSightingdbList($user)); } - $this->set('includeSightingdb', (!empty($filters['includeSightingdb']) && Configure::read('Plugin.Sightings_sighting_db_enable'))); + $this->set('includeSightingdb', !empty($filters['includeSightingdb'] && Configure::read('Plugin.Sightings_sighting_db_enable'))); $this->set('relatedEventCorrelationCount', $relatedEventCorrelationCount); $this->set('oldest_timestamp', $oldest_timestamp); $this->set('missingTaxonomies', $this->Event->missingTaxonomies($event)); - $this->set('orgTable', $orgTable); $this->set('currentUri', $attributeUri); $this->set('filters', $filters); $advancedFiltering = $this->__checkIfAdvancedFiltering($filters); $this->set('advancedFilteringActive', $advancedFiltering['active'] ? 1 : 0); $this->set('advancedFilteringActiveRules', $advancedFiltering['activeRules']); - $this->set('defaultFilteringRules', $this->defaultFilteringRules); $this->set('modificationMapCSV', $modificationMapCSV); $this->set('title_for_layout', __('Event #%s', $event['Event']['id'])); + $this->set('attribute_count', $attributeCount); + $this->set('object_count', $objectCount); + $this->__eventViewCommon($user); + } + + private function __eventViewCommon(array $user) + { + $this->set('defaultFilteringRules', $this->defaultFilteringRules); + $this->set('typeGroups', array_keys($this->Event->Attribute->typeGroupings)); + + $orgTable = $this->Event->Orgc->find('list', array( + 'fields' => array('Orgc.id', 'Orgc.name') + )); + $this->set('orgTable', $orgTable); + + $this->loadModel('Warninglist'); + $warninglists = $this->Warninglist->find('list', [ + 'fields' => ['Warninglist.id', 'Warninglist.name'], + 'order' => ['Warninglist.name'], + 'conditions' => ['Warninglist.enabled' => true], + ]); + $this->set('warninglists', $warninglists); + + $dataForView = array( + 'Attribute' => array('attrDescriptions' => 'fieldDescriptions', 'distributionDescriptions' => 'distributionDescriptions', 'distributionLevels' => 'distributionLevels', 'shortDist' => 'shortDist'), + 'Event' => array('eventDescriptions' => 'fieldDescriptions', 'analysisDescriptions' => 'analysisDescriptions', 'analysisLevels' => 'analysisLevels') + ); + foreach ($dataForView as $m => $variables) { + if ($m === 'Event') { + $currentModel = $this->Event; + } elseif ($m === 'Attribute') { + $currentModel = $this->Event->Attribute; + } + foreach ($variables as $alias => $variable) { + $this->set($alias, $currentModel->{$variable}); + } + } + + if (Configure::read('Plugin.Enrichment_services_enable')) { + $this->loadModel('Module'); + $modules = $this->Module->getEnabledModules($user); + $this->set('modules', $modules); + } + if (Configure::read('Plugin.Cortex_services_enable')) { + $this->loadModel('Module'); + $cortex_modules = $this->Module->getEnabledModules($user, false, 'Cortex'); + $this->set('cortex_modules', $cortex_modules); + } } public function view($id = null, $continue = false, $fromEvent = null) diff --git a/app/Model/Event.php b/app/Model/Event.php index 04965a8a6..61fa54fa4 100755 --- a/app/Model/Event.php +++ b/app/Model/Event.php @@ -4989,6 +4989,14 @@ class Event extends AppModel return (preg_match('/^[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])$/', $date)) ? $date : false; } + /** + * @param array $attribute + * @param array $correlatedAttributes + * @param array $correlatedShadowAttributes + * @param array $filterType + * @param array $sightingsData + * @return array|null + */ private function __prepareAttributeForView( $attribute, $correlatedAttributes, @@ -4997,8 +5005,10 @@ class Event extends AppModel $sightingsData ) { $attribute['objectType'] = 'attribute'; - $include = true; + if ($filterType) { + $include = true; + /* proposal */ if ($filterType['proposal'] == 0) { // `both` // pass, do not consider as `both` is selected @@ -5060,15 +5070,27 @@ class Event extends AppModel } else { // `exclude` $include = $include && ($filterType['warning'] == 2); } - } - if (!$include) { - return null; + if ($filterType['warninglistId']) { + $include = false; + if (isset($attribute['warnings'])) { + foreach ($attribute['warnings'] as $warning) { + if (in_array($warning['warninglist_id'], $filterType['warninglistId'])) { + $include = true; + break; + } + } + } + } + + if (!$include) { + return null; + } } if (!empty($attribute['ShadowAttribute'])) { $temp = array(); - foreach ($attribute['ShadowAttribute'] as $k => $proposal) { + foreach ($attribute['ShadowAttribute'] as $proposal) { $result = $this->__prepareProposalForView($proposal, $correlatedShadowAttributes, $filterType); if ($result) { $temp[] = $result; @@ -5148,7 +5170,7 @@ class Event extends AppModel $object, $correlatedAttributes, $correlatedShadowAttributes, - $filterType = false, + $filterType, $sightingsData ) { $object['category'] = $object['meta-category']; @@ -5177,13 +5199,14 @@ class Event extends AppModel } // filters depend on child objects - if (in_array($filterType['attributeFilter'], array('correlation', 'proposal', 'warning')) + if (in_array($filterType['attributeFilter'], array('correlation', 'proposal', 'warning'), true) || $filterType['correlation'] != 0 || $filterType['proposal'] != 0 || $filterType['warning'] != 0 || $filterType['sighting'] != 0 || $filterType['feed'] != 0 || $filterType['server'] != 0 + || $filterType['warninglistId'] !== null ) { $include = $this->__checkObjectByFilter($object, $filterType, $correlatedAttributes, $correlatedShadowAttributes, $sightingsData); if (!$include) { @@ -5221,7 +5244,7 @@ class Event extends AppModel // pass, do not consider as `both` is selected } else if ($filterType['warning'] == 1 || $filterType['warning'] == 2) { $flagKeep = false; - foreach ($object['Attribute'] as $k => $attribute) { // check if object contains at least 1 warning + foreach ($object['Attribute'] as $attribute) { // check if object contains at least 1 warning if (!empty($attribute['warnings'])) { $flagKeep = ($filterType['warning'] == 1); // keep if warnings are included } else { @@ -5244,6 +5267,24 @@ class Event extends AppModel } } + if ($filterType['warninglistId']) { + // check if object contains at least one attribute that is part of given warninglist + $flagKeep = false; + if (isset($attribute['warnings'])) { + foreach ($object['Attribute'] as $attribute) { + foreach ($attribute['warnings'] as $warning) { + if (in_array($warning['warninglist_id'], $filterType['warninglistId'])) { + $flagKeep = true; + break; + } + } + } + } + if (!$flagKeep) { + return false; + } + } + /* correlation */ if ($filterType['correlation'] == 0) { // `both` // pass, do not consider as `both` is selected @@ -5383,6 +5424,13 @@ class Event extends AppModel return $object; } + /** + * @param array $event + * @param array $passedArgs + * @param false $all + * @param array $sightingsData + * @return array + */ public function rearrangeEventForView(&$event, $passedArgs = array(), $all = false, $sightingsData=array()) { foreach ($event['Event'] as $k => $v) { @@ -5400,7 +5448,8 @@ class Event extends AppModel 'toIDS' => isset($passedArgs['toIDS']) ? $passedArgs['toIDS'] : 0, 'sighting' => isset($passedArgs['sighting']) ? $passedArgs['sighting'] : 0, 'feed' => isset($passedArgs['feed']) ? $passedArgs['feed'] : 0, - 'server' => isset($passedArgs['server']) ? $passedArgs['server'] : 0 + 'server' => isset($passedArgs['server']) ? $passedArgs['server'] : 0, + 'warninglistId' => isset($passedArgs['warninglistId']) ? (is_array($passedArgs['warninglistId']) ? $passedArgs['warninglistId'] : [$passedArgs['warninglistId']]) : null, ); // update proposal, correlation and warning accordingly if (in_array($filterType['attributeFilter'], array('proposal', 'correlation', 'warning'))) { diff --git a/app/View/Elements/Events/View/eventFilteringQueryBuilder.ctp b/app/View/Elements/Events/View/eventFilteringQueryBuilder.ctp index 859bdd9e5..f935142e9 100644 --- a/app/View/Elements/Events/View/eventFilteringQueryBuilder.ctp +++ b/app/View/Elements/Events/View/eventFilteringQueryBuilder.ctp @@ -91,6 +91,17 @@ function triggerEventFilteringTool(hide) { 2: "Exclude warning" } }, + { + "input": "select", + "type": "string", + "operators": [ + "equal", + ], + "unique": true, + "id": "warninglistId", + "label": "Warninglist", + "values": + }, { "input": "radio", "type": "integer", @@ -227,7 +238,7 @@ function triggerEventFilteringTool(hide) { "unique": true, "id": "taggedAttributes", "label": "Tags", - "values": + "values": }, + "values": }, { @@ -271,14 +282,14 @@ function triggerEventFilteringTool(hide) { condition: 'AND', not: false, rules: [ - + { field: 'searchFor', id: 'searchFor', value: $('
').html("").text() }, - + { field: 'attributeFilter', id: 'attributeFilter', @@ -289,77 +300,84 @@ function triggerEventFilteringTool(hide) { }, - + { field: 'proposal', id: 'proposal', value: }, - + { field: 'correlation', id: 'correlation', value: }, - + { field: 'warning', id: 'warning', value: }, - + + { + field: 'warninglistId', + id: 'warninglistId', + value: + }, + + { field: 'deleted', id: 'deleted', value: }, - + { field: 'includeRelatedTags', id: 'includeRelatedTags', value: }, - + { field: 'includeDecayScore', id: 'includeDecayScore', value: }, - + { field: 'toIDS', id: 'toIDS', value: }, - + { field: 'feed', id: 'feed', value: }, - + { field: 'server', id: 'server', value: }, - + { field: 'sighting', id: 'sighting', value: }, - + { field: 'distribution', id: 'distribution', @@ -368,7 +386,7 @@ function triggerEventFilteringTool(hide) { }, 'taggedAttributes', 'id' => 'taggedAttributes', @@ -376,7 +394,7 @@ function triggerEventFilteringTool(hide) { ); echo json_encode($tmp) . ','; // sanitize data endif; - if (!empty($filters['galaxyAttachedAttributes']) && (count($advancedFilteringActiveRules) == 0 || isset($advancedFilteringActiveRules['galaxyAttachedAttributes']))): + if (!empty($filters['galaxyAttachedAttributes']) && (empty($advancedFilteringActiveRules) || isset($advancedFilteringActiveRules['galaxyAttachedAttributes']))): $tmp = array( 'field' => 'galaxyAttachedAttributes', 'id' => 'galaxyAttachedAttributes', @@ -410,12 +428,11 @@ function triggerEventFilteringTool(hide) { updateURL(); }); if (hide === undefined || !hide) { - $('#eventFilteringQB').height(qbOptions.rules.rules.length < 7 ? 'unset' : $('#eventFilteringQB').height()); + $ev.height(qbOptions.rules.rules.length < 7 ? 'unset' : $ev.height()); $wrapper.toggle('blind', 100, { direction: 'up' }); } $('#eventFilteringQBSubmit').off('click').on('click', function() { - $button = $(this); var rules = querybuilderTool.getRules({ skip_empty: true, allow_invalid: true }); performQuery(rules); }); @@ -446,7 +463,6 @@ function triggerEventFilteringTool(hide) { } } - function buildFilterURL(res) { var url = ""; Object.keys(res).forEach(function(k) { @@ -505,10 +521,10 @@ function performQuery(rules) { var url = "/events/viewEventAttributes/"; $.ajax({ - type:"post", + type: "post", url: url, data: res, - beforeSend: function (XMLHttpRequest) { + beforeSend: function () { $(".loading").show(); }, success:function (data) { @@ -521,14 +537,6 @@ function performQuery(rules) { }); } -function copyToClipboard(element) { - var $temp = $(""); - $("body").append($temp); - $temp.val($(element).val()).select(); - document.execCommand("copy"); - $temp.remove(); -} - function clickMessage(clicked) { $clicked = $(clicked); $clicked.tooltip({ diff --git a/app/View/Elements/eventattributetoolbar.ctp b/app/View/Elements/eventattributetoolbar.ctp index 070e8dc86..a905d668a 100644 --- a/app/View/Elements/eventattributetoolbar.ctp +++ b/app/View/Elements/eventattributetoolbar.ctp @@ -224,7 +224,6 @@ ), 'active' => $advancedFilteringActive, 'onClick' => 'triggerEventFilteringTool', - 'onClickParams' > array('this') ) ) ), diff --git a/app/webroot/js/misp.js b/app/webroot/js/misp.js index e6eb5f638..77da74c9c 100644 --- a/app/webroot/js/misp.js +++ b/app/webroot/js/misp.js @@ -12,6 +12,14 @@ if (!String.prototype.startsWith) { }; } +function copyToClipboard(element) { + var $temp = $(""); + $("body").append($temp); + $temp.val($(element).val()).select(); + document.execCommand("copy"); + $temp.remove(); +} + function stringToRGB(str){ var hash = 0; if (str.length == 0) return hash; @@ -3695,12 +3703,11 @@ function toggleBoolFilter(url, param) { xhr({ type: "get", url: url, - success:function (data) { + success: function (data) { $("#attributes_div").html(data); querybuilderTool = undefined; - }, - error:function() { + error: function() { showMessage('fail', 'Something went wrong - could not fetch attributes.'); } });