Merge branch '2.4' of github.com:MISP/MISP into stix

pull/3212/head
chrisr3d 2018-04-25 10:52:17 +02:00
commit 05fd501389
46 changed files with 1001 additions and 205 deletions

View File

@ -1200,7 +1200,7 @@ INSERT INTO `roles` (`id`, `name`, `created`, `modified`, `perm_add`, `perm_modi
VALUES (1, 'admin', NOW(), NOW(), 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0);
INSERT INTO `roles` (`id`, `name`, `created`, `modified`, `perm_add`, `perm_modify`, `perm_modify_org`, `perm_publish`, `perm_sync`, `perm_admin`, `perm_audit`, `perm_full`, `perm_auth`, `perm_regexp_access`, `perm_tagger`, `perm_site_admin`, `perm_template`, `perm_sharing_group`, `perm_tag_editor`, `perm_delegate`, `perm_sighting`, `perm_object_template`, `default_role`)
VALUES (2, 'Org Admin', NOW(), NOW(), 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0);
VALUES (2, 'Org Admin', NOW(), NOW(), 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0);
INSERT INTO `roles` (`id`, `name`, `created`, `modified`, `perm_add`, `perm_modify`, `perm_modify_org`, `perm_publish`, `perm_sync`, `perm_admin`, `perm_audit`, `perm_full`, `perm_auth`, `perm_regexp_access`, `perm_tagger`, `perm_site_admin`, `perm_template`, `perm_sharing_group`, `perm_tag_editor`, `perm_delegate`, `perm_sighting`, `perm_object_template`, `default_role`)
VALUES (3, 'User', NOW(), NOW(), 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1);

View File

@ -1 +1 @@
{"major":2, "minor":4, "hotfix":89}
{"major":2, "minor":4, "hotfix":90}

View File

@ -479,4 +479,42 @@ class EventShell extends AppShell
$log->createLogEntry($user, 'publish', 'Event', $id, 'Event (' . $id . '): published.', 'published () => (1)');
}
public function enrichment() {
file_put_contents('/var/www/MISP4/app/tmp/test', "0");
if (empty($this->args[0]) || empty($this->args[1]) || empty($this->args[2])) {
die('Usage: ' . $this->Server->command_line_functions['enrichment'] . PHP_EOL);
}
$userId = $this->args[0];
$user = $this->User->getAuthUser($userId);
if (empty($user)) die('Invalid user.');
$eventId = $this->args[1];
$modules = $this->args[2];
try {
$modules = json_decode($modules);
} catch (Exception $e) {
die('Invalid module JSON');
}
if (!empty($this->args[3])) {
$jobId = $this->args[3];
} else {
$this->Job->create();
$data = array(
'worker' => 'default',
'job_type' => 'enrichment',
'job_input' => 'Event: ' . $eventId . ' modules: ' . $modules,
'status' => 0,
'retries' => 0,
'org' => $user['Organisation']['name'],
'message' => 'Enriching event.',
);
$this->Job->save($data);
$jobId = $this->Job->id;
}
$options = array(
'user' => $user,
'event_id' => $eventId,
'modules' => $modules
);
$result = $this->Event->enrichment($options);
}
}

View File

@ -46,7 +46,7 @@ class AppController extends Controller {
public $helpers = array('Utility', 'OrgImg');
private $__queryVersion = '33';
private $__queryVersion = '35';
public $pyMispVersion = '2.4.89';
public $phpmin = '5.6.5';
public $phprec = '7.0.16';

View File

@ -1768,10 +1768,7 @@ class AttributesController extends AppController {
$this->Attribute->contain(array('AttributeTag' => array('Tag')));
if (!$this->_isSiteAdmin()) {
// merge in private conditions
$conditions = $this->Attribute->buildConditions($this->Auth->user());
$this->paginate = Set::merge($this->paginate, array(
'conditions' => $conditions
));
$this->paginate['conditions'] = array('AND' => array($conditions, $this->Attribute->buildConditions($this->Auth->user())));
}
$idList = array();
$attributeIdList = array();

View File

@ -99,6 +99,7 @@ class ACLComponent extends Component {
'downloadOpenIOCEvent' => array('*'),
'downloadSearchResult' => array('*'),
'edit' => array('perm_add'),
'enrichEvent' => array('perm_add'),
'export' => array('*'),
'exportChoice' => array('*'),
'filterEventIdsForPush' => array('perm_sync'),

View File

@ -51,6 +51,20 @@ class RestResponseComponent extends Component {
'params' => array('event_id')
)
),
'Feed' => array(
'add' => array(
'description' => "POST a MISP Feed descriptor JSON to this API to add a Feed.",
'mandatory' => array('source_format', 'url', 'name', 'input_source', 'provider'),
'optional' => array('enabled', 'caching_enabled', 'lookup_visible', 'delete_local_file', 'headers', 'fixed_event', 'target_event', 'settings', 'publish', 'override_ids', 'delta_merge', 'distribution', 'sharing_group_id', 'tag_id', 'pull_rules', 'rules', 'event_id'),
'params' => array()
),
'edit' => array(
'description' => "POST a MISP Feed descriptor JSON to this API to edit a Feed.",
'mandatory' => array(),
'optional' => array('source_format', 'url', 'name', 'enabled', 'caching_enabled', 'lookup_visible', 'provider', 'input_source', 'delete_local_file', 'headers', 'fixed_event', 'target_event', 'settings', 'publish', 'override_ids', 'delta_merge', 'distribution', 'sharing_group_id', 'tag_id', 'pull_rules', 'rules', 'event_id'),
'params' => array('feed_id')
),
),
'Organisation' => array(
'admin_add' => array(
'description' => "POST an Organisation object in JSON format to this API to create a new organsiation.",

View File

@ -628,15 +628,20 @@ class EventsController extends AppController {
)
)
));
foreach ($eventTags as $et) {
$et['EventTag']['Tag'] = $et['Tag'];
unset($et['Tag']);
if (empty($event_tag_objects[$et['EventTag']['event_id']])) {
$event_tag_objects[$et['EventTag']['event_id']] = array($et['EventTag']);
foreach ($eventTags as $ket => $et) {
if (empty($et['Tag']['id'])) {
unset($eventTags[$ket]);
} else {
$event_tag_objects[$et['EventTag']['event_id']][] = $et['EventTag'];
$et['EventTag']['Tag'] = $et['Tag'];
unset($et['Tag']);
if (empty($event_tag_objects[$et['EventTag']['event_id']])) {
$event_tag_objects[$et['EventTag']['event_id']] = array($et['EventTag']);
} else {
$event_tag_objects[$et['EventTag']['event_id']][] = $et['EventTag'];
}
}
}
$eventTags = array_values($eventTags);
for ($j = 0; $j < $elements; $j++) {
if (!empty($event_tag_objects[$events[($i*1000) + $j]['Event']['id']])) {
$events[($i*1000) + $j]['EventTag'] = $event_tag_objects[$events[($i*1000) + $j]['Event']['id']];
@ -4300,6 +4305,8 @@ class EventsController extends AppController {
public function viewGraph($id) {
$event = $this->Event->fetchEvent($this->Auth->user(), array('eventid' => $id));
if (empty($event)) throw new MethodNotAllowedException('Invalid Event.');
$this->set('ajax', $this->request->is('ajax'));
$this->set('event', $event[0]);
$this->set('scope', 'event');
$this->set('id', $id);
@ -4348,6 +4355,7 @@ class EventsController extends AppController {
public function getEventGraphReferences($id, $type = 'event') {
$validTools = array('event');
if (!in_array($type, $validTools)) throw new MethodNotAllowedException('Invalid type.');
$this->loadModel('Tag');
App::uses('EventGraphTool', 'Tools');
$grapher = new EventGraphTool();
$data = $this->request->is('post') ? $this->request->data : array();
@ -4358,7 +4366,7 @@ class EventsController extends AppController {
$extended = 0;
}
$grapher->construct($this->Event, $this->Auth->user(), $data['filtering'], $extended);
$grapher->construct($this->Event, $this->Tag, $this->Auth->user(), $data['filtering'], $extended);
$json = $grapher->get_references($id);
array_walk_recursive($json, function(&$item, $key){
@ -4373,6 +4381,7 @@ class EventsController extends AppController {
public function getEventGraphTags($id, $type = 'event') {
$validTools = array('event');
if (!in_array($type, $validTools)) throw new MethodNotAllowedException('Invalid type.');
$this->loadModel('Tag');
App::uses('EventGraphTool', 'Tools');
$grapher = new EventGraphTool();
$data = $this->request->is('post') ? $this->request->data : array();
@ -4383,7 +4392,7 @@ class EventsController extends AppController {
$extended = 0;
}
$grapher->construct($this->Event, $this->Auth->user(), $data['filtering'], $extended);
$grapher->construct($this->Event, $this->Tag, $this->Auth->user(), $data['filtering'], $extended);
$json = $grapher->get_tags($id);
array_walk_recursive($json, function(&$item, $key){
@ -4398,6 +4407,7 @@ class EventsController extends AppController {
public function getEventGraphGeneric($id, $type = 'event') {
$validTools = array('event');
if (!in_array($type, $validTools)) throw new MethodNotAllowedException('Invalid type.');
$this->loadModel('Tag');
App::uses('EventGraphTool', 'Tools');
$grapher = new EventGraphTool();
$data = $this->request->is('post') ? $this->request->data : array();
@ -4408,7 +4418,7 @@ class EventsController extends AppController {
$extended = 0;
}
$grapher->construct($this->Event, $this->Auth->user(), $data['filtering'], $extended);
$grapher->construct($this->Event, $this->Tag, $this->Auth->user(), $data['filtering'], $extended);
if (!array_key_exists('keyType', $data)) {
$keyType = ''; // empty key
} else {
@ -4832,13 +4842,17 @@ class EventsController extends AppController {
}
public function getEventInfoById($id) {
if (empty($id)) throw new MethodNotAllowedException('Invalid ID.');
$conditions = array('Event.id' => $id);
if (Validation::uuid($id)) {
$conditions = array('Event.uuid' => $id);
} else if (!is_numeric($id)) {
$conditions = array('Event.uuid' => -1);
}
$event = $this->Event->find('first', array(
'conditions' => $conditions,
'fields' => array('Event.id', 'Event.distribution', 'Event.sharing_group_id', 'Event.info', 'Event.org_id'),
'fields' => array('Event.id', 'Event.distribution', 'Event.sharing_group_id', 'Event.info', 'Event.org_id', 'Event.date', 'Event.threat_level_id', 'Event.analysis'),
'contain' => array('Orgc.id', 'Orgc.name', 'EventTag' => array('Tag.id', 'Tag.name', 'Tag.colour'), 'ThreatLevel.name'),
'recursive' => -1
));
if (!empty($event) && !$this->_isSiteAdmin() && $event['Event']['org_id'] != $this->Auth->user('org_id')) {
@ -4853,6 +4867,54 @@ class EventsController extends AppController {
}
}
}
return $this->RestResponse->viewData($event, $this->response->type());
if ($this->_isRest()) {
return $this->RestResponse->viewData($event, $this->response->type());
} else {
if ($this->request->is('ajax')) {
$this->layout = 'ajax';
}
$this->set('analysisLevels', $this->Event->analysisLevels);
$this->set('validUuid', Validation::uuid($id));
$this->set('id', $id);
$this->set('event', $event);
}
}
public function enrichEvent($id) {
if (Validation::uuid($id)) {
$conditions = array('Event.uuid' => $id);
} else {
$conditions = array('Event.id' => $id);
}
$event = $this->Event->find('first', array('conditions' => $conditions, 'recursive' => -1));
if (empty($event) || (!$this->_isSiteAdmin() && ($this->Auth->user('org_id') != $event['Event']['orgc_id'] || !$this->userRole['perm_modify']))) {
throw new MethodNotAllowedException('Invalid Event');
}
if ($this->request->is('post')) {
$modules = array();
foreach ($this->request->data['Event'] as $module => $enabled) {
if ($enabled) $modules[] = $module;
}
$result = $this->Event->enrichmentRouter(array(
'user' => $this->Auth->user(),
'event_id' => $event['Event']['id'],
'modules' => $modules
));
if ($this->_isRest()) {
} else {
if ($result === true) {
$result = __('Enrichment task queued for background processing. Check back later to see the results.');
}
$this->Session->setFlash($result);
$this->redirect('/events/view/' . $id);
}
} else {
$this->loadModel('Module');
$modules = $this->Module->getEnabledModules($this->Auth->user(), 'expansion');
$this->layout = 'ajax';
$this->set('modules', $modules);
$this->render('ajax/enrich_event');
}
}
}

View File

@ -117,13 +117,25 @@ class FeedsController extends AppController {
$tags[0] = 'None';
$this->set('tags', $tags);
if ($this->request->is('post')) {
if ($this->_isRest()) {
if (empty($this->request->data['Feed'])) {
$this->request->data['Feed'] = $this->request->data;
if (empty($this->request->data['Feed']['source_format'])) {
$this->request->data['Feed']['source_format'] = 'freetext';
}
if (empty($this->request->data['Feed']['fixed_event'])) {
$this->request->data['Feed']['source_format'] = 1;
}
}
}
$error = false;
if (isset($this->request->data['Feed']['pull_rules'])) $this->request->data['Feed']['rules'] = $this->request->data['Feed']['pull_rules'];
if (!isset($this->request->data['Feed']['distribution'])) $this->request->data['Feed']['distribution'] = 0;
if ($this->request->data['Feed']['distribution'] != 4) $this->request->data['Feed']['sharing_group_id'] = 0;
$this->request->data['Feed']['default'] = 0;
if ($this->request->data['Feed']['source_format'] == 'freetext') {
if ($this->request->data['Feed']['fixed_event'] == 1) {
if (is_numeric($this->request->data['Feed']['target_event'])) {
if (!empty($this->request->data['Feed']['target_event']) && is_numeric($this->request->data['Feed']['target_event'])) {
$this->request->data['Feed']['event_id'] = $this->request->data['Feed']['target_event'];
}
}
@ -147,6 +159,8 @@ class FeedsController extends AppController {
}
if (empty($this->request->data['Feed']['input_source'])) {
$this->request->data['Feed']['input_source'] = 'network';
} else if (!in_array($this->request->data['Feed']['input_source'], array('network', 'file'))) {
$this->request->data['Feed']['input_source'] = 'network';
}
if (!isset($this->request->data['Feed']['delete_local_file'])) {
$this->request->data['Feed']['delete_local_file'] = 0;
@ -156,11 +170,24 @@ class FeedsController extends AppController {
if (!$error) {
$result = $this->Feed->save($this->request->data);
if ($result) {
$this->Session->setFlash('Feed added.');
$message = __('Feed added.');
if ($this->_isRest()) {
$feed = $this->Feed->find('first', array('conditions' => array('Feed.id' => $this->Feed->id), 'recursive' => -1));
return $this->RestResponse->viewData($feed, $this->response->type());
}
$this->Session->setFlash($message);
$this->redirect(array('controller' => 'feeds', 'action' => 'index'));
}
else $this->Session->setFlash('Feed could not be added. Invalid field: ' . array_keys($this->Feed->validationErrors)[0]);
else {
$messsage = __('Feed could not be added. Invalid field: %s', array_keys($this->Feed->validationErrors)[0]);
if ($this->_isRest()) {
return $this->RestResponse->saveFailResponse('Feeds', 'add', false, $message, $this->response->type());
}
$this->Session->setFlash($message);
}
}
} else if ($this->_isRest()) {
return $this->RestResponse->describe('Feeds', 'add', false, $this->response->type());
}
}
@ -189,12 +216,17 @@ class FeedsController extends AppController {
$this->Feed->data['Feed']['settings'] = json_decode($this->Feed->data['Feed']['settings'], true);
}
if ($this->request->is('post') || $this->request->is('put')) {
if ($this->_isRest()) {
if (empty($this->request->data['Feed'])) {
$this->request->data['Feed'] = $this->request->data;
}
}
if (isset($this->request->data['Feed']['pull_rules'])) $this->request->data['Feed']['rules'] = $this->request->data['Feed']['pull_rules'];
if ($this->request->data['Feed']['distribution'] != 4) $this->request->data['Feed']['sharing_group_id'] = 0;
if (isset($this->request->data['Feed']['distribution']) && $this->request->data['Feed']['distribution'] != 4) $this->request->data['Feed']['sharing_group_id'] = 0;
$this->request->data['Feed']['id'] = $feedId;
if ($this->request->data['Feed']['source_format'] == 'freetext' || $this->request->data['Feed']['source_format'] == 'csv') {
if (!empty($this->request->data['Feed']['source_format']) && ($this->request->data['Feed']['source_format'] == 'freetext' || $this->request->data['Feed']['source_format'] == 'csv')) {
if ($this->request->data['Feed']['fixed_event'] == 1) {
if (is_numeric($this->request->data['Feed']['target_event'])) {
if (isset($this->request->data['Feed']['target_event']) && is_numeric($this->request->data['Feed']['target_event'])) {
$this->request->data['Feed']['event_id'] = $this->request->data['Feed']['target_event'];
} else {
$this->request->data['Feed']['event_id'] = 0;
@ -226,12 +258,24 @@ class FeedsController extends AppController {
if (file_exists($feedCache)) {
unlink($feedCache);
}
$this->Session->setFlash('Feed updated.');
$message = __('Feed added.');
if ($this->_isRest()) {
$feed = $this->Feed->find('first', array('conditions' => array('Feed.id' => $this->Feed->id), 'recursive' => -1));
return $this->RestResponse->viewData($feed, $this->response->type());
}
$this->Session->setFlash($message);
$this->redirect(array('controller' => 'feeds', 'action' => 'index'));
} else {
$this->Session->setFlash('Feed could not be updated. Invalid fields: ' . implode(', ', array_keys($this->Feed->validationErrors)));
$message = __('Feed could not be updated. Invalid fields: %s', implode(', ', array_keys($this->Feed->validationErrors)));
if ($this->_isRest()) {
return $this->RestResponse->saveFailResponse('Feeds', 'add', false, $message, $this->response->type());
}
$this->Session->setFlash($message);
}
} else {
if ($this->_isRest()) {
return $this->RestResponse->describe('Feeds', 'edit', false, $this->response->type());
}
if (!isset($this->request->data['Feed'])) {
$this->request->data = $this->Feed->data;
if ($this->Feed->data['Feed']['event_id']) {
@ -246,8 +290,18 @@ class FeedsController extends AppController {
if (!$this->request->is('post')) throw new MethodNotAllowedException('This action requires a post request.');
$this->Feed->id = $feedId;
if (!$this->Feed->exists()) throw new NotFoundException('Invalid feed.');
if ($this->Feed->delete($feedId)) $this->Session->setFlash('Feed deleted.');
else $this->Session->setFlash('Feed could not be deleted.');
if ($this->Feed->delete($feedId)) {
$message = 'Feed deleted.';
if ($this->_isRest()) {
return $this->RestResponse->saveSuccessResponse('Feeds', 'delete', $feedId, false, $message);
}
} else {
$message = 'Feed could not be deleted.';
if ($this->_isRest()) {
return $this->RestResponse->saveFailResponse('Feeds', 'delete', false, $message, $this->response->type());
}
}
$this->Session->setFlash($message);
$this->redirect(array('controller' => 'feeds', 'action' => 'index'));
}

View File

@ -208,7 +208,7 @@ class ObjectsController extends AppController {
$distributionData = $this->MispObject->Event->Attribute->fetchDistributionData($this->Auth->user());
$this->set('distributionData', $distributionData);
$this->set('event', $event);
$this->set('ajax', false);
$this->set('ajax', $this->request->is('ajax'));
$this->set('action', 'add');
$this->set('template', $template);
}

View File

@ -171,7 +171,7 @@ class TagsController extends AppController {
if ($this->Tag->save($this->request->data)) {
if ($this->_isRest()) {
$tag = $this->Tag->find('first', array(
'contidions' => array(
'conditions' => array(
'Tag.id' => $this->Tag->id
),
'recursive' => -1

View File

@ -10,11 +10,16 @@
private $__related_events = array();
private $__related_attributes = array();
public function construct($eventModel, $user, $filterRules, $extended_view=0) {
public function construct($eventModel, $tagModel, $user, $filterRules, $extended_view=0) {
$this->__eventModel = $eventModel;
$this->__Tag = $tagModel;
$this->__user = $user;
$this->__filterRules = $filterRules;
$this->__json = array();
$this->__json['existing_tags'] = $this->__Tag->find('list', array(
'fields' => array('Tag.id', 'Tag.name'),
'sort' => array('lower(Tag.name) asc'),
));
$this->__extended_view = $extended_view;
$this->__lookupTables = array(
'analysisLevels' => $this->__eventModel->analysisLevels,
@ -56,24 +61,28 @@
private function __get_filtered_event($id) {
$event = $this->__get_event($id);
if (empty($this->__filterRules)) return $event;
$filtered = array('Object' => array(), 'Attribute' => array());
// perform filtering
foreach($event['Object'] as $obj) {
if ($this->__satisfy_obj_filtering($obj)) {
array_push($filtered['Object'], $obj);
foreach($event['Object'] as $i => $obj) {
$check1 = $this->__satisfy_obj_filtering($obj);
$check2 = $this->__satisfy_obj_tag($obj);
if (!($check1 && $check2)) {
unset($event['Object'][$i]);
}
}
foreach($event['Attribute'] as $attr) {
if ($this->__satisfy_val_filtering($attr, false)) {
array_push($filtered['Attribute'], $attr);
foreach($event['Attribute'] as $i => $attr) {
$check1 = $this->__satisfy_val_filtering($attr, false);
$check2 = $this->__satisfy_attr_tag($attr);
if (!($check1 && $check2)) {
unset($event['Attribute'][$i]);
}
}
return $filtered;
return $event;
}
// NOT OPTIMIZED: But allow clearer code
// perform filtering on obj_rel presence and then perform filtering on obj_rel value
private function __satisfy_obj_filtering($obj) {
// presence rule - search in the object's attribute
$presenceMatch = true;
@ -132,6 +141,64 @@
}
}
// iterate over all filter rules for obj
private function __satisfy_obj_tag($obj) {
foreach ($this->__filterRules['tag_presence'] as $rule) {
$relation = $rule[0];
$tagName = $rule[1];
if ($relation === "Contains") {
$presenceMatch = $this->__contain_tag($obj['Attribute'], $tagName);
} else if ($relation === "Do not contain") {
$presenceMatch = !$this->__contain_tag($obj['Attribute'], $tagName);
}
if (!$presenceMatch) { // Does not match, can stop filtering
return false;
}
}
return true;
}
// iterate over all filter rules for attr
private function __satisfy_attr_tag($attr) {
foreach ($this->__filterRules['tag_presence'] as $rule) {
$relation = $rule[0];
$tagName = $rule[1];
if ($relation === "Contains") {
$presenceMatch = $this->__contain_tag(array($attr), $tagName);
} else if ($relation === "Do not contain") {
$presenceMatch = !$this->__contain_tag(array($attr), $tagName);
}
if (!$presenceMatch) { // Does not match, can stop filtering
return false;
}
}
return true;
}
// iterate over all attributes
private function __contain_tag($attrList, $tagName) {
foreach ($attrList as $attr) {
if (empty($attr['AttributeTag'])) {
continue;
}
$presenceMatch = $this->__tag_in_AttributeTag($attr['AttributeTag'], $tagName);
if ($presenceMatch) {
return true;
}
}
return false;
}
// iterate over all tags
private function __tag_in_AttributeTag($attrTag, $tagName) {
foreach($attrTag as $tag) {
if($tag['Tag']['name'] === $tagName) {
return true;
}
}
return false;
}
private function __contain_object_relation($attrList, $obj_rel) {
foreach ($attrList as $attr) {
if ($attr['object_relation'] === $obj_rel) {
@ -145,6 +212,7 @@
$event = $this->__get_filtered_event($id);
$this->__json['items'] = array();
$this->__json['relations'] = array();
$this->__json['existing_object_relation'] = array();
if (empty($event)) return $this->__json;
@ -167,6 +235,7 @@
'uuid' => $attr['uuid'],
'type' => $attr['type'],
'label' => $attr['value'],
'event_id' => $attr['event_id'],
'node_type' => 'attribute',
);
array_push($this->__json['items'], $toPush);
@ -182,6 +251,7 @@
'node_type' => 'object',
'meta-category' => $obj['meta-category'],
'template_uuid' => $obj['template_uuid'],
'event_id' => $obj['event_id'],
);
array_push($this->__json['items'], $toPush);
@ -198,6 +268,7 @@
'to' => $rel['referenced_id'],
'type' => $rel['relationship_type'],
'comment' => $rel['comment'],
'event_id' => $rel['event_id'],
);
array_push($this->__json['relations'], $toPush);
}
@ -236,8 +307,8 @@
'uuid' => $attr['uuid'],
'type' => $attr['type'],
'label' => $attr['value'],
'event_id' => $attr['event_id'],
'node_type' => 'attribute',
//'Tag' => $Tags,
);
array_push($this->__json['items'], $toPush);
@ -264,6 +335,7 @@
'node_type' => 'object',
'meta-category' => $obj['meta-category'],
'template_uuid' => $obj['template_uuid'],
'event_id' => $obj['event_id'],
);
array_push($this->__json['items'], $toPush);
@ -342,6 +414,7 @@
'uuid' => $attr['uuid'],
'type' => $attr['type'],
'label' => $attr['value'],
'event_id' => $attr['event_id'],
'node_type' => 'attribute',
);
array_push($this->__json['items'], $toPush);
@ -369,6 +442,7 @@
'node_type' => 'object',
'meta-category' => $obj['meta-category'],
'template_uuid' => $obj['template_uuid'],
'event_id' => $obj['event_id'],
);
array_push($this->__json['items'], $toPush);

View File

@ -961,10 +961,10 @@ class Attribute extends AppModel {
break;
case 'hostname':
case 'domain':
if (preg_match("#^[A-Z0-9.\-_]+\.[A-Z0-9\-]{2,}$#i", $value)) {
if (preg_match("#^[A-Z0-9.\-_]+\.[A-Z0-9\-]{2,}[\.]?$#i", $value)) {
$returnValue = true;
} else {
$returnValue = 'Domain name has an invalid format. Please double check the value or select type "other".';
$returnValue = ucfirst($type) . ' name has an invalid format. Please double check the value or select type "other".';
}
break;
case 'hostname|port':
@ -2571,6 +2571,7 @@ class Attribute extends AppModel {
}
}
}
$results = array_values($results);
return $results;
}

View File

@ -2700,6 +2700,7 @@ class Event extends AppModel {
}
}
}
throw new Exception();
}
if ($fromXml) $created_id = $this->id;
if (!empty($data['Event']['published']) && 1 == $data['Event']['published']) {
@ -3608,7 +3609,8 @@ class Event extends AppModel {
$this->__fTool = new FinancialTool();
}
if ($object['type'] == 'attachment' && preg_match('/.*\.(jpg|png|jpeg|gif)$/i', $object['value'])) {
$object['image'] = $this->Attribute->base64EncodeAttachment($object);
if (!empty($object['data'])) $object['image'] = $object['data'];
else $object['image'] = $this->Attribute->base64EncodeAttachment($object);
}
if (isset($object['distribution']) && $object['distribution'] != 4) unset($object['SharingGroup']);
if ($object['objectType'] !== 'object') {
@ -4162,4 +4164,98 @@ class Event extends AppModel {
return $response;
}
}
public function enrichmentRouter($options) {
if (Configure::read('MISP.background_jobs')) {
$job = ClassRegistry::init('Job');
$job->create();
$this->ResqueStatus = new ResqueStatus\ResqueStatus(Resque::redis());
$workers = $this->ResqueStatus->getWorkers();
$workerType = 'default';
foreach ($workers as $worker) {
if ($worker['queue'] === 'prio') {
$workerType = 'prio';
}
}
$data = array(
'worker' => $workerType,
'job_type' => 'enrichment',
'job_input' => 'Event ID: ' . $options['event_id'] . ' modules: ' . json_encode($options['modules']),
'status' => 0,
'retries' => 0,
'org_id' => $options['user']['org_id'],
'org' => $options['user']['Organisation']['name'],
'message' => 'Enriching event.',
);
$job->save($data);
$jobId = $job->id;
$process_id = CakeResque::enqueue(
'prio',
'EventShell',
array('enrichment', $options['user']['id'], $options['event_id'], json_encode($options['modules']), $jobId),
true
);
$job->saveField('process_id', $process_id);
return true;
} else {
$result = $this->enrichment($options);
return __('#' . $result . ' attributes have been created during the enrichment process.');
}
}
public function enrichment($params) {
$option_fields = array('user', 'event_id', 'modules');
foreach ($option_fields as $option_field) {
if (empty($params[$option_field])) {
throw new MethodNotAllowedException(__('%s not set', $params[$option_field]));
}
}
$event = $this->fetchEvent($params['user'], array('eventid' => $params['event_id'], 'includeAttachments' => 1, 'flatten' => 1));
$this->Module = ClassRegistry::init('Module');
$enabledModules = $this->Module->getEnabledModules($params['user']);
if (empty($enabledModules)) return true;
$options = array();
foreach ($enabledModules['modules'] as $k => $temp) {
if (isset($temp['meta']['config'])) {
$settings = array();
foreach ($temp['meta']['config'] as $conf) {
$settings[$conf] = Configure::read('Plugin.Enrichment_' . $temp['name'] . '_' . $conf);
}
$enabledModules['modules'][$k]['config'] = $settings;
}
}
if (empty($event)) throw new MethodNotAllowedException('Invalid event.');
$attributes_added = 0;
foreach ($event[0]['Attribute'] as $attribute) {
foreach ($enabledModules['modules'] as $module) {
if (in_array($module['name'], $params['modules'])) {
if (in_array($attribute['type'], $module['mispattributes']['input'])) {
$data = array('module' => $module['name'], $attribute['type'] => $attribute['value'], 'event_id' => $attribute['event_id'], 'attribute_uuid' => $attribute['uuid']);
if (!empty($module['config'])) $data['config'] = $module['config'];
$data = json_encode($data);
$result = $this->Module->queryModuleServer('/query', $data, false, 'Enrichment');
if (!$result) throw new MethodNotAllowedException($type . ' service not reachable.');
if (isset($result['error'])) $this->Session->setFlash($result['error']);
if (!is_array($result)) throw new Exception($result);
$attributes = $this->handleModuleResult($result, $attribute['event_id']);
foreach ($attributes as $a) {
$this->Attribute->create();
$a['distribution'] = $attribute['distribution'];
$a['sharing_group_id'] = $attribute['sharing_group_id'];
$comment = 'Attribute #' . $attribute['id'] . ' enriched by ' . $module['name'] . '.';
if (!empty($a['comment'])) {
$a['comment'] .= PHP_EOL . $comment;
} else {
$a['comment'] = $comment;
}
$a['type'] = empty($a['default_type']) ? $a['types'][0] : $a['default_type'];
$result = $this->Attribute->save($a);
if ($result) $attributes_added++;
}
}
}
}
}
return $attributes_added;
}
}

View File

@ -16,45 +16,45 @@ class Log extends AppModel {
public $validate = array(
'action' => array(
'rule' => array('inList', array(
'accept',
'accept_delegation',
'add',
'admin_email',
'auth',
'auth_fail',
'blacklisted',
'change_pw',
'delete',
'disable',
'discard',
'edit',
'email',
'enable',
'error',
'export',
'file_upload',
'galaxy',
'login',
'login_fail',
'logout',
'add',
'edit',
'change_pw',
'delete',
'merge',
'pruneUpdateLogs',
'publish',
'accept',
'discard',
'publish alert',
'pull',
'push',
'blacklisted',
'admin_email',
'tag',
'publish alert',
'warning',
'error',
'email',
'serverSettingsEdit',
'remove_dead_workers',
'upload_sample',
'request_delegation',
'reset_auth_key',
'serverSettingsEdit',
'tag',
'undelete',
'update',
'update_database',
'upgrade_24',
'upload_sample',
'version_warning',
'auth',
'auth_fail',
'reset_auth_key',
'update',
'enable',
'disable',
'accept_delegation',
'request_delegation',
'merge',
'undelete',
'file_upload',
'export',
'pruneUpdateLogs',
'galaxy'
'warning'
)),
'message' => 'Options : ...'
)

View File

@ -89,7 +89,7 @@ class Module extends AppModel {
$modules = $this->getModules($type, $moduleFamily);
if (is_array($modules)) {
foreach ($modules['modules'] as $k => $module) {
if (!Configure::read('Plugin.' . $moduleFamily . '_' . $module['name'] . '_enabled') || ($type && in_array(strtolower($type), $module['meta']['module-type']))) {
if (!Configure::read('Plugin.' . $moduleFamily . '_' . $module['name'] . '_enabled') || ($type && !in_array(strtolower($type), $module['meta']['module-type']))) {
unset($modules['modules'][$k]);
continue;
}

View File

@ -43,11 +43,7 @@ class Server extends AppModel {
'url' => array( // TODO add extra validation to refuse multiple time the same url from the same org
'url' => array(
'rule' => array('url'),
'message' => 'Please enter a valid base-url.',
//'allowEmpty' => false,
//'required' => false,
//'last' => false, // Stop validation after this rule
//'on' => 'create', // Limit validation to 'create' or 'update' operations
'message' => 'Please enter a valid base-url.'
)
),
'authkey' => array(
@ -113,7 +109,8 @@ class Server extends AppModel {
'pull' => 'MISP/app/Console/cake Server pull [user_id] [server_id] [full|update]',
'push' => 'MISP/app/Console/cake Server push [user_id] [server_id]',
'cacheFeed' => 'MISP/app/Console/cake Server cacheFeed [user_id] [feed_id|all|csv|text|misp]',
'fetchFeed' => 'MISP/app/Console/cake Server fetchFeed [user_id] [feed_id|all|csv|text|misp]'
'fetchFeed' => 'MISP/app/Console/cake Server fetchFeed [user_id] [feed_id|all|csv|text|misp]',
'enrichment' => 'MISP/app/Console/cake Event enrichEvent [user_id] [event_id] [json_encoded_module_list]'
);
public $serverSettings = array(
@ -1066,11 +1063,19 @@ class Server extends AppModel {
),
'timeout' => array(
'level' => 0,
'description' => 'The timeout duration of sessions (in MINUTES). Keep in mind that autoregenerate can be used to extend the session on user activity.',
'description' => 'The timeout duration of sessions (in MINUTES).',
'value' => '',
'errorMessage' => '',
'test' => 'testForNumeric',
'type' => 'string',
'type' => 'string'
),
'cookie_timeout' => array(
'level' => 0,
'description' => 'The expiration of the cookie (in MINUTES). The session timeout gets refreshed frequently, however the cookies do not. Generally it is recommended to have a much higher cookie_timeout than timeout.',
'value' => '',
'errorMessage' => '',
'test' => 'testForNumeric',
'type' => 'numeric'
)
),
'Plugin' => array(
@ -2729,7 +2734,7 @@ class Server extends AppModel {
Configure::write($settingFix, $arrayElements);
}
}
$settingsToSave = array('debug', 'MISP', 'GnuPG', 'SMIME', 'Proxy', 'SecureAuth', 'Security', 'Session.defaults', 'Session.timeout', 'Session.autoRegenerate', 'site_admin_debug', 'Plugin', 'CertAuth', 'ApacheShibbAuth', 'ApacheSecureAuth');
$settingsToSave = array('debug', 'MISP', 'GnuPG', 'SMIME', 'Proxy', 'SecureAuth', 'Security', 'Session.defaults', 'Session.timeout', 'Session.cookie_timeout', 'Session.autoRegenerate', 'site_admin_debug', 'Plugin', 'CertAuth', 'ApacheShibbAuth', 'ApacheSecureAuth');
$settingsArray = array();
foreach ($settingsToSave as $setting) {
$settingsArray[$setting] = Configure::read($setting);

View File

@ -477,6 +477,7 @@ class User extends AppModel {
} catch (Exception $e) {
$result[2] ='GnuPG is not configured on this system.';
$result[0] = true;
return $result;
}
}
$result = array();

View File

@ -31,12 +31,16 @@
?>
<div class="input clear"></div>
<?php
echo $this->Form->input('distribution', array(
$distArray = array(
'options' => array($distributionLevels),
'label' => __('Distribution ') . $this->element('formInfo', array('type' => 'distribution')),
'selected' => $initialDistribution,
));
);
if ($action == 'add') {
$distArray['selected'] = $initialDistribution;
}
echo $this->Form->input('distribution', $distArray);
?>
<div id="SGContainer" style="display:none;">
<?php

View File

@ -10,7 +10,7 @@
if ($attribute['Attribute']['disable_correlation']) {
echo __('Re-enable the correlation for this attribute.');
} else {
echo __('This will remove all correlations that already exist for this attribute and prevents any attributes to be related as long as this setting is disabled. Make sure you understand the downasides of disabling correlations.');
echo __('This will remove all correlations that already exist for this attribute and prevents any attributes to be related as long as this setting is disabled. Make sure you understand the downsides of disabling correlations.');
}
?>
</p>

View File

@ -283,7 +283,7 @@
<?php
endif;
else:
if ($isSiteAdmin || !$mayModify):
if ($isAclAdd && ($isSiteAdmin || !$mayModify)):
if (isset($modules) && isset($modules['types'][$object['type']])):
?>
<span class="icon-asterisk useCursorPointer" title="<?php echo __('Query enrichment');?>" role="button" tabindex="0" aria-label="<?php echo __('Query enrichment');?>" onClick="simplePopup('<?php echo $baseurl;?>/events/queryEnrichment/<?php echo h($object['id']);?>/ShadowAttribute');" title="<?php echo __('Propose enrichment');?>">&nbsp;</span>

View File

@ -1,23 +1,40 @@
<div style="display:inline-block;">
<span style="display:inline-block;">
<?php
$full = $isAclTagger && $tagAccess;
foreach ($tags as $tag):
$tagClass = $full ? 'tagFirstHalf' : 'tag';
$tagData = "";
foreach ($tags as $tag) {
$aStyle = 'display:inline-block; background-color:' . h($tag['Tag']['colour']) . ';color:' . $this->TextColour->getTextColour($tag['Tag']['colour']) . ';';
$aClass = $full ? 'tagFirstHalf' : 'tag';
$aText = h($tag['Tag']['name']);
$aSearchTagUrl = $baseurl . '/events/index/searchtag: ' . h($tag['Tag']['id']);
$span1 = sprintf('<a href"%s" style="%s" class="%s">%s</a>', $aSearchTagUrl, $aStyle, $aClass, $aText);
$span2 = '';
if ($full) {
$spanClass = "tagSecondHalf useCursorPointer noPrint";
$spanTitle = __('Remove tag');
$spanRole = "button";
$spanTabIndex = "0";
$spanAriaLabel = __('Remove tag %s', h($tag['Tag']['name']));
$spanOnClick = "removeObjectTagPopup('event', '" . h($event['Event']['id']) . "', '" . h($tag['Tag']['id']) . "')";
$span2 = sprintf('<span class="%s" title="%s" role="%s" tabindex="%s" aria-label="%s" onClick="%s">x</span>', $spanClass, $spanTitle, $spanRole, $spanTabIndex, $spanAriaLabel, $spanOnClick);
}
$tagData .= '<span style="white-space:nowrap;">' . $span1 . $span2 . '</span> ';
}
$buttonData = "&nbsp;";
if ($full) {
$buttonVars = array(
'addTagButton',
__('Add a tag'),
'button',
'0',
__('Add a tag'),
'btn btn-inverse noPrint',
'line-height:10px; padding: 4px 4px;',
'getPopup(\'' . h($event['Event']['id']) . '\', \'tags\', \'selectTaxonomy\');'
);
$buttonData = vsprintf('<button id="%s" title="%s" role ="%s" tabindex="%s" aria-label="%s" class="%s" style="%s" onClick="%s">+</button>', $buttonVars);
}
$tagData .= $buttonData;
?>
<div style="padding:1px; overflow:hidden; white-space:nowrap; display:flex; float:left; margin-right:2px;">
<a href="<?php echo $baseurl;?>/events/index/searchtag:<?php echo h($tag['Tag']['id']); ?>" class="<?php echo $tagClass; ?>" style="display:inline-block; background-color:<?php echo h($tag['Tag']['colour']);?>;color:<?php echo $this->TextColour->getTextColour($tag['Tag']['colour']);?>"><?php echo h($tag['Tag']['name']); ?></a>
<?php if ($full): ?>
<div class="tagSecondHalf useCursorPointer noPrint" title="<?php echo __('Remove tag');?>" role="button" tabindex="0" aria-label="<?php echo __('Remove tag %s', h($tag['Tag']['name']));?>" onClick="removeObjectTagPopup('event', '<?php echo h($event['Event']['id']); ?>', '<?php echo h($tag['Tag']['id']); ?>');">x</div>
<?php endif;?>
</div>
<?php
endforeach;
?>
<div style="float:left">
<?php if ($full): ?>
<button id="addTagButton" title="<?php echo __('Add a tag');?>" role="button" tabindex="0" aria-label="<?php echo __('Add a tag');?>" class="btn btn-inverse noPrint" style="line-height:10px; padding: 4px 4px;" onClick="getPopup('<?php echo h($event['Event']['id']); ?>', 'tags', 'selectTaxonomy');">+</button>
<?php else:?>
&nbsp;
<?php endif; ?>
</div>
</div>
<span style="padding:1px; display:flex; display: inline-block; margin-right:2px;word-wrap:break-word;"><?php echo $tagData; ?></span>
</span>

View File

@ -57,7 +57,11 @@
if ($cluster_field['key'] == 'refs') {
$value = array();
foreach ($cluster_field['value'] as $k => $v) {
$value[$k] = '<a href="' . h($v) . '">' . h($v) . '</a>';
$v_name = $v;
if (strlen($v_name) > 30) {
$v_name = substr($v, 0, 30) . '...';
}
$value[$k] = '<a href="' . h($v) . '" title="' . h($v) . '">' . h($v_name) . '</a>';
}
echo nl2br(implode("\n", $value));
} else if($cluster_field['key'] == 'country') {

View File

@ -42,6 +42,7 @@
<?php if ($menuItem === 'populateFromtemplate'): ?>
<li class="active"><a href="<?php echo $baseurl;?>/templates/populateEventFromTemplate/<?php echo $template_id . '/' . h($event['Event']['id']); ?>"><?php echo __('Populate From Template');?></a></li>
<?php endif; ?>
<li id='lienrichEvent'><a href="#" onClick="genericPopup('<?php echo $baseurl?>/events/enrichEvent/<?php echo h($event['Event']['id']); ?>', '#confirmation_box');" style="cursor:pointer;"><?php echo __('Enrich event');?></a></li>
<li id='merge'><a href="<?php echo $baseurl;?>/events/merge/<?php echo h($event['Event']['id']);?>"><?php echo __('Merge attributes from…');?></a></li>
<?php endif; ?>
<?php if (($isSiteAdmin && (!isset($mayModify) || !$mayModify)) || (!isset($mayModify) || !$mayModify)): ?>

View File

@ -17,7 +17,7 @@
</div>
<span class="shortcut-help btn btn-xs btn-info">?</span>
<span class="fullscreen-btn btn btn-xs btn-primary" data-toggle="tooltip" data-placement="top" data-title="<?php echo __('Toggle fullscreen');?>"><span class="fa fa-desktop"></span></span>
<span id="fullscreen-btn-eventgraph" class="fullscreen-btn btn btn-xs btn-primary" data-toggle="tooltip" data-placement="top" data-title="<?php echo __('Toggle fullscreen');?>"><span class="fa fa-desktop"></span></span>
<div id="eventgraph_shortcuts_background" class="eventgraph_network_background"></div>
<div id="eventgraph_network" class="eventgraph_network" data-event-id="<?php echo h($event['Event']['id']); ?>" data-user-manipulation="<?php echo $mayModify || $isSiteAdmin ? 'true' : 'false'; ?>" data-extended="<?php echo $extended; ?>"></div>

View File

@ -54,6 +54,9 @@
'class' => 'form-control span6',
'placeholder' => __('Event UUID or ID. Leave blank if not applicable.')
));
?>
<div id="extended_event_preview" style="width:446px;"></div>
<?php
echo $this->Form->input('Event.submittedgfi', array(
'label' => '<b>GFI sandbox</b>',
'type' => 'file',
@ -92,6 +95,10 @@ echo $this->Form->end();
initPopoverContent('Event');
});
$("#EventExtendsUuid").keyup(function() {
previewEventBasedOnUuids();
});
$(document).ready(function() {
if ($('#EventDistribution').val() == 4) $('#SGContainer').show();
else $('#SGContainer').hide();

View File

@ -0,0 +1,38 @@
<div class="confirmation">
<legend><?php echo __('Enrich Event'); ?></legend>
<div style="padding-left:5px;padding-right:5px;padding-bottom:5px;">
<p><?php echo __('Select the enrichments you wish to run');?></p>
<?php
echo $this->Form->create('', array('style' => 'margin-bottom:0px;'));
foreach ($modules['modules'] as $module) {
echo $this->Form->input($module['name'], array('type' => 'checkbox', 'label' => h($module['name'])));
}
?>
<table>
<tr>
<td style="vertical-align:top">
<?php
echo $this->Form->submit('Enrich', array('class' => 'btn btn-primary'));
?>
</td>
<td style="width:540px;">
</td>
<td style="vertical-align:top;">
<span role="button" tabindex="0" aria-label="<?php echo __('Cancel');?>" title="<?php echo __('Cancel');?>" class="btn btn-inverse" id="PromptNoButton" onClick="cancelPrompt();"><?php echo __('Cancel');?></span>
</td>
</tr>
</table>
<?php
echo $this->Form->end();
?>
</div>
</div>
<script type="text/javascript">
$(document).ready(function() {
resizePopoverBody();
});
$(window).resize(function() {
resizePopoverBody();
});
</script>

View File

@ -53,6 +53,7 @@ $mayPublish = ($isAclPublish && $event['Event']['orgc_id'] == $me['org_id']);
'placeholder' => __('Event UUID or ID. Leave blank if not applicable.')
));
?>
<div id="extended_event_preview" style="width:446px;"></div>
</fieldset>
<?php
echo $this->Form->button(__('Submit'), array('class' => 'btn btn-primary'));
@ -88,10 +89,15 @@ echo $this->Form->end();
initPopoverContent('Event');
});
$("#EventExtendsUuid").keyup(function() {
previewEventBasedOnUuids();
});
$(document).ready(function() {
if ($('#EventDistribution').val() == 4) $('#SGContainer').show();
else $('#SGContainer').hide();
initPopoverContent('Event');
previewEventBasedOnUuids();
});
});
</script>

View File

@ -0,0 +1,33 @@
<div class="info_container_form">
<div class="bold blue">Matched event</div>
<?php
if (empty($event)) {
$message = __('No matching events found.');
if ($validUuid) $message .= ' ' . __('This will still allow you to store the UUID. It will extend the assigned event as soon as it is created / becomes visible.');
echo '<div class="red bold">' . $message . '</div>';
} else {
$fields = array(
'id' => 'Event.id',
'analysis' => 'Event.analysis',
'threat level' => 'ThreatLevel.name',
'tags' => 'Tag',
'info' => 'Event.info'
);
foreach ($fields as $field => $fieldData) {
if ($field == 'tags') {
echo '<div><span class="blue bold">Tags</span>: ';
if (!empty($event['EventTag'])) {
echo '<span>' . $this->element('ajaxTags', array('event' => $event, 'tags' => $event['EventTag'], 'tagAccess' => false)) . '</span>';
}
echo '</div>';
} else {
$data = Hash::extract($event, $fieldData);
if ($field == 'analysis') {
$data[0] = $analysisLevels[intval($data[0])];
}
echo '<span class="blue bold">' . ucfirst(__($field)) . '</span>: ' . h($data[0]) . '<br />';
}
}
}
?>
</div>

View File

@ -361,6 +361,9 @@
<button class="btn btn-inverse toggle qet galaxy-toggle-button" id="eventgraph_toggle" data-toggle-type="eventgraph" onclick="enable_interactive_graph();">
<span class="icon-plus icon-white" title="<?php echo __('Toggle Event graph');?>" role="button" tabindex="0" aria-label="<?php echo __('Toggle Event graph');?>" style="vertical-align:top;"></span><?php echo __('Event graph');?>
</button>
<button class="btn btn-inverse toggle qet galaxy-toggle-button" id="correlationgraph_toggle" data-toggle-type="correlationgraph" onclick="enable_correlation_graph();">
<span class="icon-plus icon-white" title="<?php echo __('Toggle Correlation graph');?>" role="button" tabindex="0" aria-label="<?php echo __('Toggle Correlation graph');?>" style="vertical-align:top;"></span><?php echo __('Correlation graph');?>
</button>
<button class="btn btn-inverse toggle qet galaxy-toggle-button" id="attributes_toggle" data-toggle-type="attributes">
<span class="icon-minus icon-white" title="<?php echo __('Toggle attributes');?>" role="button" tabindex="0" aria-label="<?php echo __('Toggle attributes');?>" style="vertical-align:top;"></span><?php echo __('Attributes');?>
</button>
@ -380,6 +383,8 @@
<div id="eventgraph_div" class="info_container_eventgraph_network" style="display: none;" data-fullscreen="false">
<?php echo $this->element('view_event_graph'); ?>
</div>
<div id="correlationgraph_div" class="info_container_eventgraph_network" style="display: none;" data-fullscreen="false">
</div>
<div id="attributes_div">
<?php echo $this->element('eventattribute'); ?>
</div>
@ -399,9 +404,15 @@ $(document).ready(function () {
delay: { show: 500, hide: 100 }
});
$.get("/threads/view/<?php echo $event['Event']['id']; ?>/true", function(data) {
$.get("/threads/view/<?php echo h($event['Event']['id']); ?>/true", function(data) {
$("#discussions_div").html(data);
});
});
function enable_correlation_graph() {
$.get("/events/viewGraph/<?php echo h($event['Event']['id']); ?>", function(data) {
$("#correlationgraph_div").html(data);
});
}
</script>
<input type="hidden" value="/shortcuts/event_view.json" class="keyboardShortcutsConfig" />

View File

@ -8,24 +8,33 @@
echo $this->Html->script('d3');
echo $this->Html->script('correlation-graph');
?>
<div class="view">
<div id="chart" style="width:100%;height:100%"></div>
<div id="hover-menu-container" class="menu-container">
<span class="bold hidden" id="hover-header"><?php echo __('Hover target');?></span><br />
<ul id="hover-menu" class="menu">
<?php
if (!$ajax):
?>
<div class="view">
<?php endif; ?>
<span id="fullscreen-btn-correlation" class="fullscreen-btn-correlation btn btn-xs btn-primary" data-toggle="tooltip" data-placement="top" data-title="<?php echo __('Toggle fullscreen');?>"><span class="fa fa-desktop"></span></span>
<div id="chart" style="width:100%;height:100%"></div>
<div id="hover-menu-container" class="menu-container">
<span class="bold hidden" id="hover-header"><?php echo __('Hover target');?></span><br />
<ul id="hover-menu" class="menu">
</ul>
</div>
<div id="selected-menu-container" class="menu-container">
<span class="bold hidden" id="selected-header"><?php echo __('Selected');?></span><br />
<ul id = "selected-menu" class="menu">
</ul>
</div>
<ul id="context-menu" class="menu">
<li id="expand"><?php echo __('Expand');?></li>
<li id="context-delete"><?php echo __('Delete');?></li>
</ul>
<?php
if (!$ajax):
?>
</div>
<div id="selected-menu-container" class="menu-container">
<span class="bold hidden" id="selected-header"><?php echo __('Selected');?></span><br />
<ul id = "selected-menu" class="menu">
</ul>
</div>
<ul id="context-menu" class="menu">
<li id="expand"><?php echo __('Expand');?></li>
<li id="context-delete"><?php echo __('Delete');?></li>
</ul>
</div>
<div id="graph_init" class="hidden" data-id="<?php echo h($id);?>" data-scope="<?php echo h($scope);?>">
<?php endif; ?>
<div id="graph_init" class="hidden" data-id="<?php echo h($id);?>" data-scope="<?php echo h($scope);?>" data-ajax="<?php echo $ajax ? 'true' : 'false'; ?>">
</div>
<?php
$scope_list = array(
@ -46,5 +55,8 @@
$params['taxonomy'] = $taxonomy['Taxonomy']['id'];
}
}
echo $this->element('side_menu', $params);
if (!$ajax) {
echo $this->element('side_menu', $params);
}
?>

View File

@ -129,6 +129,8 @@ if (h($user['User']['change_pw']) == 1) {
</dd>
</dl>
<br />
<a href="<?php echo $baseurl . '/admin/users/view/' . h($user['User']['id']) . '.json'; ?>" class="btn btn-inverse" download>Download user profile for data portability</a>
<br />
<div id="userEvents"></div>
</div>
<?php

View File

@ -86,6 +86,8 @@
</dd>
<?php endif; ?>
</dl>
<br />
<a href="<?php echo $baseurl . '/users/view/me.json'; ?>" class="btn btn-inverse" download>Download user profile for data portability</a>
</div>
<?php
echo $this->element('side_menu', array('menuList' => 'globalActions', 'menuItem' => 'view'));

@ -1 +1 @@
Subproject commit d360e6733cb4dff2cc5e8f042b9d322f339d2f83
Subproject commit 01b05f66aa9a88e61d0303c32ae5b530926aaa83

@ -1 +1 @@
Subproject commit c8e9155a3eba26696219127d09bcbedf95014416
Subproject commit 3b5db95174a13e20db15eecb7837a4447a08e0dd

View File

@ -93,13 +93,13 @@ SCHEMALOC_DICT = {
def main(args):
if len(sys.argv) < 4:
if len(args) < 4:
sys.exit("Invalid parameters")
baseURL = sys.argv[1]
baseURL = args[1]
if not baseURL:
baseURL = 'https://www.misp-project.org'
orgname = sys.argv[2]
orgname = args[2]
namespace = [baseURL, orgname.replace(" ", "_")]
namespace[1] = re.sub('[\W]+', '', namespace[1])
@ -123,11 +123,11 @@ def main(args):
stix_package = STIXPackage()
stix_header = STIXHeader()
stix_header.title="Export from " + orgname + " MISP"
stix_header.title="Export from {} MISP".format(orgname)
stix_header.package_intents="Threat Report"
stix_package.stix_header = stix_header
if sys.argv[3] == 'json':
if args[3] == 'json':
stix_string = stix_package.to_json()[:-1]
stix_string += ', "related_packages": ['
else:

View File

@ -42,7 +42,9 @@ class StixParser():
def __init__(self):
self.misp_event = MISPEvent()
self.misp_event['Galaxy'] = []
self.references = defaultdict(list)
# Load data from STIX document, and other usefull data
def load(self, args, pathname):
try:
filename = '{}/tmp/{}'.format(pathname, args[1])
@ -62,6 +64,7 @@ class StixParser():
print(json.dumps({'success': 0, 'message': 'The temporary STIX export file could not be read'}))
sys.exit(0)
# Event loading function, recursively itterating as long as namespace errors appear
def load_event(self, filename):
try:
return STIXPackage.from_xml(filename)
@ -75,6 +78,7 @@ class StixParser():
else:
return None
# Load the mapping dictionary for STIX object types
def load_mapping(self):
self.attribute_types_mapping = {
'AddressObjectType': self.handle_address,
@ -91,9 +95,13 @@ class StixParser():
'URIObjectType': self.handle_domain_or_url,
"WhoisObjectType": self.handle_whois,
'WindowsRegistryKeyObjectType': self.handle_regkey,
"WindowsExecutableFileObjectType": self.handle_pe
"WindowsExecutableFileObjectType": self.handle_pe,
"WindowsServiceObjectType": self.handle_windows_service
}
# Define if the STIX document is from MISP or is an external one
# and call the appropriate function to parse it.
# Then, make references between objects
def handler(self):
self.outputname = '{}.json'.format(self.filename)
if self.fromMISP:
@ -102,13 +110,16 @@ class StixParser():
else:
# external STIX format file
self.buildExternalDict()
self.build_references()
# Build a MISP event, parsing STIX data following the structure used in our own exporter
def buildMispDict(self):
self.dictTimestampAndDate()
self.eventInfo()
for indicator in self.event.related_indicators.indicator:
self.parse_misp_indicator(indicator)
# Try to parse data from external STIX documents
def buildExternalDict(self):
self.dictTimestampAndDate()
self.eventInfo()
@ -121,6 +132,15 @@ class StixParser():
if self.event.courses_of_action:
self.parse_coa(self.event.courses_of_action)
# Make references between objects
def build_references(self):
for misp_object in self.misp_event.objects:
object_uuid = misp_object.uuid
if object_uuid in self.references:
for reference in self.references[object_uuid]:
misp_object.add_reference(reference['idref'], reference['relationship'])
# Set timestamp & date values in the new MISP event
def dictTimestampAndDate(self):
if self.event.timestamp:
stixTimestamp = self.event.timestamp
@ -131,6 +151,7 @@ class StixParser():
self.misp_event.date = date
self.misp_event.timestamp = self.getTimestampfromDate(stixTimestamp)
# Translate date into timestamp
@staticmethod
def getTimestampfromDate(date):
try:
@ -144,6 +165,7 @@ class StixParser():
d = int(time.mktime(date.timetuple()))
return d
# Set info & title values in the new MISP event
def eventInfo(self):
info = "Imported from external STIX event"
try:
@ -157,6 +179,7 @@ class StixParser():
pass
self.misp_event.info = str(info)
# Parse indicators of a STIX document coming from our exporter
def parse_misp_indicator(self, indicator):
# define is an indicator will be imported as attribute or object
if indicator.relationship in categories:
@ -164,6 +187,7 @@ class StixParser():
else:
self.parse_misp_object(indicator)
# Parse STIX objects that we know will give MISP attributes
def parse_misp_attribute(self, indicator):
misp_attribute = {'category': str(indicator.relationship)}
item = indicator.item
@ -184,6 +208,7 @@ class StixParser():
attribute_type, attribute_value = self.composite_type(attribute_dict)
self.misp_event.add_attribute(attribute_type, attribute_value, **misp_attribute)
# Return type & value of a composite attribute in MISP
@staticmethod
def composite_type(attributes):
if "port" in attributes:
@ -200,6 +225,7 @@ class StixParser():
ip_value = attributes["ip-dst"]
return "domain|ip", "{}|{}".format(attributes["domain"], ip_value)
# Define type & value of an attribute or object in MISP
def handle_attribute_type(self, properties, is_object=False, title=None):
xsi_type = properties._XSI_TYPE
try:
@ -214,6 +240,7 @@ class StixParser():
print("Unparsed type: {}".format(xsi_type))
sys.exit(1)
# Return type & value of an ip address attribute
@staticmethod
def handle_address(properties):
if properties.is_source:
@ -222,15 +249,18 @@ class StixParser():
ip_type = "ip-dst"
return ip_type, properties.address_value.value, "ip"
# Return type & value of an attachment attribute
@staticmethod
def handle_attachment(properties, title):
return eventTypes[properties._XSI_TYPE]['type'], title, properties.raw_artifact.value
# Return type & value of a domain or url attribute
@staticmethod
def handle_domain_or_url(properties):
event_types = eventTypes[properties._XSI_TYPE]
return event_types['type'], properties.value.value, event_types['relation']
# Return type & value of an email attribute
def handle_email_attribute(self, properties):
try:
if properties.from_:
@ -257,11 +287,13 @@ class StixParser():
print("Unsupported Email property")
sys.exit(1)
# Return type & value of an email attachment
@staticmethod
def handle_email_attachment(indicator_object):
properties = indicator_object.related_objects[0].properties
return "email-attachment", properties.file_name.value, "attachment"
# Return type & attributes of a file object
def handle_file(self, properties, is_object):
b_hash, b_file = False, False
attributes = []
@ -296,6 +328,7 @@ class StixParser():
return self.handle_filename_object(attributes, is_object)
return "file", self.return_attributes(attributes), ""
# Return the appropriate type & value when we have 1 filename & 1 hash value
@staticmethod
def handle_filename_object(attributes, is_object):
for attribute in attributes:
@ -313,6 +346,7 @@ class StixParser():
# it could be malware-sample as well, but STIX is losing this information
return "filename|{}".format(hash_type), value, ""
# Return type & value of a hash attribute
@staticmethod
def handle_hashes_attribute(properties):
hash_type = properties.type_.value.lower()
@ -322,11 +356,13 @@ class StixParser():
hash_value = properties.fuzzy_hash_value.value
return hash_type, hash_value, hash_type
# Return type & value of a hostname attribute
@staticmethod
def handle_hostname(properties):
event_types = eventTypes[properties._XSI_TYPE]
return event_types['type'], properties.hostname_value.value, event_types['relation']
# Return type & value of a http request attribute
@staticmethod
def handle_http(properties):
client_request = properties.http_request_response[0].http_client_request
@ -342,21 +378,25 @@ class StixParser():
value = client_request.http_request_line.http_method.value
return "http-method", value, "method"
# Return type & value of a mutex attribute
@staticmethod
def handle_mutex(properties):
event_types = eventTypes[properties._XSI_TYPE]
return event_types['type'], properties.name.value, event_types['relation']
# Return type & value of a port attribute
@staticmethod
def handle_port(properties):
event_types = eventTypes[properties._XSI_TYPE]
return event_types['type'], properties.port_value.value, event_types['relation']
# Return type & value of a regkey attribute
@staticmethod
def handle_regkey(properties):
event_types = eventTypes[properties._XSI_TYPE]
return event_types['type'], properties.key.value, event_types['relation']
# Return type & value of a composite attribute ip|port or hostname|port
def handle_socket_address(self, properties):
if properties.ip_address:
type1, value1, _ = self.handle_address(properties.ip_address)
@ -365,6 +405,9 @@ class StixParser():
value1 = properties.hostname.hostname_value.value
return "{}|port".format(type1), "{}|{}".format(value1, properties.port.port_value.value), ""
# Parse a whois object:
# Return type & attributes of a whois object if we have the required fields
# Otherwise create attributes and return type & value of the last attribute to avoid crashing the parent function
def handle_whois(self, properties):
required_one_of = False
attributes = []
@ -411,11 +454,19 @@ class StixParser():
self.misp_event.add_attribute(attribute_type, attribute_value, **misp_attributes)
return last_attribute
@staticmethod
def handle_windows_service(properties):
if properties.name:
return "windows-service-name", properties.name.value, ""
# Return type & attributes of the file defining a portable executable object
def handle_pe(self, properties):
pe_uuid = self.parse_pe(properties)
file_type, file_value, _ = self.handle_file(properties, False)
return file_type, file_value, pe_uuid
# Parse attributes of a portable executable, create the corresponding object,
# and return its uuid to build the reference for the file object generated at the same time
def parse_pe(self, properties):
misp_object = MISPObject('pe')
filename = properties.file_name.value
@ -437,14 +488,16 @@ class StixParser():
header_object.add_attribute(**{"type": "size-in-bytes", "object_relation": "size-in-bytes",
"value": file_header.size_of_optional_header.value})
self.misp_event.add_object(**header_object)
misp_object.add_reference(header_object.uuid, 'pe-section')
misp_object.add_reference(header_object.uuid, 'included-in')
if properties.sections:
for section in properties.sections:
section_uuid = self.parse_pe_section(section)
misp_object.add_reference(section_uuid, 'pe-section')
misp_object.add_reference(section_uuid, 'included-in')
self.misp_event.add_object(**misp_object)
return {"pe_uuid": misp_object.uuid}
# Parse attributes of a portable executable section, create the corresponding object,
# and return its uuid to build the reference for the pe object generated at the same time
def parse_pe_section(self, section):
section_object = MISPObject('pe-section')
header_hashes = section.header_hashes
@ -463,6 +516,7 @@ class StixParser():
self.misp_event.add_object(**section_object)
return section_object.uuid
# Parse STIX object that we know will give MISP objects
def parse_misp_object(self, indicator):
object_type = str(indicator.relationship)
if object_type == 'file':
@ -477,6 +531,7 @@ class StixParser():
if object_type != "misc":
print("Unparsed Object type: {}".format(name))
# Create a MISP object, its attributes, and add it in the MISP event
def fill_misp_object(self, item, name):
misp_object = MISPObject(name)
misp_object.timestamp = self.getTimestampfromDate(item.timestamp)
@ -490,11 +545,13 @@ class StixParser():
self.parse_observable(properties, misp_object)
self.misp_event.add_object(**misp_object)
# Create a MISP attribute and add it in its MISP object
def parse_observable(self, properties, misp_object):
misp_attribute = MISPAttribute()
misp_attribute.type, misp_attribute.value, misp_attribute.object_relation = self.handle_attribute_type(properties, is_object=True)
misp_object.add_attribute(**misp_attribute)
# Parse indicators of an external STIX document
def parse_external_indicator(self, indicators):
for indicator in indicators:
try:
@ -514,29 +571,41 @@ class StixParser():
# otherwise, it is a dictionary of attributes, so we build an object
self.handle_object_case(attribute_type, attribute_value, compl_data)
# Parse observables of an external STIX document
def parse_external_observable(self, observables):
for observable in observables:
title = observable.title
observable_object = observable.object_
try:
properties = observable.object_.properties
properties = observable_object.properties
except:
self.parse_description(observable)
continue
if properties:
try:
attribute_type, attribute_value, compl_data = self.handle_attribute_type(properties, title=title)
except:
except KeyError:
# print("Error with an object of type: {}\n{}".format(properties._XSI_TYPE, observable.to_json()))
continue
object_uuid = self.fetch_uuid(observable_object.id_)
attr_type = type(attribute_value)
if attr_type is str or attr_type is int:
# if the returned value is a simple value, we build an attribute
attribute = {'to_ids': False}
attribute = {'to_ids': False, 'uuid': object_uuid}
# if observable_object.related_objects:
# attribute
self.handle_attribute_case(attribute_type, attribute_value, compl_data, attribute)
else:
# otherwise, it is a dictionary of attributes, so we build an object
if attribute_value:
self.handle_object_case(attribute_type, attribute_value, compl_data)
self.handle_object_case(attribute_type, attribute_value, compl_data, object_uuid=object_uuid)
if observable_object.related_objects:
for related_object in observable_object.related_objects:
relationship = related_object.relationship.value.inner().replace('_', '-')
self.references[object_uuid].append({"idref": self.fetch_uuid(related_object.idref),
"relationship": relationship})
# Parse description of an external indicator or observable and add it in the MISP event as an attribute
def parse_description(self, stix_object):
if stix_object.description:
misp_attribute = {}
@ -544,22 +613,36 @@ class StixParser():
misp_attribute['timestamp'] = self.getTimestampfromDate(stix_object.timestamp)
self.misp_event.add_attribute("text", stix_object.description.value, **misp_attribute)
# The value returned by the indicators or observables parser is of type str or int
# Thus we can add an attribute in the MISP event with the type & value
def handle_attribute_case(self, attribute_type, attribute_value, data, attribute):
if attribute_type == 'attachment':
attribute['data'] = data
self.misp_event.add_attribute(attribute_type, attribute_value, **attribute)
def handle_object_case(self, attribute_type, attribute_value, compl_data):
# The value returned by the indicators or observables parser is a list of dictionaries
# These dictionaries are the attributes we add in an object, itself added in the MISP event
def handle_object_case(self, attribute_type, attribute_value, compl_data, object_uuid=None):
misp_object = MISPObject(attribute_type)
if object_uuid:
misp_object.uuid = object_uuid
for attribute in attribute_value:
misp_object.add_attribute(**attribute)
if type(compl_data) is dict and "pe_uuid" in compl_data:
# if some complementary data is a dictionary containing an uuid,
# it means we are using it to add an object reference of a pe object
# in a file object
misp_object.add_reference(compl_data['pe_uuid'], 'pe')
# it means we are using it to add an object reference
misp_object.add_reference(compl_data['pe_uuid'], 'included-in')
self.misp_event.add_object(**misp_object)
@staticmethod
def fetch_uuid(object_id):
identifier = object_id.split(':')[1]
return_id = ""
for part in identifier.split('-')[1:]:
return_id += "{}-".format(part)
return return_id[:-1]
# Parse the ttps field of an external STIX document
def parse_ttps(self, ttps):
for ttp in ttps:
if ttp.behavior and ttp.behavior.malware_instances:
@ -580,6 +663,7 @@ class StixParser():
galaxy['GalaxyCluster'] = [cluster]
self.misp_event['Galaxy'].append(galaxy)
# Parse the courses of action field of an external STIX document
def parse_coa(self, courses_of_action):
for coa in courses_of_action:
misp_object = MISPObject('course-of-action')
@ -626,6 +710,7 @@ class StixParser():
misp_object.add_reference(referenced_uuid, 'observable', None, **attribute)
self.misp_event.add_object(**misp_object)
# Return the attributes that will be added in a MISP object as a list of dictionaries
@staticmethod
def return_attributes(attributes):
return_attributes = []
@ -633,6 +718,8 @@ class StixParser():
return_attributes.append(dict(zip(('type', 'value', 'object_relation'), attribute)))
return return_attributes
# Convert the MISP event we create from the STIX document into json format
# and write it in the output file
def saveFile(self):
eventDict = self.misp_event.to_json()
with open(self.outputname, 'w') as f:

@ -1 +1 @@
Subproject commit f54d241f2b159114224db2e8447e2c8bcf36ed8e
Subproject commit bd95487001b0415e1060f8f2a73a76e2db7b8b18

View File

@ -79,3 +79,13 @@ line.link {
top:-5px;
margin-left:100px;
}
.fullscreen-btn-correlation {
position: absolute;
right: 5px;
bottom: 2px;
font-weight: bold;
z-index: 1;
cursor: nesw-resize;
}

View File

@ -133,6 +133,7 @@ label.center-in-network-header {
z-index: 1;
cursor: nesw-resize;
}
.popover-content {
white-space: pre-wrap;
}

View File

@ -1677,6 +1677,16 @@ a.discrete {
height: 100%;
}
.info_container_form {
border: 1px solid black; /*like Alexandre's heart*/
border-radius: 7px;
padding-left:10px;
padding-right:10px;
width: 438px;
height: 100%;
margin-bottom:10px;
}
.info_container_key {
vertical-align:text-top;
width:100px;

View File

@ -203,8 +203,13 @@ class ActionTable {
__add_options_to_select(select, options) {
for(var value of options) {
var option = document.createElement('option');
option.value = value;
option.innerHTML = value;
if (Array.isArray(value)) { // array of type [value, text]
option.value = value[1];
option.innerHTML = value[1];
} else { // only value, text=value
option.value = value;
option.innerHTML = value;
}
select.appendChild(option);
}
}

View File

@ -2,6 +2,7 @@ class ContextualMenu {
constructor(options) {
this.options = options;
this.trigger_container = options.trigger_container;
this.container = options.container;
this.bootstrap_popover = options.bootstrap_popover;
this.right_click = options.right_click;
this.has_been_shown_once = false;
@ -90,7 +91,7 @@ class ContextualMenu {
__toggleMenu(x, y, hide) {
var that = this;
if(this.__is_shown || hide) {
that.menu.style.visibility = 'hidden';
this.menu.style.visibility = 'hidden';
} else {
this.menu.style.left = x+'px';
this.menu.style.top = y+'px';
@ -103,7 +104,7 @@ class ContextualMenu {
var div = document.createElement('div');
div.classList.add("contextual-menu");
div.classList.add("contextual-menu-styling");
document.body.appendChild(div);
this.container.appendChild(div);
// register on click for the trigger_container
var that = this;
if (this.right_click) {
@ -130,16 +131,17 @@ class ContextualMenu {
__create_menu_div_bootstrap_popover() {
var div = document.createElement('div');
div.classList.add("contextual-menu");
document.body.appendChild(div);
this.container.appendChild(div);
var that = this;
this.trigger_container.tabIndex = 0; // required for the popover focus feature
var additional_styling = this.options.style === undefined ? "" : this.options.style;
$(this.trigger_container).popover({
container: 'body',
html: true,
placement: "bottom",
content: function () {return $(that.menu); }, // return contextual menu htlm
content: function () {return $(that.menu); }, // return contextual menu html
trigger: "manual",
template: '<div class="popover" id="popover-contextual-menu-'+this.trigger_container.id+'" role="tooltip"><div class="arrow"></div></h3><div class="popover-content"></div></div>'
template: '<div class="popover" id="popover-contextual-menu-'+this.trigger_container.id+'" role="tooltip" style="'+additional_styling+'"><div class="arrow"></div></h3><div class="popover-content"></div></div>'
})
// Overwrite the default popover behavior: hidding cause the popover to be detached from the DOM, making impossible to fetch input values in the form

View File

@ -1,5 +1,4 @@
$(document).ready( function() {
var currentMousePos = { x: -1, y: -1 };
$(document).mousemove(function(event) {
currentMousePos.x = event.pageX;
@ -11,12 +10,32 @@ $(document).ready( function() {
height = $(window).height() - 160 - margin.top - margin.bottom;
var menu_x_buffer_ = width - 150;
var menu_y_buffer = height - 100;
$('.menu-container').css('left', '200px');
$('#hover-menu-container').css('top', '50px');
if ($('#graph_init').data('ajax')) {
$('.menu-container').css('left', '20px');
$('#hover-menu-container').css('top', '20px');
$('#selected-menu-container').css('top', '270px');
} else {
$('.menu-container').css('left', '200px');
$('#hover-menu-container').css('top', '50px');
$('#selected-menu-container').css('top', '400px');
}
$('#hover-menu-container').css('z-index', 0);
$('#selected-menu-container').css('top', '400px');
$('#selected-menu-container').css('z-index', 1);
$('#fullscreen-btn-correlation').click(function() {
var network_div = $('#correlationgraph_div');
var fullscreen_enabled = !network_div.data('fullscreen');
network_div.data('fullscreen', fullscreen_enabled);
var height_val = fullscreen_enabled == true ? "calc(100vh - 42px - 42px - 10px)" : "500px";
network_div.css("height", height_val);
network_div[0].scrollIntoView({
behavior: "smooth",
});
});
var root;
var highlighted;
@ -162,7 +181,7 @@ $(document).ready( function() {
}
}
)
.append("xhtml:body")
.append("xhtml:div")
.html(function (d) {
var result = 'fa-' + d.imgClass;
if (d.type == 'galaxy' || d.type == 'tag') result = 'fa-2x ' + result;

View File

@ -22,6 +22,7 @@ mapping_root_id_to_type[root_id_tag] = 'tag';
mapping_root_id_to_type[root_id_keyType] = 'keyType';
var root_node_x_pos = 800;
var cluster_expand_threshold = 100;
var nodes_ask_threshold = 300;
/*=========
* CLASSES
@ -65,6 +66,9 @@ class EventGraph {
this.cluster_index = 0; // use to get uniq cluster ID
this.clusters = [];
this.extended_event_color_mapping = {};
this.extended_event_points = {};
this.network = new vis.Network(container, data, this.network_options);
this.add_unreferenced_root_node();
@ -89,6 +93,26 @@ class EventGraph {
this.network.on("dragEnd", function (params) {
eventGraph.physics_state($('#checkbox_physics_enable').prop("checked"));
});
// create Hull for extending events
this.network.on("beforeDrawing", function (ctx) {
if (that.scope_name != "Reference" || !that.canDrawHull) {
return;
}
for (var event_id in that.extended_event_points) {
if (that.extended_event_color_mapping[event_id] === undefined) {
eventGraph.extended_event_color_mapping[event_id] = getRandomColor(event_id);
}
var chosen_color = eventGraph.extended_event_color_mapping[event_id];
var nodes = that.network.getPositions(that.extended_event_points[event_id]);
nodes = $.map(nodes, function(value, index) { // object to array
return [value];
});
drawExtendedEventHull(ctx, nodes, chosen_color, "Event "+event_id);
}
});
}
// Util
@ -124,6 +148,8 @@ class EventGraph {
var menu_scope = new ContextualMenu({
trigger_container: document.getElementById("network-scope"),
bootstrap_popover: true,
style: "z-index: 1",
container: document.getElementById("eventgraph_div")
});
menu_scope.add_select({
id: "select_graph_scope",
@ -162,7 +188,9 @@ class EventGraph {
init_physic_menu() {
var menu_physic = new ContextualMenu({
trigger_container: document.getElementById("network-physic"),
bootstrap_popover: true
bootstrap_popover: true,
style: "z-index: 1",
container: document.getElementById("eventgraph_div")
});
menu_physic.add_select({
id: "select_physic_solver",
@ -200,7 +228,9 @@ class EventGraph {
init_display_menu() {
var menu_display = new ContextualMenu({
trigger_container: document.getElementById("network-display"),
bootstrap_popover: true
bootstrap_popover: true,
style: "z-index: 1",
container: document.getElementById("eventgraph_div")
});
menu_display.add_select({
id: "select_display_layout",
@ -283,7 +313,9 @@ class EventGraph {
init_filter_menu() {
var menu_filter = new ContextualMenu({
trigger_container: document.getElementById("network-filter"),
bootstrap_popover: true
bootstrap_popover: true,
style: "z-index: 1",
container: document.getElementById("eventgraph_div")
});
menu_filter.add_action_table({
id: "table_attr_presence",
@ -308,6 +340,29 @@ class EventGraph {
data: [],
});
menu_filter.create_divider(3);
menu_filter.add_action_table({
id: "table_tag_presence",
container: menu_filter.menu,
title: "Filter on Tag presence",
header: ["Relation", "Tag"],
control_items: [
{
DOMType: "select",
item_options: {
options: ["Contains", "Do not contain"]
}
},
{
DOMType: "select",
item_options: {
id: "table_control_select_tag_presence",
options: []
}
},
],
data: [],
});
menu_filter.create_divider(3);
menu_filter.add_action_table({
id: "table_attr_value",
container: menu_filter.menu,
@ -351,7 +406,9 @@ class EventGraph {
init_canvas_menu() {
var menu_canvas = new ContextualMenu({
trigger_container: document.getElementById("eventgraph_network"),
right_click: true
right_click: true,
style: "z-index: 1",
container: document.getElementById("eventgraph_div")
});
menu_canvas.add_button({
label: "View/Edit",
@ -402,8 +459,9 @@ class EventGraph {
get_filtering_rules() {
var rules_presence = eventGraph.menu_filter.items["table_attr_presence"].get_data();
var rules_tag_presence = eventGraph.menu_filter.items["table_tag_presence"].get_data();
var rules_value = eventGraph.menu_filter.items["table_attr_value"].get_data();
var rules = { presence: rules_presence, value: rules_value };
var rules = { presence: rules_presence, tag_presence: rules_tag_presence, value: rules_value };
return rules;
}
// Graph interaction
@ -434,11 +492,14 @@ class EventGraph {
this.edges.clear();
if (hard) {
this.backup_connection_edges = {};
this.extended_event_points = {};
this.extended_event_color_mapping = {};
}
}
update_graph(data) {
setTimeout(function() { eventGraph.network_loading(true, loadingText_creating); });
var that = this;
that.network_loading(true, loadingText_creating);
// New nodes will be automatically added
// removed references will be deleted
@ -447,10 +508,17 @@ class EventGraph {
var newNodeIDs = [];
for(var node of data.items) {
var group, label;
if (node.event_id != scope_id) { // add node ids of extended event
if (that.extended_event_points[node.event_id] === undefined) {
that.extended_event_points[node.event_id] = [];
}
that.extended_event_points[node.event_id].push(node.id);
}
if ( node.node_type == 'object' ) {
var group = 'object';
var label = dataHandler.generate_label(node);
var striped_value = this.strip_text_value(label);
var striped_value = that.strip_text_value(label);
node_conf = {
id: node.id,
uuid: node.uuid,
@ -462,7 +530,7 @@ class EventGraph {
icon: {
color: getRandomColor(),
face: 'FontAwesome',
code: this.get_FA_icon(node['meta-category']),
code: that.get_FA_icon(node['meta-category']),
}
};
dataHandler.mapping_value_to_nodeID.set(striped_value, node.id);
@ -493,8 +561,8 @@ class EventGraph {
dataHandler.mapping_value_to_nodeID.set(striped_value, node.id);
} else if (node.node_type == 'keyType') {
group = 'keyType';
label = this.scope_keyType + ": " + node.label;
var striped_value = this.strip_text_value(label);
label = that.scope_keyType + ": " + node.label;
var striped_value = that.strip_text_value(label);
node_conf = {
id: node.id,
label: striped_value,
@ -505,7 +573,7 @@ class EventGraph {
} else {
group = 'attribute';
label = node.type + ': ' + node.label;
var striped_value = this.strip_text_value(label);
var striped_value = that.strip_text_value(label);
node_conf = {
id: node.id,
uuid: node.uuid,
@ -521,7 +589,7 @@ class EventGraph {
newNodeIDs.push(node.id);
}
// check if nodes got deleted
var old_node_ids = this.nodes.getIds();
var old_node_ids = that.nodes.getIds();
for (var old_id of old_node_ids) {
// Ignore root node
if (old_id == "rootNode:attribute" || old_id == "rootNode:object" || old_id == "rootNode:tag" || old_id == "rootNode:keyType") {
@ -529,11 +597,11 @@ class EventGraph {
}
// This old node got removed
if (newNodeIDs.indexOf(old_id) == -1) {
this.nodes.remove(old_id);
that.nodes.remove(old_id);
}
}
this.nodes.update(newNodes);
that.nodes.update(newNodes);
// New relations will be automatically added
// removed references will be deleted
@ -554,42 +622,46 @@ class EventGraph {
newRelationIDs.push(rel.id);
}
// check if nodes got deleted
var old_rel_ids = this.edges.getIds();
var old_rel_ids = that.edges.getIds();
for (var old_id of old_rel_ids) {
// This old node got removed
if (newRelationIDs.indexOf(old_id) == -1) {
this.edges.remove(old_id);
that.edges.remove(old_id);
}
}
this.edges.update(newRelations);
that.edges.update(newRelations);
this.remove_root_nodes();
if (this.scope_name == 'Reference') {
this.add_unreferenced_root_node();
// links unreferenced attributes and object to root nodes
if (this.first_draw) {
this.link_not_referenced_nodes();
this.first_draw = !this.first_draw
}
} else if (this.scope_name == 'Tag') {
this.add_tag_root_node();
// links untagged attributes and object to root nodes
if (this.first_draw) {
this.link_not_referenced_nodes();
this.first_draw = !this.first_draw
}
} else if (this.scope_name == 'Distribution') {
} else if (this.scope_name == 'Correlation') {
} else {
this.add_keyType_root_node();
if (this.first_draw) {
this.link_not_referenced_nodes();
this.first_draw = !this.first_draw
that.remove_root_nodes();
// do not clusterize if the network is filtered
if (!that.is_filtered) {
if (that.scope_name == 'Reference') {
that.add_unreferenced_root_node();
// links unreferenced attributes and object to root nodes
if (that.first_draw) {
that.link_not_referenced_nodes();
that.first_draw = !that.first_draw
}
} else if (that.scope_name == 'Tag') {
that.add_tag_root_node();
// links untagged attributes and object to root nodes
if (that.first_draw) {
that.link_not_referenced_nodes();
that.first_draw = !that.first_draw
}
} else if (that.scope_name == 'Distribution') {
} else if (that.scope_name == 'Correlation') {
} else {
that.add_keyType_root_node();
if (that.first_draw) {
that.link_not_referenced_nodes();
that.first_draw = !that.first_draw
}
}
}
this.network_loading(false, "");
eventGraph.canDrawHull = true;
that.network_loading(false, "");
}
strip_text_value(text) {
@ -1030,9 +1102,10 @@ class DataHandler {
return label;
}
update_available_object_references(available_object_references) {
update_filtering_selectors(available_object_references, available_tags) {
eventGraph.menu_display.add_options("select_display_object_field", available_object_references);
eventGraph.menu_filter.items["table_attr_presence"].add_options("table_control_select_attr_presence", available_object_references);
eventGraph.menu_filter.items["table_tag_presence"].add_options("table_control_select_tag_presence", available_tags);
eventGraph.menu_filter.items["table_attr_value"].add_options("table_control_select_attr_value", available_object_references);
}
@ -1045,6 +1118,7 @@ class DataHandler {
payload.filtering = filtering_rules;
payload.keyType = keyType;
var extended_text = dataHandler.extended_event ? "extended:1" : "";
eventGraph.canDrawHull = false;
$.ajax({
url: "/events/"+dataHandler.get_scope_url()+"/"+scope_id+"/"+extended_text+"/event.json",
dataType: 'json',
@ -1058,10 +1132,22 @@ class DataHandler {
eventGraph.first_draw = true;
// update object state
var available_object_references = Object.keys(data.existing_object_relation);
dataHandler.update_available_object_references(available_object_references);
var available_tags = Object.keys(data.existing_tags);
var available_tags = $.map(data.existing_tags, function(value, index) { // object to array
return [[index, value]];
});
dataHandler.update_filtering_selectors(available_object_references, available_tags);
dataHandler.available_rotation_key = data.available_rotation_key;
eventGraph.menu_scope.add_options("input_graph_scope_jsonkey", dataHandler.available_rotation_key);
eventGraph.update_graph(data);
if (data.items.length < nodes_ask_threshold) {
eventGraph.update_graph(data);
} else if (data.items.length > nodes_ask_threshold && confirm("The network contains a lot of nodes, displaying it may slow down your browser. Continue?")) {
eventGraph.update_graph(data);
} else {
eventGraph.network_loading(false, "");
$("#eventgraph_toggle").click();
}
if ( stabilize === undefined || stabilize) {
eventGraph.reset_view_on_stabilized();
}
@ -1139,7 +1225,6 @@ class MispInteraction {
add_reference(edgeData, callback) {
var that = mispInteraction;
//var uuid = dataHandler.mapping_attr_id_to_uuid.get(edgeData.to);
var uuid = that.nodes.get(edgeData.to).uuid;
if (!that.can_create_reference(edgeData.from) || !that.can_be_referenced(edgeData.to)) {
return;
@ -1233,6 +1318,84 @@ class MispInteraction {
/*=========
* UTILS
* ========*/
function drawExtendedEventHull(ctx, nodes, color, text) {
ctx.fillStyle = color+'88';
var hull = getHullFromPoints(nodes);
var start = hull[0];
var end = hull[hull.length-1];
var prev = start;
ctx.beginPath();
ctx.moveTo(start.x, start.y);
for (var i=1; i<hull.length; i++) {
var cur = hull[i];
ctx.lineTo(cur.x,cur.y);
prev = cur;
}
ctx.moveTo(end.x, end.y);
var centerX = (end.x+start.x)/2;
var centerY = (end.y+start.y)/2;
ctx.quadraticCurveTo(centerX,centerY,start.x,start.y);
ctx.fill();
var centroid = getCentroid(hull);
ctx.beginPath();
ctx.font="30px Verdana";
ctx.fillStyle = getTextColour(color);
ctx.fillText(text, centroid.x, centroid.y);
}
function orientation(p, q, r) {
var val = (q.y - p.y) * (r.x - q.x) -
(q.x - p.x) * (r.y - q.y);
if (val == 0) {
return 0; // collinear
}
return val > 0 ? 1 : 2; // clock or counterclock wise
}
// Implementation of Gift wrapping algorithm (jarvis march in 2D)
// Inspired from https://www.geeksforgeeks.org/convex-hull-set-1-jarviss-algorithm-or-wrapping/
function getHullFromPoints(points) {
var n = points.length;
var l = 0;
var hull = [];
// get leftmost point
for (var i=0; i<n; i++) {
l = points[l].x > points[i].x ? l : i;
}
var p = l;
var q;
do {
hull.push(points[p]);
q = (p+1) % n;
for (var i=0; i<n; i++) {
if (orientation(points[p], points[i], points[q]) == 2) {
q = i;
}
}
p = q;
} while (p != l);
return hull;
}
function getCentroid(coordList) {
var cx = 0;
var cy = 0;
var a = 0;
for (var i=0; i<coordList.length; i++) {
var ci = coordList[i];
var cj = i+1 == coordList.length ? coordList[0] : coordList[i+1]; // j = i+1 AND loop around
var mul = (ci.x*cj.y - cj.x*ci.y);
cx += (ci.x + cj.x)*mul;
cy += (ci.y + cj.y)*mul;
a += mul;
}
a = a / 2;
cx = cx / (6*a);
cy = cy / (6*a);
return {x: cx, y: cy};
}
function getRandomColor() {
var letters = '0123456789ABCDEF';
var color = '#';
@ -1300,7 +1463,7 @@ function enable_interactive_graph() {
html: true,
});
generate_background_shortcuts(shortcut_text);
$('.fullscreen-btn').click(function() {
$('#fullscreen-btn-eventgraph').click(function() {
var network_div = $('#eventgraph_div');
var fullscreen_enabled = !network_div.data('fullscreen');
network_div.data('fullscreen', fullscreen_enabled);

View File

@ -1268,7 +1268,7 @@ function simplePopup(url) {
error:function() {
$(".loading").hide();
$("#gray_out").fadeOut();
showMessage('fail', 'Could not fetch the given GnuPG key.');
showMessage('fail', 'Something went wrong - the queried function returned an exception. Contact your administrator for further details (the exception has been logged).');
},
url: url,
});
@ -3284,6 +3284,25 @@ function changeObjectReferenceSelectOption() {
}
}
function previewEventBasedOnUuids() {
var currentValue = $("#EventExtendsUuid").val();
if (currentValue == '') {
$('#extended_event_preview').hide();
} else {
$.ajax({
url: "/events/getEventInfoById/" + currentValue,
type: "get",
error: function() {
$('#extended_event_preview').hide();
},
success: function(data) {
$('#extended_event_preview').show();
$('#extended_event_preview').html(data);
}
});
}
}
$('.add_object_attribute_row').click(function() {
var template_id = $(this).data('template-id');
var object_relation = $(this).data('object-relation');